podman run -v "$PWD:/repo:z" quay.io/validatedpatterns/patternizer init
From slim common to no common
Retiring the common
Directory with a Smarter Makefile
We’re simplifying our Validated Patterns workflow in a big way: the common
directory is going away.
Why does this matter? Fewer moving parts, leaner repositories, and updates that flow automatically to every pattern. Instead of juggling wrapper scripts and redundant logic across repos, everything now runs through a centralized, smarter Makefile.
This post explains why we made the change, how it benefits you, and what you need to do to start using this streamlined approach.
The Old Way: A common
Directory in Every Pattern
Originally, every pattern repository included a common
directory. As the name suggests, it housed shared resources that didn’t vary between patterns, primarily Helm charts and Ansible playbooks. While this worked, it meant that any update to a common script or chart had to be manually propagated across every single pattern repository.
The Evolution: Creating Central Hubs of Logic
To improve this, we began breaking the common
directory apart in a few key stages:
Centralizing Helm charts: We leveraged Argo CD’s multi-source config feature to publish and consume our Helm charts from a central repository, now available at https://charts.validatedpatterns.io/.
Creating an Ansible Collection: We moved the common Ansible playbooks into their own dedicated Git repository and published them as a collection:
rhvp.cluster_utils
.Introducing the
utility-container
: We created theutility-container
. This container image bundles all our essential tools—therhvp.cluster_utils
Ansible collection, other collections likekubernetes.core
, and common binaries (oc
,helm
, etc.).
This evolution gave us the ubiquitous pattern.sh
script. By running ./pattern.sh make install
, you use the utility-container
to deploy a pattern to an OpenShift cluster. This powerful script ensures you have the correct versions of all tools without needing to install them on your local machine.
At this point, the common
directory was much slimmer, containing only the Makefile and some shell scripts that were mostly just thin wrappers for Ansible playbook calls.
Today’s Change: Removing the common
Directory Entirely
After slimming things down, we realized we could take one final step. We moved the logic from the few remaining wrapper scripts directly into our Ansible playbooks. This made the common/scripts
directory redundant, and at that point, there was no reason to have the common
directory at all.
Now, instead of a git subtree, patterns can use a single, powerful Makefile.
This new approach has several key advantages:
Lighter Repos: Pattern repositories no longer need the
common
directory, making them much smaller.Centralized Updates: When we fix a bug or add a feature to the common logic, we update it in one place. The changes are automatically available to all patterns using the new Makefile—no more updating dozens of repos!
Simpler Makefiles: Most patterns now only need a single line in their Makefile:
include Makefile-common
.
You can always set the environment variable PATTERN_UTILITY_CONTAINER inside or outside of pattern.sh to a specific semver tag to manually control the updates. (For example, you can set it to quay.io/validatedpatterns/utility-container:v1.0.0 .) |
The Adjustment: Discoverability
One change to be aware of is discoverability. Without a local common
directory full of scripts, it may not be immediately obvious which make
targets exist or which environment variables you can tweak.
We’ve solved this in two ways:
Built-in docs: Every
Makefile-common
includes a link back to this post, so the reference is always at your fingertips.make help
target: Run./pattern.sh make help
to see the list of available targets directly in your terminal.
Getting Started with the New Structure
Adopting the new approach is straightforward whether you’re bootstrapping a brand-new pattern repository or upgrading an existing one.
New repositories
From the root of your repo, run:
Add secrets scaffolding at any time with:
podman run -v "$PWD:/repo:z" quay.io/validatedpatterns/patternizer init --with-secrets
Review the changes (
values-*.yaml
,pattern.sh
,Makefile
,Makefile-common
), then commit.Try it out:
./pattern.sh make show
./pattern.sh make install
Existing repositories
Move from the legacy common
structure to the new Makefile-driven setup with a single command:
podman run -v "$PWD:/repo:z" quay.io/validatedpatterns/patternizer upgrade
What this does:
* Removes common
(if present)
* Removes ./pattern.sh
(symlink or file), then copies the latest script
* Copies Makefile-common
to the repo root
* Updates your Makefile
:
If it already contains include Makefile-common
, it is left unchanged
If it exists but lacks the include, the include is prepended to the first line
** If it doesn’t exist, a default Makefile
is created
If you prefer to fully replace your Makefile
with the default version, run:
podman run -v "$PWD:/repo:z" quay.io/validatedpatterns/patternizer upgrade --replace-makefile
After upgrading, commit the changes and use the targets described below (for example, ./pattern.sh make show
or ./pattern.sh make install
).
Using Makefile-common
Patterns shipped without the common
dir now have a Makefile-common
that provides consistent developer and user targets.
How commands are executed
All targets are thin wrappers around ansible playbooks defined in rhvp.cluster_utils. By default, ANSIBLE_STDOUT_CALLBACK
is set to null
which means all of the noise from ansible is suppressed. If you need it, set that variable in your environment export ANSIBLE_STDOUT_CALLBACK=default
or just directly use it as part of calling the make targets (ANSIBLE_STDOUT_CALLBACK=default ./pattern.sh make <target>
).
When you are getting started with secrets it can be difficult to debug what may be wrong in your secret values files. To aid debugging, you could try running:
ANSIBLE_STDOUT_CALLBACK=default EXTRA_PLAYBOOK_OPTS='-e hide_sensitive_output="false" -vvv' ./pattern.sh make load-secrets
DO NOT do this in CI environments as it will expose your secrets…Also, for auto-generated secrets they will be different each time you run that command. |
Overriding variables
All pattern settings can be overridden in two ways:
Ansible variable
Via the env var
EXTRA_PLAYBOOK_OPTS
, e.g.:EXTRA_PLAYBOOK_OPTS="-e target_branch=myfeature -e target_origin=upstream" ./pattern.sh make show
Environment variable
TARGET_BRANCH=myfeature TARGET_ORIGIN=upstream ./pattern.sh make show
Precedence:
-e var=value
(CLI/inventory) → environment variables → defaults.
Targets
make show
Renders the Helm template of the pattern-install chart to show the manifests that would be applied by make install
.
It does not render the namespaces, subscriptions, and projects managed by the Patterns Operator via the clustergroup-chart. It only shows: |
the Patterns Operator subscription and associated configmap
the
Pattern
CR which enables installation of the pattern
Overrides
Ansible var | Environment var | Purpose | Default |
---|---|---|---|
|
| Directory containing the pattern repo to be shown/installed | current working directory |
|
| Name for the Helm release / Pattern CR | basename of |
|
| OCI URL for the install chart |
|
|
| Git branch used for the repo |
|
|
| Git remote for the branch |
|
|
| Which clustergroup to install (if different from | value of |
|
| Secret name for private repos (docs) | empty |
|
| Namespace of the secret | empty |
|
| Extra args to pass to | empty |
|
| Path to a file containing a UUID used for analytics. Ensures team-internal deploys can be filtered from external ones. Users generally do not need to override this. |
|
make operator-deploy
Performs validations and applies the manifests rendered by make show
to your cluster.
The make install
target is usually preferred for new installs, since it also handles secret loading.
The primary use case for operator-deploy
is updating an existing pattern to point to a different git repo, revision, or origin—without refreshing secrets.
Overrides
Same as make show
plus:
Ansible var | Environment var | Purpose | Default |
---|---|---|---|
|
| Whether git origin reachability should be validated | false |
make install
Identical to make operator-deploy
up through applying the pattern manifests.
Afterward, if global.secretLoader.disabled
is not set or is false
in values-global.yaml
, this target also runs make load-secrets
.
If your pattern doesn’t use secrets, this is functionally identical to
operator-deploy
.If your pattern does use secrets,
install
ensures they are loaded into Vault in the cluster.
Overrides
Same as make operator-deploy
.
make validate-prereq
Validates prerequisites needed for installation:
Confirms the
pattern_name
matches the value invalues-global.yaml
On host: checks for
python-kubernetes
and thekubernetes.core
Ansible collectionIn container: ensures
.main.multiSourceConfig.enabled
is set totrue
invalues-global.yaml
Typically run as part of make install
/make operator-deploy
but can be invoked manually.
make validate-origin
Ensures the pattern’s git origin and branch are reachable. Invoked automatically during install unless explicitly disabled, but can also be run standalone.
make validate-cluster
Ensures you are logged into an OpenShift cluster and that a storage class is available. This prevents failed installs due to missing cluster prerequisites. Invoked automatically during install but may be run manually for a quick sanity check.
make load-secrets
Loads secrets into Vault in the cluster if global.secretLoader.disabled
is not set or is false
.
Run automatically as part of make install
, but can also be run independently if you need to reload secrets.
Wrapping Up
By retiring the common
directory, we’ve eliminated duplication and made every pattern repo smaller, simpler, and easier to maintain.
With a single, centralized Makefile: * Updates flow automatically across all patterns * Repositories stay lean and uncluttered * Developers get a consistent, predictable experience
Ready to try it? Run:
podman run -v "$PWD:/repo:z" quay.io/validatedpatterns/patternizer upgrade
and commit the changes. Your repo will instantly be on the new path.