diff --git a/cmd/container.go b/cmd/container.go deleted file mode 100644 index c447838a..00000000 --- a/cmd/container.go +++ /dev/null @@ -1,123 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "github.com/zapier/kubechecks/pkg/app_watcher" - "github.com/zapier/kubechecks/pkg/appdir" - "github.com/zapier/kubechecks/pkg/argo_client" - "github.com/zapier/kubechecks/pkg/config" - "github.com/zapier/kubechecks/pkg/container" - "github.com/zapier/kubechecks/pkg/git" - client "github.com/zapier/kubechecks/pkg/kubernetes" - "github.com/zapier/kubechecks/pkg/vcs/github_client" - "github.com/zapier/kubechecks/pkg/vcs/gitlab_client" -) - -func newContainer(ctx context.Context, cfg config.ServerConfig, watchApps bool) (container.Container, error) { - var err error - - var ctr = container.Container{ - Config: cfg, - RepoManager: git.NewRepoManager(cfg), - } - - // create vcs client - switch cfg.VcsType { - case "gitlab": - ctr.VcsClient, err = gitlab_client.CreateGitlabClient(cfg) - case "github": - ctr.VcsClient, err = github_client.CreateGithubClient(cfg) - default: - err = fmt.Errorf("unknown vcs-type: %q", cfg.VcsType) - } - if err != nil { - return ctr, errors.Wrap(err, "failed to create vcs client") - } - var kubeClient client.Interface - - switch cfg.KubernetesType { - // TODO: expand with other cluster types - case client.ClusterTypeLOCAL: - kubeClient, err = client.New(&client.NewClientInput{ - KubernetesConfigPath: cfg.KubernetesConfig, - ClusterType: cfg.KubernetesType, - }) - if err != nil { - return ctr, errors.Wrap(err, "failed to create kube client") - } - case client.ClusterTypeEKS: - kubeClient, err = client.New(&client.NewClientInput{ - KubernetesConfigPath: cfg.KubernetesConfig, - ClusterType: cfg.KubernetesType, - }, - client.EKSClientOption(ctx, cfg.KubernetesClusterID), - ) - if err != nil { - return ctr, errors.Wrap(err, "failed to create kube client") - } - } - ctr.KubeClientSet = kubeClient - // create argo client - if ctr.ArgoClient, err = argo_client.NewArgoClient(cfg); err != nil { - return ctr, errors.Wrap(err, "failed to create argo client") - } - - // create vcs to argo map - vcsToArgoMap := appdir.NewVcsToArgoMap(ctr.VcsClient.Username()) - ctr.VcsToArgoMap = vcsToArgoMap - - // watch app modifications, if necessary - if cfg.MonitorAllApplications { - if err = buildAppsMap(ctx, ctr.ArgoClient, ctr.VcsToArgoMap); err != nil { - return ctr, errors.Wrap(err, "failed to build apps map") - } - - if err = buildAppSetsMap(ctx, ctr.ArgoClient, ctr.VcsToArgoMap); err != nil { - return ctr, errors.Wrap(err, "failed to build appsets map") - } - - if watchApps { - ctr.ApplicationWatcher, err = app_watcher.NewApplicationWatcher(kubeClient.Config(), vcsToArgoMap, cfg) - if err != nil { - return ctr, errors.Wrap(err, "failed to create watch applications") - } - ctr.ApplicationSetWatcher, err = app_watcher.NewApplicationSetWatcher(kubeClient.Config(), vcsToArgoMap, cfg) - if err != nil { - return ctr, errors.Wrap(err, "failed to create watch application sets") - } - - go ctr.ApplicationWatcher.Run(ctx, 1) - go ctr.ApplicationSetWatcher.Run(ctx) - } - } else { - log.Info().Msgf("not monitoring applications, MonitorAllApplications: %+v", cfg.MonitorAllApplications) - } - - return ctr, nil -} - -func buildAppsMap(ctx context.Context, argoClient *argo_client.ArgoClient, result container.VcsToArgoMap) error { - apps, err := argoClient.GetApplications(ctx) - if err != nil { - return errors.Wrap(err, "failed to list applications") - } - for _, app := range apps.Items { - result.AddApp(&app) - } - return nil -} - -func buildAppSetsMap(ctx context.Context, argoClient *argo_client.ArgoClient, result container.VcsToArgoMap) error { - appSets, err := argoClient.GetApplicationSets(ctx) - if err != nil { - return errors.Wrap(err, "failed to list application sets") - } - for _, appSet := range appSets.Items { - result.AddAppSet(&appSet) - } - return nil -} diff --git a/cmd/controller_cmd.go b/cmd/controller.go similarity index 85% rename from cmd/controller_cmd.go rename to cmd/controller.go index 5b8b0440..2345b3f0 100644 --- a/cmd/controller_cmd.go +++ b/cmd/controller.go @@ -11,6 +11,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/zapier/kubechecks/pkg/app_watcher" "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/checks" @@ -41,19 +42,39 @@ var ControllerCmd = &cobra.Command{ log.Fatal().Err(err).Msg("failed to parse configuration") } - ctr, err := newContainer(ctx, cfg, true) + ctr, err := container.New(ctx, cfg) if err != nil { log.Fatal().Err(err).Msg("failed to create container") } + // watch app modifications, if necessary + if cfg.MonitorAllApplications { + appWatcher, err := app_watcher.NewApplicationWatcher(ctr) + if err != nil { + log.Fatal().Err(err).Msg("failed to create watch applications") + } + go appWatcher.Run(ctx, 1) + + appSetWatcher, err := app_watcher.NewApplicationSetWatcher(ctr) + if err != nil { + log.Fatal().Err(err).Msg("failed to create watch application sets") + } + go appSetWatcher.Run(ctx) + } else { + log.Info().Msgf("not monitoring applications, MonitorAllApplications: %+v", cfg.MonitorAllApplications) + } + log.Info().Msg("initializing git settings") if err = initializeGit(ctr); err != nil { log.Fatal().Err(err).Msg("failed to initialize git settings") } + log.Info().Strs("locations", cfg.PoliciesLocation).Msg("processing policies locations") if err = processLocations(ctx, ctr, cfg.PoliciesLocation); err != nil { log.Fatal().Err(err).Msg("failed to process policy locations") } + + log.Info().Strs("locations", cfg.SchemasLocations).Msg("processing schemas locations") if err = processLocations(ctx, ctr, cfg.SchemasLocations); err != nil { log.Fatal().Err(err).Msg("failed to process schema locations") } diff --git a/cmd/locations.go b/cmd/locations.go index 47b5774d..62705816 100644 --- a/cmd/locations.go +++ b/cmd/locations.go @@ -25,6 +25,8 @@ func processLocations(ctx context.Context, ctr container.Container, locations [] } } + log.Debug().Strs("locations", locations).Msg("locations after processing") + return nil } diff --git a/cmd/process.go b/cmd/process.go index 208ab847..456e0b0f 100644 --- a/cmd/process.go +++ b/cmd/process.go @@ -1,8 +1,13 @@ package cmd import ( + "os" + "path/filepath" + + "github.com/argoproj/argo-cd/v2/common" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/config" "github.com/zapier/kubechecks/pkg/server" @@ -15,14 +20,42 @@ var processCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() + tempPath, err := os.MkdirTemp("", "") + if err != nil { + log.Fatal().Err(err).Msg("fail to create ssh data dir") + } + defer func() { + os.RemoveAll(tempPath) + }() + + // symlink local ssh known hosts to argocd ssh known hosts + homeDir, err := os.UserHomeDir() + if err != nil { + log.Fatal().Err(err).Msg("failed to get user home dir") + } + source := filepath.Join(homeDir, ".ssh", "known_hosts") + target := filepath.Join(tempPath, common.DefaultSSHKnownHostsName) + + if err := os.Symlink(source, target); err != nil { + log.Fatal().Err(err).Msg("fail to symlink ssh_known_hosts file") + } + + if err := os.Setenv("ARGOCD_SSH_DATA_PATH", tempPath); err != nil { + log.Fatal().Err(err).Msg("fail to set ARGOCD_SSH_DATA_PATH") + } + cfg, err := config.New() if err != nil { log.Fatal().Err(err).Msg("failed to generate config") } - ctr, err := newContainer(ctx, cfg, false) + if len(args) != 1 { + log.Fatal().Msg("usage: kubechecks process PR_REF") + } + + ctr, err := container.New(ctx, cfg) if err != nil { - log.Fatal().Err(err).Msg("failed to create container") + log.Fatal().Err(err).Msg("failed to create clients") } log.Info().Msg("initializing git settings") diff --git a/cmd/root.go b/cmd/root.go index 470f675b..554a76b6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -45,7 +45,7 @@ func init() { zerolog.LevelDebugValue, zerolog.LevelTraceValue, ). - withDefault("info"). + withDefault("debug"). withShortHand("l"), ) boolFlag(flags, "persist-log-level", "Persists the set log level down to other module loggers.") @@ -79,7 +79,7 @@ func init() { newStringOpts(). withChoices("hide", "delete"). withDefault("hide")) - stringSliceFlag(flags, "schemas-location", "Sets schema locations to be used for every check request. Can be common paths inside the repos being checked or git urls in either git or http(s) format.") + stringSliceFlag(flags, "schemas-location", "Sets schema locations to be used for every check request. Can be a common path on the host or git urls in either git or http(s) format.") boolFlag(flags, "enable-conftest", "Set to true to enable conftest policy checking of manifests.") stringSliceFlag(flags, "policies-location", "Sets rego policy locations to be used for every check request. Can be common path inside the repos being checked or git urls in either git or http(s) format.", newStringSliceOpts(). @@ -115,14 +115,15 @@ func init() { } func setupLogOutput() { - output := zerolog.ConsoleWriter{Out: os.Stdout} - log.Logger = log.Output(output) - // Default level is info, unless debug flag is present levelFlag := viper.GetString("log-level") level, _ := zerolog.ParseLevel(levelFlag) zerolog.SetGlobalLevel(level) + + output := zerolog.ConsoleWriter{Out: os.Stdout} + log.Logger = log.Output(output) + log.Debug().Msg("Debug level logging enabled.") log.Trace().Msg("Trace level logging enabled.") log.Info().Msg("Initialized logger.") diff --git a/pkg/affected_apps/argocd_matcher.go b/pkg/affected_apps/argocd_matcher.go index 0c1b7ded..935daeeb 100644 --- a/pkg/affected_apps/argocd_matcher.go +++ b/pkg/affected_apps/argocd_matcher.go @@ -6,7 +6,6 @@ import ( "github.com/rs/zerolog/log" "github.com/zapier/kubechecks/pkg/appdir" - "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/git" ) @@ -15,7 +14,7 @@ type ArgocdMatcher struct { appSetsDirectory *appdir.AppSetDirectory } -func NewArgocdMatcher(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo) (*ArgocdMatcher, error) { +func NewArgocdMatcher(vcsToArgoMap appdir.VcsToArgoMap, repo *git.Repo) (*ArgocdMatcher, error) { repoApps := getArgocdApps(vcsToArgoMap, repo) kustomizeAppFiles := getKustomizeApps(vcsToArgoMap, repo, repo.Directory) @@ -41,7 +40,7 @@ func logCounts(repoApps *appdir.AppDirectory) { } } -func getKustomizeApps(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo, repoPath string) *appdir.AppDirectory { +func getKustomizeApps(vcsToArgoMap appdir.VcsToArgoMap, repo *git.Repo, repoPath string) *appdir.AppDirectory { log.Debug().Msgf("creating fs for %s", repoPath) fs := os.DirFS(repoPath) log.Debug().Msg("following kustomize apps") @@ -51,7 +50,7 @@ func getKustomizeApps(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo, repoP return kustomizeAppFiles } -func getArgocdApps(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo) *appdir.AppDirectory { +func getArgocdApps(vcsToArgoMap appdir.VcsToArgoMap, repo *git.Repo) *appdir.AppDirectory { log.Debug().Msgf("looking for %s repos", repo.CloneURL) repoApps := vcsToArgoMap.GetAppsInRepo(repo.CloneURL) @@ -59,7 +58,7 @@ func getArgocdApps(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo) *appdir. return repoApps } -func getArgocdAppSets(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo) *appdir.AppSetDirectory { +func getArgocdAppSets(vcsToArgoMap appdir.VcsToArgoMap, repo *git.Repo) *appdir.AppSetDirectory { log.Debug().Msgf("looking for %s repos", repo.CloneURL) repoApps := vcsToArgoMap.GetAppSetsInRepo(repo.CloneURL) diff --git a/pkg/app_watcher/app_watcher.go b/pkg/app_watcher/app_watcher.go index adb39c26..db183dff 100644 --- a/pkg/app_watcher/app_watcher.go +++ b/pkg/app_watcher/app_watcher.go @@ -12,11 +12,11 @@ import ( informers "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions/application/v1alpha1" applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1" "github.com/rs/zerolog/log" + "github.com/zapier/kubechecks/pkg/appdir" + "github.com/zapier/kubechecks/pkg/container" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" - "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/config" ) @@ -34,16 +34,16 @@ type ApplicationWatcher struct { // - kubeCfg is the Kubernetes configuration. // - vcsToArgoMap is the mapping between VCS and Argo applications. // - cfg is the server configuration. -func NewApplicationWatcher(kubeCfg *rest.Config, vcsToArgoMap appdir.VcsToArgoMap, cfg config.ServerConfig) (*ApplicationWatcher, error) { - if kubeCfg == nil { +func NewApplicationWatcher(ctr container.Container) (*ApplicationWatcher, error) { + if ctr.KubeClientSet == nil { return nil, fmt.Errorf("kubeCfg cannot be nil") } ctrl := ApplicationWatcher{ - applicationClientset: appclientset.NewForConfigOrDie(kubeCfg), - vcsToArgoMap: vcsToArgoMap, + applicationClientset: appclientset.NewForConfigOrDie(ctr.KubeClientSet.Config()), + vcsToArgoMap: ctr.VcsToArgoMap, } - appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second*30, cfg) + appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second*30, ctr.Config) ctrl.appInformer = appInformer ctrl.appLister = appLister @@ -152,14 +152,14 @@ func canProcessApp(obj interface{}) (*appv1alpha1.Application, bool) { return nil, false } - for _, src := range app.Spec.Sources { + if src := app.Spec.Source; src != nil { if isGitRepo(src.RepoURL) { return app, true } } - if app.Spec.Source != nil { - if isGitRepo(app.Spec.Source.RepoURL) { + for _, src := range app.Spec.Sources { + if isGitRepo(src.RepoURL) { return app, true } } diff --git a/pkg/app_watcher/appset_watcher.go b/pkg/app_watcher/appset_watcher.go index cc90fed6..c98fedf6 100644 --- a/pkg/app_watcher/appset_watcher.go +++ b/pkg/app_watcher/appset_watcher.go @@ -13,8 +13,8 @@ import ( "github.com/rs/zerolog/log" "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/container" "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" ) @@ -28,16 +28,16 @@ type ApplicationSetWatcher struct { } // NewApplicationSetWatcher creates new instance of ApplicationWatcher. -func NewApplicationSetWatcher(kubeCfg *rest.Config, vcsToArgoMap appdir.VcsToArgoMap, cfg config.ServerConfig) (*ApplicationSetWatcher, error) { - if kubeCfg == nil { +func NewApplicationSetWatcher(ctr container.Container) (*ApplicationSetWatcher, error) { + if ctr.KubeClientSet == nil { return nil, fmt.Errorf("kubeCfg cannot be nil") } ctrl := ApplicationSetWatcher{ - applicationClientset: appclientset.NewForConfigOrDie(kubeCfg), - vcsToArgoMap: vcsToArgoMap, + applicationClientset: appclientset.NewForConfigOrDie(ctr.KubeClientSet.Config()), + vcsToArgoMap: ctr.VcsToArgoMap, } - appInformer, appLister := ctrl.newApplicationSetInformerAndLister(time.Second*30, cfg) + appInformer, appLister := ctrl.newApplicationSetInformerAndLister(time.Second*30, ctr.Config) ctrl.appInformer = appInformer ctrl.appLister = appLister diff --git a/pkg/appdir/vcstoargomap.go b/pkg/appdir/vcstoargomap.go index 0becae94..eeafc070 100644 --- a/pkg/appdir/vcstoargomap.go +++ b/pkg/appdir/vcstoargomap.go @@ -79,37 +79,39 @@ func (v2a VcsToArgoMap) WalkKustomizeApps(cloneURL string, fs fs.FS) *AppDirecto return result } -func (v2a VcsToArgoMap) AddApp(app *v1alpha1.Application) { - if app.Spec.Source == nil { - log.Warn().Msgf("%s/%s: no source, skipping", app.Namespace, app.Name) - return +func (v2a VcsToArgoMap) processApp(app v1alpha1.Application, fn func(*AppDirectory)) { + + if src := app.Spec.Source; src != nil { + appDirectory := v2a.GetAppsInRepo(src.RepoURL) + fn(appDirectory) } - appDirectory := v2a.GetAppsInRepo(app.Spec.Source.RepoURL) - appDirectory.ProcessApp(*app) + for _, src := range app.Spec.Sources { + appDirectory := v2a.GetAppsInRepo(src.RepoURL) + fn(appDirectory) + } } -func (v2a VcsToArgoMap) UpdateApp(old *v1alpha1.Application, new *v1alpha1.Application) { - if new.Spec.Source == nil { - log.Warn().Msgf("%s/%s: no source, skipping", new.Namespace, new.Name) - return - } +func (v2a VcsToArgoMap) AddApp(app *v1alpha1.Application) { + v2a.processApp(*app, func(directory *AppDirectory) { + directory.AddApp(*app) + }) +} - oldAppDirectory := v2a.GetAppsInRepo(old.Spec.Source.RepoURL) - oldAppDirectory.RemoveApp(*old) +func (v2a VcsToArgoMap) UpdateApp(old *v1alpha1.Application, new *v1alpha1.Application) { + v2a.processApp(*old, func(directory *AppDirectory) { + directory.RemoveApp(*old) + }) - newAppDirectory := v2a.GetAppsInRepo(new.Spec.Source.RepoURL) - newAppDirectory.ProcessApp(*new) + v2a.processApp(*new, func(directory *AppDirectory) { + directory.AddApp(*new) + }) } func (v2a VcsToArgoMap) DeleteApp(app *v1alpha1.Application) { - if app.Spec.Source == nil { - log.Warn().Msgf("%s/%s: no source, skipping", app.Namespace, app.Name) - return - } - - oldAppDirectory := v2a.GetAppsInRepo(app.Spec.Source.RepoURL) - oldAppDirectory.RemoveApp(*app) + v2a.processApp(*app, func(directory *AppDirectory) { + directory.RemoveApp(*app) + }) } func (v2a VcsToArgoMap) GetVcsRepos() []string { diff --git a/pkg/argo_client/applications.go b/pkg/argo_client/applications.go index 8694a555..11acb80c 100644 --- a/pkg/argo_client/applications.go +++ b/pkg/argo_client/applications.go @@ -24,11 +24,11 @@ var ErrNoVersionFound = errors.New("no kubernetes version found") // GetApplicationByName takes a context and a name, then queries the Argo Application client to retrieve the Application with the specified name. // It returns the found Application and any error encountered during the process. // If successful, the Application client connection is closed before returning. -func (argo *ArgoClient) GetApplicationByName(ctx context.Context, name string) (*v1alpha1.Application, error) { +func (a *ArgoClient) GetApplicationByName(ctx context.Context, name string) (*v1alpha1.Application, error) { ctx, span := tracer.Start(ctx, "GetApplicationByName") defer span.End() - closer, appClient := argo.GetApplicationClient() + closer, appClient := a.GetApplicationClient() defer closer.Close() resp, err := appClient.Get(ctx, &application.ApplicationQuery{Name: &name}) @@ -43,7 +43,7 @@ func (argo *ArgoClient) GetApplicationByName(ctx context.Context, name string) ( // GetKubernetesVersionByApplication is a method on the ArgoClient struct that takes a context and an application name as parameters, // and returns the Kubernetes version of the destination cluster where the specified application is running. // It returns an error if the application or cluster information cannot be retrieved. -func (argo *ArgoClient) GetKubernetesVersionByApplication(ctx context.Context, app v1alpha1.Application) (string, error) { +func (a *ArgoClient) GetKubernetesVersionByApplication(ctx context.Context, app v1alpha1.Application) (string, error) { ctx, span := tracer.Start(ctx, "GetKubernetesVersionByApplicationName") defer span.End() @@ -58,7 +58,7 @@ func (argo *ArgoClient) GetKubernetesVersionByApplication(ctx context.Context, a } // Get cluster client - clusterCloser, clusterClient := argo.GetClusterClient() + clusterCloser, clusterClient := a.GetClusterClient() defer clusterCloser.Close() // Get cluster @@ -85,11 +85,11 @@ func (argo *ArgoClient) GetKubernetesVersionByApplication(ctx context.Context, a // GetApplicationsByLabels takes a context and a labelselector, then queries the Argo Application client to retrieve the Applications with the specified label. // It returns the found ApplicationList and any error encountered during the process. // If successful, the Application client connection is closed before returning. -func (argo *ArgoClient) GetApplicationsByLabels(ctx context.Context, labels string) (*v1alpha1.ApplicationList, error) { +func (a *ArgoClient) GetApplicationsByLabels(ctx context.Context, labels string) (*v1alpha1.ApplicationList, error) { ctx, span := tracer.Start(ctx, "GetApplicationsByLabels") defer span.End() - closer, appClient := argo.GetApplicationClient() + closer, appClient := a.GetApplicationClient() defer closer.Close() resp, err := appClient.List(ctx, &application.ApplicationQuery{Selector: &labels}) @@ -103,31 +103,31 @@ func (argo *ArgoClient) GetApplicationsByLabels(ctx context.Context, labels stri // GetApplicationsByAppset takes a context and an appset, then queries the Argo Application client to retrieve the Applications managed by the appset // It returns the found ApplicationList and any error encountered during the process. -func (argo *ArgoClient) GetApplicationsByAppset(ctx context.Context, name string) (*v1alpha1.ApplicationList, error) { +func (a *ArgoClient) GetApplicationsByAppset(ctx context.Context, name string) (*v1alpha1.ApplicationList, error) { appsetLabelSelector := "argocd.argoproj.io/application-set-name=" + name - return argo.GetApplicationsByLabels(ctx, appsetLabelSelector) + return a.GetApplicationsByLabels(ctx, appsetLabelSelector) } -func (argo *ArgoClient) GetApplications(ctx context.Context) (*v1alpha1.ApplicationList, error) { +func (a *ArgoClient) GetApplications(ctx context.Context) (*v1alpha1.ApplicationList, error) { ctx, span := tracer.Start(ctx, "GetApplications") defer span.End() - closer, appClient := argo.GetApplicationClient() + closer, appClient := a.GetApplicationClient() defer closer.Close() resp, err := appClient.List(ctx, new(application.ApplicationQuery)) if err != nil { telemetry.SetError(span, err, "Argo List All Applications error") - return nil, errors.Wrap(err, "failed to applications") + return nil, errors.Wrap(err, "failed to list applications") } return resp, nil } -func (argo *ArgoClient) GetApplicationSets(ctx context.Context) (*v1alpha1.ApplicationSetList, error) { +func (a *ArgoClient) GetApplicationSets(ctx context.Context) (*v1alpha1.ApplicationSetList, error) { ctx, span := tracer.Start(ctx, "GetApplications") defer span.End() - closer, appClient := argo.GetApplicationSetClient() + closer, appClient := a.GetApplicationSetClient() defer closer.Close() resp, err := appClient.List(ctx, new(applicationset.ApplicationSetListQuery)) diff --git a/pkg/argo_client/client.go b/pkg/argo_client/client.go index ac3af7c9..a93cbcb7 100644 --- a/pkg/argo_client/client.go +++ b/pkg/argo_client/client.go @@ -9,6 +9,9 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset" "github.com/argoproj/argo-cd/v2/pkg/apiclient/settings" "github.com/rs/zerolog/log" + client "github.com/zapier/kubechecks/pkg/kubernetes" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster" @@ -19,9 +22,16 @@ type ArgoClient struct { client apiclient.Client manifestsLock sync.Mutex + + namespace string + k8s kubernetes.Interface + k8sConfig *rest.Config } -func NewArgoClient(cfg config.ServerConfig) (*ArgoClient, error) { +func NewArgoClient( + cfg config.ServerConfig, + k8s client.Interface, +) (*ArgoClient, error) { opts := &apiclient.ClientOptions{ ServerAddr: cfg.ArgoCDServerAddr, AuthToken: cfg.ArgoCDToken, @@ -43,37 +53,40 @@ func NewArgoClient(cfg config.ServerConfig) (*ArgoClient, error) { } return &ArgoClient{ - client: argo, + client: argo, + namespace: cfg.ArgoCDNamespace, + k8s: k8s.ClientSet(), + k8sConfig: k8s.Config(), }, nil } // GetApplicationClient has related argocd diff code https://github.com/argoproj/argo-cd/blob/d3ff9757c460ae1a6a11e1231251b5d27aadcdd1/cmd/argocd/commands/app.go#L899 -func (argo *ArgoClient) GetApplicationClient() (io.Closer, application.ApplicationServiceClient) { - closer, appClient, err := argo.client.NewApplicationClient() +func (a *ArgoClient) GetApplicationClient() (io.Closer, application.ApplicationServiceClient) { + closer, appClient, err := a.client.NewApplicationClient() if err != nil { log.Fatal().Err(err).Msg("could not create ArgoCD Application Client") } return closer, appClient } -func (argo *ArgoClient) GetApplicationSetClient() (io.Closer, applicationset.ApplicationSetServiceClient) { - closer, appClient, err := argo.client.NewApplicationSetClient() +func (a *ArgoClient) GetApplicationSetClient() (io.Closer, applicationset.ApplicationSetServiceClient) { + closer, appClient, err := a.client.NewApplicationSetClient() if err != nil { log.Fatal().Err(err).Msg("could not create ArgoCD Application Set Client") } return closer, appClient } -func (argo *ArgoClient) GetSettingsClient() (io.Closer, settings.SettingsServiceClient) { - closer, appClient, err := argo.client.NewSettingsClient() +func (a *ArgoClient) GetSettingsClient() (io.Closer, settings.SettingsServiceClient) { + closer, appClient, err := a.client.NewSettingsClient() if err != nil { log.Fatal().Err(err).Msg("could not create ArgoCD Settings Client") } return closer, appClient } -func (argo *ArgoClient) GetClusterClient() (io.Closer, cluster.ClusterServiceClient) { - closer, clusterClient, err := argo.client.NewClusterClient() +func (a *ArgoClient) GetClusterClient() (io.Closer, cluster.ClusterServiceClient) { + closer, clusterClient, err := a.client.NewClusterClient() if err != nil { log.Fatal().Err(err).Msg("could not create ArgoCD Cluster Client") } diff --git a/pkg/argo_client/manifests.go b/pkg/argo_client/manifests.go index 0587e223..f22527d2 100644 --- a/pkg/argo_client/manifests.go +++ b/pkg/argo_client/manifests.go @@ -19,7 +19,7 @@ import ( "github.com/zapier/kubechecks/telemetry" ) -func (argo *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir, changedAppFilePath string, app argoappv1.Application) ([]string, error) { +func (a *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir, changedAppFilePath string, app argoappv1.Application) ([]string, error) { var err error ctx, span := tracer.Start(ctx, "GetManifestsLocal") @@ -33,10 +33,10 @@ func (argo *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir getManifestsDuration.WithLabelValues(name).Observe(duration.Seconds()) }() - clusterCloser, clusterClient := argo.GetClusterClient() + clusterCloser, clusterClient := a.GetClusterClient() defer clusterCloser.Close() - settingsCloser, settingsClient := argo.GetSettingsClient() + settingsCloser, settingsClient := a.GetSettingsClient() defer settingsCloser.Close() log.Debug(). @@ -58,7 +58,7 @@ func (argo *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir } log.Debug().Str("name", name).Msg("generating diff for application...") - res, err := argo.generateManifests(ctx, fmt.Sprintf("%s/%s", tempRepoDir, changedAppFilePath), tempRepoDir, app, argoSettings, cluster) + res, err := a.generateManifests(ctx, fmt.Sprintf("%s/%s", tempRepoDir, changedAppFilePath), tempRepoDir, app, argoSettings, cluster) if err != nil { telemetry.SetError(span, err, "Generate Manifests") return nil, errors.Wrap(err, "failed to generate manifests") @@ -71,30 +71,57 @@ func (argo *ArgoClient) GetManifestsLocal(ctx context.Context, name, tempRepoDir return res.Manifests, nil } -func (argo *ArgoClient) generateManifests( +type repoRef struct { + // revision is the git revision - can be any valid revision like a branch, tag, or commit SHA. + revision string + // commitSHA is the actual commit to which revision refers. + commitSHA string + // key is the name of the key which was used to reference this repo. + key string +} + +func (a *ArgoClient) generateManifests( ctx context.Context, appPath, tempRepoDir string, app argoappv1.Application, argoSettings *settings.Settings, cluster *argoappv1.Cluster, ) (*repoapiclient.ManifestResponse, error) { - argo.manifestsLock.Lock() - defer argo.manifestsLock.Unlock() + a.manifestsLock.Lock() + defer a.manifestsLock.Unlock() source := app.Spec.GetSource() + var projectSourceRepos []string + var helmRepos []*argoappv1.Repository + var helmCreds []*argoappv1.RepoCreds + var enableGenerateManifests map[string]bool + var helmOptions *argoappv1.HelmOptions + var refSources map[string]*argoappv1.RefTarget + + q := repoapiclient.ManifestRequest{ + Repo: &argoappv1.Repository{Repo: source.RepoURL}, + Revision: source.TargetRevision, + AppLabelKey: argoSettings.AppLabelKey, + AppName: app.Name, + Namespace: app.Spec.Destination.Namespace, + ApplicationSource: &source, + Repos: helmRepos, + KustomizeOptions: argoSettings.KustomizeOptions, + KubeVersion: cluster.Info.ServerVersion, + ApiVersions: cluster.Info.APIVersions, + HelmRepoCreds: helmCreds, + TrackingMethod: argoSettings.TrackingMethod, + EnabledSourceTypes: enableGenerateManifests, + HelmOptions: helmOptions, + HasMultipleSources: app.Spec.HasMultipleSources(), + RefSources: refSources, + ProjectSourceRepos: projectSourceRepos, + ProjectName: app.Spec.Project, + } + return repository.GenerateManifests( ctx, appPath, tempRepoDir, source.TargetRevision, - &repoapiclient.ManifestRequest{ - Repo: &argoappv1.Repository{Repo: source.RepoURL}, - AppLabelKey: argoSettings.AppLabelKey, - AppName: app.Name, - Namespace: app.Spec.Destination.Namespace, - ApplicationSource: &source, - KustomizeOptions: argoSettings.KustomizeOptions, - KubeVersion: cluster.Info.ServerVersion, - ApiVersions: cluster.Info.APIVersions, - TrackingMethod: argoSettings.TrackingMethod, - }, + &q, true, new(git.NoopCredsStore), resource.MustParse("0"), diff --git a/pkg/checks/kubeconform/check.go b/pkg/checks/kubeconform/check.go index 2198470b..5dcfdb02 100644 --- a/pkg/checks/kubeconform/check.go +++ b/pkg/checks/kubeconform/check.go @@ -8,8 +8,5 @@ import ( ) func Check(ctx context.Context, request checks.Request) (msg.Result, error) { - return argoCdAppValidate( - ctx, request.Container, request.AppName, request.KubernetesVersion, request.Repo.Directory, - request.YamlManifests, - ) + return argoCdAppValidate(ctx, request.Container, request.AppName, request.KubernetesVersion, request.YamlManifests) } diff --git a/pkg/checks/kubeconform/validate.go b/pkg/checks/kubeconform/validate.go index 71439ef3..efba18e2 100644 --- a/pkg/checks/kubeconform/validate.go +++ b/pkg/checks/kubeconform/validate.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "os" - "path/filepath" "strings" "github.com/pkg/errors" @@ -20,7 +19,7 @@ import ( var tracer = otel.Tracer("pkg/checks/kubeconform") -func getSchemaLocations(ctx context.Context, ctr container.Container, tempRepoPath string) []string { +func getSchemaLocations(ctr container.Container) []string { cfg := ctr.Config locations := []string{ @@ -29,28 +28,13 @@ func getSchemaLocations(ctx context.Context, ctr container.Container, tempRepoPa } // schemas configured globally - for _, schemasLocation := range cfg.SchemasLocations { - if strings.HasPrefix(schemasLocation, "http://") || strings.HasPrefix(schemasLocation, "https://") { - locations = append(locations, schemasLocation) - } else { - if !filepath.IsAbs(schemasLocation) { - schemasLocation = filepath.Join(tempRepoPath, schemasLocation) - } - - if _, err := os.Stat(schemasLocation); err != nil { - log.Warn(). - Err(err). - Str("path", schemasLocation). - Msg("schemas location is invalid, skipping") - } else { - locations = append(locations, schemasLocation) - } - } - } + locations = append(locations, cfg.SchemasLocations...) for index := range locations { location := locations[index] + oldLocation := location if location == "default" || strings.Contains(location, "{{") { + log.Debug().Str("location", location).Msg("location requires no processing to be valid") continue } @@ -60,12 +44,14 @@ func getSchemaLocations(ctx context.Context, ctr container.Container, tempRepoPa location += "{{ .NormalizedKubernetesVersion }}/{{ .ResourceKind }}{{ .KindSuffix }}.json" locations[index] = location + + log.Debug().Str("old", oldLocation).Str("new", location).Msg("processed schema location") } return locations } -func argoCdAppValidate(ctx context.Context, ctr container.Container, appName, targetKubernetesVersion, tempRepoPath string, appManifests []string) (msg.Result, error) { +func argoCdAppValidate(ctx context.Context, ctr container.Container, appName, targetKubernetesVersion string, appManifests []string) (msg.Result, error) { _, span := tracer.Start(ctx, "ArgoCdAppValidate") defer span.End() @@ -92,7 +78,7 @@ func argoCdAppValidate(ctx context.Context, ctr container.Container, appName, ta var ( outputString []string - schemaLocations = getSchemaLocations(ctx, ctr, tempRepoPath) + schemaLocations = getSchemaLocations(ctr) ) log.Debug().Msgf("cache location: %s", vOpts.Cache) diff --git a/pkg/checks/kubeconform/validate_test.go b/pkg/checks/kubeconform/validate_test.go index bd502b01..b68324cc 100644 --- a/pkg/checks/kubeconform/validate_test.go +++ b/pkg/checks/kubeconform/validate_test.go @@ -1,7 +1,6 @@ package kubeconform import ( - "context" "fmt" "os" "strings" @@ -16,9 +15,8 @@ import ( ) func TestDefaultGetSchemaLocations(t *testing.T) { - ctx := context.TODO() ctr := container.Container{} - schemaLocations := getSchemaLocations(ctx, ctr, "/some/other/path") + schemaLocations := getSchemaLocations(ctr) // default schema location is "./schemas" assert.Len(t, schemaLocations, 1) @@ -26,7 +24,6 @@ func TestDefaultGetSchemaLocations(t *testing.T) { } func TestGetRemoteSchemaLocations(t *testing.T) { - ctx := context.TODO() ctr := container.Container{} if os.Getenv("CI") == "" { @@ -39,7 +36,7 @@ func TestGetRemoteSchemaLocations(t *testing.T) { // t.Setenv("KUBECHECKS_SCHEMAS_LOCATION", fixture.URL) // doesn't work because viper needs to initialize from root, which doesn't happen viper.Set("schemas-location", []string{fixture.URL}) - schemaLocations := getSchemaLocations(ctx, ctr, "/some/other/path") + schemaLocations := getSchemaLocations(ctr) hasTmpDirPrefix := strings.HasPrefix(schemaLocations[0], "/tmp/schemas") assert.Equal(t, hasTmpDirPrefix, true, "invalid schemas location. Schema location should have prefix /tmp/schemas but has %s", schemaLocations[0]) } diff --git a/pkg/container/main.go b/pkg/container/main.go index a330af3f..4a281a43 100644 --- a/pkg/container/main.go +++ b/pkg/container/main.go @@ -2,13 +2,14 @@ package container import ( "context" - "io/fs" + "fmt" - "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" client "github.com/zapier/kubechecks/pkg/kubernetes" + "github.com/zapier/kubechecks/pkg/vcs/github_client" + "github.com/zapier/kubechecks/pkg/vcs/gitlab_client" - "github.com/zapier/kubechecks/pkg" - "github.com/zapier/kubechecks/pkg/app_watcher" "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/argo_client" "github.com/zapier/kubechecks/pkg/config" @@ -17,35 +18,107 @@ import ( ) type Container struct { - ApplicationWatcher *app_watcher.ApplicationWatcher - ApplicationSetWatcher *app_watcher.ApplicationSetWatcher - ArgoClient *argo_client.ArgoClient + ArgoClient *argo_client.ArgoClient Config config.ServerConfig RepoManager *git.RepoManager VcsClient vcs.Client - VcsToArgoMap VcsToArgoMap + VcsToArgoMap appdir.VcsToArgoMap KubeClientSet client.Interface } -type VcsToArgoMap interface { - AddApp(*v1alpha1.Application) - AddAppSet(*v1alpha1.ApplicationSet) - UpdateApp(old, new *v1alpha1.Application) - UpdateAppSet(old *v1alpha1.ApplicationSet, new *v1alpha1.ApplicationSet) - DeleteApp(*v1alpha1.Application) - DeleteAppSet(app *v1alpha1.ApplicationSet) - GetVcsRepos() []string - GetAppsInRepo(string) *appdir.AppDirectory - GetAppSetsInRepo(string) *appdir.AppSetDirectory - GetMap() map[pkg.RepoURL]*appdir.AppDirectory - WalkKustomizeApps(cloneURL string, fs fs.FS) *appdir.AppDirectory -} - type ReposCache interface { Clone(ctx context.Context, repoUrl string) (string, error) CloneWithBranch(ctx context.Context, repoUrl, targetBranch string) (string, error) } + +func New(ctx context.Context, cfg config.ServerConfig) (Container, error) { + var err error + + var ctr = Container{ + Config: cfg, + RepoManager: git.NewRepoManager(cfg), + } + + // create vcs client + switch cfg.VcsType { + case "gitlab": + ctr.VcsClient, err = gitlab_client.CreateGitlabClient(cfg) + case "github": + ctr.VcsClient, err = github_client.CreateGithubClient(cfg) + default: + err = fmt.Errorf("unknown vcs-type: %q", cfg.VcsType) + } + if err != nil { + return ctr, errors.Wrap(err, "failed to create vcs client") + } + var kubeClient client.Interface + + switch cfg.KubernetesType { + // TODO: expand with other cluster types + case client.ClusterTypeLOCAL: + kubeClient, err = client.New(&client.NewClientInput{ + KubernetesConfigPath: cfg.KubernetesConfig, + ClusterType: cfg.KubernetesType, + }) + if err != nil { + return ctr, errors.Wrap(err, "failed to create kube client") + } + case client.ClusterTypeEKS: + kubeClient, err = client.New(&client.NewClientInput{ + KubernetesConfigPath: cfg.KubernetesConfig, + ClusterType: cfg.KubernetesType, + }, + client.EKSClientOption(ctx, cfg.KubernetesClusterID), + ) + if err != nil { + return ctr, errors.Wrap(err, "failed to create kube client") + } + } + ctr.KubeClientSet = kubeClient + // create argo client + if ctr.ArgoClient, err = argo_client.NewArgoClient(cfg, kubeClient); err != nil { + return ctr, errors.Wrap(err, "failed to create argo client") + } + + // create vcs to argo map + vcsToArgoMap := appdir.NewVcsToArgoMap(ctr.VcsClient.Username()) + ctr.VcsToArgoMap = vcsToArgoMap + + if cfg.MonitorAllApplications { + if err = buildAppsMap(ctx, ctr.ArgoClient, ctr.VcsToArgoMap); err != nil { + log.Fatal().Err(err).Msg("failed to build apps map") + } + + if err = buildAppSetsMap(ctx, ctr.ArgoClient, ctr.VcsToArgoMap); err != nil { + log.Fatal().Err(err).Msg("failed to build appsets map") + } + } + + return ctr, nil +} + +func buildAppsMap(ctx context.Context, argoClient *argo_client.ArgoClient, result appdir.VcsToArgoMap) error { + apps, err := argoClient.GetApplications(ctx) + if err != nil { + return errors.Wrap(err, "failed to list applications") + } + for _, app := range apps.Items { + result.AddApp(&app) + } + return nil +} + +func buildAppSetsMap(ctx context.Context, argoClient *argo_client.ArgoClient, result appdir.VcsToArgoMap) error { + appSets, err := argoClient.GetApplicationSets(ctx) + if err != nil { + return errors.Wrap(err, "failed to list application sets") + } + for _, appSet := range appSets.Items { + result.AddAppSet(&appSet) + } + return nil +} diff --git a/pkg/events/check.go b/pkg/events/check.go index 54b54bdf..8e0ee609 100644 --- a/pkg/events/check.go +++ b/pkg/events/check.go @@ -270,11 +270,12 @@ func (ce *CheckEvent) Process(ctx context.Context) error { for num := 0; num <= ce.ctr.Config.MaxConcurrenctChecks; num++ { w := worker{ - appChannel: ce.appChannel, - ctr: ce.ctr, - logger: ce.logger.With().Int("workerID", num).Logger(), - processors: ce.processors, - vcsNote: ce.vcsNote, + appChannel: ce.appChannel, + ctr: ce.ctr, + logger: ce.logger.With().Int("workerID", num).Logger(), + pullRequest: ce.pullRequest, + processors: ce.processors, + vcsNote: ce.vcsNote, done: ce.wg.Done, getRepo: ce.getRepo, diff --git a/pkg/events/runner.go b/pkg/events/runner.go index 29c25242..f845d88f 100644 --- a/pkg/events/runner.go +++ b/pkg/events/runner.go @@ -11,7 +11,6 @@ import ( "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/container" - "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/msg" "github.com/zapier/kubechecks/telemetry" ) @@ -23,9 +22,13 @@ type Runner struct { } func newRunner( - ctr container.Container, app v1alpha1.Application, repo *git.Repo, - appName, k8sVersion string, jsonManifests, yamlManifests []string, - logger zerolog.Logger, note *msg.Message, queueApp, removeApp func(application v1alpha1.Application), + ctr container.Container, + app v1alpha1.Application, + appName, k8sVersion string, + jsonManifests, yamlManifests []string, + logger zerolog.Logger, + note *msg.Message, + queueApp, removeApp func(application v1alpha1.Application), ) *Runner { return &Runner{ Request: checks.Request{ @@ -38,7 +41,6 @@ func newRunner( Note: note, QueueApp: queueApp, RemoveApp: removeApp, - Repo: repo, YamlManifests: yamlManifests, }, } diff --git a/pkg/events/worker.go b/pkg/events/worker.go index 58f0e47b..b99f3bc3 100644 --- a/pkg/events/worker.go +++ b/pkg/events/worker.go @@ -3,8 +3,10 @@ package events import ( "context" "fmt" + "runtime/debug" "sync/atomic" + "github.com/zapier/kubechecks/pkg/vcs" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -20,11 +22,12 @@ import ( ) type worker struct { - appChannel chan *v1alpha1.Application - ctr container.Container - logger zerolog.Logger - processors []checks.ProcessorEntry - vcsNote *msg.Message + appChannel chan *v1alpha1.Application + ctr container.Container + logger zerolog.Logger + processors []checks.ProcessorEntry + pullRequest vcs.PullRequest + vcsNote *msg.Message done func() getRepo func(ctx context.Context, vcsClient hasUsername, cloneURL, branchName string) (*git.Repo, error) @@ -45,6 +48,32 @@ func (w *worker) run(ctx context.Context) { } } +type pathAndRepoUrl struct { + Path, RepoURL, TargetRevision string +} + +func getAppSources(app v1alpha1.Application) []pathAndRepoUrl { + var items []pathAndRepoUrl + + if src := app.Spec.Source; src != nil { + items = append(items, pathAndRepoUrl{ + Path: src.Path, + RepoURL: src.RepoURL, + TargetRevision: src.TargetRevision, + }) + } + + for _, src := range app.Spec.Sources { + items = append(items, pathAndRepoUrl{ + Path: src.Path, + RepoURL: src.RepoURL, + TargetRevision: src.TargetRevision, + }) + } + + return items +} + // processApp is a function that validates and processes a given application manifest against various checks, // such as ArgoCD schema validation, diff generation, conftest policy validation, and pre-upgrade checks using kubepug. // It takes a context (ctx), application name (app), directory (dir) as input and returns an error if any check fails. @@ -54,87 +83,95 @@ func (w *worker) processApp(ctx context.Context, app v1alpha1.Application) { var ( err error - appName = app.Name - appSrc = app.Spec.Source - appPath = appSrc.Path - appRepoUrl = appSrc.RepoURL + appName = app.Name - logger = w.logger.With(). - Str("app_name", appName). - Str("app_path", appPath). - Logger() + rootLogger = w.logger.With(). + Str("app_name", appName). + Logger() ) ctx, span := tracer.Start(ctx, "processApp", trace.WithAttributes( attribute.String("app", appName), - attribute.String("dir", appPath), )) defer span.End() atomic.AddInt32(&inFlight, 1) defer atomic.AddInt32(&inFlight, -1) - logger.Info().Msg("Processing app") + rootLogger.Info().Msg("Processing app") // Build a new section for this app in the parent comment w.vcsNote.AddNewApp(ctx, appName) defer func() { - if err := recover(); err != nil { + if r := recover(); r != nil { desc := fmt.Sprintf("panic while checking %s", appName) - w.logger.Error().Str("app", appName).Msgf("panic while running check") + w.logger.Error().Any("error", r). + Str("app", appName).Msgf("panic while running check") + println(string(debug.Stack())) - telemetry.SetError(span, fmt.Errorf("%v", err), "panic while running check") + telemetry.SetError(span, fmt.Errorf("%v", r), "panic while running check") result := msg.Result{ State: pkg.StatePanic, Summary: desc, - Details: fmt.Sprintf(errorCommentFormat, desc, err), + Details: fmt.Sprintf(errorCommentFormat, desc, r), } w.vcsNote.AddToAppMessage(ctx, appName, result) } }() - repo, err := w.getRepo(ctx, w.ctr.VcsClient, appRepoUrl, appSrc.TargetRevision) - if err != nil { - logger.Error().Err(err).Msg("Unable to clone repository") - w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ - State: pkg.StateError, - Summary: "failed to clone repo", - Details: fmt.Sprintf("Clone URL: `%s`\nTarget Revision: `%s`\n```\n%s\n```", appRepoUrl, appSrc.TargetRevision, err.Error()), - }) - return - } - repoPath := repo.Directory + var jsonManifests []string + sources := getAppSources(app) + for _, appSrc := range sources { + var ( + appPath = appSrc.Path + appRepoUrl = appSrc.RepoURL + logger = rootLogger.With(). + Str("app_path", appPath). + Logger() + ) + + repo, err := w.getRepo(ctx, w.ctr.VcsClient, appRepoUrl, appSrc.TargetRevision) + if err != nil { + logger.Error().Err(err).Msg("Unable to clone repository") + w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ + State: pkg.StateError, + Summary: "failed to clone repo", + Details: fmt.Sprintf("Clone URL: `%s`\nTarget Revision: `%s`\n```\n%s\n```", appRepoUrl, appSrc.TargetRevision, err.Error()), + }) + return + } + repoPath := repo.Directory + + logger.Debug().Str("repo_path", repoPath).Msg("Getting manifests") + someJsonManifests, err := w.ctr.ArgoClient.GetManifestsLocal(ctx, appName, repoPath, appPath, app) + if err != nil { + logger.Error().Err(err).Msg("Unable to get manifests") + w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ + State: pkg.StateError, + Summary: "Unable to get manifests", + Details: fmt.Sprintf("```\n%s\n```", cleanupGetManifestsError(err, repo.Directory)), + }) + return + } - logger.Debug().Str("repo_path", repoPath).Msg("Getting manifests") - jsonManifests, err := w.ctr.ArgoClient.GetManifestsLocal(ctx, appName, repoPath, appPath, app) - if err != nil { - logger.Error().Err(err).Msg("Unable to get manifests") - w.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ - State: pkg.StateError, - Summary: "Unable to get manifests", - Details: fmt.Sprintf("```\n%s\n```", cleanupGetManifestsError(err, repo.Directory)), - }) - return + jsonManifests = append(jsonManifests, someJsonManifests...) } // Argo diff logic wants unformatted manifests but everything else wants them as YAML, so we prepare both yamlManifests := argo_client.ConvertJsonToYamlManifests(jsonManifests) - logger.Trace().Msgf("Manifests:\n%+v\n", yamlManifests) + rootLogger.Trace().Msgf("Manifests:\n%+v\n", yamlManifests) k8sVersion, err := w.ctr.ArgoClient.GetKubernetesVersionByApplication(ctx, app) if err != nil { - logger.Error().Err(err).Msg("Error retrieving the Kubernetes version") + rootLogger.Error().Err(err).Msg("Error retrieving the Kubernetes version") k8sVersion = w.ctr.Config.FallbackK8sVersion } else { k8sVersion = fmt.Sprintf("%s.0", k8sVersion) - logger.Info().Msgf("Kubernetes version: %s", k8sVersion) + rootLogger.Info().Msgf("Kubernetes version: %s", k8sVersion) } - runner := newRunner( - w.ctr, app, repo, appName, k8sVersion, jsonManifests, yamlManifests, logger, w.vcsNote, - w.queueApp, w.removeApp, - ) + runner := newRunner(w.ctr, app, appName, k8sVersion, jsonManifests, yamlManifests, rootLogger, w.vcsNote, w.queueApp, w.removeApp) for _, processor := range w.processors { runner.Run(ctx, processor.Name, processor.Processor, processor.WorstState)