From 4ff44b426650e2c48019125227b657e8e20696ba Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Fri, 17 Dec 2021 16:05:57 +0100 Subject: [PATCH 1/2] ocm command and some ctf cmd extension --- cmd/component-cli/main.go | 2 +- cmd/{component-cli => ocm}/app/app.go | 0 cmd/ocm/main.go | 24 ++++++ docs/reference/component-cli_ctf_add.md | 2 +- docs/reference/component-cli_ctf_push.md | 2 +- hack/generate-docs/main.go | 4 +- pkg/commands/ctf/add.go | 5 +- pkg/commands/ctf/push.go | 103 ++++++++++++----------- pkg/commands/ctf/push_test.go | 8 +- 9 files changed, 90 insertions(+), 60 deletions(-) rename cmd/{component-cli => ocm}/app/app.go (100%) create mode 100644 cmd/ocm/main.go diff --git a/cmd/component-cli/main.go b/cmd/component-cli/main.go index 75d0e076..d4a4569e 100644 --- a/cmd/component-cli/main.go +++ b/cmd/component-cli/main.go @@ -9,7 +9,7 @@ import ( "fmt" "os" - "github.com/gardener/component-cli/cmd/component-cli/app" + "github.com/gardener/component-cli/cmd/ocm/app" ) func main() { diff --git a/cmd/component-cli/app/app.go b/cmd/ocm/app/app.go similarity index 100% rename from cmd/component-cli/app/app.go rename to cmd/ocm/app/app.go diff --git a/cmd/ocm/main.go b/cmd/ocm/main.go new file mode 100644 index 00000000..d4a4569e --- /dev/null +++ b/cmd/ocm/main.go @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2020 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "os" + + "github.com/gardener/component-cli/cmd/ocm/app" +) + +func main() { + ctx := context.Background() + defer ctx.Done() + cmd := app.NewComponentsCliCommand(ctx) + + if err := cmd.Execute(); err != nil { + fmt.Print(err) + os.Exit(1) + } +} diff --git a/docs/reference/component-cli_ctf_add.md b/docs/reference/component-cli_ctf_add.md index adca37f3..d3801cd7 100644 --- a/docs/reference/component-cli_ctf_add.md +++ b/docs/reference/component-cli_ctf_add.md @@ -3,7 +3,7 @@ Adds component archives to a ctf ``` -component-cli ctf add CTF_PATH [-f component-archive]... [flags] +component-cli ctf add ... [flags] ``` ### Options diff --git a/docs/reference/component-cli_ctf_push.md b/docs/reference/component-cli_ctf_push.md index 6d24bcbf..3f1260b1 100644 --- a/docs/reference/component-cli_ctf_push.md +++ b/docs/reference/component-cli_ctf_push.md @@ -13,7 +13,7 @@ Note: Currently only component archives are supoprted. Generic OCI Artifacts wil ``` -component-cli ctf push CTF_PATH [flags] +component-cli ctf push ... [flags] ``` ### Options diff --git a/hack/generate-docs/main.go b/hack/generate-docs/main.go index a8fa3462..821f6bf6 100644 --- a/hack/generate-docs/main.go +++ b/hack/generate-docs/main.go @@ -11,14 +11,14 @@ import ( "github.com/spf13/cobra/doc" - "github.com/gardener/component-cli/cmd/component-cli/app" + "github.com/gardener/component-cli/cmd/ocm/app" ) func main() { fmt.Println("> Generate Docs for ComponentCli") if len(os.Args) != 2 { // expect 2 as the first one is the programm name - fmt.Printf("Expected exactly one argument, but got %d", len(os.Args) - 1) + fmt.Printf("Expected exactly one argument, but got %d", len(os.Args)-1) os.Exit(1) } outputDir := os.Args[1] diff --git a/pkg/commands/ctf/add.go b/pkg/commands/ctf/add.go index cea64e68..9801dfad 100644 --- a/pkg/commands/ctf/add.go +++ b/pkg/commands/ctf/add.go @@ -36,8 +36,8 @@ type AddOptions struct { func NewAddCommand(ctx context.Context) *cobra.Command { opts := &AddOptions{} cmd := &cobra.Command{ - Use: "add CTF_PATH [-f component-archive]...", - Args: cobra.RangeArgs(1, 4), + Use: "add ...", + Args: cobra.MinimumNArgs(1), Short: "Adds component archives to a ctf", Run: func(cmd *cobra.Command, args []string) { if err := opts.Complete(args); err != nil { @@ -117,6 +117,7 @@ It is expected that the given path points to a CTF Archive`, o.CTFPath) func (o *AddOptions) Complete(args []string) error { o.CTFPath = args[0] + o.ComponentArchives = append(o.ComponentArchives, args[1:]...) if err := o.Validate(); err != nil { return err } diff --git a/pkg/commands/ctf/push.go b/pkg/commands/ctf/push.go index 86cb114b..b4d223e2 100644 --- a/pkg/commands/ctf/push.go +++ b/pkg/commands/ctf/push.go @@ -27,8 +27,8 @@ import ( ) type PushOptions struct { - // CTFPath is the path to the directory containing the ctf archive. - CTFPath string + // CTFPaths is the path to the directory containing the ctf archive. + CTFPaths []string // BaseUrl is the repository context base url for all included component descriptors. BaseUrl string // AdditionalTags defines additional tags that the oci artifact should be tagged with. @@ -42,8 +42,8 @@ type PushOptions struct { func NewPushCommand(ctx context.Context) *cobra.Command { opts := &PushOptions{} cmd := &cobra.Command{ - Use: "push CTF_PATH", - Args: cobra.ExactArgs(1), + Use: "push ...", + Args: cobra.MinimumNArgs(1), Short: "Pushes all archives of a ctf to a remote repository", Long: ` Push pushes all component archives and oci artifacts to the defined oci repository. @@ -73,69 +73,74 @@ Note: Currently only component archives are supoprted. Generic OCI Artifacts wil } func (o *PushOptions) Run(ctx context.Context, log logr.Logger, fs vfs.FileSystem) error { - info, err := fs.Stat(o.CTFPath) - if err != nil { - return fmt.Errorf("unable to get info for %s: %w", o.CTFPath, err) - } - if info.IsDir() { - return fmt.Errorf(`%q is a directory. -It is expected that the given path points to a CTF Archive`, o.CTFPath) - } - - ociClient, cache, err := o.OciOptions.Build(log, fs) - if err != nil { - return fmt.Errorf("unable to build oci client: %s", err.Error()) - } - - ctfArchive, err := ctf.NewCTF(fs, o.CTFPath) - if err != nil { - return fmt.Errorf("unable to open ctf at %q: %s", o.CTFPath, err.Error()) - } - - err = ctfArchive.Walk(func(ca *ctf.ComponentArchive) error { - // update repository context - if len(o.BaseUrl) != 0 { - if err := cdv2.InjectRepositoryContext(ca.ComponentDescriptor, cdv2.NewOCIRegistryRepository(o.BaseUrl, "")); err != nil { - return fmt.Errorf("unable to add repository context: %w", err) - } + for _, path := range o.CTFPaths { + info, err := fs.Stat(path) + if err != nil { + return fmt.Errorf("unable to get info for %s: %w", path, err) + } + if info.IsDir() { + return fmt.Errorf(`%q is a directory. +It is expected that the given path points to a CTF Archive`, path) } - manifest, err := cdoci.NewManifestBuilder(cache, ca).Build(ctx) + ociClient, cache, err := o.OciOptions.Build(log, fs) if err != nil { - return fmt.Errorf("unable to build oci artifact for component acrchive: %w", err) + return fmt.Errorf("unable to build oci client: %s", err.Error()) } - ref, err := components.OCIRef(ca.ComponentDescriptor.GetEffectiveRepositoryContext(), ca.ComponentDescriptor.GetName(), ca.ComponentDescriptor.GetVersion()) + ctfArchive, err := ctf.NewCTF(fs, path) if err != nil { - return fmt.Errorf("unable to calculate oci ref for %q: %s", ca.ComponentDescriptor.GetName(), err.Error()) - } - if err := ociClient.PushManifest(ctx, ref, manifest); err != nil { - return fmt.Errorf("unable to upload component archive to %q: %s", ref, err.Error()) + return fmt.Errorf("unable to open ctf at %q: %s", o.CTFPaths, err.Error()) } - log.Info(fmt.Sprintf("Successfully uploaded component archive to %q", ref)) - for _, tag := range o.AdditionalTags { - ref, err := components.OCIRef(ca.ComponentDescriptor.GetEffectiveRepositoryContext(), ca.ComponentDescriptor.GetName(), tag) + err = ctfArchive.Walk(func(ca *ctf.ComponentArchive) error { + // update repository context + if len(o.BaseUrl) != 0 { + if err := cdv2.InjectRepositoryContext(ca.ComponentDescriptor, cdv2.NewOCIRegistryRepository(o.BaseUrl, "")); err != nil { + return fmt.Errorf("unable to add repository context: %w", err) + } + } + + manifest, err := cdoci.NewManifestBuilder(cache, ca).Build(ctx) + if err != nil { + return fmt.Errorf("unable to build oci artifact for component acrchive: %w", err) + } + + ref, err := components.OCIRef(ca.ComponentDescriptor.GetEffectiveRepositoryContext(), ca.ComponentDescriptor.GetName(), ca.ComponentDescriptor.GetVersion()) if err != nil { return fmt.Errorf("unable to calculate oci ref for %q: %s", ca.ComponentDescriptor.GetName(), err.Error()) } if err := ociClient.PushManifest(ctx, ref, manifest); err != nil { return fmt.Errorf("unable to upload component archive to %q: %s", ref, err.Error()) } - log.Info(fmt.Sprintf("Successfully tagged component archive with %q", ref)) - } + log.Info(fmt.Sprintf("Successfully uploaded component archive to %q", ref)) + + for _, tag := range o.AdditionalTags { + ref, err := components.OCIRef(ca.ComponentDescriptor.GetEffectiveRepositoryContext(), ca.ComponentDescriptor.GetName(), tag) + if err != nil { + return fmt.Errorf("unable to calculate oci ref for %q: %s", ca.ComponentDescriptor.GetName(), err.Error()) + } + if err := ociClient.PushManifest(ctx, ref, manifest); err != nil { + return fmt.Errorf("unable to upload component archive to %q: %s", ref, err.Error()) + } + log.Info(fmt.Sprintf("Successfully tagged component archive with %q", ref)) + } - return nil - }) - if err != nil { - return fmt.Errorf("error while reading component archives in ctf: %w", err) + return nil + }) + if err != nil { + return fmt.Errorf("error while reading component archives in ctf: %w", err) + } + err = ctfArchive.Close() + if err != nil { + return fmt.Errorf("error while closing component archives in ctf: %w", err) + } } - - return ctfArchive.Close() + return nil } func (o *PushOptions) Complete(args []string) error { - o.CTFPath = args[0] + o.CTFPaths = args var err error o.OciOptions.CacheDir, err = utils.CacheDir() @@ -152,7 +157,7 @@ func (o *PushOptions) Complete(args []string) error { // Validate validates push options func (o *PushOptions) Validate() error { - if len(o.CTFPath) == 0 { + if len(o.CTFPaths) == 0 { return errors.New("a path to the component descriptor must be defined") } return nil diff --git a/pkg/commands/ctf/push_test.go b/pkg/commands/ctf/push_test.go index c2261ccd..8b21bf2a 100644 --- a/pkg/commands/ctf/push_test.go +++ b/pkg/commands/ctf/push_test.go @@ -58,8 +58,8 @@ var _ = Describe("Add", func() { Expect(vfs.WriteFile(testdataFs, "/auth.json", cf, os.ModePerm)) opts := cmd.PushOptions{ - CTFPath: "/component.ctf", - BaseUrl: testenv.Addr + "/test", + CTFPaths: []string{"/component.ctf"}, + BaseUrl: testenv.Addr + "/test", OciOptions: options.Options{ AllowPlainHttp: false, RegistryConfigPath: "/auth.json", @@ -117,8 +117,8 @@ var _ = Describe("Add", func() { Expect(vfs.WriteFile(testdataFs, "/auth.json", cf, os.ModePerm)) opts := cmd.PushOptions{ - CTFPath: "/component.ctf", - BaseUrl: testenv.Addr + "/test", + CTFPaths: []string{"/component.ctf"}, + BaseUrl: testenv.Addr + "/test", OciOptions: options.Options{ AllowPlainHttp: false, RegistryConfigPath: "/auth.json", From a57b19d4d6aa5d8ec438162b4b398e37f35a14e1 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Mon, 10 Jan 2022 12:56:53 +0100 Subject: [PATCH 2/2] add set command to modify comp desc --- .../componentarchive/componentarchive.go | 5 + pkg/commands/componentarchive/create.go | 6 + pkg/commands/componentarchive/get/get.go | 113 ++++++++++++++++++ pkg/commands/componentarchive/get/get_test.go | 60 ++++++++++ .../00-component/component-descriptor.yaml | 19 +++ pkg/commands/componentarchive/remote/push.go | 11 +- pkg/commands/componentarchive/set/set.go | 110 +++++++++++++++++ pkg/commands/componentarchive/set/set_test.go | 110 +++++++++++++++++ .../00-component/component-descriptor.yaml | 19 +++ pkg/commands/ctf/push.go | 9 +- pkg/componentarchive/archive.go | 10 +- 11 files changed, 462 insertions(+), 10 deletions(-) create mode 100644 pkg/commands/componentarchive/get/get.go create mode 100644 pkg/commands/componentarchive/get/get_test.go create mode 100644 pkg/commands/componentarchive/get/testdata/00-component/component-descriptor.yaml 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..eeb14bca 100644 --- a/pkg/commands/componentarchive/componentarchive.go +++ b/pkg/commands/componentarchive/componentarchive.go @@ -19,8 +19,10 @@ import ( pflag "github.com/spf13/pflag" "github.com/gardener/component-cli/pkg/commands/componentarchive/componentreferences" + "github.com/gardener/component-cli/pkg/commands/componentarchive/get" "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 +76,8 @@ 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)) + cmd.AddCommand(get.NewGetCommand(ctx)) return cmd } @@ -110,6 +114,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..92b16c40 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 o.Overwrite && 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/get/get.go b/pkg/commands/componentarchive/get/get.go new file mode 100644 index 00000000..cbc0cad4 --- /dev/null +++ b/pkg/commands/componentarchive/get/get.go @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2020 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package get + +import ( + "context" + "errors" + "fmt" + "os" + + cdvalidation "github.com/gardener/component-spec/bindings-go/apis/v2/validation" + "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" + + "github.com/gardener/component-cli/pkg/componentarchive" + "github.com/gardener/component-cli/pkg/logger" +) + +// Options defines the options that are used to add resources to a component descriptor +type Options struct { + componentarchive.BuilderOptions + Property string +} + +// NewGetCommand creates a command to add additional resources to a component descriptor. +func NewGetCommand(ctx context.Context) *cobra.Command { + opts := &Options{} + cmd := &cobra.Command{ + Use: "get COMPONENT_ARCHIVE_PATH [options...]", + Args: cobra.MinimumNArgs(1), + Short: "set some component descriptor properties", + Long: ` +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 { + result, err := o.Get(ctx, log, fs) + if err != nil { + return err + } + fmt.Println(result) + return nil +} + +func (o *Options) Get(ctx context.Context, log logr.Logger, fs vfs.FileSystem) (string, error) { + o.Modify = true + archive, err := o.BuilderOptions.Build(fs) + if err != nil { + return "", err + } + + if err := cdvalidation.Validate(archive.ComponentDescriptor); err != nil { + return "", fmt.Errorf("invalid component descriptor: %w", err) + } + + switch o.Property { + case "name": + return archive.ComponentDescriptor.Name, nil + case "version": + return archive.ComponentDescriptor.Version, nil + } + + return "", nil +} + +func (o *Options) Complete(args []string) error { + + 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 { + if len(o.Property) == 0 { + return errors.New("a property must be specified") + } + return o.BuilderOptions.Validate() +} + +func (o *Options) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.Property, "property", "", "name of the property (name or version)") + + o.BuilderOptions.AddFlags(fs) +} diff --git a/pkg/commands/componentarchive/get/get_test.go b/pkg/commands/componentarchive/get/get_test.go new file mode 100644 index 00000000..5a6d4e8c --- /dev/null +++ b/pkg/commands/componentarchive/get/get_test.go @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2020 SAP SE or an SAP affiliate company and Gardener contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package get_test + +import ( + "context" + "testing" + + "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/gardener/component-cli/pkg/commands/componentarchive/get" + "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 get name", func() { + opts := &get.Options{ + BuilderOptions: componentarchive.BuilderOptions{ + ComponentArchivePath: "./00-component", + }, + Property: "name", + } + + Expect(opts.Get(context.TODO(), logr.Discard(), testdataFs)).To(Equal("example.com/component")) + }) + + It("should get version", func() { + opts := &get.Options{ + BuilderOptions: componentarchive.BuilderOptions{ + ComponentArchivePath: "./00-component", + }, + Property: "version", + } + + Expect(opts.Get(context.TODO(), logr.Discard(), testdataFs)).To(Equal("v0.0.0")) + }) +}) diff --git a/pkg/commands/componentarchive/get/testdata/00-component/component-descriptor.yaml b/pkg/commands/componentarchive/get/testdata/00-component/component-descriptor.yaml new file mode 100644 index 00000000..010db28f --- /dev/null +++ b/pkg/commands/componentarchive/get/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/commands/componentarchive/remote/push.go b/pkg/commands/componentarchive/remote/push.go index b6286c83..3de39dda 100644 --- a/pkg/commands/componentarchive/remote/push.go +++ b/pkg/commands/componentarchive/remote/push.go @@ -85,6 +85,7 @@ func (o *PushOptions) Run(ctx context.Context, log logr.Logger, fs vfs.FileSyste } // update repository context if len(o.BaseUrl) != 0 { + log.Info(fmt.Sprintf("Update repository context in component descriptor %q", o.BaseUrl)) if err := cdv2.InjectRepositoryContext(archive.ComponentDescriptor, cdv2.NewOCIRegistryRepository(o.BaseUrl, "")); err != nil { return fmt.Errorf("unable to add repository context to component descriptor: %w", err) } @@ -95,7 +96,12 @@ func (o *PushOptions) Run(ctx context.Context, log logr.Logger, fs vfs.FileSyste return fmt.Errorf("unable to build oci artifact for component acrchive: %w", err) } - ref, err := components.OCIRef(archive.ComponentDescriptor.GetEffectiveRepositoryContext(), archive.ComponentDescriptor.Name, archive.ComponentDescriptor.Version) + rctx := archive.ComponentDescriptor.GetEffectiveRepositoryContext() + if rctx == nil { + return fmt.Errorf("no repository context given") + } + // attention nil struct pointer passed to interface parameter (non-nil value) + ref, err := components.OCIRef(rctx, archive.ComponentDescriptor.Name, archive.ComponentDescriptor.Version) if err != nil { return fmt.Errorf("invalid component reference: %w", err) } @@ -105,7 +111,8 @@ func (o *PushOptions) Run(ctx context.Context, log logr.Logger, fs vfs.FileSyste log.Info(fmt.Sprintf("Successfully uploaded component descriptor at %q", ref)) for _, tag := range o.AdditionalTags { - ref, err := components.OCIRef(archive.ComponentDescriptor.GetEffectiveRepositoryContext(), archive.ComponentDescriptor.Name, tag) + log.Info(fmt.Sprintf("Push for additional tag %q", tag)) + ref, err := components.OCIRef(rctx, archive.ComponentDescriptor.Name, tag) if err != nil { return fmt.Errorf("invalid component reference: %w", err) } diff --git a/pkg/commands/componentarchive/set/set.go b/pkg/commands/componentarchive/set/set.go new file mode 100644 index 00000000..8f8f4d41 --- /dev/null +++ b/pkg/commands/componentarchive/set/set.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 + +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" +) + +// Options defines the options that are used to add resources to a component descriptor +type Options struct { + componentarchive.BuilderOptions +} + +// NewSetCommand 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(1), + Short: "set some component descriptor properties", + Long: ` +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("Successfully changed component descriptor") + return nil +} + +func (o *Options) Complete(args []string) error { + 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/commands/ctf/push.go b/pkg/commands/ctf/push.go index b4d223e2..4b7f350d 100644 --- a/pkg/commands/ctf/push.go +++ b/pkg/commands/ctf/push.go @@ -96,6 +96,7 @@ It is expected that the given path points to a CTF Archive`, path) err = ctfArchive.Walk(func(ca *ctf.ComponentArchive) error { // update repository context if len(o.BaseUrl) != 0 { + log.Info(fmt.Sprintf("Update repository context in component descriptor %q", o.BaseUrl)) if err := cdv2.InjectRepositoryContext(ca.ComponentDescriptor, cdv2.NewOCIRegistryRepository(o.BaseUrl, "")); err != nil { return fmt.Errorf("unable to add repository context: %w", err) } @@ -106,10 +107,16 @@ It is expected that the given path points to a CTF Archive`, path) return fmt.Errorf("unable to build oci artifact for component acrchive: %w", err) } - ref, err := components.OCIRef(ca.ComponentDescriptor.GetEffectiveRepositoryContext(), ca.ComponentDescriptor.GetName(), ca.ComponentDescriptor.GetVersion()) + rctx := ca.ComponentDescriptor.GetEffectiveRepositoryContext() + if rctx == nil { + return fmt.Errorf("no repository context given") + } + + ref, err := components.OCIRef(rctx, ca.ComponentDescriptor.GetName(), ca.ComponentDescriptor.GetVersion()) if err != nil { return fmt.Errorf("unable to calculate oci ref for %q: %s", ca.ComponentDescriptor.GetName(), err.Error()) } + if err := ociClient.PushManifest(ctx, ref, manifest); err != nil { return fmt.Errorf("unable to upload component archive to %q: %s", ref, err.Error()) } 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