From 64bd9fd21a95d4fd4756cbc5a17d2edcd593157c Mon Sep 17 00:00:00 2001 From: Emily McMullan Date: Tue, 18 Jun 2024 16:39:31 -0400 Subject: [PATCH] update from comments Signed-off-by: Emily McMullan --- cmd/analyze.go | 330 ++++++++++++++++++++++++++++--------------------- 1 file changed, 186 insertions(+), 144 deletions(-) diff --git a/cmd/analyze.go b/cmd/analyze.go index 7cae7de..4f75590 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -55,9 +55,6 @@ const ( goProvider = "go" pythonProvider = "python" nodeJSProvider = "javascript" - dotNetProvider = "dotnet" - // windows only - dotNetFrameworkProvider = "dotnetframework" ) // provider config options @@ -69,10 +66,20 @@ const ( dependencyProviderPath = "dependencyProviderPath" ) +// TODO add network and volume w/ interface +type ProviderInit struct { + port int + image string + // used for failed provider container retry attempts + isRunning bool + containerName string +} + // kantra analyze flags type analyzeCommand struct { listSources bool listTargets bool + listProviders bool skipStaticReport bool analyzeKnownLibraries bool jsonOutput bool @@ -93,6 +100,7 @@ type analyzeCommand struct { depFolders []string overrideProviderSettings string provider []string + providersMap map[string]ProviderInit // tempDirs list of temporary dirs created, used for cleanup tempDirs []string @@ -120,7 +128,8 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { PreRunE: func(cmd *cobra.Command, args []string) error { // TODO (pgaikwad): this is nasty if !cmd.Flags().Lookup("list-sources").Changed && - !cmd.Flags().Lookup("list-targets").Changed { + !cmd.Flags().Lookup("list-targets").Changed && + !cmd.Flags().Lookup("list-providers").Changed { //cmd.MarkFlagRequired("input") cmd.MarkFlagRequired("output") if err := cmd.ValidateRequiredFlags(); err != nil { @@ -141,7 +150,6 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { if val, err := cmd.Flags().GetBool(noCleanupFlag); err == nil { analyzeCmd.cleanup = !val } - var providersMap map[string]ProviderInit if analyzeCmd.overrideProviderSettings == "" { if analyzeCmd.listSources || analyzeCmd.listTargets { err := analyzeCmd.ListLabels(cmd.Context()) @@ -151,6 +159,17 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { } return nil } + if analyzeCmd.listProviders { + err := analyzeCmd.ListSupportedProviders(cmd.Context()) + if err != nil { + log.Error(err, "failed to list providers") + return err + } + return nil + } + if analyzeCmd.providersMap == nil { + analyzeCmd.providersMap = make(map[string]ProviderInit) + } xmlOutputDir, err := analyzeCmd.ConvertXML(cmd.Context()) if err != nil { log.Error(err, "failed to convert xml rules") @@ -176,7 +195,7 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { return err } } - providersMap, err = setProviderInitInfo(foundProviders) + err = analyzeCmd.setProviderInitInfo(foundProviders) if err != nil { log.Error(err, "failed to set provider init info") return err @@ -192,20 +211,13 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { log.Error(err, "failed to create container volume") return err } - // defer cleaning created resources here instead of PostRun - // if Run returns an error, PostRun does not run - defer func() { - if err := analyzeCmd.CleanAnalysisResources(cmd.Context()); err != nil { - log.Error(err, "failed to clean temporary directories") - } - }() // allow for 5 retries of running provider in the case of port in use - _, err = analyzeCmd.RunProviders(cmd.Context(), containerNetworkName, containerVolName, providersMap, 5) + err = analyzeCmd.RunProviders(cmd.Context(), containerNetworkName, containerVolName, 5) if err != nil { log.Error(err, "failed to run provider") return err } - err = analyzeCmd.RunAnalysis(cmd.Context(), xmlOutputDir, containerVolName, providersMap) + err = analyzeCmd.RunAnalysis(cmd.Context(), xmlOutputDir, containerVolName) if err != nil { log.Error(err, "failed to run analysis") return err @@ -230,7 +242,7 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { return err } - err = analyzeCmd.GenerateStaticReport(cmd.Context(), providersMap) + err = analyzeCmd.GenerateStaticReport(cmd.Context()) if err != nil { log.Error(err, "failed to generate static report") return err @@ -241,6 +253,7 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { } analyzeCommand.Flags().BoolVar(&analyzeCmd.listSources, "list-sources", false, "list rules for available migration sources") analyzeCommand.Flags().BoolVar(&analyzeCmd.listTargets, "list-targets", false, "list rules for available migration targets") + analyzeCommand.Flags().BoolVar(&analyzeCmd.listProviders, "list-providers", false, "list available supported providers") analyzeCommand.Flags().StringArrayVarP(&analyzeCmd.sources, "source", "s", []string{}, "source technology to consider for analysis. Use multiple times for additional sources: --source --source ...") analyzeCommand.Flags().StringArrayVarP(&analyzeCmd.targets, "target", "t", []string{}, "target technology to consider for analysis. Use multiple times for additional targets: --target --target ...") analyzeCommand.Flags().StringVarP(&analyzeCmd.labelSelector, "label-selector", "l", "", "run rules based on specified label selector expression") @@ -260,13 +273,13 @@ func NewAnalyzeCmd(log logr.Logger) *cobra.Command { analyzeCommand.Flags().StringVar(&analyzeCmd.incidentSelector, "incident-selector", "", "an expression to select incidents based on custom variables. ex: (!package=io.konveyor.demo.config-utils)") analyzeCommand.Flags().StringArrayVarP(&analyzeCmd.depFolders, "dependency-folders", "d", []string{}, "directory for dependencies") analyzeCommand.Flags().StringVar(&analyzeCmd.overrideProviderSettings, "override-provider-settings", "", "override the provider settings, the analysis pod will be run on the host network and no providers with be started up") - analyzeCommand.Flags().StringArrayVar(&analyzeCmd.provider, "provider", []string{}, "set provider to use") + analyzeCommand.Flags().StringArrayVar(&analyzeCmd.provider, "provider", []string{}, "specify which provider(s) to run") return analyzeCommand } func (a *analyzeCommand) Validate(ctx context.Context) error { - if a.listSources || a.listTargets { + if a.listSources || a.listTargets || a.listProviders { return nil } if a.labelSelector != "" && (len(a.sources) > 0 || len(a.targets) > 0) { @@ -429,12 +442,6 @@ func (a *analyzeCommand) CheckOverwriteOutput() error { return nil } -// TODO add network and volume w/ interface -type ProviderInit struct { - port int - image string -} - func (a *analyzeCommand) setProviders(components []model.Component, foundProviders []string) ([]string, error) { if len(a.provider) > 0 { for _, p := range a.provider { @@ -445,70 +452,45 @@ func (a *analyzeCommand) setProviders(components []model.Component, foundProvide for _, c := range components { a.log.V(5).Info("Got component", "component language", c.Languages, "path", c.Path) for _, l := range c.Languages { - if l.Name == "C#" { - foundDotNetProv, err := setDotNetProvider(l.Frameworks) - if err != nil { - return nil, err - } - foundProviders = append(foundProviders, foundDotNetProv) - } else { - foundProviders = append(foundProviders, strings.ToLower(l.Name)) - } - } - } - return foundProviders, nil -} -func setDotNetProvider(frameworks []string) (string, error) { - if len(frameworks) > 0 { - for _, f := range frameworks { - if f == "v4.5" || f == "v4.6" || f == "v4.7" || f == "v4.8" { - return dotNetFrameworkProvider, nil - } else { - return dotNetProvider, nil - } + foundProviders = append(foundProviders, strings.ToLower(l.Name)) + } } - return "", fmt.Errorf("unable to set dotnet provider") + return foundProviders, nil } -func setProviderInitInfo(foundProviders []string) (map[string]ProviderInit, error) { - providers := map[string]ProviderInit{} +func (a *analyzeCommand) setProviderInitInfo(foundProviders []string) error { for _, prov := range foundProviders { port, err := freeport.GetFreePort() if err != nil { - return nil, err + return err } switch prov { case javaProvider: - providers[javaProvider] = ProviderInit{ + a.providersMap[javaProvider] = ProviderInit{ port: port, image: Settings.JavaProviderImage, } case goProvider: - providers[goProvider] = ProviderInit{ + a.providersMap[goProvider] = ProviderInit{ port: port, image: Settings.GenericProviderImage, } case pythonProvider: - providers[pythonProvider] = ProviderInit{ + a.providersMap[pythonProvider] = ProviderInit{ port: port, image: Settings.GenericProviderImage, } case nodeJSProvider: - providers[nodeJSProvider] = ProviderInit{ + a.providersMap[nodeJSProvider] = ProviderInit{ port: port, image: Settings.GenericProviderImage, } - // TODO - // case dotNetProvider, dotNetFrameworkProvider: - // providers[nodeJSProvider] = ProviderInit{ - // port: port, - // image: "", - // } + } } - return providers, nil + return nil } func (a *analyzeCommand) validateProviders(providers []string) error { @@ -517,18 +499,63 @@ func (a *analyzeCommand) validateProviders(providers []string) error { pythonProvider, goProvider, nodeJSProvider, - dotNetProvider, - dotNetFrameworkProvider, } for _, prov := range providers { //validate other providers if !slices.Contains(validProvs, prov) { - return fmt.Errorf("provider %v not supported. Use --providerOverride option", prov) + return fmt.Errorf("provider %v not supported. Use --providerOverride or --provider option", prov) } } return nil } +func (a *analyzeCommand) ListSupportedProviders(ctx context.Context) error { + return a.fetchProviders(ctx, os.Stdout) +} + +func (a *analyzeCommand) fetchProviders(ctx context.Context, out io.Writer) error { + runMode := "RUN_MODE" + runModeContainer := "container" + if os.Getenv(runMode) == runModeContainer { + a.listAllProviders(out) + return nil + } else { + args := []string{"analyze", + "--list-providers", + } + err := container.NewContainer().Run( + ctx, + container.WithImage(Settings.RunnerImage), + container.WithLog(a.log.V(1)), + container.WithEnv(runMode, runModeContainer), + container.WithEntrypointBin(fmt.Sprintf("/usr/local/bin/%s", Settings.RootCommandName)), + container.WithContainerToolBin(Settings.PodmanBinary), + container.WithEntrypointArgs(args...), + container.WithStdout(out), + container.WithCleanup(a.cleanup), + ) + if err != nil { + a.log.Error(err, "failed listing labels") + return err + } + } + return nil +} + +func (a *analyzeCommand) listAllProviders(out io.Writer) { + supportedProvs := []string{ + "java", + "python", + "go", + "dotnet", + "nodejs", + } + fmt.Fprintln(out, "\navailable supported providers:") + for _, prov := range supportedProvs { + fmt.Fprintln(out, prov) + } +} + func (a *analyzeCommand) ListLabels(ctx context.Context) error { return a.fetchLabels(ctx, a.listSources, a.listTargets, os.Stdout) } @@ -685,7 +712,7 @@ func (a *analyzeCommand) getDepsFolders() (map[string]string, []string) { return vols, dependencyFolders } -func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (map[string]string, error) { +func (a *analyzeCommand) getConfigVolumes() (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) @@ -704,7 +731,7 @@ func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (ma javaConfig := provider.Config{ Name: javaProvider, - Address: fmt.Sprintf("0.0.0.0:%v", providers[javaProvider].port), + Address: fmt.Sprintf("0.0.0.0:%v", a.providersMap[javaProvider].port), InitConfig: []provider.InitConfig{ { Location: SourceMountPath, @@ -730,7 +757,7 @@ func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (ma goConfig := provider.Config{ Name: goProvider, - Address: fmt.Sprintf("0.0.0.0:%v", providers[goProvider].port), + Address: fmt.Sprintf("0.0.0.0:%v", a.providersMap[goProvider].port), InitConfig: []provider.InitConfig{ { AnalysisMode: provider.FullAnalysisMode, @@ -746,7 +773,7 @@ func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (ma pythonConfig := provider.Config{ Name: pythonProvider, - Address: fmt.Sprintf("0.0.0.0:%v", providers[pythonProvider].port), + Address: fmt.Sprintf("0.0.0.0:%v", a.providersMap[pythonProvider].port), InitConfig: []provider.InitConfig{ { AnalysisMode: provider.SourceOnlyAnalysisMode, @@ -761,7 +788,7 @@ func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (ma nodeJSConfig := provider.Config{ Name: nodeJSProvider, - Address: fmt.Sprintf("0.0.0.0:%v", providers[nodeJSProvider].port), + Address: fmt.Sprintf("0.0.0.0:%v", a.providersMap[nodeJSProvider].port), InitConfig: []provider.InitConfig{ { AnalysisMode: provider.SourceOnlyAnalysisMode, @@ -786,7 +813,7 @@ func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (ma }, } vols, dependencyFolders := a.getDepsFolders() - for prov, _ := range providers { + for prov, _ := range a.providersMap { switch prov { case javaProvider: provConfig = append(provConfig, javaConfig) @@ -804,7 +831,7 @@ func (a *analyzeCommand) getConfigVolumes(providers map[string]ProviderInit) (ma provConfig = append(provConfig, nodeJSConfig) } } - for prov, _ := range providers { + for prov, _ := range a.providersMap { err = a.getProviderOptions(tempDir, provConfig, prov) if err != nil { if errors.Is(err, os.ErrNotExist) { @@ -1035,39 +1062,43 @@ func (a *analyzeCommand) createContainerVolume() (string, error) { return volName, nil } -func (a *analyzeCommand) retryProviderContainer(ctx context.Context, networkName string, volName string, providers map[string]ProviderInit, retry int) error { +func (a *analyzeCommand) retryProviderContainer(ctx context.Context, networkName string, volName string, retry int) error { if retry == 0 { return fmt.Errorf("too many provider container retry attempts") } retry-- - _, err := a.RunProviders(ctx, networkName, volName, providers, retry) + err := a.RunProviders(ctx, networkName, volName, retry) if err != nil { return fmt.Errorf("error retrying run provider %v", err) } return nil } -func (a *analyzeCommand) RunProviders(ctx context.Context, networkName string, volName string, providers map[string]ProviderInit, retry int) (map[string]ProviderInit, error) { - runFirstProv := false - for prov, inits := range providers { - volumes := map[string]string{ - // application source code - volName: SourceMountPath, - } - vols, _ := a.getDepsFolders() - if len(vols) != 0 { - maps.Copy(volumes, vols) +func (a *analyzeCommand) RunProviders(ctx context.Context, networkName string, volName string, retry int) error { + volumes := map[string]string{ + // application source code + volName: SourceMountPath, + } + vols, _ := a.getDepsFolders() + if len(vols) != 0 { + maps.Copy(volumes, vols) + } + firstProvRun := false + for prov, init := range a.providersMap { + // if retrying provider, skip providers already running + if init.isRunning { + continue } - args := []string{fmt.Sprintf("--port=%v", inits.port)} + args := []string{fmt.Sprintf("--port=%v", init.port)} // we have to start the fist provider separately to create the shared // container network to then add other providers to the network - if !runFirstProv { + if !firstProvRun { a.log.Info("starting first provider", "provider", prov) con := container.NewContainer() err := con.Run( ctx, - container.WithImage(inits.image), + container.WithImage(init.image), container.WithLog(a.log.V(1)), container.WithVolumes(volumes), container.WithContainerToolBin(Settings.PodmanBinary), @@ -1077,20 +1108,21 @@ func (a *analyzeCommand) RunProviders(ctx context.Context, networkName string, v container.WithNetwork(networkName), ) if err != nil { - err := a.retryProviderContainer(ctx, networkName, volName, providers, retry) + err := a.retryProviderContainer(ctx, networkName, volName, retry) if err != nil { - return nil, err + return err } } a.providerContainerNames = append(a.providerContainerNames, con.Name) + init.isRunning = true } // start additional providers - if runFirstProv && len(providers) > 1 { + if firstProvRun && len(a.providersMap) > 1 { a.log.Info("starting provider", "provider", prov) con := container.NewContainer() err := con.Run( ctx, - container.WithImage(inits.image), + container.WithImage(init.image), container.WithLog(a.log.V(1)), container.WithVolumes(volumes), container.WithContainerToolBin(Settings.PodmanBinary), @@ -1100,16 +1132,17 @@ func (a *analyzeCommand) RunProviders(ctx context.Context, networkName string, v container.WithNetwork(fmt.Sprintf("container:%v", a.providerContainerNames[0])), ) if err != nil { - err := a.retryProviderContainer(ctx, networkName, volName, providers, retry) + err := a.retryProviderContainer(ctx, networkName, volName, retry) if err != nil { - return nil, err + return err } } a.providerContainerNames = append(a.providerContainerNames, con.Name) + init.isRunning = true } - runFirstProv = true + firstProvRun = true } - return providers, nil + return nil } func (a *analyzeCommand) RunAnalysisOverrideProviderSettings(ctx context.Context) error { @@ -1210,7 +1243,7 @@ func (a *analyzeCommand) RunAnalysisOverrideProviderSettings(ctx context.Context return nil } -func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, volName string, providersMap map[string]ProviderInit) error { +func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, volName string) error { volumes := map[string]string{ // application source code volName: SourceMountPath, @@ -1228,7 +1261,7 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, v // for cleanup purposes a.tempDirs = append(a.tempDirs, xmlOutputDir) } - configVols, err := a.getConfigVolumes(providersMap) + configVols, err := a.getConfigVolumes() if err != nil { a.log.V(1).Error(err, "failed to get config volumes for analysis") return err @@ -1250,7 +1283,7 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, v } // default rulesets are only java rules // may want to change this in the future - if _, ok := providersMap[javaProvider]; !ok { + if _, ok := a.providersMap[javaProvider]; !ok { a.enableDefaultRulesets = false } if a.enableDefaultRulesets { @@ -1278,17 +1311,18 @@ func (a *analyzeCommand) RunAnalysis(ctx context.Context, xmlOutputDir string, v args = append(args, fmt.Sprintf("--label-selector=%s", labelSelector)) } - // as of now only java has dep capability - if _, ok := providersMap[javaProvider]; ok { - // need a smarter way of handling this with a java provider + other provider - if len(providersMap) == 1 { - if !a.analyzeKnownLibraries { - args = append(args, - fmt.Sprintf("--dep-label-selector=(!%s=open-source)", provider.DepSourceLabel)) - } - a.log.Info("running dependency retrieval during analysis") - args = append(args, fmt.Sprintf("--dep-output-file=%s", DepsOutputMountPath)) + // as of now only java & go have dep capability + _, hasJava := a.providersMap[javaProvider] + _, hasGo := a.providersMap[goProvider] + // TODO currently cannot run these dep options with providers + // other than java and go + if (hasJava || hasGo) && len(a.providersMap) == 1 && a.mode == string(provider.FullAnalysisMode) { + if !a.analyzeKnownLibraries { + args = append(args, + fmt.Sprintf("--dep-label-selector=(!%s=open-source)", provider.DepSourceLabel)) } + a.log.Info("running dependency retrieval during analysis") + args = append(args, fmt.Sprintf("--dep-output-file=%s", DepsOutputMountPath)) } analysisLogFilePath := filepath.Join(a.output, "analysis.log") @@ -1358,32 +1392,37 @@ func (a *analyzeCommand) CreateJSONOutput() error { return err } - depData, err := os.ReadFile(depPath) - if err != nil { - return err - } - depOutput := &[]outputv1.DepsFlatItem{} - err = yaml.Unmarshal(depData, depOutput) - if err != nil { - a.log.V(1).Error(err, "failed to unmarshal dependencies yaml") - return err - } + // as of now only java & go have dep capability + _, hasJava := a.providersMap[javaProvider] + _, hasGo := a.providersMap[goProvider] + if (hasJava || hasGo) && len(a.providersMap) == 1 && a.mode == string(provider.FullAnalysisMode) { + depData, err := os.ReadFile(depPath) + if err != nil { + return err + } + depOutput := &[]outputv1.DepsFlatItem{} + err = yaml.Unmarshal(depData, depOutput) + if err != nil { + a.log.V(1).Error(err, "failed to unmarshal dependencies yaml") + return err + } - jsonDataDep, err := json.MarshalIndent(depOutput, "", " ") - if err != nil { - a.log.V(1).Error(err, "failed to marshal dependencies file to json") - return err - } - err = os.WriteFile(filepath.Join(a.output, "dependencies.json"), jsonDataDep, os.ModePerm) - if err != nil { - a.log.V(1).Error(err, "failed to write json dependencies output", "dir", a.output, "file", "dependencies.json") - return err + jsonDataDep, err := json.MarshalIndent(depOutput, "", " ") + if err != nil { + a.log.V(1).Error(err, "failed to marshal dependencies file to json") + return err + } + err = os.WriteFile(filepath.Join(a.output, "dependencies.json"), jsonDataDep, os.ModePerm) + if err != nil { + a.log.V(1).Error(err, "failed to write json dependencies output", "dir", a.output, "file", "dependencies.json") + return err + } } return nil } -func (a *analyzeCommand) GenerateStaticReport(ctx context.Context, provMap map[string]ProviderInit) error { +func (a *analyzeCommand) GenerateStaticReport(ctx context.Context) error { if a.skipStaticReport { return nil } @@ -1429,13 +1468,13 @@ func (a *analyzeCommand) GenerateStaticReport(ctx context.Context, provMap map[s outputDeps[i] = "" } } + // as of now, only java provider has dep capability - // TODO smarter way to handle this - if _, ok := provMap[javaProvider]; ok { - if len(provMap) == 1 && a.mode == string(provider.FullAnalysisMode) { - staticReportArgs = append(staticReportArgs, - fmt.Sprintf("--deps-output-list=%s", strings.Join(outputDeps, ","))) - } + _, hasJava := a.providersMap[javaProvider] + _, hasGo := a.providersMap[goProvider] + if (hasJava || hasGo) && a.mode == string(provider.FullAnalysisMode) && len(a.providersMap) == 1 { + staticReportArgs = append(staticReportArgs, + fmt.Sprintf("--deps-output-list=%s", strings.Join(outputDeps, ","))) } cpArgs := []string{"&& cp -r", @@ -1833,7 +1872,6 @@ func (a *analyzeCommand) RmVolumes(ctx context.Context) error { return cmd.Run() } -// TODO: multiple provider containers func (a *analyzeCommand) RmProviderContainers(ctx context.Context) error { for i := range a.providerContainerNames { con := a.providerContainerNames[i] @@ -1851,10 +1889,10 @@ func (a *analyzeCommand) RmProviderContainers(ctx context.Context) error { continue } } + return nil } -// TODO multiple providers func (a *analyzeCommand) getProviderLogs(ctx context.Context) error { if len(a.providerContainerNames) == 0 { return nil @@ -1865,16 +1903,20 @@ func (a *analyzeCommand) getProviderLogs(ctx context.Context) error { return fmt.Errorf("failed creating provider log file at %s", providerLogFilePath) } defer providerLog.Close() - a.log.V(1).Info("getting provider container logs", - "container", a.providerContainerNames[0]) + for i := range a.providerContainerNames { + a.log.V(1).Info("getting provider container logs", + "container", a.providerContainerNames[i]) - cmd := exec.CommandContext( - ctx, - Settings.PodmanBinary, - "logs", - a.providerContainerNames[0]) + cmd := exec.CommandContext( + ctx, + Settings.PodmanBinary, + "logs", + a.providerContainerNames[i]) - cmd.Stdout = providerLog - cmd.Stderr = providerLog - return cmd.Run() + cmd.Stdout = providerLog + cmd.Stderr = providerLog + return cmd.Run() + } + + return nil }