From 3a3d4c5d18b7dfc64186b5d8256d7d760099d756 Mon Sep 17 00:00:00 2001 From: Tullio Sebastiani Date: Fri, 8 Nov 2024 17:39:16 +0100 Subject: [PATCH] Dependency Graph multiple scenarios execution (#6) Signed-off-by: Tullio Sebastiani --- cmd/clean.go | 4 +- cmd/describe.go | 4 +- cmd/graph.go | 250 + cmd/list.go | 4 +- cmd/root.go | 12 + cmd/run.go | 24 +- cmd/tables.go | 10 + go.mod | 3 + go.sum | 32 + internal/config/config.go | 1 + internal/config/config.json | 3 +- main.go | 4 +- pkg/container_manager/container_manager.go | 54 +- .../docker/container_manager.go | 75 +- .../docker/container_manager_test.go | 4 +- pkg/container_manager/factory/factory.go | 6 +- .../podman/container_manager.go | 86 +- .../podman/container_manager_test.go | 144 +- pkg/dependencygraph/dependency_graph.go | 21 + pkg/dependencygraph/dependency_graph_test.go | 267 + pkg/provider/factory/provider_factory.go | 2 +- pkg/provider/models/models.go | 25 +- pkg/provider/offline/scenario_provider.go | 5 + pkg/provider/provider.go | 1 + pkg/provider/quay/scenario_provider.go | 97 +- pkg/provider/quay/scenario_provider_test.go | 30 +- pkg/typing/field.go | 2 + plan.json | 38 + .../dummyscenario/Containerfile | 10 + .../dummyscenario/Containerfile.template | 10 + tests/containerfiles/dummyscenario/build.sh | 4 + .../dummyscenario/krknctl-input.json | 10 + tests/containerfiles/dummyscenario/run.sh | 5 + .../kendru/darwin/go/depgraph/depgraph.go | 232 + vendor/github.com/tjarratt/babble/.travis.yml | 17 + vendor/github.com/tjarratt/babble/LICENSE | 21 + vendor/github.com/tjarratt/babble/README.md | 36 + vendor/github.com/tjarratt/babble/babble.go | 33 + .../github.com/tjarratt/babble/babble_unix.go | 24 + .../tjarratt/babble/babble_windows.go | 235898 +++++++++++++++ vendor/github.com/tjarratt/babble/main.go | 45 + vendor/modules.txt | 8 + 42 files changed, 237468 insertions(+), 93 deletions(-) create mode 100644 cmd/graph.go create mode 100644 pkg/dependencygraph/dependency_graph.go create mode 100644 pkg/dependencygraph/dependency_graph_test.go create mode 100644 plan.json create mode 100644 tests/containerfiles/dummyscenario/Containerfile create mode 100644 tests/containerfiles/dummyscenario/Containerfile.template create mode 100755 tests/containerfiles/dummyscenario/build.sh create mode 100644 tests/containerfiles/dummyscenario/krknctl-input.json create mode 100755 tests/containerfiles/dummyscenario/run.sh create mode 100644 vendor/github.com/kendru/darwin/go/depgraph/depgraph.go create mode 100644 vendor/github.com/tjarratt/babble/.travis.yml create mode 100644 vendor/github.com/tjarratt/babble/LICENSE create mode 100644 vendor/github.com/tjarratt/babble/README.md create mode 100644 vendor/github.com/tjarratt/babble/babble.go create mode 100644 vendor/github.com/tjarratt/babble/babble_unix.go create mode 100644 vendor/github.com/tjarratt/babble/babble_windows.go create mode 100644 vendor/github.com/tjarratt/babble/main.go diff --git a/cmd/clean.go b/cmd/clean.go index f5e2feb7..083cbec7 100644 --- a/cmd/clean.go +++ b/cmd/clean.go @@ -8,7 +8,7 @@ import ( ) func NewCleanCommand(containerManager *container_manager.ContainerManager, config config.Config) *cobra.Command { - var cleanCmd = &cobra.Command{ + var command = &cobra.Command{ Use: "clean", Short: "cleans already run scenario files and containers", Long: `cleans already run scenario files and containers`, @@ -27,5 +27,5 @@ func NewCleanCommand(containerManager *container_manager.ContainerManager, confi }, } - return cleanCmd + return command } diff --git a/cmd/describe.go b/cmd/describe.go index df078125..5d56e4ca 100644 --- a/cmd/describe.go +++ b/cmd/describe.go @@ -12,7 +12,7 @@ import ( ) func NewDescribeCommand(factory *factory.ProviderFactory, config config.Config) *cobra.Command { - var describeCmd = &cobra.Command{ + var command = &cobra.Command{ Use: "describe", Short: "describes a scenario", Long: `describes a scenario`, @@ -65,7 +65,7 @@ func NewDescribeCommand(factory *factory.ProviderFactory, config config.Config) return nil }, } - return describeCmd + return command } func PrintScenarioDetail(scenarioDetail *models.ScenarioDetail) { diff --git a/cmd/graph.go b/cmd/graph.go new file mode 100644 index 00000000..06891efd --- /dev/null +++ b/cmd/graph.go @@ -0,0 +1,250 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "github.com/krkn-chaos/krknctl/internal/config" + "github.com/krkn-chaos/krknctl/pkg/container_manager" + "github.com/krkn-chaos/krknctl/pkg/dependencygraph" + "github.com/krkn-chaos/krknctl/pkg/provider" + provider_factory "github.com/krkn-chaos/krknctl/pkg/provider/factory" + "github.com/spf13/cobra" + "log" + "os" +) + +func NewGraphCommand(factory *provider_factory.ProviderFactory, config config.Config) *cobra.Command { + var command = &cobra.Command{ + Use: "graph", + Short: "Runs or scaffolds a dependency graph based run", + Long: `Runs or scaffolds a dependency graph based run`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + return command +} + +func NewGraphRunCommand(factory *provider_factory.ProviderFactory, containerManager *container_manager.ContainerManager, config config.Config) *cobra.Command { + var command = &cobra.Command{ + Use: "run", + Short: "Runs a dependency graph based run", + Long: `Runs graph based run`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + spinner := NewSpinnerWithSuffix("running graph based chaos plan...") + container_manager.PrintDetectedContainerRuntime(containerManager) + volumes := make(map[string]string) + environment := make(map[string]string) + kubeconfig, err := cmd.LocalFlags().GetString("kubeconfig") + if err != nil { + return err + } + alertsProfile, err := cmd.LocalFlags().GetString("alerts-profile") + if err != nil { + return err + } + metricsProfile, err := cmd.LocalFlags().GetString("metrics-profile") + if err != nil { + return err + } + + kubeconfigPath, err := container_manager.PrepareKubeconfig(&kubeconfig, config) + if err != nil { + return err + } + volumes[*kubeconfigPath] = config.KubeconfigPath + + if metricsProfile != "" { + volumes[metricsProfile] = config.MetricsProfilePath + } + + if alertsProfile != "" { + volumes[alertsProfile] = config.AlertsProfilePath + } + + file, err := os.ReadFile(args[0]) + if err != nil { + return fmt.Errorf("failed to open scenario file: %s", args[0]) + } + + nodes := make(map[string]container_manager.ScenarioNode) + err = json.Unmarshal(file, &nodes) + + dataSource := BuildDataSource(config, false, nil) + dataProvider := GetProvider(false, factory) + nameChannel := make(chan *struct { + name *string + err error + }) + spinner.Start() + go func() { + validateScenariosInput(dataProvider, dataSource, nodes, nameChannel) + }() + + for { + validateResult := <-nameChannel + if validateResult == nil { + break + } + if validateResult.err != nil { + return fmt.Errorf("failed to validate scenario: %s, error: %s", *validateResult.name, validateResult.err) + } + if validateResult.name != nil { + spinner.Suffix = fmt.Sprintf("validating input for scenario: %s", *validateResult.name) + } + } + + spinner.Stop() + + convertedNodes := make(map[string]dependencygraph.ParentProvider, len(nodes)) + + // Populate the new map + for key, node := range nodes { + // Since ScenarioNode implements ParentProvider, this is valid + convertedNodes[key] = node + } + graph, err := dependencygraph.NewGraphFromNodes(convertedNodes) + if err != nil { + return err + } + + executionPlan := graph.TopoSortedLayers() + table := NewGraphTable(executionPlan) + table.Print() + fmt.Print("\n\n") + spinner.Suffix = "starting chaos scenarios..." + spinner.Start() + + commChannel := make(chan *container_manager.CommChannel) + socket, err := (*containerManager).GetContainerRuntimeSocket(nil) + + if err != nil { + return err + } + go func() { + (*containerManager).RunGraph(nodes, executionPlan, *socket, environment, volumes, false, commChannel) + }() + + for { + c := <-commChannel + if c == nil { + break + } else { + if c.Err != nil { + // interrupt all running scenarios + return c.Err + } + spinner.FinalMSG = fmt.Sprintf("Running step %d scenario: %s\n", *c.Layer, *c.ScenarioId) + + } + + } + spinner.Stop() + + return nil + }, + } + return command +} + +func validateScenariosInput(provider provider.ScenarioDataProvider, dataSource string, nodes map[string]container_manager.ScenarioNode, scenarioNameChannel chan *struct { + name *string + err error +}) { + for _, n := range nodes { + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: nil} + scenarioDetail, err := provider.GetScenarioDetail(n.Name, dataSource) + if err != nil { + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: err} + return + } + for k, v := range n.Env { + field := scenarioDetail.GetFieldByEnvVar(k) + if field == nil { + + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: fmt.Errorf("environment variable %s not found", k)} + return + } + _, err := field.Validate(&v) + if err != nil { + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: err} + return + } + } + + for k, v := range n.Volumes { + field := scenarioDetail.GetFileFieldByMountPath(v) + if field == nil { + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: fmt.Errorf("no file parameter found of type field for scenario %s with mountPath %s", n.Name, v)} + return + } + _, err := field.Validate(&k) + if err != nil { + scenarioNameChannel <- &struct { + name *string + err error + }{name: &n.Name, err: err} + return + } + } + } + scenarioNameChannel <- nil +} + +func NewGraphScaffoldCommand(factory *provider_factory.ProviderFactory, config config.Config) *cobra.Command { + var command = &cobra.Command{ + Use: "scaffold", + Short: "Scaffolds a dependency graph based run", + Long: `Scaffolds a dependency graph based run`, + Args: cobra.MinimumNArgs(1), + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // TODO: datasource offline TBD + /* + offline, err := cmd.Flags().GetBool("offline") + offlineRepo, err := cmd.Flags().GetString("offline-repo-config") + if err != nil { + return []string{}, cobra.ShellCompDirectiveError + } + */ + + dataSource := BuildDataSource(config, false, nil) + provider := GetProvider(false, factory) + + scenarios, err := FetchScenarios(provider, dataSource) + if err != nil { + log.Fatalf("Error fetching scenarios: %v", err) + return []string{}, cobra.ShellCompDirectiveError + } + + return *scenarios, cobra.ShellCompDirectiveNoFileComp + }, + RunE: func(cmd *cobra.Command, args []string) error { + dataSource := BuildDataSource(config, false, nil) + provider := GetProvider(false, factory) + output, err := provider.ScaffoldScenarios(args, dataSource) + if err != nil { + return err + } + fmt.Println(*output) + return nil + }, + } + return command +} diff --git a/cmd/list.go b/cmd/list.go index 250a8ff1..374a15cd 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -9,7 +9,7 @@ import ( ) func NewListCommand(factory *provider_factory.ProviderFactory, config config.Config) *cobra.Command { - var listCmd = &cobra.Command{ + var command = &cobra.Command{ Use: "list", Short: "list scenarios", Long: `list available krkn-hub scenarios`, @@ -39,5 +39,5 @@ func NewListCommand(factory *provider_factory.ProviderFactory, config config.Con return nil }, } - return listCmd + return command } diff --git a/cmd/root.go b/cmd/root.go index 8efe6482..5fe0f5c7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -39,6 +39,18 @@ func Execute(providerFactory *factory.ProviderFactory, containerManager *contain cleanCmd := NewCleanCommand(containerManager, config) rootCmd.AddCommand(cleanCmd) + // graph subcommands + graphCmd := NewGraphCommand(providerFactory, config) + graphRunCmd := NewGraphRunCommand(providerFactory, containerManager, config) + graphRunCmd.LocalFlags().String("kubeconfig", "", "kubeconfig path (if not set will default to ~/.kube/config)") + graphRunCmd.LocalFlags().String("alerts-profile", "", "custom alerts profile file path") + graphRunCmd.LocalFlags().String("metrics-profile", "", "custom metrics profile file path") + + graphScaffoldCmd := NewGraphScaffoldCommand(providerFactory, config) + graphCmd.AddCommand(graphRunCmd) + graphCmd.AddCommand(graphScaffoldCmd) + rootCmd.AddCommand(graphCmd) + if err := rootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/run.go b/cmd/run.go index 13073b29..faa78141 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -6,15 +6,17 @@ import ( "github.com/krkn-chaos/krknctl/internal/config" "github.com/krkn-chaos/krknctl/pkg/container_manager" "github.com/krkn-chaos/krknctl/pkg/provider/factory" + "github.com/krkn-chaos/krknctl/pkg/typing" "github.com/spf13/cobra" "log" + "os" "strings" "time" ) func NewRunCommand(factory *factory.ProviderFactory, containerManager *container_manager.ContainerManager, config config.Config) *cobra.Command { collectedFlags := make(map[string]*string) - var runCmd = &cobra.Command{ + var command = &cobra.Command{ Use: "run", Short: "runs a scenario", Long: `runs a scenario`, @@ -84,6 +86,8 @@ func NewRunCommand(factory *factory.ProviderFactory, containerManager *container return err } */ + container_manager.PrintDetectedContainerRuntime(containerManager) + spinner := NewSpinnerWithSuffix("validating input...") dataSource := BuildDataSource(config, false, nil) spinner.Start() @@ -139,7 +143,7 @@ func NewRunCommand(factory *factory.ProviderFactory, containerManager *container if err != nil { return err } - volumes[*kubeconfigPath] = scenarioDetail.KubeconfigPath + volumes[*kubeconfigPath] = config.KubeconfigPath if metricsProfile != nil { volumes[*metricsProfile] = config.MetricsProfilePath } @@ -150,6 +154,9 @@ func NewRunCommand(factory *factory.ProviderFactory, containerManager *container //dynamic flags parsing for k, _ := range collectedFlags { field := scenarioDetail.GetFieldByName(k) + if field == nil { + return fmt.Errorf("field %s not found", k) + } var foundArg *string = nil for i, a := range args { if a == fmt.Sprintf("--%s", k) { @@ -165,8 +172,11 @@ func NewRunCommand(factory *factory.ProviderFactory, containerManager *container if err != nil { return err } - if value != nil { + 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] } /* @@ -189,11 +199,15 @@ func NewRunCommand(factory *factory.ProviderFactory, containerManager *container } startTime := time.Now() if runDetached == false { - _, err = (*containerManager).RunAttached(config.GetQuayImageUri()+":"+scenarioDetail.Name, scenarioDetail.Name, *socket, environment, false, volumes) + _, err = (*containerManager).RunAttached(config.GetQuayImageUri()+":"+scenarioDetail.Name, scenarioDetail.Name, *socket, environment, false, volumes, os.Stdout, os.Stderr) if err != nil { return err } } else { + _, err := color.New(color.FgGreen, color.Underline).Println("hit CTRL+C to terminate the scenario") + if err != nil { + return err + } containerId, _, err := (*containerManager).Run(config.GetQuayImageUri()+":"+scenarioDetail.Name, scenarioDetail.Name, *socket, environment, false, volumes) if err != nil { return err @@ -208,7 +222,7 @@ func NewRunCommand(factory *factory.ProviderFactory, containerManager *container return nil }, } - return runCmd + return command } func checkStringArgValue(args []string, index int) error { diff --git a/cmd/tables.go b/cmd/tables.go index 76eb15b4..b27bb332 100644 --- a/cmd/tables.go +++ b/cmd/tables.go @@ -5,6 +5,7 @@ import ( "github.com/fatih/color" "github.com/krkn-chaos/krknctl/pkg/provider/models" "github.com/krkn-chaos/krknctl/pkg/typing" + "strings" ) import "github.com/rodaine/table" @@ -42,3 +43,12 @@ func NewEnvironmentTable(env map[string]string) table.Table { return tbl } + +func NewGraphTable(graph [][]string) table.Table { + tbl := table.New("Step", "Scenario ID") + tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt) + for i, v := range graph { + tbl.AddRow(i, strings.Join(v, ", ")) + } + return tbl +} diff --git a/go.mod b/go.mod index 78974745..cceeaef9 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,12 @@ require ( github.com/containers/podman/v5 v5.2.4 github.com/docker/docker v27.1.1+incompatible github.com/fatih/color v1.15.0 + github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767 github.com/opencontainers/runtime-spec v1.2.0 github.com/rodaine/table v1.3.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 k8s.io/client-go v0.26.2 ) @@ -102,6 +104,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.1.14 // indirect diff --git a/go.sum b/go.sum index d69439a2..7a2f5ab4 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= @@ -141,6 +143,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= @@ -163,6 +167,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= @@ -201,6 +206,7 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -213,6 +219,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767 h1:Ds6xHRvL0yjG4kZD05leRKt70mM18Fjt0+B5gIqqe1g= +github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767/go.mod h1:VOfm8h1NySetVlpHDSnbpCMsvCgYaU+YDn4XezUy2+4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= @@ -263,12 +271,20 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -338,6 +354,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -353,6 +370,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= +github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 h1:j8whCiEmvLCXI3scVn+YnklCU8mwJ9ZJ4/DGAKqQbRE= +github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1/go.mod h1:O5hBrCGqzfb+8WyY8ico2AyQau7XQwAfEQeEQ5/5V9E= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= @@ -408,11 +427,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -432,11 +453,17 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -470,6 +497,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= @@ -509,11 +537,15 @@ google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWn gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index f212c692..b396708c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,6 +21,7 @@ type Config struct { DefaultContainerPlatform string `json:"default_container_platform"` MetricsProfilePath string `json:"metrics_profile_path"` AlertsProfilePath string `json:"alerts_profile_path"` + KubeconfigPath string `json:"kubeconfig_path"` } //go:embed config.json diff --git a/internal/config/config.json b/internal/config/config.json index f54dc7a1..ee593922 100644 --- a/internal/config/config.json +++ b/internal/config/config.json @@ -13,5 +13,6 @@ "docker_socket_root": "unix:///var/run/docker.sock", "default_container_platform": "Podman", "metrics_profile_path" : "/home/krkn/kraken/config/metrics-aggregated.yaml", - "alerts_profile_path":"/home/krkn/kraken/config/alerts" + "alerts_profile_path":"/home/krkn/kraken/config/alerts", + "kubeconfig_path": "/home/krkn/.kube/config" } diff --git a/main.go b/main.go index e572484d..6a9b171b 100644 --- a/main.go +++ b/main.go @@ -23,9 +23,7 @@ func main() { fmt.Printf("%s\n", color.New(color.FgHiRed).Sprint("failed to determine container runtime enviroment please install podman or docker and retry")) os.Exit(1) } - green := color.New(color.FgGreen).SprintFunc() - boldGreen := color.New(color.FgHiGreen, color.Bold).SprintFunc() - fmt.Printf("%s %s\n\n", green("detected runtime:"), boldGreen(detectedRuntime.String())) + containerManager := containerManagerFactory.NewInstance(detectedRuntime, &config) providerFactory := providerfactory.NewProviderFactory(&config) diff --git a/pkg/container_manager/container_manager.go b/pkg/container_manager/container_manager.go index 3909886e..4d584288 100644 --- a/pkg/container_manager/container_manager.go +++ b/pkg/container_manager/container_manager.go @@ -3,6 +3,8 @@ package container_manager import ( "context" "fmt" + "github.com/fatih/color" + "io" ) type ContainerRuntime int64 @@ -39,16 +41,64 @@ func EnvironmentFromString(s string) ContainerRuntime { } } +type ScenarioNode struct { + Scenario + Parent *string `json:"depends_on,omitempty"` +} + +func (s ScenarioNode) GetParent() *string { + return s.Parent +} + +type Scenario struct { + // the only purpose of this attribute is to put a comment in the json + Comment string `json:"_comment,omitempty"` + Image string `json:"image,omitempty"` + Name string `json:"name,omitempty"` + Env map[string]string `json:"env,omitempty"` + Volumes map[string]string `json:"volumes,omitempty"` +} + +type ScenarioSet map[string]ScenarioNode +type ResolvedGraph [][]string + +type CommChannel struct { + Layer *int + ScenarioId *string + ScenarioLogFile *string + Err error +} + type ContainerManager interface { Run(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string) (*string, *context.Context, error) - RunAttached(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string) (*string, error) + RunAttached(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string, stdout io.Writer, stderr io.Writer) (*string, error) + + RunGraph(scenarios ScenarioSet, + resolvedGraph ResolvedGraph, + containerRuntimeUri string, + extraEnv map[string]string, + extraVolumeMounts map[string]string, + cache bool, + commChannel chan *CommChannel, + ) CleanContainers() (*int, error) - Attach(containerId *string, ctx *context.Context) error + Attach(containerId *string, ctx *context.Context, stdout io.Writer, stderr io.Writer) error Kill(containerId *string, ctx *context.Context) error GetContainerRuntimeSocket(userId *int) (*string, error) + + GetContainerRuntime() ContainerRuntime +} + +func PrintDetectedContainerRuntime(containerManager *ContainerManager) { + if containerManager == nil { + panic("nil container manager") + } + green := color.New(color.FgGreen).SprintFunc() + boldGreen := color.New(color.FgHiGreen, color.Bold).SprintFunc() + fmt.Printf("%s %s\n\n", green("detected runtime:"), boldGreen((*containerManager).GetContainerRuntime().String())) } diff --git a/pkg/container_manager/docker/container_manager.go b/pkg/container_manager/docker/container_manager.go index 77d00db3..4a12c252 100644 --- a/pkg/container_manager/docker/container_manager.go +++ b/pkg/container_manager/docker/container_manager.go @@ -2,16 +2,13 @@ package docker import ( "context" - "encoding/json" "errors" "fmt" - "github.com/briandowns/spinner" dockercontainer "github.com/docker/docker/api/types/container" dockerimage "github.com/docker/docker/api/types/image" images "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/client" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stdcopy" "github.com/fatih/color" "github.com/krkn-chaos/krknctl/internal/config" @@ -24,7 +21,8 @@ import ( ) type ContainerManager struct { - Config config.Config + Config config.Config + ContainerRuntime container_manager.ContainerRuntime } func (c *ContainerManager) Run(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string) (*string, *context.Context, error) { @@ -78,7 +76,7 @@ func (c *ContainerManager) Run(image string, scenarioName string, containerRunti return &resp.ID, &ctxWithClient, nil } -func (c *ContainerManager) RunAttached(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string) (*string, error) { +func (c *ContainerManager) RunAttached(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string, stdout io.Writer, stderr io.Writer) (*string, error) { _, err := color.New(color.FgGreen, color.Underline).Println("hit CTRL+C to terminate the scenario") if err != nil { return nil, err @@ -94,7 +92,7 @@ func (c *ContainerManager) RunAttached(image string, scenarioName string, contai signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) - kill, err := c.attach(containerId, ctx, signalChan) + kill, err := c.attach(containerId, ctx, signalChan, stdout, stderr) if err != nil { return nil, err @@ -113,7 +111,12 @@ func (c *ContainerManager) RunAttached(image string, scenarioName string, contai return containerId, nil } -func (c *ContainerManager) attach(containerId *string, ctx *context.Context, signalChannel chan os.Signal) (bool, error) { +func (c *ContainerManager) RunSerialPlan() { + //TODO implement me + panic("implement me") +} + +func (c *ContainerManager) attach(containerId *string, ctx *context.Context, signalChannel chan os.Signal, stdout io.Writer, stderr io.Writer) (bool, error) { cli, err := dockerClientFromContext(*ctx) if err != nil { return false, err @@ -134,7 +137,7 @@ func (c *ContainerManager) attach(containerId *string, ctx *context.Context, sig // copies demultiplexed reader to Stdout and Stderr go func() { - _, err := stdcopy.StdCopy(os.Stdout, os.Stderr, reader) + _, err := stdcopy.StdCopy(stdout, stderr, reader) if err != nil { errorChan <- err } @@ -171,14 +174,14 @@ func (c *ContainerManager) attach(containerId *string, ctx *context.Context, sig } -func (c *ContainerManager) Attach(containerId *string, ctx *context.Context) error { +func (c *ContainerManager) Attach(containerId *string, ctx *context.Context, stdout io.Writer, stderr io.Writer) error { _, err := color.New(color.FgGreen, color.Underline).Println("hit CTRL+C to stop streaming scenario output (scenario won't be interrupted)") if err != nil { return err } sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - interrupted, err := c.attach(containerId, ctx, sigCh) + interrupted, err := c.attach(containerId, ctx, sigCh, stdout, stderr) if err != nil { return err } @@ -230,6 +233,11 @@ func (c *ContainerManager) checkImageAndPull(cli *client.Client, ctx context.Con } +func (c *ContainerManager) RunGraph(scenarios container_manager.ScenarioSet, resolvedGraph container_manager.ResolvedGraph, containerRuntimeUri string, extraEnv map[string]string, extraVolumeMounts map[string]string, cache bool, commChannel chan *container_manager.CommChannel) { + //TODO implement me + panic("implement me") +} + type contextKey string const clientKey contextKey = "dockerClient" @@ -267,31 +275,34 @@ func pullImage(ctx context.Context, cli *client.Client, imageName string) error } defer reader.Close() - var totalSize int - s := spinner.New(spinner.CharSets[39], 100*time.Millisecond) - s.Suffix = "pulling image...." - s.Start() + /* + ********* update to send updates to a channel ******** + var totalSize int - decoder := json.NewDecoder(reader) - for { + decoder := json.NewDecoder(reader) - var message jsonmessage.JSONMessage - if err := decoder.Decode(&message); err == io.EOF { - break - } else if err != nil { - return err - } + for { - if message.Progress != nil && message.Progress.Total > 0 { - if totalSize == 0 { - totalSize = int(message.Progress.Total) - } - if message.Progress.Current > 0 { - s.Suffix = fmt.Sprintf("Downloading image %s: %d/%d MB ", imageName, int(message.Progress.Current/1024/1024), totalSize/1024/1024) - } - } - } - s.Stop() + var message jsonmessage.JSONMessage + if err := decoder.Decode(&message); err == io.EOF { + break + } else if err != nil { + return err + } + + if message.Progress != nil && message.Progress.Total > 0 { + if totalSize == 0 { + totalSize = int(message.Progress.Total) + } + if message.Progress.Current > 0 { + s.Suffix = fmt.Sprintf("Downloading image %s: %d/%d MB ", imageName, int(message.Progress.Current/1024/1024), totalSize/1024/1024) + } + } + }*/ return nil } + +func (c *ContainerManager) GetContainerRuntime() container_manager.ContainerRuntime { + return c.ContainerRuntime +} diff --git a/pkg/container_manager/docker/container_manager_test.go b/pkg/container_manager/docker/container_manager_test.go index a47c26d6..28cad5c8 100644 --- a/pkg/container_manager/docker/container_manager_test.go +++ b/pkg/container_manager/docker/container_manager_test.go @@ -43,7 +43,7 @@ func TestContainerManager_Run(t *testing.T) { currentUser, err := user.Current() fmt.Println("Current user: " + (*currentUser).Name) fmt.Println("current user id" + (*currentUser).Uid) - quayProvider := quay.ScenarioProvider{} + quayProvider := quay.ScenarioProvider{Config: &conf} scenario, err := quayProvider.GetScenarioDetail("node-cpu-hog", conf.GetQuayRepositoryApiUri()) assert.Nil(t, err) assert.NotNil(t, scenario) @@ -65,7 +65,7 @@ func TestContainerManager_Run(t *testing.T) { assert.NotNil(t, socket) fmt.Println("CONTAINER SOCKET -> " + *socket) - containerId, err := cm.RunAttached(conf.GetQuayImageUri()+":"+scenario.Name, scenario.Name, *socket, env, false, map[string]string{}) + containerId, err := cm.RunAttached(conf.GetQuayImageUri()+":"+scenario.Name, scenario.Name, *socket, env, false, map[string]string{}, os.Stdout, os.Stderr) if err != nil { fmt.Println("ERROR -> " + err.Error()) } diff --git a/pkg/container_manager/factory/factory.go b/pkg/container_manager/factory/factory.go index e3e2aff2..f3212ab8 100644 --- a/pkg/container_manager/factory/factory.go +++ b/pkg/container_manager/factory/factory.go @@ -18,11 +18,13 @@ func (f *ContainerManagerFactory) NewInstance(containerEnvironment container_man switch containerEnvironment { case container_manager.Podman: return &podman.ContainerManager{ - Config: *config, + Config: *config, + ContainerRuntime: containerEnvironment, } case container_manager.Docker: return &docker.ContainerManager{ - Config: *config, + Config: *config, + ContainerRuntime: containerEnvironment, } } return nil diff --git a/pkg/container_manager/podman/container_manager.go b/pkg/container_manager/podman/container_manager.go index 8b962145..e80f811a 100644 --- a/pkg/container_manager/podman/container_manager.go +++ b/pkg/container_manager/podman/container_manager.go @@ -3,7 +3,6 @@ package podman import ( "context" "fmt" - "github.com/briandowns/spinner" "github.com/containers/podman/v5/pkg/bindings" "github.com/containers/podman/v5/pkg/bindings/containers" "github.com/containers/podman/v5/pkg/bindings/images" @@ -12,16 +11,25 @@ import ( "github.com/fatih/color" "github.com/krkn-chaos/krknctl/internal/config" "github.com/krkn-chaos/krknctl/pkg/container_manager" + "github.com/krkn-chaos/krknctl/pkg/text" "github.com/opencontainers/runtime-spec/specs-go" + "io" "os" "os/signal" "regexp" + "sync" "syscall" "time" ) type ContainerManager struct { - Config config.Config + Config config.Config + ContainerRuntime container_manager.ContainerRuntime +} + +func (c *ContainerManager) RunSerialPlan() { + //TODO implement me + panic("implement me") } func (c *ContainerManager) Run(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string) (*string, *context.Context, error) { @@ -32,16 +40,14 @@ func (c *ContainerManager) Run(image string, scenarioName string, containerRunti //if the image exists but the digest has changed pulls the image again imageExists, err := images.Exists(conn, image, nil) if cache == false || imageExists == false { - s := spinner.New(spinner.CharSets[39], 100*time.Millisecond) - s.Suffix = fmt.Sprintf("pulling image %s....", image) - s.Start() + + // add a channel to update the status (eventually) options := images.PullOptions{} options.WithQuiet(true) _, err = images.Pull(conn, image, &options) if err != nil { return nil, nil, err } - s.Stop() } if err != nil { @@ -49,7 +55,7 @@ func (c *ContainerManager) Run(image string, scenarioName string, containerRunti } s := specgen.NewSpecGenerator(image, false) - s.Name = fmt.Sprintf("%s-%s-%d", c.Config.ContainerPrefix, scenarioName, time.Now().Unix()) + s.Name = fmt.Sprintf("%s-%s-%s-%d", c.Config.ContainerPrefix, scenarioName, text.RandString(5), time.Now().Unix()) s.Env = env for k, v := range volumeMounts { containerMount := specs.Mount{ @@ -76,20 +82,14 @@ func (c *ContainerManager) Run(image string, scenarioName string, containerRunti return &createResponse.ID, &conn, nil } -func (c *ContainerManager) RunAttached(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string) (*string, error) { - _, err := color.New(color.FgGreen, color.Underline).Println("hit CTRL+C to terminate the scenario") - if err != nil { - return nil, err - } - // to make the above message readable - time.Sleep(2) +func (c *ContainerManager) RunAttached(image string, scenarioName string, containerRuntimeUri string, env map[string]string, cache bool, volumeMounts map[string]string, stdout io.Writer, stderr io.Writer) (*string, error) { containerId, conn, err := c.Run(image, scenarioName, containerRuntimeUri, env, cache, volumeMounts) if err != nil { return nil, err } sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - kill, err := c.attach(containerId, conn, sigCh) + kill, err := c.attach(containerId, conn, sigCh, stdout, stderr) if err != nil { return nil, err @@ -108,14 +108,14 @@ func (c *ContainerManager) RunAttached(image string, scenarioName string, contai } -func (c *ContainerManager) attach(containerId *string, conn *context.Context, signalChannel chan os.Signal) (bool, error) { +func (c *ContainerManager) attach(containerId *string, conn *context.Context, signalChannel chan os.Signal, stdout io.Writer, stderr io.Writer) (bool, error) { options := new(containers.AttachOptions).WithLogs(true).WithStream(true).WithDetachKeys("ctrl-c") errorChannel := make(chan error, 1) finishChannel := make(chan bool, 1) go func() { - err := containers.Attach(*conn, *containerId, nil, os.Stdout, os.Stderr, nil, options) + err := containers.Attach(*conn, *containerId, nil, stdout, stderr, nil, options) if err != nil { errorChannel <- err } @@ -132,14 +132,14 @@ func (c *ContainerManager) attach(containerId *string, conn *context.Context, si } } -func (c *ContainerManager) Attach(containerId *string, conn *context.Context) error { +func (c *ContainerManager) Attach(containerId *string, conn *context.Context, stdout io.Writer, stderr io.Writer) error { _, err := color.New(color.FgGreen, color.Underline).Println("hit CTRL+C to stop streaming scenario output (scenario won't be interrupted)") if err != nil { return err } sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - interrupted, err := c.attach(containerId, conn, sigCh) + interrupted, err := c.attach(containerId, conn, sigCh, stdout, stderr) if err != nil { return err } @@ -192,6 +192,49 @@ func (c *ContainerManager) CleanContainers() (*int, error) { return &deletedContainers, nil } +func (c *ContainerManager) RunGraph(scenarios container_manager.ScenarioSet, resolvedGraph container_manager.ResolvedGraph, containerRuntimeUri string, extraEnv map[string]string, extraVolumeMounts map[string]string, cache bool, commChannel chan *container_manager.CommChannel) { + env := make(map[string]string) + volumes := make(map[string]string) + + for k, v := range extraEnv { + env[k] = v + } + + for k, v := range extraVolumeMounts { + volumes[k] = v + } + + for step, s := range resolvedGraph { + var wg sync.WaitGroup + for _, scId := range s { + scenario := scenarios[scId] + for k, v := range scenario.Env { + env[k] = v + } + for k, v := range scenario.Volumes { + volumes[k] = v + } + filename := fmt.Sprintf("%s-%s-%d.log", scId, scenario.Name, time.Now().Unix()) + file, err := os.Create(filename) + + if err != nil { + commChannel <- &container_manager.CommChannel{Layer: nil, ScenarioId: nil, ScenarioLogFile: nil, Err: err} + return + } + + commChannel <- &container_manager.CommChannel{Layer: &step, ScenarioId: &scId, ScenarioLogFile: &filename, Err: nil} + wg.Add(1) + go func() { + defer wg.Done() + _, _ = c.RunAttached(scenario.Image, scenario.Name, containerRuntimeUri, env, cache, volumes, file, file) + }() + + } + wg.Wait() + } + commChannel <- nil +} + func (c *ContainerManager) Kill(containerId *string, ctx *context.Context) error { err := containers.Kill(*ctx, *containerId, nil) if err != nil { @@ -203,3 +246,8 @@ func (c *ContainerManager) Kill(containerId *string, ctx *context.Context) error func (c *ContainerManager) GetContainerRuntimeSocket(userId *int) (*string, error) { return container_manager.GetSocketByContainerEnvironment(container_manager.Podman, c.Config, userId) } + +func (c *ContainerManager) GetContainerRuntime() container_manager.ContainerRuntime { + //TODO implement me + return c.ContainerRuntime +} diff --git a/pkg/container_manager/podman/container_manager_test.go b/pkg/container_manager/podman/container_manager_test.go index 8b82a93f..a8dc9d70 100644 --- a/pkg/container_manager/podman/container_manager_test.go +++ b/pkg/container_manager/podman/container_manager_test.go @@ -1,9 +1,11 @@ package podman import ( + "encoding/json" "fmt" krknctlconfig "github.com/krkn-chaos/krknctl/internal/config" "github.com/krkn-chaos/krknctl/pkg/container_manager" + "github.com/krkn-chaos/krknctl/pkg/dependencygraph" "github.com/krkn-chaos/krknctl/pkg/provider/quay" "github.com/stretchr/testify/assert" "os" @@ -28,6 +30,21 @@ func getTestConfig() krknctlconfig.Config { } } +func getGraphConfig() krknctlconfig.Config { + return krknctlconfig.Config{ + Version: "0.0.1", + QuayProtocol: "https", + QuayHost: "quay.io", + QuayOrg: "krkn-chaos", + QuayRegistry: "krknctl-test", + QuayRepositoryApi: "api/v1/repository", + ContainerPrefix: "krknctl-containers", + KubeconfigPrefix: "krknctl-kubeconfig", + PodmanDarwinSocketTemplate: "unix://%s/.local/share/containers/podman/machine/podman.sock", + PodmanLinuxSocketTemplate: "unix://run/user/%d/podman/podman.sock", + PodmanSocketRoot: "unix://run/podman/podman.sock", + } +} func TestConnect(t *testing.T) { env := map[string]string{ "CHAOS_DURATION": "2", @@ -64,7 +81,7 @@ func TestConnect(t *testing.T) { assert.NotNil(t, socket) fmt.Println("CONTAINER SOCKET -> " + *socket) - containerId, err := cm.RunAttached(conf.GetQuayImageUri()+":"+scenario.Name, scenario.Name, *socket, env, false, map[string]string{}) + containerId, err := cm.RunAttached(conf.GetQuayImageUri()+":"+scenario.Name, scenario.Name, *socket, env, false, map[string]string{}, os.Stdout, os.Stderr) if err != nil { fmt.Println("ERROR -> " + err.Error()) } @@ -72,3 +89,128 @@ func TestConnect(t *testing.T) { assert.NotNil(t, containerId) } + +func TestRunGraph(t *testing.T) { + data := ` +{ + "root":{ + "image":"quay.io/krkn-chaos/krknctl-test:dummy-scenario", + "name":"dummy-scenario", + "env":{ + "END":"7" + }, + "volumes":{} + }, + "first-row-1":{ + "depends_on":"root", + "image":"quay.io/krkn-chaos/krknctl-test:dummy-scenario", + "name":"dummy-scenario", + "env":{ + "END":"7" + }, + "volumes":{} + }, + "first-row-2":{ + "depends_on":"root", + "image":"quay.io/krkn-chaos/krknctl-test:dummy-scenario", + "name":"dummy-scenario", + "env":{ + "END":"7" + }, + "volumes":{} + }, + "second-row":{ + "depends_on":"first-row-1", + "image":"quay.io/krkn-chaos/krknctl-test:dummy-scenario", + "name":"dummy-scenario", + "env":{ + "END":"7" + }, + "volumes":{} + }, + "third-row-1":{ + "depends_on":"second-row", + "image":"quay.io/krkn-chaos/krknctl-test:dummy-scenario", + "name":"dummy-scenario", + "env":{ + "END":"7" + }, + "volumes":{} + }, + "third-row-2":{ + "depends_on":"second-row", + "image":"quay.io/krkn-chaos/krknctl-test:dummy-scenario", + "name":"dummy-scenario", + "env":{ + "END":"7" + }, + "volumes":{} + } +} +` + conf := getGraphConfig() + cm := ContainerManager{ + Config: conf, + } + currentUser, err := user.Current() + fmt.Println("Current user: " + (*currentUser).Name) + fmt.Println("current user id" + (*currentUser).Uid) + quayProvider := quay.ScenarioProvider{} + scenario, err := quayProvider.GetScenarioDetail("dummy-scenario", conf.GetQuayRepositoryApiUri()) + assert.Nil(t, err) + assert.NotNil(t, scenario) + kubeconfig, err := container_manager.PrepareKubeconfig(nil, getTestConfig()) + assert.Nil(t, err) + assert.NotNil(t, kubeconfig) + fmt.Println("KUBECONFIG PARSED -> " + *kubeconfig) + + envuid := os.Getenv("USERID") + var uid *int = nil + if envuid != "" { + _uid, err := strconv.Atoi(envuid) + assert.Nil(t, err) + uid = &_uid + fmt.Println("USERID -> ", *uid) + } + socket, err := cm.GetContainerRuntimeSocket(uid) + assert.Nil(t, err) + assert.NotNil(t, socket) + + fmt.Println("CONTAINER SOCKET -> " + *socket) + + nodes := make(map[string]container_manager.ScenarioNode) + err = json.Unmarshal([]byte(data), &nodes) + assert.Nil(t, err) + + convertedNodes := make(map[string]dependencygraph.ParentProvider, len(nodes)) + + // Populate the new map + for key, node := range nodes { + // Since ScenarioNode implements ParentProvider, this is valid + convertedNodes[key] = node + } + + graph, err := dependencygraph.NewGraphFromNodes(convertedNodes) + + assert.Nil(t, err) + assert.NotNil(t, graph) + executionPlan := graph.TopoSortedLayers() + assert.NotNil(t, executionPlan) + + commChannel := make(chan *container_manager.CommChannel) + go func() { + cm.RunGraph(nodes, executionPlan, *socket, map[string]string{}, map[string]string{}, false, commChannel) + }() + + for { + c := <-commChannel + if c == nil { + break + } else { + assert.Nil(t, (*c).Err) + fmt.Printf("Running step %d scenario: %s\n", *c.Layer, *c.ScenarioId) + } + + } + +} diff --git a/pkg/dependencygraph/dependency_graph.go b/pkg/dependencygraph/dependency_graph.go new file mode 100644 index 00000000..91b2c534 --- /dev/null +++ b/pkg/dependencygraph/dependency_graph.go @@ -0,0 +1,21 @@ +package dependencygraph + +import "github.com/kendru/darwin/go/depgraph" + +type ParentProvider interface { + GetParent() *string +} + +func NewGraphFromNodes(nodes map[string]ParentProvider) (*depgraph.Graph, error) { + graph := depgraph.New() + for k, v := range nodes { + if parent := v.GetParent(); parent != nil { + err := graph.DependOn(k, *parent) + if err != nil { + return nil, err + } + } + } + + return graph, nil +} diff --git a/pkg/dependencygraph/dependency_graph_test.go b/pkg/dependencygraph/dependency_graph_test.go new file mode 100644 index 00000000..f1afb3e2 --- /dev/null +++ b/pkg/dependencygraph/dependency_graph_test.go @@ -0,0 +1,267 @@ +package dependencygraph + +import ( + "encoding/json" + "fmt" + "github.com/kendru/darwin/go/depgraph" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +type GenericNode struct { + Parent *string `json:"depends_on"` +} + +func (g *GenericNode) GetParent() *string { + return g.Parent +} + +type NodeList map[string]GenericNode + +func TestDependencyGraph(t *testing.T) { + + data := ` +{ + "alectoropodous-retrogradely": { + "image": "quay.io/krkn-chaos/krkn-hub:pod-network-chaos", + "name": "pod-network-chaos", + "env": { + "EGRESS_PORTS": "[]", + "INGRESS_PORTS": "[]", + "INSTANCE_COUNT": "1", + "LABEL_SELECTOR": "", + "NAMESPACE": "", + "TEST_DURATION": "120", + "TRAFFIC_TYPE": "[ingress,egress]", + "WAIT_DURATION": "300" + }, + "volumes": {}, + "depends_on": "precedential-desiccative" + }, + "bathysophical-hunh": { + "image": "quay.io/krkn-chaos/krkn-hub:pod-scenarios", + "name": "pod-scenarios", + "env": { + "DISRUPTION_COUNT": "1", + "EXPECTED_RECOVERY_TIME": "120", + "KILL_TIMEOUT": "180", + "NAMESPACE": "openshift-*", + "NAME_PATTERN": ".*", + "POD_LABEL": "