Skip to content

Commit 49a3390

Browse files
committed
ci: adding matrix run to cli so its easy to test the ci run from the local machine
1 parent a7a8674 commit 49a3390

File tree

8 files changed

+2162
-25
lines changed

8 files changed

+2162
-25
lines changed
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"scripts/camunda-core/pkg/logging"
8+
"scripts/deploy-camunda/config"
9+
"scripts/deploy-camunda/matrix"
10+
"scripts/prepare-helm-values/pkg/env"
11+
"strings"
12+
13+
"github.com/spf13/cobra"
14+
)
15+
16+
// newMatrixCommand creates the matrix parent command with list and run subcommands.
17+
func newMatrixCommand() *cobra.Command {
18+
matrixCmd := &cobra.Command{
19+
Use: "matrix",
20+
Short: "Generate and run the CI test matrix across all active chart versions",
21+
}
22+
23+
matrixCmd.AddCommand(newMatrixListCommand())
24+
matrixCmd.AddCommand(newMatrixRunCommand())
25+
26+
return matrixCmd
27+
}
28+
29+
// newMatrixListCommand creates the "matrix list" subcommand.
30+
func newMatrixListCommand() *cobra.Command {
31+
var (
32+
versions []string
33+
includeDisabled bool
34+
scenarioFilter string
35+
flowFilter string
36+
outputFormat string
37+
platform string
38+
repoRoot string
39+
)
40+
41+
cmd := &cobra.Command{
42+
Use: "list",
43+
Short: "List the CI test matrix for all active chart versions",
44+
Long: `List the full CI test matrix generated from chart-versions.yaml,
45+
ci-test-config.yaml (PR scenarios only), and permitted-flows.yaml.
46+
47+
This command does not require cluster access.`,
48+
RunE: func(cmd *cobra.Command, args []string) error {
49+
repoRoot = resolveRepoRoot(repoRoot)
50+
if repoRoot == "" {
51+
return fmt.Errorf("--repo-root is required (or set repoRoot in config)")
52+
}
53+
54+
entries, err := matrix.Generate(repoRoot, matrix.GenerateOptions{
55+
Versions: versions,
56+
IncludeDisabled: includeDisabled,
57+
})
58+
if err != nil {
59+
return err
60+
}
61+
62+
entries = matrix.Filter(entries, matrix.FilterOptions{
63+
ScenarioFilter: scenarioFilter,
64+
FlowFilter: flowFilter,
65+
Platform: platform,
66+
})
67+
68+
output, err := matrix.Print(entries, outputFormat)
69+
if err != nil {
70+
return err
71+
}
72+
fmt.Fprintln(os.Stdout, output)
73+
return nil
74+
},
75+
}
76+
77+
f := cmd.Flags()
78+
f.StringSliceVar(&versions, "versions", nil, "Limit to specific chart versions (comma-separated, e.g., 8.8,8.9)")
79+
f.BoolVar(&includeDisabled, "include-disabled", false, "Include disabled scenarios in the output")
80+
f.StringVar(&scenarioFilter, "scenario-filter", "", "Filter scenarios by substring match")
81+
f.StringVar(&flowFilter, "flow-filter", "", "Filter entries by exact flow name")
82+
f.StringVar(&outputFormat, "format", "table", "Output format: table, json")
83+
f.StringVar(&platform, "platform", "", "Filter entries to those supporting this platform")
84+
f.StringVar(&repoRoot, "repo-root", "", "Repository root path (or set repoRoot in config)")
85+
86+
return cmd
87+
}
88+
89+
// newMatrixRunCommand creates the "matrix run" subcommand.
90+
func newMatrixRunCommand() *cobra.Command {
91+
var (
92+
versions []string
93+
includeDisabled bool
94+
scenarioFilter string
95+
flowFilter string
96+
platform string
97+
repoRoot string
98+
dryRun bool
99+
testIT bool
100+
testE2E bool
101+
testAll bool
102+
stopOnFailure bool
103+
namespacePrefix string
104+
cleanup bool
105+
kubeContext string
106+
kubeContextGKE string
107+
kubeContextEKS string
108+
ingressBaseDomain string
109+
maxParallel int
110+
envFile string
111+
envFile86 string
112+
envFile87 string
113+
envFile88 string
114+
envFile89 string
115+
logLevel string
116+
)
117+
118+
cmd := &cobra.Command{
119+
Use: "run",
120+
Short: "Run the CI test matrix against a live cluster",
121+
Long: `Run the full CI test matrix, deploying each scenario + flow combination sequentially.
122+
Each entry gets its own namespace (<prefix>-<version>-<shortname>).
123+
124+
Use --cleanup to automatically delete all created namespaces after the run finishes.
125+
Cleanup runs regardless of whether entries succeeded or failed.
126+
127+
This command calls deploy.Execute() for each matrix entry.`,
128+
RunE: func(cmd *cobra.Command, args []string) error {
129+
// Setup logging
130+
if err := logging.Setup(logging.Options{
131+
LevelString: logLevel,
132+
ColorEnabled: logging.IsTerminal(os.Stdout.Fd()),
133+
}); err != nil {
134+
return err
135+
}
136+
137+
// Load .env file — use flag value if set, otherwise default to .env.
138+
// This loads the fallback env file for vars shared across all versions.
139+
envFileToLoad := envFile
140+
if envFileToLoad == "" {
141+
envFileToLoad = ".env"
142+
}
143+
logging.Logger.Debug().
144+
Str("envFile", envFileToLoad).
145+
Msg("Loading environment file")
146+
_ = env.Load(envFileToLoad)
147+
148+
repoRoot = resolveRepoRoot(repoRoot)
149+
if repoRoot == "" {
150+
return fmt.Errorf("--repo-root is required (or set repoRoot in config)")
151+
}
152+
153+
// Validate ingress base domain early so the user gets immediate feedback.
154+
if ingressBaseDomain != "" {
155+
valid := false
156+
for _, d := range config.ValidIngressBaseDomains {
157+
if d == ingressBaseDomain {
158+
valid = true
159+
break
160+
}
161+
}
162+
if !valid {
163+
return fmt.Errorf("--ingress-base-domain must be one of: %s", strings.Join(config.ValidIngressBaseDomains, ", "))
164+
}
165+
}
166+
167+
entries, err := matrix.Generate(repoRoot, matrix.GenerateOptions{
168+
Versions: versions,
169+
IncludeDisabled: includeDisabled,
170+
})
171+
if err != nil {
172+
return err
173+
}
174+
175+
entries = matrix.Filter(entries, matrix.FilterOptions{
176+
ScenarioFilter: scenarioFilter,
177+
FlowFilter: flowFilter,
178+
Platform: platform,
179+
})
180+
181+
if len(entries) == 0 {
182+
fmt.Fprintln(os.Stdout, "No matrix entries matched the filters.")
183+
return nil
184+
}
185+
186+
// Show what will be run
187+
output, _ := matrix.Print(entries, "table")
188+
fmt.Fprintln(os.Stdout, output)
189+
190+
// Build platform-to-context map from per-platform flags
191+
kubeContexts := make(map[string]string)
192+
if kubeContextGKE != "" {
193+
kubeContexts["gke"] = kubeContextGKE
194+
}
195+
if kubeContextEKS != "" {
196+
kubeContexts["eks"] = kubeContextEKS
197+
}
198+
199+
// Build version-to-env-file map from per-version flags
200+
envFiles := make(map[string]string)
201+
for version, path := range map[string]string{
202+
"8.6": envFile86,
203+
"8.7": envFile87,
204+
"8.8": envFile88,
205+
"8.9": envFile89,
206+
} {
207+
if path != "" {
208+
envFiles[version] = path
209+
}
210+
}
211+
212+
results, err := matrix.Run(context.Background(), entries, matrix.RunOptions{
213+
DryRun: dryRun,
214+
StopOnFailure: stopOnFailure,
215+
Cleanup: cleanup,
216+
KubeContexts: kubeContexts,
217+
KubeContext: kubeContext,
218+
NamespacePrefix: namespacePrefix,
219+
Platform: platform,
220+
MaxParallel: maxParallel,
221+
TestIT: testIT,
222+
TestE2E: testE2E,
223+
TestAll: testAll,
224+
RepoRoot: repoRoot,
225+
EnvFiles: envFiles,
226+
EnvFile: envFile,
227+
IngressBaseDomain: ingressBaseDomain,
228+
LogLevel: logLevel,
229+
})
230+
231+
fmt.Fprintln(os.Stdout, matrix.PrintRunSummary(results))
232+
233+
return err
234+
},
235+
}
236+
237+
f := cmd.Flags()
238+
f.StringSliceVar(&versions, "versions", nil, "Limit to specific chart versions (comma-separated, e.g., 8.8,8.9)")
239+
f.BoolVar(&includeDisabled, "include-disabled", false, "Include disabled scenarios in the output")
240+
f.StringVar(&scenarioFilter, "scenario-filter", "", "Filter scenarios by substring match")
241+
f.StringVar(&flowFilter, "flow-filter", "", "Filter entries by exact flow name")
242+
f.StringVar(&platform, "platform", "", "Filter entries to those supporting this platform (also sets deploy platform)")
243+
f.StringVar(&repoRoot, "repo-root", "", "Repository root path (or set repoRoot in config)")
244+
f.BoolVar(&dryRun, "dry-run", false, "Log what would be deployed without actually deploying")
245+
f.BoolVar(&testIT, "test-it", false, "Run integration tests after each deployment")
246+
f.BoolVar(&testE2E, "test-e2e", false, "Run e2e tests after each deployment")
247+
f.BoolVar(&testAll, "test-all", false, "Run both integration and e2e tests after each deployment")
248+
f.BoolVar(&stopOnFailure, "stop-on-failure", false, "Stop the run on the first failure")
249+
f.StringVar(&namespacePrefix, "namespace-prefix", "matrix", "Prefix for generated namespaces")
250+
f.BoolVar(&cleanup, "cleanup", false, "Delete all created namespaces after the run completes")
251+
f.StringVar(&kubeContext, "kube-context", "", "Default Kubernetes context for all platforms (overridden by --kube-context-gke/--kube-context-eks)")
252+
f.StringVar(&kubeContextGKE, "kube-context-gke", "", "Kubernetes context for GKE entries")
253+
f.StringVar(&kubeContextEKS, "kube-context-eks", "", "Kubernetes context for EKS entries")
254+
f.StringVar(&ingressBaseDomain, "ingress-base-domain", "", "Base domain for ingress hosts; each entry gets <namespace>.<base-domain>")
255+
f.IntVar(&maxParallel, "max-parallel", 1, "Maximum number of entries to run concurrently (1 = sequential)")
256+
f.StringVar(&envFile, "env-file", "", "Default .env file for all versions (overridden by --env-file-X.Y)")
257+
f.StringVar(&envFile86, "env-file-8.6", "", "Path to .env file for 8.6 entries")
258+
f.StringVar(&envFile87, "env-file-8.7", "", "Path to .env file for 8.7 entries")
259+
f.StringVar(&envFile88, "env-file-8.8", "", "Path to .env file for 8.8 entries")
260+
f.StringVar(&envFile89, "env-file-8.9", "", "Path to .env file for 8.9 entries")
261+
f.StringVarP(&logLevel, "log-level", "l", "info", "Log level (debug, info, warn, error)")
262+
263+
registerIngressBaseDomainCompletion(cmd)
264+
registerKubeContextCompletion(cmd)
265+
registerKubeContextCompletionForFlag(cmd, "kube-context-gke")
266+
registerKubeContextCompletionForFlag(cmd, "kube-context-eks")
267+
_ = cmd.RegisterFlagCompletionFunc("log-level", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
268+
return completeLogLevels(toComplete)
269+
})
270+
271+
return cmd
272+
}
273+
274+
// registerKubeContextCompletionForFlag adds tab completion for a named kube-context flag.
275+
func registerKubeContextCompletionForFlag(cmd *cobra.Command, flagName string) {
276+
_ = cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
277+
contexts, err := getKubeContexts()
278+
if err != nil {
279+
return nil, cobra.ShellCompDirectiveNoFileComp
280+
}
281+
282+
var completions []string
283+
for _, ctx := range contexts {
284+
if toComplete == "" || strings.HasPrefix(ctx, toComplete) {
285+
completions = append(completions, ctx)
286+
}
287+
}
288+
return completions, cobra.ShellCompDirectiveNoFileComp
289+
})
290+
}
291+
292+
// resolveRepoRoot resolves the repository root from the flag or config file.
293+
func resolveRepoRoot(flagValue string) string {
294+
if flagValue != "" {
295+
return flagValue
296+
}
297+
298+
// Try to resolve from config file
299+
var tempFlags config.RuntimeFlags
300+
if _, err := config.LoadAndMerge(configFile, false, &tempFlags); err == nil {
301+
if tempFlags.RepoRoot != "" {
302+
return tempFlags.RepoRoot
303+
}
304+
}
305+
306+
return ""
307+
}

