Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Patternizer

![Version: 2.2.0](https://img.shields.io/badge/Version-2.2.0-informational?style=flat-square)
![Version: 2.3.0](https://img.shields.io/badge/Version-2.3.0-informational?style=flat-square)
[![Quay Repository](https://img.shields.io/badge/Quay.io-patternizer-blue?logo=quay)](https://quay.io/repository/validatedpatterns/patternizer)
[![CI Pipeline](https://github.com/validatedpatterns/patternizer/actions/workflows/build-push.yaml/badge.svg?branch=main)](https://github.com/validatedpatterns/patternizer/actions/workflows/build-push.yaml)

Expand Down Expand Up @@ -132,6 +132,11 @@ What upgrade does:
- If no `Makefile` exists: copies the default `Makefile`
- If a `Makefile` exists and already contains `include Makefile-common` anywhere: leaves it unchanged
- Otherwise: prepends `include Makefile-common` to the first line so your existing targets are preserved
- Prints a deprecation warning if `values-global.yaml` still uses `main.clusterGroupName`, which the Validated Patterns operator deprecated in favor of `main.variant` in version 0.0.78 (`upgrade` does not modify the file; run `patternizer init` to migrate automatically)
- Prints a deprecation warning if the pattern does not use the [variants folder structure](https://validatedpatterns.io/blog/2026-08-04-variants-folder-structure/) (introduced in operator version 0.0.78) — that is, if `variants/<variant>/values-<variant>.yaml` is missing for the configured variant (`upgrade` does not move any files; run `patternizer init` to migrate automatically)
- Prints a warning if `global.singleArgoCD` is not set in `values-global.yaml`, recommending you set `global.singleArgoCD: true` if possible (see the [single ArgoCD instance](https://validatedpatterns.io/blog/2026-06-04-single-argocd-option/) blog post)

These warnings are written to stderr, grouped after the success message, so they stand out as a call to action.

### Understanding Secrets Management

Expand All @@ -148,7 +153,7 @@ For more details on how secrets work in the framework, see the [Secrets Manageme
Running `patternizer init` creates the following:

- `values-global.yaml`: Global pattern configuration.
- `values-<cluster_group>.yaml`: Cluster group-specific values.
- `variants/<variant>/values-<variant>.yaml`: Variant-specific (cluster group) values, using the [variants folder structure](https://validatedpatterns.io/blog/2026-08-04-variants-folder-structure/). A legacy root-level `values-<variant>.yaml` is migrated into this folder automatically.
- `pattern.sh`: A utility script for common pattern operations (`install`, `upgrade`, etc.).
- `Makefile`: A simple Makefile that includes `Makefile-common`.
- `Makefile-common`: The core Makefile with all pattern-related targets.
Expand Down
46 changes: 25 additions & 21 deletions src/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ var _ = Describe("patternizer init", func() {
},
},
Main: types.Main{
ClusterGroupName: "prod",
Variant: "prod",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -91,7 +91,7 @@ var _ = Describe("patternizer init", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "prod",
Expand Down Expand Up @@ -129,7 +129,7 @@ var _ = Describe("patternizer init", func() {
},
},
Main: types.Main{
ClusterGroupName: "prod",
Variant: "prod",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -140,7 +140,7 @@ var _ = Describe("patternizer init", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedNamespace := filepath.Base(tempDir)
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Expand Down Expand Up @@ -189,7 +189,7 @@ var _ = Describe("patternizer init", func() {
},
},
Main: types.Main{
ClusterGroupName: "test",
Variant: "test",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -200,7 +200,7 @@ var _ = Describe("patternizer init", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-test.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "test", "values-test.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "test",
Expand Down Expand Up @@ -238,7 +238,7 @@ var _ = Describe("patternizer init", func() {
},
},
Main: types.Main{
ClusterGroupName: "test",
Variant: "test",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -249,7 +249,7 @@ var _ = Describe("patternizer init", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-test.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "test", "values-test.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "test",
Expand All @@ -270,6 +270,10 @@ var _ = Describe("patternizer init", func() {
}
verifyClusterGroupValues(clusterGroupValuesFile, &expectedClusterGroupValues)
})

It("should remove the legacy root-level values file after migration", func() {
Expect(filepath.Join(tempDir, "values-test.yaml")).NotTo(BeAnExistingFile())
})
})

for _, alias := range []string{"create", "bootstrap"} {
Expand Down Expand Up @@ -308,7 +312,7 @@ var _ = Describe("patternizer init", func() {
},
},
Main: types.Main{
ClusterGroupName: "test",
Variant: "test",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand Down Expand Up @@ -348,7 +352,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
},
},
Main: types.Main{
ClusterGroupName: "prod",
Variant: "prod",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -359,7 +363,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "prod",
Expand Down Expand Up @@ -428,7 +432,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
},
},
Main: types.Main{
ClusterGroupName: "prod",
Variant: "prod",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -439,7 +443,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedNamespace := filepath.Base(tempDir)
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Expand Down Expand Up @@ -518,7 +522,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
},
},
Main: types.Main{
ClusterGroupName: "test",
Variant: "test",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -529,7 +533,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-test.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "test", "values-test.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "test",
Expand Down Expand Up @@ -595,7 +599,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
},
},
Main: types.Main{
ClusterGroupName: "prod",
Variant: "prod",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -606,7 +610,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
})

It("should update the clustergroup values file to include secrets", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedNamespace := filepath.Base(tempDir)
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Expand Down Expand Up @@ -686,7 +690,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
},
},
Main: types.Main{
ClusterGroupName: "test",
Variant: "test",
MultiSourceConfig: types.MultiSourceConfig{
Enabled: true,
ClusterGroupChartVersion: "0.9.*",
Expand All @@ -697,7 +701,7 @@ var _ = Describe("patternizer init --with-secrets", func() {
})

It("should create an appropriate clustergroup values file", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-test.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "test", "values-test.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "test",
Expand Down Expand Up @@ -788,7 +792,7 @@ clusterGroup:
})

It("should migrate list-style namespaces to map-style", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "prod",
Expand Down Expand Up @@ -833,7 +837,7 @@ clusterGroup:
})

It("should preserve map-style namespaces", func() {
clusterGroupValuesFile := filepath.Join(tempDir, "values-prod.yaml")
clusterGroupValuesFile := filepath.Join(tempDir, "variants", "prod", "values-prod.yaml")
expectedClusterGroupValues := types.ValuesClusterGroup{
ClusterGroup: types.ClusterGroup{
Name: "prod",
Expand Down
52 changes: 52 additions & 0 deletions src/cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,58 @@ func runUpgrade(replaceMakefile bool) error {
return fmt.Errorf("error installing skills: %w", err)
}

warnings, err := collectUpgradeWarnings(repoRoot)
if err != nil {
return err
}

fmt.Printf("Successfully upgraded pattern repository in %s\n", repoRoot)

emitUpgradeWarnings(warnings)
return nil
}

// collectUpgradeWarnings inspects the pattern's values-global.yaml and returns a list of
// compact deprecation warnings that should be surfaced to the user as a call to action.
func collectUpgradeWarnings(repoRoot string) ([]string, error) {
var warnings []string

deprecated, err := pattern.UsesDeprecatedClusterGroupName(repoRoot)
if err != nil {
return nil, fmt.Errorf("error checking for deprecated fields: %w", err)
}
if deprecated {
warnings = append(warnings, "WARNING: values-global.yaml uses deprecated 'main.clusterGroupName' (operator 0.0.78). Set 'main.variant' instead.")
}

variant, folderMissing, err := pattern.VariantFolderFileMissing(repoRoot)
if err != nil {
return nil, fmt.Errorf("error checking for variants folder structure: %w", err)
}
if folderMissing {
warnings = append(warnings, fmt.Sprintf("WARNING: not using the variants folder structure (variants/%s/values-%s.yaml not found; operator 0.0.78). See https://validatedpatterns.io/blog/2026-08-04-variants-folder-structure/", variant, variant))
}

missingSingleArgoCD, err := pattern.MissingSingleArgoCD(repoRoot)
if err != nil {
return nil, fmt.Errorf("error checking for singleArgoCD: %w", err)
}
if missingSingleArgoCD {
warnings = append(warnings, "WARNING: 'global.singleArgoCD' is not set. Set 'global.singleArgoCD: true' if possible. See https://validatedpatterns.io/blog/2026-06-04-single-argocd-option/")
}

return warnings, nil
}

// emitUpgradeWarnings prints deprecation warnings to stderr as a grouped block, separated
// from the normal stdout logs so they stand out as a call to action.
func emitUpgradeWarnings(warnings []string) {
if len(warnings) == 0 {
return
}

fmt.Fprintln(os.Stderr)
for _, warning := range warnings {
fmt.Fprintln(os.Stderr, warning)
}
}
Loading
Loading