diff --git a/.github/workflows/on_pull-request_docs.yaml b/.github/workflows/on_pull-request_docs.yaml new file mode 100644 index 00000000..c95bf0ab --- /dev/null +++ b/.github/workflows/on_pull-request_docs.yaml @@ -0,0 +1,42 @@ +name: docs ci +on: + pull_request: + paths: + - '.github/workflows/on_pull_request_docs.yaml' + - 'Earthfile' + - '*/**.go' + - '*.go' + - 'go.mod' + - 'go.sum' + - 'docs/usage.md*' +jobs: + lint-docs: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - uses: wistia/parse-tool-versions@v1.0 + + - uses: earthly/actions-setup@v1 + with: { version: "${{ env.EARTHLY_TOOL_VERSION }}" } + + - name: rebuild the docs + run: | + earthly \ + +rebuild-docs \ + --GOLANG_VERSION="${{ env.GOLANG_TOOL_VERSION }}" + + - name: verify that the checked in file has not changed + run: | + #!/usr/bin/env bash + + exitCode=0 + + # Log the actual diff for debugging purposes + git diff --name-only | cat + if ! git diff --exit-code --quiet; then + echo "Please run 'earthly +rebuild-docs' and commit the results to this PR" + exitCode=1 + fi + + exit $exitCode diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml index 263779c6..f37c52b0 100644 --- a/.github/workflows/on_pull_request.yaml +++ b/.github/workflows/on_pull_request.yaml @@ -11,9 +11,6 @@ on: - '!.gitignore' # TODO: Move this to repo config -env: - EARTHLY_TOOL_VERSION: 0.7 - GOLANG_TOOL_VERSION: 1.19.3 jobs: build: runs-on: ubuntu-22.04 diff --git a/.github/workflows/on_pull_request_go.yaml b/.github/workflows/on_pull_request_go.yaml index e130d16c..61e77c08 100644 --- a/.github/workflows/on_pull_request_go.yaml +++ b/.github/workflows/on_pull_request_go.yaml @@ -8,9 +8,6 @@ on: - '*.go' - 'go.mod' - 'go.sum' -env: - EARTHLY_TOOL_VERSION: 0.7 - GOLANG_TOOL_VERSION: 1.19.3 jobs: ci-golang: runs-on: ubuntu-22.04 @@ -22,4 +19,4 @@ jobs: - uses: earthly/actions-setup@v1 with: { version: "v${{ env.EARTHLY_TOOL_VERSION }}" } - - run: earthly +ci-golang --GOLANG_VERSION=${{ env.GOLANG_TOOL_VERSION }} \ No newline at end of file + - run: earthly +ci-golang --GOLANG_VERSION=${{ env.GOLANG_TOOL_VERSION }} diff --git a/.tool-versions b/.tool-versions index 8f571f07..d7b8ebc1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,4 @@ -earthly 0.7.12 +earthly 0.7.14 golang 1.19.11 helm 3.12.2 helm-ct 3.8.0 diff --git a/Earthfile b/Earthfile index 574cd8d9..ba57d53d 100644 --- a/Earthfile +++ b/Earthfile @@ -5,7 +5,6 @@ test: BUILD +ci-helm ci-golang: - # This should be enabled at some point BUILD +lint-golang BUILD +validate-golang BUILD +test-golang @@ -34,6 +33,14 @@ go-deps: SAVE ARTIFACT go.mod AS LOCAL go.mod SAVE ARTIFACT go.sum AS LOCAL go.sum +rebuild-docs: + FROM +go-deps + + COPY . /src + RUN go run hacks/env-to-docs.go + + SAVE ARTIFACT ./docs/usage.md AS LOCAL ./docs/usage.md + validate-golang: FROM +go-deps @@ -139,13 +146,13 @@ docker-debug: SAVE IMAGE --push $CI_REGISTRY_IMAGE lint-golang: - ARG STATICCHECK_VERSION="0.3.3" + ARG STATICCHECK_VERSION="2023.1.3" FROM +go-deps # install staticcheck RUN FILE=staticcheck.tgz \ - && URL=https://github.com/dominikh/go-tools/releases/download/v$STATICCHECK_VERSION/staticcheck_linux_amd64.tar.gz \ + && URL=https://github.com/dominikh/go-tools/releases/download/$STATICCHECK_VERSION/staticcheck_linux_amd64.tar.gz \ && wget ${URL} \ --output-document ${FILE} \ && tar \ diff --git a/cmd/controller_cmd.go b/cmd/controller_cmd.go index 7018a47e..3a37b4b4 100644 --- a/cmd/controller_cmd.go +++ b/cmd/controller_cmd.go @@ -17,8 +17,8 @@ import ( "github.com/zapier/kubechecks/pkg/server" ) -// controllerCmd represents the run command -var controllerCmd = &cobra.Command{ +// ControllerCmd represents the run command +var ControllerCmd = &cobra.Command{ Use: "controller", Short: "Start the VCS Webhook handler.", Long: ``, @@ -61,35 +61,28 @@ var controllerCmd = &cobra.Command{ }, } +func panicIfError(err error) { + if err != nil { + panic(err) + } +} + func init() { - rootCmd.AddCommand(controllerCmd) + RootCmd.AddCommand(ControllerCmd) - flags := controllerCmd.Flags() - flags.String("fallback-k8s-version", "1.23.0", "Fallback target Kubernetes version for schema / upgrade checks (KUBECHECKS_FALLBACK_K8S_VERSION).") - flags.Bool("show-debug-info", false, "Set to true to print debug info to the footer of MR comments (KUBECHECKS_SHOW_DEBUG_INFO).") - flags.Bool("enable-conftest", false, "Set to true to enable conftest policy checking of manifests (KUBECHECKS_ENABLE_CONFTEST).") - flags.String("label-filter", "", "(Optional) If set, The label that must be set on an MR (as \"kubechecks:\") for kubechecks to process the merge request webhook (KUBECHECKS_LABEL_FILTER).") - flags.String("openai-api-token", "", "OpenAI API Token (KUBECHECKS_OPENAI_API_TOKEN).") - flags.String("webhook-url-base", "", "The URL where KubeChecks receives webhooks from Gitlab") - flags.String("webhook-url-prefix", "", "If your application is running behind a proxy that uses path based routing, set this value to match the path prefix.") - flags.String("webhook-secret", "", "Optional secret key for validating the source of incoming webhooks.") - flags.Bool("monitor-all-applications", false, "Monitor all applications in argocd automatically") - flags.Bool("ensure-webhooks", false, "Ensure that webhooks are created in repositories referenced by argo") + flags := ControllerCmd.Flags() + stringFlag(flags, "fallback-k8s-version", "Fallback target Kubernetes version for schema / upgrade checks.", + newStringOpts(). + withDefault("1.23.0")) + boolFlag(flags, "show-debug-info", "Set to true to print debug info to the footer of MR comments.") + boolFlag(flags, "enable-conftest", "Set to true to enable conftest policy checking of manifests.") + stringFlag(flags, "label-filter", `(Optional) If set, The label that must be set on an MR (as "kubechecks:") for kubechecks to process the merge request webhook.`) + stringFlag(flags, "openai-api-token", "OpenAI API Token.") + stringFlag(flags, "webhook-url-base", "The URL where KubeChecks receives webhooks from Gitlab.") + stringFlag(flags, "webhook-url-prefix", "If your application is running behind a proxy that uses path based routing, set this value to match the path prefix.") + stringFlag(flags, "webhook-secret", "Optional secret key for validating the source of incoming webhooks.") + boolFlag(flags, "monitor-all-applications", "Monitor all applications in argocd automatically.") + boolFlag(flags, "ensure-webhooks", "Ensure that webhooks are created in repositories referenced by argo.") - // Map viper to cobra flags, so we can get these parameters from Environment variables if set. - panicIfError := func(err error) { - if err != nil { - panic(err) - } - } - panicIfError(viper.BindPFlag("enable-conftest", flags.Lookup("enable-conftest"))) - panicIfError(viper.BindPFlag("fallback-k8s-version", flags.Lookup("fallback-k8s-version"))) - panicIfError(viper.BindPFlag("show-debug-info", flags.Lookup("show-debug-info"))) - panicIfError(viper.BindPFlag("label-filter", flags.Lookup("label-filter"))) - panicIfError(viper.BindPFlag("openai-api-token", flags.Lookup("openai-api-token"))) - panicIfError(viper.BindPFlag("webhook-url-base", flags.Lookup("webhook-url-base"))) - panicIfError(viper.BindPFlag("webhook-url-prefix", flags.Lookup("webhook-url-prefix"))) - panicIfError(viper.BindPFlag("webhook-secret", flags.Lookup("webhook-secret"))) - panicIfError(viper.BindPFlag("ensure-webhooks", flags.Lookup("ensure-webhooks"))) - panicIfError(viper.BindPFlag("monitor-all-applications", flags.Lookup("monitor-all-applications"))) + panicIfError(viper.BindPFlags(flags)) } diff --git a/cmd/flags.go b/cmd/flags.go new file mode 100644 index 00000000..dec203c4 --- /dev/null +++ b/cmd/flags.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/spf13/pflag" +) + +type DocOpt[D any] struct { + choices []string + defaultValue *D + shorthand *string +} + +func combine[D any](dst *DocOpt[D], src DocOpt[D]) { + if src.choices != nil { + dst.choices = src.choices + } + if src.defaultValue != nil { + dst.defaultValue = src.defaultValue + } + if src.shorthand != nil { + dst.shorthand = src.shorthand + } +} + +func ViperNameToEnv(s string) string { + s = envKeyReplacer.Replace(s) + s = fmt.Sprintf("%s_%s", envPrefix, s) + s = strings.ToUpper(s) + return s +} + +func boolFlag(flags *pflag.FlagSet, name, usage string, opts ...DocOpt[bool]) { + addFlag(name, usage, opts, flags.Bool, flags.BoolP) +} + +func newStringOpts() DocOpt[string] { + return DocOpt[string]{} +} + +func stringFlag(flags *pflag.FlagSet, name, usage string, opts ...DocOpt[string]) { + addFlag(name, usage, opts, flags.String, flags.StringP) +} + +func addFlag[D any]( + name, usage string, + opts []DocOpt[D], + onlyLong func(string, D, string) *D, + longAndShort func(string, string, D, string) *D, +) { + var opt DocOpt[D] + for _, o := range opts { + combine(&opt, o) + } + + usage = generateUsage(opt, usage, name) + var defaultValue D + if opt.defaultValue != nil { + defaultValue = *opt.defaultValue + } + + if opt.shorthand != nil { + longAndShort(name, *opt.shorthand, defaultValue, usage) + } else { + onlyLong(name, defaultValue, usage) + } +} + +func generateUsage[D any](opt DocOpt[D], usage string, name string) string { + if !strings.HasSuffix(usage, ".") { + panic(fmt.Sprintf("usage for %q must end with a period.", name)) + } + + if opt.choices != nil { + usage = fmt.Sprintf("%s One of %s.", usage, strings.Join(opt.choices, ", ")) + } + + envVar := ViperNameToEnv(name) + usage = fmt.Sprintf("%s (%s)", usage, envVar) + return usage +} + +func (d DocOpt[D]) withDefault(def D) DocOpt[D] { + d.defaultValue = &def + return d +} + +func (d DocOpt[D]) withShortHand(short string) DocOpt[D] { + d.shorthand = &short + return d +} + +func (d DocOpt[D]) withChoices(choices ...string) DocOpt[D] { + d.choices = choices + return d +} diff --git a/cmd/flags_test.go b/cmd/flags_test.go new file mode 100644 index 00000000..73c52fff --- /dev/null +++ b/cmd/flags_test.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStringUsages(t *testing.T) { + tests := map[string]struct { + expected string + name string + opt DocOpt[any] + usage string + }{ + "string with choices": { + name: "simple-string", + opt: DocOpt[any]{ + choices: []string{ + "blah", + "test", + }, + }, + usage: "This is a test.", + expected: "This is a test. One of blah, test. (KUBECHECKS_SIMPLE_STRING)", + }, + "string with out of order choices": { + name: "simple-string", + opt: DocOpt[any]{ + choices: []string{ + "test", + "blah", + }, + }, + usage: "This is a test.", + expected: "This is a test. One of test, blah. (KUBECHECKS_SIMPLE_STRING)", + }, + "string with no choices": { + name: "string", + opt: DocOpt[any]{}, + usage: "This is a test.", + expected: "This is a test. (KUBECHECKS_STRING)", + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + + actual := generateUsage(test.opt, test.usage, test.name) + assert.Equal(t, test.expected, actual) + }) + } +} diff --git a/cmd/root.go b/cmd/root.go index 6130497e..ef717751 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -10,12 +10,11 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/zapier/kubechecks/telemetry" ) -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ Use: "kubechecks", Short: "Argo Git Hooks", Long: `A Kubernetes controller and webhook server for integration of ArgoCD applications into CI`, @@ -33,35 +32,58 @@ func Execute() { defer t.Shutdown() - cobra.CheckErr(rootCmd.Execute()) - + cobra.CheckErr(RootCmd.Execute()) } +const envPrefix = "kubechecks" + +var envKeyReplacer = strings.NewReplacer("-", "_") + func init() { // allows environment variables to use _ instead of - - viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) // sync-provider becomes SYNC_PROVIDER - viper.SetEnvPrefix("kubechecks") // port becomes KUBECHECKS_PORT - viper.AutomaticEnv() // read in environment variables that match - - flags := rootCmd.PersistentFlags() - flags.StringP("log-level", "l", "info", "Set the log output level (info, debug, trace)") - flags.Bool("persist_log_level", false, "Persists the set log level down to other module loggers") - flags.String("vcs-base-url", "", "VCS base url, useful if self hosting gitlab, enterprise github, etc") - flags.String("vcs-type", "gitlab", "VCS type, defaults to Gitlab. (KUBECHECKS_VCS_TYPE).") - flags.String("vcs-token", "", "VCS API token (KUBECHECKS_VCS_TOKEN).") - flags.String("argocd-api-token", "", "ArgoCD API token (KUBECHECKS_ARGOCD_API_TOKEN).") - flags.String("argocd-api-server-addr", "argocd-server", "ArgoCD API Server Address (KUBECHECKS_ARGOCD_API_SERVER_ADDR).") - flags.Bool("argocd-api-insecure", false, "Enable to use insecure connections to the ArgoCD API server (KUBECHECKS_ARGOCD_API_INSECURE).") - - flags.String("otel-collector-port", "", "The OpenTelemetry collector port (KUBECHECKS_OTEL_COLLECTOR_PORT).") - flags.String("otel-collector-host", "", "The OpenTelemetry collector host (KUBECHECKS_OTEL_COLLECTOR_HOST).") - flags.Bool("otel-enabled", false, "Enable OpenTelemetry (KUBECHECKS_OTEL_ENABLED).") - - flags.StringP("tidy-outdated-comments-mode", "", "hide", "Sets the mode to use when tidying outdated comments. Defaults to hide. Other options are delete, hide (KUBECHECKS_TIDY_OUTDATED_COMMENTS_MODE).") - flags.StringP("schemas-location", "", "./schemas", "Sets the schema location. Can be local path or git repository. Defaults to ./schemas (KUBECHECKS_SCHEMAS_LOCATION)") - - viper.BindPFlags(flags) + viper.SetEnvKeyReplacer(envKeyReplacer) // sync-provider becomes SYNC_PROVIDER + viper.SetEnvPrefix(envPrefix) // port becomes KUBECHECKS_PORT + viper.AutomaticEnv() // read in environment variables that match + + flags := RootCmd.PersistentFlags() + stringFlag(flags, "log-level", "Set the log output level.", + newStringOpts(). + withChoices( + zerolog.LevelErrorValue, + zerolog.LevelWarnValue, + zerolog.LevelInfoValue, + zerolog.LevelDebugValue, + zerolog.LevelTraceValue, + ). + withDefault("info"). + withShortHand("l"), + ) + boolFlag(flags, "persist-log-level", "Persists the set log level down to other module loggers.") + stringFlag(flags, "vcs-base-url", "VCS base url, useful if self hosting gitlab, enterprise github, etc.") + stringFlag(flags, "vcs-type", "VCS type. One of gitlab or github. Defaults to gitlab.", + newStringOpts(). + withChoices("github", "gitlab"). + withDefault("gitlab")) + stringFlag(flags, "vcs-token", "VCS API token.") + stringFlag(flags, "argocd-api-token", "ArgoCD API token.") + stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.", newStringOpts().withDefault("argocd-server")) + boolFlag(flags, "argocd-api-insecure", "Enable to use insecure connections to the ArgoCD API server.") + + stringFlag(flags, "otel-collector-port", "The OpenTelemetry collector port.") + stringFlag(flags, "otel-collector-host", "The OpenTelemetry collector host.") + boolFlag(flags, "otel-enabled", "Enable OpenTelemetry.") + + stringFlag(flags, "tidy-outdated-comments-mode", "Sets the mode to use when tidying outdated comments.", + newStringOpts(). + withChoices("hide", "delete"). + withDefault("hide"), + ) + stringFlag(flags, "schemas-location", "Sets the schema location. Can be local path or git repository.", + newStringOpts(). + withDefault("./schemas")) + + panicIfError(viper.BindPFlags(flags)) setupLogOutput() diff --git a/cmd/version.go b/cmd/version.go index 48eaaa7e..d8a5bd8b 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -17,5 +17,5 @@ var versionCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(versionCmd) + RootCmd.AddCommand(versionCmd) } diff --git a/docs/usage.md b/docs/usage.md index 035f42ad..9d2e920f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -35,30 +35,27 @@ Refer to [configuration](#configuration) for details about the various options a The full list of supported environment variables is described below: |Env Var|Description|Default Value| -|-------|-----------|-------------| -|`KUBECHECKS_ARGOCD_API_INSECURE`|Configure whether `kubechecks` is allowed to communicate with ArgoCD insecurely|`false`| -|`KUBECHECKS_ARGOCD_API_PATH_PREFIX`|Prefix to apply to ArgoCD API calls made by `kubechecks`|`"/"`| -|`KUBECHECKS_ARGOCD_API_SERVER_ADDR`|ArgoCD API Server Address|`null`| -|`KUBECHECKS_ARGOCD_WEBHOOK_URL`|ArgoCD Webhook URL `kubechecks` should use|`null`| -|`KUBECHECKS_FALLBACK_K8S_VERSION`|Fallback target Kubernetes version for schema / upgrade checks|`"1.22.0"`| -|`KUBECHECKS_LOG_LEVEL`|Log level verbosity, one of `[info, debug, trace]`|`"info"`| -|`KUBECHECKS_NAMESPACE`|Kubernetes namespace `kubechecks` is deployed in|`kubechecks`| -|`KUBECHECKS_WEBHOOK_URL_BASE`|The URL where KubeChecks receives webhooks from|`null`| -|`KUBECHECKS_WEBHOOK_URL_PREFIX`|If your application is running behind a proxy that uses path based routing, set this value to match the path prefix.|`null`| -|`KUBECHECKS_WEBHOOK_SECRET`|Optional secret key for validating the source of incoming webhooks.|`""`| -|`KUBECHECKS_OTEL_ENABLED`|Enable OpenTelemetry tracing|`false`| -|`KUBECHECKS_OTEL_COLLECTOR_PORT`|OpenTelemetry collector port \(if OTel is enabled\) |`null`| -|`KUBECHECKS_OTEL_COLLECTOR_HOST`|The OpenTelemetry collector host|`null`| -|`KUBECHECKS_SHOW_DEBUG_INFO`| Set to true to print debug info to the footer of MR comments | `false`| -|`KUBECHECKS_VCS_TYPE`| Which VCS Client to utilise (one of `gitlab` or `github`) | `gitlab`| -|`KUBECHECKS_LABEL_FILTER`|If set, the label that must be set on an PR/MR (as "kubechecks:") for kubechecks to process the merge request webhook|`null`| - -The following configuration is done via Kubernetes Secrets; ensure these are specified under the `secrets` section of `values.yaml` or through your chosen secrets provider before attempting to run `kubechecks`: - -|Secret|Description|Default Value| -|-------|-----------|-------------| -|`KUBECHECKS_VCS_TOKEN`| VCS API Token for communicating with your VCS provider | `null`| -|`KUBECHECKS_ARGOCD_API_TOKEN`| ArgoCD API Token for communicating with your ArgoCD installation| `null`| -|`KUBECHECKS_OPENAI_API_TOKEN`| OpenAI API Token for generating AI diff summaries |`null`| - -**Note that the prefix `KUBECHECKS_` is required for all environment variables due to the way the application is designed.** +|-----------|-------------|------| +|`KUBECHECKS_ARGOCD_API_INSECURE`|Enable to use insecure connections to the ArgoCD API server.|`false`| +|`KUBECHECKS_ARGOCD_API_SERVER_ADDR`|ArgoCD API Server Address.|`argocd-server`| +|`KUBECHECKS_ARGOCD_API_TOKEN`|ArgoCD API token.|| +|`KUBECHECKS_ENABLE_CONFTEST`|Set to true to enable conftest policy checking of manifests.|`false`| +|`KUBECHECKS_ENSURE_WEBHOOKS`|Ensure that webhooks are created in repositories referenced by argo.|`false`| +|`KUBECHECKS_FALLBACK_K8S_VERSION`|Fallback target Kubernetes version for schema / upgrade checks.|`1.23.0`| +|`KUBECHECKS_LABEL_FILTER`|(Optional) If set, The label that must be set on an MR (as "kubechecks:") for kubechecks to process the merge request webhook.|| +|`KUBECHECKS_LOG_LEVEL`|Set the log output level. One of error, warn, info, debug, trace.|`info`| +|`KUBECHECKS_MONITOR_ALL_APPLICATIONS`|Monitor all applications in argocd automatically.|`false`| +|`KUBECHECKS_OPENAI_API_TOKEN`|OpenAI API Token.|| +|`KUBECHECKS_OTEL_COLLECTOR_HOST`|The OpenTelemetry collector host.|| +|`KUBECHECKS_OTEL_COLLECTOR_PORT`|The OpenTelemetry collector port.|| +|`KUBECHECKS_OTEL_ENABLED`|Enable OpenTelemetry.|`false`| +|`KUBECHECKS_PERSIST_LOG_LEVEL`|Persists the set log level down to other module loggers.|`false`| +|`KUBECHECKS_SCHEMAS_LOCATION`|Sets the schema location. Can be local path or git repository.|`./schemas`| +|`KUBECHECKS_SHOW_DEBUG_INFO`|Set to true to print debug info to the footer of MR comments.|`false`| +|`KUBECHECKS_TIDY_OUTDATED_COMMENTS_MODE`|Sets the mode to use when tidying outdated comments. One of hide, delete.|`hide`| +|`KUBECHECKS_VCS_BASE_URL`|VCS base url, useful if self hosting gitlab, enterprise github, etc.|| +|`KUBECHECKS_VCS_TOKEN`|VCS API token.|| +|`KUBECHECKS_VCS_TYPE`|VCS type. One of gitlab or github.|`gitlab`| +|`KUBECHECKS_WEBHOOK_SECRET`|Optional secret key for validating the source of incoming webhooks.|| +|`KUBECHECKS_WEBHOOK_URL_BASE`|The URL where KubeChecks receives webhooks from Gitlab.|| +|`KUBECHECKS_WEBHOOK_URL_PREFIX`|If your application is running behind a proxy that uses path based routing, set this value to match the path prefix.|| diff --git a/docs/usage.md.tpl b/docs/usage.md.tpl new file mode 100644 index 00000000..edf2f496 --- /dev/null +++ b/docs/usage.md.tpl @@ -0,0 +1,41 @@ +# Usage + +## Installation + +`kubechecks` currently only officially supports deployment to a Kubernetes Cluster via Helm. + +### Requirements + +1. Kubernetes Cluster +2. Github/Gitlab token (for authenticating to the repository) +3. ArgoCD + +### Helm Installation + +To get started, add the `kubechecks` repository to Helm: + +# Add kubechecks helm chart repo + +```console +helm repo add kubechecks https://zapier.github.io/kubechecks/ +``` + +Once installed, simply run: + +```console +helm install kubechecks charts/kubechecks -n kubechecks --create-namespace +``` + +Refer to [configuration](#configuration) for details about the various options available for customising `kubechecks`. You **must** provide the required secrets in some capacity; refer to the chart for more details + +## Configuration + +`kubechecks` can be configured to meet your specific set up through the use of enviornment variables defined in your provided `values.yaml`. + +The full list of supported environment variables is described below: + +|Env Var|Description|Default Value| +|-----------|-------------|------| +{{- range .Options }} +|`{{ .Env }}`|{{ .Usage }}|{{ if .Default }}`{{ .Default }}`{{ end }}| +{{- end }} diff --git a/hacks/env-to-docs.go b/hacks/env-to-docs.go new file mode 100644 index 00000000..091cb9dc --- /dev/null +++ b/hacks/env-to-docs.go @@ -0,0 +1,100 @@ +package main + +import ( + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "text/template" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/zapier/kubechecks/cmd" +) + +type option struct { + Option string + Env string + Usage string + Default string +} + +var UsageEnvVar = regexp.MustCompile(` \(KUBECHECKS_[_A-Z0-9]+\)`) +var UsageDefaultValue = regexp.MustCompile(`Defaults to \.?(.*)+\.`) + +func main() { + outputFilename := filepath.Join("docs", "usage.md") + templateFilename := outputFilename + ".tpl" + + data, err := os.ReadFile(templateFilename) + if err != nil { + panic(err) + } + t, err := template.New("usage").Parse(string(data)) + if err != nil { + panic(err) + } + + _, err = os.Stat(outputFilename) + if err != nil && !os.IsNotExist(err) { + panic(err) + } else if err == nil { + if err = os.Remove(outputFilename); err != nil { + panic(err) + } + } + + flagUsage := make(map[string]option) + + cleanUpUsage := func(s string) string { + s = UsageEnvVar.ReplaceAllString(s, "") + s = UsageDefaultValue.ReplaceAllString(s, "") + s = strings.TrimSpace(s) + return s + } + + visitFlag := func(flag *pflag.Flag) { + flagUsage[flag.Name] = option{ + Default: flag.DefValue, + Env: cmd.ViperNameToEnv(flag.Name), + Option: flag.Name, + Usage: cleanUpUsage(flag.Usage), + } + } + + addFlags(cmd.RootCmd, visitFlag) + addFlags(cmd.ControllerCmd, visitFlag) + + vars := getSortedFlags(flagUsage) + + f, err := os.OpenFile(outputFilename, os.O_WRONLY|os.O_CREATE, 0o666) + if err != nil { + panic(err) + } + + type templateVars struct { + Options []option + } + if err = t.Execute(f, templateVars{vars}); err != nil { + panic(err) + } +} + +func getSortedFlags(flagUsage map[string]option) []option { + var keys []string + for key := range flagUsage { + keys = append(keys, key) + } + sort.Strings(keys) + var vars []option + for _, key := range keys { + vars = append(vars, flagUsage[key]) + } + return vars +} + +func addFlags(cmd *cobra.Command, visitFlag func(flag *pflag.Flag)) { + cmd.Flags().VisitAll(visitFlag) + cmd.PersistentFlags().VisitAll(visitFlag) +}