scripts/deploy-camunda/cmd/root.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ func NewRootCommand() *cobra.Command {
6363
if cmd.Name() == "config" || (cmd.Parent() != nil && cmd.Parent().Name() == "config") {
6464
return nil
6565
}
66+
if cmd.Name() == "matrix" || (cmd.Parent() != nil && cmd.Parent().Name() == "matrix") {
67+
return nil
68+
}
6669
if cmd.Name() == "completion" ||
6770
cmd.Name() == cobra.ShellCompRequestCmd ||
6871
cmd.Name() == cobra.ShellCompNoDescRequestCmd {
@@ -342,6 +345,8 @@ func getKubeContexts() ([]string, error) {
342345
}
343346
}
344347
return contexts, nil
348+
}
349+
345350
// registerSelectionCompletion adds tab completion for the new selection + composition flags.
346351
func registerSelectionCompletion(cmd *cobra.Command) {
347352
// Identity completion
@@ -526,6 +531,7 @@ func Execute() error {
526531
rootCmd := NewRootCommand()
527532
rootCmd.AddCommand(newCompletionCommand(rootCmd))
528533
rootCmd.AddCommand(newConfigCommand())
534+
rootCmd.AddCommand(newMatrixCommand())
529535

530536
err := rootCmd.Execute()
531537
if err != nil {

scripts/deploy-camunda/config/merge.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,31 @@ type RuntimeFlags struct {
5858
DebugSuspend bool // Suspend JVM on startup until debugger attaches
5959
OutputTestEnv bool // Generate .env file for E2E tests after deployment
6060
OutputTestEnvPath string // Path for the test .env file output
61+
// Selection + composition flags (new model)
62+
Identity string // Identity selection: keycloak, keycloak-external, oidc, basic, hybrid
63+
Persistence string // Persistence selection: elasticsearch, opensearch, rdbms, rdbms-oracle
64+
TestPlatform string // Test platform selection: gke, eks, openshift
65+
Features []string // Feature selections: multitenancy, rba, documentstore
66+
QA bool // Enable QA configuration (test users, etc.)
67+
ImageTags bool // Enable image tag overrides from env vars
68+
UpgradeFlow bool // Enable upgrade flow configuration
69+
70+
// Deprecated layered values flags (backward compat)
71+
ValuesAuth string // DEPRECATED: use Identity instead
72+
ValuesBackend string // DEPRECATED: use Persistence instead
73+
ValuesFeatures []string // DEPRECATED: use Features instead
74+
ValuesQA bool // DEPRECATED: use QA instead
75+
ValuesInfra string // DEPRECATED: use TestPlatform instead
76+
6177
// Test execution flags
6278
RunIntegrationTests bool // Run integration tests after deployment
6379
RunE2ETests bool // Run e2e tests after deployment
6480
RunAllTests bool // Run both integration and e2e tests after deployment
6581
KubeContext string
6682
UseVaultBackedSecrets bool
67-
RunTestsIT bool // Alias for RunIntegrationTests (backward compat)
68-
RunTestsE2E bool // Alias for RunE2ETests (backward compat)
83+
RunTestsIT bool // Alias for RunIntegrationTests (backward compat)
84+
RunTestsE2E bool // Alias for RunE2ETests (backward compat)
85+
TestExclude string // Comma-separated test suites to exclude (passed as --test-exclude to test scripts)
6986
}
7087

7188
// ParseDebugFlag parses a debug flag value in the format "component" or "component:port".
@@ -303,6 +320,8 @@ func isValidIngressBaseDomain(domain string) bool {
303320
}
304321
}
305322
return false
323+
}
324+
306325
// HasExplicitSelectionConfig returns true if any selection + composition flags were explicitly set.
307326
func (f *RuntimeFlags) HasExplicitSelectionConfig() bool {
308327
return f.Identity != "" || f.Persistence != "" || f.TestPlatform != "" || len(f.Features) > 0 || f.QA || f.ImageTags || f.UpgradeFlow

0 commit comments

Comments
 (0)