diff --git a/.github/workflows/docker-ci.yaml b/.github/workflows/docker-ci.yaml index 5148fe8..61c1355 100644 --- a/.github/workflows/docker-ci.yaml +++ b/.github/workflows/docker-ci.yaml @@ -47,6 +47,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 + if: ${{ github.ref_name == 'main' }} with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -68,4 +69,4 @@ jobs: with: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - repository: ${{ vars.DOCKERHUB_USERNAME }}/swarm-cd \ No newline at end of file + repository: ${{ vars.DOCKERHUB_USERNAME }}/swarm-cd diff --git a/README.md b/README.md index a5e3fd5..f648395 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ docker stack deploy --compose-file docker-compose.yaml swarm-cd This will start SwarmCD, it will periodically check the stack repo for new changes, pulling them and updating the stack. - ## Manage Encrypted Secrets Using SOPS You can use [sops](https://github.com/getsops/sops) to encrypt secrets in git repos and @@ -113,6 +112,19 @@ secrets: This way, SwarmCD will decrypt the files each time before it updates the stack. +### Automatic SOPS secrets detection + +Instead of specifying the paths of every single secrets you need to decrypt, +you can use the `sops_secrets_discovery: true` option: + +- in the `config.yaml` file to enable it globally +- in the `stacks.yaml` file for the individual stacks. + +Please note that: + +- if the global setting is set to `true`, it ignores individual stacks overrides. +- if the stack-level setting is set to `true`, it ignores the `sops_files` setting altogether. + ## Connect SwarmCD to a remote docker socket You can use the `DOCKER_HOST` environment variable to point SwarmCD to a remote docker socket, diff --git a/docs/README.md b/docs/README.md index 5ece8f8..66753d5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,7 @@ # Documentation -Here you can find configuration file references for +Here you can find configuration file references for: + - [repos.yaml](repos.yaml) - [stacks.yaml](stacks.yaml) -- [config.yaml](config.yaml) \ No newline at end of file +- [config.yaml](config.yaml) diff --git a/docs/config.yaml b/docs/config.yaml index abe2ec7..6c70361 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -7,6 +7,9 @@ update_interval: 120 # The path where SwarmCD will checkout repos repos_path: repos/ +# Automatically detect secrets to decrypt with SOPS +sops_secrets_discovery: true + # Automatically rotate configs and secrets # when the change. Adds a hash to config # and secret names @@ -19,4 +22,3 @@ repos: # You can define stacks here instead of # defining a separate stacks.yaml file stacks: - diff --git a/docs/stacks.yaml b/docs/stacks.yaml index 524d2f0..720ea53 100644 --- a/docs/stacks.yaml +++ b/docs/stacks.yaml @@ -22,4 +22,6 @@ stack-name: # before updating stack sops_files: - path/to/sops/encrypted/file - + # Enable the automatic secret discovery + # alternative to sops_files + sops_secrets_discovery: false diff --git a/swarmcd/init.go b/swarmcd/init.go index ba48ca0..e17940d 100644 --- a/swarmcd/init.go +++ b/swarmcd/init.go @@ -19,7 +19,6 @@ type StackStatus struct { RepoURL string } - var config *util.Config = &util.Configs var logger *slog.Logger = util.Logger @@ -44,7 +43,7 @@ func Init() (err error) { return } -func initRepos() (error) { +func initRepos() error { for repoName, repoConfig := range config.RepoConfigs { repoPath := path.Join(config.ReposPath, repoName) auth, err := createHTTPBasicAuth(repoName) @@ -98,7 +97,8 @@ func initStacks() error { if !ok { return fmt.Errorf("error initializing %s stack, no such repo: %s", stack, stackConfig.Repo) } - swarmStack := newSwarmStack(stack, stackRepo, stackConfig.Branch, stackConfig.ComposeFile, stackConfig.SopsFiles, stackConfig.ValuesFile) + discoverSecrets := config.SopsSecretsDiscovery || stackConfig.SopsSecretsDiscovery + swarmStack := newSwarmStack(stack, stackRepo, stackConfig.Branch, stackConfig.ComposeFile, stackConfig.SopsFiles, stackConfig.ValuesFile, discoverSecrets) stacks = append(stacks, swarmStack) stackStatus[stack] = &StackStatus{} stackStatus[stack].RepoURL = stackRepo.url diff --git a/swarmcd/stack.go b/swarmcd/stack.go index b1d71c4..2b11584 100644 --- a/swarmcd/stack.go +++ b/swarmcd/stack.go @@ -1,6 +1,7 @@ package swarmcd import ( + "bytes" "crypto/md5" "fmt" "log/slog" @@ -14,22 +15,24 @@ import ( ) type swarmStack struct { - name string - repo *stackRepo - branch string - composePath string - sopsFiles []string - valuesFile string + name string + repo *stackRepo + branch string + composePath string + sopsFiles []string + valuesFile string + discoverSecrets bool } -func newSwarmStack(name string, repo *stackRepo, branch string, composePath string, sopsFiles []string, valuesFile string) *swarmStack { +func newSwarmStack(name string, repo *stackRepo, branch string, composePath string, sopsFiles []string, valuesFile string, discoverSecrets bool) *swarmStack { return &swarmStack{ - name: name, - repo: repo, - branch: branch, - composePath: composePath, - sopsFiles: sopsFiles, - valuesFile: valuesFile, + name: name, + repo: repo, + branch: branch, + composePath: composePath, + sopsFiles: sopsFiles, + valuesFile: valuesFile, + discoverSecrets: discoverSecrets, } } @@ -46,92 +49,138 @@ func (swarmStack *swarmStack) updateStack() (revision string, err error) { } log.Debug("changes pulled", "revision", revision) - log.Debug("decrypting sops files...") - err = swarmStack.decryptSopsFiles() + log.Debug("reading stack file...") + stackBytes, err := swarmStack.readStack() if err != nil { - return "", fmt.Errorf("failed to decrypt one or more sops files for %s stack: %w", swarmStack.name, err) + return } if swarmStack.valuesFile != "" { log.Debug("rendering template...") - err = swarmStack.renderComposeTemplate() - if err != nil { - return - } + stackBytes, err = swarmStack.renderComposeTemplate(stackBytes) + } + if err != nil { + return } - log.Debug("rotating configs and secrets...") - err = swarmStack.rotateConfigsAndSecrets() + log.Debug("parsing stack content...") + stackContents, err := swarmStack.parseStackString([]byte(stackBytes)) if err != nil { return } - log.Debug("deploying stack...") - err = swarmStack.deployStack() + log.Debug("decrypting secrets...") + err = swarmStack.decryptSopsFiles(stackContents) + if err != nil { + return "", fmt.Errorf("failed to decrypt one or more sops files for %s stack: %w", swarmStack.name, err) + } + + log.Debug("rotating configs and secrets...") + err = swarmStack.rotateConfigsAndSecrets(stackContents) if err != nil { return } - return -} -func (swarmStack *swarmStack) decryptSopsFiles() (err error) { - for _, sopsFile := range swarmStack.sopsFiles { - err = util.DecryptFile(path.Join(swarmStack.repo.path, sopsFile)) - if err != nil { - return - } + log.Debug("writing stack to file...") + err = swarmStack.writeStack(stackContents) + if err != nil { + return } + + log.Debug("deploying stack...") + err = swarmStack.deployStack() return } -func (swarmStack *swarmStack) deployStack() error { - cmd := stack.NewStackCommand(dockerCli) - cmd.SetArgs([]string{ - "deploy", "--detach", "--with-registry-auth", "-c", - path.Join(swarmStack.repo.path, swarmStack.composePath), - swarmStack.name, - }) - // To stop printing errors and usage message to stdout - cmd.SilenceErrors = true - cmd.SilenceUsage = true - err := cmd.Execute() +func (swarmStack *swarmStack) readStack() ([]byte, error) { + composeFile := path.Join(swarmStack.repo.path, swarmStack.composePath) + composeFileBytes, err := os.ReadFile(composeFile) if err != nil { - return fmt.Errorf("could not deploy stack %s: %s", swarmStack.name, err) + return nil, fmt.Errorf("could not read compose file %s: %w", composeFile, err) } - return nil + return composeFileBytes, nil } -func (swarmStack *swarmStack) rotateConfigsAndSecrets() error { - composeFile := path.Join(swarmStack.repo.path, swarmStack.composePath) - composeFileBytes, err := os.ReadFile(composeFile) +func (swarmStack *swarmStack) renderComposeTemplate(templateContents []byte) ([]byte, error) { + valuesFile := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.valuesFile) + valuesBytes, err := os.ReadFile(valuesFile) + if err != nil { + return nil, fmt.Errorf("could not read %s stack values file: %w", swarmStack.name, err) + } + var valuesMap map[string]any + yaml.Unmarshal(valuesBytes, &valuesMap) + templ, err := template.New(swarmStack.name).Parse(string(templateContents[:])) + if err != nil { + return nil, fmt.Errorf("could not parse %s stack compose file as a Go template: %w", swarmStack.name, err) + } + var stackContents bytes.Buffer + err = templ.Execute(&stackContents, map[string]map[string]any{"Values": valuesMap}) if err != nil { - return fmt.Errorf("could not read compose file %s: %w", composeFile, err) + return nil, fmt.Errorf("error rending %s stack compose template: %w", swarmStack.name, err) } + return stackContents.Bytes(), nil +} + +func (swarmStack *swarmStack) parseStackString(stackContent []byte) (map[string]any, error) { var composeMap map[string]any - err = yaml.Unmarshal(composeFileBytes, &composeMap) + err := yaml.Unmarshal(stackContent, &composeMap) if err != nil { - return fmt.Errorf("could not parse yaml file %s: %w", composeFile, err) + return nil, fmt.Errorf("could not parse stack yaml: %w", err) } + return composeMap, nil +} +func (swarmStack *swarmStack) decryptSopsFiles(composeMap map[string]any) (err error) { + var sopsFiles []string + if !swarmStack.discoverSecrets { + sopsFiles = swarmStack.sopsFiles + } else { + sopsFiles, err = discoverSecrets(composeMap, swarmStack.composePath) + if err != nil { + return + } + } + for _, sopsFile := range sopsFiles { + err = util.DecryptFile(path.Join(swarmStack.repo.path, sopsFile)) + if err != nil { + return + } + } + return +} + +func discoverSecrets(composeMap map[string]any, composePath string) ([]string, error) { + var sopsFiles []string + if secrets, ok := composeMap["secrets"].(map[string]any); ok { + for secretName, secret := range secrets { + secretMap, ok := secret.(map[string]any) + if !ok { + return nil, fmt.Errorf("invalid compose file: %s secret must be a map", secretName) + } + secretFile, ok := secretMap["file"].(string) + if !ok { + return nil, fmt.Errorf("invalid compose file: %s file field must be a string", secretName) + } + objectDir := path.Join(path.Dir(composePath), secretFile) + sopsFiles = append(sopsFiles, objectDir) + } + } + return sopsFiles, nil +} + +func (swarmStack *swarmStack) rotateConfigsAndSecrets(composeMap map[string]any) error { if configs, ok := composeMap["configs"].(map[string]any); ok { - err = swarmStack.rotateObjects(configs) + err := swarmStack.rotateObjects(configs) if err != nil { return fmt.Errorf("could not rotate one or more config files of stack %s: %w", swarmStack.name, err) } } if secrets, ok := composeMap["secrets"].(map[string]any); ok { - err = swarmStack.rotateObjects(secrets) + err := swarmStack.rotateObjects(secrets) if err != nil { return fmt.Errorf("could not rotate one or more secret files of stack %s: %w", swarmStack.name, err) } } - - composeFileBytes, err = yaml.Marshal(composeMap) - if err != nil { - return fmt.Errorf("could not store comopse file as yaml after calculating hashes for stack %s", swarmStack.name) - } - fileInfo, _ := os.Stat(composeFile) - os.WriteFile(composeFile, composeFileBytes, fileInfo.Mode()) return nil } @@ -157,26 +206,31 @@ func (swarmStack *swarmStack) rotateObjects(objects map[string]any) error { return nil } -func (swarmStack *swarmStack) renderComposeTemplate() error { - composeFile := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.composePath) - valuesFile := path.Join(config.ReposPath, swarmStack.repo.path, swarmStack.valuesFile) - valuesBytes, err := os.ReadFile(valuesFile) +func (swarmStack *swarmStack) writeStack(composeMap map[string]any) error { + composeFileBytes, err := yaml.Marshal(composeMap) if err != nil { - return fmt.Errorf("could not read %s stack values file: %w", swarmStack.name, err) + return fmt.Errorf("could not store compose file as yaml after calculating hashes for stack %s", swarmStack.name) } - var valuesMap map[string]any - yaml.Unmarshal(valuesBytes, &valuesMap) - templ, err := template.New(path.Base(composeFile)).ParseFiles(composeFile) - if err != nil { - return fmt.Errorf("could not parse %s stack compose file as a Go template: %w", swarmStack.name, err) - } - composeFileWriter, err := os.Create(composeFile) - if err != nil { - return fmt.Errorf("could not open %s stack compose file: %w", swarmStack.name, err) - } - err = templ.Execute(composeFileWriter, map[string]map[string]any{"Values": valuesMap}) + composeFile := path.Join(swarmStack.repo.path, swarmStack.composePath) + fileInfo, _ := os.Stat(composeFile) + os.WriteFile(composeFile, composeFileBytes, fileInfo.Mode()) + return nil +} + +func (swarmStack *swarmStack) deployStack() error { + cmd := stack.NewStackCommand(dockerCli) + cmd.SetArgs([]string{ + "deploy", "--detach", "--with-registry-auth", "-c", + path.Join(swarmStack.repo.path, swarmStack.composePath), + swarmStack.name, + }) + // To stop printing errors and + // usage message to stdout + cmd.SilenceErrors = true + cmd.SilenceUsage = true + err := cmd.Execute() if err != nil { - return fmt.Errorf("error rending %s stack compose template: %w", swarmStack.name, err) + return fmt.Errorf("could not deploy stack %s: %s", swarmStack.name, err) } return nil } diff --git a/ui/package-lock.json b/ui/package-lock.json index df31ebb..62c503c 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -20,7 +20,7 @@ "devDependencies": { "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react": "^4.3.1", "eslint": "^8.57.0", @@ -2617,31 +2617,31 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz", + "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/type-utils": "8.8.0", + "@typescript-eslint/utils": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -2649,6 +2649,53 @@ } } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/parser": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", @@ -2695,25 +2742,63 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz", + "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/typescript-estree": "8.8.0", + "@typescript-eslint/utils": "8.8.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^8.56.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -2721,6 +2806,23 @@ } } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/types": { "version": "7.18.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", @@ -2763,25 +2865,100 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz", + "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@typescript-eslint/scope-manager": "8.8.0", + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/typescript-estree": "8.8.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz", + "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz", + "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz", + "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "@typescript-eslint/visitor-keys": "8.8.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz", + "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.8.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@typescript-eslint/visitor-keys": { diff --git a/ui/package.json b/ui/package.json index 3612b82..aad8153 100644 --- a/ui/package.json +++ b/ui/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@types/react": "^18.3.8", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.8.0", "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react": "^4.3.1", "eslint": "^8.57.0", diff --git a/util/config.go b/util/config.go index 76ad5b8..ef17535 100644 --- a/util/config.go +++ b/util/config.go @@ -8,11 +8,12 @@ import ( ) type StackConfig struct { - Repo string - Branch string - ComposeFile string `mapstructure:"compose_file"` - ValuesFile string `mapstructure:"values_file"` - SopsFiles []string `mapstructure:"sops_files"` + Repo string + Branch string + ComposeFile string `mapstructure:"compose_file"` + ValuesFile string `mapstructure:"values_file"` + SopsFiles []string `mapstructure:"sops_files"` + SopsSecretsDiscovery bool `mapstructure:"sops_secrets_discovery"` } type RepoConfig struct { @@ -23,11 +24,12 @@ type RepoConfig struct { } type Config struct { - ReposPath string `mapstructure:"repos_path"` - UpdateInterval int `mapstructure:"update_interval"` - AutoRotate bool `mapstructure:"auto_rotate"` - StackConfigs map[string]*StackConfig `mapstructure:"stacks"` - RepoConfigs map[string]*RepoConfig `mapstructure:"repos"` + ReposPath string `mapstructure:"repos_path"` + UpdateInterval int `mapstructure:"update_interval"` + AutoRotate bool `mapstructure:"auto_rotate"` + StackConfigs map[string]*StackConfig `mapstructure:"stacks"` + RepoConfigs map[string]*RepoConfig `mapstructure:"repos"` + SopsSecretsDiscovery bool `mapstructure:"sops_secrets_discovery"` } var Configs Config @@ -59,6 +61,7 @@ func readConfig() (err error) { configViper.SetDefault("update_interval", 120) configViper.SetDefault("repos_path", "repos") configViper.SetDefault("auto_rotate", true) + configViper.SetDefault("sops_secrets_discovery", false) err = configViper.ReadInConfig() if err != nil && !errors.As(err, &viper.ConfigFileNotFoundError{}) { return