diff --git a/docs/content/2_concepts/catalog_distribution.md b/docs/content/2_concepts/catalog_distribution.md index d4e9de6f..62c35aa5 100644 --- a/docs/content/2_concepts/catalog_distribution.md +++ b/docs/content/2_concepts/catalog_distribution.md @@ -186,3 +186,31 @@ kubara catalog unpackage oci://ghcr.io/acme/platform-catalogs/my-catalog:1.2.3 . ``` This is useful when you want to inspect or edit a catalog that was distributed through a registry. + +## Automatic catalog updates with Renovate + +You can automatically keep catalog versions up-to-date in your GitOps configuration using a custom Renovate configuration. + +Add a custom manager block to your `renovate.json` or GitOps repository's Renovate configuration: + +```json +{ + "customManagers": [ + { + "customType": "regex", + "description": "Update kubara catalog OCI references", + "fileMatch": ["(^|/)config\\.yaml$"], + "matchStrings": ["oci:\\/\\/(?[^:\\s\"'\\`]+):(?[^\\s\"'\\`]+)"], + "datasourceTemplate": "docker" + } + ], + "packageRules": [ + { + "description": "Enforce semantic versioning for kubara catalogs", + "matchDatasources": ["docker"], + "matchPackagePatterns": ["^ghcr\\.io/kubara-io/catalogs/"], + "versioning": "regex:^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)$" + } + ] +} +``` diff --git a/src/cmd/init.go b/src/cmd/init.go index 9c38b7fb..e441e1fb 100644 --- a/src/cmd/init.go +++ b/src/cmd/init.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "github.com/kubara-io/kubara/internal/catalog" "github.com/kubara-io/kubara/internal/config" @@ -230,6 +231,10 @@ func (o *InitOptions) runForceMode(es *envconfig.EnvStore, cs *config.ConfigStor return fmt.Errorf("load config file: %w", configLoadErr) } + if err := o.ensureRenovateConfig(cs); err != nil { + return err + } + if err := workflow.CreateOrUpdateCluster(cs.GetConfig(), es.GetConfig(), o.catalogLoadOptions()); err != nil { return fmt.Errorf("create or update cluster from env: %w", err) } @@ -264,6 +269,10 @@ func (o *InitOptions) runNormalMode(es *envconfig.EnvStore, cs *config.ConfigSto return err } + if err := o.ensureRenovateConfig(cs); err != nil { + return err + } + if fileExists { log.Info().Msgf("Config file already exist. To overwrite existing variables in the config from env: set flag \"--overwrite\"") log.Info().Msg("Initialized successfully") @@ -296,3 +305,77 @@ func (o *InitOptions) runNormalMode(es *envconfig.EnvStore, cs *config.ConfigSto log.Info().Msgf("Generated config in path: %v", cs.GetFilepath()) return nil } + +func (o *InitOptions) ensureRenovateConfig(cs *config.ConfigStore) error { + baseDir := filepath.Dir(cs.GetFilepath()) + // https://docs.renovatebot.com/configuration-options/#locations-for-configuration-filenames + renovateFiles := []string{ + "renovate.json", + "renovate.jsonc", + "renovate.json5", + ".github/renovate.json", + ".github/renovate.jsonc", + ".github/renovate.json5", + ".gitlab/renovate.json", + ".gitlab/renovate.jsonc", + ".gitlab/renovate.json5", + ".renovaterc", + ".renovaterc.json", + ".renovaterc.jsonc", + ".renovaterc.json5", + } + + for _, file := range renovateFiles { + exists, err := utils.FileExist(filepath.Join(baseDir, file)) + if err != nil { + return err + } + if exists { + return nil + } + } + + renovatePath := filepath.Join(baseDir, "renovate.json") + configFileBase := filepath.Base(cs.GetFilepath()) + fileMatchPattern := fmt.Sprintf("(^|/)%s$", regexp.QuoteMeta(configFileBase)) + + content := fmt.Sprintf(`{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ], + "customManagers": [ + { + "customType": "regex", + "description": "Update kubara catalog OCI references", + "fileMatch": [ + %q + ], + "matchStrings": [ + "oci:\\/\\/(?[^:\\s\"'`+"`"+`]+):(?[^\\s\"'`+"`"+`]+)" + ], + "datasourceTemplate": "docker" + } + ], + "packageRules": [ + { + "description": "Enforce semantic versioning for kubara catalogs", + "matchDatasources": [ + "docker" + ], + "matchPackagePatterns": [ + "^ghcr\\.io/kubara-io/catalogs/" + ], + "versioning": "regex:^(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)\\.(?0|[1-9]\\d*)$" + } + ] +} +`, fileMatchPattern) + + if err := os.WriteFile(renovatePath, []byte(content), 0o644); err != nil { + return fmt.Errorf("write renovate config: %w", err) + } + + log.Info().Msgf("Generated renovate config in path: %s", renovatePath) + return nil +}