mirror of
https://github.com/gravitational/teleport.git
synced 2026-08-28 21:12:20 +08:00
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:
@@ -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'
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -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)
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package main
|
||||
package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user