diff --git a/pkg/app_watcher/appwatcher.go b/pkg/app_watcher/appwatcher.go index a4fe66da..67ba6aa3 100644 --- a/pkg/app_watcher/appwatcher.go +++ b/pkg/app_watcher/appwatcher.go @@ -87,9 +87,8 @@ func (ctrl *ApplicationWatcher) onApplicationUpdated(old, new interface{}) { if err != nil { log.Warn().Err(err).Msg("appwatcher: could not get key for updated application") } - // TODO - // have any of the Source repoURLs changed? log.Trace().Str("key", key).Msg("appwatcher: onApplicationUpdated") + ctrl.cfg.VcsToArgoMap.UpdateApp(old.(appv1alpha1.Application), new.(appv1alpha1.Application)) } func (ctrl *ApplicationWatcher) onApplicationDeleted(obj interface{}) { @@ -102,6 +101,7 @@ func (ctrl *ApplicationWatcher) onApplicationDeleted(obj interface{}) { } log.Trace().Str("key", key).Msg("appwatcher: onApplicationDeleted") + ctrl.cfg.VcsToArgoMap.DeleteApp(obj.(appv1alpha1.Application)) } /* diff --git a/pkg/config/app_directory.go b/pkg/config/app_directory.go index 603e6fe5..958056d4 100644 --- a/pkg/config/app_directory.go +++ b/pkg/config/app_directory.go @@ -163,6 +163,30 @@ func (d *AppDirectory) AddFile(appName, path string) { d.appFiles[path] = append(d.appFiles[path], appName) } +func (d *AppDirectory) RemoveApp(app v1alpha1.Application) { + // remove app from appsMap + delete(d.appsMap, app.Name) + + // Clean up app from appDirs + sourcePath := getSourcePath(app) + d.appDirs[sourcePath] = removeFromSlice[string](d.appDirs[sourcePath], app.Name, func(a, b string) bool { return a == b }) + + // Clean up app from appFiles + src := app.Spec.GetSource() + srcPath := src.Path + if helm := src.Helm; helm != nil { + for _, param := range helm.FileParameters { + path := filepath.Join(srcPath, param.Path) + d.appFiles[path] = removeFromSlice[string](d.appFiles[path], app.Name, func(a, b string) bool { return a == b }) + } + + for _, valueFilePath := range helm.ValueFiles { + path := filepath.Join(srcPath, valueFilePath) + d.appFiles[path] = removeFromSlice[string](d.appFiles[path], app.Name, func(a, b string) bool { return a == b }) + } + } +} + func mergeMaps[T any](first map[string]T, second map[string]T, combine func(T, T) T) map[string]T { result := make(map[string]T) for key, value := range first { @@ -186,3 +210,12 @@ func mergeLists[T any](a []T, b []T) []T { func takeFirst[T any](a, _ T) T { return a } + +func removeFromSlice[T any](slice []T, element T, equal func(T, T) bool) []T { + for i, j := range slice { + if equal(j, element) { + return append(slice[:i], slice[i+1:]...) + } + } + return slice +} diff --git a/pkg/config/config.go b/pkg/config/config.go index cebfe480..54ed7326 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -53,6 +53,29 @@ func (v2a *VcsToArgoMap) AddApp(app v1alpha1.Application) { appDirectory.ProcessApp(app) } +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 + } + + oldAppDirectory := v2a.GetAppsInRepo(old.Spec.Source.RepoURL) + oldAppDirectory.RemoveApp(old) + + newAppDirectory := v2a.GetAppsInRepo(new.Spec.Source.RepoURL) + newAppDirectory.ProcessApp(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) +} + type ServerConfig struct { UrlPrefix string WebhookSecret string diff --git a/pkg/server/server.go b/pkg/server/server.go index fd2abe11..be35585b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/viper" "github.com/ziflex/lecho/v3" + "github.com/zapier/kubechecks/pkg/app_watcher" "github.com/zapier/kubechecks/pkg/config" "github.com/zapier/kubechecks/pkg/vcs" ) @@ -22,18 +23,33 @@ import ( const KubeChecksHooksPathPrefix = "/hooks" type Server struct { - cfg *config.ServerConfig + cfg *config.ServerConfig + appWatcher *app_watcher.ApplicationWatcher } func NewServer(cfg *config.ServerConfig) *Server { - return &Server{cfg: cfg} + var appWatcher *app_watcher.ApplicationWatcher + if viper.GetBool("monitor-all-applications") { + argoMap, err := config.BuildAppsMap(context.TODO()) + if err != nil { + log.Fatal().Err(err).Msg("could not build VcsToArgoMap") + } + cfg.VcsToArgoMap = argoMap + + appWatcher, err = app_watcher.NewApplicationWatcher(cfg) + if err != nil { + log.Fatal().Err(err).Msg("could not create ApplicationWatcher") + } + } else { + cfg.VcsToArgoMap = config.NewVcsToArgoMap() + } + + return &Server{cfg: cfg, appWatcher: appWatcher} } func (s *Server) Start(ctx context.Context) { - if argoMap, err := s.buildVcsToArgoMap(ctx); err != nil { - log.Warn().Err(err).Msg("failed to build vcs app map from argo") - } else { - s.cfg.VcsToArgoMap = argoMap + if s.appWatcher != nil { + go s.appWatcher.Run(context.Background(), 1) } if err := s.ensureWebhooks(); err != nil { @@ -120,13 +136,3 @@ func (s *Server) ensureWebhooks() error { return nil } - -func (s *Server) buildVcsToArgoMap(ctx context.Context) (config.VcsToArgoMap, error) { - if !viper.GetBool("monitor-all-applications") { - return config.NewVcsToArgoMap(), nil - } - - log.Debug().Msg("building VCS to Application Map") - - return config.BuildAppsMap(ctx) -}