From d9204c4db74e8e957ea5f786ed74862355f73b01 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Mon, 10 Jan 2022 12:56:53 +0100 Subject: [PATCH] add set command to modify comp desc --- .../componentarchive/componentarchive.go | 3 + pkg/commands/componentarchive/create.go | 6 + pkg/commands/componentarchive/set/set.go | 114 ++++++++++++++++++ pkg/commands/componentarchive/set/set_test.go | 110 +++++++++++++++++ .../00-component/component-descriptor.yaml | 19 +++ pkg/componentarchive/archive.go | 10 +- 6 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 pkg/commands/componentarchive/set/set.go create mode 100644 pkg/commands/componentarchive/set/set_test.go create mode 100644 pkg/commands/componentarchive/set/testdata/00-component/component-descriptor.yaml diff --git a/pkg/commands/componentarchive/componentarchive.go b/pkg/commands/componentarchive/componentarchive.go index 3e18f7d7..0c6143bd 100644 --- a/pkg/commands/componentarchive/componentarchive.go +++ b/pkg/commands/componentarchive/componentarchive.go @@ -21,6 +21,7 @@ import ( "github.com/gardener/component-cli/pkg/commands/componentarchive/componentreferences" "github.com/gardener/component-cli/pkg/commands/componentarchive/remote" "github.com/gardener/component-cli/pkg/commands/componentarchive/resources" + "github.com/gardener/component-cli/pkg/commands/componentarchive/set" "github.com/gardener/component-cli/pkg/commands/componentarchive/sources" ctfcmd "github.com/gardener/component-cli/pkg/commands/ctf" "github.com/gardener/component-cli/pkg/componentarchive" @@ -74,6 +75,7 @@ func NewComponentArchiveCommand(ctx context.Context) *cobra.Command { cmd.AddCommand(resources.NewResourcesCommand(ctx)) cmd.AddCommand(componentreferences.NewCompRefCommand(ctx)) cmd.AddCommand(sources.NewSourcesCommand(ctx)) + cmd.AddCommand(set.NewSetCommand(ctx)) return cmd } @@ -110,6 +112,7 @@ func (o *ComponentArchiveOptions) Run(ctx context.Context, log logr.Logger, fs v return fmt.Errorf("unable to add component archive to ctf: %w", err) } log.Info("Successfully added ctf\n") + return nil } // only copy essential files to the temp dir diff --git a/pkg/commands/componentarchive/create.go b/pkg/commands/componentarchive/create.go index 5d340a53..c6d41b73 100644 --- a/pkg/commands/componentarchive/create.go +++ b/pkg/commands/componentarchive/create.go @@ -6,6 +6,7 @@ package componentarchive import ( "context" + "errors" "fmt" "os" @@ -70,6 +71,11 @@ func (o *CreateOptions) Complete(args []string) error { } func (o *CreateOptions) validate() error { + if len(o.Name) != 0 { + if len(o.Version) == 0 { + return errors.New("a version has to be provided for a minimal component descriptor") + } + } return o.BuilderOptions.Validate() } diff --git a/pkg/commands/componentarchive/set/set.go b/pkg/commands/componentarchive/set/set.go new file mode 100644 index 00000000..3678fcda --- /dev/null +++ b/pkg/commands/componentarchive/set/set.go @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2020 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package set + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + + cdvalidation "github.com/gardener/component-spec/bindings-go/apis/v2/validation" + "github.com/gardener/component-spec/bindings-go/ctf" + "github.com/go-logr/logr" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/vfs" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "sigs.k8s.io/yaml" + + "github.com/gardener/component-cli/pkg/componentarchive" + "github.com/gardener/component-cli/pkg/logger" + "github.com/gardener/component-cli/pkg/template" +) + +// Options defines the options that are used to add resources to a component descriptor +type Options struct { + componentarchive.BuilderOptions + TemplateOptions template.Options +} + +// NewAddCommand creates a command to add additional resources to a component descriptor. +func NewSetCommand(ctx context.Context) *cobra.Command { + opts := &Options{} + cmd := &cobra.Command{ + Use: "set COMPONENT_ARCHIVE_PATH [options...]", + Args: cobra.MinimumNArgs(0), + Short: "set some component descriptor properties", + Long: fmt.Sprintf(` +the set command sets some component descriptor properies like the component name and/or version. + +The component archive can be specified by the first argument, the flag "--archive" or as env var "COMPONENT_ARCHIVE_PATH". +The component archive is expected to be a filesystem archive. +`), + Run: func(cmd *cobra.Command, args []string) { + if err := opts.Complete(args); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + if err := opts.Run(ctx, logger.Log, osfs.New()); err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + }, + } + + opts.AddFlags(cmd.Flags()) + + return cmd +} + +func (o *Options) Run(ctx context.Context, log logr.Logger, fs vfs.FileSystem) error { + compDescFilePath := filepath.Join(o.ComponentArchivePath, ctf.ComponentDescriptorFileName) + + o.Modify = true + archive, err := o.BuilderOptions.Build(fs) + if err != nil { + return err + } + + if len(o.Name) != 0 { + archive.ComponentDescriptor.Name = o.Name + } + if len(o.Version) != 0 { + archive.ComponentDescriptor.Version = o.Version + } + + if err := cdvalidation.Validate(archive.ComponentDescriptor); err != nil { + return fmt.Errorf("invalid component descriptor: %w", err) + } + + data, err := yaml.Marshal(archive.ComponentDescriptor) + if err != nil { + return fmt.Errorf("unable to encode component descriptor: %w", err) + } + if err := vfs.WriteFile(fs, compDescFilePath, data, 0664); err != nil { + return fmt.Errorf("unable to write modified comonent descriptor: %w", err) + } + log.V(2).Info(fmt.Sprintf("Successfully changed component descriptor")) + return nil +} + +func (o *Options) Complete(args []string) error { + args = o.TemplateOptions.Parse(args) + + if len(args) == 0 { + return errors.New("at least a component archive path argument has to be defined") + } + o.BuilderOptions.ComponentArchivePath = args[0] + o.BuilderOptions.Default() + + return o.validate() +} + +func (o *Options) validate() error { + return o.BuilderOptions.Validate() +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + o.BuilderOptions.AddFlags(fs) +} diff --git a/pkg/commands/componentarchive/set/set_test.go b/pkg/commands/componentarchive/set/set_test.go new file mode 100644 index 00000000..259761f7 --- /dev/null +++ b/pkg/commands/componentarchive/set/set_test.go @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2020 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package set_test + +import ( + "context" + "path/filepath" + "testing" + + cdv2 "github.com/gardener/component-spec/bindings-go/apis/v2" + "github.com/gardener/component-spec/bindings-go/codec" + "github.com/gardener/component-spec/bindings-go/ctf" + "github.com/go-logr/logr" + "github.com/mandelsoft/vfs/pkg/layerfs" + "github.com/mandelsoft/vfs/pkg/memoryfs" + "github.com/mandelsoft/vfs/pkg/osfs" + "github.com/mandelsoft/vfs/pkg/projectionfs" + "github.com/mandelsoft/vfs/pkg/vfs" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + + "github.com/gardener/component-cli/pkg/commands/componentarchive/set" + "github.com/gardener/component-cli/pkg/componentarchive" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Resources Test Suite") +} + +var _ = Describe("Set", func() { + + var testdataFs vfs.FileSystem + + BeforeEach(func() { + fs, err := projectionfs.New(osfs.New(), "./testdata") + Expect(err).ToNot(HaveOccurred()) + testdataFs = layerfs.New(memoryfs.New(), fs) + }) + + It("should set name", func() { + opts := &set.Options{ + BuilderOptions: componentarchive.BuilderOptions{ + ComponentArchivePath: "./00-component", + Name: "xxxx.xx/name", + }, + } + + Expect(opts.Run(context.TODO(), logr.Discard(), testdataFs)).To(Succeed()) + + data, err := vfs.ReadFile(testdataFs, filepath.Join(opts.ComponentArchivePath, ctf.ComponentDescriptorFileName)) + Expect(err).ToNot(HaveOccurred()) + + cd := &cdv2.ComponentDescriptor{} + Expect(codec.Decode(data, cd)).To(Succeed()) + + Expect(cd.Resources).To(HaveLen(1)) + Expect(cd.Resources[0].IdentityObjectMeta).To(MatchFields(IgnoreExtras, Fields{ + "Name": Equal("ubuntu"), + "Version": Equal("v0.0.1"), + "Type": Equal("ociImage"), + "ExtraIdentity": HaveLen(0), + })) + Expect(cd.Resources[0]).To(MatchFields(IgnoreExtras, Fields{ + "Relation": Equal(cdv2.ResourceRelation("external")), + })) + Expect(cd.Resources[0].Access.Object).To(HaveKeyWithValue("type", "ociRegistry")) + Expect(cd.Resources[0].Access.Object).To(HaveKeyWithValue("imageReference", "ubuntu:18.0")) + + Expect(cd.Name).To(Equal("xxxx.xx/name")) + Expect(cd.Version).To(Equal("v0.0.0")) + }) + + It("should set version", func() { + opts := &set.Options{ + BuilderOptions: componentarchive.BuilderOptions{ + ComponentArchivePath: "./00-component", + Version: "v1", + }, + } + + Expect(opts.Run(context.TODO(), logr.Discard(), testdataFs)).To(Succeed()) + + data, err := vfs.ReadFile(testdataFs, filepath.Join(opts.ComponentArchivePath, ctf.ComponentDescriptorFileName)) + Expect(err).ToNot(HaveOccurred()) + + cd := &cdv2.ComponentDescriptor{} + Expect(codec.Decode(data, cd)).To(Succeed()) + + Expect(cd.Resources).To(HaveLen(1)) + Expect(cd.Resources[0].IdentityObjectMeta).To(MatchFields(IgnoreExtras, Fields{ + "Name": Equal("ubuntu"), + "Version": Equal("v0.0.1"), + "Type": Equal("ociImage"), + "ExtraIdentity": HaveLen(0), + })) + Expect(cd.Resources[0]).To(MatchFields(IgnoreExtras, Fields{ + "Relation": Equal(cdv2.ResourceRelation("external")), + })) + Expect(cd.Resources[0].Access.Object).To(HaveKeyWithValue("type", "ociRegistry")) + Expect(cd.Resources[0].Access.Object).To(HaveKeyWithValue("imageReference", "ubuntu:18.0")) + + Expect(cd.Name).To(Equal("example.com/component")) + Expect(cd.Version).To(Equal("v1")) + }) + +}) diff --git a/pkg/commands/componentarchive/set/testdata/00-component/component-descriptor.yaml b/pkg/commands/componentarchive/set/testdata/00-component/component-descriptor.yaml new file mode 100644 index 00000000..010db28f --- /dev/null +++ b/pkg/commands/componentarchive/set/testdata/00-component/component-descriptor.yaml @@ -0,0 +1,19 @@ +component: + componentReferences: [] + name: example.com/component + provider: internal + repositoryContexts: + - baseUrl: eu.gcr.io/gardener-project/components/dev + type: ociRegistry + resources: + - name: 'ubuntu' + version: 'v0.0.1' + type: 'ociImage' + relation: 'external' + access: + type: 'ociRegistry' + imageReference: 'ubuntu:18.0' + sources: [] + version: v0.0.0 +meta: + schemaVersion: v2 diff --git a/pkg/componentarchive/archive.go b/pkg/componentarchive/archive.go index 3f130b25..4b6a4f76 100644 --- a/pkg/componentarchive/archive.go +++ b/pkg/componentarchive/archive.go @@ -34,6 +34,7 @@ type BuilderOptions struct { ComponentNameMapping string Overwrite bool + Modify bool } func (o *BuilderOptions) AddFlags(fs *pflag.FlagSet) { @@ -58,11 +59,6 @@ func (o *BuilderOptions) Validate() error { return errors.New("a component archive path must be defined") } - if len(o.Name) != 0 { - if len(o.Version) == 0 { - return errors.New("a version has to be provided for a minimal component descriptor") - } - } if len(o.ComponentNameMapping) != 0 { if o.ComponentNameMapping != string(cdv2.OCIRegistryURLPathMapping) && o.ComponentNameMapping != string(cdv2.OCIRegistryDigestMapping) { @@ -100,14 +96,14 @@ func (o *BuilderOptions) Build(fs vfs.FileSystem) (*ctf.ComponentArchive, error) cd := archive.ComponentDescriptor if o.Name != "" { - if cd.Name != "" && cd.Name != o.Name { + if !o.Modify && cd.Name != "" && cd.Name != o.Name { return nil, errors.New("unable to overwrite the existing component name: forbidden") } cd.Name = o.Name } if o.Version != "" { - if cd.Version != "" && cd.Version != o.Version { + if !o.Modify && cd.Version != "" && cd.Version != o.Version { return nil, errors.New("unable to overwrite the existing component version: forbidden") } cd.Version = o.Version