diff --git a/cmd/graph.go b/cmd/graph.go index 177aab2..d84a956 100644 --- a/cmd/graph.go +++ b/cmd/graph.go @@ -197,6 +197,7 @@ func validateScenariosInput(provider provider.ScenarioDataProvider, nodes map[st err error }{name: &n.Name, err: nil} scenarioDetail, err := provider.GetScenarioDetail(n.Name) + if err != nil { scenarioNameChannel <- &struct { name *string @@ -204,6 +205,7 @@ func validateScenariosInput(provider provider.ScenarioDataProvider, nodes map[st }{name: &n.Name, err: err} return } + if scenarioDetail == nil { scenarioNameChannel <- &struct { name *string @@ -211,6 +213,21 @@ func validateScenariosInput(provider provider.ScenarioDataProvider, nodes map[st }{name: &n.Name, err: fmt.Errorf("scenario %s not found", n.Name)} return } + + globalDetail, err := provider.GetGlobalEnvironment() + if err != nil { + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: err} + return + } + + // adding the global env fields to the scenario fields so, if global env is + // added to the scenario the validation is available + + scenarioDetail.Fields = append(scenarioDetail.Fields, globalDetail.Fields...) + for k, v := range n.Env { field := scenarioDetail.GetFieldByEnvVar(k) if field == nil { @@ -272,7 +289,12 @@ func NewGraphScaffoldCommand(factory *providerfactory.ProviderFactory, config co }, RunE: func(cmd *cobra.Command, args []string) error { dataProvider := GetProvider(false, factory) - output, err := dataProvider.ScaffoldScenarios(args) + includeGlobalEnv, err := cmd.Flags().GetBool("global-env") + if err != nil { + return err + } + + output, err := dataProvider.ScaffoldScenarios(args, includeGlobalEnv) if err != nil { return err } diff --git a/cmd/root.go b/cmd/root.go index 550754b..316a589 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -69,6 +69,7 @@ func Execute(providerFactory *factory.ProviderFactory, scenarioOrchestrator *sce graphRunCmd.Flags().Bool("debug", false, "if set this flag will enable debug output in krkn") graphScaffoldCmd := NewGraphScaffoldCommand(providerFactory, config) + graphScaffoldCmd.Flags().Bool("global-env", false, "if set this flag will add global environment variables to each scenario in the graph") graphCmd.AddCommand(graphRunCmd) graphCmd.AddCommand(graphScaffoldCmd) rootCmd.AddCommand(graphCmd) diff --git a/cmd/run.go b/cmd/run.go index 2bb87c7..b26b1a6 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -6,10 +6,8 @@ import ( "github.com/fatih/color" "github.com/krkn-chaos/krknctl/pkg/config" "github.com/krkn-chaos/krknctl/pkg/provider/factory" - "github.com/krkn-chaos/krknctl/pkg/provider/models" "github.com/krkn-chaos/krknctl/pkg/scenario_orchestrator" "github.com/krkn-chaos/krknctl/pkg/scenario_orchestrator/utils" - "github.com/krkn-chaos/krknctl/pkg/typing" "github.com/spf13/cobra" "github.com/spf13/pflag" "log" @@ -187,11 +185,11 @@ func NewRunCommand(factory *factory.ProviderFactory, scenarioOrchestrator *scena volumes[*alertsProfile] = config.AlertsProfilePath } //dynamic flags parsing - scenarioEnv, scenarioVol, err := parseFlags(scenarioDetail, args, scenarioCollectedFlags, false) + scenarioEnv, scenarioVol, err := ParseFlags(scenarioDetail, args, scenarioCollectedFlags, false) if err != nil { return err } - globalEnv, globalVol, err := parseFlags(globalDetail, args, globalCollectedFlags, true) + globalEnv, globalVol, err := ParseFlags(globalDetail, args, globalCollectedFlags, true) if err != nil { return err } @@ -273,43 +271,6 @@ func NewRunCommand(factory *factory.ProviderFactory, scenarioOrchestrator *scena return command } -func parseFlags(scenarioDetail *models.ScenarioDetail, args []string, scenarioCollectedFlags map[string]*string, skipDefault bool) (vol *map[string]string, env *map[string]string, err error) { - environment := make(map[string]string) - volumes := make(map[string]string) - for k := range scenarioCollectedFlags { - field := scenarioDetail.GetFieldByName(k) - if field == nil { - return nil, nil, fmt.Errorf("field %s not found", k) - } - var foundArg *string = nil - for i, a := range args { - if a == fmt.Sprintf("--%s", k) { - if len(args) < i+2 || strings.HasPrefix(args[i+1], "--") { - return nil, nil, fmt.Errorf("%s has no value", args[i]) - } - foundArg = &args[i+1] - } - } - var value *string = nil - if foundArg != nil || skipDefault == false { - value, err = field.Validate(foundArg) - if err != nil { - return nil, nil, err - } - } - - if value != nil && field.Type != typing.File { - environment[*field.Variable] = *value - } else if value != nil && field.Type == typing.File { - fileSrcDst := strings.Split(*value, ":") - volumes[fileSrcDst[0]] = fileSrcDst[1] - } - - } - - return &environment, &volumes, nil -} - func checkStringArgValue(args []string, index int) error { if len(args) < index+2 || strings.HasPrefix(args[index+1], "--") { return fmt.Errorf("%s has no value", args[index]) diff --git a/cmd/utils.go b/cmd/utils.go index 968130d..0f2c6be 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -1,12 +1,16 @@ package cmd import ( + "fmt" "github.com/briandowns/spinner" "github.com/krkn-chaos/krknctl/pkg/config" "github.com/krkn-chaos/krknctl/pkg/provider" "github.com/krkn-chaos/krknctl/pkg/provider/factory" + "github.com/krkn-chaos/krknctl/pkg/provider/models" + "github.com/krkn-chaos/krknctl/pkg/typing" "github.com/spf13/cobra" "os" + "strings" "time" ) @@ -57,3 +61,40 @@ func CheckFileExists(filePath string) bool { } return true } + +func ParseFlags(scenarioDetail *models.ScenarioDetail, args []string, scenarioCollectedFlags map[string]*string, skipDefault bool) (vol *map[string]string, env *map[string]string, err error) { + environment := make(map[string]string) + volumes := make(map[string]string) + for k := range scenarioCollectedFlags { + field := scenarioDetail.GetFieldByName(k) + if field == nil { + return nil, nil, fmt.Errorf("field %s not found", k) + } + var foundArg *string = nil + for i, a := range args { + if a == fmt.Sprintf("--%s", k) { + if len(args) < i+2 || strings.HasPrefix(args[i+1], "--") { + return nil, nil, fmt.Errorf("%s has no value", args[i]) + } + foundArg = &args[i+1] + } + } + var value *string = nil + if foundArg != nil || skipDefault == false { + value, err = field.Validate(foundArg) + if err != nil { + return nil, nil, err + } + } + + if value != nil && field.Type != typing.File { + environment[*field.Variable] = *value + } else if value != nil && field.Type == typing.File { + fileSrcDst := strings.Split(*value, ":") + volumes[fileSrcDst[0]] = fileSrcDst[1] + } + + } + + return &environment, &volumes, nil +} diff --git a/pkg/provider/offline/scenario_provider.go b/pkg/provider/offline/scenario_provider.go index 950b22b..2183b9d 100644 --- a/pkg/provider/offline/scenario_provider.go +++ b/pkg/provider/offline/scenario_provider.go @@ -16,7 +16,7 @@ func (p *ScenarioProvider) GetScenarioDetail(scenario string) (*models.ScenarioD return nil, errors.New("not yet implemented") } -func (p *ScenarioProvider) ScaffoldScenarios(scenarios []string) (*string, error) { +func (p *ScenarioProvider) ScaffoldScenarios(scenarios []string, includeGlobalEnv bool) (*string, error) { //TODO implement me panic("implement me") } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 4b0789b..284093c 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -13,5 +13,5 @@ type ScenarioDataProvider interface { GetRegistryImages() (*[]models.ScenarioTag, error) GetGlobalEnvironment() (*models.ScenarioDetail, error) GetScenarioDetail(scenario string) (*models.ScenarioDetail, error) - ScaffoldScenarios(scenarios []string) (*string, error) + ScaffoldScenarios(scenarios []string, includeGlobalEnv bool) (*string, error) } diff --git a/pkg/provider/quay/scenario_provider.go b/pkg/provider/quay/scenario_provider.go index 882bb70..1690ba6 100644 --- a/pkg/provider/quay/scenario_provider.go +++ b/pkg/provider/quay/scenario_provider.go @@ -87,7 +87,7 @@ func (p *ScenarioProvider) getInstructionScenario(rootNodeName string) models2.S return node } -func (p *ScenarioProvider) ScaffoldScenarios(scenarios []string) (*string, error) { +func (p *ScenarioProvider) ScaffoldScenarios(scenarios []string, includeGlobalEnv bool) (*string, error) { var scenarioDetails []models.ScenarioDetail // handles babble panic when american word dictionary is not installed @@ -157,6 +157,18 @@ func (p *ScenarioProvider) ScaffoldScenarios(scenarios []string) (*string, error } } + + if includeGlobalEnv == true { + globalDetail, err := p.GetGlobalEnvironment() + if err != nil { + return nil, err + } + + for _, field := range globalDetail.Fields { + scenarioNode.Env[*field.Variable] = *field.Default + + } + } scenarioNodes[indexes[i]] = scenarioNode } diff --git a/pkg/provider/quay/scenario_provider_test.go b/pkg/provider/quay/scenario_provider_test.go index 90ba198..3cccff6 100644 --- a/pkg/provider/quay/scenario_provider_test.go +++ b/pkg/provider/quay/scenario_provider_test.go @@ -86,11 +86,15 @@ func TestQuayScenarioProvider_ScaffoldScenarios(t *testing.T) { assert.NotNil(t, scenarios) scenarioNames := []string{"node-cpu-hog", "node-memory-hog", "dummy-scenario"} - json, err := provider.ScaffoldScenarios(scenarioNames) + json, err := provider.ScaffoldScenarios(scenarioNames, false) assert.Nil(t, err) assert.NotNil(t, json) - json, err = provider.ScaffoldScenarios([]string{"node-cpu-hog", "does-not-exist"}) + json, err = provider.ScaffoldScenarios(scenarioNames, true) + assert.Nil(t, err) + assert.NotNil(t, json) + + json, err = provider.ScaffoldScenarios([]string{"node-cpu-hog", "does-not-exist"}, false) assert.Nil(t, json) assert.NotNil(t, err) diff --git a/pkg/scenario_orchestrator/docker/scenario_orchestrator.go b/pkg/scenario_orchestrator/docker/scenario_orchestrator.go index 2ef0023..6a155d3 100644 --- a/pkg/scenario_orchestrator/docker/scenario_orchestrator.go +++ b/pkg/scenario_orchestrator/docker/scenario_orchestrator.go @@ -53,9 +53,6 @@ func (c *ScenarioOrchestrator) Run(image string, containerName string, env map[s envVars = append(envVars, fmt.Sprintf("%s='True'", c.GetConfig().DebugEnvironmentVariable)) } - // THIS WILL BE REMOVED WHEN GLOBAL ENV WILL BE INTRODUCED - envVars = append(envVars, "WAIT_DURATION=1") - for k, v := range env { envVars = append(envVars, fmt.Sprintf("%s=%s", k, v)) } diff --git a/pkg/scenario_orchestrator/podman/scenario_orchestrator.go b/pkg/scenario_orchestrator/podman/scenario_orchestrator.go index d4bc382..fafcfcb 100644 --- a/pkg/scenario_orchestrator/podman/scenario_orchestrator.go +++ b/pkg/scenario_orchestrator/podman/scenario_orchestrator.go @@ -99,9 +99,6 @@ func (c *ScenarioOrchestrator) Run(image string, containerName string, env map[s s.Env[c.GetConfig().DebugEnvironmentVariable] = "True" } - // THIS WILL BE REMOVED WHEN GLOBAL ENV WILL BE INTRODUCED - s.Env["WAIT_DURATION"] = "1" - for k, v := range volumeMounts { containerMount := specs.Mount{ Destination: v, diff --git a/plan.json b/plan.json index 85c4745..40d1ea7 100644 --- a/plan.json +++ b/plan.json @@ -1,38 +1,55 @@ { "_comment": { - "_comment": "****DELETE THIS NODE**** To create your scenario run plan, assign an ID to each scenario definition (or keep the existing randomly assigned ones if preferred). Define dependencies between scenarios using the `depends_on` field, ensuring there are no cycles (including transitive ones) or self-references.Nodes not referenced will not be executed, Nodes without dependencies will run first, while nodes that share the same parent will execute in parallel. [CURRENT ROOT SCENARIO IS `node-cpu-hog-lachrymogenic`]" + "_comment": "**READ CAREFULLY** To create your scenario run plan, assign an ID to each scenario definition (or keep the existing randomly assigned ones if preferred). Define dependencies between scenarios using the `depends_on` field, ensuring there are no cycles (including transitive ones) or self-references.Nodes not referenced will not be executed, Nodes without dependencies will run first, while nodes that share the same parent will execute in parallel. [CURRENT ROOT SCENARIO IS `dummy-scenario-keraterpeton`]" }, - "node-cpu-hog-arctogaeal": { - "image": "quay.io/krkn-chaos/krkn-hub:node-cpu-hog", - "name": "node-cpu-hog", + "dummy-scenario-keraterpeton": { + "_comment": "I'm the root Node!", + "image": "quay.io/krkn-chaos/krkn-hub:dummy-scenario", + "name": "dummy-scenario", "env": { - "NAMESPACE": "default", - "NODE_CPU_CORE": "2", - "NODE_CPU_PERCENTAGE": "50", - "TOTAL_CHAOS_DURATION": "5" - }, - "depends_on": "node-cpu-hog-lachrymogenic" - }, - "node-cpu-hog-lachrymogenic": { - "image": "quay.io/krkn-chaos/krkn-hub:node-cpu-hog", - "name": "node-cpu-hog", - "env": { - "NAMESPACE": "default", - "NODE_CPU_CORE": "2", - "NODE_CPU_PERCENTAGE": "50", - "TOTAL_CHAOS_DURATION": "5" + "ALERTS_PATH": "config/alerts.yaml", + "CAPTURE_METRICS": "False", + "CERBERUS_ENABLED": "False", + "CERBERUS_URL": "http://0.0.0.0:8080", + "CHECK_CRITICAL_ALERTS": "False", + "DAEMON_MODE": "False", + "DISTRIBUTION": "openshift", + "ENABLE_ALERTS": "False", + "ENABLE_ES": "False", + "END": "10", + "ES_ALERTS_INDEX": "krkn-alerts", + "ES_COLLECT_ALERTS": "False", + "ES_COLLECT_METRICS": "False", + "ES_METRICS_INDEX": "krkn-metrics", + "ES_PASSWORD": "", + "ES_PORT": "443", + "ES_SERVER": "http://0.0.0.0", + "ES_TELEMETRY_INDEX": "krkn-telemetry", + "ES_USERNAME": "elastic", + "ES_VERIFY_CERTS": "False", + "EXIT_STATUS": "0", + "ITERATIONS": "1", + "KRKN_DEBUG": "False", + "KRKN_KUBE_CONFIG": "/home/krkn/.kube/config", + "TELEMETRY_API_URL": "https://ulnmf9xv7j.execute-api.us-west-2.amazonaws.com/production", + "TELEMETRY_ARCHIVE_PATH": "/tmp", + "TELEMETRY_ARCHIVE_SIZE": "1000", + "TELEMETRY_BACKUP_THREADS": "5", + "TELEMETRY_CLI_PATH": "", + "TELEMETRY_ENABLED": "False", + "TELEMETRY_EVENTS_BACKUP": "True", + "TELEMETRY_FILTER_PATTERN": "[\"(\\\\w{3}\\\\s\\\\d{1,2}\\\\s\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d+).+\",\"kinit (\\\\d+/\\\\d+/\\\\d+\\\\s\\\\d{2}:\\\\d{2}:\\\\d{2})\\\\s+\",\"(\\\\d{4}-\\\\d{2}-\\\\d{2}T\\\\d{2}:\\\\d{2}:\\\\d{2}\\\\.\\\\d+Z).+\"]", + "TELEMETRY_FULL_PROMETHEUS_BACKUP": "False", + "TELEMETRY_GROUP": "default", + "TELEMETRY_LOGS_BACKUP": "False", + "TELEMETRY_MAX_RETRIES": "0", + "TELEMETRY_PASSWORD": "", + "TELEMETRY_PROMETHEUS_BACKUP": "True", + "TELEMETRY_RUN_TAG": "chaos", + "TELEMETRY_USERNAME": "redhat-chaos", + "UUID": "", + "WAIT_DURATION": "1" } - }, - "node-cpu-hog-tutelo": { - "image": "quay.io/krkn-chaos/krkn-hub:node-cpu-hog", - "name": "node-cpu-hog", - "env": { - "NAMESPACE": "default", - "NODE_CPU_CORE": "2", - "NODE_CPU_PERCENTAGE": "50", - "TOTAL_CHAOS_DURATION": "5" - }, - "depends_on": "node-cpu-hog-lachrymogenic" } }