From 1c25f4fa2edc1613181e78633958670f860b49a6 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Fri, 14 Apr 2023 16:31:10 +0200 Subject: [PATCH 1/3] extends toi spiff functions to access cred properties --- cmds/ocm/topics/toi/bootstrapping/topic.go | 17 ++- ...otstrap_configuration_toi-bootstrapping.md | 17 ++- ...ocm_bootstrap_package_toi-bootstrapping.md | 17 ++- .../ocm_describe_package_toi-bootstrapping.md | 17 ++- docs/reference/ocm_toi-bootstrapping.md | 17 ++- go.mod | 2 +- go.sum | 2 + pkg/toi/install/action_test.go | 117 +++++++++++++++++- pkg/toi/install/functions.go | 51 ++++++-- 9 files changed, 226 insertions(+), 31 deletions(-) diff --git a/cmds/ocm/topics/toi/bootstrapping/topic.go b/cmds/ocm/topics/toi/bootstrapping/topic.go index b6b9a9e10b..c2d92c1fb3 100644 --- a/cmds/ocm/topics/toi/bootstrapping/topic.go +++ b/cmds/ocm/topics/toi/bootstrapping/topic.go @@ -117,6 +117,7 @@ contained in the component version, which contains the package resource. It is a plain yaml resource with the media types media type ` + mime.MIME_YAML + `, ` + mime.MIME_YAML_ALT + ` or + ` + toi.PackageSpecificationMimeType + `) containing information required to control the instantiation of an executor. @@ -253,7 +254,7 @@ to provide specifc values expected by the executor. This is done by a _spiff_ template. Here special functions are provided to access specific content: -- hasCredentials(string) bool +- hasCredentials(string[,string]) bool This function can be used to check whether dedicated credentials are effectively provided for the actual installation. @@ -262,9 +263,18 @@ are provided to access specific content: request section optionally mapped to the name used for the executor (field credentialMapping). -- getCredentials(string) map[string]string + If the second argument is given, it checks for the named property + in the credential set. + +- getCredentials(string[,string]) map[string]string | string + + This functions provides the property set of the provided credentials. - This functions provides the property set of the provided credentials. + If the second argument is given, it returns the named property in the + selected credential set. + + If the property name is an asterisks (*) a single property + is expected, whose value is returned. #### User Config vs Executor Config @@ -301,7 +311,6 @@ definitions. ### The ` + toi.TypeTOIExecutor + ` Resource - Instead of directly describing an image resource in the package file, it is possible to refer to a resource of type ` + toi.TypeTOIExecutor + `. This is a yaml file with the media type ` + mime.MIME_YAML + `, diff --git a/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md b/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md index c54e0de5db..d2bc583a41 100644 --- a/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md +++ b/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md @@ -56,6 +56,7 @@ contained in the component version, which contains the package resource. It is a plain yaml resource with the media types media type application/x-yaml, text/yaml or + application/vnd.toi.ocm.software.package.v1+yaml) containing information required to control the instantiation of an executor. @@ -192,7 +193,7 @@ to provide specifc values expected by the executor. This is done by a _spiff_ template. Here special functions are provided to access specific content: -- hasCredentials(string) bool +- hasCredentials(string[,string]) bool This function can be used to check whether dedicated credentials are effectively provided for the actual installation. @@ -201,9 +202,18 @@ are provided to access specific content: request section optionally mapped to the name used for the executor (field credentialMapping). -- getCredentials(string) map[string]string + If the second argument is given, it checks for the named property + in the credential set. + +- getCredentials(string[,string]) map[string]string | string + + This functions provides the property set of the provided credentials. - This functions provides the property set of the provided credentials. + If the second argument is given, it returns the named property in the + selected credential set. + + If the property name is an asterisks (*) a single property + is expected, whose value is returned. #### User Config vs Executor Config @@ -240,7 +250,6 @@ definitions. ### The toiExecutor Resource - Instead of directly describing an image resource in the package file, it is possible to refer to a resource of type toiExecutor. This is a yaml file with the media type application/x-yaml, diff --git a/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md b/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md index 9bfc96a236..79303e6ad2 100644 --- a/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md +++ b/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md @@ -56,6 +56,7 @@ contained in the component version, which contains the package resource. It is a plain yaml resource with the media types media type application/x-yaml, text/yaml or + application/vnd.toi.ocm.software.package.v1+yaml) containing information required to control the instantiation of an executor. @@ -192,7 +193,7 @@ to provide specifc values expected by the executor. This is done by a _spiff_ template. Here special functions are provided to access specific content: -- hasCredentials(string) bool +- hasCredentials(string[,string]) bool This function can be used to check whether dedicated credentials are effectively provided for the actual installation. @@ -201,9 +202,18 @@ are provided to access specific content: request section optionally mapped to the name used for the executor (field credentialMapping). -- getCredentials(string) map[string]string + If the second argument is given, it checks for the named property + in the credential set. + +- getCredentials(string[,string]) map[string]string | string + + This functions provides the property set of the provided credentials. - This functions provides the property set of the provided credentials. + If the second argument is given, it returns the named property in the + selected credential set. + + If the property name is an asterisks (*) a single property + is expected, whose value is returned. #### User Config vs Executor Config @@ -240,7 +250,6 @@ definitions. ### The toiExecutor Resource - Instead of directly describing an image resource in the package file, it is possible to refer to a resource of type toiExecutor. This is a yaml file with the media type application/x-yaml, diff --git a/docs/reference/ocm_describe_package_toi-bootstrapping.md b/docs/reference/ocm_describe_package_toi-bootstrapping.md index e6e7af098b..17640f0d4e 100644 --- a/docs/reference/ocm_describe_package_toi-bootstrapping.md +++ b/docs/reference/ocm_describe_package_toi-bootstrapping.md @@ -56,6 +56,7 @@ contained in the component version, which contains the package resource. It is a plain yaml resource with the media types media type application/x-yaml, text/yaml or + application/vnd.toi.ocm.software.package.v1+yaml) containing information required to control the instantiation of an executor. @@ -192,7 +193,7 @@ to provide specifc values expected by the executor. This is done by a _spiff_ template. Here special functions are provided to access specific content: -- hasCredentials(string) bool +- hasCredentials(string[,string]) bool This function can be used to check whether dedicated credentials are effectively provided for the actual installation. @@ -201,9 +202,18 @@ are provided to access specific content: request section optionally mapped to the name used for the executor (field credentialMapping). -- getCredentials(string) map[string]string + If the second argument is given, it checks for the named property + in the credential set. + +- getCredentials(string[,string]) map[string]string | string + + This functions provides the property set of the provided credentials. - This functions provides the property set of the provided credentials. + If the second argument is given, it returns the named property in the + selected credential set. + + If the property name is an asterisks (*) a single property + is expected, whose value is returned. #### User Config vs Executor Config @@ -240,7 +250,6 @@ definitions. ### The toiExecutor Resource - Instead of directly describing an image resource in the package file, it is possible to refer to a resource of type toiExecutor. This is a yaml file with the media type application/x-yaml, diff --git a/docs/reference/ocm_toi-bootstrapping.md b/docs/reference/ocm_toi-bootstrapping.md index 1bd0e85ba8..4326adaaac 100644 --- a/docs/reference/ocm_toi-bootstrapping.md +++ b/docs/reference/ocm_toi-bootstrapping.md @@ -56,6 +56,7 @@ contained in the component version, which contains the package resource. It is a plain yaml resource with the media types media type application/x-yaml, text/yaml or + application/vnd.toi.ocm.software.package.v1+yaml) containing information required to control the instantiation of an executor. @@ -192,7 +193,7 @@ to provide specifc values expected by the executor. This is done by a _spiff_ template. Here special functions are provided to access specific content: -- hasCredentials(string) bool +- hasCredentials(string[,string]) bool This function can be used to check whether dedicated credentials are effectively provided for the actual installation. @@ -201,9 +202,18 @@ are provided to access specific content: request section optionally mapped to the name used for the executor (field credentialMapping). -- getCredentials(string) map[string]string + If the second argument is given, it checks for the named property + in the credential set. + +- getCredentials(string[,string]) map[string]string | string + + This functions provides the property set of the provided credentials. - This functions provides the property set of the provided credentials. + If the second argument is given, it returns the named property in the + selected credential set. + + If the property name is an asterisks (*) a single property + is expected, whose value is returned. #### User Config vs Executor Config @@ -240,7 +250,6 @@ definitions. ### The toiExecutor Resource - Instead of directly describing an image resource in the package file, it is possible to refer to a resource of type toiExecutor. This is a yaml file with the media type application/x-yaml, diff --git a/go.mod b/go.mod index 808ed5c1e8..5c159623f4 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/ghodss/yaml v1.0.0 github.com/go-logr/logr v1.2.3 github.com/mandelsoft/filepath v0.0.0-20230412200429-36b1eb66bd27 - github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230412151612-088be0cbcd26 + github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230414131619-0bc30bac11fc github.com/modern-go/reflect2 v1.0.2 github.com/onsi/gomega v1.26.0 github.com/opencontainers/go-digest v1.0.0 diff --git a/go.sum b/go.sum index 057328a22f..c37a6fc674 100644 --- a/go.sum +++ b/go.sum @@ -834,6 +834,8 @@ github.com/mandelsoft/logging v0.0.0-20230331123830-36542ef18f6f h1:ZoqAURpfhE54 github.com/mandelsoft/logging v0.0.0-20230331123830-36542ef18f6f/go.mod h1:HjtBPH5tsJygvyYEu2r/ZwOOBGcu+oR2sKH73VEnHtg= github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230412151612-088be0cbcd26 h1:IuVluKKXjKASZe6DvmhlJJNMO5opIFEuS1y3iuPVDGs= github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230412151612-088be0cbcd26/go.mod h1:TwEeOPuRZxlzQBCLEyVTlHmBSruSGGNdiQ2fovVJ8ao= +github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230414131619-0bc30bac11fc h1:ZAPrgV5lQ6LziSVrf8HPTH9TwjNh7l6haECmDrjES+o= +github.com/mandelsoft/spiff v1.3.0-beta-7.0.20230414131619-0bc30bac11fc/go.mod h1:TwEeOPuRZxlzQBCLEyVTlHmBSruSGGNdiQ2fovVJ8ao= github.com/mandelsoft/vfs v0.0.0-20220805210647-bf14a11bfe31 h1:5gmUtnP0NYOODvS/gTeQOJKSu4W8bOUImDiKdAb/j1A= github.com/mandelsoft/vfs v0.0.0-20220805210647-bf14a11bfe31/go.mod h1:74aV7kulg9C434HiI3zNALN79QHc9IZMN+SI4UdLn14= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= diff --git a/pkg/toi/install/action_test.go b/pkg/toi/install/action_test.go index f32288ab40..baf2cc5069 100644 --- a/pkg/toi/install/action_test.go +++ b/pkg/toi/install/action_test.go @@ -73,7 +73,7 @@ var _ = Describe("Transfer handler", func() { Expect(c.Properties()).To(Equal(creds1.Properties())) }) - It("executes with crednetial substitution", func() { + It("executes with credential substitution", func() { env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) p, _ := common.NewBufferedPrinter() @@ -130,6 +130,121 @@ creds: testparam: value `)) }) + + It("executes with credential property substitution", func() { + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + p, _ := common.NewBufferedPrinter() + + mapping := ` +testparam: (( merge )) +creds: (( hasCredentials("mycred") ? getCredentials("mycred", "user") :"" )) +` + spec := &toi.PackageSpecification{ + CredentialsRequest: toi.CredentialsRequest{ + Credentials: map[string]toi.CredentialsRequestSpec{ + "mycred": toi.CredentialsRequestSpec{ + ConsumerId: cid1, + Description: "test", + Optional: false, + }, + }, + }, + Executors: []toi.Executor{ + toi.Executor{ + Actions: []string{"install"}, + Image: &toi.Image{ + Ref: "a/b:v1", + }, + ParameterMapping: []byte(mapping), + }, + }, + } + + credspec := &toi.Credentials{ + Credentials: map[string]toi.CredentialSpec{ + "mycred": toi.CredentialSpec{ + ConsumerId: cid1, + }, + }, + } + + params := ` +testparam: value +` + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + + Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) + + effparams := Must(driver.Found.Files[install.InputParameters].Get()) + Expect(string(effparams)).To(StringEqualTrimmedWithContext(` +creds: test +testparam: value +`)) + }) + + It("executes with single credential property substitution", func() { + creds1 := credentials.NewCredentials(common.Properties{"user": "test"}) + + env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) + + p, _ := common.NewBufferedPrinter() + + mapping := ` +testparam: (( merge )) +creds: (( hasCredentials("mycred") ? getCredentials("mycred", "*") :"" )) +` + spec := &toi.PackageSpecification{ + CredentialsRequest: toi.CredentialsRequest{ + Credentials: map[string]toi.CredentialsRequestSpec{ + "mycred": toi.CredentialsRequestSpec{ + ConsumerId: cid1, + Description: "test", + Optional: false, + }, + }, + }, + Executors: []toi.Executor{ + toi.Executor{ + Actions: []string{"install"}, + Image: &toi.Image{ + Ref: "a/b:v1", + }, + ParameterMapping: []byte(mapping), + }, + }, + } + + credspec := &toi.Credentials{ + Credentials: map[string]toi.CredentialSpec{ + "mycred": toi.CredentialSpec{ + ConsumerId: cid1, + }, + }, + } + + params := ` +testparam: value +` + + repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, "/ctf", 0, env)) + defer Close(repo) + cv := Must(repo.LookupComponentVersion(COMPONENT, VERSION)) + defer Close(cv) + + Must(install.ExecuteAction(p, driver, "install", spec, credspec, []byte(params), env, cv, nil)) + + effparams := Must(driver.Found.Files[install.InputParameters].Get()) + Expect(string(effparams)).To(StringEqualTrimmedWithContext(` +creds: test +testparam: value +`)) + }) + It("executes with optional credential substitution without credentials", func() { env.CredentialsContext().SetCredentialsForConsumer(cid1, creds1) diff --git a/pkg/toi/install/functions.go b/pkg/toi/install/functions.go index 48ce7643fb..e67552187e 100644 --- a/pkg/toi/install/functions.go +++ b/pkg/toi/install/functions.go @@ -23,8 +23,8 @@ func NewFunctions(ctx ocm.Context, credvals CredentialValues) spiffing.Functions func spiffGetCredentials(credvals CredentialValues) spiffing.Function { return func(arguments []interface{}, binding dynaml.Binding) (interface{}, dynaml.EvaluationInfo, bool) { var info dynaml.EvaluationInfo - if len(arguments) != 1 { - return info.Error("credential name argument required") + if len(arguments) < 1 || len(arguments) > 2 { + return info.Error("credential name and optional property name argument required") } name, ok := arguments[0].(string) if !ok { @@ -34,25 +34,58 @@ func spiffGetCredentials(credvals CredentialValues) spiffing.Function { if creds == nil { return info.Error("credential %q not found", name) } - val := map[string]spiffing.Node{} - for n, v := range creds { - val[n] = yaml.NewNode(v, "credential") + if len(arguments) == 1 { + val := map[string]spiffing.Node{} + for n, v := range creds { + val[n] = yaml.NewNode(v, "credential") + } + return val, info, true } - return val, info, true + key, ok := arguments[1].(string) + if !ok { + return info.Error("credential property argument must be string") + } + if key == "*" || key == "" { + if len(creds) > 1 { + return info.Error("there are multiple credential properties") + } + for _, v := range creds { + return v, info, true + } + return "", info, true + } + v, ok := creds[key] + if !ok { + return info.Error("there is no credential property %q", key) + } + return v, info, true } } func spiffHasCredentials(credvals CredentialValues) dynaml.Function { return func(arguments []interface{}, binding dynaml.Binding) (interface{}, dynaml.EvaluationInfo, bool) { var info dynaml.EvaluationInfo - if len(arguments) != 1 { - return info.Error("credential name argument required") + if len(arguments) < 1 || len(arguments) > 2 { + return info.Error("credential name and optional property name argument required") } name, ok := arguments[0].(string) if !ok { return info.Error("credential name argument must be string") } creds := credvals[name] - return creds != nil, info, true + if creds == nil || len(arguments) == 1 { + return creds != nil, info, true + } + + key, ok := arguments[1].(string) + if !ok { + return info.Error("credential property argument must be string") + } + + if key == "*" || key == "" { + return len(creds) == 1, info, true + } + _, ok = creds[key] + return ok, info, true } } From 8eaee71623e52aced7f149a98bafcce22bec75b7 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Fri, 14 Apr 2023 22:29:03 +0200 Subject: [PATCH 2/3] toi: support inline content for additional files --- .../commands/toicmds/config/bootstrap/cmd.go | 59 +++++++-- .../toicmds/config/bootstrap/cmd_test.go | 122 ++++++++++++++++++ .../toicmds/config/bootstrap/suite_test.go | 17 +++ cmds/ocm/topics/toi/bootstrapping/topic.go | 16 ++- ...otstrap_configuration_toi-bootstrapping.md | 16 ++- ...ocm_bootstrap_package_toi-bootstrapping.md | 16 ++- .../ocm_describe_package_toi-bootstrapping.md | 16 ++- docs/reference/ocm_toi-bootstrapping.md | 16 ++- .../ocm/compdesc/meta/v1/resourceref.go | 2 +- pkg/toi/install/action_test.go | 31 +++-- pkg/toi/install/credentials_test.go | 1 - pkg/toi/spec.go | 17 ++- 12 files changed, 282 insertions(+), 47 deletions(-) create mode 100644 cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go create mode 100644 cmds/ocm/commands/toicmds/config/bootstrap/suite_test.go diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go index b33e23694a..dd927a4eda 100644 --- a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go @@ -5,6 +5,7 @@ package bootstrap import ( + "encoding/json" "fmt" "strings" @@ -32,6 +33,7 @@ import ( utils2 "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/out" + "github.com/open-component-model/ocm/pkg/runtime" "github.com/open-component-model/ocm/pkg/toi" "github.com/open-component-model/ocm/pkg/toi/install" utils3 "github.com/open-component-model/ocm/pkg/utils" @@ -197,24 +199,55 @@ func (a *action) Out() error { out.Outf(a.cmd.Context, "%s: %d byte(s) written\n", schemeFile, len(spec.Scheme)) } } - if a.download("configuration template", a.cmd.ParameterFile, cv, spec.AdditionalResources[toi.AdditionalResourceConfigFile], resolver) != nil { - err = fmt.Errorf("download failed") - } - if e := a.download("credentials template", a.cmd.CredentialsFile, cv, spec.AdditionalResources[toi.AdditionalResourceCredentialsFile], resolver); e != nil { - out.Errf(a.cmd.Context, "%s", e.Error()) - err = fmt.Errorf("download failed") - } + list := errors.ErrList() + list.Add(a.handle("configuration template", a.cmd.ParameterFile, cv, spec.AdditionalResources[toi.AdditionalResourceConfigFile], resolver)) + list.Add(a.handle("credentials template", a.cmd.CredentialsFile, cv, spec.AdditionalResources[toi.AdditionalResourceCredentialsFile], resolver)) + return list.Result() +} - return err +func (a *action) handle(kind, path string, cv ocm.ComponentVersionAccess, spec *toi.AdditionalResource, resolver ocm.ComponentVersionResolver) error { + var err error + if spec != nil { + if spec.ResourceReference != nil && len(spec.ResourceReference.Resource) != 0 { + return a.download(kind, a.cmd.ParameterFile, cv, spec.ResourceReference, resolver) + } else { + var content interface{} + if len(spec.Content) > 0 { + if err = json.Unmarshal(spec.Content, &content); err != nil { + return errors.Wrapf(err, "cannot marshal %s content", kind) + } + l := 0 + out.Outf(a.cmd.Context, "writing %s...\n", kind) + switch c := content.(type) { + case string: + l = len(c) + err = vfs.WriteFile(a.cmd.FileSystem(), path, []byte(c), 0o600) + case []byte: + l = len(c) + err = vfs.WriteFile(a.cmd.FileSystem(), path, c, 0o600) + default: + data, err := runtime.DefaultYAMLEncoding.Marshal(spec.Content) + if err != nil { + data = spec.Content + } + l = len(spec.Content) + err = vfs.WriteFile(a.cmd.FileSystem(), path, data, 0o600) + } + if err != nil { + return errors.Wrapf(err, "cannot write %s to %s", kind, path) + } + out.Outf(a.cmd.Context, "%s: %d byte(s) written\n", path, l) + return nil + } + return nil + } + } + out.Outf(a.cmd.Context, "no %s configured\n", kind) + return nil } func (a *action) download(kind, path string, cv ocm.ComponentVersionAccess, spec *metav1.ResourceReference, resolver ocm.ComponentVersionResolver) error { - - if spec == nil { - out.Outf(a.cmd.Context, "no %s configured\n", kind) - return nil - } res, _, err := utils2.MatchResourceReference(cv, toi.TypeYAML, *spec, resolver) if err != nil { return errors.Wrapf(err, "%s resource", kind) diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go b/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go new file mode 100644 index 0000000000..969aa6463c --- /dev/null +++ b/cmds/ocm/commands/toicmds/config/bootstrap/cmd_test.go @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package bootstrap_test + +import ( + "bytes" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/open-component-model/ocm/cmds/ocm/testhelper" + . "github.com/open-component-model/ocm/pkg/testutils" + + "github.com/mandelsoft/vfs/pkg/vfs" + + "github.com/open-component-model/ocm/cmds/ocm/commands/toicmds/config/bootstrap" + "github.com/open-component-model/ocm/pkg/common/accessio" + v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "github.com/open-component-model/ocm/pkg/mime" + "github.com/open-component-model/ocm/pkg/toi" +) + +const ARCH = "/tmp/ctf" +const VERSION = "v1" +const COMP1 = "test.de/a" +const COMP2 = "test.de/b" +const PROVIDER = "mandelsoft" + +var _ = Describe("Test Environment", func() { + var env *TestEnv + + respkg := ` +description: with config example by resource +additionalResources: + ` + toi.AdditionalResourceConfigFile + `: + content: + param: value +` + cntpkg := ` +description: with example by content +additionalResources: + ` + toi.AdditionalResourceCredentialsFile + `: + content: | + credentials: none + ` + toi.AdditionalResourceConfigFile + `: + content: + param: value +` + + BeforeEach(func() { + env = NewTestEnv() + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.Component(COMP1, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("package", VERSION, toi.TypeTOIPackage, v1.LocalRelation, func() { + env.BlobStringData(toi.PackageSpecificationMimeType, respkg) + }) + env.Resource("example", VERSION, toi.TypeTOIPackage, v1.LocalRelation, func() { + env.BlobStringData(mime.MIME_YAML, "param: value") + }) + }) + }) + env.Component(COMP2, func() { + env.Version(VERSION, func() { + env.Provider(PROVIDER) + env.Resource("package", VERSION, toi.TypeTOIPackage, v1.LocalRelation, func() { + env.BlobStringData(toi.PackageSpecificationMimeType, cntpkg) + }) + }) + }) + }) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("config by resource", func() { + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("bootstrap", "config", ARCH+"//"+COMP1)).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext( + ` +Warning: repository is no OCI registry, consider importing it or use upload repository with option ' -X ociuploadrepo=... +found package resource "package" in test.de/a:v1 + +Package Description: + with config example by resource + +writing configuration template... +TOIParameters: 17 byte(s) written +no credentials template configured +`)) + data := Must(vfs.ReadFile(env.FileSystem(), bootstrap.DEFAULT_PARAMETER_FILE)) + Expect(string(data)).To(Equal(`param: value +`)) + }) + It("config by content", func() { + buf := bytes.NewBuffer(nil) + Expect(env.CatchOutput(buf).Execute("bootstrap", "config", ARCH+"//"+COMP2)).To(Succeed()) + Expect(buf.String()).To(StringEqualTrimmedWithContext( + ` +Warning: repository is no OCI registry, consider importing it or use upload repository with option ' -X ociuploadrepo=... +found package resource "package" in test.de/b:v1 + +Package Description: + with example by content + +writing configuration template... +TOIParameters: 17 byte(s) written +writing credentials template... +TOICredentials: 18 byte(s) written +`)) + data := Must(vfs.ReadFile(env.FileSystem(), bootstrap.DEFAULT_PARAMETER_FILE)) + Expect(string(data)).To(Equal(`param: value +`)) + data = Must(vfs.ReadFile(env.FileSystem(), bootstrap.DEFAULT_CREDENTIALS_FILE)) + Expect(string(data)).To(Equal(`credentials: none +`)) + }) +}) diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/suite_test.go b/cmds/ocm/commands/toicmds/config/bootstrap/suite_test.go new file mode 100644 index 0000000000..6ca42b93c2 --- /dev/null +++ b/cmds/ocm/commands/toicmds/config/bootstrap/suite_test.go @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package bootstrap_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "TOI bootstrap config") +} diff --git a/cmds/ocm/topics/toi/bootstrapping/topic.go b/cmds/ocm/topics/toi/bootstrapping/topic.go index c2d92c1fb3..6d7277b7b6 100644 --- a/cmds/ocm/topics/toi/bootstrapping/topic.go +++ b/cmds/ocm/topics/toi/bootstrapping/topic.go @@ -152,9 +152,10 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. -- **additionalResources** (optional) *map[string]ResourceReference* +- **additionalResources** (optional) *map[string]AdditionalResource)* - A set of additional resources specified by OCM resource references. + A set of additional resources specified by an OCM resource reference or + direct data as byte, string or yaml. The key describes the meaning of the resource. The following keys have a special meaning: @@ -237,6 +238,17 @@ that contains the resource reference. It uses the following fields: This is the identity of the resource in the selected component version. +#### *AdditionalResource* + +This field has either the fields of a *ResourceReference* to refer to the +content of an OCM resource or the field: + +- **content** *string|[]byte|YAML* + + Either a resource reference or the field content must be given. + The content field may contain a string or an inline YAML document. + For larger content the resource reference form should be preferred. + #### *Identity* An identity specification is a map[string]string. It describes diff --git a/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md b/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md index d2bc583a41..e7ddd7cbe3 100644 --- a/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md +++ b/docs/reference/ocm_bootstrap_configuration_toi-bootstrapping.md @@ -91,9 +91,10 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. -- **additionalResources** (optional) *map[string]ResourceReference* +- **additionalResources** (optional) *map[string]AdditionalResource)* - A set of additional resources specified by OCM resource references. + A set of additional resources specified by an OCM resource reference or + direct data as byte, string or yaml. The key describes the meaning of the resource. The following keys have a special meaning: @@ -176,6 +177,17 @@ that contains the resource reference. It uses the following fields: This is the identity of the resource in the selected component version. +#### *AdditionalResource* + +This field has either the fields of a *ResourceReference* to refer to the +content of an OCM resource or the field: + +- **content** *string|[]byte|YAML* + + Either a resource reference or the field content must be given. + The content field may contain a string or an inline YAML document. + For larger content the resource reference form should be preferred. + #### *Identity* An identity specification is a map[string]string. It describes diff --git a/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md b/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md index 79303e6ad2..5414c7ac82 100644 --- a/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md +++ b/docs/reference/ocm_bootstrap_package_toi-bootstrapping.md @@ -91,9 +91,10 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. -- **additionalResources** (optional) *map[string]ResourceReference* +- **additionalResources** (optional) *map[string]AdditionalResource)* - A set of additional resources specified by OCM resource references. + A set of additional resources specified by an OCM resource reference or + direct data as byte, string or yaml. The key describes the meaning of the resource. The following keys have a special meaning: @@ -176,6 +177,17 @@ that contains the resource reference. It uses the following fields: This is the identity of the resource in the selected component version. +#### *AdditionalResource* + +This field has either the fields of a *ResourceReference* to refer to the +content of an OCM resource or the field: + +- **content** *string|[]byte|YAML* + + Either a resource reference or the field content must be given. + The content field may contain a string or an inline YAML document. + For larger content the resource reference form should be preferred. + #### *Identity* An identity specification is a map[string]string. It describes diff --git a/docs/reference/ocm_describe_package_toi-bootstrapping.md b/docs/reference/ocm_describe_package_toi-bootstrapping.md index 17640f0d4e..12a806361d 100644 --- a/docs/reference/ocm_describe_package_toi-bootstrapping.md +++ b/docs/reference/ocm_describe_package_toi-bootstrapping.md @@ -91,9 +91,10 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. -- **additionalResources** (optional) *map[string]ResourceReference* +- **additionalResources** (optional) *map[string]AdditionalResource)* - A set of additional resources specified by OCM resource references. + A set of additional resources specified by an OCM resource reference or + direct data as byte, string or yaml. The key describes the meaning of the resource. The following keys have a special meaning: @@ -176,6 +177,17 @@ that contains the resource reference. It uses the following fields: This is the identity of the resource in the selected component version. +#### *AdditionalResource* + +This field has either the fields of a *ResourceReference* to refer to the +content of an OCM resource or the field: + +- **content** *string|[]byte|YAML* + + Either a resource reference or the field content must be given. + The content field may contain a string or an inline YAML document. + For larger content the resource reference form should be preferred. + #### *Identity* An identity specification is a map[string]string. It describes diff --git a/docs/reference/ocm_toi-bootstrapping.md b/docs/reference/ocm_toi-bootstrapping.md index 4326adaaac..a2ec778142 100644 --- a/docs/reference/ocm_toi-bootstrapping.md +++ b/docs/reference/ocm_toi-bootstrapping.md @@ -91,9 +91,10 @@ It has the following format: requites the specification of a credentials file providing the information how to satisfy those credential requests. -- **additionalResources** (optional) *map[string]ResourceReference* +- **additionalResources** (optional) *map[string]AdditionalResource)* - A set of additional resources specified by OCM resource references. + A set of additional resources specified by an OCM resource reference or + direct data as byte, string or yaml. The key describes the meaning of the resource. The following keys have a special meaning: @@ -176,6 +177,17 @@ that contains the resource reference. It uses the following fields: This is the identity of the resource in the selected component version. +#### *AdditionalResource* + +This field has either the fields of a *ResourceReference* to refer to the +content of an OCM resource or the field: + +- **content** *string|[]byte|YAML* + + Either a resource reference or the field content must be given. + The content field may contain a string or an inline YAML document. + For larger content the resource reference form should be preferred. + #### *Identity* An identity specification is a map[string]string. It describes diff --git a/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go b/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go index 6445e9c4ff..4fd557a076 100644 --- a/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go +++ b/pkg/contexts/ocm/compdesc/meta/v1/resourceref.go @@ -7,7 +7,7 @@ package v1 // ResourceReference describes re resource identity relative to an (aggregation) // component version. type ResourceReference struct { - Resource Identity `json:"resource"` + Resource Identity `json:"resource,omitempty"` ReferencePath []Identity `json:"referencePath,omitempty"` } diff --git a/pkg/toi/install/action_test.go b/pkg/toi/install/action_test.go index baf2cc5069..b54027a092 100644 --- a/pkg/toi/install/action_test.go +++ b/pkg/toi/install/action_test.go @@ -7,23 +7,22 @@ package install_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/open-component-model/ocm/pkg/common/accessobj" - v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" . "github.com/open-component-model/ocm/pkg/env" . "github.com/open-component-model/ocm/pkg/env/builder" - "github.com/open-component-model/ocm/pkg/mime" . "github.com/open-component-model/ocm/pkg/testutils" - "github.com/open-component-model/ocm/pkg/toi/drivers/mock" "github.com/mandelsoft/vfs/pkg/memoryfs" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/common/accessobj" "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/credentials/identity/hostpath" + v1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" + "github.com/open-component-model/ocm/pkg/mime" "github.com/open-component-model/ocm/pkg/toi" + "github.com/open-component-model/ocm/pkg/toi/drivers/mock" "github.com/open-component-model/ocm/pkg/toi/install" ) @@ -85,7 +84,7 @@ creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) spec := &toi.PackageSpecification{ CredentialsRequest: toi.CredentialsRequest{ Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": toi.CredentialsRequestSpec{ + "mycred": { ConsumerId: cid1, Description: "test", Optional: false, @@ -93,7 +92,7 @@ creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) }, }, Executors: []toi.Executor{ - toi.Executor{ + { Actions: []string{"install"}, Image: &toi.Image{ Ref: "a/b:v1", @@ -105,7 +104,7 @@ creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) credspec := &toi.Credentials{ Credentials: map[string]toi.CredentialSpec{ - "mycred": toi.CredentialSpec{ + "mycred": { ConsumerId: cid1, }, }, @@ -143,7 +142,7 @@ creds: (( hasCredentials("mycred") ? getCredentials("mycred", "user") :"" )) spec := &toi.PackageSpecification{ CredentialsRequest: toi.CredentialsRequest{ Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": toi.CredentialsRequestSpec{ + "mycred": { ConsumerId: cid1, Description: "test", Optional: false, @@ -151,7 +150,7 @@ creds: (( hasCredentials("mycred") ? getCredentials("mycred", "user") :"" )) }, }, Executors: []toi.Executor{ - toi.Executor{ + { Actions: []string{"install"}, Image: &toi.Image{ Ref: "a/b:v1", @@ -163,7 +162,7 @@ creds: (( hasCredentials("mycred") ? getCredentials("mycred", "user") :"" )) credspec := &toi.Credentials{ Credentials: map[string]toi.CredentialSpec{ - "mycred": toi.CredentialSpec{ + "mycred": { ConsumerId: cid1, }, }, @@ -201,7 +200,7 @@ creds: (( hasCredentials("mycred") ? getCredentials("mycred", "*") :"" )) spec := &toi.PackageSpecification{ CredentialsRequest: toi.CredentialsRequest{ Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": toi.CredentialsRequestSpec{ + "mycred": { ConsumerId: cid1, Description: "test", Optional: false, @@ -209,7 +208,7 @@ creds: (( hasCredentials("mycred") ? getCredentials("mycred", "*") :"" )) }, }, Executors: []toi.Executor{ - toi.Executor{ + { Actions: []string{"install"}, Image: &toi.Image{ Ref: "a/b:v1", @@ -221,7 +220,7 @@ creds: (( hasCredentials("mycred") ? getCredentials("mycred", "*") :"" )) credspec := &toi.Credentials{ Credentials: map[string]toi.CredentialSpec{ - "mycred": toi.CredentialSpec{ + "mycred": { ConsumerId: cid1, }, }, @@ -257,7 +256,7 @@ creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) spec := &toi.PackageSpecification{ CredentialsRequest: toi.CredentialsRequest{ Credentials: map[string]toi.CredentialsRequestSpec{ - "mycred": toi.CredentialsRequestSpec{ + "mycred": { ConsumerId: cid1, Description: "test", Optional: true, @@ -265,7 +264,7 @@ creds: (( hasCredentials("mycred") ? [getCredentials("mycred")] :[] )) }, }, Executors: []toi.Executor{ - toi.Executor{ + { Actions: []string{"install"}, Image: &toi.Image{ Ref: "a/b:v1", diff --git a/pkg/toi/install/credentials_test.go b/pkg/toi/install/credentials_test.go index 3637ae8c40..65f59c6e01 100644 --- a/pkg/toi/install/credentials_test.go +++ b/pkg/toi/install/credentials_test.go @@ -7,7 +7,6 @@ package install_test import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - . "github.com/open-component-model/ocm/pkg/testutils" "github.com/open-component-model/ocm/pkg/common" diff --git a/pkg/toi/spec.go b/pkg/toi/spec.go index 21bd186c41..d5916c1f8c 100644 --- a/pkg/toi/spec.go +++ b/pkg/toi/spec.go @@ -33,12 +33,17 @@ const ( type PackageSpecification struct { CredentialsRequest `json:",inline"` - Template json.RawMessage `json:"configTemplate,omitempty"` - Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` - Scheme json.RawMessage `json:"configScheme,omitempty"` - Executors []Executor `json:"executors"` - Description string `json:"description"` - AdditionalResources map[string]*metav1.ResourceReference `json:"additionalResources,omitempty"` + Template json.RawMessage `json:"configTemplate,omitempty"` + Libraries []metav1.ResourceReference `json:"templateLibraries,omitempty"` + Scheme json.RawMessage `json:"configScheme,omitempty"` + Executors []Executor `json:"executors"` + Description string `json:"description"` + AdditionalResources map[string]*AdditionalResource `json:"additionalResources,omitempty"` +} + +type AdditionalResource struct { + *metav1.ResourceReference `json:",inline"` + Content json.RawMessage `json:"content,omitempty"` } type Executor struct { From 4832aa9b3dffe60f8e060feaa454193c11165dee Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Sat, 15 Apr 2023 17:00:25 +0200 Subject: [PATCH 3/3] toi repo credential propagation --- pkg/contexts/credentials/cpi/interface.go | 15 ++++--- pkg/contexts/credentials/interface.go | 13 +++--- .../credentials/internal/consumers.go | 30 ++++++++++++++ pkg/contexts/credentials/internal/identity.go | 5 +++ pkg/contexts/oci/internal/repository.go | 5 +++ pkg/contexts/oci/repositories/ocireg/creds.go | 12 +++++- .../oci/repositories/ocireg/repository.go | 17 +++++++- pkg/contexts/ocm/internal/repository.go | 6 +++ .../repositories/genericocireg/repository.go | 25 +++++++++++- pkg/toi/install/action.go | 40 +++++++++++++------ 10 files changed, 140 insertions(+), 28 deletions(-) diff --git a/pkg/contexts/credentials/cpi/interface.go b/pkg/contexts/credentials/cpi/interface.go index b768866ad7..2b6e0a1d0e 100644 --- a/pkg/contexts/credentials/cpi/interface.go +++ b/pkg/contexts/credentials/cpi/interface.go @@ -35,12 +35,15 @@ type ( ) type ( - ProviderIdentity = internal.ProviderIdentity - ConsumerProvider = internal.ConsumerProvider - ConsumerIdentity = internal.ConsumerIdentity - IdentityMatcher = internal.IdentityMatcher - IdentityMatcherInfo = internal.IdentityMatcherInfo - IdentityMatcherRegistry = internal.IdentityMatcherRegistry + ConsumerIdentity = internal.ConsumerIdentity + ConsumerIdentityProvider = internal.ConsumerIdentityProvider + ProviderIdentity = internal.ProviderIdentity + ConsumerProvider = internal.ConsumerProvider + UsageContext = internal.UsageContext + StringUsageContext = internal.StringUsageContext + IdentityMatcher = internal.IdentityMatcher + IdentityMatcherInfo = internal.IdentityMatcherInfo + IdentityMatcherRegistry = internal.IdentityMatcherRegistry ) var DefaultContext = internal.DefaultContext diff --git a/pkg/contexts/credentials/interface.go b/pkg/contexts/credentials/interface.go index 895298d13e..fca688d9ac 100644 --- a/pkg/contexts/credentials/interface.go +++ b/pkg/contexts/credentials/interface.go @@ -36,11 +36,14 @@ type ( ) type ( - ProviderIdentity = internal.ProviderIdentity - ConsumerIdentity = internal.ConsumerIdentity - IdentityMatcher = internal.IdentityMatcher - IdentityMatcherInfo = internal.IdentityMatcherInfo - IdentityMatcherRegistry = internal.IdentityMatcherRegistry + ConsumerIdentity = internal.ConsumerIdentity + ConsumerIdentityProvider = internal.ConsumerIdentityProvider + ProviderIdentity = internal.ProviderIdentity + UsageContext = internal.UsageContext + StringUsageContext = internal.StringUsageContext + IdentityMatcher = internal.IdentityMatcher + IdentityMatcherInfo = internal.IdentityMatcherInfo + IdentityMatcherRegistry = internal.IdentityMatcherRegistry ) type ( diff --git a/pkg/contexts/credentials/internal/consumers.go b/pkg/contexts/credentials/internal/consumers.go index 107910b746..e2107382c3 100644 --- a/pkg/contexts/credentials/internal/consumers.go +++ b/pkg/contexts/credentials/internal/consumers.go @@ -11,6 +11,36 @@ import ( "github.com/open-component-model/ocm/pkg/generics" ) +// UsageContext descibes a dediacetd type specific +// sub usage kinds for an object requiring credentials. +// For example, for an object providing a hierarchical +// namespace this might be a namespace prefix for +// included objects, for which credentials should be requested. +type UsageContext interface { + String() string +} + +type StringUsageContext string + +func (s StringUsageContext) String() string { + return string(s) +} + +// ConsumerIdentityProvider is an interface for objects requiring +// credentials, which want to expose the ConsumerId they are +// using to request implicit credentials. +type ConsumerIdentityProvider interface { + // GetConsumerId provides information about the consumer id + // used for the object implementing this interface. + // Optionally a sub context can be given to specify + // a dedicated type specific sub realm. + GetConsumerId(uctx ...UsageContext) ConsumerIdentity + // GetIdentityMatcher provides the identity macher type to use + // to match the consumer identities configured in a credentials + // context. + GetIdentityMatcher() string +} + type _consumers struct { data map[string]*_consumer } diff --git a/pkg/contexts/credentials/internal/identity.go b/pkg/contexts/credentials/internal/identity.go index dd76b79768..bdde59990e 100644 --- a/pkg/contexts/credentials/internal/identity.go +++ b/pkg/contexts/credentials/internal/identity.go @@ -98,6 +98,11 @@ func orMatcher(list []IdentityMatcher) IdentityMatcher { // ConsumerIdentity describes the identity of a credential consumer. type ConsumerIdentity map[string]string +// IsSet checks whether an identity is given. +func (i ConsumerIdentity) IsSet() bool { + return len(i) != 0 +} + // IdentityByURL return a simple url identity. func IdentityByURL(url string) ConsumerIdentity { return ConsumerIdentity{"url": url} diff --git a/pkg/contexts/oci/internal/repository.go b/pkg/contexts/oci/internal/repository.go index 8d50fb29b5..2d4c1fc0b9 100644 --- a/pkg/contexts/oci/internal/repository.go +++ b/pkg/contexts/oci/internal/repository.go @@ -8,6 +8,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" ) @@ -21,6 +22,10 @@ type Repository interface { Close() error } +// ConsumerIdentityProvider is an optional interface for repositories +// to tell about their credential requests. +type ConsumerIdentityProvider = credentials.ConsumerIdentityProvider + type RepositorySource interface { GetRepository() Repository } diff --git a/pkg/contexts/oci/repositories/ocireg/creds.go b/pkg/contexts/oci/repositories/ocireg/creds.go index 75d62483c5..6ad5319b9f 100644 --- a/pkg/contexts/oci/repositories/ocireg/creds.go +++ b/pkg/contexts/oci/repositories/ocireg/creds.go @@ -13,6 +13,10 @@ import ( ) func GetCredentials(ctx credentials.ContextProvider, locator, repo string) (credentials.Credentials, error) { + return credentials.CredentialsForConsumer(ctx.CredentialsContext(), GetConsumerId(locator, repo), identity.IdentityMatcher) +} + +func GetConsumerId(locator, repo string) credentials.ConsumerIdentity { host, port, base := utils.SplitLocator(locator) id := credentials.ConsumerIdentity{ identity.ID_TYPE: identity.CONSUMER_TYPE, @@ -21,6 +25,10 @@ func GetCredentials(ctx credentials.ContextProvider, locator, repo string) (cred if port != "" { id[identity.ID_PORT] = port } - id[identity.ID_PATHPREFIX] = path.Join(base, repo) - return credentials.CredentialsForConsumer(ctx.CredentialsContext(), id, identity.IdentityMatcher) + if repo == "" { + id[identity.ID_PATHPREFIX] = base + } else { + id[identity.ID_PATHPREFIX] = path.Join(base, repo) + } + return id } diff --git a/pkg/contexts/oci/repositories/ocireg/repository.go b/pkg/contexts/oci/repositories/ocireg/repository.go index 6491fcd7ac..1bf4085739 100644 --- a/pkg/contexts/oci/repositories/ocireg/repository.go +++ b/pkg/contexts/oci/repositories/ocireg/repository.go @@ -16,6 +16,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" + "github.com/open-component-model/ocm/pkg/contexts/oci/identity" "github.com/open-component-model/ocm/pkg/docker" "github.com/open-component-model/ocm/pkg/docker/resolve" "github.com/open-component-model/ocm/pkg/errors" @@ -50,7 +51,10 @@ type Repository struct { info *RepositoryInfo } -var _ cpi.Repository = &Repository{} +var ( + _ cpi.Repository = &Repository{} + _ credentials.ConsumerIdentityProvider = &Repository{} +) func NewRepository(ctx cpi.Context, spec *RepositorySpec, info *RepositoryInfo) (*Repository, error) { urs := spec.UniformRepositorySpec() @@ -62,6 +66,17 @@ func NewRepository(ctx cpi.Context, spec *RepositorySpec, info *RepositoryInfo) }, nil } +func (r *Repository) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + if c, ok := utils.Optional(uctx...).(credentials.StringUsageContext); ok { + return GetConsumerId(r.info.Locator, c.String()) + } + return GetConsumerId(r.info.Locator, "") +} + +func (r *Repository) GetIdentityMatcher() string { + return identity.CONSUMER_TYPE +} + func (r *Repository) NamespaceLister() cpi.NamespaceLister { return nil } diff --git a/pkg/contexts/ocm/internal/repository.go b/pkg/contexts/ocm/internal/repository.go index a458f6efd2..7d170a1390 100644 --- a/pkg/contexts/ocm/internal/repository.go +++ b/pkg/contexts/ocm/internal/repository.go @@ -9,6 +9,7 @@ import ( "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" ) @@ -30,6 +31,11 @@ type Repository interface { Close() error } +// ConsumerIdentityProvider is an interface for object requiring +// credentials, which want to expose the ConsumerId they are +// usingto request implicit credentials. +type ConsumerIdentityProvider = credentials.ConsumerIdentityProvider + type ( DataAccess = accessio.DataAccess BlobAccess = accessio.BlobAccess diff --git a/pkg/contexts/ocm/repositories/genericocireg/repository.go b/pkg/contexts/ocm/repositories/genericocireg/repository.go index 1852afcc66..15d38fda8a 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/repository.go +++ b/pkg/contexts/ocm/repositories/genericocireg/repository.go @@ -12,11 +12,13 @@ import ( "strings" "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/oci" ocicpi "github.com/open-component-model/ocm/pkg/contexts/oci/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/utils" ) type OCIBasedRepository interface { @@ -45,7 +47,10 @@ type RepositoryImpl struct { ocirepo oci.Repository } -var _ OCIBasedRepository = (*Repository)(nil) +var ( + _ OCIBasedRepository = (*Repository)(nil) + _ credentials.ConsumerIdentityProvider = (*Repository)(nil) +) func NewRepository(ctx cpi.Context, meta *ComponentRepositoryMeta, ocirepo oci.Repository) (cpi.Repository, error) { repo := &RepositoryImpl{ @@ -65,6 +70,24 @@ func (r *RepositoryImpl) View(main ...bool) (*Repository, error) { return &Repository{view: v, RepositoryImpl: r}, nil } +func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + prefix := r.meta.SubPath + if c, ok := utils.Optional(uctx...).(credentials.StringUsageContext); ok { + prefix = path.Join(prefix, componentmapping.ComponentDescriptorNamespace, c.String()) + } + if p, ok := r.ocirepo.(credentials.ConsumerIdentityProvider); ok { + return p.GetConsumerId(credentials.StringUsageContext(prefix)) + } + return nil +} + +func (r *RepositoryImpl) GetIdentityMatcher() string { + if p, ok := r.ocirepo.(credentials.ConsumerIdentityProvider); ok { + return p.GetIdentityMatcher() + } + return "" +} + func (r *RepositoryImpl) Close() error { return r.ocirepo.Close() } diff --git a/pkg/toi/install/action.go b/pkg/toi/install/action.go index 28035e5ab1..77fa91da7f 100644 --- a/pkg/toi/install/action.go +++ b/pkg/toi/install/action.go @@ -21,6 +21,7 @@ import ( "github.com/open-component-model/ocm/pkg/common/accessobj" "github.com/open-component-model/ocm/pkg/contexts/config" globalconfig "github.com/open-component-model/ocm/pkg/contexts/config/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/logforward" logcfg "github.com/open-component-model/ocm/pkg/contexts/datacontext/config/logging" "github.com/open-component-model/ocm/pkg/contexts/ocm" @@ -163,7 +164,7 @@ func mappingKeyFor(value string, m map[string]string) string { // CheckCredentialRequests determine required credentials for executor. func CheckCredentialRequests(executor *toi.Executor, spec *toi.PackageSpecification, espec *toi.ExecutorSpecification) (map[string]CredentialsRequestSpec, map[string]string, error) { - credentials := spec.Credentials + credsspec := spec.Credentials credmapping := map[string]string{} if len(espec.Credentials) > 0 { @@ -178,7 +179,7 @@ func CheckCredentialRequests(executor *toi.Executor, spec *toi.PackageSpecificat if _, ok := espec.Credentials[ke]; ok { credmapping[k] = ke } else { - delete(credentials, k) + delete(credsspec, k) } } } @@ -189,10 +190,10 @@ func CheckCredentialRequests(executor *toi.Executor, spec *toi.PackageSpecificat if ko == "" { return nil, nil, errors.Newf("credential mapping missing for executor credential key %q", ke) } - if o, ok := credentials[ko]; !ok { + if o, ok := credsspec[ko]; !ok { if !e.Optional { // implicit inheritance of executor spec setting - credentials[ko] = e + credsspec[ko] = e credmapping[ko] = ke } } else { @@ -203,27 +204,27 @@ func CheckCredentialRequests(executor *toi.Executor, spec *toi.PackageSpecificat } } else { // no credential requests specified for package, use the one from the executor - credentials = espec.Credentials + credsspec = espec.Credentials } } else { if len(executor.CredentialMapping) > 0 { // determine subset of credentials required for executor credmapping = executor.CredentialMapping - for k := range credentials { + for k := range credsspec { if _, ok := credmapping[k]; !ok { - delete(credentials, k) + delete(credsspec, k) } else { credmapping[k] = k } } } else { // assume to require all as defined - for k := range credentials { + for k := range credsspec { credmapping[k] = k } } } - return credentials, credmapping, nil + return credsspec, credmapping, nil } func ProcessConfig(name string, octx ocm.Context, cv ocm.ComponentVersionAccess, resolver ocm.ComponentVersionResolver, template []byte, config []byte, libraries []metav1.ResourceReference, schemedata []byte) ([]byte, error) { @@ -354,18 +355,31 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *toi.PackageSpe p.Printf("using executor config:\n%s\n", utils2.IndentLines(string(econfig), " ")) } // handle credentials - credentials, credmapping, err := CheckCredentialRequests(executor, spec, &espec.Spec) + credreqs, credmapping, err := CheckCredentialRequests(executor, spec, &espec.Spec) if err != nil { return nil, err } // prepare ocm config with credential settings and logging config forwarding - if len(credentials) > 0 { + if len(credreqs) > 0 { if creds == nil { return nil, errors.Newf("credential settings required") } } - ccfg, credvals, err := GetCredentials(octx.CredentialsContext(), creds, credentials, credmapping) + + if p, ok := cv.Repository().(credentials.ConsumerIdentityProvider); ok { + cid := p.GetConsumerId() + if cid.IsSet() { + if creds == nil { + creds = &Credentials{} + } + creds.Forwarded = append(creds.Forwarded, toi.ForwardSpec{ + ConsumerId: cid, + ConsumerType: p.GetIdentityMatcher(), + }) + } + } + ccfg, credvals, err := GetCredentials(octx.CredentialsContext(), creds, credreqs, credmapping) if err != nil { return nil, errors.Wrapf(err, "credential evaluation failed") } @@ -411,7 +425,7 @@ func ExecuteAction(p common.Printer, d Driver, name string, spec *toi.PackageSpe } names := []string{} - for n := range credentials { + for n := range credreqs { m := credmapping[n] names = append(names, n+"->"+m) }