From e72a10f649c960c9ae78448bbfd5cf48eb29fe5b Mon Sep 17 00:00:00 2001 From: Tobias Giese Date: Mon, 25 Nov 2024 10:22:04 +0100 Subject: [PATCH] crdutil: Add feature to also apply also single files Signed-off-by: Tobias Giese --- go.mod | 2 +- pkg/crdutil/crdutil.go | 53 +++++++++++++++++++++++++------------ pkg/crdutil/crdutil_test.go | 14 +++++----- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index e5b4476..6458179 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-logr/logr v1.4.2 github.com/onsi/ginkgo/v2 v2.21.0 github.com/onsi/gomega v1.34.2 + github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 k8s.io/api v0.31.2 k8s.io/apiextensions-apiserver v0.31.0 @@ -70,7 +71,6 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect diff --git a/pkg/crdutil/crdutil.go b/pkg/crdutil/crdutil.go index 9fa4f9e..29f02d9 100644 --- a/pkg/crdutil/crdutil.go +++ b/pkg/crdutil/crdutil.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +//nolint:depguard,lll package crdutil import ( @@ -26,6 +27,7 @@ import ( "path/filepath" "strings" + "github.com/spf13/pflag" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" v1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" @@ -49,20 +51,25 @@ func (s *StringList) Set(value string) error { } var ( - crdsDir StringList + files []string + recursive bool ) func initFlags() { - flag.Var(&crdsDir, "crds-dir", "Path to the directory containing the CRD manifests") - flag.Parse() - - if len(crdsDir) == 0 { - log.Fatalf("CRDs directory is required") + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.StringSliceVarP(&files, "filename", "f", files, + "The files that contain the configurations to apply.") + pflag.BoolVarP(&recursive, "recursive", "R", false, + "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.") + pflag.Parse() + + if len(files) == 0 { + log.Fatalf("CRDs directory or single CRDs are required") } - for _, crdDir := range crdsDir { + for _, crdDir := range files { if _, err := os.Stat(crdDir); os.IsNotExist(err) { - log.Fatalf("CRDs directory %s does not exist", crdsDir) + log.Fatalf("CRDs directory %s does not exist", files) } } } @@ -84,38 +91,47 @@ func EnsureCRDsCmd() { log.Fatalf("Failed to create API extensions client: %v", err) } - if err := walkCrdsDir(ctx, client.ApiextensionsV1().CustomResourceDefinitions()); err != nil { + if err := walkCRDs(ctx, client.ApiextensionsV1().CustomResourceDefinitions()); err != nil { log.Fatalf("Failed to apply CRDs: %v", err) } } -// walkCrdsDir walks the CRDs directory and applies each YAML file. -func walkCrdsDir(ctx context.Context, crdClient v1.CustomResourceDefinitionInterface) error { - for _, crdDir := range crdsDir { +// walkCRDs walks the CRDs directory and applies each YAML file. +func walkCRDs(ctx context.Context, crdClient v1.CustomResourceDefinitionInterface) error { + for _, crdDir := range files { + // We can skip the errors as it has been checked in initFlags. + parentDir, _ := os.Stat(crdDir) // Walk the directory recursively and apply each YAML file. err := filepath.Walk(crdDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if info.IsDir() || filepath.Ext(path) != ".yaml" { + if info.IsDir() { + return nil + } + if filepath.Ext(path) != ".yaml" && filepath.Ext(path) != ".yml" { + return nil + } + // If not recursive we want to only apply the CRDs in the top-level directory. + if !recursive && parentDir.IsDir() && filepath.Dir(path) != strings.TrimRight(crdDir, "/") { return nil } log.Printf("Apply CRDs from file: %s", path) - if err := applyCRDsFromFile(ctx, crdClient, path); err != nil { + if err := applyCRDs(ctx, crdClient, path); err != nil { return fmt.Errorf("apply CRD %s: %w", path, err) } return nil }) if err != nil { - return fmt.Errorf("walk the path %s: %w", crdsDir, err) + return fmt.Errorf("walk the path %s: %w", files, err) } } return nil } -// applyCRDsFromFile reads a YAML file, splits it into documents, and applies each CRD to the cluster. -func applyCRDsFromFile(ctx context.Context, crdClient v1.CustomResourceDefinitionInterface, filePath string) error { +// applyCRDs reads a YAML file, splits it into documents, and applies each CRD to the cluster. +func applyCRDs(ctx context.Context, crdClient v1.CustomResourceDefinitionInterface, filePath string) error { file, err := os.Open(filePath) if err != nil { return fmt.Errorf("open file %q: %w", filePath, err) @@ -171,6 +187,9 @@ func applyCRD( return fmt.Errorf("create CRD %s: %w", crd.Name, err) } } else { + if err != nil { + return fmt.Errorf("get CRD %s: %w", crd.Name, err) + } log.Printf("Update CRD %s", crd.Name) // Set resource version to update an existing CRD. crd.SetResourceVersion(curCRD.GetResourceVersion()) diff --git a/pkg/crdutil/crdutil_test.go b/pkg/crdutil/crdutil_test.go index 9691570..3de6671 100644 --- a/pkg/crdutil/crdutil_test.go +++ b/pkg/crdutil/crdutil_test.go @@ -38,13 +38,13 @@ var _ = Describe("CRD Application", func() { Expect(testCRDClient.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{})).NotTo(HaveOccurred()) }) - Describe("applyCRDsFromFile", func() { + Describe("applyCRDs", func() { It("should apply CRDs multiple times from a valid YAML file", func() { By("applying CRDs") - Expect(applyCRDsFromFile(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) - Expect(applyCRDsFromFile(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) - Expect(applyCRDsFromFile(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) - Expect(applyCRDsFromFile(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) + Expect(applyCRDs(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) + Expect(applyCRDs(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) + Expect(applyCRDs(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) + Expect(applyCRDs(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) By("verifying CRDs are applied") crds, err := testCRDClient.List(ctx, metav1.ListOptions{}) @@ -54,7 +54,7 @@ var _ = Describe("CRD Application", func() { It("should update CRDs", func() { By("applying CRDs") - Expect(applyCRDsFromFile(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) + Expect(applyCRDs(ctx, testCRDClient, "test-files/test-crds.yaml")).To(Succeed()) By("verifying CRDs do not have spec.foobar") for _, crdName := range []string{"bars.example.com", "foos.example.com"} { @@ -66,7 +66,7 @@ var _ = Describe("CRD Application", func() { } By("updating CRDs") - Expect(applyCRDsFromFile(ctx, testCRDClient, "test-files/updated-test-crds.yaml")).To(Succeed()) + Expect(applyCRDs(ctx, testCRDClient, "test-files/updated-test-crds.yaml")).To(Succeed()) By("verifying CRDs are updated") for _, crdName := range []string{"bars.example.com", "foos.example.com"} {