diff --git a/Dockerfile b/Dockerfile index 55e2b45..1dcbca5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,9 @@ RUN microdnf -y install git &&\ FROM quay.io/konveyor/static-report:latest as static-report +FROM quay.io/konveyor/java-external-provider:latest as java-provider +FROM quay.io/konveyor/generic-external-provider:latest as generic-provider + # Build the manager binary FROM golang:1.19 as builder @@ -34,6 +37,7 @@ RUN CGO_ENABLED=0 GOOS=windows go build --ldflags="-X 'github.com/konveyor-ecosy FROM quay.io/konveyor/analyzer-lsp:latest RUN mkdir /opt/rulesets /opt/rulesets/input /opt/rulesets/convert /opt/openrewrite /opt/input /opt/input/rules /opt/input/rules/custom /opt/output /opt/xmlrules /opt/shimoutput /tmp/source-app /tmp/source-app/input +RUN go install golang.org/x/tools/gopls@latest COPY --from=builder /workspace/kantra /usr/local/bin/kantra COPY --from=builder /workspace/darwin-kantra /usr/local/bin/darwin-kantra @@ -43,6 +47,9 @@ COPY --from=rulesets /rulesets/default/generated /opt/rulesets COPY --from=rulesets /windup-rulesets/rules/rules-reviewed/openrewrite /opt/openrewrite COPY --from=static-report /usr/bin/js-bundle-generator /usr/local/bin COPY --from=static-report /usr/local/static-report /usr/local/static-report +COPY --from=java-provider /usr/local/bin/java-external-provider /usr/local/bin/java-external-provider +COPY --from=generic-provider /usr/local/bin/go-dependency-provider /usr/local/bin/go-dependency-provider +COPY --from=generic-provider /usr/local/bin/generic-external-provider /usr/local/bin/generic-external-provider COPY --chmod=755 entrypoint.sh /usr/bin/entrypoint.sh COPY --chmod=755 openrewrite_entrypoint.sh /usr/bin/openrewrite_entrypoint.sh diff --git a/cmd/analyze.go b/cmd/analyze.go index aa8ed24..cf0bd02 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -9,6 +9,7 @@ import ( "io" "io/fs" "os" + "os/exec" "path" "runtime" @@ -16,7 +17,6 @@ import ( "sort" "strings" - "github.com/devfile/alizer/pkg/apis/model" "github.com/devfile/alizer/pkg/apis/recognizer" "github.com/go-logr/logr" "github.com/konveyor-ecosystem/kantra/cmd/internal/hiddenfile" @@ -24,6 +24,7 @@ import ( "github.com/konveyor/analyzer-lsp/engine" outputv1 "github.com/konveyor/analyzer-lsp/output/v1/konveyor" "github.com/konveyor/analyzer-lsp/provider" + "github.com/phayes/freeport" "go.lsp.dev/uri" "gopkg.in/yaml.v2" @@ -47,6 +48,14 @@ var ( ProviderSettingsMountPath = path.Join(ConfigMountPath, "settings.json") ) +// supported provider images +const ( + javaExternalProviderImg = "quay.io/konveyor/java-external-provider:latest" + genericProviderImg = "quay.io/konveyor/generic-external-provider:latest" + dotNetProviderImg = "quay.io/konveyor/dotnet-external-provider:latest" + yqProviderImg = "quay.io/konveyor/yq-external-provider:latest" +) + // kantra analyze flags type analyzeCommand struct { listSources bool @@ -75,7 +84,11 @@ type analyzeCommand struct { // isFileInput is set when input points to a file and not a dir isFileInput bool logLevel *uint32 - cleanup bool + // used for cleanup + networkName string + volumeName string + providerContainerNames []string + cleanup bool } // analyzeCmd represents the analyze command @@ -130,10 +143,30 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { log.Error(err, "Failed to determine languages for input") return err } + foundProviders := []string{} for _, c := range components { log.Info("Got component", "component language", c.Languages, "path", c.Path) + for _, l := range c.Languages { + foundProviders = append(foundProviders, l.Name) + } + } + containerNetworkName, err := analyzeCmd.createContainerNetwork() + if err != nil { + log.Error(err, "failed to create container network") + return err + } + // share source app with provider and engine containers + containerVolName, err := analyzeCmd.createContainerVolume(analyzeCmd.input) + if err != nil { + log.Error(err, "failed to create container volume") + return err + } + providerPorts, err := analyzeCmd.RunProviders(cmd.Context(), containerNetworkName, containerVolName, foundProviders) + if err != nil { + log.Error(err, "failed to run provider") + return err } - err = analyzeCmd.RunAnalysis(cmd.Context(), xmlOutputDir, components) + err = analyzeCmd.RunAnalysis(cmd.Context(), xmlOutputDir, containerVolName, foundProviders, providerPorts) if err != nil { log.Error(err, "failed to run analysis") return err @@ -148,6 +181,10 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { log.Error(err, "failed to generate static report") return err } + err = analyzeCmd.getProviderLogs(cmd.Context()) + if err != nil { + log.Error(err, "failed to get provider container logs") + } return nil }, PostRunE: func(cmd *cobra.Command, args []string) error { @@ -156,6 +193,10 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { log.Error(err, "failed to clean temporary container resources") return err } + err = analyzeCmd.RmAnalysisResources(cmd.Context()) + if err != nil { + log.Error(err, "failed to remove analysis resource") + } return nil }, } @@ -411,7 +452,7 @@ func listOptionsFromLabels(sl []string, label string) { } } -func (a *analyzeCommand) getConfigVolumes(components []model.Component) (map[string]string, error) { +func (a *analyzeCommand) getConfigVolumes(providers []string, ports map[string]int) (map[string]string, error) { tempDir, err := os.MkdirTemp("", "analyze-config-") if err != nil { a.log.V(1).Error(err, "failed creating temp dir", "dir", tempDir) @@ -422,14 +463,12 @@ func (a *analyzeCommand) getConfigVolumes(components []model.Component) (map[str var foundJava bool var foundGolang bool - for _, c := range components { - for _, l := range c.Languages { - if l.Name == "Java" { - foundJava = true - } - if l.Name == "Go" { - foundGolang = true - } + for _, p := range providers { + if p == "Java" { + foundJava = true + } + if p == "Go" { + foundGolang = true } } @@ -442,8 +481,8 @@ func (a *analyzeCommand) getConfigVolumes(components []model.Component) (map[str } javaConfig := provider.Config{ - Name: "java", - BinaryPath: "/usr/local/bin/java-external-provider", + Name: "java", + Address: "0.0.0.0:14651", InitConfig: []provider.InitConfig{ { Location: SourceMountPath, @@ -470,15 +509,15 @@ func (a *analyzeCommand) getConfigVolumes(components []model.Component) (map[str } goConfig := provider.Config{ - Name: "go", - BinaryPath: "/usr/local/bin/generic-external-provider", + Name: "go", + Address: fmt.Sprintf("0.0.0.0:%v", ports["go"]), InitConfig: []provider.InitConfig{ { - Location: otherProvsMountPath, AnalysisMode: provider.FullAnalysisMode, ProviderSpecificConfig: map[string]interface{}{ - "name": "go", - "dependencyProviderPath": "/usr/local/bin/golang-dependency-provider", + "lspServerName": "generic", + "workspaceFolders": []string{fmt.Sprintf("file://%s", otherProvsMountPath)}, + "dependencyProviderPath": "/usr/local/bin/go-dependency-provider", provider.LspServerPathConfigKey: "/root/go/bin/gopls", }, }, @@ -499,7 +538,7 @@ func (a *analyzeCommand) getConfigVolumes(components []model.Component) (map[str if foundJava { provConfig = append(provConfig, javaConfig) } - if foundGolang { + if foundGolang && a.mode == string(provider.FullAnalysisMode) { provConfig = append(provConfig, goConfig) } @@ -515,11 +554,6 @@ func (a *analyzeCommand) getConfigVolumes(components []model.Component) (map[str } } - // go provider only supports full analysis mode - if a.mode == string(provider.FullAnalysisMode) { - provConfig = append(provConfig, goConfig) - } - jsonData, err := json.MarshalIndent(&provConfig, "", " ") if err != nil { a.log.V(1).Error(err, "failed to marshal provider config") @@ -667,8 +701,8 @@ func copyFileContents(src string, dst string) (err error) { return nil } -func (a analyzeCommand) createTempRuleSet(path string, name string) error { - a.log.Info("createing temp ruleset ", "path", path, "name", name) +func (a *analyzeCommand) createTempRuleSet(path string, name string) error { + a.log.Info("creating temp ruleset ", "path", path, "name", name) _, err := os.Stat(path) if os.IsNotExist(err) { return nil @@ -691,7 +725,144 @@ func (a analyzeCommand) createTempRuleSet(path string, name string) error { return nil } -func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, components []model.Component) error { +func (a *analyzeCommand) createContainerNetwork() (string, error) { + networkName := container.RandomName() + args := []string{ + "network", + "create", + networkName, + } + + cmd := exec.Command(Settings.PodmanBinary, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return "", err + } + a.log.V(1).Info("created container network", "network", networkName) + // for cleanup + a.networkName = networkName + return networkName, nil +} + +// TODO: create for each source input once accepting multiple apps is completed +func (a *analyzeCommand) createContainerVolume(sourceInput string) (string, error) { + volName := container.RandomName() + args := []string{ + "volume", + "create", + "--opt", + "type=none", + "--opt", + fmt.Sprintf("device=%v", sourceInput), + "--opt", + "o=bind", + volName, + } + + cmd := exec.Command(Settings.PodmanBinary, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + return "", err + } + a.log.V(1).Info("created container volume", "volume", volName) + // for cleanup + a.volumeName = volName + return volName, nil +} + +func (a *analyzeCommand) RunProviders(ctx context.Context, networkName string, volName string, providers []string) (map[string]int, error) { + providerPorts := map[string]int{} + port, err := freeport.GetFreePort() + if err != nil { + return nil, err + } + args := []string{} + volumes := map[string]string{ + // application source code + a.input: SourceMountPath, + } + // this will make more sense when we have more than 2 supported providers + var providerImage string + switch providers[0] { + case "Java": + // right now the Java provider port is hard-coded + port = 14651 + providerImage = javaExternalProviderImg + providerPorts["java"] = port + args = append(args, fmt.Sprintf("--port=%v", port)) + case "Go": + providerImage = genericProviderImg + providerPorts["go"] = port + args = append(args, fmt.Sprintf("--port=%v", port)) + default: + } + + a.log.Info("starting provider", "provider", providers[0]) + + con := container.NewContainer() + err = con.Run( + ctx, + container.WithImage(providerImage), + container.WithLog(a.log.V(1)), + container.WithVolumes(volumes), + container.WithContainerToolBin(Settings.PodmanBinary), + container.WithEntrypointArgs(args...), + container.WithDetachedMode(true), + container.WithCleanup(a.cleanup), + container.WithNetwork(networkName), + ) + if err != nil { + return nil, err + } + a.providerContainerNames = append(a.providerContainerNames, con.Name) + + // we need to start each provider container separately from the first one so that + // we can join the first container's network + // this is so not for the provider containers to share the same network but + // because the network needs to be shared with the analyzer engine + // if len(providers) > 1 { + // for _, prov := range providers[1:] { + // // this will make more sense when we have more than 2 supported providers + // var provImage string + // switch prov { + // case "Java": + // provImage = javaExternalProviderImg + // providerPorts["java"] = port + // case "Go": + // provImage = golangDepProviderImg + // providerPorts["go"] = port + // default: + // } + + // a.log.Info("starting provider", "provider", prov) + // c := container.NewContainer() + // err := con.Run( + // ctx, + // container.WithImage(provImage), + // container.WithLog(a.log.V(1)), + // container.WithVolumes(volumes), + // container.WithContainerToolBin(Settings.PodmanBinary), + // container.WithEntrypointArgs(args...), + // container.WithDetachedMode(true), + // container.WithCleanup(a.cleanup), + // container.WithNetwork(fmt.Sprintf("container:%v", providers[0])), + // ) + // if err != nil { + // return err + // } + // // for cleanup + // a.providerContainerNames = append(a.providerContainerNames, c.Name) + // } + // } + + return providerPorts, nil +} + +func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, volName string, providers []string, ports map[string]int) error { volumes := map[string]string{ // application source code a.input: SourceMountPath, @@ -710,7 +881,7 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, c a.tempDirs = append(a.tempDirs, xmlOutputDir) } - configVols, err := a.getConfigVolumes(components) + configVols, err := a.getConfigVolumes(providers, ports) if err != nil { a.log.V(1).Error(err, "failed to get config volumes for analysis") return err @@ -731,6 +902,10 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, c fmt.Sprintf("--output-file=%s", AnalysisOutputMountPath), fmt.Sprintf("--context-lines=%d", 100), } + // TODO update for running multiple apps + if providers[0] != "Java" { + a.enableDefaultRulesets = false + } if a.enableDefaultRulesets { args = append(args, fmt.Sprintf("--rules=%s/", RulesetPath)) @@ -774,7 +949,9 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, c "input", a.input, "output", a.output, "args", strings.Join(args, " "), "volumes", volumes) a.log.Info("generating analysis log in file", "file", analysisLogFilePath) // TODO (pgaikwad): run analysis & deps in parallel - err = container.NewContainer().Run( + + c := container.NewContainer() + err = c.Run( ctx, container.WithImage(Settings.RunnerImage), container.WithLog(a.log.V(1)), @@ -782,7 +959,8 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, c container.WithStdout(analysisLog), container.WithStderr(analysisLog), container.WithEntrypointArgs(args...), - container.WithEntrypointBin("/usr/bin/entrypoint.sh"), + container.WithEntrypointBin("/usr/local/bin/konveyor-analyzer"), + container.WithNetwork(fmt.Sprintf("container:%v", a.providerContainerNames[0])), container.WithContainerToolBin(Settings.PodmanBinary), container.WithCleanup(a.cleanup), ) @@ -1068,3 +1246,82 @@ func (a *analyzeCommand) ConvertXML(ctx context.Context) (string, error) { return tempOutputDir, nil } + +func (a *analyzeCommand) RmAnalysisResources(ctx context.Context) error { + if len(a.providerContainerNames) == 0 { + return nil + } + err := a.RmProviderContainers(ctx) + if err != nil { + a.log.Error(err, "failed to remove provider container") + return err + } + err = a.RmNetwork(ctx) + if err != nil { + a.log.Error(err, "failed to remove network", "network", a.networkName) + return err + } + err = a.RmVolumes(ctx) + if err != nil { + a.log.Error(err, "failed to remove volume", "volume", a.volumeName) + return err + } + return nil +} + +func (a *analyzeCommand) RmNetwork(ctx context.Context) error { + cmd := exec.CommandContext( + ctx, + Settings.PodmanBinary, + "network", + "rm", a.networkName) + a.log.V(1).Info("removing container network", + "network", a.networkName) + return cmd.Run() +} + +func (a *analyzeCommand) RmVolumes(ctx context.Context) error { + cmd := exec.CommandContext( + ctx, + Settings.PodmanBinary, + "volume", + "rm", a.volumeName) + a.log.V(1).Info("removing created volume", + "volume", a.volumeName) + return cmd.Run() +} + +// TODO: multiple provider containers +func (a *analyzeCommand) RmProviderContainers(ctx context.Context) error { + for i := range a.providerContainerNames { + cmd := exec.CommandContext( + ctx, + Settings.PodmanBinary, + "stop", a.providerContainerNames[i]) + a.log.V(1).Info("removing container", + "container", a.providerContainerNames[i]) + return cmd.Run() + } + return nil +} + +// TODO multiple providers +func (a *analyzeCommand) getProviderLogs(ctx context.Context) error { + if len(a.providerContainerNames) == 0 { + return nil + } + providerLogFilePath := filepath.Join(a.output, "provider.log") + a.log.V(1).Info("getting provider container logs", + "container", a.providerContainerNames[0]) + + // send each provider logs to log file + cmd := exec.CommandContext( + ctx, + Settings.PodmanBinary, + "logs", + a.providerContainerNames[0], + "&>", + providerLogFilePath) + + return cmd.Run() +} diff --git a/go.mod b/go.mod index c57ddee..a58d224 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module github.com/konveyor-ecosystem/kantra go 1.21 require ( + github.com/devfile/alizer v1.4.0 github.com/getkin/kin-openapi v0.108.0 github.com/go-logr/logr v1.4.1 + github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/spf13/cobra v1.8.0 go.lsp.dev/uri v0.3.0 gopkg.in/yaml.v2 v2.4.0 @@ -14,7 +16,6 @@ require ( require ( github.com/cbroglie/mustache v1.3.0 // indirect github.com/containerd/typeurl/v2 v2.1.1 // indirect - github.com/devfile/alizer v1.4.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -26,13 +27,13 @@ require ( github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/pretty v0.3.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/buildkit v0.13.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect go.opentelemetry.io/otel v1.21.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect diff --git a/go.sum b/go.sum index 3f5e2a2..4827f1c 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaL github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -47,6 +49,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20230323073829-e72429f035bd h1:r8yyd+DJDmsUhGrRBxH5Pj7KeFK5l+Y3FsgT8keqKtk= +github.com/google/pprof v0.0.0-20230323073829-e72429f035bd/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -83,6 +87,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= +github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= +github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -130,6 +140,8 @@ go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8 go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -179,6 +191,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -207,6 +221,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.26.10 h1:skTnrDR0r8dg4MMLf6YZIzugxNM0BjFsWKPkNc5kOvk= +k8s.io/api v0.26.10/go.mod h1:ou/H3yviqrHtP/DSPVTfsc7qNfmU06OhajytJfYXkXw= k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= @@ -219,3 +235,5 @@ sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMm sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/container/container.go b/pkg/container/container.go index 127f45c..8df0020 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -20,8 +20,9 @@ import ( type container struct { stdout []io.Writer stderr []io.Writer - name string + Name string image string + NetworkName string entrypointBin string entrypointArgs []string workdir string @@ -31,6 +32,7 @@ type container struct { // map of source -> dest paths to mount volumes map[string]string cFlag bool + detached bool log logr.Logger containerToolBin string reproducerCmd *string @@ -46,7 +48,13 @@ func WithImage(i string) Option { func WithName(n string) Option { return func(c *container) { - c.name = n + c.Name = n + } +} + +func WithNetwork(w string) Option { + return func(c *container) { + c.NetworkName = w } } @@ -98,6 +106,12 @@ func WithcFlag(cl bool) Option { } } +func WithDetachedMode(d bool) Option { + return func(c *container) { + c.detached = d + } +} + func WithCleanup(cl bool) Option { return func(c *container) { c.cleanup = cl @@ -122,7 +136,7 @@ func WithReproduceCmd(r *string) Option { } } -func randomName() string { +func RandomName() string { rand.Seed(int64(time.Now().Nanosecond())) charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" b := make([]byte, 16) @@ -141,11 +155,13 @@ func NewContainer() *container { stdout: []io.Writer{os.Stdout}, env: map[string]string{}, stderr: []io.Writer{os.Stderr}, - name: randomName(), + Name: RandomName(), + NetworkName: "", // by default, remove the container after run() - cleanup: true, - cFlag: false, - log: logr.Discard(), + cleanup: true, + cFlag: false, + detached: false, + log: logr.Discard(), } } @@ -159,12 +175,19 @@ func (c *container) Run(ctx context.Context, opts ...Option) error { } args := []string{"run"} os := runtime.GOOS + if c.detached { + args = append(args, "-d") + } if c.cleanup { args = append(args, "--rm") } - if c.name != "" { + if c.Name != "" { args = append(args, "--name") - args = append(args, c.name) + args = append(args, c.Name) + } + if c.NetworkName != "" { + args = append(args, "--network") + args = append(args, c.NetworkName) } if c.entrypointBin != "" { args = append(args, "--entrypoint") @@ -228,8 +251,8 @@ func (c *container) Rm(ctx context.Context) error { cmd := exec.CommandContext( ctx, c.containerToolBin, - "rm", c.name) + "rm", c.Name) c.log.Info("removing container", - "container tool", c.containerToolBin, "name", c.name) + "container tool", c.containerToolBin, "name", c.Name) return cmd.Run() }