diff --git a/.golangci.yml b/.golangci.yml index 2c7b25c2975..7dc561c2842 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -108,6 +108,7 @@ linters: - '!**/integrations/operator/controllers/resources/testlib/**' - '!**/lib/auth/test/**' - '!**/lib/services/suite/**' + - '!**/build.assets/tooling/**' deny: - pkg: github.com/google/go-cmp/cmp desc: '"github.com/google/go-cmp/cmp" should only be used in tests' diff --git a/Makefile b/Makefile index 2d5d47d2bdf..a8263cc8a2d 100644 --- a/Makefile +++ b/Makefile @@ -288,6 +288,14 @@ VERSRC = gitref.go api/version.go KUBECONFIG ?= TEST_KUBE ?= +# This unexport statement is required for make to work with the `-e` flag. +# With -e, the first Makefile sets HELMJANITOR=$$(go tool ...), +# passes HELMJANITOR=$(go tool) to the child make process. +# Because of the `-e` flag it takes precedence over the child's make definition +# of HELMJANITOR and breaks environment variable expansion. +# To avoid breaking other parts of the release pipeline, the easiest fix is to +# unexport HELMJANITOR so the child uses the definition from its Makefile. +unexport HELMJANITOR export KUBECONFIG export TEST_KUBE @@ -950,25 +958,11 @@ helmunit/installed: # environment variable. .PHONY: test-helm test-helm: helmunit/installed - helm unittest -3 --with-subchart=false examples/chart/teleport-cluster - helm unittest -3 --with-subchart=false examples/chart/teleport-kube-agent - helm unittest -3 --with-subchart=false examples/chart/teleport-relay - helm unittest -3 --with-subchart=false examples/chart/teleport-cluster/charts/teleport-operator - helm unittest -3 --with-subchart=false examples/chart/access/* - helm unittest -3 --with-subchart=false examples/chart/event-handler - helm unittest -3 --with-subchart=false examples/chart/tbot - helm unittest -3 --with-subchart=false examples/chart/tbot-spiffe-daemon-set + $(HELMJANITOR) test .PHONY: test-helm-update-snapshots test-helm-update-snapshots: helmunit/installed - helm unittest -3 -u --with-subchart=false examples/chart/teleport-cluster - helm unittest -3 -u --with-subchart=false examples/chart/teleport-kube-agent - helm unittest -3 -u --with-subchart=false examples/chart/teleport-relay - helm unittest -3 -u --with-subchart=false examples/chart/teleport-cluster/charts/teleport-operator - helm unittest -3 -u --with-subchart=false examples/chart/access/* - helm unittest -3 -u --with-subchart=false examples/chart/event-handler - helm unittest -3 -u --with-subchart=false examples/chart/tbot - helm unittest -3 -u --with-subchart=false examples/chart/tbot-spiffe-daemon-set + $(HELMJANITOR) test --update-snapshots # # Runs all Go tests except integration, called by CI/CD. @@ -1382,30 +1376,8 @@ lint-sh: # If errors are found, the file is printed with line numbers to aid in debugging. .PHONY: lint-helm lint-helm: - @if ! type yamllint 2>&1 >/dev/null; then \ - echo "Not running 'lint-helm' target as 'yamllint' is not installed."; \ - if [ "$${CI}" = "true" ]; then echo "This is a failure when running in CI." && exit 1; fi; \ - exit 0; \ - fi; \ - for CHART in ./examples/chart/teleport-cluster ./examples/chart/teleport-kube-agent ./examples/chart/teleport-relay ./examples/chart/teleport-cluster/charts/teleport-operator ./examples/chart/tbot ./examples/chart/tbot-spiffe-daemon-set; do \ - if [ -d $${CHART}/.lint ]; then \ - for VALUES in $${CHART}/.lint/*.yaml; do \ - export HELM_TEMP=$$(mktemp); \ - echo -n "Using values from '$${VALUES}': "; \ - yamllint -c examples/chart/.lint-config.yaml $${VALUES} || { cat -en $${VALUES}; exit 1; }; \ - helm lint --quiet --strict $${CHART} -f $${VALUES} || exit 1; \ - helm template test $${CHART} -f $${VALUES} 1>$${HELM_TEMP} || exit 1; \ - yamllint -c examples/chart/.lint-config.yaml $${HELM_TEMP} || { cat -en $${HELM_TEMP}; exit 1; }; \ - echo; \ - done \ - else \ - export HELM_TEMP=$$(mktemp); \ - helm lint --quiet --strict $${CHART} || exit 1; \ - helm template test $${CHART} 1>$${HELM_TEMP} || exit 1; \ - yamllint -c examples/chart/.lint-config.yaml $${HELM_TEMP} || { cat -en $${HELM_TEMP}; exit 1; }; \ - fi; \ - done - $(MAKE) -C examples/chart check-chart-ref + $(HELMJANITOR) reference -check + $(HELMJANITOR) lint ADDLICENSE_COMMON_ARGS := -c 'Gravitational, Inc.' \ -ignore '**/*.c' \ diff --git a/build.assets/tooling/cmd/helm-janitor/lint.go b/build.assets/tooling/cmd/helm-janitor/lint.go new file mode 100644 index 00000000000..7108afdde9c --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/lint.go @@ -0,0 +1,159 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "context" + "fmt" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/gravitational/trace" +) + +func runLint(ctx context.Context, charts []Chart, rootDir string) error { + // Preflight check ot make sure yamllint is installed + if err := checkDependencies(yamllintBinName, helmBinName); err != nil { + return trace.Wrap(err, "preflight checks") + } + + configPath := filepath.Join(rootDir, yamlLintConfigPath) + + for _, chart := range charts { + if chart.IsLibrary { + continue + } + fmt.Println("Running lint for chart:", chart.Name) + valuesDir := filepath.Join(chart.Path, ".lint") + content, err := os.ReadDir(valuesDir) + if err != nil && !trace.IsNotFound(trace.ConvertSystemError(err)) { + return trace.Wrap(err, "reading values directory for %s", chart.Path) + } + + values := make([]os.DirEntry, 0, len(content)) + for _, file := range content { + if file.IsDir() { + log.Println("Skipping directory", file.Name()) + continue + } + ext := filepath.Ext(file.Name()) + if ext != ".yaml" && ext != ".yml" { + log.Printf("Skipping non-yaml file %s", file.Name()) + continue + } + values = append(values, file) + } + + // If the chart has no lint directory or if it's empty, we lint with the default values. + if len(values) == 0 { + if err := lint(ctx, "", configPath, chart); err != nil { + return trace.Wrap(err, "linting chart %s with default values", chart.Path) + } + } + + for _, file := range values { + if err := lint(ctx, filepath.Join(valuesDir, file.Name()), configPath, chart); err != nil { + return trace.Wrap(err, "linting chart %s", chart.Path) + } + } + } + + fmt.Println(" ✅ Charts successfully linted.") + return nil +} + +// lint runs all the lint operations on a chart for a given value file. +// If the value file path is empty, the chart is linted with its default values. +func lint(ctx context.Context, valuesPath, configPath string, chart Chart) error { + // Yamllint the values + if valuesPath != "" { + if stdout, stderr, err := run(ctx, yamllintBinName, "-c", configPath, valuesPath); err != nil { + fmt.Printf(" ❌ yamllint values %q failed\n", valuesPath) + // yamllint seems to output to stdout + fmt.Println(string(stdout)) + fmt.Println(string(stderr)) + return trace.Wrap(err, "linting values %q", valuesPath) + } + } + + // Helm lint + args := []string{ + "lint", "--quiet", "--strict", chart.Path, + } + if valuesPath != "" { + args = append(args, "-f", valuesPath) + } + if stdout, stderr, err := run(ctx, helmBinName, args...); err != nil { + fmt.Printf(" ❌ Helm linting chart %q failed with values %q\n", chart.Name, valuesPath) + fmt.Println(string(stdout)) + fmt.Println(string(stderr)) + return trace.Wrap(err, "linting with values %q", valuesPath) + } + + // Render the manifests + tmpDest, err := os.CreateTemp("", "*-out.yaml") + if err != nil { + return trace.ConvertSystemError(err) + } + defer func() { + tmpDest.Close() + os.Remove(tmpDest.Name()) + }() + + args = []string{"template", "test", chart.Path} + if valuesPath != "" { + args = append(args, "-f", valuesPath) + } + rendered, stderr, err := run(ctx, helmBinName, args...) + if err != nil { + fmt.Println(string(stderr)) + return trace.Wrap(err, "rendering templates for values %q", valuesPath) + } + if _, err := tmpDest.Write(rendered); err != nil { + return trace.ConvertSystemError(err) + } + + // Yammllint the manifests + if stdout, stderr, err := run(ctx, yamllintBinName, "-c", configPath, tmpDest.Name()); err != nil { + fmt.Printf(" ❌ yamllint rendered chart %q with values %q failed\n", chart.Name, valuesPath) + // We output the linted template to stdout with line numbers to make finding and fixing the error easier. + fmt.Println(" 🔎 Linted templates:") + printWithLineNumbers(rendered) + fmt.Println() + fmt.Println(" ⚠️ Linting errors:") + fmt.Println(string(stdout)) + fmt.Println(string(stderr)) + return trace.Wrap(err, "linting rendered templates for values %q", valuesPath) + } + + return nil +} + +func printWithLineNumbers(stdout []byte) { + lines := strings.Split(string(stdout), "\n") + count := len(lines) + numDigit := len(strconv.FormatInt(int64(count), 10)) + + for i := 0; i < count; i++ { + fmt.Printf("%*d | %s\n", numDigit+1, i+1, lines[i]) + } +} diff --git a/build.assets/tooling/cmd/helm-janitor/main.go b/build.assets/tooling/cmd/helm-janitor/main.go new file mode 100644 index 00000000000..e10d37f4f23 --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/main.go @@ -0,0 +1,259 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "os/signal" + "sort" + "syscall" + + "github.com/gravitational/trace" + "gopkg.in/yaml.v3" +) + +// Chart represents a chart we want to test, lint, publish a reference for, and update. +type Chart struct { + // Name of the chart. + Name string + // Path of the chart, relative to the teleport repo root. + Path string + // ReferencePath is where the generated reference is stored. + // When it's empty, no reference is generated. + ReferencePath string + // IsLibrary describes if the chart is a library chart. + // Library charts cannot be installed and are not directly tested, nor linted. + IsLibrary bool +} + +// charts is the source of truth for the list of charts we maintain. +// If you need to introduce a new chart, add to this list. +var charts = []Chart{ + { + Name: "teleport-cluster", + Path: "examples/chart/teleport-cluster", + // teleport-cluster reference is still hand-written. + ReferencePath: "", + }, + { + Name: "teleport-kube-agent", + Path: "examples/chart/teleport-kube-agent", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx", + }, + { + Name: "teleport-relay", + Path: "examples/chart/teleport-relay", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-relay.mdx", + }, + { + Name: "teleport-operator", + Path: "examples/chart/teleport-cluster/charts/teleport-operator", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-operator.mdx", + }, + { + Name: "access-email", + Path: "examples/chart/access/email", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-email.mdx", + }, + { + Name: "access-jira", + Path: "examples/chart/access/jira", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-jira.mdx", + }, + { + Name: "access-mattermost", + Path: "examples/chart/access/mattermost", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-mattermost.mdx", + }, + { + Name: "access-msteams", + Path: "examples/chart/access/msteams", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-msteams.mdx", + }, + { + Name: "access-pagerduty", + Path: "examples/chart/access/pagerduty", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-pagerduty.mdx", + }, + { + Name: "access-slack", + Path: "examples/chart/access/slack", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-slack.mdx", + }, + { + Name: "access-discord", + Path: "examples/chart/access/discord", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-discord.mdx", + }, + { + Name: "access-datadog", + Path: "examples/chart/access/datadog", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.access-datadog.mdx", + }, + { + Name: "event-handler", + Path: "examples/chart/event-handler", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.event-handler.mdx", + }, + { + Name: "tbot", + Path: "examples/chart/tbot", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.tbot.mdx", + }, + { + Name: "tbot-spiffe-daemon-set", + Path: "examples/chart/tbot-spiffe-daemon-set", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.tbot-spiffe-daemon-set.mdx", + }, + { + Name: "teleport-kube-updater", + Path: "examples/chart/teleport-kube-updater", + ReferencePath: "", + IsLibrary: true, + }, +} + +const usage = `Usage: + helm-janitor all [--charts=] [--root-dir=] + helm-janitor test [--charts=] [--root-dir=] + helm-janitor lint [--charts=] [--root-dir=] + helm-janitor reference [--check] [--charts=] [--root-dir=] + + helm-janitor list [--root-dir=] + helm-janitor update-version [--root-dir=] + + is a comma-separated list of chart names. + is the path to the teleport repo root. +` + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + + go func() { + select { + case <-ch: + cancel() + case <-ctx.Done(): + } + }() + + if len(os.Args) < 2 { + fmt.Fprint(os.Stderr, usage) + os.Exit(1) + } + command := os.Args[1] + + fs := flag.NewFlagSet("helm-janitor", flag.ExitOnError) + chartsFlag := fs.String("charts", "", "Comma-separated list of chart names") + updateSnapshotsFlag := fs.Bool("update-snapshots", false, "Update Helm test snapshots") + checkFlag := fs.Bool("check", false, "Check if references are up to date") + rootDirFlag := fs.String("root-dir", "", "Root directory of the teleport repo.") + + if err := fs.Parse(os.Args[2:]); err != nil { + log.Fatal(err) + } + + selectedCharts, err := selectCharts(*chartsFlag, *rootDirFlag) + if err != nil { + log.Fatal(err) + } + + switch command { + case "all": + if err := runAll(ctx, selectedCharts, *rootDirFlag); err != nil { + log.Fatal(err) + } + + case "test": + if err := runTest(ctx, selectedCharts, *updateSnapshotsFlag); err != nil { + log.Fatal(err) + } + + case "lint": + if err := runLint(ctx, selectedCharts, *rootDirFlag); err != nil { + log.Fatal(err) + } + + case "reference", "ref": + if err := runReference(ctx, selectedCharts, *checkFlag); err != nil { + log.Fatal(err) + } + + case "list": + if err := listCharts(ctx, selectedCharts); err != nil { + log.Fatal(err) + } + + case "update-version": + args := fs.Args() + if len(args) != 1 { + fmt.Fprintln(os.Stderr, "Error: update-version requires exactly one argument (version)") + fmt.Fprint(os.Stderr, usage) + os.Exit(1) + } + version := args[0] + + if err := updateVersion(ctx, version, selectedCharts); err != nil { + log.Fatal(err) + } + + default: + fmt.Fprintf(os.Stderr, "Unknown command: %s\n\n", command) + fmt.Fprint(os.Stderr, usage) + os.Exit(1) + } +} + +func runAll(ctx context.Context, charts []Chart, rootDir string) error { + fmt.Println("Running all operations...") + if err := runLint(ctx, charts, rootDir); err != nil { + return trace.Wrap(err) + } + const updateSnapshots = false + if err := runTest(ctx, charts, updateSnapshots); err != nil { + return trace.Wrap(err) + } + if err := runReference(ctx, charts, false); err != nil { + return trace.Wrap(err) + } + return nil +} + +func listCharts(ctx context.Context, charts []Chart) error { + fmt.Println("Available charts:") + paths := make([]string, len(charts)) + for i, chart := range charts { + paths[i] = chart.Path + } + sort.Strings(paths) + out, err := yaml.Marshal(paths) + if err != nil { + return err + } + fmt.Println(string(out)) + return nil +} diff --git a/build.assets/tooling/cmd/helm-janitor/reference.go b/build.assets/tooling/cmd/helm-janitor/reference.go new file mode 100644 index 00000000000..1a01f49a4d8 --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/reference.go @@ -0,0 +1,68 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "bytes" + "context" + "fmt" + "os" + + "github.com/google/go-cmp/cmp" + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/build.assets/tooling/lib/helm" +) + +func runReference(ctx context.Context, charts []Chart, check bool) error { + for _, chart := range charts { + if chart.ReferencePath == "" { + // teleport-cluster's reference is not yet rendered + continue + } + fmt.Printf("Rendering reference for chart %s\n", chart.Path) + ref, err := helm.RenderReference(chart.Path) + if err != nil { + return trace.Wrap(err, "rendering chart reference for %q", chart.Path) + } + if check { + existing, err := os.ReadFile(chart.ReferencePath) + if err != nil { + return trace.ConvertSystemError(err) + } + if !bytes.Equal(existing, ref) { + fmt.Printf(" ❌ Out-of-sync reference for chart %q.\n", chart.Name) + fmt.Println("Please run `make -C example/chart render-chart-ref`") + fmt.Println() + fmt.Println(cmp.Diff(string(existing), string(ref))) + return trace.CompareFailed("reference is out of date for chart %q", chart.Path) + } + continue + } + if err := os.WriteFile(chart.ReferencePath, ref, 0644); err != nil { + return trace.ConvertSystemError(err) + } + } + if check { + fmt.Println(" ✅ All references are up-to-date") + } else { + fmt.Println(" ✅ All references rendered") + } + return nil +} diff --git a/build.assets/tooling/cmd/helm-janitor/test.go b/build.assets/tooling/cmd/helm-janitor/test.go new file mode 100644 index 00000000000..526c793f583 --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/test.go @@ -0,0 +1,63 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/gravitational/trace" +) + +func runTest(ctx context.Context, charts []Chart, updateSnapshots bool) error { + if err := checkDependencies(helmBinName); err != nil { + return trace.Wrap(err, "preflight checks") + } + + for _, chart := range charts { + if chart.IsLibrary { + continue + } + if err := testHelm(ctx, chart, updateSnapshots); err != nil { + return trace.Wrap(err) + } + } + + fmt.Println(" ✅ All tests succeeded") + return nil +} + +func testHelm(ctx context.Context, chart Chart, updateSnapshots bool) error { + fmt.Println("Running tests for chart:", chart.Name) + args := []string{"unittest", "-3", "--with-subchart=false", chart.Path} + if updateSnapshots { + args = append(args, "-u") + } + // We log the test command so it's easier for a developer to copy it and re-run to target a failing test. + fmt.Printf(" ▶️ %s %s\n", helmBinName, strings.Join(args, " ")) + stdout, stderr, err := run(ctx, helmBinName, args...) + if err != nil { + fmt.Printf(" ❌ Helm unit tests failed for chart %q", chart.Path) + fmt.Println(string(stdout)) + fmt.Println(string(stderr)) + return trace.Wrap(err, "testing chart %q", chart.Path) + } + return nil +} diff --git a/build.assets/tooling/cmd/helm-janitor/utils.go b/build.assets/tooling/cmd/helm-janitor/utils.go new file mode 100644 index 00000000000..70e3449d83e --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/utils.go @@ -0,0 +1,116 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "bytes" + "context" + "errors" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/gravitational/trace" +) + +const ( + yamllintBinName = "yamllint" + helmBinName = "helm" + yamlLintConfigPath = "examples/chart/.lint-config.yaml" +) + +func checkDependencies(names ...string) error { + for _, name := range names { + _, err := exec.LookPath(name) + if err != nil { + return trace.NotFound("%s not found in $PATH", name) + } + } + return nil +} + +func run(ctx context.Context, command string, args ...string) ([]byte, []byte, error) { + cmd := exec.CommandContext(ctx, command, args...) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd.Env = os.Environ() + if err := cmd.Run(); err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + return stdout.Bytes(), stderr.Bytes(), trace.Wrap(err, "command %s exited with status %d", command, exitErr.ExitCode()) + } + return stdout.Bytes(), stderr.Bytes(), trace.Wrap(err) + } + return stdout.Bytes(), stderr.Bytes(), nil +} + +func chartsWithPath(rootDir string) []Chart { + if rootDir == "" { + rootDir = "." + } + pathedCharts := make([]Chart, len(charts)) + for i, chart := range charts { + var path, referencePath string + if chart.Path != "" { + path = filepath.Join(rootDir, chart.Path) + } + if chart.ReferencePath != "" { + referencePath = filepath.Join(rootDir, chart.ReferencePath) + } + pathedCharts[i] = Chart{ + Name: chart.Name, + Path: path, + ReferencePath: referencePath, + IsLibrary: chart.IsLibrary, + } + } + return pathedCharts +} + +func selectCharts(chartNames string, rootDir string) ([]Chart, error) { + charts := chartsWithPath(rootDir) + if chartNames == "" { + return charts, nil + } + + validNameSet := make(map[string]struct{}) + for _, chart := range charts { + validNameSet[chart.Name] = struct{}{} + } + + selectedNames := strings.Split(chartNames, ",") + selectedNameSet := make(map[string]struct{}) + for _, name := range selectedNames { + if _, ok := validNameSet[name]; !ok { + return nil, trace.NotFound("unknown chart name: %s", name) + } + selectedNameSet[strings.TrimSpace(name)] = struct{}{} + } + + var selected []Chart + for _, chart := range charts { + if _, ok := selectedNameSet[chart.Name]; ok { + selected = append(selected, chart) + } + } + + return selected, nil +} diff --git a/build.assets/tooling/cmd/helm-janitor/utils_test.go b/build.assets/tooling/cmd/helm-janitor/utils_test.go new file mode 100644 index 00000000000..d1828734b5b --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/utils_test.go @@ -0,0 +1,115 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSelectCharts(t *testing.T) { + tests := []struct { + name string + chartNames string + rootDir string + expected []Chart + expectErr require.ErrorAssertionFunc + }{ + { + name: "no chart name should select all charts", + chartNames: "", + rootDir: "", + expected: chartsWithPath("."), + expectErr: require.NoError, + }, + { + name: "single chart name", + chartNames: "teleport-kube-agent", + rootDir: "", + expected: []Chart{ + { + Name: "teleport-kube-agent", + Path: "examples/chart/teleport-kube-agent", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx", + }, + }, + expectErr: require.NoError, + }, + { + name: "multiple chart name", + chartNames: "teleport-kube-agent,teleport-relay", + rootDir: "", + expected: []Chart{ + { + Name: "teleport-kube-agent", + Path: "examples/chart/teleport-kube-agent", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx", + }, + { + Name: "teleport-relay", + Path: "examples/chart/teleport-relay", + ReferencePath: "docs/pages/includes/helm-reference/zz_generated.teleport-relay.mdx", + }, + }, + expectErr: require.NoError, + }, + { + name: "single chart name with root dir", + chartNames: "teleport-kube-agent", + rootDir: "/tmp/teleport", + expected: []Chart{ + { + Name: "teleport-kube-agent", + Path: "/tmp/teleport/examples/chart/teleport-kube-agent", + ReferencePath: "/tmp/teleport/docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx", + }, + }, + expectErr: require.NoError, + }, + { + name: "single library chart", + chartNames: "teleport-kube-updater", + rootDir: "", + expected: []Chart{ + { + Name: "teleport-kube-updater", + Path: "examples/chart/teleport-kube-updater", + IsLibrary: true, + }, + }, + expectErr: require.NoError, + }, + { + name: "unknown chart name", + chartNames: "unknown-chart", + rootDir: "", + expected: nil, + expectErr: require.Error, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result, err := selectCharts(test.chartNames, test.rootDir) + test.expectErr(t, err) + require.Equal(t, test.expected, result) + }) + } + +} diff --git a/build.assets/tooling/cmd/helm-janitor/version.go b/build.assets/tooling/cmd/helm-janitor/version.go new file mode 100644 index 00000000000..d412dc1c725 --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/version.go @@ -0,0 +1,60 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/gravitational/trace" +) + +func updateVersion(ctx context.Context, version string, charts []Chart) error { + for _, chart := range charts { + if err := updateChartVersion(ctx, chart, version); err != nil { + return trace.Wrap(err, "updating version of chart %q", chart.Name) + } + } + fmt.Printf(" ✅ Version updated to %s\n", version) + return nil +} + +var versionRegex = regexp.MustCompile(`\.version: .*`) + +func updateChartVersion(ctx context.Context, chart Chart, version string) error { + version = strings.TrimPrefix(version, "v") + chartYaml, err := os.ReadFile(filepath.Join(chart.Path, "Chart.yaml")) + if err != nil { + return trace.Wrap(trace.ConvertSystemError(err), "reading Chart.yaml") + } + newChartYaml := versionRegex.ReplaceAll(chartYaml, []byte(fmt.Sprintf(`.version: &version %q`, version))) + if bytes.Equal(chartYaml, newChartYaml) { + fmt.Printf(" ⚠️ Warning: Chart.yaml unchanged: %q\n", chart.Path) + } + if err := os.WriteFile(filepath.Join(chart.Path, "Chart.yaml"), newChartYaml, 0644); err != nil { + return trace.Wrap(err, "writing Chart.yaml") + } + + return nil +} diff --git a/build.assets/tooling/cmd/helm-janitor/version_test.go b/build.assets/tooling/cmd/helm-janitor/version_test.go new file mode 100644 index 00000000000..b78d74179eb --- /dev/null +++ b/build.assets/tooling/cmd/helm-janitor/version_test.go @@ -0,0 +1,67 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + testChartYaml = ` +.version: &version "1.2.3" + +name: test-chart +version: *version +appVersion: *version + +dependencies: + - name: other-test-chart + version: *version +` + updatedTestChartYaml = ` +.version: &version "1.2.4-foobar" + +name: test-chart +version: *version +appVersion: *version + +dependencies: + - name: other-test-chart + version: *version +` +) + +func TestUpdateChartVersion(t *testing.T) { + dir := t.TempDir() + chart := Chart{ + Name: "test-chart", + Path: dir, + } + require.NoError(t, os.WriteFile(filepath.Join(dir, "Chart.yaml"), []byte(testChartYaml), 0644)) + require.NoError(t, updateChartVersion(t.Context(), chart, "1.2.4-foobar")) + require.FileExists(t, filepath.Join(dir, "Chart.yaml")) + updatedChart, err := os.ReadFile(filepath.Join(dir, "Chart.yaml")) + require.NoError(t, err) + require.Equal(t, updatedTestChartYaml, string(updatedChart)) + +} diff --git a/build.assets/tooling/cmd/render-helm-ref/README.md b/build.assets/tooling/cmd/render-helm-ref/README.md index 5927b426705..08350c7451a 100644 --- a/build.assets/tooling/cmd/render-helm-ref/README.md +++ b/build.assets/tooling/cmd/render-helm-ref/README.md @@ -13,8 +13,8 @@ go run ./cmd/render-helm-ref/ \ ### `values.yaml` syntax -See [the test data `values.yaml`](./testdata/values.yaml) for an example input, -and [its expected output](./testdata/values.yaml) +See [the test data `values.yaml`](../../lib/helm/testdata/values.yaml) for an example input, +and [its expected output](../../lib/helm/testdata/values.yaml) ### Why? diff --git a/build.assets/tooling/cmd/render-helm-ref/main.go b/build.assets/tooling/cmd/render-helm-ref/main.go index 5cfa13b35eb..7a97e6317e4 100644 --- a/build.assets/tooling/cmd/render-helm-ref/main.go +++ b/build.assets/tooling/cmd/render-helm-ref/main.go @@ -19,33 +19,15 @@ package main import ( - "bufio" "context" - "encoding/json" "flag" "fmt" "log/slog" "os" - "regexp" - "strings" - "github.com/gravitational/trace" - "gopkg.in/yaml.v3" - "helm.sh/helm/v3/pkg/chart/loader" + "github.com/gravitational/teleport/build.assets/tooling/lib/helm" ) -// tagRegex matches tagged values with kind. For example: -// # full.path.valueName(kind) -- controls how to do X -var tagRegex = regexp.MustCompile(`^\s*#\s*(.*)\((.+)\)\s+--\s*(.*)$`) - -// tagRegex matches tagged values without kind. For example: -// # full.path.valueName -- controls how to do X -var tagRegexNoKind = regexp.MustCompile(`^\s*#\s*(.*)\s+--\s*(.*)$`) - -// defaultRegex matches the default override tag. For example: -// # @default -- see values.yaml -var defaultRegex = regexp.MustCompile(`^\s*# @default -- (.*)$`) - func main() { var chartPath string var outputPath string @@ -59,7 +41,7 @@ func main() { os.Exit(1) } - reference, err := parseAndRender(chartPath) + reference, err := helm.RenderReference(chartPath) if err != nil { slog.ErrorContext(ctx, "failed parsing chart and rendering reference", "error", err) os.Exit(1) @@ -76,192 +58,3 @@ func main() { } slog.InfoContext(ctx, "File successfully written", "file_path", outputPath) } - -func parseAndRender(chartPath string) ([]byte, error) { - chartValues := chartPath + "/" + "values.yaml" - - // First, we check if we can load all the data we need. - // We need both the Helm chart (for automatic default detection) - chrt, err := loader.Load(chartPath) - if err != nil { - return nil, trace.Wrap(err, "failed to load chart") - } - - // And the raw values.yaml (for the documentation in comments) - valuesYAML, err := os.ReadFile(chartValues) - if err != nil { - return nil, trace.Wrap(err, "failed to open values '%s'", chartValues) - } - - // We parse the YAML and extract all the documented values form its comments - var n yaml.Node - err = yaml.Unmarshal(valuesYAML, &n) - if err != nil { - return nil, trace.Wrap(err, "failed to unmarshall values") - } - values := processYAMLNode(&n) - - // Then, we backfill the default value when possible - for _, value := range values { - // We can skip the default detection by setting no kind. This is useful when - // we are also documenting subfields and don't want an ugly Type/Default table. - if value.Kind != "" && value.Default == "" { - defaultValue, err := getDefaultForValue(value.Name, chrt.Values) - if err != nil { - slog.WarnContext(context.Background(), "failed to look up default value", - "value", value.Name, - "error", err, - ) - } else { - value.Default = string(defaultValue) - } - } - } - - // Finally we render - reference, err := renderTemplate(values) - return reference, trace.Wrap(err, "failed to render template") -} - -func processYAMLNode(node *yaml.Node) []*Value { - // The YAML structure does not represent the value hierarchy - // So we process all comments the same way and don't care about their position - var values []*Value - if value := grabValue(node.HeadComment); value != nil { - values = append(values, value) - } - if value := grabValue(node.LineComment); value != nil { - values = append(values, value) - } - if value := grabValue(node.FootComment); value != nil { - values = append(values, value) - } - if len(node.Content) != 0 { - for _, subNode := range node.Content { - values = append(values, processYAMLNode(subNode)...) - } - } - return values -} - -type Value struct { - Name string - Kind string - - Description string - Default string -} - -type state struct { - isDescription bool - description strings.Builder - - value *Value -} - -// grabValue walks through a comment and checks if it has a value tag. -// Once the value tag is found, everything after it will be part of the description. -func grabValue(comment string) *Value { - if comment == "" { - return nil - } - - scanner := bufio.NewScanner(strings.NewReader(comment)) - - var line string - - s := state{} - for scanner.Scan() { - line = scanner.Text() - if !s.isDescription { - // We are not yet in a comment containing documentation - match, name, kind, remain := matchTag(line) - if !match { - // no tag on this line, we skip it - continue - } - // start of a value documentation - s.isDescription = true - s.value = &Value{Name: name, Kind: kind} - s.description.WriteString(remain) - s.description.WriteRune('\n') - continue - } - // We already saw a tag on a previous line - // If we find a default override tag we process it, else we just add the - // line to the existing value description. - if match, defaultValue := matchDefaultTag(line); match { - s.value.Default = defaultValue - continue - } - s.description.WriteString(cleanLine(line)) - s.description.WriteRune('\n') - } - - if s.isDescription { - s.value.Description = strings.TrimSpace(s.description.String()) - } - return s.value -} - -func matchTag(line string) (match bool, name, kind, remain string) { - // If kind is specified - subMatches := tagRegex.FindStringSubmatch(line) - if len(subMatches) == 4 && subMatches[1] != "" { - return true, subMatches[1], subMatches[2], subMatches[3] - } - // If kind is not specified - subMatches = tagRegexNoKind.FindStringSubmatch(line) - if len(subMatches) == 3 && subMatches[1] != "" { - return true, subMatches[1], "", subMatches[2] - } - return false, "", "", "" -} - -func matchDefaultTag(line string) (bool, string) { - subMatches := defaultRegex.FindStringSubmatch(line) - if len(subMatches) != 2 { - return false, "" - } - return true, subMatches[1] -} - -func cleanLine(line string) string { - line2 := strings.TrimSpace(line) - if len(line2) < 3 { - return "" - } - if line2[0] != '#' { - slog.WarnContext(context.Background(), "Misformatted line", "line", line) - return "" - } - return line2[2:] -} - -// getDefaultForValue takes a value detected from the comments, and looks up its -// default value in the Helm chart. -func getDefaultForValue(valueName string, chartValues map[string]interface{}) ([]byte, error) { - parts := strings.Split(valueName, ".") - // Check if this is a nested value - if len(parts) > 1 { - chartValue, ok := chartValues[parts[0]] - if !ok { - // Stop if the value is unknown - return nil, trace.NotFound("value '%s' not found", parts[0]) - } - - // The value name is part0.part1...partX - // We expect the detected Helm value to be a map, else we don't know - // how to access "part1...partX" - if subValue, ok := chartValue.(map[string]interface{}); ok { - return getDefaultForValue(strings.Join(parts[1:], "."), subValue) - } - return nil, trace.CompareFailed("value %s cannot be cast to a map", parts[0]) - - } - // If the value is known we marshall it as JSON - if chartValue, ok := chartValues[parts[0]]; ok { - return json.Marshal(chartValue) - } - return nil, trace.NotFound("value '%s' not found", parts[0]) -} diff --git a/build.assets/tooling/go.mod b/build.assets/tooling/go.mod index 8962e196137..746c45fca4f 100644 --- a/build.assets/tooling/go.mod +++ b/build.assets/tooling/go.mod @@ -2,6 +2,8 @@ module github.com/gravitational/teleport/build.assets/tooling go 1.25.9 +tool github.com/gravitational/teleport/build.assets/tooling/cmd/helm-janitor + require ( buf.build/go/bufplugin v0.9.0 github.com/DataDog/datadog-agent/pkg/template v0.77.2 @@ -26,6 +28,8 @@ require ( k8s.io/apiextensions-apiserver v0.35.1 ) +require github.com/google/go-cmp v0.7.0 + require ( buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.36.3-20250121211742-6d880cc6cc8d.1 // indirect buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect @@ -147,7 +151,6 @@ require ( github.com/google/certificate-transparency-go v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-attestation v0.6.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry v0.21.4 // indirect github.com/google/go-github/v70 v70.0.0 // indirect github.com/google/go-querystring v1.2.0 // indirect diff --git a/build.assets/tooling/cmd/render-helm-ref/main_test.go b/build.assets/tooling/lib/helm/main_test.go similarity index 92% rename from build.assets/tooling/cmd/render-helm-ref/main_test.go rename to build.assets/tooling/lib/helm/main_test.go index c6f4551e39a..c83c888a18e 100644 --- a/build.assets/tooling/cmd/render-helm-ref/main_test.go +++ b/build.assets/tooling/lib/helm/main_test.go @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package main +package helm import ( "os" @@ -30,14 +30,14 @@ const ( testSnapshotPath = "./testdata/expected-output.mdx" ) -func Test_parseAndRender(t *testing.T) { +func Test_RenderReference(t *testing.T) { // Test setup: we load the fixtures expected, err := os.ReadFile(testSnapshotPath) require.NoError(t, err) require.NotEmpty(t, expected) // Test execution: we render templates, expect no error and check result - actual, err := parseAndRender(testChartPath) + actual, err := RenderReference(testChartPath) require.NoError(t, err) require.Equal(t, expected, actual) } diff --git a/build.assets/tooling/lib/helm/reference.go b/build.assets/tooling/lib/helm/reference.go new file mode 100644 index 00000000000..2f09bf3fceb --- /dev/null +++ b/build.assets/tooling/lib/helm/reference.go @@ -0,0 +1,234 @@ +/* + * Teleport + * Copyright (C) 2026 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package helm + +import ( + "bufio" + "context" + "encoding/json" + "log/slog" + "os" + "regexp" + "strings" + + "github.com/gravitational/trace" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chart/loader" +) + +// tagRegex matches tagged values with kind. For example: +// # full.path.valueName(kind) -- controls how to do X +var tagRegex = regexp.MustCompile(`^\s*#\s*(.*)\((.+)\)\s+--\s*(.*)$`) + +// tagRegex matches tagged values without kind. For example: +// # full.path.valueName -- controls how to do X +var tagRegexNoKind = regexp.MustCompile(`^\s*#\s*(.*)\s+--\s*(.*)$`) + +// defaultRegex matches the default override tag. For example: +// # @default -- see values.yaml +var defaultRegex = regexp.MustCompile(`^\s*# @default -- (.*)$`) + +func RenderReference(chartPath string) ([]byte, error) { + chartValues := chartPath + "/" + "values.yaml" + + // First, we check if we can load all the data we need. + // We need both the Helm chart (for automatic default detection) + chrt, err := loader.Load(chartPath) + if err != nil { + return nil, trace.Wrap(err, "failed to load chart") + } + + // And the raw values.yaml (for the documentation in comments) + valuesYAML, err := os.ReadFile(chartValues) + if err != nil { + return nil, trace.Wrap(err, "failed to open values '%s'", chartValues) + } + + // We parse the YAML and extract all the documented values form its comments + var n yaml.Node + err = yaml.Unmarshal(valuesYAML, &n) + if err != nil { + return nil, trace.Wrap(err, "failed to unmarshall values") + } + values := processYAMLNode(&n) + + // Then, we backfill the default value when possible + for _, value := range values { + // We can skip the default detection by setting no kind. This is useful when + // we are also documenting subfields and don't want an ugly Type/Default table. + if value.Kind != "" && value.Default == "" { + defaultValue, err := getDefaultForValue(value.Name, chrt.Values) + if err != nil { + slog.WarnContext(context.Background(), "failed to look up default value", + "value", value.Name, + "error", err, + ) + } else { + value.Default = string(defaultValue) + } + } + } + + // Finally we render + reference, err := renderTemplate(values) + return reference, trace.Wrap(err, "failed to render template") +} + +func processYAMLNode(node *yaml.Node) []*Value { + // The YAML structure does not represent the value hierarchy + // So we process all comments the same way and don't care about their position + var values []*Value + if value := grabValue(node.HeadComment); value != nil { + values = append(values, value) + } + if value := grabValue(node.LineComment); value != nil { + values = append(values, value) + } + if value := grabValue(node.FootComment); value != nil { + values = append(values, value) + } + if len(node.Content) != 0 { + for _, subNode := range node.Content { + values = append(values, processYAMLNode(subNode)...) + } + } + return values +} + +type Value struct { + Name string + Kind string + + Description string + Default string +} + +type state struct { + isDescription bool + description strings.Builder + + value *Value +} + +// grabValue walks through a comment and checks if it has a value tag. +// Once the value tag is found, everything after it will be part of the description. +func grabValue(comment string) *Value { + if comment == "" { + return nil + } + + scanner := bufio.NewScanner(strings.NewReader(comment)) + + var line string + + s := state{} + for scanner.Scan() { + line = scanner.Text() + if !s.isDescription { + // We are not yet in a comment containing documentation + match, name, kind, remain := matchTag(line) + if !match { + // no tag on this line, we skip it + continue + } + // start of a value documentation + s.isDescription = true + s.value = &Value{Name: name, Kind: kind} + s.description.WriteString(remain) + s.description.WriteRune('\n') + continue + } + // We already saw a tag on a previous line + // If we find a default override tag we process it, else we just add the + // line to the existing value description. + if match, defaultValue := matchDefaultTag(line); match { + s.value.Default = defaultValue + continue + } + s.description.WriteString(cleanLine(line)) + s.description.WriteRune('\n') + } + + if s.isDescription { + s.value.Description = strings.TrimSpace(s.description.String()) + } + return s.value +} + +func matchTag(line string) (match bool, name, kind, remain string) { + // If kind is specified + subMatches := tagRegex.FindStringSubmatch(line) + if len(subMatches) == 4 && subMatches[1] != "" { + return true, subMatches[1], subMatches[2], subMatches[3] + } + // If kind is not specified + subMatches = tagRegexNoKind.FindStringSubmatch(line) + if len(subMatches) == 3 && subMatches[1] != "" { + return true, subMatches[1], "", subMatches[2] + } + return false, "", "", "" +} + +func matchDefaultTag(line string) (bool, string) { + subMatches := defaultRegex.FindStringSubmatch(line) + if len(subMatches) != 2 { + return false, "" + } + return true, subMatches[1] +} + +func cleanLine(line string) string { + line2 := strings.TrimSpace(line) + if len(line2) < 3 { + return "" + } + if line2[0] != '#' { + slog.WarnContext(context.Background(), "Misformatted line", "line", line) + return "" + } + return line2[2:] +} + +// getDefaultForValue takes a value detected from the comments, and looks up its +// default value in the Helm chart. +func getDefaultForValue(valueName string, chartValues map[string]interface{}) ([]byte, error) { + parts := strings.Split(valueName, ".") + // Check if this is a nested value + if len(parts) > 1 { + chartValue, ok := chartValues[parts[0]] + if !ok { + // Stop if the value is unknown + return nil, trace.NotFound("value '%s' not found", parts[0]) + } + + // The value name is part0.part1...partX + // We expect the detected Helm value to be a map, else we don't know + // how to access "part1...partX" + if subValue, ok := chartValue.(map[string]interface{}); ok { + return getDefaultForValue(strings.Join(parts[1:], "."), subValue) + } + return nil, trace.CompareFailed("value %s cannot be cast to a map", parts[0]) + + } + // If the value is known we marshall it as JSON + if chartValue, ok := chartValues[parts[0]]; ok { + return json.Marshal(chartValue) + } + return nil, trace.NotFound("value '%s' not found", parts[0]) +} diff --git a/build.assets/tooling/cmd/render-helm-ref/template.go b/build.assets/tooling/lib/helm/template.go similarity index 99% rename from build.assets/tooling/cmd/render-helm-ref/template.go rename to build.assets/tooling/lib/helm/template.go index 10168526e2d..1bb3b43a972 100644 --- a/build.assets/tooling/cmd/render-helm-ref/template.go +++ b/build.assets/tooling/lib/helm/template.go @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package main +package helm import ( "bytes" diff --git a/build.assets/tooling/cmd/render-helm-ref/testdata/Chart.yaml b/build.assets/tooling/lib/helm/testdata/Chart.yaml similarity index 100% rename from build.assets/tooling/cmd/render-helm-ref/testdata/Chart.yaml rename to build.assets/tooling/lib/helm/testdata/Chart.yaml diff --git a/build.assets/tooling/cmd/render-helm-ref/testdata/expected-output.mdx b/build.assets/tooling/lib/helm/testdata/expected-output.mdx similarity index 100% rename from build.assets/tooling/cmd/render-helm-ref/testdata/expected-output.mdx rename to build.assets/tooling/lib/helm/testdata/expected-output.mdx diff --git a/build.assets/tooling/cmd/render-helm-ref/testdata/values.yaml b/build.assets/tooling/lib/helm/testdata/values.yaml similarity index 100% rename from build.assets/tooling/cmd/render-helm-ref/testdata/values.yaml rename to build.assets/tooling/lib/helm/testdata/values.yaml diff --git a/common.mk b/common.mk index 9038263c888..319eb14542b 100644 --- a/common.mk +++ b/common.mk @@ -56,8 +56,12 @@ diag-bpf-vars: # Dir of last included file, in this case common.mk: COMMON_MK_DIR := $(dir $(lastword $(MAKEFILE_LIST))) # This allows $(GOTESTSUM) in any Makefile that includes common.mk: +# TOOLS_DIR contains external test tools TOOLS_DIR := $(abspath $(COMMON_MK_DIR)/build.assets/tools) GOTESTSUM = "$$( GOWORK=off go -C $(TOOLS_DIR)/gotestsum tool -n gotestsum )" GCI = "$$( GOWORK=off go -C $(TOOLS_DIR)/gci tool -n gci )" GODA = "$$( GOWORK=off go -C $(TOOLS_DIR)/goda tool -n goda )" BENCHSTAT = "$$( GOWORK=off go -C $(TOOLS_DIR)/benchstat tool -n benchstat )" +# TOOLING_DIR contains internal tooling +TOOLING_DIR := $(abspath $(COMMON_MK_DIR)/build.assets/tooling) +HELMJANITOR = "$$( GOWORK=off CGO_ENABLED=0 go -C $(TOOLING_DIR) tool -n helm-janitor )" diff --git a/docs/pages/includes/helm-reference/zz_generated.access-mattermost.mdx b/docs/pages/includes/helm-reference/zz_generated.access-mattermost.mdx index 8af5e5a5aaf..dac861bf7a3 100644 --- a/docs/pages/includes/helm-reference/zz_generated.access-mattermost.mdx +++ b/docs/pages/includes/helm-reference/zz_generated.access-mattermost.mdx @@ -73,7 +73,7 @@ You can pass the Mattermost token: |------|---------| | `string` | `""` | -`mattermost.url` is the Jira URL. For example: `https://mattermost.example.com`. +`mattermost.url` is the Mattermost URL. For example: `https://mattermost.example.com`. ### `mattermost.token` diff --git a/examples/chart/Makefile b/examples/chart/Makefile index c78a5cd71fb..9f1b0261361 100644 --- a/examples/chart/Makefile +++ b/examples/chart/Makefile @@ -1,122 +1,33 @@ -# TODO(hugoShaka): uncomment the additional targets as we start sync-ing -# the reference and the values.yaml - -access = discord email jira mattermost msteams pagerduty slack datadog - -check_access = $(addprefix check-chart-ref-access-,$(access)) -render_access = $(addprefix render-chart-ref-access-,$(access)) +include ../../common.mk .PHONY: render-chart-ref -render-chart-ref: render-chart-ref-example render-chart-ref-teleport-operator render-chart-ref-teleport-kube-agent render-chart-ref-teleport-relay render-chart-ref-tbot render-chart-ref-tbot-spiffe-daemon-set $(render_access) render-chart-ref-event-handler # render-chart-ref-teleport-cluster +render-chart-ref: + $(HELMJANITOR) reference --root-dir="../.." -.PHONY: render-chart-ref-example -render-chart-ref-example: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ./cmd/render-helm-ref/testdata -output ./cmd/render-helm-ref/testdata/expected-output.mdx - -# .PHONY: render-chart-ref-teleport-cluster -# render-chart-ref-teleport-cluster: -# cd ../../build.assets/tooling && \ -# go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-cluster/ -output ../../docs/pages/includes/helm-reference/zz_generated.teleport-cluster.mdx -# -# -.PHONY: render-chart-ref-teleport-kube-agent -render-chart-ref-teleport-kube-agent: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-kube-agent/ -output ../../docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx - -.PHONY: render-chart-ref-teleport-relay -render-chart-ref-teleport-relay: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-relay/ -output ../../docs/pages/includes/helm-reference/zz_generated.teleport-relay.mdx - -.PHONY: render-chart-ref-teleport-operator -render-chart-ref-teleport-operator: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-cluster/charts/teleport-operator -output ../../docs/pages/includes/helm-reference/zz_generated.teleport-operator.mdx - -.PHONY: render-chart-ref-tbot -render-chart-ref-tbot: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/tbot -output ../../docs/pages/includes/helm-reference/zz_generated.tbot.mdx - -.PHONY: render-chart-ref-tbot-spiffe-daemon-set -render-chart-ref-tbot-spiffe-daemon-set: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/tbot-spiffe-daemon-set -output ../../docs/pages/includes/helm-reference/zz_generated.tbot-spiffe-daemon-set.mdx - -.PHONY: render-chart-ref-access-% -render-chart-ref-access-%: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/access/$* -output ../../docs/pages/includes/helm-reference/zz_generated.access-$*.mdx - -.PHONY: render-chart-ref-event-handler -render-chart-ref-event-handler: - cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/event-handler -output ../../docs/pages/includes/helm-reference/zz_generated.event-handler.mdx +.PHONY: render-chart-ref-% +render-chart-ref-%: + $(HELMJANITOR) reference --charts=$* --root-dir="../.." .PHONY: check-chart-ref -check-chart-ref: check-chart-ref-example check-chart-ref-teleport-operator check-chart-ref-teleport-kube-agent check-chart-ref-teleport-relay check-chart-ref-tbot check-chart-ref-tbot-spiffe-daemon-set $(check_access) check-chart-ref-event-handler #check-chart-ref-teleport-cluster +check-chart-ref: + $(HELMJANITOR) reference --root-dir="../.." --check -.PHONY: check-chart-ref-example -check-chart-ref-example: - @ echo "Checking example chart reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ./cmd/render-helm-ref/testdata -output - | diff ../../build.assets/tooling/cmd/render-helm-ref/testdata/expected-output.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) +.PHONY: check-chart-ref-% +check-chart-ref-%: + $(HELMJANITOR) reference --charts=$* --root-dir="../.." --check -# .PHONY: check-chart-ref-teleport-cluster -# check-chart-ref-teleport-cluster: -# echo "Checking teleport-cluster reference" -# cd ../../build.assets/tooling && \ -# go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-cluster -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.teleport-cluster.mdx - || \ -# ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) -# -.PHONY: check-chart-ref-teleport-kube-agent -check-chart-ref-teleport-kube-agent: - @ echo "Checking teleport-kube-agent reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-kube-agent -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.teleport-kube-agent.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) +.PHONY: test +test: + $(HELMJANITOR) test --root-dir="../.." -.PHONY: check-chart-ref-teleport-relay -check-chart-ref-teleport-relay: - @ echo "Checking teleport-relay reference" - @ cd ../../build.assets/tooling && \ - GOWORK=off go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-relay -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.teleport-relay.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) +.PHONY: test-% +test-%: + $(HELMJANITOR) test --charts=$* --root-dir="../.." -.PHONY: check-chart-ref-teleport-operator -check-chart-ref-teleport-operator: - @echo "Checking teleport-operator reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/teleport-cluster/charts/teleport-operator -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.teleport-operator.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) +.PHONY: lint +lint: + $(HELMJANITOR) lint --root-dir="../.." -.PHONY: check-chart-ref-tbot -check-chart-ref-tbot: - @echo "Checking tbot reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/tbot -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.tbot.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make render-chart-ref'" && exit 1 ) - -.PHONY: check-chart-ref-tbot-spiffe-daemon-set -check-chart-ref-tbot-spiffe-daemon-set: - @echo "Checking tbot-spiffe-daemon-set reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/tbot-spiffe-daemon-set -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.tbot-spiffe-daemon-set.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make render-chart-ref'" && exit 1 ) - -.PHONY: check-chart-ref-access-% -check-chart-ref-access-%: - @echo "Checking access/$* reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/access/$* -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.access-$*.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) - -.PHONY: check-chart-ref-event-handler -check-chart-ref-event-handler: - @echo "Checking event-handler reference" - @ cd ../../build.assets/tooling && \ - go run ./cmd/render-helm-ref -chart ../../examples/chart/event-handler -output - | diff ../../docs/pages/includes/helm-reference/zz_generated.event-handler.mdx - || \ - ( echo "Chart values.yaml and reference differ, please run 'make -C examples/chart render-chart-ref'" && exit 1 ) +.PHONY: lint-% +lint-%: + $(HELMJANITOR) lint --charts=$* --root-dir="../.." diff --git a/examples/chart/access/datadog/templates/configmap.yaml b/examples/chart/access/datadog/templates/configmap.yaml index 418c1db7d49..1432de048ee 100644 --- a/examples/chart/access/datadog/templates/configmap.yaml +++ b/examples/chart/access/datadog/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: data: teleport-datadog.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/datadog/teleport-identity/{{ include "datadog.identitySecretPath" . }}" refresh_identity = true diff --git a/examples/chart/access/discord/templates/configmap.yaml b/examples/chart/access/discord/templates/configmap.yaml index 9ce7d1d11b4..eb1e3afe98a 100644 --- a/examples/chart/access/discord/templates/configmap.yaml +++ b/examples/chart/access/discord/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: data: teleport-discord.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/discord/teleport-identity/{{ include "discord.identitySecretPath" . }}" refresh_identity = true diff --git a/examples/chart/access/email/templates/configmap.yaml b/examples/chart/access/email/templates/configmap.yaml index ce263faa24d..75d77934f89 100644 --- a/examples/chart/access/email/templates/configmap.yaml +++ b/examples/chart/access/email/templates/configmap.yaml @@ -11,15 +11,17 @@ metadata: data: teleport-email.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/email/teleport-identity/{{ include "email.identitySecretPath" . }}" refresh_identity = true - {{ if .Values.mailgun.enabled -}} + {{- if .Values.mailgun.enabled }} + [mailgun] domain = "{{ .Values.mailgun.domain }}" private_key_file = "/var/lib/teleport/plugins/email/mailgun_private_key" - {{ else if .Values.smtp.enabled -}} + {{- else if .Values.smtp.enabled }} + [smtp] host = "{{ .Values.smtp.host }}" port = {{ .Values.smtp.port }} diff --git a/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap index 33962fd77ac..b66711f5507 100644 --- a/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/email/tests/__snapshot__/configmap_test.yaml.snap @@ -12,7 +12,6 @@ should match the snapshot (mailgun on): domain = "mymailgunsubdomain.mailgun.org" private_key_file = "/var/lib/teleport/plugins/email/mailgun_private_key" - [delivery] sender = "" recipients = [] diff --git a/examples/chart/access/jira/templates/configmap.yaml b/examples/chart/access/jira/templates/configmap.yaml index 95d1c3d842d..7bf43cea2af 100644 --- a/examples/chart/access/jira/templates/configmap.yaml +++ b/examples/chart/access/jira/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: data: teleport-jira.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/jira/teleport-identity/{{ include "jira.identitySecretPath" . }}" refresh_identity = true @@ -30,7 +30,7 @@ data: https_key_file = "/var/lib/teleport/plugins/jira/tls/tls.key" https_cert_file = "/var/lib/teleport/plugins/jira/tls/tls.crt" - {{ if .Values.http.basicAuth.enabled -}} + {{- if .Values.http.basicAuth.enabled }} [http.basic_auth] user = {{ .Values.http.basicAuth.user }} password = {{ .Values.http.basicAuth.password }} diff --git a/examples/chart/access/jira/templates/secret.yaml b/examples/chart/access/jira/templates/secret.yaml index 76365c2e58a..b6a9060b066 100644 --- a/examples/chart/access/jira/templates/secret.yaml +++ b/examples/chart/access/jira/templates/secret.yaml @@ -8,6 +8,6 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} data: - jiraApiToken: {{ .Values.jira.apiToken | b64enc }} + jiraApiToken: {{ default "" (.Values.jira.apiToken | b64enc) | quote}} type: Opaque {{- end }} diff --git a/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap b/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap index f158ec22500..d7324dfc53d 100644 --- a/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap +++ b/examples/chart/access/jira/tests/__snapshot__/configmap_test.yaml.snap @@ -21,8 +21,6 @@ should match the snapshot (smtp on): https_key_file = "/var/lib/teleport/plugins/jira/tls/tls.key" https_cert_file = "/var/lib/teleport/plugins/jira/tls/tls.crt" - - [log] output = "/var/log/teleport-jira.log" severity = "DEBUG" diff --git a/examples/chart/access/mattermost/templates/configmap.yaml b/examples/chart/access/mattermost/templates/configmap.yaml index 95ea14e9ad8..7c0367f203c 100644 --- a/examples/chart/access/mattermost/templates/configmap.yaml +++ b/examples/chart/access/mattermost/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: data: teleport-mattermost.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/mattermost/teleport-identity/{{ include "mattermost.identitySecretPath" . }}" refresh_identity = true diff --git a/examples/chart/access/mattermost/values.yaml b/examples/chart/access/mattermost/values.yaml index 552e9eaefaf..691d72c84d1 100644 --- a/examples/chart/access/mattermost/values.yaml +++ b/examples/chart/access/mattermost/values.yaml @@ -47,7 +47,7 @@ teleport: # - via the chart Values by setting `mattermost.token` # - via an existing Kubernetes Secret by setting `mattermost.tokenFromSecret` mattermost: - # mattermost.url(string) -- is the Jira URL. For example: `https://mattermost.example.com`. + # mattermost.url(string) -- is the Mattermost URL. For example: `https://mattermost.example.com`. url: "" # mattermost.token(string) -- is the Mattermost token used by the plugin to interact # with Mattermost. When set, the Chart creates a Kubernetes Secret for you. diff --git a/examples/chart/access/msteams/templates/configmap.yaml b/examples/chart/access/msteams/templates/configmap.yaml index e54bc5e6861..a913efac228 100644 --- a/examples/chart/access/msteams/templates/configmap.yaml +++ b/examples/chart/access/msteams/templates/configmap.yaml @@ -11,9 +11,9 @@ metadata: data: teleport-msteams.toml: | preload = true - + [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/msteams/teleport-identity/{{ include "msteams.identitySecretPath" . }}" refresh_identity = true diff --git a/examples/chart/access/pagerduty/templates/configmap.yaml b/examples/chart/access/pagerduty/templates/configmap.yaml index 44d2808ac13..aa9396ddebc 100644 --- a/examples/chart/access/pagerduty/templates/configmap.yaml +++ b/examples/chart/access/pagerduty/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: data: teleport-pagerduty.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/pagerduty/teleport-identity/{{ include "pagerduty.identitySecretPath" . }}" refresh_identity = true diff --git a/examples/chart/access/slack/templates/configmap.yaml b/examples/chart/access/slack/templates/configmap.yaml index 829135c52bc..f35ad9c5846 100644 --- a/examples/chart/access/slack/templates/configmap.yaml +++ b/examples/chart/access/slack/templates/configmap.yaml @@ -11,7 +11,7 @@ metadata: data: teleport-slack.toml: | [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/slack/teleport-identity/{{ include "slack.identitySecretPath" . }}" refresh_identity = true diff --git a/examples/chart/event-handler/templates/configmap.yaml b/examples/chart/event-handler/templates/configmap.yaml index c2feef57525..e8f94728931 100644 --- a/examples/chart/event-handler/templates/configmap.yaml +++ b/examples/chart/event-handler/templates/configmap.yaml @@ -52,7 +52,7 @@ data: {{- end }} [teleport] - addr = {{ coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress | quote }} + addr = {{ default "" (coalesce .Values.teleport.address .Values.tbot.teleportProxyAddress .Values.tbot.teleportAuthAddress) | quote }} identity = "/var/lib/teleport/plugins/event-handler/teleport-identity/{{ include "event-handler.identitySecretPath" . }}" refresh.enabled = true diff --git a/version.mk b/version.mk index cd02ec5ef72..63d1dcb4138 100644 --- a/version.mk +++ b/version.mk @@ -1,3 +1,6 @@ +# Include common makefile shared between OSS and Ent. +include common.mk + GITREF=$(shell git describe --long --tags) # $(GITREF_GO) will be written to gitref.go @@ -17,14 +20,10 @@ setver: validate-semver helm-version tsh-version # so that chart versions are also kept in sync when the Teleport version is updated for a release. # If the version contains '-dev' (as it does on the master branch, or for development builds) then we get the latest # published major version number by parsing a sorted list of git tags instead, to make deploying the chart from master -# work as expected. Version numbers are quoted as a string because Helm otherwise treats dotted decimals as floats. -# The weird -i usage is to make the sed commands work the same on both Linux and Mac. Test on both platforms if you change it. +# work as expected. .PHONY:helm-version helm-version: - for CHART in teleport-cluster tbot tbot-spiffe-daemon-set teleport-kube-updater teleport-kube-agent teleport-cluster/charts/teleport-operator teleport-relay event-handler access/discord access/email access/jira access/mattermost access/msteams access/pagerduty access/slack access/datadog; do \ - sed -i'.bak' -e "s_^\\.version:\ .*_.version: \\&version \"$${VERSION}\"_g" examples/chart/$${CHART}/Chart.yaml || exit 1; \ - rm -f examples/chart/$${CHART}/Chart.yaml.bak; \ - done + $(HELMJANITOR) update-version $(VERSION) TSH_APP_PLISTS := $(wildcard build.assets/macos/*/tsh.app/Contents/Info.plist) PLIST_FILES := $(abspath $(TSH_APP_PLISTS))