Consolidate all the Helm Makefiles (#65455)

* Extract Helm logic into lib

* Add helm-janitor

* Fix lint values

* Use helmjanitor for tests, lint, and version-update

* Convert example/chart Makefile to helm-janitor + add library support

* Fix release pipeline

* lint

* Disable CGO

* lint
This commit is contained in:
Hugo Shaka
2026-04-29 17:26:31 -04:00
committed by GitHub
parent 7bb55db115
commit 8ea6a9b687
36 changed files with 1216 additions and 393 deletions
+1
View File
@@ -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'
+12 -40
View File
@@ -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' \
@@ -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 <http://www.gnu.org/licenses/>.
*/
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])
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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=<names>] [--root-dir=<path>]
helm-janitor test [--charts=<names>] [--root-dir=<path>]
helm-janitor lint [--charts=<names>] [--root-dir=<path>]
helm-janitor reference [--check] [--charts=<names>] [--root-dir=<path>]
helm-janitor list [--root-dir=<path>]
helm-janitor update-version <version> [--root-dir=<path>]
<names> is a comma-separated list of chart names.
<path> 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
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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)
})
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
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))
}
@@ -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?
@@ -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])
}
+4 -1
View File
@@ -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
@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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)
}
+234
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
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])
}
@@ -16,7 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
package helm
import (
"bytes"
+4
View File
@@ -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 )"
@@ -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`
+23 -112
View File
@@ -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="../.."
@@ -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
@@ -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
@@ -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 }}
@@ -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 = []
@@ -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 }}
@@ -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 }}
@@ -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"
@@ -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
+1 -1
View File
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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
+5 -6
View File
@@ -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))