diff --git a/cmd/flux/debug_kustomization.go b/cmd/flux/debug_kustomization.go new file mode 100644 index 00000000..10366005 --- /dev/null +++ b/cmd/flux/debug_kustomization.go @@ -0,0 +1,125 @@ +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "sort" + "strings" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" + "github.com/fluxcd/pkg/kustomize" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/yaml" + + "github.com/fluxcd/flux2/v2/internal/utils" +) + +var debugKustomizationCmd = &cobra.Command{ + Use: "kustomization [name]", + Aliases: []string{"ks"}, + Short: "Debug a Flux Kustomization resource", + Long: withPreviewNote(`The debug kustomization command can be used to troubleshoot failing Flux Kustomization reconciliations. +WARNING: This command will print sensitive information if Kubernetes Secrets are referenced in the Kustomization .spec.postBuild.substituteFrom field.`), + Example: ` # Print the status of a Flux Kustomization + flux debug ks podinfo --show-status + + # Export the final variables used for post-build substitutions composed from referred ConfigMaps and Secrets + flux debug ks podinfo --show-vars > vars.env`, + RunE: debugKustomizationCmdRun, + Args: cobra.ExactArgs(1), +} + +type debugKustomizationFlags struct { + name string + showStatus bool + showVars bool +} + +var debugKustomizationArgs debugKustomizationFlags + +func init() { + debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showStatus, "show-status", false, "print the status of the Flux Kustomization") + debugKustomizationCmd.Flags().BoolVar(&debugKustomizationArgs.showVars, "show-vars", false, "print the final vars of the Flux Kustomization in dot env format") + debugCmd.AddCommand(debugKustomizationCmd) +} + +func debugKustomizationCmdRun(cmd *cobra.Command, args []string) error { + name := args[0] + + if (!debugKustomizationArgs.showStatus && !debugKustomizationArgs.showVars) || + (debugKustomizationArgs.showStatus && debugKustomizationArgs.showVars) { + return fmt.Errorf("either --show-status or --show-vars must be set") + } + + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + ks := &kustomizev1.Kustomization{} + ksName := types.NamespacedName{Namespace: *kubeconfigArgs.Namespace, Name: name} + if err := kubeClient.Get(ctx, ksName, ks); err != nil { + return err + } + + if debugKustomizationArgs.showStatus { + status, err := yaml.Marshal(ks.Status) + if err != nil { + return err + } + rootCmd.Print(string(status)) + return nil + } + + if debugKustomizationArgs.showVars { + ksObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ks) + if err != nil { + return err + } + + finalVars, err := kustomize.LoadVariables(ctx, kubeClient, unstructured.Unstructured{Object: ksObj}) + if err != nil { + return err + } + + if len(ks.Spec.PostBuild.Substitute) > 0 { + for k, v := range ks.Spec.PostBuild.Substitute { + finalVars[k] = strings.ReplaceAll(v, "\n", "") + } + } + + keys := make([]string, 0, len(finalVars)) + for k := range finalVars { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + rootCmd.Println(k + "=" + finalVars[k]) + } + } + + return nil +} diff --git a/cmd/flux/debug_kustomization_test.go b/cmd/flux/debug_kustomization_test.go new file mode 100644 index 00000000..d3742ed3 --- /dev/null +++ b/cmd/flux/debug_kustomization_test.go @@ -0,0 +1,71 @@ +//go:build unit +// +build unit + +/* +Copyright 2024 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "testing" +) + +func TestDebugKustomization(t *testing.T) { + namespace := allocateNamespace("debug") + + objectFile := "testdata/debug_kustomization/objects.yaml" + tmpl := map[string]string{ + "fluxns": namespace, + } + testEnv.CreateObjectFile(objectFile, tmpl, t) + + cases := []struct { + name string + arg string + goldenFile string + tmpl map[string]string + }{ + { + "debug status", + "debug ks test --show-status --show-vars=false", + "testdata/debug_kustomization/status.golden.yaml", + tmpl, + }, + { + "debug vars", + "debug ks test --show-vars --show-status=false", + "testdata/debug_kustomization/vars.golden.env", + tmpl, + }, + { + "debug vars from", + "debug ks test-from --show-vars --show-status=false", + "testdata/debug_kustomization/vars-from.golden.env", + tmpl, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + cmd := cmdTestCase{ + args: tt.arg + " -n=" + namespace, + assert: assertGoldenTemplateFile(tt.goldenFile, tmpl), + } + + cmd.runTestCmd(t) + }) + } +} diff --git a/cmd/flux/main_test.go b/cmd/flux/main_test.go index e814c585..30673b1f 100644 --- a/cmd/flux/main_test.go +++ b/cmd/flux/main_test.go @@ -470,6 +470,7 @@ func resetCmdArgs() { } envsubstArgs = envsubstFlags{} debugHelmReleaseArgs = debugHelmReleaseFlags{} + debugKustomizationArgs = debugKustomizationFlags{} } func isChangeError(err error) bool { diff --git a/cmd/flux/testdata/debug_kustomization/objects.yaml b/cmd/flux/testdata/debug_kustomization/objects.yaml new file mode 100644 index 00000000..b724d7d3 --- /dev/null +++ b/cmd/flux/testdata/debug_kustomization/objects.yaml @@ -0,0 +1,63 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .fluxns }} +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: test + namespace: {{ .fluxns }} +spec: + sourceRef: + kind: GitRepository + name: test + interval: 1m + path: "./" + prune: true + postBuild: + substitute: + TEST_OVERRIDE: "in-line" + TEST_INLINE: "in-line" + substituteFrom: + - kind: ConfigMap + name: test + - kind: Secret + name: test +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: test-from + namespace: {{ .fluxns }} +spec: + sourceRef: + kind: GitRepository + name: test + interval: 1m + path: "./" + prune: true + postBuild: + substituteFrom: + - kind: ConfigMap + name: test + - kind: Secret + name: test +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test + namespace: {{ .fluxns }} +data: + TEST_OVERRIDE: "cm" + TEST_CM: "cm" +--- +apiVersion: v1 +kind: Secret +metadata: + name: test + namespace: {{ .fluxns }} +stringData: + TEST_OVERRIDE: "secret" + TEST_SECRET: "secret" diff --git a/cmd/flux/testdata/debug_kustomization/status.golden.yaml b/cmd/flux/testdata/debug_kustomization/status.golden.yaml new file mode 100644 index 00000000..8f988e76 --- /dev/null +++ b/cmd/flux/testdata/debug_kustomization/status.golden.yaml @@ -0,0 +1 @@ +observedGeneration: -1 diff --git a/cmd/flux/testdata/debug_kustomization/vars-from.golden.env b/cmd/flux/testdata/debug_kustomization/vars-from.golden.env new file mode 100644 index 00000000..f69b74a9 --- /dev/null +++ b/cmd/flux/testdata/debug_kustomization/vars-from.golden.env @@ -0,0 +1,3 @@ +TEST_CM=cm +TEST_OVERRIDE=secret +TEST_SECRET=secret diff --git a/cmd/flux/testdata/debug_kustomization/vars.golden.env b/cmd/flux/testdata/debug_kustomization/vars.golden.env new file mode 100644 index 00000000..9d9f18a0 --- /dev/null +++ b/cmd/flux/testdata/debug_kustomization/vars.golden.env @@ -0,0 +1,4 @@ +TEST_CM=cm +TEST_INLINE=in-line +TEST_OVERRIDE=in-line +TEST_SECRET=secret