diff --git a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go index 4200633632..099b9a0451 100644 --- a/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/sign/cmd_test.go @@ -238,7 +238,7 @@ Error: signing: github.com/mandelsoft/ref:v1: failed resolving component referen buf := bytes.NewBuffer(nil) Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, "--repo", ARCH, COMPONENTB+":"+VERSION)).To(HaveOccurred()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` -Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found +Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component "github.com/mandelsoft/test" not found in ComponentArchive `)) }) @@ -246,7 +246,7 @@ Error: signing: github.com/mandelsoft/ref:v1: failed resolving component referen buf := bytes.NewBuffer(nil) Expect(env.CatchErrorOutput(buf).Execute("sign", "components", "-s", SIGNATURE, "-K", PRIVKEY, ARCH)).To(HaveOccurred()) Expect(buf.String()).To(StringEqualTrimmedWithContext(` -Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: ocm reference "github.com/mandelsoft/test:v1" not found +Error: signing: github.com/mandelsoft/ref:v1: failed resolving component reference ref[github.com/mandelsoft/test:v1]: component "github.com/mandelsoft/test" not found in ComponentArchive `)) }) }) diff --git a/examples/lib/helper/helper.go b/examples/lib/helper/helper.go index 26bd9ad00d..0739fa781c 100644 --- a/examples/lib/helper/helper.go +++ b/examples/lib/helper/helper.go @@ -15,14 +15,14 @@ import ( ) type Config struct { - Username string `json:"username"` - Password string `json:"password"` - Component string `json:"component"` - Repository string `json:"repository"` - Version string `json:"version"` - - Target json.RawMessage `json:"targetRepository"` - OCMConfig string `json:"ocmConfig"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Component string `json:"component,omitempty"` + Repository string `json:"repository,omitempty"` + Version string `json:"version,omitempty"` + + Target json.RawMessage `json:"targetRepository,omitempty"` + OCMConfig string `json:"ocmConfig,omitempty"` } func ReadConfig(path string) (*Config, error) { diff --git a/examples/lib/tour/01-getting-started/README.md b/examples/lib/tour/01-getting-started/README.md new file mode 100644 index 0000000000..b9623c4739 --- /dev/null +++ b/examples/lib/tour/01-getting-started/README.md @@ -0,0 +1,13 @@ +# Basic Usage of OCM Repositories + +This [tour](example.go) illustrates the basic usage of the API to +access component versions in an OCM repository. + +You can just call the main program with some config file argument +with the following content: + +```yaml +component: github.com/mandelsoft/examples/cred1 +repository: ghcr.io/mandelsoft/ocm +version: 0.1.0 +``` \ No newline at end of file diff --git a/examples/lib/tour/02-composing-a-component-version/example-a.go b/examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go similarity index 99% rename from examples/lib/tour/02-composing-a-component-version/example-a.go rename to examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go index 6a5fa0142d..08785c7097 100644 --- a/examples/lib/tour/02-composing-a-component-version/example-a.go +++ b/examples/lib/tour/02-composing-a-component-version/01-basic-componentversion-creation.go @@ -279,6 +279,7 @@ func listVersions(repo ocm.Repository, list ...string) error { } return nil } + func ComposingAComponentVersionA() error { // yes, we need an OCM context, again ctx := ocm.DefaultContext() diff --git a/examples/lib/tour/02-composing-a-component-version/example-b.go b/examples/lib/tour/02-composing-a-component-version/02-composition-version.go similarity index 100% rename from examples/lib/tour/02-composing-a-component-version/example-b.go rename to examples/lib/tour/02-composing-a-component-version/02-composition-version.go diff --git a/examples/lib/tour/02-composing-a-component-version/README.md b/examples/lib/tour/02-composing-a-component-version/README.md new file mode 100644 index 0000000000..6a7ac375cf --- /dev/null +++ b/examples/lib/tour/02-composing-a-component-version/README.md @@ -0,0 +1,10 @@ +# Composing a Component Version + +This tor illustrates the basic usage of the API to +create/compose component versions. + +It covers two basic scenarios: +- [`basic`](01-basic-componentversion-creation.go) Create a component version stored in the filesystem +- [`compose`](02-composition-version.go) Create a component version stored in memory using a non-persistent composition version. + +You can just call the main program with the scenario as argument. diff --git a/examples/lib/tour/03-working-with-credentials/example-a.go b/examples/lib/tour/03-working-with-credentials/01-using-credentials.go similarity index 100% rename from examples/lib/tour/03-working-with-credentials/example-a.go rename to examples/lib/tour/03-working-with-credentials/01-using-credentials.go diff --git a/examples/lib/tour/03-working-with-credentials/example-b.go b/examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go similarity index 95% rename from examples/lib/tour/03-working-with-credentials/example-b.go rename to examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go index 96efde1dd3..4cd8527215 100644 --- a/examples/lib/tour/03-working-with-credentials/example-b.go +++ b/examples/lib/tour/03-working-with-credentials/02-basic-credential-management.go @@ -10,7 +10,6 @@ import ( "os" "github.com/open-component-model/ocm/examples/lib/helper" - "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/credentials" ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" "github.com/open-component-model/ocm/pkg/contexts/oci" @@ -19,19 +18,6 @@ import ( "github.com/open-component-model/ocm/pkg/errors" ) -func obfuscate(props common.Properties) string { - if pw, ok := props[credentials.ATTR_PASSWORD]; ok { - if len(pw) > 5 { - pw = pw[:5] + "***" - } else { - pw = "***" - } - props = props.Copy() - props[credentials.ATTR_PASSWORD] = pw - } - return props.String() -} - func UsingCredentialsB(cfg *helper.Config, create bool) error { ctx := ocm.DefaultContext() @@ -137,6 +123,11 @@ func UsingCredentialsB(cfg *helper.Config, create bool) error { if err != nil { return errors.Wrapf(err, "no credentials") } + // an error is only provided if something went wrong while determining + // the credentials. Delivering NO credentials is a valid result. + if creds == nil { + return fmt.Errorf("no credentials found") + } fmt.Printf("credentials: %s\n", obfuscate(creds.Properties())) // Now we can continue with our basic component version composition diff --git a/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go b/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go new file mode 100644 index 0000000000..26a1c3a66e --- /dev/null +++ b/examples/lib/tour/03-working-with-credentials/03-credential-repositories.go @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + + "github.com/open-component-model/ocm/examples/lib/helper" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" + "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/errors" +) + +func UsingCredentialsRepositories(cfg *helper.Config) error { + ctx := ocm.DefaultContext() + credctx := ctx.CredentialsContext() + + // The OCM toolset embraces multiple storage + // backend technologies, for OCM meta data as well + // as for artifacts described by a component version. + // All those technologies typically have their own + // way to configure credentials for command line + // tools or servers. + // + // The credential management provides so-called + // credential repositories. Such a repository + // is able to provide any number of names + // credential sets. This way any special + // credential store can be connected to the + // OCM credential management jsu by providing + // an own implementation for the repository interface. + + // One such case is the docker config json, a config + // file used by docker login to store + // credentials for dedicatd OCI regsitries. + dspec := dockerconfig.NewRepositorySpec("~/.docker/config.json") + + // There are general credential stores, like a HashiCorp Vault + // or type-specific ones, like the docker config json + // used to configure credentials for the docker client. + // (working with OCI registries). + // Those specialized repository implementation are not only able to + // provide credential sets, they also know about the usage context. + // Such repository implementations are able to provide credential + // mappings for consumer ids, also. + + // The docker config is such a case, so we can instruct the + // repository to automatically propagate appropriate the consumer id + // mappings. + dspec = dspec.WithConsumerPropagation(true) + + // now we can jsut add the repository for this specification to + // the credential context. + _, err := credctx.RepositoryForSpec(dspec) + if err != nil { + return errors.Wrapf(err, "invalid credential repository") + } + // we are not interested in the repository object, so we just ignore + // the result. + + // so, if you have done the appropriate docker login for your + // OCI registry, it should be possible now to get the credentials + // for the configured repository. + id, err := oci.GetConsumerIdForRef(cfg.Repository) + if err != nil { + return errors.Wrapf(err, "invalid consumer") + } + + // the returned credentials are provided via an interface, which might change its + // content, if the underlying credential source changes. + creds, err := credentials.CredentialsForConsumer(credctx, id, ociidentity.IdentityMatcher) + if err != nil { + return errors.Wrapf(err, "no credentials") + } + // an error is only provided if something went wrong while determining + // the credentials. Delivering NO credentials is a valid result. + if creds == nil { + return fmt.Errorf("no credentials found") + } + fmt.Printf("credentials: %s\n", obfuscate(creds.Properties())) + + return nil +} diff --git a/examples/lib/tour/03-working-with-credentials/README.md b/examples/lib/tour/03-working-with-credentials/README.md new file mode 100644 index 0000000000..56407a1b75 --- /dev/null +++ b/examples/lib/tour/03-working-with-credentials/README.md @@ -0,0 +1,21 @@ +# Working with Credentials + +This tour illustrates the basic handling of credentials +using the OCM library. The library provides +an extensible framework to bring together credential providers +and credential cosunmers in a technology-agnostic way. + +It covers four basic scenarios: +- [`basic`](01-using-credentials.go) Writing to a repository with directly specified credentials. +- [`generic`](02-basic-credential-management.go) Using credentials via the credential management. +- [`read`](02-basic-credential-management.go) Read the previously component version using the credential management. +- [`credrepo`](03-credential-repositories.go) Providing credentials via credential repositories. + +You can just call the main program with some config file option (`--config `) and the name of the scenario. +The config file should have the following content: + +```yaml +repository: ghcr.io/mandelsoft/ocm +username: +password: +``` \ No newline at end of file diff --git a/examples/lib/tour/03-working-with-credentials/common.go b/examples/lib/tour/03-working-with-credentials/common.go index d5efc5f78c..4846450c8c 100644 --- a/examples/lib/tour/03-working-with-credentials/common.go +++ b/examples/lib/tour/03-working-with-credentials/common.go @@ -10,6 +10,7 @@ import ( "github.com/open-component-model/ocm/pkg/blobaccess" "github.com/open-component-model/ocm/pkg/common" + "github.com/open-component-model/ocm/pkg/contexts/credentials" "github.com/open-component-model/ocm/pkg/contexts/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" @@ -278,3 +279,16 @@ func listVersions(repo ocm.Repository, list ...string) error { } return nil } + +func obfuscate(props common.Properties) string { + if pw, ok := props[credentials.ATTR_PASSWORD]; ok { + if len(pw) > 5 { + pw = pw[:5] + "***" + } else { + pw = "***" + } + props = props.Copy() + props[credentials.ATTR_PASSWORD] = pw + } + return props.String() +} diff --git a/examples/lib/tour/03-working-with-credentials/main.go b/examples/lib/tour/03-working-with-credentials/main.go index 53b9465fbf..7abd06c7e0 100644 --- a/examples/lib/tour/03-working-with-credentials/main.go +++ b/examples/lib/tour/03-working-with-credentials/main.go @@ -52,6 +52,8 @@ func main() { err = UsingCredentialsB(cfg, true) case "read": err = UsingCredentialsB(cfg, false) + case "credrepo": + err = UsingCredentialsRepositories(cfg) default: err = fmt.Errorf("unknown example %q", cmd) } diff --git a/examples/lib/tour/04-working-with-config/01-basic-config-management.go b/examples/lib/tour/04-working-with-config/01-basic-config-management.go new file mode 100644 index 0000000000..bea3a74d7c --- /dev/null +++ b/examples/lib/tour/04-working-with-config/01-basic-config-management.go @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "encoding/json" + "fmt" + + "github.com/go-test/deep" + "github.com/open-component-model/ocm/examples/lib/helper" + "github.com/open-component-model/ocm/pkg/contexts/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/errors" +) + +func BasicConfigurationHandling(cfg *helper.Config) error { + // configuration is handled by the configuration context. + ctx := config.DefaultContext() + + // the configuration context handles configuration objects. + // a configuration object is any object implementing + // the config.Config interface. + + // one such object is the configuration object for + // credentials. + + creds := credcfg.New() + + // here we can configure credential settings: + // credential repositories and consumer is mappings. + id, err := oci.GetConsumerIdForRef(cfg.Repository) + if err != nil { + return errors.Wrapf(err, "invalid consumer") + } + creds.AddConsumer( + id, + directcreds.NewRepositorySpec(cfg.GetCredentials().Properties()), + ) + + // credential objects are typically serializable and deserializable. + + spec, err := json.MarshalIndent(creds, " ", " ") + if err != nil { + return errors.Wrapf(err, "marshal credential config") + } + + fmt.Printf("this a a credential configuration object:\n%s\n", string(spec)) + + // like all the other maifest based description this format always includes + // a type field, which can be used to deserialize a specification into + // the appropriate object. + // This can ebe done by the config context. It accepts YAML or JSON. + + o, err := ctx.GetConfigForData(spec, nil) + if err != nil { + return errors.Wrapf(err, "deserialize config") + } + + if diff := deep.Equal(o, creds); len(diff) != 0 { + fmt.Printf("diff:\n%v\n", diff) + return fmt.Errorf("invalid des/erialization") + } + + // regardless what variant is used (direct object or descriptor) + // the config object can be added to a config context. + err = ctx.ApplyConfig(creds, "explicit cred setting") + if err != nil { + return errors.Wrapf(err, "cannot apply config") + } + + // Every config object implements the + // ApplyTo(ctx config.Context, target interface{}) error method. + // It takes an object, which wants to be configured. + // The config object then decides, whether it provides + // settings for the given object and calls the appropriate + // methods on this object (after a type cast). + // + // This way the config mechanism reverts the configuration + // request, it does not actively configure something, instead + // an object, which wants to be configured calls the config + // context to apply pending configs. + // The config context manages a queue of config objects + // and applys them to an object to be configured. + + // If ask he credential context now for credentials, + // it asks the config context for pending config objects + // and apply them. + // Theregore, we now should the configured creentials, here. + + credctx := credentials.DefaultContext() + + found, err := credentials.CredentialsForConsumer(credctx, id) + if err != nil { + return errors.Wrapf(err, "cannot get credentials") + } + // an error is only provided if something went wrong while determining + // the credentials. Delivering NO credentials is a valid result. + if found == nil { + return fmt.Errorf("no credentials found") + } + fmt.Printf("consumer id: %s\n", id) + fmt.Printf("credentials: %s\n", obfuscate(found)) + + if found.GetProperty(credentials.ATTR_USERNAME) != cfg.Username { + return fmt.Errorf("password mismatch") + } + if found.GetProperty(credentials.ATTR_PASSWORD) != cfg.Password { + return fmt.Errorf("password mismatch") + } + return nil +} diff --git a/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go b/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go new file mode 100644 index 0000000000..e130d31f5b --- /dev/null +++ b/examples/lib/tour/04-working-with-config/02-handle-arbitrary-config.go @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "encoding/json" + "fmt" + + "github.com/open-component-model/ocm/examples/lib/helper" + "github.com/open-component-model/ocm/pkg/contexts/config" + configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/directcreds" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/errors" +) + +func credConfig(cfg *helper.Config) (config.Config, error) { + creds := credcfg.New() + + // here we can configure credential settings: + // credential repositories and consumer is mappings. + id, err := oci.GetConsumerIdForRef(cfg.Repository) + if err != nil { + return nil, errors.Wrapf(err, "invalid consumer") + } + creds.AddConsumer( + id, + directcreds.NewRepositorySpec(cfg.GetCredentials().Properties()), + ) + return creds, nil +} + +func HandleArbitraryConfiguration(cfg *helper.Config) error { + // The configuration management provides a configuration object + // for it own. + + generic := configcfg.New() + + // the generic config holds a list of config objects, + // or their specification formats. + // Additionally, it is possible to configure names sets + // of configurations, which can later be enabled + // on-demand at the config context. + + // we recycle our credential config from the last example. + creds, err := credConfig(cfg) + if err != nil { + return err + } + err = generic.AddConfig(creds) + if err != nil { + return errors.Wrapf(err, "adding config") + } + + // credential objects are typically serializable and deserializable. + // this also holds for the generic config object of the config context. + + spec, err := json.MarshalIndent(generic, " ", " ") + if err != nil { + return errors.Wrapf(err, "marshal credential config") + } + + // the result is a config object hosting a list (with 1 entry) + // of other config object specifications. + fmt.Printf("this a a generic configuration object:\n%s\n", string(spec)) + + // the generic config object can be added to a config context, again. + ctx := config.DefaultContext() + err = ctx.ApplyConfig(creds, "generic setting") + if err != nil { + return errors.Wrapf(err, "cannot apply config") + } + credctx := credentials.DefaultContext() + + // query now works, also. + id, err := oci.GetConsumerIdForRef(cfg.Repository) + if err != nil { + return errors.Wrapf(err, "invalid consumer") + } + found, err := credentials.CredentialsForConsumer(credctx, id) + if err != nil { + return errors.Wrapf(err, "cannot get credentials") + } + fmt.Printf("consumer id: %s\n", id) + fmt.Printf("credentials: %s\n", obfuscate(found)) + return nil +} diff --git a/examples/lib/tour/04-working-with-config/03-using-ocm-config.go b/examples/lib/tour/04-working-with-config/03-using-ocm-config.go new file mode 100644 index 0000000000..f6e4872ee1 --- /dev/null +++ b/examples/lib/tour/04-working-with-config/03-using-ocm-config.go @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + + "github.com/open-component-model/ocm/examples/lib/helper" + configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + credcfg "github.com/open-component-model/ocm/pkg/contexts/credentials/config" + "github.com/open-component-model/ocm/pkg/contexts/credentials/repositories/dockerconfig" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/contexts/ocm" + "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" + "github.com/open-component-model/ocm/pkg/errors" + "sigs.k8s.io/yaml" +) + +func HandleOCMConfig(cfg *helper.Config) error { + + // Although the configuration of an OCM context can + // be done by a sequence of explicit calls the mechanism + // shown in the example before, is used to provide a simple + // library function, which can be used to configure an OCM + // context and all related other contexts with a single call + // based on a central configuration file (~/.ocmconfig) + ctx := ocm.DefaultContext() + _, err := utils.Configure(ctx, "") + if err != nil { + return errors.Wrapf(err, "configuration") + } + + // It is typically such a generic configuration specification, + // enriched with specialized config specifications for + // credentials, default repositories signing keys and any + // other configuration specification. + // Most important are here the credentials. + // Because OCM embraces lots of storage technologies + // for artifact storage as well as storing OCM meta data, + // tzere are typically multiple technology specific ways + // to configure credentials for command line tools. + // Using the credentials settings shown in the previous examples, + // it ius possible to specify credentials for all + // required purposes, but the configuration mangement provides + // an extensible way to embed native technology specific ways + // to provide credentials just by adding an appropriate type + // of config objects, which reads the specialized stoarge and + // feeds it into the credential context. + // + // One such config object type is the docker config type. It + // reads a dockerconfig.json file and fed in the credentials. + // because it is sed for a dedicated purpose (credentials for + // OCI registries), it not only can feed the credentials, but + // also their mapping to consumer ids. + + // create the specification for a new credential repository of + // type dockerconfig. + credspec := dockerconfig.NewRepositorySpec("~/.docker/config.json", true) + + // add this repository specification to a credential configuration. + ccfg := credcfg.New() + err = ccfg.AddRepository(credspec) + if err != nil { + return errors.Wrapf(err, "invalid credential config") + } + + // By adding the default location for the standard docker config + // file, all credentials provided by the docker login + // are available in the OCM toolset, also. + + // A typical minimal .ocmconfig file can be composed as follows. + + ocmcfg := configcfg.New() + err = ocmcfg.AddConfig(ccfg) + + spec, err := yaml.Marshal(ocmcfg) + if err != nil { + return errors.Wrapf(err, "marshal ocm config") + } + + // the result is a typical minimal ocm configuration file + // just providing the credentials configured with + // doicker login. + fmt.Printf("this a typical ocm config file:\n%s\n", string(spec)) + + // Besides from a file, such a config can be provided as data, also, + // taken from any other source, for example from a Kubernetes secret + + err = utils.ConfigureByData(ctx, spec, "from data") + if err != nil { + return errors.Wrapf(err, "configuration") + } + + // If you have provided your OCI credentials with + // docker login, they should now be available. + + id, err := oci.GetConsumerIdForRef(cfg.Repository) + if err != nil { + return errors.Wrapf(err, "invalid consumer") + } + found, err := credentials.CredentialsForConsumer(ctx, id) + if err != nil { + return errors.Wrapf(err, "cannot get credentials") + } + fmt.Printf("consumer id: %s\n", id) + fmt.Printf("credentials: %s\n", obfuscate(found)) + return nil +} diff --git a/examples/lib/tour/04-working-with-config/04-write-config-type.go b/examples/lib/tour/04-working-with-config/04-write-config-type.go new file mode 100644 index 0000000000..931dab7e75 --- /dev/null +++ b/examples/lib/tour/04-working-with-config/04-write-config-type.go @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + + "github.com/open-component-model/ocm/examples/lib/helper" + configcfg "github.com/open-component-model/ocm/pkg/contexts/config/config" + "github.com/open-component-model/ocm/pkg/contexts/config/cpi" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/runtime" + "sigs.k8s.io/yaml" +) + +// TYPE is the name of our new configuration object type. +// To be globally unique, it should always end with a +// DNS domain owned by the provider of the new type. +const TYPE = "example.config.acme.org" + +// ExampleConfigSpec is a new type of config specification +// covering our example configuration. +type ExampleConfigSpec struct { + // ObjectVersionedType is the base type providing the type feature + // form config specifications. + runtime.ObjectVersionedType `json:",inline"` + // Config is our example config representation. + helper.Config `json:",inline"` +} + +// NewConfig provides a config object for out helper configuration. +func NewConfig(cfg *helper.Config) cpi.Config { + return &ExampleConfigSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(TYPE), + Config: *cfg, + } +} + +// RepositoryTarget consumes a repository name. +type RepositoryTarget interface { + SetRepository(r string) +} + +// ApplyTo is used to apply the provided configuration settings +// to a dedicated object, which wants to be configured. +func (c *ExampleConfigSpec) ApplyTo(_ cpi.Context, tgt interface{}) error { + + switch t := tgt.(type) { + // if the target is a credentials context + // configure the credentials to be used for the + // described OCI repository. + case credentials.Context: + // determine the consumer id for our target repository- + id, err := oci.GetConsumerIdForRef(c.Repository) + if err != nil { + return errors.Wrapf(err, "invalid consumer") + } + // create the credentials. + creds := c.GetCredentials() + + // configure the targeted credential context with + // the provided credentials (see previous examples). + t.SetCredentialsForConsumer(id, creds) + + // if the target consumes an OCI repository, propagate + // the provided OCI repository ref. + case RepositoryTarget: + t.SetRepository(c.Repository) + + // all other targets are ignored, we don't have + // something to set at these objects. + default: + return cpi.ErrNoContext(TYPE) + } + return nil +} + +func init() { + // register the new config type, so that is can be used + // by the config management to deserialize appropriately + // typed specifications. + cpi.RegisterConfigType(cpi.NewConfigType[*ExampleConfigSpec](TYPE, "this ia config object type based on the example config data.")) +} + +func WriteConfigType(cfg *helper.Config) error { + + // after preparing aout new special config type + // we can feed it into the config management. + + credctx := credentials.DefaultContext() + + // the credential context is based on a config context + // used to configure it. + ctx := credctx.ConfigContext() + + // create our new config based on the actual settings + // and apply it to the config context. + examplecfg := NewConfig(cfg) + ctx.ApplyConfig(examplecfg, "special acme config") + // If you omit the above call, no credentials + // will be found later. + // _, _ = ctx, examplecfg + + // now we should be prepared to get the credentials + id, err := oci.GetConsumerIdForRef(cfg.Repository) + if err != nil { + return errors.Wrapf(err, "cannot get consumer id") + } + fmt.Printf("usage context: %s\n", id) + + // the returned credentials are provided via an interface, which might change its + // content, if the underlying credential source changes. + creds, err := credentials.CredentialsForConsumer(credctx, id, ociidentity.IdentityMatcher) + if err != nil { + return errors.Wrapf(err, "credentials") + } + fmt.Printf("credentials: %s\n", obfuscate(creds)) + + // Because of the new credential type, such a specification can + // now be added to the ocm config, also. + // So, we could use our special tour config file content + // directly as part of the ocm config. + + ocmcfg := configcfg.New() + err = ocmcfg.AddConfig(examplecfg) + + spec, err := yaml.Marshal(ocmcfg) + if err != nil { + return errors.Wrapf(err, "marshal ocm config") + } + + // the result is a minimal ocm configuration file + // just providing our new example configuration. + fmt.Printf("this a typical ocm config file:\n%s\n", string(spec)) + + // above, we added a new kind of target, the RepositoryTarget interface. + // Just by providing an implementation for this interface, we can + // configure such an object using the config management. + target := &SimpleRepositoryTarget{} + + _, err = ctx.ApplyTo(0, target) + if err != nil { + return errors.Wrapf(err, "applying to new target") + } + fmt.Printf("repository for target: %s\n", target.repository) + + // This way any specialized configuration object can be added + // by a user of the OCM library. It can be used to configure + // existing objects or even new object types, even in combination. + // + // What is still required is a way + // to implement new config targets, objects, which want + // to be configured and which autoconfigure themselves when + // used. Our simple repository target is just an example + // for some kind of ad-hoc configuration. + // This is shown in the next example. + return nil +} + +type SimpleRepositoryTarget struct { + repository string +} + +var _ RepositoryTarget = (*SimpleRepositoryTarget)(nil) + +func (t *SimpleRepositoryTarget) SetRepository(repo string) { + t.repository = repo +} diff --git a/examples/lib/tour/04-working-with-config/05-write-config-consumer.go b/examples/lib/tour/04-working-with-config/05-write-config-consumer.go new file mode 100644 index 0000000000..af95a8d29f --- /dev/null +++ b/examples/lib/tour/04-working-with-config/05-write-config-consumer.go @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "sync" + + "github.com/open-component-model/ocm/examples/lib/helper" + "github.com/open-component-model/ocm/pkg/contexts/config/cpi" + "github.com/open-component-model/ocm/pkg/contexts/credentials" + ociidentity "github.com/open-component-model/ocm/pkg/contexts/credentials/builtin/oci/identity" + "github.com/open-component-model/ocm/pkg/contexts/oci" + "github.com/open-component-model/ocm/pkg/errors" +) + +// we already have our new acme.org config object type, +// now we want to provide an object, which configures +// itself when used. + +// RepositoryProvider should be an object, which is +// able to provide an OCI repository reference. +// It has a setter and a getter (the setter is +// provided by our ad-hoc SimpleRepositoryTarget). +type RepositoryProvider struct { + lock sync.Mutex + // updater is a utility, which ia able to + // configure an object basesd a a managed configuration + // watermark. It remembers which config objects from the + // config queue are already applies, and replays + // the config objects applied to the config context + // after the last update. + updater cpi.Updater + SimpleRepositoryTarget +} + +func NewRepositoryProvider(ctx cpi.ContextProvider) *RepositoryProvider { + p := &RepositoryProvider{} + // To do its work, the updater needs a connection to + // the config context to use and the object, which should be + // configured. + p.updater = cpi.NewUpdater(ctx.ConfigContext(), p) + return p +} + +// GetRepository returns a repository ref. +func (p *RepositoryProvider) GetRepository() (string, error) { + p.lock.Lock() + defer p.lock.Unlock() + + // the first step for methods of configurable objects + // dependent on potential configuration is always + // to update itself using the embedded updater. + // Please remember, the config management reverses the + // request direction. Applying a config object to + // the config context does not configure dependent objects, + // it just manages a config queue, which is used by potential + // configuration targets to configure themselves. + // The reason for this is to avoid references from the + // management to managed objects. This would prohibit + // the garbage collection of all configurable objects. + err := p.updater.Update() + if err != nil { + return "", err + } + // now, we can do our regular function, aka + // providing a repository ref. + return p.repository, nil +} + +func WriteConfigTargets(cfg *helper.Config) error { + credctx := credentials.DefaultContext() + + // after defining or repository provider type + // we can now use it. + prov := NewRepositoryProvider(credctx) + + // If we ask now for a repository we will get the empty + // answer. + repo, err := prov.GetRepository() + if err != nil { + errors.Wrapf(err, "get repo") + } + if repo != "" { + return fmt.Errorf("Oops, found repository %q", repo) + } + + // Now, we apply our config from the last example. + ctx := credctx.ConfigContext() + examplecfg := NewConfig(cfg) + err = ctx.ApplyConfig(examplecfg, "special acme config") + if err != nil { + errors.Wrapf(err, "apply config") + } + + // asking for a repository now will return the configured + // ref. + repo, err = prov.GetRepository() + if err != nil { + errors.Wrapf(err, "get repo") + } + if repo == "" { + return fmt.Errorf("no repository provided") + } + fmt.Printf("using repository: %s\n", repo) + + // now, we should also be prepared to get the credentials, + // our config object configures the provider as well as + // the credential context. + id, err := oci.GetConsumerIdForRef(repo) + if err != nil { + return errors.Wrapf(err, "cannot get consumer id") + } + fmt.Printf("usage context: %s\n", id) + + creds, err := credentials.CredentialsForConsumer(credctx, id, ociidentity.IdentityMatcher) + if err != nil { + return errors.Wrapf(err, "credentials") + } + fmt.Printf("credentials: %s\n", obfuscate(creds)) + + return nil +} diff --git a/examples/lib/tour/04-working-with-config/README.md b/examples/lib/tour/04-working-with-config/README.md new file mode 100644 index 0000000000..48397d7179 --- /dev/null +++ b/examples/lib/tour/04-working-with-config/README.md @@ -0,0 +1,23 @@ +# Working with Configurations + +This tour illustrates the basic configuration management +included in the OCM library. The library provides +an extensible framework to bring together configuration settings +and configurable objects. + +It covers five basic scenarios: +- [`basic`](01-basic-config-management.go) Basic configuration management illustarting the configuration of credentials. +- [`generic`](02-handle-arbitrary-config.go) Handling of arbitrary configuration. +- [`ocm`](03-using-ocm-config.go) Central configuration +- [`provide`](04-write-config-type.go) Providing new config object types +- [`consume`](05-write-config-consumer.go) Preparing objects to be configured by the config management + + +You can just call the main program with some config file option (`--config `) and the name of the scenario. +The config file should have the following content: + +```yaml +repository: ghcr.io/mandelsoft/ocm +username: +password: +``` \ No newline at end of file diff --git a/examples/lib/tour/04-working-with-config/common.go b/examples/lib/tour/04-working-with-config/common.go new file mode 100644 index 0000000000..4957898867 --- /dev/null +++ b/examples/lib/tour/04-working-with-config/common.go @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "github.com/open-component-model/ocm/pkg/contexts/credentials" +) + +func obfuscate(creds credentials.Credentials) string { + if creds == nil { + return "no credentials" + } + props := creds.Properties() + if pw, ok := props[credentials.ATTR_PASSWORD]; ok { + if len(pw) > 5 { + pw = pw[:5] + "***" + } else { + pw = "***" + } + props = props.Copy() + props[credentials.ATTR_PASSWORD] = pw + } + return props.String() +} diff --git a/examples/lib/tour/04-working-with-config/main.go b/examples/lib/tour/04-working-with-config/main.go new file mode 100644 index 0000000000..e25482bcd5 --- /dev/null +++ b/examples/lib/tour/04-working-with-config/main.go @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/open-component-model/ocm/examples/lib/helper" +) + +// CFG is the path to the file containing the credentials +var CFG = "../examples/lib/cred.yaml" + +var current_version string + +func init() { + data, err := os.ReadFile("VERSION") + if err != nil { + panic("VERSION not found") + } + current_version = strings.TrimSpace(string(data)) +} + +func main() { + arg := 1 + if len(os.Args) > 1 { + if os.Args[1] == "--config" { + if len(os.Args) > 2 { + CFG = os.Args[2] + arg = 3 + } else { + fmt.Fprintf(os.Stderr, "error: config file missing\n") + os.Exit(1) + } + } + } + cfg, err := helper.ReadConfig(CFG) + if err == nil { + cmd := "basic" + + if len(os.Args) > arg { + cmd = os.Args[arg] + } + switch cmd { + case "basic": + err = BasicConfigurationHandling(cfg) + case "generic": + err = HandleArbitraryConfiguration(cfg) + case "ocm": + err = HandleOCMConfig(cfg) + case "provide": + err = WriteConfigType(cfg) + case "consume": + err = WriteConfigTargets(cfg) + default: + err = fmt.Errorf("unknown example %q", cmd) + } + } + + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + os.Exit(1) + } +} diff --git a/examples/lib/tour/04-working-with-config/resources/ocmconfig b/examples/lib/tour/04-working-with-config/resources/ocmconfig new file mode 100644 index 0000000000..6a663d533c --- /dev/null +++ b/examples/lib/tour/04-working-with-config/resources/ocmconfig @@ -0,0 +1,8 @@ +type: generic.config.ocm.software +configurations: +- type: credentials.config.ocm.software + repositories: + - repository: + type: DockerConfig + dockerConfigFile: ~/.docker/config.json + propagateConsumerIdentity: true diff --git a/examples/lib/tour/05-transporting-component-versions/README.md b/examples/lib/tour/05-transporting-component-versions/README.md new file mode 100644 index 0000000000..a5b494a501 --- /dev/null +++ b/examples/lib/tour/05-transporting-component-versions/README.md @@ -0,0 +1,38 @@ +# Transporting Component Versions + +This [tour](example.go) illustrates the basic support for +transporting content from one environment into another. + + +You can just call the main program with some config file option (`--config `). +The config file should have the following content: + +```yaml +repository: ghcr.io/mandelsoft/ocm +targetRepository: + type: CommonTransportFormat + filePath: /tmp/example05.target.ctf + fileFormat: directory + accessMode: 2 +username: +password: +``` + +Any supported kind of target repository can be specified by using its +specification type. An OCI regisztry target would look like this: + +```yaml +repository: ghcr.io/mandelsoft/ocm +username: +password: +targetRepository: + type: OCIRegistry + baseUrl: ghcr.io/mandelsoft/targetocm +ocmConfig: +``` + +The actual version of the example just works with the filesystem +target, because it is not possible to specify credentials for the +target repository in this simple config file. But, if you specific an [OCM config file](../04-working-with-config/README.md) you can +add more credential settings to make target repositories possible +requiring credentials. \ No newline at end of file diff --git a/examples/lib/tour/05-transporting-component-versions/example.go b/examples/lib/tour/05-transporting-component-versions/example.go index 9ba4943cf1..e22b66d08e 100644 --- a/examples/lib/tour/05-transporting-component-versions/example.go +++ b/examples/lib/tour/05-transporting-component-versions/example.go @@ -14,12 +14,32 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ocireg" "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer" "github.com/open-component-model/ocm/pkg/contexts/ocm/transfer/transferhandler/standard" + "github.com/open-component-model/ocm/pkg/contexts/ocm/utils" "github.com/open-component-model/ocm/pkg/errors" ) +func ReadConfiguration(ctx ocm.Context, cfg *helper.Config) error { + if cfg.OCMConfig != "" { + fmt.Printf("*** applying config from %s\n", cfg.OCMConfig) + + _, err := utils.Configure(ctx, cfg.OCMConfig) + if err != nil { + return errors.Wrapf(err, "error in ocm config %s", cfg.OCMConfig) + } + } + return nil +} + func TransportingComponentVersions(cfg *helper.Config) error { ctx := ocm.DefaultContext() + // Configure context with optional ocm config. + // See OCM config scenario in tour 04. + err := ReadConfiguration(ctx, cfg) + if err != nil { + return err + } + // the context acts as factory for various model types based on // specification descriptor serialization formats in YAML or JSON. // Access method specifications and repository specification are diff --git a/examples/lib/tour/06-signing-component-versions/example-a.go b/examples/lib/tour/06-signing-component-versions/01-basic-signing.go similarity index 95% rename from examples/lib/tour/06-signing-component-versions/example-a.go rename to examples/lib/tour/06-signing-component-versions/01-basic-signing.go index 82aaf4a5dd..0154795279 100644 --- a/examples/lib/tour/06-signing-component-versions/example-a.go +++ b/examples/lib/tour/06-signing-component-versions/01-basic-signing.go @@ -19,6 +19,13 @@ func SigningComponentVersions(cfg *helper.Config) error { ctx := ocm.DefaultContext() + // Configure context with optional ocm config. + // See OCM config scenario in tour 04. + err := ReadConfiguration(ctx, cfg) + if err != nil { + return err + } + // siginfo := signingattr.Get(ctx) // to sign a component version we need a private key. diff --git a/examples/lib/tour/06-signing-component-versions/example-b.go b/examples/lib/tour/06-signing-component-versions/02-using-context-settings.go similarity index 93% rename from examples/lib/tour/06-signing-component-versions/example-b.go rename to examples/lib/tour/06-signing-component-versions/02-using-context-settings.go index 107ec85bf1..9656119a5e 100644 --- a/examples/lib/tour/06-signing-component-versions/example-b.go +++ b/examples/lib/tour/06-signing-component-versions/02-using-context-settings.go @@ -49,12 +49,19 @@ func prepareComponentInRepo(ctx ocm.Context, cfg *helper.Config) error { func SigningComponentVersionInRepo(cfg *helper.Config) error { ctx := ocm.DefaultContext() - err := prepareComponentInRepo(ctx, cfg) + // Configure context with optional ocm config. + // See OCM config scenario in tour 04. + err := ReadConfiguration(ctx, cfg) + if err != nil { + return err + } + + err = prepareComponentInRepo(ctx, cfg) if err != nil { return errors.Wrapf(err, "cannot prepare component version in target repo") } - // evrey context features a signing registry, which provides available + // every context features a signing registry, which provides available // signers and hashers, but also keys for various purposes. // It is always asked, if a key is required for a purpose, which is // not explicitly given to a signing/verification call. diff --git a/examples/lib/tour/06-signing-component-versions/README.md b/examples/lib/tour/06-signing-component-versions/README.md new file mode 100644 index 0000000000..0fa356f6ed --- /dev/null +++ b/examples/lib/tour/06-signing-component-versions/README.md @@ -0,0 +1,26 @@ +# Signing Component Versions + +This tour illustrates the basic functionality to +sign and verify signatures. + +It covers two basic scenarios: +- [`sign`](01-basic-signing.go) Create, Sign, Transport and Verify a component version. +- [`repo`](02-using-context-settings.go) Using context settings to configure signing and verification in target repo. + +You can just call the main program with some config file option (`--config `) and the name of the scenario. +The config file should have the following content: + +```yaml +targetRepository: + type: CommonTransportFormat + filePath: /tmp/example06.target.ctf + fileFormat: directory + accessMode: 2 +ocmConfig: +``` + +The actual version of the example just works with the filesystem +target, because it is not possible to specify credentials for the +target repository in this simple config file. But, if you specific an [OCM config file](../04-working-with-config/README.md) you can +add more credential settings to make target repositories possible +requiring credentials. diff --git a/examples/lib/tour/06-signing-component-versions/common.go b/examples/lib/tour/06-signing-component-versions/common.go index ee36877893..0bb09b78cf 100644 --- a/examples/lib/tour/06-signing-component-versions/common.go +++ b/examples/lib/tour/06-signing-component-versions/common.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/open-component-model/ocm/examples/lib/helper" "github.com/open-component-model/ocm/pkg/blobaccess" "github.com/open-component-model/ocm/pkg/common" "github.com/open-component-model/ocm/pkg/contexts/ocm" @@ -18,6 +19,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/dockermultiblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactblob/textblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" + "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/finalizer" "github.com/open-component-model/ocm/pkg/mime" @@ -300,3 +302,15 @@ func listVersions(repo ocm.Repository, list ...string) error { } return nil } + +func ReadConfiguration(ctx ocm.Context, cfg *helper.Config) error { + if cfg.OCMConfig != "" { + fmt.Printf("*** applying config from %s\n", cfg.OCMConfig) + + _, err := utils.Configure(ctx, cfg.OCMConfig) + if err != nil { + return errors.Wrapf(err, "error in ocm config %s", cfg.OCMConfig) + } + } + return nil +} diff --git a/examples/lib/tour/06-signing-component-versions/main.go b/examples/lib/tour/06-signing-component-versions/main.go index 905a6921d6..e2b91e6d83 100644 --- a/examples/lib/tour/06-signing-component-versions/main.go +++ b/examples/lib/tour/06-signing-component-versions/main.go @@ -46,7 +46,7 @@ func main() { cmd = os.Args[arg] } switch cmd { - case "basic": + case "sign": err = SigningComponentVersions(cfg) case "repo": err = SigningComponentVersionInRepo(cfg) diff --git a/examples/lib/tour/README.md b/examples/lib/tour/README.md new file mode 100644 index 0000000000..58ac1d1da1 --- /dev/null +++ b/examples/lib/tour/README.md @@ -0,0 +1,15 @@ +# A Tour through Usage Scenarios of the OCM Library + +This tour guides you from a very basic usage of the +OCM repository API to more complex scenarios +handling credentials and configurations. + +So far, it does not cover the implementation +of extension points of the library. + +- [Basic Usage of OCM Repositories](01-getting-started/README.md) +- [Composing Component Versions](02-composing-a-component-version/README.md) +- [Working with Credentials](03-working-with-credentials/README.md) +- [Working with Configuration](04-working-with-config/README.md) +- [Transporting Component Versions](05-transporting-component-versions/README.md) +- [Signing Component Versions](06-signing-component-versions/README.md) \ No newline at end of file diff --git a/pkg/blobaccess/standard.go b/pkg/blobaccess/standard.go index 857d6e5606..1283933458 100644 --- a/pkg/blobaccess/standard.go +++ b/pkg/blobaccess/standard.go @@ -9,6 +9,7 @@ import ( "github.com/open-component-model/ocm/pkg/blobaccess/bpi" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/finalizer" "github.com/open-component-model/ocm/pkg/mime" "github.com/open-component-model/ocm/pkg/refmgmt" "github.com/open-component-model/ocm/pkg/utils" @@ -217,6 +218,7 @@ type AnnotatedBlobAccess[T DataAccess] interface { type annotatedBlobAccessView[T DataAccess] struct { _blobAccess + id finalizer.ObjectIdentity annotation T } @@ -230,6 +232,7 @@ func (a *annotatedBlobAccessView[T]) Dup() (BlobAccess, error) { return nil, err } return &annotatedBlobAccessView[T]{ + id: finalizer.NewObjectIdentity(a.id.String()), _blobAccess: b, annotation: a.annotation, }, nil @@ -247,6 +250,7 @@ func ForDataAccess[T DataAccess](digest digest.Digest, size int64, mimeType stri a := bpi.BaseAccessForDataAccessAndMeta(mimeType, access, digest, size) return &annotatedBlobAccessView[T]{ + id: finalizer.NewObjectIdentity("annotatedBlobAccess"), _blobAccess: bpi.NewBlobAccessForBase(a), annotation: access, } diff --git a/pkg/contexts/config/internal/config.go b/pkg/contexts/config/internal/config.go index 9ec143cb66..b65e3bc599 100644 --- a/pkg/contexts/config/internal/config.go +++ b/pkg/contexts/config/internal/config.go @@ -7,6 +7,7 @@ package internal import ( "fmt" + "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/runtime" ) @@ -37,3 +38,17 @@ func (c *ConfigurationList) AddConfig(cfg Config) error { return nil } + +func (c *ConfigurationList) AddConfigData(ctx Context, data []byte) error { + cfg, err := ctx.GetConfigForData(data, nil) + if err != nil { + return errors.Wrapf(err, "invalid config specification") + } + g, err := ToGenericConfig(cfg) + if err != nil { + return fmt.Errorf("unable to convert config to generic: %w", err) + } + + c.Configurations = append(c.Configurations, g) + return nil +} diff --git a/pkg/contexts/credentials/repositories/dockerconfig/type.go b/pkg/contexts/credentials/repositories/dockerconfig/type.go index 30acfd93c9..bba00f371b 100644 --- a/pkg/contexts/credentials/repositories/dockerconfig/type.go +++ b/pkg/contexts/credentials/repositories/dockerconfig/type.go @@ -41,6 +41,9 @@ func NewRepositorySpec(path string, prop ...bool) *RepositorySpec { for _, e := range prop { p = p || e } + if path == "" { + path = "~/.docker/config.json" + } return &RepositorySpec{ ObjectVersionedType: runtime.NewVersionedTypedObject(Type), DockerConfigFile: path, diff --git a/pkg/contexts/oci/cpi/support/artifact.go b/pkg/contexts/oci/cpi/support/artifact.go index 016bfe213b..131f861600 100644 --- a/pkg/contexts/oci/cpi/support/artifact.go +++ b/pkg/contexts/oci/cpi/support/artifact.go @@ -38,7 +38,6 @@ func NewArtifactForBlob(container NamespaceAccessImpl, blob blobaccess.BlobAcces if err != nil { return nil, err } - return newArtifact(container, state, closer...) } diff --git a/pkg/contexts/ocm/accessmethods/ociartifact/method.go b/pkg/contexts/ocm/accessmethods/ociartifact/method.go index d669c4a034..5e937f7cf3 100644 --- a/pkg/contexts/ocm/accessmethods/ociartifact/method.go +++ b/pkg/contexts/ocm/accessmethods/ociartifact/method.go @@ -100,12 +100,15 @@ func (a *AccessSpec) GetReferenceHint(cv accspeccpi.ComponentVersionAccess) stri if err != nil { return "" } - prefix := ocmcpi.RepositoryPrefix(cv.Repository().GetSpecification()) hint := ref.Repository - if strings.HasPrefix(hint, prefix+grammar.RepositorySeparator) { - // try to keep hint identical, even across intermediate - // artifact globalizations - hint = hint[len(prefix)+1:] + r := cv.Repository() + if r != nil { + prefix := ocmcpi.RepositoryPrefix(cv.Repository().GetSpecification()) + if strings.HasPrefix(hint, prefix+grammar.RepositorySeparator) { + // try to keep hint identical, even across intermediate + // artifact globalizations + hint = hint[len(prefix)+1:] + } } if ref.Tag != nil { hint += grammar.TagSeparator + *ref.Tag diff --git a/pkg/contexts/ocm/cpi/dummy.go b/pkg/contexts/ocm/cpi/dummy.go index 8282a5fbce..099bf84ab4 100644 --- a/pkg/contexts/ocm/cpi/dummy.go +++ b/pkg/contexts/ocm/cpi/dummy.go @@ -36,39 +36,39 @@ func (d *DummyComponentVersionAccess) Dup() (ComponentVersionAccess, error) { } func (d *DummyComponentVersionAccess) GetProvider() *compdesc.Provider { - panic("implement me") + return nil } func (d *DummyComponentVersionAccess) SetProvider(p *compdesc.Provider) error { - panic("implement me") + return errors.ErrNotSupported() } func (d *DummyComponentVersionAccess) AdjustSourceAccess(meta *internal.SourceMeta, acc compdesc.AccessSpec) error { - panic("implement me") + return errors.ErrNotSupported() } func (c *DummyComponentVersionAccess) Repository() Repository { - panic("implement me") + return nil } func (d *DummyComponentVersionAccess) GetName() string { - panic("implement me") + return "" } func (d *DummyComponentVersionAccess) GetVersion() string { - panic("implement me") + return "" } func (d *DummyComponentVersionAccess) GetDescriptor() *compdesc.ComponentDescriptor { - panic("implement me") + return nil } func (d *DummyComponentVersionAccess) GetResources() []ResourceAccess { return nil } -func (d *DummyComponentVersionAccess) GetResource(meta metav1.Identity) (ResourceAccess, error) { - return nil, errors.ErrNotFound("resource", meta.String()) +func (d *DummyComponentVersionAccess) GetResource(id metav1.Identity) (ResourceAccess, error) { + return nil, errors.ErrNotFound("resource", id.String()) } func (d *DummyComponentVersionAccess) GetResourceIndex(metav1.Identity) int { @@ -84,11 +84,11 @@ func (d *DummyComponentVersionAccess) GetResourcesByName(name string, selectors } func (d *DummyComponentVersionAccess) GetSources() []SourceAccess { - panic("implement me") + return nil } -func (d *DummyComponentVersionAccess) GetSource(meta metav1.Identity) (SourceAccess, error) { - panic("implement me") +func (d *DummyComponentVersionAccess) GetSource(id metav1.Identity) (SourceAccess, error) { + return nil, errors.ErrNotFound(KIND_SOURCE, id.String()) } func (d *DummyComponentVersionAccess) GetSourceIndex(metav1.Identity) int { @@ -158,11 +158,11 @@ func (d *DummyComponentVersionAccess) SetSource(meta *SourceMeta, spec compdesc. } func (d *DummyComponentVersionAccess) SetSourceByAccess(art SourceAccess) error { - panic("implement me") + return errors.ErrNotSupported() } func (d *DummyComponentVersionAccess) SetReference(ref *ComponentReference) error { - panic("implement me") + return errors.ErrNotSupported() } func (d *DummyComponentVersionAccess) DiscardChanges() { diff --git a/pkg/contexts/ocm/cpi/modopts.go b/pkg/contexts/ocm/cpi/modopts.go index cc4141f9c4..5b5773f4df 100644 --- a/pkg/contexts/ocm/cpi/modopts.go +++ b/pkg/contexts/ocm/cpi/modopts.go @@ -20,10 +20,24 @@ type ( BlobUploadOption = internal.BlobUploadOption BlobUploadOptions = internal.BlobUploadOptions + + AddVersionOption = internal.AddVersionOption + AddVersionOptions = internal.AddVersionOptions ) //////////////////////////////////////////////////////////////////////////////// +func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { + return internal.NewAddVersionOptions(list...) +} + +// Overwrite enabled the overwrite mode for adding a component version. +func Overwrite(flag ...bool) AddVersionOption { + return internal.Overwrite(flag...) +} + +//////////////////////////////////////////////////////////////////////////////// + func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { return internal.NewBlobModificationOptions(list...) } diff --git a/pkg/contexts/ocm/cpi/repocpi/README.md b/pkg/contexts/ocm/cpi/repocpi/README.md new file mode 100644 index 0000000000..c97d5c00e9 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/README.md @@ -0,0 +1,77 @@ +# Context Programming Interface for Repositories + +Package repocpi contains the implementation support + for repository backends. It offers three methods + to create component version, component and repository + objects based on three simple implementation interfaces. + + The basic provisioning model is layered: + + ![Implamentation Layers](ocmimpllayers.png) + + - on layer 1 there is the *user facing API* defined + in package [github.com/open-component-model/ocm/pkg/contexts/ocm]. + + - on layer 2 (this package) there is a backend agnostic + implementation of standard functionality based on layer 3. + This is divided into two parts + + a) the *view* objects provided by the `Dup()` calls of the layer 1 API. + All dups are internally based on a single base object. + These objects are called *bridge*. They act as base object + for the views and as abstraction for the implementation objects + providing *generic* implementations potentially based on + the implementation functionality. + (see bridge design pattern https://refactoring.guru/design-patterns/bridge) + + b) the *bridge* object as base for all dup views is used to implement some + common functionality like the view management. The bridge object + is closed, when the last view disappears. + This bridge object then calls the final + storage backend implementation interface. + + - the storage backend implementations based on the implementation + interfaces provided by layer 2. + + The implementation interfaces and the functions to create API objects are: + + - interface [ComponentVersionAccessImpl] is used to create an ocm.ComponentVersionAccess object + using the function [NewComponentVersionAccess]. + - interface [ComponentAccessImpl] is used to create an ocm.ComponentAccess object + using the function [NewComponentAccess]. + - interface [RepositoryImpl] is used to create an ocm.ComponentAccess object + using the function [NewRepository]. + + Component version implementations provide basic access to component versions + and their descriptors. They keep a reference to component implementations, which are + again based on repository implementations. The task of repository implementations is + to provide component objects. Their implementations are responsible to provide + component version objects. + +## Simplified Respository Implementation Interface + Besides this basic implementation interfaces with separated objects for a + repository, component and component version, there is support for a simplified + implementation interface (`StorageBackendImpl`). This is a single interface + bundling all required functionality to implement the objects for the three + concerned elements. With `NewStorageBackend` it is possible to instantiate + a new kind of repository based on this single interface. The required + objects for components and component versions are generically provided + based on the methods provided by this interface. + +## Comparison of Implementation Models + +The simplified implementation model does not provide access to the +implementation objects for components and component versions. +Therefore, it is not possible to keep state for those elements. + +Storage Backend Implementations requiring such state, like the OCI +implementation based on the OCI abstraction provided by the OCI +context, therefore use dedicated implementations for repository, +component and component version objects. This model provides +complete control over the lifecycle of those elements. + +If a storage backend implementation is stateless or just keeps +state at the repository level, the simplified implementation model +can be chosen. + + diff --git a/pkg/contexts/ocm/cpi/repocpi/backend.go b/pkg/contexts/ocm/cpi/repocpi/backend.go new file mode 100644 index 0000000000..cbfeabee7b --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/backend.go @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "io" + "sync/atomic" + + "github.com/open-component-model/ocm/pkg/common" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/utils" +) + +// StorageBackendImpl is an interface which can be implemented +// to provide a complete repository view with repository, component +// and component version objects, which are generically implemented +// based on the methods of this interface. +// +// A repository interface based on this implementation interface can be +// created using the function NewStorageBackend. +type StorageBackendImpl interface { + // repository related methods. + + io.Closer + GetContext() cpi.Context + GetSpecification() cpi.RepositorySpec + IsReadOnly() bool + + ComponentLister() cpi.ComponentLister + HasComponent(name string) (bool, error) + HasComponentVersion(key common.NameVersion) (bool, error) + + // component related methods. + + ListVersions(comp string) ([]string, error) + HasVersion(vers string) (bool, error) + + // version related methods. + + GetDescriptor(key common.NameVersion) (*compdesc.ComponentDescriptor, error) + SetDescriptor(key common.NameVersion, descriptor *compdesc.ComponentDescriptor) error + AccessMethod(key common.NameVersion, acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) + GetInexpensiveContentVersionIdentity(key common.NameVersion, acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) string + GetStorageContext(key common.NameVersion) cpi.StorageContext + GetBlob(key common.NameVersion, name string) (cpi.DataAccess, error) + AddBlob(key common.NameVersion, blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) +} + +type storageBackendRepository struct { + bridge RepositoryBridge + closed atomic.Bool + noref cpi.Repository + kind string + impl StorageBackendImpl +} + +var _ RepositoryImpl = (*storageBackendRepository)(nil) + +// NewStorageBackend provides a complete repository view +// with repository, component and component version objects +// based on the implementation of the sole interface StorageBackendImpl. +// No further implementations are required besides a dedicated +// specification object, the dependent object +// types are generically provided based on the methods of this +// interface. +// The kind parameter is used to denote the kind of repository +// in ids and log messages. +func NewStorageBackend(kind string, impl StorageBackendImpl) cpi.Repository { + backend := &storageBackendRepository{ + impl: impl, + kind: kind, + } + return NewRepository(backend, kind) +} + +func (s *storageBackendRepository) SetBridge(bridge RepositoryBridge) { + s.bridge = bridge + s.noref = NewNoneRefRepositoryView(bridge) +} + +func (s *storageBackendRepository) Close() error { + if s.closed.Swap(true) { + return ErrClosed + } + return s.impl.Close() +} + +func (s *storageBackendRepository) GetContext() cpi.Context { + return s.impl.GetContext() +} + +func (s *storageBackendRepository) GetSpecification() cpi.RepositorySpec { + return s.impl.GetSpecification() +} + +func (s *storageBackendRepository) ComponentLister() cpi.ComponentLister { + return s.impl.ComponentLister() +} + +func (s *storageBackendRepository) ExistsComponentVersion(name string, version string) (bool, error) { + return s.impl.HasComponentVersion(common.NewNameVersion(name, version)) +} + +func (s *storageBackendRepository) LookupComponent(name string) (*ComponentAccessInfo, error) { + if ok, err := s.impl.HasComponent(name); !ok || err != nil { + return nil, err + } + impl := &storageBackendComponent{ + repo: s, + name: name, + } + return &ComponentAccessInfo{ + Impl: impl, + Kind: s.kind + " component", + Main: true, + }, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type storageBackendComponent struct { + bridge ComponentAccessBridge + repo *storageBackendRepository + name string +} + +var _ ComponentAccessImpl = (*storageBackendComponent)(nil) + +func (s *storageBackendComponent) SetBridge(bridge ComponentAccessBridge) { + s.bridge = bridge +} + +func (s *storageBackendComponent) GetParentBridge() RepositoryViewManager { + return s.repo.bridge +} + +func (s *storageBackendComponent) Close() error { + return nil +} + +func (s *storageBackendComponent) GetContext() cpi.Context { + return s.repo.impl.GetContext() +} + +func (s *storageBackendComponent) GetName() string { + return s.name +} + +func (s *storageBackendComponent) IsReadOnly() bool { + return s.repo.impl.IsReadOnly() +} + +func (s *storageBackendComponent) ListVersions() ([]string, error) { + return s.repo.impl.ListVersions(s.name) +} + +func (s *storageBackendComponent) HasVersion(vers string) (bool, error) { + return s.repo.impl.HasVersion(s.name) +} + +func (s *storageBackendComponent) LookupVersion(version string) (*ComponentVersionAccessInfo, error) { + if ok, err := s.repo.impl.HasComponentVersion(common.NewNameVersion(s.name, version)); !ok || err != nil { + return nil, err + } + + name := common.NewNameVersion(s.name, version) + d, err := s.repo.impl.GetDescriptor(name) + if err != nil { + return nil, err + } + + impl := &storageBackendComponentVersion{ + comp: s, + name: name, + descriptor: d, + } + return &ComponentVersionAccessInfo{ + Impl: impl, + Lazy: true, + Persistent: true, + }, nil +} + +func (s *storageBackendComponent) NewVersion(version string, overwrite ...bool) (*ComponentVersionAccessInfo, error) { + ok, err := s.repo.impl.HasComponentVersion(common.NewNameVersion(s.name, version)) + if err != nil { + return nil, err + } + if ok && !utils.Optional(overwrite...) { + return nil, errors.ErrAlreadyExists(cpi.KIND_COMPONENTVERSION, s.name+"/"+version) + } + + name := common.NewNameVersion(s.name, version) + d := compdesc.New(s.name, version) + + impl := &storageBackendComponentVersion{ + comp: s, + name: name, + descriptor: d, + } + return &ComponentVersionAccessInfo{ + Impl: impl, + Lazy: true, + Persistent: false, + }, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type storageBackendComponentVersion struct { + bridge ComponentVersionAccessBridge + comp *storageBackendComponent + name common.NameVersion + descriptor *compdesc.ComponentDescriptor +} + +var _ ComponentVersionAccessImpl = (*storageBackendComponentVersion)(nil) + +func (s *storageBackendComponentVersion) Close() error { + return nil +} + +func (s *storageBackendComponentVersion) GetContext() cpi.Context { + return s.comp.repo.impl.GetContext() +} + +func (s *storageBackendComponentVersion) SetBridge(bridge ComponentVersionAccessBridge) { + s.bridge = bridge +} + +func (s *storageBackendComponentVersion) GetParentBridge() ComponentAccessBridge { + return s.comp.bridge +} + +func (s *storageBackendComponentVersion) Repository() cpi.Repository { + return s.comp.repo.noref +} + +func (s *storageBackendComponentVersion) IsReadOnly() bool { + return s.comp.repo.impl.IsReadOnly() +} + +func (s *storageBackendComponentVersion) GetDescriptor() *compdesc.ComponentDescriptor { + d, err := s.comp.repo.impl.GetDescriptor(s.name) + if err != nil { + return nil // TODO: handler error + } + return d +} + +func (s *storageBackendComponentVersion) SetDescriptor(descriptor *compdesc.ComponentDescriptor) error { + err := s.comp.repo.impl.SetDescriptor(s.name, descriptor) + if err != nil { + return err + } + s.descriptor = descriptor + return nil +} + +func (s *storageBackendComponentVersion) AccessMethod(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { + return s.comp.repo.impl.AccessMethod(s.name, acc, cv) +} + +func (s *storageBackendComponentVersion) GetInexpensiveContentVersionIdentity(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) string { + return s.comp.repo.impl.GetInexpensiveContentVersionIdentity(s.name, acc, cv) +} + +func (s storageBackendComponentVersion) GetStorageContext() cpi.StorageContext { + return s.comp.repo.impl.GetStorageContext(s.name) +} + +func (s storageBackendComponentVersion) GetBlob(name string) (cpi.DataAccess, error) { + return s.comp.repo.impl.GetBlob(s.name, name) +} + +func (s storageBackendComponentVersion) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { + return s.comp.repo.impl.AddBlob(s.name, blob, refName, global) +} diff --git a/pkg/contexts/ocm/cpi/repocpi/blobcache.go b/pkg/contexts/ocm/cpi/repocpi/blobcache.go new file mode 100644 index 0000000000..b324c97bd1 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/blobcache.go @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "sync" + + "github.com/open-component-model/ocm/pkg/blobaccess" + "github.com/open-component-model/ocm/pkg/errors" +) + +type ( + BlobCacheEntry = blobaccess.BlobAccess + BlobCacheKey = interface{} +) + +type BlobCache interface { + // AddBlobFor stores blobs for added blobs not yet accessible + // by generated access method until version is finally added. + AddBlobFor(acc BlobCacheKey, blob BlobCacheEntry) error + + // GetBlobFor retrieves the original blob access for + // a given access specification. + GetBlobFor(acc BlobCacheKey) BlobCacheEntry + + RemoveBlobFor(acc BlobCacheKey) + Clear() error +} + +type blobCache struct { + lock sync.Mutex + blobcache map[BlobCacheKey]BlobCacheEntry +} + +func NewBlobCache() BlobCache { + return &blobCache{ + blobcache: map[BlobCacheKey]BlobCacheEntry{}, + } +} + +func (c *blobCache) RemoveBlobFor(acc BlobCacheKey) { + c.lock.Lock() + defer c.lock.Unlock() + if b := c.blobcache[acc]; b != nil { + b.Close() + delete(c.blobcache, acc) + } +} + +func (c *blobCache) AddBlobFor(acc BlobCacheKey, blob BlobCacheEntry) error { + if s, ok := acc.(string); ok && s == "" { + return errors.ErrInvalid("blob key") + } + c.lock.Lock() + defer c.lock.Unlock() + + if c.blobcache[acc] == nil { + l, err := blob.Dup() + if err != nil { + return err + } + c.blobcache[acc] = l + } + return nil +} + +func (c *blobCache) GetBlobFor(acc BlobCacheKey) BlobCacheEntry { + c.lock.Lock() + defer c.lock.Unlock() + + return c.blobcache[acc] +} + +func (c *blobCache) Clear() error { + list := errors.ErrList() + c.lock.Lock() + defer c.lock.Unlock() + for _, b := range c.blobcache { + list.Add(b.Close()) + } + c.blobcache = map[BlobCacheKey]BlobCacheEntry{} + return list.Result() +} diff --git a/pkg/contexts/ocm/cpi/repocpi/bridge_c.go b/pkg/contexts/ocm/cpi/repocpi/bridge_c.go new file mode 100644 index 0000000000..c552832888 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/bridge_c.go @@ -0,0 +1,185 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "io" + + "github.com/open-component-model/ocm/pkg/blobaccess" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/optionutils" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" +) + +type ComponentVersionAccessInfo struct { + Impl ComponentVersionAccessImpl + Lazy bool + Persistent bool +} + +// ComponentAccessImpl is the provider implementation +// interface for component versions. +type ComponentAccessImpl interface { + SetBridge(bridge ComponentAccessBridge) + GetParentBridge() RepositoryViewManager + + GetContext() cpi.Context + GetName() string + IsReadOnly() bool + + ListVersions() ([]string, error) + HasVersion(vers string) (bool, error) + LookupVersion(version string) (*ComponentVersionAccessInfo, error) + NewVersion(version string, overrides ...bool) (*ComponentVersionAccessInfo, error) + + io.Closer +} + +type _componentAccessBridgeBase = resource.ResourceImplBase[cpi.ComponentAccess] + +type componentAccessBridge struct { + *_componentAccessBridgeBase + ctx cpi.Context + name string + impl ComponentAccessImpl +} + +func newComponentAccessBridge(impl ComponentAccessImpl, closer ...io.Closer) (ComponentAccessBridge, error) { + base, err := resource.NewResourceImplBase[cpi.ComponentAccess, cpi.Repository](impl.GetParentBridge(), closer...) + if err != nil { + return nil, err + } + b := &componentAccessBridge{ + _componentAccessBridgeBase: base, + ctx: impl.GetContext(), + name: impl.GetName(), + impl: impl, + } + impl.SetBridge(b) + return b, nil +} + +func (b *componentAccessBridge) Close() error { + list := errors.ErrListf("closing component %s", b.name) + refmgmt.AllocLog.Trace("closing component bridge", "name", b.name) + list.Add(b.impl.Close()) + list.Add(b._componentAccessBridgeBase.Close()) + refmgmt.AllocLog.Trace("closed component bridge", "name", b.name) + return list.Result() +} + +func (b *componentAccessBridge) GetContext() cpi.Context { + return b.ctx +} + +func (b *componentAccessBridge) GetName() string { + return b.name +} + +func (b *componentAccessBridge) IsReadOnly() bool { + return b.impl.IsReadOnly() +} + +func (c *componentAccessBridge) IsOwned(cv cpi.ComponentVersionAccess) bool { + bridge, err := GetComponentVersionAccessBridge(cv) + if err != nil { + return false + } + + impl := bridge.(*componentVersionAccessBridge).impl + cvcompmgr := impl.GetParentBridge() + return c == cvcompmgr +} + +func (b *componentAccessBridge) ListVersions() ([]string, error) { + return b.impl.ListVersions() +} + +func (b *componentAccessBridge) LookupVersion(version string) (cpi.ComponentVersionAccess, error) { + i, err := b.impl.LookupVersion(version) + if err != nil { + return nil, err + } + if i == nil || i.Impl == nil { + return nil, errors.ErrInvalid("component implementation behaviour", "LookupVersion") + } + return NewComponentVersionAccess(b.GetName(), version, i.Impl, i.Lazy, i.Persistent, !compositionmodeattr.Get(b.GetContext())) +} + +func (b *componentAccessBridge) HasVersion(vers string) (bool, error) { + return b.impl.HasVersion(vers) +} + +func (b *componentAccessBridge) NewVersion(version string, overrides ...bool) (cpi.ComponentVersionAccess, error) { + i, err := b.impl.NewVersion(version, overrides...) + if err != nil { + return nil, err + } + if i == nil || i.Impl == nil { + return nil, errors.ErrInvalid("component implementation behaviour", "NewVersion") + } + return NewComponentVersionAccess(b.GetName(), version, i.Impl, i.Lazy, false, !compositionmodeattr.Get(b.GetContext())) +} + +func (c *componentAccessBridge) AddVersion(cv cpi.ComponentVersionAccess, opts *cpi.AddVersionOptions) (ferr error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&ferr) + + cvbridge, err := GetComponentVersionAccessBridge(cv) + if err != nil { + return err + } + + forcestore := c.IsOwned(cv) + if !forcestore { + eff, err := c.NewVersion(cv.GetVersion(), optionutils.AsValue(opts.Overwrite)) + if err != nil { + return err + } + finalize.With(func() error { + return eff.Close() + }) + cvbridge, err = GetComponentVersionAccessBridge(eff) + if err != nil { + return err + } + + d := eff.GetDescriptor() + *d = *cv.GetDescriptor().Copy() + + err = c.setupLocalBlobs("resource", cv, cvbridge, d.Resources, &opts.BlobUploadOptions) + if err == nil { + err = c.setupLocalBlobs("source", cv, cvbridge, d.Sources, &opts.BlobUploadOptions) + } + if err != nil { + return err + } + } + cvbridge.EnablePersistence() + err = cvbridge.Update(!cvbridge.UseDirectAccess()) + return err +} + +func (c *componentAccessBridge) setupLocalBlobs(kind string, src cpi.ComponentVersionAccess, tgtbridge ComponentVersionAccessBridge, it compdesc.ArtifactAccessor, opts *cpi.BlobUploadOptions) (ferr error) { + ctx := src.GetContext() + // transfer all local blobs + prov := func(spec cpi.AccessSpec) (blob blobaccess.BlobAccess, ref string, global cpi.AccessSpec, err error) { + if spec.IsLocal(ctx) { + m, err := spec.AccessMethod(src) + if err != nil { + return nil, "", nil, err + } + return m.AsBlobAccess(), cpi.ReferenceHint(spec, src), cpi.GlobalAccess(spec, tgtbridge.GetContext()), nil + } + return nil, "", nil, nil + } + + return tgtbridge.(*componentVersionAccessBridge).setupLocalBlobs(kind, prov, it, false, opts) +} diff --git a/pkg/contexts/ocm/cpi/repocpi/bridge_cv.go b/pkg/contexts/ocm/cpi/repocpi/bridge_cv.go new file mode 100644 index 0000000000..95bdc20a47 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/bridge_cv.go @@ -0,0 +1,488 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "encoding/json" + "fmt" + "io" + "sync" + + "github.com/open-component-model/ocm/pkg/blobaccess" + "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/ocm/accessmethods/compose" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/finalizer" + "github.com/open-component-model/ocm/pkg/optionutils" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" + "github.com/open-component-model/ocm/pkg/utils" +) + +// here, we define the common implementation agnostic parts +// for component version objects referred to by a ComponentVersionView. + +// ComponentVersionAccessImpl is the provider implementation +// interface for component versions. +type ComponentVersionAccessImpl interface { + GetContext() cpi.Context + SetBridge(bridge ComponentVersionAccessBridge) + GetParentBridge() ComponentAccessBridge + + Repository() cpi.Repository + + IsReadOnly() bool + + GetDescriptor() *compdesc.ComponentDescriptor + SetDescriptor(*compdesc.ComponentDescriptor) error + + AccessMethod(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) + GetInexpensiveContentVersionIdentity(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) string + + BlobContainer + io.Closer +} + +type _componentVersionAccessBridgeBase = resource.ResourceImplBase[cpi.ComponentVersionAccess] + +// componentVersionAccessBridge is the counterpart to views, all views +// created by Dup calls use this base object to work on. +// Besides some functionality covered by view objects these base objects +// implement provider-agnostic parts of the ComponentVersionAccess API. +type componentVersionAccessBridge struct { + lock sync.Mutex + id finalizer.ObjectIdentity + + *_componentVersionAccessBridgeBase + ctx cpi.Context + name string + version string + + descriptor *compdesc.ComponentDescriptor + blobcache BlobCache + + lazy bool + directAccess bool + persistent bool + discardChanges bool + + impl ComponentVersionAccessImpl +} + +var _ ComponentVersionAccessBridge = (*componentVersionAccessBridge)(nil) + +func newComponentVersionAccessBridge(name, version string, impl ComponentVersionAccessImpl, lazy, persistent, direct bool, closer ...io.Closer) (ComponentVersionAccessBridge, error) { + base, err := resource.NewResourceImplBase[cpi.ComponentVersionAccess, cpi.ComponentAccess](impl.GetParentBridge(), closer...) + if err != nil { + return nil, err + } + b := &componentVersionAccessBridge{ + _componentVersionAccessBridgeBase: base, + id: finalizer.NewObjectIdentity(fmt.Sprintf("%s:%s", name, version)), + ctx: impl.GetContext(), + name: name, + version: version, + blobcache: NewBlobCache(), + lazy: lazy, + persistent: persistent, + directAccess: direct, + impl: impl, + } + impl.SetBridge(b) + return b, nil +} + +func GetComponentVersionImpl[T ComponentVersionAccessImpl](cv cpi.ComponentVersionAccess) (T, error) { + var _nil T + + impl, err := GetComponentVersionAccessBridge(cv) + if err != nil { + return _nil, err + } + if mine, ok := impl.(*componentVersionAccessBridge); ok { + cont, ok := mine.impl.(T) + if ok { + return cont, nil + } + return _nil, errors.Newf("non-matching component version implementation %T", mine.impl) + } + return _nil, errors.Newf("non-matching component version implementation %T", impl) +} + +func (b *componentVersionAccessBridge) Close() error { + list := errors.ErrListf("closing component version %s", common.VersionedElementKey(b)) + refmgmt.AllocLog.Trace("closing component version base", "name", common.VersionedElementKey(b)) + // prepare artifact access for final close in + // direct access mode. + if !compositionmodeattr.Get(b.ctx) { + list.Add(b.update(true)) + } + list.Add(b.impl.Close()) + list.Add(b._componentVersionAccessBridgeBase.Close()) + list.Add(b.blobcache.Clear()) + refmgmt.AllocLog.Trace("closed component version base", "name", common.VersionedElementKey(b)) + return list.Result() +} + +func (b *componentVersionAccessBridge) GetContext() cpi.Context { + return b.ctx +} + +func (b *componentVersionAccessBridge) GetName() string { + return b.name +} + +func (b *componentVersionAccessBridge) GetVersion() string { + return b.version +} + +func (b *componentVersionAccessBridge) GetImplementation() ComponentVersionAccessImpl { + return b.impl +} + +func (b *componentVersionAccessBridge) GetBlobCache() BlobCache { + return b.blobcache +} + +func (b *componentVersionAccessBridge) EnablePersistence() bool { + if b.discardChanges { + return false + } + b.persistent = true + b.GetStorageContext() + return true +} + +func (b *componentVersionAccessBridge) IsPersistent() bool { + return b.persistent +} + +func (b *componentVersionAccessBridge) UseDirectAccess() bool { + return b.directAccess +} + +func (b *componentVersionAccessBridge) DiscardChanges() { + b.discardChanges = true +} + +func (b *componentVersionAccessBridge) Repository() cpi.Repository { + return b.impl.Repository() +} + +func (b *componentVersionAccessBridge) IsReadOnly() bool { + return b.impl.IsReadOnly() +} + +//////////////////////////////////////////////////////////////////////////////// +// with access to actual view + +func (b *componentVersionAccessBridge) AccessMethod(spec cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (meth cpi.AccessMethod, err error) { + switch { + case compose.Is(spec): + cspec, ok := spec.(*compose.AccessSpec) + if !ok { + return nil, fmt.Errorf("invalid implementation (%T) for access method compose", spec) + } + blob := b.getLocalBlob(cspec) + if blob == nil { + return nil, errors.ErrUnknown(blobaccess.KIND_BLOB, cspec.Id, common.VersionedElementKey(b).String()) + } + meth, err = compose.NewMethod(cspec, blob) + case spec.IsLocal(b.ctx): + meth, err = b.impl.AccessMethod(spec, cv) + if err == nil { + if blob := b.getLocalBlob(spec); blob != nil { + meth, err = newFakeMethod(meth, blob) + } + } + } + return meth, err +} + +func (b *componentVersionAccessBridge) GetInexpensiveContentVersionIdentity(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) string { + return b.impl.GetInexpensiveContentVersionIdentity(acc, cv) +} + +func (b *componentVersionAccessBridge) GetDescriptor() *compdesc.ComponentDescriptor { + b.lock.Lock() + defer b.lock.Unlock() + + return b.getDescriptor() +} + +func (b *componentVersionAccessBridge) getDescriptor() *compdesc.ComponentDescriptor { + if b.descriptor == nil { + b.descriptor = b.impl.GetDescriptor() + } + return b.descriptor +} + +func (b *componentVersionAccessBridge) GetStorageContext() cpi.StorageContext { + return b.impl.GetStorageContext() +} + +func (b *componentVersionAccessBridge) ShouldUpdate(final bool) bool { + b.lock.Lock() + defer b.lock.Unlock() + + return b.shouldUpdate(final) +} + +func (b *componentVersionAccessBridge) shouldUpdate(final bool) bool { + if b.discardChanges { + return false + } + if final { + return b.persistent + } + return !b.lazy && b.directAccess && b.persistent +} + +func (b *componentVersionAccessBridge) Update(final bool) error { + b.lock.Lock() + defer b.lock.Unlock() + + return b.update(final) +} + +func (b *componentVersionAccessBridge) update(final bool) error { + if !b.shouldUpdate(final) { + return nil + } + + d := b.getDescriptor() + + opts := &cpi.BlobUploadOptions{ + UseNoDefaultIfNotSet: optionutils.PointerTo(true), + } + err := b.setupLocalBlobs("resource", b.composeAccess, d.Resources, true, opts) + if err == nil { + err = b.setupLocalBlobs("source", b.composeAccess, d.Sources, true, opts) + } + if err != nil { + return err + } + + err = b.impl.SetDescriptor(b.descriptor.Copy()) + if err != nil { + return err + } + err = b.blobcache.Clear() + return err +} + +func (b *componentVersionAccessBridge) getLocalBlob(acc cpi.AccessSpec) cpi.BlobAccess { + key, err := json.Marshal(acc) + if err != nil { + return nil + } + return b.blobcache.GetBlobFor(string(key)) +} + +func (b *componentVersionAccessBridge) AddBlob(blob cpi.BlobAccess, artType, refName string, global cpi.AccessSpec, final bool, opts *cpi.BlobUploadOptions) (cpi.AccessSpec, error) { + if blob == nil { + return nil, errors.New("a resource has to be defined") + } + if b.IsReadOnly() { + return nil, accessio.ErrReadOnly + } + blob, err := blob.Dup() + if err != nil { + return nil, errors.Wrapf(err, "invalid blob access") + } + defer blob.Close() + err = utils.ValidateObject(blob) + if err != nil { + return nil, errors.Wrapf(err, "invalid blob access") + } + + ctx := b.GetContext() + + // handle foreign blob upload + var prov cpi.BlobHandlerProvider + if opts.BlobHandlerProvider != nil { + prov = opts.BlobHandlerProvider + } else { + if !optionutils.AsValue(opts.UseNoDefaultIfNotSet) { + prov = internal.BlobHandlerProviderForRegistry(ctx.BlobHandlers()) + } else { //nolint: staticcheck // yes + // use no blob uploader + } + } + if prov != nil { + storagectx := b.GetStorageContext() + h := prov.LookupHandler(storagectx, artType, blob.MimeType()) + if h != nil { + acc, err := h.StoreBlob(blob, artType, refName, nil, storagectx) + if err != nil { + return nil, err + } + if acc != nil { + if !keepblobattr.Get(ctx) || acc.IsLocal(ctx) { + return acc, nil + } + global = acc + } + } + } + + var acc cpi.AccessSpec + + if final || b.UseDirectAccess() { + acc, err = b.impl.AddBlob(blob, refName, global) + if err != nil { + return nil, err + } + } else { + // use local composition access to be added to the repository with AddVersion. + acc = compose.New(refName, blob.MimeType(), global) + } + return b.cacheLocalBlob(acc, blob) +} + +func (b *componentVersionAccessBridge) cacheLocalBlob(acc cpi.AccessSpec, blob cpi.BlobAccess) (cpi.AccessSpec, error) { + key, err := json.Marshal(acc) + if err != nil { + return nil, errors.Wrapf(err, "cannot marshal access spec") + } + // local blobs might not be accessible from the underlying + // repository implementation if the component version is not + // finally added (for example ghcr.io as OCI repository). + // Therefore, we keep a copy of the blob access for further usage. + + // if a local blob is uploader and the access method is replaced + // we have to handle the case that the technical upload repo + // is the same as the storage backend of the OCM repository, which + // might have been configured with local credentials, which were + // reused by the uploader. + // The access spec is independent of the actual repo, so it does + // not have access to those credentials. Therefore, we have to + // keep the original blob for further usage, also. + k := BlobCacheKey(string(key)) + err = b.blobcache.AddBlobFor(k, blob) + if err != nil { + return nil, err + } + return acc, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +func (b *componentVersionAccessBridge) composeAccess(spec cpi.AccessSpec) (blobaccess.BlobAccess, string, cpi.AccessSpec, error) { + if !compose.Is(spec) { + return nil, "", nil, nil + } + cspec, ok := spec.(*compose.AccessSpec) + if !ok { + return nil, "", nil, fmt.Errorf("invalid implementation (%T) for access method compose", spec) + } + blob := b.getLocalBlob(cspec) + if blob == nil { + return nil, "", nil, errors.ErrUnknown(blobaccess.KIND_BLOB, cspec.Id, common.VersionedElementKey(b).String()) + } + blob, err := blob.Dup() + if err != nil { + return nil, "", nil, errors.Wrapf(err, "cached blob") + } + + return blob, cspec.ReferenceName, cspec.GlobalAccess.Get(), nil +} + +func (b *componentVersionAccessBridge) setupLocalBlobs(kind string, accprov func(cpi.AccessSpec) (blobaccess.BlobAccess, string, cpi.AccessSpec, error), it compdesc.ArtifactAccessor, final bool, opts *cpi.BlobUploadOptions) (ferr error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&ferr) + + for i := 0; i < it.Len(); i++ { + nested := finalize.Nested() + a := it.GetArtifact(i) + spec, err := b.ctx.AccessSpecForSpec(a.GetAccess()) + if err != nil { + return errors.Wrapf(err, "%s %d", kind, i) + } + blob, ref, global, err := accprov(spec) + if err != nil { + return errors.Wrapf(err, "%s %d", kind, i) + } + if blob != nil { + nested.Close(blob) + + effspec, err := b.AddBlob(blob, a.GetType(), ref, global, final, opts) + if err != nil { + return errors.Wrapf(err, "cannot store %s %d", kind, i) + } + a.SetAccess(effspec) + } + err = nested.Finalize() + if err != nil { + return errors.Wrapf(err, "%s %d", kind, i) + } + } + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type fakeMethod struct { + spec cpi.AccessSpec + local bool + mime string + blob blobaccess.BlobAccess +} + +var _ accspeccpi.AccessMethodImpl = (*fakeMethod)(nil) + +func newFakeMethod(m cpi.AccessMethod, blob cpi.BlobAccess) (cpi.AccessMethod, error) { + b, err := blob.Dup() + if err != nil { + return nil, errors.Wrapf(err, "cannot remember blob for access method") + } + f := &fakeMethod{ + spec: m.AccessSpec(), + local: m.IsLocal(), + mime: m.MimeType(), + blob: b, + } + err = m.Close() + if err != nil { + _ = b.Close() + return nil, errors.Wrapf(err, "closing access method") + } + return accspeccpi.AccessMethodForImplementation(f, nil) +} + +func (f *fakeMethod) MimeType() string { + return f.mime +} + +func (f *fakeMethod) IsLocal() bool { + return f.local +} + +func (f *fakeMethod) GetKind() string { + return f.spec.GetKind() +} + +func (f *fakeMethod) AccessSpec() internal.AccessSpec { + return f.spec +} + +func (f *fakeMethod) Close() error { + return f.blob.Close() +} + +func (f *fakeMethod) Reader() (io.ReadCloser, error) { + return f.blob.Reader() +} + +func (f *fakeMethod) Get() ([]byte, error) { + return f.blob.Get() +} diff --git a/pkg/contexts/ocm/cpi/repocpi/bridge_r.go b/pkg/contexts/ocm/cpi/repocpi/bridge_r.go new file mode 100644 index 0000000000..b2d6f093dc --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/bridge_r.go @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "io" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" +) + +type ComponentAccessInfo struct { + Impl ComponentAccessImpl + Kind string + Main bool +} + +type RepositoryImpl interface { + SetBridge(bridge RepositoryBridge) + + GetContext() cpi.Context + + GetSpecification() cpi.RepositorySpec + ComponentLister() cpi.ComponentLister + + ExistsComponentVersion(name string, version string) (bool, error) + LookupComponent(name string) (*ComponentAccessInfo, error) + + io.Closer +} + +type _repositoryBridgeBase = resource.ResourceImplBase[cpi.Repository] + +type repositoryBridge struct { + *_repositoryBridgeBase + ctx cpi.Context + kind string + impl RepositoryImpl +} + +func newRepositoryBridge(impl RepositoryImpl, kind string, closer ...io.Closer) RepositoryBridge { + base := resource.NewSimpleResourceImplBase[cpi.Repository](closer...) + b := &repositoryBridge{ + _repositoryBridgeBase: base, + ctx: impl.GetContext(), + impl: impl, + } + impl.SetBridge(b) + return b +} + +func (b *repositoryBridge) Close() error { + list := errors.ErrListf("closing %s", b.kind) + refmgmt.AllocLog.Trace("closing repository bridge", "kind", b.kind) + list.Add(b.impl.Close()) + list.Add(b._repositoryBridgeBase.Close()) + refmgmt.AllocLog.Trace("closed repository bridge", "kind", b.kind) + return list.Result() +} + +func (b *repositoryBridge) GetContext() cpi.Context { + return b.ctx +} + +func (b *repositoryBridge) GetSpecification() cpi.RepositorySpec { + return b.impl.GetSpecification() +} + +func (b *repositoryBridge) ComponentLister() cpi.ComponentLister { + return b.impl.ComponentLister() +} + +func (b *repositoryBridge) ExistsComponentVersion(name string, version string) (bool, error) { + return b.impl.ExistsComponentVersion(name, version) +} + +func (b *repositoryBridge) LookupComponentVersion(name string, version string) (cv cpi.ComponentVersionAccess, rerr error) { + c, err := b.LookupComponent(name) + if err != nil { + return nil, err + } + defer refmgmt.PropagateCloseTemporary(&rerr, c) // temporary component object not exposed. + refmgmt.AllocLog.Trace("lookup version for temporary component ref", "component", name, "version", version) + return c.LookupVersion(version) +} + +func (b *repositoryBridge) LookupComponent(name string) (cpi.ComponentAccess, error) { + i, err := b.impl.LookupComponent(name) + if err != nil { + return nil, err + } + return NewComponentAccess(i.Impl, i.Kind, i.Main) +} diff --git a/pkg/contexts/ocm/cpi/repocpi/doc.go b/pkg/contexts/ocm/cpi/repocpi/doc.go new file mode 100644 index 0000000000..017ad14993 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/doc.go @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +// Package repocpi contains the implementation support +// for repository backends. It offers three methods +// to create component version, component and repository +// objects based on three simple implementation interfaces. +// +// The basic provisioning model is layered: +// +// - on layer 1 there is the user facing API defined +// in package [github.com/open-component-model/ocm/pkg/contexts/ocm]. +// +// - on layer 2 (this package) there is a backend agnostic +// implementation of standard functionality based on layer 3. +// This is divided into two parts +// +// a) the view objects provided by the Dup() calls of the layer 1 API. +// All dups are internally based on a single base object. +// These objects are called bridge. They act as base object +// for the views and as abstraction for the implementation objects +// providing generic implementations potentially based on +// the implementation functionality. +// (see bridge design pattern https://refactoring.guru/design-patterns/bridge) +// +// b) the bridge object as base for all dup views is used to implement some +// common functionality like the view management. The bridge object +// is closed, when the last view disappears. +// This bridge object then calls the final +// storage backend implementation interface. +// +// - the storage backend implementations based on the implementation +// interfaces provided by layer 2. +// +// The implementation interfaces and the functions to create API objects are: +// +// - interface [ComponentVersionAccessImpl] is used to create an ocm.ComponentVersionAccess object +// using the function [NewComponentVersionAccess]. +// - interface [ComponentAccessImpl] is used to create an ocm.ComponentAccess object +// using the function [NewComponentAccess]. +// - interface [RepositoryImpl] is used to create an ocm.ComponentAccess object +// using the function [NewRepository]. +// +// Component version implementations provide basic access to component versions +// and their descriptors. They keep a reference to component implementations, which are +// again based on repository implementations. The task of repository implementations is +// to provide component objects. Their implementations are responsible to provide +// component version objects. +// +// Besides this basic implementation interface with separated object for a +// repository, component and component version, there is support for a simplified +// implementation interface (StorageBackendImpl). This is a single interface +// bundling all required functionality to implement the objects for the three +// concerned elements. With NewStorageBackend it is possible to instantiate +// a new kind of repository based on this single interface. The required +// objects for components and component versions are generically provided +// based on the methods provided by this interface. +package repocpi diff --git a/pkg/contexts/ocm/cpi/repocpi/helperinterfaces.go b/pkg/contexts/ocm/cpi/repocpi/helperinterfaces.go new file mode 100644 index 0000000000..aec5b364f0 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/helperinterfaces.go @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "fmt" + + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" +) + +var ( + ErrClosed = resource.ErrClosed + ErrTempVersion = fmt.Errorf("temporary component version cannot be updated") +) + +// BlobContainer is the interface for an element capable to store blobs. +type BlobContainer interface { + GetBlob(name string) (cpi.DataAccess, error) + + // GetStorageContext creates a storage context for blobs + // that is used to feed blob handlers for specific blob storage methods. + // If no handler accepts the blob, the AddBlobFor method will + // be used to store the blob + GetStorageContext() cpi.StorageContext + + // AddBlob stores a local blob together with the component and + // potentially provides a global reference according to the OCI distribution spec + // if the blob described an oci artifact. + // The resulting access information (global and local) is provided as + // an access method specification usable in a component descriptor. + // This is the direct technical storage, without caring about any handler. + AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) +} diff --git a/pkg/contexts/ocm/cpi/repocpi/ocmimpllayers.png b/pkg/contexts/ocm/cpi/repocpi/ocmimpllayers.png new file mode 100755 index 0000000000..d0fc75645f Binary files /dev/null and b/pkg/contexts/ocm/cpi/repocpi/ocmimpllayers.png differ diff --git a/pkg/contexts/ocm/cpi/repocpi/view_c.go b/pkg/contexts/ocm/cpi/repocpi/view_c.go new file mode 100644 index 0000000000..fa6678c1a7 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/view_c.go @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "fmt" + "io" + + "github.com/open-component-model/ocm/pkg/common/accessio" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" + "github.com/open-component-model/ocm/pkg/utils" +) + +type _componentAccessView interface { + resource.ResourceViewInt[cpi.ComponentAccess] // here you have to redeclare +} + +type ComponentAccessViewManager = resource.ViewManager[cpi.ComponentAccess] // here you have to use an alias + +type ComponentAccessBridge interface { + resource.ResourceImplementation[cpi.ComponentAccess] + + GetContext() cpi.Context + IsReadOnly() bool + GetName() string + + IsOwned(access cpi.ComponentVersionAccess) bool + + ListVersions() ([]string, error) + LookupVersion(version string) (cpi.ComponentVersionAccess, error) + HasVersion(vers string) (bool, error) + NewVersion(version string, overrides ...bool) (cpi.ComponentVersionAccess, error) + + Close() error + AddVersion(cv cpi.ComponentVersionAccess, opts *cpi.AddVersionOptions) (ferr error) +} + +type componentAccessView struct { + _componentAccessView + bridge ComponentAccessBridge +} + +var ( + _ cpi.ComponentAccess = (*componentAccessView)(nil) + _ utils.Unwrappable = (*componentAccessView)(nil) +) + +func GetComponentAccessBridge(n cpi.ComponentAccess) (ComponentAccessBridge, error) { + if v, ok := n.(*componentAccessView); ok { + return v.bridge, nil + } + return nil, errors.ErrNotSupported("component base type", fmt.Sprintf("%T", n)) +} + +func GetComponentAccessImplementation(n cpi.ComponentAccess) (ComponentAccessImpl, error) { + if v, ok := n.(*componentAccessView); ok { + if b, ok := v.bridge.(*componentAccessBridge); ok { + return b.impl, nil + } + return nil, errors.ErrNotSupported("component base type", fmt.Sprintf("%T", v.bridge)) + } + return nil, errors.ErrNotSupported("component implementation type", fmt.Sprintf("%T", n)) +} + +func componentAccessViewCreator(i ComponentAccessBridge, v resource.CloserView, d ComponentAccessViewManager) cpi.ComponentAccess { + return &componentAccessView{ + _componentAccessView: resource.NewView[cpi.ComponentAccess](v, d), + bridge: i, + } +} + +func NewComponentAccess(impl ComponentAccessImpl, kind string, main bool, closer ...io.Closer) (cpi.ComponentAccess, error) { + bridge, err := newComponentAccessBridge(impl, closer...) + if err != nil { + return nil, errors.Join(err, impl.Close()) + } + if kind == "" { + kind = "component" + } + cv := resource.NewResource[cpi.ComponentAccess](bridge, componentAccessViewCreator, fmt.Sprintf("%s %s", kind, impl.GetName()), main) + return cv, nil +} + +func (c *componentAccessView) Unwrap() interface{} { + return c.bridge +} + +func (c *componentAccessView) GetContext() cpi.Context { + return c.bridge.GetContext() +} + +func (c *componentAccessView) GetName() string { + return c.bridge.GetName() +} + +func (c *componentAccessView) ListVersions() (list []string, err error) { + err = c.Execute(func() error { + list, err = c.bridge.ListVersions() + return err + }) + return list, err +} + +func (c *componentAccessView) LookupVersion(version string) (acc cpi.ComponentVersionAccess, err error) { + err = c.Execute(func() error { + acc, err = c.bridge.LookupVersion(version) + return err + }) + return acc, err +} + +func (c *componentAccessView) AddVersion(acc cpi.ComponentVersionAccess, overwrite ...bool) error { + if acc.GetName() != c.GetName() { + return errors.ErrInvalid("component name", acc.GetName()) + } + + return c.Execute(func() error { + return c.bridge.AddVersion(acc, cpi.NewAddVersionOptions(cpi.Overwrite(utils.Optional(overwrite...)))) + }) +} + +func (c *componentAccessView) AddVersionOpt(acc cpi.ComponentVersionAccess, opts ...cpi.AddVersionOption) error { + if acc.GetName() != c.GetName() { + return errors.ErrInvalid("component name", acc.GetName()) + } + return c.Execute(func() error { + return c.bridge.AddVersion(acc, cpi.NewAddVersionOptions(opts...)) + }) +} + +func (c *componentAccessView) NewVersion(version string, overrides ...bool) (acc cpi.ComponentVersionAccess, err error) { + err = c.Execute(func() error { + if c.bridge.IsReadOnly() { + return accessio.ErrReadOnly + } + acc, err = c.bridge.NewVersion(version, overrides...) + return err + }) + return acc, err +} + +func (c *componentAccessView) HasVersion(vers string) (ok bool, err error) { + err = c.Execute(func() error { + ok, err = c.bridge.HasVersion(vers) + return err + }) + return ok, err +} diff --git a/pkg/contexts/ocm/cpi/repocpi/view_cv.go b/pkg/contexts/ocm/cpi/repocpi/view_cv.go new file mode 100644 index 0000000000..f6c131f989 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/view_cv.go @@ -0,0 +1,741 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "fmt" + "io" + "strconv" + + "github.com/open-component-model/ocm/pkg/blobaccess" + "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/ocm/accessmethods/compose" + "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" + metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" + "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" + "github.com/open-component-model/ocm/pkg/utils" + "github.com/open-component-model/ocm/pkg/utils/selector" +) + +// View objects are the user facing generic implementations of the context interfaces. +// They are responsible to handle the reference counting and use +// shared implementations objects for th concrete type-specific implementations. +// Additionally, they are used to implement interface functionality which is +// common to all implementations and NOT dependent on the backend system technology. + +// here are the views implementing the user facing ComponentVersionAccess +// interface. + +type _componentVersionAccessView interface { + resource.ResourceViewInt[cpi.ComponentVersionAccess] +} + +type ComponentVersionAccessViewManager = resource.ViewManager[cpi.ComponentVersionAccess] + +type ComponentVersionAccessBridge interface { + resource.ResourceImplementation[cpi.ComponentVersionAccess] + common.VersionedElement + io.Closer + + GetContext() cpi.Context + Repository() cpi.Repository + + GetImplementation() ComponentVersionAccessImpl + + EnablePersistence() bool + DiscardChanges() + IsPersistent() bool + + GetDescriptor() *compdesc.ComponentDescriptor + + AccessMethod(cpi.AccessSpec, refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) + GetInexpensiveContentVersionIdentity(cpi.AccessSpec, refmgmt.ExtendedAllocatable) string + + // GetStorageContext creates a storage context for blobs + // that is used to feed blob handlers for specific blob storage methods. + // If no handler accepts the blob, the AddBlob method will + // be used to store the blob + GetStorageContext() cpi.StorageContext + + // AddBlob stores a local blob together with the component and + // potentially provides a global reference. + // The resulting access information (global and local) is provided as + // an access method specification usable in a component descriptor. + // This is the direct technical storage, without caring about any handler. + AddBlob(blob cpi.BlobAccess, arttype, refName string, global cpi.AccessSpec, final bool, opts *cpi.BlobUploadOptions) (cpi.AccessSpec, error) + + IsReadOnly() bool + + // ShouldUpdate checks, whether an update is indicated + // by the state of object, considering persistence, lazy, discard + // and update mode state + ShouldUpdate(final bool) bool + + // GetBlobCache retieves the blob cache used to store preliminary + // blob accesses for freshly generated local access specs not directly + // usable until a component version is finally added to the repository. + GetBlobCache() BlobCache + + // UseDirectAccess returns true if composition should be directly + // forwarded to the repository backend., + UseDirectAccess() bool + + // Update persists the current state of the component version to the + // underlying repository backend. + Update(final bool) error +} + +type componentVersionAccessView struct { + _componentVersionAccessView + bridge ComponentVersionAccessBridge + err error +} + +var ( + _ cpi.ComponentVersionAccess = (*componentVersionAccessView)(nil) + _ utils.Unwrappable = (*componentVersionAccessView)(nil) +) + +func GetComponentVersionAccessBridge(n cpi.ComponentVersionAccess) (ComponentVersionAccessBridge, error) { + if v, ok := n.(*componentVersionAccessView); ok { + return v.bridge, nil + } + return nil, errors.ErrNotSupported("component version bridge type", fmt.Sprintf("%T", n)) +} + +func GetComponentVersionAccessImplementation(n cpi.ComponentVersionAccess) (ComponentVersionAccessImpl, error) { + if v, ok := n.(*componentVersionAccessView); ok { + if b, ok := v.bridge.(*componentVersionAccessBridge); ok { + return b.impl, nil + } + return nil, errors.ErrNotSupported("component version bridge type", fmt.Sprintf("%T", v.bridge)) + } + return nil, errors.ErrNotSupported("component version implementation type", fmt.Sprintf("%T", n)) +} + +func artifactAccessViewCreator(i ComponentVersionAccessBridge, v resource.CloserView, d resource.ViewManager[cpi.ComponentVersionAccess]) cpi.ComponentVersionAccess { + cv := &componentVersionAccessView{ + _componentVersionAccessView: resource.NewView[cpi.ComponentVersionAccess](v, d), + bridge: i, + } + return cv +} + +func NewComponentVersionAccess(name, version string, impl ComponentVersionAccessImpl, lazy, persistent, direct bool, closer ...io.Closer) (cpi.ComponentVersionAccess, error) { + bridge, err := newComponentVersionAccessBridge(name, version, impl, lazy, persistent, direct, closer...) + if err != nil { + return nil, errors.Join(err, impl.Close()) + } + return resource.NewResource[cpi.ComponentVersionAccess](bridge, artifactAccessViewCreator, fmt.Sprintf("component version %s/%s", name, version), true), nil +} + +func (c *componentVersionAccessView) Unwrap() interface{} { + return c.bridge +} + +func (c *componentVersionAccessView) Close() error { + list := errors.ErrListf("closing %s", common.VersionedElementKey(c)) + err := c._componentVersionAccessView.Close() + return list.Add(c.err, err).Result() +} + +func (c *componentVersionAccessView) Repository() cpi.Repository { + return c.bridge.Repository() +} + +func (c *componentVersionAccessView) GetContext() internal.Context { + return c.bridge.GetContext() +} + +func (c *componentVersionAccessView) GetName() string { + return c.bridge.GetName() +} + +func (c *componentVersionAccessView) GetVersion() string { + return c.bridge.GetVersion() +} + +func (c *componentVersionAccessView) GetDescriptor() *compdesc.ComponentDescriptor { + return c.bridge.GetDescriptor() +} + +func (c *componentVersionAccessView) GetProvider() *compdesc.Provider { + return c.GetDescriptor().Provider.Copy() +} + +func (c *componentVersionAccessView) SetProvider(p *compdesc.Provider) error { + return c.Execute(func() error { + c.GetDescriptor().Provider = *p.Copy() + return nil + }) +} + +func (c *componentVersionAccessView) AccessMethod(spec cpi.AccessSpec) (meth cpi.AccessMethod, err error) { + spec, err = c.GetContext().AccessSpecForSpec(spec) + if err != nil { + return nil, err + } + err = c.Execute(func() error { + var err error + meth, err = c.accessMethod(spec) + return err + }) + return meth, err +} + +func (c *componentVersionAccessView) accessMethod(spec cpi.AccessSpec) (meth cpi.AccessMethod, err error) { + switch { + case spec.IsLocal(c.GetContext()): + return c.bridge.AccessMethod(spec, c.Allocatable()) + default: + return spec.AccessMethod(c) + } +} + +func (c *componentVersionAccessView) GetInexpensiveContentVersionIdentity(spec cpi.AccessSpec) string { + var err error + + spec, err = c.GetContext().AccessSpecForSpec(spec) + if err != nil { + return "" + } + + var id string + _ = c.Execute(func() error { + id = c.getInexpensiveContentVersionIdentity(spec) + return nil + }) + return id +} + +func (c *componentVersionAccessView) getInexpensiveContentVersionIdentity(spec cpi.AccessSpec) string { + switch { + case compose.Is(spec): + fallthrough + case !spec.IsLocal(c.GetContext()): + // fall back to original version + return spec.GetInexpensiveContentVersionIdentity(c) + default: + return c.bridge.GetInexpensiveContentVersionIdentity(spec, c.Allocatable()) + } +} + +func (c *componentVersionAccessView) Update() error { + return c.Execute(func() error { + if !c.bridge.IsPersistent() { + return ErrTempVersion + } + return c.bridge.Update(true) + }) +} + +func (c *componentVersionAccessView) AddBlob(blob cpi.BlobAccess, artType, refName string, global cpi.AccessSpec, opts ...internal.BlobUploadOption) (cpi.AccessSpec, error) { + var spec cpi.AccessSpec + eff := cpi.NewBlobUploadOptions(opts...) + err := c.Execute(func() error { + var err error + spec, err = c.bridge.AddBlob(blob, artType, refName, global, false, eff) + return err + }) + + return spec, err +} + +func (c *componentVersionAccessView) AdjustResourceAccess(meta *cpi.ResourceMeta, acc compdesc.AccessSpec, opts ...internal.ModificationOption) error { + cd := c.GetDescriptor() + if idx := cd.GetResourceIndex(meta); idx >= 0 { + return c.SetResource(&cd.Resources[idx].ResourceMeta, acc, opts...) + } + return errors.ErrUnknown(cpi.KIND_RESOURCE, meta.GetIdentity(cd.Resources).String()) +} + +// SetResourceBlob adds a blob resource to the component version. +func (c *componentVersionAccessView) SetResourceBlob(meta *cpi.ResourceMeta, blob cpi.BlobAccess, refName string, global cpi.AccessSpec, opts ...internal.BlobModificationOption) error { + cpi.Logger(c).Debug("adding resource blob", "resource", meta.Name) + if err := utils.ValidateObject(blob); err != nil { + return err + } + eff := cpi.NewBlobModificationOptions(opts...) + acc, err := c.AddBlob(blob, meta.Type, refName, global, eff) + if err != nil { + return fmt.Errorf("unable to add blob (component %s:%s resource %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) + } + + if err := c.SetResource(meta, acc, eff, cpi.ModifyResource()); err != nil { + return fmt.Errorf("unable to set resource: %w", err) + } + + return nil +} + +func (c *componentVersionAccessView) AdjustSourceAccess(meta *cpi.SourceMeta, acc compdesc.AccessSpec) error { + cd := c.GetDescriptor() + if idx := cd.GetSourceIndex(meta); idx >= 0 { + return c.SetSource(&cd.Sources[idx].SourceMeta, acc) + } + return errors.ErrUnknown(cpi.KIND_RESOURCE, meta.GetIdentity(cd.Resources).String()) +} + +func (c *componentVersionAccessView) SetSourceBlob(meta *cpi.SourceMeta, blob cpi.BlobAccess, refName string, global cpi.AccessSpec) error { + cpi.Logger(c).Debug("adding source blob", "source", meta.Name) + if err := utils.ValidateObject(blob); err != nil { + return err + } + acc, err := c.AddBlob(blob, meta.Type, refName, global) + if err != nil { + return fmt.Errorf("unable to add blob: (component %s:%s source %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) + } + + if err := c.SetSource(meta, acc); err != nil { + return fmt.Errorf("unable to set source: %w", err) + } + + return nil +} + +func setAccess[T any, A internal.ArtifactAccess[T]](c *componentVersionAccessView, kind string, art A, + set func(*T, compdesc.AccessSpec) error, + setblob func(*T, cpi.BlobAccess, string, cpi.AccessSpec) error, +) error { + if c.bridge.IsReadOnly() { + return accessio.ErrReadOnly + } + meta := art.Meta() + if meta == nil { + return errors.Newf("no meta data provided by %s access", kind) + } + acc, err := art.Access() + if err != nil && !errors.IsErrNotFoundElem(err, "", descriptor.KIND_ACCESSMETHOD) { + return err + } + + var ( + blob cpi.BlobAccess + hint string + global cpi.AccessSpec + ) + + if acc != nil { + if !acc.IsLocal(c.GetContext()) { + return set(meta, acc) + } + + blob, err = accspeccpi.BlobAccessForAccessSpec(acc, c) + if err != nil && errors.IsErrNotFoundElem(err, "", blobaccess.KIND_BLOB) { + return err + } + hint = cpi.ReferenceHint(acc, c) + global = cpi.GlobalAccess(acc, c.GetContext()) + } + if blob == nil { + blob, err = art.BlobAccess() + if err != nil { + return err + } + defer blob.Close() + } + if blob == nil { + return errors.Newf("neither access nor blob specified in %s access", kind) + } + if v := art.ReferenceHint(); v != "" { + hint = v + } + if v := art.GlobalAccess(); v != nil { + global = v + } + return setblob(meta, blob, hint, global) +} + +func (c *componentVersionAccessView) SetResourceAccess(art cpi.ResourceAccess, modopts ...cpi.BlobModificationOption) error { + return setAccess(c, "resource", art, + func(meta *cpi.ResourceMeta, acc compdesc.AccessSpec) error { + return c.SetResource(meta, acc, cpi.NewBlobModificationOptions(modopts...)) + }, + func(meta *cpi.ResourceMeta, blob cpi.BlobAccess, hint string, global cpi.AccessSpec) error { + return c.SetResourceBlob(meta, blob, hint, global, modopts...) + }) +} + +func (c *componentVersionAccessView) SetResource(meta *internal.ResourceMeta, acc compdesc.AccessSpec, modopts ...cpi.ModificationOption) error { + if c.bridge.IsReadOnly() { + return accessio.ErrReadOnly + } + + res := &compdesc.Resource{ + ResourceMeta: *meta.Copy(), + Access: acc, + } + + ctx := c.bridge.GetContext() + opts := internal.NewModificationOptions(modopts...) + cpi.CompleteModificationOptions(ctx, opts) + + spec, err := c.bridge.GetContext().AccessSpecForSpec(acc) + if err != nil { + return err + } + + // if the blob described by the access spec has been added + // as local blob, just reuse the stored blob access + // to calculate the digest to circumvent credential problems + // for access specs generated by an uploader. + meth, err := c.AccessMethod(spec) + if err != nil { + return err + } + defer meth.Close() + + return c.Execute(func() error { + var old *compdesc.Resource + + if res.Relation == metav1.LocalRelation { + if res.Version == "" { + res.Version = c.GetVersion() + } + } + + cd := c.bridge.GetDescriptor() + idx := cd.GetResourceIndex(&res.ResourceMeta) + if idx >= 0 { + old = &cd.Resources[idx] + } + + if old == nil { + if !opts.IsModifyResource() && c.bridge.IsPersistent() { + return fmt.Errorf("new resource would invalidate signature") + } + } + + // evaluate given digesting constraints and settings + hashAlgo, digester, digest := c.evaluateResourceDigest(res, old, *opts) + hasher := opts.GetHasher(hashAlgo) + if digester.HashAlgorithm == "" && hasher == nil { + return errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, hashAlgo) + } + + if !compdesc.IsNoneAccessKind(res.Access.GetKind()) { + var calculatedDigest *cpi.DigestDescriptor + if (!opts.IsSkipVerify() && digest != "") || (!opts.IsSkipDigest() && digest == "") { + dig, err := ctx.BlobDigesters().DetermineDigests(res.Type, hasher, opts.HasherProvider, meth, digester) + if err != nil { + return err + } + if len(dig) == 0 { + return fmt.Errorf("%s: no digester accepts resource", res.Name) + } + calculatedDigest = &dig[0] + } + + if digest != "" && !opts.IsSkipVerify() { + if digest != calculatedDigest.Value { + return fmt.Errorf("digest mismatch: %s != %s", calculatedDigest.Value, digest) + } + } + + if !opts.IsSkipDigest() { + if digest == "" { + res.Digest = calculatedDigest + } else { + res.Digest = &compdesc.DigestSpec{ + HashAlgorithm: digester.HashAlgorithm, + NormalisationAlgorithm: digester.NormalizationAlgorithm, + Value: digest, + } + } + } + } + + if old != nil { + eq := res.Equivalent(old) + if !eq.IsLocalHashEqual() && c.bridge.IsPersistent() { + if !opts.IsModifyResource() { + return fmt.Errorf("resource would invalidate signature") + } + cd.Signatures = nil + } + } + + if old == nil { + cd.Resources = append(cd.Resources, *res) + } else { + cd.Resources[idx] = *res + } + return c.bridge.Update(false) + }) +} + +// evaluateResourceDigest evaluate given potentially partly set digest to determine defaults. +func (c *componentVersionAccessView) evaluateResourceDigest(res, old *compdesc.Resource, opts cpi.ModificationOptions) (string, cpi.DigesterType, string) { + var digester cpi.DigesterType + + hashAlgo := opts.DefaultHashAlgorithm + value := "" + if !res.Digest.IsNone() { + if res.Digest.IsComplete() { + value = res.Digest.Value + } + if res.Digest.HashAlgorithm != "" { + hashAlgo = res.Digest.HashAlgorithm + } + if res.Digest.NormalisationAlgorithm != "" { + digester = cpi.DigesterType{ + HashAlgorithm: hashAlgo, + NormalizationAlgorithm: res.Digest.NormalisationAlgorithm, + } + } + } + res.Digest = nil + + // keep potential old digest settings + if old != nil && old.Type == res.Type { + if !old.Digest.IsNone() { + digester.HashAlgorithm = old.Digest.HashAlgorithm + digester.NormalizationAlgorithm = old.Digest.NormalisationAlgorithm + if opts.IsAcceptExistentDigests() && !opts.IsModifyResource() && c.bridge.IsPersistent() { + res.Digest = old.Digest + value = old.Digest.Value + } + } + } + return hashAlgo, digester, value +} + +func (c *componentVersionAccessView) SetSourceByAccess(art cpi.SourceAccess) error { + return setAccess(c, "source", art, + c.SetSource, c.SetSourceBlob) +} + +func (c *componentVersionAccessView) SetSource(meta *cpi.SourceMeta, acc compdesc.AccessSpec) error { + if c.bridge.IsReadOnly() { + return accessio.ErrReadOnly + } + + res := &compdesc.Source{ + SourceMeta: *meta.Copy(), + Access: acc, + } + return c.Execute(func() error { + if res.Version == "" { + res.Version = c.bridge.GetVersion() + } + cd := c.bridge.GetDescriptor() + if idx := cd.GetSourceIndex(&res.SourceMeta); idx == -1 { + cd.Sources = append(cd.Sources, *res) + } else { + cd.Sources[idx] = *res + } + return c.bridge.Update(false) + }) +} + +func (c *componentVersionAccessView) SetReference(ref *cpi.ComponentReference) error { + return c.Execute(func() error { + cd := c.bridge.GetDescriptor() + if idx := cd.GetComponentReferenceIndex(*ref); idx == -1 { + cd.References = append(cd.References, *ref) + } else { + cd.References[idx] = *ref + } + return c.bridge.Update(false) + }) +} + +func (c *componentVersionAccessView) DiscardChanges() { + c.bridge.DiscardChanges() +} + +func (c *componentVersionAccessView) IsPersistent() bool { + return c.bridge.IsPersistent() +} + +func (c *componentVersionAccessView) UseDirectAccess() bool { + return c.bridge.UseDirectAccess() +} + +//////////////////////////////////////////////////////////////////////////////// +// Standard Implementation for descriptor based methods + +func (c *componentVersionAccessView) GetResource(id metav1.Identity) (cpi.ResourceAccess, error) { + r, err := c.GetDescriptor().GetResourceByIdentity(id) + if err != nil { + return nil, err + } + return cpi.NewResourceAccess(c, r.Access, r.ResourceMeta), nil +} + +func (c *componentVersionAccessView) GetResourceIndex(id metav1.Identity) int { + return c.GetDescriptor().GetResourceIndexByIdentity(id) +} + +func (c *componentVersionAccessView) GetResourceByIndex(i int) (cpi.ResourceAccess, error) { + if i < 0 || i >= len(c.GetDescriptor().Resources) { + return nil, errors.ErrInvalid("resource index", strconv.Itoa(i)) + } + r := c.GetDescriptor().Resources[i] + return cpi.NewResourceAccess(c, r.Access, r.ResourceMeta), nil +} + +func (c *componentVersionAccessView) GetResourcesByName(name string, selectors ...compdesc.IdentitySelector) ([]cpi.ResourceAccess, error) { + resources, err := c.GetDescriptor().GetResourcesByName(name, selectors...) + if err != nil { + return nil, err + } + + result := []cpi.ResourceAccess{} + for _, resource := range resources { + result = append(result, cpi.NewResourceAccess(c, resource.Access, resource.ResourceMeta)) + } + return result, nil +} + +func (c *componentVersionAccessView) GetResources() []cpi.ResourceAccess { + result := []cpi.ResourceAccess{} + for _, r := range c.GetDescriptor().Resources { + result = append(result, cpi.NewResourceAccess(c, r.Access, r.ResourceMeta)) + } + return result +} + +// GetResourcesByIdentitySelectors returns resources that match the given identity selectors. +func (c *componentVersionAccessView) GetResourcesByIdentitySelectors(selectors ...compdesc.IdentitySelector) ([]cpi.ResourceAccess, error) { + return c.GetResourcesBySelectors(selectors, nil) +} + +// GetResourcesByResourceSelectors returns resources that match the given resource selectors. +func (c *componentVersionAccessView) GetResourcesByResourceSelectors(selectors ...compdesc.ResourceSelector) ([]cpi.ResourceAccess, error) { + return c.GetResourcesBySelectors(nil, selectors) +} + +// GetResourcesBySelectors returns resources that match the given selector. +func (c *componentVersionAccessView) GetResourcesBySelectors(selectors []compdesc.IdentitySelector, resourceSelectors []compdesc.ResourceSelector) ([]cpi.ResourceAccess, error) { + resources := make([]cpi.ResourceAccess, 0) + rscs := c.GetDescriptor().Resources + for i := range rscs { + selctx := compdesc.NewResourceSelectionContext(i, rscs) + if len(selectors) > 0 { + ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + } + ok, err := compdesc.MatchResourceByResourceSelector(selctx, resourceSelectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + r, err := c.GetResourceByIndex(i) + if err != nil { + return nil, err + } + resources = append(resources, r) + } + if len(resources) == 0 { + return resources, compdesc.NotFound + } + return resources, nil +} + +func (c *componentVersionAccessView) GetSource(id metav1.Identity) (cpi.SourceAccess, error) { + r, err := c.GetDescriptor().GetSourceByIdentity(id) + if err != nil { + return nil, err + } + return cpi.NewSourceAccess(c, r.Access, r.SourceMeta), nil +} + +func (c *componentVersionAccessView) GetSourceIndex(id metav1.Identity) int { + return c.GetDescriptor().GetSourceIndexByIdentity(id) +} + +func (c *componentVersionAccessView) GetSourceByIndex(i int) (cpi.SourceAccess, error) { + if i < 0 || i >= len(c.GetDescriptor().Sources) { + return nil, errors.ErrInvalid("source index", strconv.Itoa(i)) + } + r := c.GetDescriptor().Sources[i] + return cpi.NewSourceAccess(c, r.Access, r.SourceMeta), nil +} + +func (c *componentVersionAccessView) GetSources() []cpi.SourceAccess { + result := []cpi.SourceAccess{} + for _, r := range c.GetDescriptor().Sources { + result = append(result, cpi.NewSourceAccess(c, r.Access, r.SourceMeta)) + } + return result +} + +func (c *componentVersionAccessView) GetReferences() compdesc.References { + return c.GetDescriptor().References +} + +func (c *componentVersionAccessView) GetReference(id metav1.Identity) (cpi.ComponentReference, error) { + return c.GetDescriptor().GetReferenceByIdentity(id) +} + +func (c *componentVersionAccessView) GetReferenceIndex(id metav1.Identity) int { + return c.GetDescriptor().GetReferenceIndexByIdentity(id) +} + +func (c *componentVersionAccessView) GetReferenceByIndex(i int) (cpi.ComponentReference, error) { + if i < 0 || i > len(c.GetDescriptor().References) { + return cpi.ComponentReference{}, errors.ErrInvalid("reference index", strconv.Itoa(i)) + } + return c.GetDescriptor().References[i], nil +} + +func (c *componentVersionAccessView) GetReferencesByName(name string, selectors ...compdesc.IdentitySelector) (compdesc.References, error) { + return c.GetDescriptor().GetReferencesByName(name, selectors...) +} + +// GetReferencesByIdentitySelectors returns references that match the given identity selectors. +func (c *componentVersionAccessView) GetReferencesByIdentitySelectors(selectors ...compdesc.IdentitySelector) (compdesc.References, error) { + return c.GetReferencesBySelectors(selectors, nil) +} + +// GetReferencesByReferenceSelectors returns references that match the given resource selectors. +func (c *componentVersionAccessView) GetReferencesByReferenceSelectors(selectors ...compdesc.ReferenceSelector) (compdesc.References, error) { + return c.GetReferencesBySelectors(nil, selectors) +} + +// GetReferencesBySelectors returns references that match the given selector. +func (c *componentVersionAccessView) GetReferencesBySelectors(selectors []compdesc.IdentitySelector, referenceSelectors []compdesc.ReferenceSelector) (compdesc.References, error) { + references := make(compdesc.References, 0) + refs := c.GetDescriptor().References + for i := range refs { + selctx := compdesc.NewReferenceSelectionContext(i, refs) + if len(selectors) > 0 { + ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + } + ok, err := compdesc.MatchReferencesByReferenceSelector(selctx, referenceSelectors...) + if err != nil { + return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) + } + if !ok { + continue + } + references = append(references, *selctx.ComponentReference) + } + if len(references) == 0 { + return references, compdesc.NotFound + } + return references, nil +} diff --git a/pkg/contexts/ocm/cpi/repocpi/view_r.go b/pkg/contexts/ocm/cpi/repocpi/view_r.go new file mode 100644 index 0000000000..3c94f10fb6 --- /dev/null +++ b/pkg/contexts/ocm/cpi/repocpi/view_r.go @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package repocpi + +import ( + "fmt" + "io" + + "github.com/open-component-model/ocm/pkg/contexts/credentials" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/refmgmt/resource" + "github.com/open-component-model/ocm/pkg/utils" +) + +// View objects are the user facing generic implementations of the context interfaces. +// They are responsible to handle the reference counting and use +// shared implementations objects for th concrete type-specific implementations. +// Additionally, they are used to implement interface functionality which is +// common to all implementations and NOT dependent on the backend system technology. + +//////////////////////////////////////////////////////////////////////////////// + +type _repositoryView interface { + resource.ResourceViewInt[cpi.Repository] // here you have to redeclare +} + +type RepositoryViewManager = resource.ViewManager[cpi.Repository] // here you have to use an alias + +type RepositoryBridge interface { + resource.ResourceImplementation[cpi.Repository] + + GetContext() cpi.Context + + GetSpecification() cpi.RepositorySpec + ComponentLister() cpi.ComponentLister + + ExistsComponentVersion(name string, version string) (bool, error) + LookupComponentVersion(name string, version string) (cpi.ComponentVersionAccess, error) + LookupComponent(name string) (cpi.ComponentAccess, error) + + io.Closer +} + +type repositoryView struct { + _repositoryView + bridge RepositoryBridge +} + +var ( + _ cpi.Repository = (*repositoryView)(nil) + _ credentials.ConsumerIdentityProvider = (*repositoryView)(nil) + _ utils.Unwrappable = (*repositoryView)(nil) +) + +func GetRepositoryBridge(n cpi.Repository) (RepositoryBridge, error) { + if v, ok := n.(*repositoryView); ok { + return v.bridge, nil + } + return nil, errors.ErrNotSupported("repository implementation type", fmt.Sprintf("%T", n)) +} + +func GetRepositoryImplementation(n cpi.Repository) (RepositoryImpl, error) { + if v, ok := n.(*repositoryView); ok { + if b, ok := v.bridge.(*repositoryBridge); ok { + return b.impl, nil + } + return nil, errors.ErrNotSupported("repository base type", fmt.Sprintf("%T", v.bridge)) + } + return nil, errors.ErrNotSupported("repository implementation type", fmt.Sprintf("%T", n)) +} + +func repositoryViewCreator(i RepositoryBridge, v resource.CloserView, d RepositoryViewManager) cpi.Repository { + return &repositoryView{ + _repositoryView: resource.NewView[cpi.Repository](v, d), + bridge: i, + } +} + +// NewNoneRefRepositoryView provides a repository reflecting the state of the +// view manager without holding an additional reference. +func NewNoneRefRepositoryView(i RepositoryBridge) cpi.Repository { + return &repositoryView{ + _repositoryView: resource.NewView[cpi.Repository](resource.NewNonRefView[cpi.Repository](i), i), + bridge: i, + } +} + +func NewRepository(impl RepositoryImpl, kind string, closer ...io.Closer) cpi.Repository { + bridge := newRepositoryBridge(impl, kind, closer...) + if kind == "" { + kind = "OCM repository" + } + return resource.NewResource[cpi.Repository](bridge, repositoryViewCreator, kind, true) +} + +func (r *repositoryView) Unwrap() interface{} { + return r.bridge +} + +func (r *repositoryView) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { + return credentials.GetProvidedConsumerId(r.bridge, uctx...) +} + +func (r *repositoryView) GetIdentityMatcher() string { + return credentials.GetProvidedIdentityMatcher(r.bridge) +} + +func (r *repositoryView) GetSpecification() cpi.RepositorySpec { + return r.bridge.GetSpecification() +} + +func (r *repositoryView) GetContext() cpi.Context { + return r.bridge.GetContext() +} + +func (r *repositoryView) ComponentLister() cpi.ComponentLister { + return r.bridge.ComponentLister() +} + +func (r *repositoryView) ExistsComponentVersion(name string, version string) (ok bool, err error) { + err = r.Execute(func() error { + ok, err = r.bridge.ExistsComponentVersion(name, version) + return err + }) + return ok, err +} + +func (r *repositoryView) LookupComponentVersion(name string, version string) (acc cpi.ComponentVersionAccess, err error) { + err = r.Execute(func() error { + acc, err = r.bridge.LookupComponentVersion(name, version) + return err + }) + return acc, err +} + +func (r *repositoryView) LookupComponent(name string) (acc cpi.ComponentAccess, err error) { + err = r.Execute(func() error { + acc, err = r.bridge.LookupComponent(name) + return err + }) + return acc, err +} + +func (r *repositoryView) NewComponentVersion(comp, vers string, overrides ...bool) (cpi.ComponentVersionAccess, error) { + c, err := refmgmt.ToLazy(r.LookupComponent(comp)) + if err != nil { + return nil, err + } + defer c.Close() + + return c.NewVersion(vers, overrides...) +} + +func (r *repositoryView) AddComponentVersion(cv cpi.ComponentVersionAccess, overrides ...bool) error { + c, err := refmgmt.ToLazy(r.LookupComponent(cv.GetName())) + if err != nil { + return err + } + defer c.Close() + + return c.AddVersion(cv, overrides...) +} diff --git a/pkg/contexts/ocm/cpi/support/compversaccess.go b/pkg/contexts/ocm/cpi/support/compversaccess.go deleted file mode 100644 index 962d6180e5..0000000000 --- a/pkg/contexts/ocm/cpi/support/compversaccess.go +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package support - -import ( - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -type _ComponentVersionAccessImplBase = cpi.ComponentVersionAccessImplBase - -type ComponentVersionAccessImpl interface { - cpi.ComponentVersionAccessImpl - EnablePersistence() bool -} - -type componentVersionAccessImpl struct { - *_ComponentVersionAccessImplBase - lazy bool - directAccess bool - persistent bool - discardChanges bool - base ComponentVersionContainer -} - -var _ ComponentVersionAccessImpl = (*componentVersionAccessImpl)(nil) - -func GetComponentVersionContainer[T ComponentVersionContainer](cv cpi.ComponentVersionAccess) (T, error) { - var _nil T - - impl, err := cpi.GetComponentVersionAccessImplementation(cv) - if err != nil { - return _nil, err - } - if mine, ok := impl.(*componentVersionAccessImpl); ok { - cont, ok := mine.base.(T) - if ok { - return cont, nil - } - return _nil, errors.Newf("non-matching component version implementation %T", mine.base) - } - return _nil, errors.Newf("non-matching component version implementation %T", impl) -} - -func NewComponentVersionAccessImpl(name, version string, container ComponentVersionContainer, lazy, persistent, direct bool) (cpi.ComponentVersionAccessImpl, error) { - base, err := cpi.NewComponentVersionAccessImplBase(container.GetContext(), name, version, container.GetParentViewManager()) - if err != nil { - return nil, err - } - impl := &componentVersionAccessImpl{ - _ComponentVersionAccessImplBase: base, - lazy: lazy, - persistent: persistent, - directAccess: direct, - base: container, - } - container.SetImplementation(impl) - return impl, nil -} - -func (a *componentVersionAccessImpl) EnablePersistence() bool { - if a.discardChanges { - return false - } - a.persistent = true - a.GetStorageContext() - return true -} - -func (a *componentVersionAccessImpl) IsPersistent() bool { - return a.persistent -} - -func (d *componentVersionAccessImpl) UseDirectAccess() bool { - return d.directAccess -} - -func (a *componentVersionAccessImpl) DiscardChanges() { - a.discardChanges = true -} - -func (a *componentVersionAccessImpl) Close() error { - list := errors.ErrListf("closing component version access %s/%s", a.GetName(), a.GetVersion()) - return list.Add(a.base.Close(), a._ComponentVersionAccessImplBase.Close()).Result() -} - -func (a *componentVersionAccessImpl) Repository() cpi.Repository { - return a.base.Repository() -} - -func (a *componentVersionAccessImpl) IsReadOnly() bool { - return a.base.IsReadOnly() -} - -//////////////////////////////////////////////////////////////////////////////// -// with access to actual view - -func (a *componentVersionAccessImpl) AccessMethod(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) { - return a.base.AccessMethod(acc, cv) -} - -func (a *componentVersionAccessImpl) GetInexpensiveContentVersionIdentity(acc cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) string { - return a.base.GetInexpensiveContentVersionIdentity(acc, cv) -} - -func (a *componentVersionAccessImpl) GetDescriptor() *compdesc.ComponentDescriptor { - return a.base.GetDescriptor() -} - -func (a *componentVersionAccessImpl) GetStorageContext() cpi.StorageContext { - return a.base.GetStorageContext() -} - -func (a *componentVersionAccessImpl) AddBlobFor(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { - return a.base.AddBlobFor(blob, refName, global) -} - -func (a *componentVersionAccessImpl) ShouldUpdate(final bool) bool { - if a.discardChanges { - return false - } - if final { - return a.persistent - } - return !a.lazy && a.directAccess && a.persistent -} - -func (a *componentVersionAccessImpl) Update(final bool) error { - if a.ShouldUpdate(final) { - return a.base.Update() - } - return nil -} diff --git a/pkg/contexts/ocm/cpi/support/container.go b/pkg/contexts/ocm/cpi/support/container.go deleted file mode 100644 index df9a1bc895..0000000000 --- a/pkg/contexts/ocm/cpi/support/container.go +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package support - -import ( - "io" - - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt" -) - -// BlobContainer is the interface for an element capable to store blobs. -type BlobContainer interface { - GetBlobData(name string) (cpi.DataAccess, error) - - // GetStorageContext creates a storage context for blobs - // that is used to feed blob handlers for specific blob storage methods. - // If no handler accepts the blob, the AddBlobFor method will - // be used to store the blob - GetStorageContext() cpi.StorageContext - - // AddBlobFor stores a local blob together with the component and - // potentially provides a global reference according to the OCI distribution spec - // if the blob described an oci artifact. - // The resulting access information (global and local) is provided as - // an access method specification usable in a component descriptor. - // This is the direct technical storage, without caring about any handler. - AddBlobFor(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) -} - -// ComponentVersionContainer is the interface of an element hosting a component version. -type ComponentVersionContainer interface { - SetImplementation(impl ComponentVersionAccessImpl) - - GetParentViewManager() cpi.ComponentAccessViewManager - - GetContext() cpi.Context - Repository() cpi.Repository - - IsReadOnly() bool - Update() error - - GetDescriptor() *compdesc.ComponentDescriptor - BlobContainer - AccessMethod(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) (cpi.AccessMethod, error) - GetInexpensiveContentVersionIdentity(a cpi.AccessSpec, cv refmgmt.ExtendedAllocatable) string - - io.Closer -} diff --git a/pkg/contexts/ocm/cpi/support/doc.go b/pkg/contexts/ocm/cpi/support/doc.go deleted file mode 100644 index de614ae9a4..0000000000 --- a/pkg/contexts/ocm/cpi/support/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -/* -Package support provides a standard implementation for the object type set -required to implement the OCM repository interface. - -This implementation is based on three interfaces that have to implemented: - - - BlobContainer - is used to provide access to blob data - - ComponentVersionContainer - is used to provide access to component version for component. - -The function NewComponentVersionAccessImpl can be used to create an -object implementing the complete ComponentVersionAccess contract. -*/ -package support diff --git a/pkg/contexts/ocm/cpi/support/error.go b/pkg/contexts/ocm/cpi/support/error.go deleted file mode 100644 index 56b12f7e41..0000000000 --- a/pkg/contexts/ocm/cpi/support/error.go +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package support - -import "fmt" - -type UpdateComponentVersionContainerError struct { - Name string - Version string - - Original error -} - -func (e UpdateComponentVersionContainerError) Error() string { - message := fmt.Sprintf( - "unable to update '%s:%s' base component container", - e.Name, - e.Version, - ) - - if e.Original != nil { - message = fmt.Sprintf("%s: %s", message, e.Original.Error()) - } - - return message -} - -func (e UpdateComponentVersionContainerError) Unwrap() error { - return e.Original -} - -type AccessCheckError struct { - Name string - Version string - Type string - - Original error -} - -func (e AccessCheckError) Error() string { - message := fmt.Sprintf( - "failed access spec check on '%s:%s' with type '%s'", - e.Name, - e.Version, - e.Type, - ) - - if e.Original != nil { - message = fmt.Sprintf("%s: %s", message, e.Original.Error()) - } - - return message -} - -func (e AccessCheckError) Unwrap() error { - return e.Original -} diff --git a/pkg/contexts/ocm/cpi/view.go b/pkg/contexts/ocm/cpi/view.go deleted file mode 100644 index 490d294e3d..0000000000 --- a/pkg/contexts/ocm/cpi/view.go +++ /dev/null @@ -1,1425 +0,0 @@ -// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package cpi - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "sync" - - "github.com/opencontainers/go-digest" - - "github.com/open-component-model/ocm/pkg/blobaccess" - "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/oci/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/compose" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/keepblobattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" - metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" - "github.com/open-component-model/ocm/pkg/contexts/ocm/plugin/descriptor" - "github.com/open-component-model/ocm/pkg/errors" - "github.com/open-component-model/ocm/pkg/finalizer" - "github.com/open-component-model/ocm/pkg/refmgmt" - "github.com/open-component-model/ocm/pkg/refmgmt/resource" - "github.com/open-component-model/ocm/pkg/utils" - "github.com/open-component-model/ocm/pkg/utils/selector" -) - -// View objects are the user facing generic implementations of the context interfaces. -// They are responsible to handle the reference counting and use -// shared implementations objects for th concrete type-specific implementations. -// Additionally, they are used to implement interface functionality which is -// common to all implementations and NOT dependent on the backend system technology. - -var ( - ErrClosed = resource.ErrClosed - ErrTempVersion = fmt.Errorf("temporary component version cannot be updated") -) - -//////////////////////////////////////////////////////////////////////////////// - -type _RepositoryView interface { - resource.ResourceViewInt[Repository] // here you have to redeclare -} - -type RepositoryViewManager = resource.ViewManager[Repository] // here you have to use an alias - -type RepositoryImpl interface { - resource.ResourceImplementation[Repository] - internal.RepositoryImpl -} - -type _RepositoryImplBase = resource.ResourceImplBase[Repository] - -type RepositoryImplBase struct { - _RepositoryImplBase - ctx Context -} - -func (b *RepositoryImplBase) GetContext() Context { - return b.ctx -} - -func NewRepositoryImplBase(ctx Context, closer ...io.Closer) *RepositoryImplBase { - base, _ := resource.NewResourceImplBase[Repository, io.Closer](nil, closer...) - return &RepositoryImplBase{ - _RepositoryImplBase: *base, - ctx: ctx, - } -} - -type repositoryView struct { - _RepositoryView - impl RepositoryImpl -} - -var ( - _ Repository = (*repositoryView)(nil) - _ credentials.ConsumerIdentityProvider = (*repositoryView)(nil) - _ utils.Unwrappable = (*repositoryView)(nil) -) - -func GetRepositoryImplementation(n Repository) (RepositoryImpl, error) { - if v, ok := n.(*repositoryView); ok { - return v.impl, nil - } - return nil, errors.ErrNotSupported("repository implementation type", fmt.Sprintf("%T", n)) -} - -func repositoryViewCreator(i RepositoryImpl, v resource.CloserView, d RepositoryViewManager) Repository { - return &repositoryView{ - _RepositoryView: resource.NewView[Repository](v, d), - impl: i, - } -} - -// NewNoneRefRepositoryView provides a repository reflecting the state of the -// view manager without holding an additional reference. -func NewNoneRefRepositoryView(i RepositoryImpl) Repository { - return &repositoryView{ - _RepositoryView: resource.NewView[Repository](resource.NewNonRefView[Repository](i), i), - impl: i, - } -} - -func NewRepository(impl RepositoryImpl, name ...string) Repository { - return resource.NewResource[Repository](impl, repositoryViewCreator, utils.OptionalDefaulted("OCM repo", name...), true) -} - -func (r *repositoryView) Unwrap() interface{} { - return r.impl -} - -func (r *repositoryView) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return credentials.GetProvidedConsumerId(r.impl, uctx...) -} - -func (r *repositoryView) GetIdentityMatcher() string { - return credentials.GetProvidedIdentityMatcher(r.impl) -} - -func (r *repositoryView) GetSpecification() RepositorySpec { - return r.impl.GetSpecification() -} - -func (r *repositoryView) GetContext() Context { - return r.impl.GetContext() -} - -func (r *repositoryView) ComponentLister() ComponentLister { - return r.impl.ComponentLister() -} - -func (r *repositoryView) ExistsComponentVersion(name string, version string) (ok bool, err error) { - err = r.Execute(func() error { - ok, err = r.impl.ExistsComponentVersion(name, version) - return err - }) - return ok, err -} - -func (r *repositoryView) LookupComponentVersion(name string, version string) (acc ComponentVersionAccess, err error) { - err = r.Execute(func() error { - acc, err = r.impl.LookupComponentVersion(name, version) - return err - }) - return acc, err -} - -func (r *repositoryView) LookupComponent(name string) (acc ComponentAccess, err error) { - err = r.Execute(func() error { - acc, err = r.impl.LookupComponent(name) - return err - }) - return acc, err -} - -func (r *repositoryView) NewComponentVersion(comp, vers string, overrides ...bool) (ComponentVersionAccess, error) { - c, err := refmgmt.ToLazy(r.LookupComponent(comp)) - if err != nil { - return nil, err - } - defer c.Close() - - return c.NewVersion(vers, overrides...) -} - -func (r *repositoryView) AddComponentVersion(cv ComponentVersionAccess, overrides ...bool) error { - c, err := refmgmt.ToLazy(r.LookupComponent(cv.GetName())) - if err != nil { - return err - } - defer c.Close() - - return c.AddVersion(cv, overrides...) -} - -//////////////////////////////////////////////////////////////////////////////// - -type _ComponentAccessView interface { - resource.ResourceViewInt[ComponentAccess] // here you have to redeclare -} - -type ComponentAccessViewManager = resource.ViewManager[ComponentAccess] // here you have to use an alias - -type ComponentAccessImpl interface { - resource.ResourceImplementation[ComponentAccess] - internal.ComponentAccessImpl - - IsReadOnly() bool - GetName() string - - IsOwned(access ComponentVersionAccess) bool - - AddVersion(cv ComponentVersionAccess) error -} - -type _ComponentAccessImplBase = resource.ResourceImplBase[ComponentAccess] - -type ComponentAccessImplBase struct { - *_ComponentAccessImplBase - ctx Context - name string -} - -func NewComponentAccessImplBase(ctx Context, name string, repo RepositoryViewManager, closer ...io.Closer) (*ComponentAccessImplBase, error) { - base, err := resource.NewResourceImplBase[ComponentAccess](repo, closer...) - if err != nil { - return nil, err - } - return &ComponentAccessImplBase{ - _ComponentAccessImplBase: base, - ctx: ctx, - name: name, - }, nil -} - -func (b *ComponentAccessImplBase) GetContext() Context { - return b.ctx -} - -func (b *ComponentAccessImplBase) GetName() string { - return b.name -} - -type componentAccessView struct { - _ComponentAccessView - impl ComponentAccessImpl -} - -var ( - _ ComponentAccess = (*componentAccessView)(nil) - _ utils.Unwrappable = (*componentAccessView)(nil) -) - -func GetComponentAccessImplementation(n ComponentAccess) (ComponentAccessImpl, error) { - if v, ok := n.(*componentAccessView); ok { - return v.impl, nil - } - return nil, errors.ErrNotSupported("component implementation type", fmt.Sprintf("%T", n)) -} - -func componentAccessViewCreator(i ComponentAccessImpl, v resource.CloserView, d ComponentAccessViewManager) ComponentAccess { - return &componentAccessView{ - _ComponentAccessView: resource.NewView[ComponentAccess](v, d), - impl: i, - } -} - -func NewComponentAccess(impl ComponentAccessImpl, kind ...string) ComponentAccess { - return resource.NewResource[ComponentAccess](impl, componentAccessViewCreator, fmt.Sprintf("%s %s", utils.OptionalDefaulted("component", kind...), impl.GetName()), true) -} - -func (c *componentAccessView) Unwrap() interface{} { - return c.impl -} - -func (c *componentAccessView) GetContext() Context { - return c.impl.GetContext() -} - -func (c *componentAccessView) GetName() string { - return c.impl.GetName() -} - -func (c *componentAccessView) ListVersions() (list []string, err error) { - err = c.Execute(func() error { - list, err = c.impl.ListVersions() - return err - }) - return list, err -} - -func (c *componentAccessView) LookupVersion(version string) (acc ComponentVersionAccess, err error) { - err = c.Execute(func() error { - acc, err = c.impl.LookupVersion(version) - return err - }) - return acc, err -} - -func (c *componentAccessView) AddVersion(acc ComponentVersionAccess, overrides ...bool) error { - if acc.GetName() != c.GetName() { - return errors.ErrInvalid("component name", acc.GetName()) - } - return c.Execute(func() error { - return c.addVersion(acc, overrides...) - }) -} - -func (c *componentAccessView) addVersion(acc ComponentVersionAccess, overrides ...bool) (ferr error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&ferr) - - ctx := acc.GetContext() - - impl, err := GetComponentVersionAccessImplementation(acc) - if err != nil { - return err - } - - var ( - d *compdesc.ComponentDescriptor - sel func(AccessSpec) bool - eff ComponentVersionAccess - ) - - opts := NewBlobUploadOptions() - - forcestore := c.impl.IsOwned(acc) - if !forcestore { - // transfer all local blobs into a new owned version. - sel = func(spec AccessSpec) bool { return spec.IsLocal(ctx) } - - eff, err = c.impl.NewVersion(acc.GetVersion(), overrides...) - if err != nil { - return err - } - finalize.With(func() error { - return eff.Close() - }) - impl, err = GetComponentVersionAccessImplementation(eff) - if err != nil { - return err - } - - d = eff.GetDescriptor() - *d = *acc.GetDescriptor().Copy() - } else { - // transfer composition blobs into local blobs - opts.UseNoDefaultIfNotSet = true - opts.BlobHandlerProvider = nil - sel = compose.Is - d = acc.GetDescriptor() - eff = acc - } - - err = setupLocalBobs(ctx, "resource", acc, nil, impl, d.Resources, sel, forcestore, opts) - if err == nil { - err = setupLocalBobs(ctx, "source", acc, nil, impl, d.Sources, sel, forcestore, opts) - } - if err != nil { - return err - } - - return c.impl.AddVersion(eff) -} - -func setupLocalBobs(ctx Context, kind string, src ComponentVersionAccess, accprov func(AccessSpec) (AccessMethod, error), tgtimpl ComponentVersionAccessImpl, it compdesc.ArtifactAccessor, sel func(AccessSpec) bool, forcestore bool, opts *BlobUploadOptions) (ferr error) { - var finalize finalizer.Finalizer - defer finalize.FinalizeWithErrorPropagation(&ferr) - - for i := 0; i < it.Len(); i++ { - nested := finalize.Nested() - a := it.GetArtifact(i) - spec, err := ctx.AccessSpecForSpec(a.GetAccess()) - if err != nil { - return errors.Wrapf(err, "%s %d", kind, i) - } - if sel(spec) { - blob, err := blobAccessForLocalAccessSpec(spec, src, accprov) - if err != nil { - return errors.Wrapf(err, "%s %d", kind, i) - } - nested.Close(blob) - - var effspec AccessSpec - if forcestore { - effspec, err = tgtimpl.AddBlobFor(blob, ReferenceHint(spec, src), GlobalAccess(spec, ctx)) - } else { - effspec, err = addBlob(tgtimpl, a.GetType(), ReferenceHint(spec, src), blob, GlobalAccess(spec, ctx)) - } - if err != nil { - return errors.Wrapf(err, "cannot store %s %d", kind, i) - } - a.SetAccess(effspec) - } - err = nested.Finalize() - if err != nil { - return errors.Wrapf(err, "%s %d", kind, i) - } - } - return nil -} - -func blobAccessForLocalAccessSpec(spec AccessSpec, cv ComponentVersionAccess, accprov func(AccessSpec) (AccessMethod, error)) (blobaccess.BlobAccess, error) { - var m AccessMethod - var err error - if accprov != nil { - m, err = accprov(spec) - } else { - m, err = spec.AccessMethod(cv) - } - if err != nil { - return nil, err - } - return m.AsBlobAccess(), nil -} - -func (c *componentAccessView) NewVersion(version string, overrides ...bool) (acc ComponentVersionAccess, err error) { - err = c.Execute(func() error { - if c.impl.IsReadOnly() { - return accessio.ErrReadOnly - } - acc, err = c.impl.NewVersion(version, overrides...) - return err - }) - return acc, err -} - -func (c *componentAccessView) HasVersion(vers string) (ok bool, err error) { - err = c.Execute(func() error { - ok, err = c.impl.HasVersion(vers) - return err - }) - return ok, err -} - -//////////////////////////////////////////////////////////////////////////////// - -type _ComponentVersionAccessView interface { - resource.ResourceViewInt[ComponentVersionAccess] -} - -type ComponentVersionAccessViewManager = resource.ViewManager[ComponentVersionAccess] - -type ComponentVersionAccessImpl interface { - resource.ResourceImplementation[ComponentVersionAccess] - common.VersionedElement - io.Closer - - GetContext() Context - Repository() Repository - - DiscardChanges() - IsPersistent() bool - - GetDescriptor() *compdesc.ComponentDescriptor - - AccessMethod(AccessSpec, refmgmt.ExtendedAllocatable) (AccessMethod, error) - GetInexpensiveContentVersionIdentity(AccessSpec, refmgmt.ExtendedAllocatable) string - - // GetStorageContext creates a storage context for blobs - // that is used to feed blob handlers for specific blob storage methods. - // If no handler accepts the blob, the AddBlobFor method will - // be used to store the blob - GetStorageContext() StorageContext - - // AddBlobFor stores a local blob together with the component and - // potentially provides a global reference. - // The resulting access information (global and local) is provided as - // an access method specification usable in a component descriptor. - // This is the direct technical storage, without caring about any handler. - AddBlobFor(blob BlobAccess, refName string, global AccessSpec) (AccessSpec, error) - - IsReadOnly() bool - - // ShouldUpdate checks, whether an update is indicated - // by the state of object, considering persistence, lazy, discard - // and update mode state - ShouldUpdate(final bool) bool - - // GetBlobCache retieves the blob cache used to store preliminary - // blob accesses for freshly generated local access specs not directly - // usable until a component version is finally added to the repository. - GetBlobCache() BlobCache - - // UseDirectAccess returns true if composition should be directly - // forwarded to the repository backend., - UseDirectAccess() bool - - // Update persists the current state of the component version to the - // underlying repository backend. - Update(final bool) error -} - -type ( - BlobCacheEntry = blobaccess.BlobAccess - BlobCacheKey = interface{} -) - -type BlobCache interface { - // AddBlobFor stores blobs for added blobs not yet accessible - // by generated access method until version is finally added. - AddBlobFor(acc BlobCacheKey, blob BlobCacheEntry) error - - // GetBlobFor retrieves the original blob access for - // a given access specification. - GetBlobFor(acc BlobCacheKey) BlobCacheEntry - - RemoveBlobFor(acc BlobCacheKey) - Clear() error -} - -type blobCache struct { - lock sync.Mutex - blobcache map[BlobCacheKey]BlobCacheEntry -} - -func NewBlobCache() BlobCache { - return &blobCache{ - blobcache: map[BlobCacheKey]BlobCacheEntry{}, - } -} - -func (c *blobCache) RemoveBlobFor(acc BlobCacheKey) { - c.lock.Lock() - defer c.lock.Unlock() - if b := c.blobcache[acc]; b != nil { - b.Close() - delete(c.blobcache, acc) - } -} - -func (c *blobCache) AddBlobFor(acc BlobCacheKey, blob BlobCacheEntry) error { - if s, ok := acc.(string); ok && s == "" { - return errors.ErrInvalid("blob key") - } - c.lock.Lock() - defer c.lock.Unlock() - - if c.blobcache[acc] == nil { - l, err := blob.Dup() - if err != nil { - return err - } - c.blobcache[acc] = l - } - return nil -} - -func (c *blobCache) GetBlobFor(acc BlobCacheKey) BlobCacheEntry { - c.lock.Lock() - defer c.lock.Unlock() - - return c.blobcache[acc] -} - -func (c *blobCache) Clear() error { - list := errors.ErrList() - c.lock.Lock() - defer c.lock.Unlock() - for _, b := range c.blobcache { - list.Add(b.Close()) - } - c.blobcache = map[BlobCacheKey]BlobCacheEntry{} - return list.Result() -} - -type _ComponentVersionAccessImplBase = resource.ResourceImplBase[ComponentVersionAccess] - -type ComponentVersionAccessImplBase struct { - *_ComponentVersionAccessImplBase - ctx Context - name string - version string - - blobcache BlobCache -} - -func NewComponentVersionAccessImplBase(ctx Context, name, version string, repo ComponentAccessViewManager, closer ...io.Closer) (*ComponentVersionAccessImplBase, error) { - base, err := resource.NewResourceImplBase[ComponentVersionAccess](repo, closer...) - if err != nil { - return nil, err - } - return &ComponentVersionAccessImplBase{ - _ComponentVersionAccessImplBase: base, - ctx: ctx, - name: name, - version: version, - blobcache: NewBlobCache(), - }, nil -} - -func (b *ComponentVersionAccessImplBase) Close() error { - list := errors.ErrListf("closing %s", common.VersionedElementKey(b)) - list.Add(b._ComponentVersionAccessImplBase.Close()) - list.Add(b.blobcache.Clear()) - return list.Result() -} - -func (b *ComponentVersionAccessImplBase) GetContext() Context { - return b.ctx -} - -func (b *ComponentVersionAccessImplBase) GetName() string { - return b.name -} - -func (b *ComponentVersionAccessImplBase) GetVersion() string { - return b.version -} - -func (b *ComponentVersionAccessImplBase) GetBlobCache() BlobCache { - return b.blobcache -} - -type componentVersionAccessView struct { - _ComponentVersionAccessView - impl ComponentVersionAccessImpl - err error -} - -var ( - _ ComponentVersionAccess = (*componentVersionAccessView)(nil) - _ utils.Unwrappable = (*componentVersionAccessView)(nil) -) - -func GetComponentVersionAccessImplementation(n ComponentVersionAccess) (ComponentVersionAccessImpl, error) { - if v, ok := n.(*componentVersionAccessView); ok { - return v.impl, nil - } - return nil, errors.ErrNotSupported("component version implementation type", fmt.Sprintf("%T", n)) -} - -func artifactAccessViewCreator(i ComponentVersionAccessImpl, v resource.CloserView, d resource.ViewManager[ComponentVersionAccess]) ComponentVersionAccess { - cv := &componentVersionAccessView{ - _ComponentVersionAccessView: resource.NewView[ComponentVersionAccess](v, d), - impl: i, - } - v.Allocatable().BeforeCleanup(refmgmt.CleanupHandlerFunc(cv.finish)) - return cv -} - -func NewComponentVersionAccess(impl ComponentVersionAccessImpl) ComponentVersionAccess { - return resource.NewResource[ComponentVersionAccess](impl, artifactAccessViewCreator, fmt.Sprintf("component version %s/%s", impl.GetName(), impl.GetVersion()), true) -} - -func (c *componentVersionAccessView) Unwrap() interface{} { - return c.impl -} - -func (c *componentVersionAccessView) Close() error { - list := errors.ErrListf("closing %s", common.VersionedElementKey(c)) - err := c._ComponentVersionAccessView.Close() - return list.Add(c.err, err).Result() -} - -func (c *componentVersionAccessView) finish() { - if !c.IsClosed() { - // prepare artifact access for final close in - // direct access mode. - if !compositionmodeattr.Get(c.GetContext()) { - c.err = c.update(true) - } - } -} - -func (c *componentVersionAccessView) Repository() Repository { - return c.impl.Repository() -} - -func (c *componentVersionAccessView) GetContext() internal.Context { - return c.impl.GetContext() -} - -func (c *componentVersionAccessView) GetName() string { - return c.impl.GetName() -} - -func (c *componentVersionAccessView) GetVersion() string { - return c.impl.GetVersion() -} - -func (c *componentVersionAccessView) GetDescriptor() *compdesc.ComponentDescriptor { - return c.impl.GetDescriptor() -} - -func (c *componentVersionAccessView) GetProvider() *compdesc.Provider { - return c.GetDescriptor().Provider.Copy() -} - -func (c *componentVersionAccessView) SetProvider(p *compdesc.Provider) error { - return c.Execute(func() error { - c.GetDescriptor().Provider = *p.Copy() - return nil - }) -} - -func (c *componentVersionAccessView) AccessMethod(spec AccessSpec) (meth AccessMethod, err error) { - spec, err = c.GetContext().AccessSpecForSpec(spec) - if err != nil { - return nil, err - } - err = c.Execute(func() error { - var err error - meth, err = c.accessMethod(spec) - return err - }) - return meth, err -} - -func (c *componentVersionAccessView) accessMethod(spec AccessSpec) (meth AccessMethod, err error) { - switch { - case compose.Is(spec): - cspec, ok := spec.(*compose.AccessSpec) - if !ok { - return nil, fmt.Errorf("invalid implementation (%T) for access method compose", spec) - } - blob := c.getLocalBlob(cspec) - if blob == nil { - return nil, errors.ErrUnknown(blobaccess.KIND_BLOB, cspec.Id, common.VersionedElementKey(c).String()) - } - meth, err = compose.NewMethod(cspec, blob) - case !spec.IsLocal(c.GetContext()): - meth, err = spec.AccessMethod(c) - default: - meth, err = c.impl.AccessMethod(spec, c.Allocatable()) - if err == nil { - if blob := c.getLocalBlob(spec); blob != nil { - meth, err = newFakeMethod(meth, blob) - } - } - } - return meth, err -} - -func (c *componentVersionAccessView) GetInexpensiveContentVersionIdentity(spec AccessSpec) string { - var err error - - spec, err = c.GetContext().AccessSpecForSpec(spec) - if err != nil { - return "" - } - - var id string - _ = c.Execute(func() error { - id = c.getInexpensiveContentVersionIdentity(spec) - return nil - }) - return id -} - -func (c *componentVersionAccessView) getInexpensiveContentVersionIdentity(spec AccessSpec) string { - switch { - case compose.Is(spec): - fallthrough - case !spec.IsLocal(c.GetContext()): - // fall back to original version - return spec.GetInexpensiveContentVersionIdentity(c) - default: - return c.impl.GetInexpensiveContentVersionIdentity(spec, c.Allocatable()) - } -} - -func (c *componentVersionAccessView) Update() error { - return c.Execute(func() error { - if !c.impl.IsPersistent() { - return ErrTempVersion - } - return c.update(true) - }) -} - -func (c *componentVersionAccessView) update(final bool) error { - if !c.impl.ShouldUpdate(final) { - return nil - } - - ctx := c.GetContext() - d := c.GetDescriptor() - impl, err := GetComponentVersionAccessImplementation(c) - if err != nil { - return err - } - // TODO: exceute for separately lockable view - err = setupLocalBobs(ctx, "resource", c, c.accessMethod, impl, d.Resources, compose.Is, true, nil) - if err == nil { - err = setupLocalBobs(ctx, "source", c, c.accessMethod, impl, d.Sources, compose.Is, true, nil) - } - if err != nil { - return err - } - - err = c.impl.Update(true) - if err != nil { - return err - } - return c.impl.GetBlobCache().Clear() -} - -func (c *componentVersionAccessView) AddBlob(blob cpi.BlobAccess, artType, refName string, global AccessSpec, opts ...internal.BlobUploadOption) (AccessSpec, error) { - if blob == nil { - return nil, errors.New("a resource has to be defined") - } - if c.impl.IsReadOnly() { - return nil, accessio.ErrReadOnly - } - blob, err := blob.Dup() - if err != nil { - return nil, errors.Wrapf(err, "invalid blob access") - } - defer blob.Close() - err = utils.ValidateObject(blob) - if err != nil { - return nil, errors.Wrapf(err, "invalid blob access") - } - - return addBlob(c.impl, artType, refName, blob, global) -} - -func addBlob(impl ComponentVersionAccessImpl, artType, refName string, blob BlobAccess, global AccessSpec) (AccessSpec, error) { - storagectx := impl.GetStorageContext() - ctx := impl.GetContext() - h := ctx.BlobHandlers().LookupHandler(storagectx.GetImplementationRepositoryType(), artType, blob.MimeType()) - if h != nil { - acc, err := h.StoreBlob(blob, artType, refName, nil, storagectx) - if err != nil { - return nil, err - } - if acc != nil { - if !keepblobattr.Get(ctx) || acc.IsLocal(ctx) { - return acc, nil - } - global = acc - } - } - if impl.UseDirectAccess() { - return impl.AddBlobFor(blob, refName, global) - } - // use local composition access to be added to the repository with AddVersion. - acc := compose.New(refName, blob.MimeType(), global) - return cacheLocalBlob(impl, acc, blob) -} - -func (c *componentVersionAccessView) getLocalBlob(acc AccessSpec) BlobAccess { - key, err := json.Marshal(acc) - if err != nil { - return nil - } - return c.impl.GetBlobCache().GetBlobFor(string(key)) -} - -func cacheLocalBlob(impl ComponentVersionAccessImpl, acc AccessSpec, blob BlobAccess) (AccessSpec, error) { - key, err := json.Marshal(acc) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal access spec") - } - // local blobs might not be accessible from the underlying - // repository implementation if the component version is not - // finally added (for example ghcr.io as OCI repository). - // Therefore, we keep a copy of the blob access for further usage. - - // if a local blob is uploader and the access method is replaced - // we have to handle the case that the technical upload repo - // is the same as the storage backend of the OCM repository, which - // might have been configured with local credentials, which were - // reused by the uploader. - // The access spec is independent of the actual repo, so it does - // not have access to those credentials. Therefore, we have to - // keep the original blob for further usage, also. - err = impl.GetBlobCache().AddBlobFor(string(key), blob) - if err != nil { - return nil, err - } - return acc, nil -} - -func (c *componentVersionAccessView) AdjustResourceAccess(meta *ResourceMeta, acc compdesc.AccessSpec, opts ...internal.ModificationOption) error { - cd := c.GetDescriptor() - if idx := cd.GetResourceIndex(meta); idx >= 0 { - return c.SetResource(&cd.Resources[idx].ResourceMeta, acc, opts...) - } - return errors.ErrUnknown(KIND_RESOURCE, meta.GetIdentity(cd.Resources).String()) -} - -// SetResourceBlob adds a blob resource to the component version. -func (c *componentVersionAccessView) SetResourceBlob(meta *ResourceMeta, blob cpi.BlobAccess, refName string, global AccessSpec, opts ...internal.BlobModificationOption) error { - Logger(c).Debug("adding resource blob", "resource", meta.Name) - if err := utils.ValidateObject(blob); err != nil { - return err - } - eff := NewBlobModificationOptions(opts...) - acc, err := c.AddBlob(blob, meta.Type, refName, global, eff) - if err != nil { - return fmt.Errorf("unable to add blob (component %s:%s resource %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) - } - - if err := c.SetResource(meta, acc, eff, ModifyResource()); err != nil { - return fmt.Errorf("unable to set resource: %w", err) - } - - return nil -} - -func (c *componentVersionAccessView) AdjustSourceAccess(meta *SourceMeta, acc compdesc.AccessSpec) error { - cd := c.GetDescriptor() - if idx := cd.GetSourceIndex(meta); idx >= 0 { - return c.SetSource(&cd.Sources[idx].SourceMeta, acc) - } - return errors.ErrUnknown(KIND_RESOURCE, meta.GetIdentity(cd.Resources).String()) -} - -func (c *componentVersionAccessView) SetSourceBlob(meta *SourceMeta, blob BlobAccess, refName string, global AccessSpec) error { - Logger(c).Debug("adding source blob", "source", meta.Name) - if err := utils.ValidateObject(blob); err != nil { - return err - } - acc, err := c.AddBlob(blob, meta.Type, refName, global) - if err != nil { - return fmt.Errorf("unable to add blob: (component %s:%s source %s): %w", c.GetName(), c.GetVersion(), meta.GetName(), err) - } - - if err := c.SetSource(meta, acc); err != nil { - return fmt.Errorf("unable to set source: %w", err) - } - - return nil -} - -type fakeMethod struct { - spec AccessSpec - local bool - mime string - blob blobaccess.BlobAccess -} - -var _ accspeccpi.AccessMethodImpl = (*fakeMethod)(nil) - -func newFakeMethod(m AccessMethod, blob BlobAccess) (AccessMethod, error) { - b, err := blob.Dup() - if err != nil { - return nil, errors.Wrapf(err, "cannot remember blob for access method") - } - f := &fakeMethod{ - spec: m.AccessSpec(), - local: m.IsLocal(), - mime: m.MimeType(), - blob: b, - } - err = m.Close() - if err != nil { - _ = b.Close() - return nil, errors.Wrapf(err, "closing access method") - } - return accspeccpi.AccessMethodForImplementation(f, nil) -} - -func (f *fakeMethod) MimeType() string { - return f.mime -} - -func (f *fakeMethod) IsLocal() bool { - return f.local -} - -func (f *fakeMethod) GetKind() string { - return f.spec.GetKind() -} - -func (f *fakeMethod) AccessSpec() internal.AccessSpec { - return f.spec -} - -func (f *fakeMethod) Close() error { - return f.blob.Close() -} - -func (f *fakeMethod) Reader() (io.ReadCloser, error) { - return f.blob.Reader() -} - -func (f *fakeMethod) Get() ([]byte, error) { - return f.blob.Get() -} - -func setAccess[T any, A internal.ArtifactAccess[T]](c *componentVersionAccessView, kind string, art A, - set func(*T, compdesc.AccessSpec) error, - setblob func(*T, BlobAccess, string, AccessSpec) error, -) error { - if c.impl.IsReadOnly() { - return accessio.ErrReadOnly - } - meta := art.Meta() - if meta == nil { - return errors.Newf("no meta data provided by %s access", kind) - } - acc, err := art.Access() - if err != nil && !errors.IsErrNotFoundElem(err, "", descriptor.KIND_ACCESSMETHOD) { - return err - } - - var ( - blob BlobAccess - hint string - global AccessSpec - ) - - if acc != nil { - if !acc.IsLocal(c.GetContext()) { - return set(meta, acc) - } - - blob, err = accspeccpi.BlobAccessForAccessSpec(acc, c) - if err != nil && errors.IsErrNotFoundElem(err, "", blobaccess.KIND_BLOB) { - return err - } - hint = ReferenceHint(acc, c) - global = GlobalAccess(acc, c.GetContext()) - } - if blob == nil { - blob, err = art.BlobAccess() - if err != nil { - return err - } - defer blob.Close() - } - if blob == nil { - return errors.Newf("neither access nor blob specified in %s access", kind) - } - if v := art.ReferenceHint(); v != "" { - hint = v - } - if v := art.GlobalAccess(); v != nil { - global = v - } - return setblob(meta, blob, hint, global) -} - -func (c *componentVersionAccessView) SetResourceAccess(art ResourceAccess, modopts ...BlobModificationOption) error { - return setAccess(c, "resource", art, - func(meta *ResourceMeta, acc compdesc.AccessSpec) error { - return c.SetResource(meta, acc, NewBlobModificationOptions(modopts...)) - }, - func(meta *ResourceMeta, blob BlobAccess, hint string, global AccessSpec) error { - return c.SetResourceBlob(meta, blob, hint, global, modopts...) - }) -} - -func (c *componentVersionAccessView) SetResource(meta *internal.ResourceMeta, acc compdesc.AccessSpec, modopts ...ModificationOption) error { - if c.impl.IsReadOnly() { - return accessio.ErrReadOnly - } - - res := &compdesc.Resource{ - ResourceMeta: *meta.Copy(), - Access: acc, - } - - ctx := c.impl.GetContext() - opts := internal.NewModificationOptions(modopts...) - CompleteModificationOptions(ctx, opts) - - spec, err := c.impl.GetContext().AccessSpecForSpec(acc) - if err != nil { - return err - } - - // if the blob described by the access spec has been added - // as local blob, just reuse the stored blob access - // to calculate the digest to circumvent credential problems - // for access specs generated by an uploader. - meth, err := c.AccessMethod(spec) - if err != nil { - return err - } - if blob := c.getLocalBlob(spec); blob != nil { - var dig digest.Digest - if s, ok := meth.(blobaccess.DigestSource); ok { - dig = s.Digest() - } - err = meth.Close() - if err != nil { - return errors.Wrapf(err, "clsoing shadowed method") - } - meth, err = accspeccpi.NewDefaultMethodForBlobAccess(c, spec, dig, blob, spec.IsLocal(c.GetContext())) - if err != nil { - return err - } - } - defer meth.Close() - - return c.Execute(func() error { - var old *compdesc.Resource - - if res.Relation == metav1.LocalRelation { - if res.Version == "" { - res.Version = c.GetVersion() - } - } - - cd := c.impl.GetDescriptor() - idx := cd.GetResourceIndex(&res.ResourceMeta) - if idx >= 0 { - old = &cd.Resources[idx] - } - - if old == nil { - if !opts.IsModifyResource() && c.impl.IsPersistent() { - return fmt.Errorf("new resource would invalidate signature") - } - } - - // evaluate given digesting constraints and settings - hashAlgo, digester, digest := c.evaluateResourceDigest(res, old, *opts) - hasher := opts.GetHasher(hashAlgo) - if digester.HashAlgorithm == "" && hasher == nil { - return errors.ErrUnknown(compdesc.KIND_HASH_ALGORITHM, hashAlgo) - } - - if !compdesc.IsNoneAccessKind(res.Access.GetKind()) { - var calculatedDigest *DigestDescriptor - if (!opts.IsSkipVerify() && digest != "") || (!opts.IsSkipDigest() && digest == "") { - dig, err := ctx.BlobDigesters().DetermineDigests(res.Type, hasher, opts.HasherProvider, meth, digester) - if err != nil { - return err - } - if len(dig) == 0 { - return fmt.Errorf("%s: no digester accepts resource", res.Name) - } - calculatedDigest = &dig[0] - } - - if digest != "" && !opts.IsSkipVerify() { - if digest != calculatedDigest.Value { - return fmt.Errorf("digest mismatch: %s != %s", calculatedDigest.Value, digest) - } - } - - if !opts.IsSkipDigest() { - if digest == "" { - res.Digest = calculatedDigest - } else { - res.Digest = &compdesc.DigestSpec{ - HashAlgorithm: digester.HashAlgorithm, - NormalisationAlgorithm: digester.NormalizationAlgorithm, - Value: digest, - } - } - } - } - - if old != nil { - eq := res.Equivalent(old) - if !eq.IsLocalHashEqual() && c.impl.IsPersistent() { - if !opts.IsModifyResource() { - return fmt.Errorf("resource would invalidate signature") - } - cd.Signatures = nil - } - } - - if old == nil { - cd.Resources = append(cd.Resources, *res) - } else { - cd.Resources[idx] = *res - } - return c.update(false) - }) -} - -// evaluateResourceDigest evaluate given potentially partly set digest to determine defaults. -func (c *componentVersionAccessView) evaluateResourceDigest(res, old *compdesc.Resource, opts ModificationOptions) (string, DigesterType, string) { - var digester DigesterType - - hashAlgo := opts.DefaultHashAlgorithm - value := "" - if !res.Digest.IsNone() { - if res.Digest.IsComplete() { - value = res.Digest.Value - } - if res.Digest.HashAlgorithm != "" { - hashAlgo = res.Digest.HashAlgorithm - } - if res.Digest.NormalisationAlgorithm != "" { - digester = DigesterType{ - HashAlgorithm: hashAlgo, - NormalizationAlgorithm: res.Digest.NormalisationAlgorithm, - } - } - } - res.Digest = nil - - // keep potential old digest settings - if old != nil && old.Type == res.Type { - if !old.Digest.IsNone() { - digester.HashAlgorithm = old.Digest.HashAlgorithm - digester.NormalizationAlgorithm = old.Digest.NormalisationAlgorithm - if opts.IsAcceptExistentDigests() && !opts.IsModifyResource() && c.impl.IsPersistent() { - res.Digest = old.Digest - value = old.Digest.Value - } - } - } - return hashAlgo, digester, value -} - -func (c *componentVersionAccessView) SetSourceByAccess(art SourceAccess) error { - return setAccess(c, "source", art, - c.SetSource, c.SetSourceBlob) -} - -func (c *componentVersionAccessView) SetSource(meta *SourceMeta, acc compdesc.AccessSpec) error { - if c.impl.IsReadOnly() { - return accessio.ErrReadOnly - } - - res := &compdesc.Source{ - SourceMeta: *meta.Copy(), - Access: acc, - } - return c.Execute(func() error { - if res.Version == "" { - res.Version = c.impl.GetVersion() - } - cd := c.impl.GetDescriptor() - if idx := cd.GetSourceIndex(&res.SourceMeta); idx == -1 { - cd.Sources = append(cd.Sources, *res) - } else { - cd.Sources[idx] = *res - } - return c.update(false) - }) -} - -func (c *componentVersionAccessView) SetReference(ref *ComponentReference) error { - return c.Execute(func() error { - cd := c.impl.GetDescriptor() - if idx := cd.GetComponentReferenceIndex(*ref); idx == -1 { - cd.References = append(cd.References, *ref) - } else { - cd.References[idx] = *ref - } - return c.update(false) - }) -} - -func (c *componentVersionAccessView) DiscardChanges() { - c.impl.DiscardChanges() -} - -func (c *componentVersionAccessView) IsPersistent() bool { - return c.impl.IsPersistent() -} - -func (c *componentVersionAccessView) UseDirectAccess() bool { - return c.impl.UseDirectAccess() -} - -//////////////////////////////////////////////////////////////////////////////// -// Standard Implementation for descriptor based methods - -func (c *componentVersionAccessView) GetResource(id metav1.Identity) (ResourceAccess, error) { - r, err := c.GetDescriptor().GetResourceByIdentity(id) - if err != nil { - return nil, err - } - return NewResourceAccess(c, r.Access, r.ResourceMeta), nil -} - -func (c *componentVersionAccessView) GetResourceIndex(id metav1.Identity) int { - return c.GetDescriptor().GetResourceIndexByIdentity(id) -} - -func (c *componentVersionAccessView) GetResourceByIndex(i int) (ResourceAccess, error) { - if i < 0 || i >= len(c.GetDescriptor().Resources) { - return nil, errors.ErrInvalid("resource index", strconv.Itoa(i)) - } - r := c.GetDescriptor().Resources[i] - return NewResourceAccess(c, r.Access, r.ResourceMeta), nil -} - -func (c *componentVersionAccessView) GetResourcesByName(name string, selectors ...compdesc.IdentitySelector) ([]ResourceAccess, error) { - resources, err := c.GetDescriptor().GetResourcesByName(name, selectors...) - if err != nil { - return nil, err - } - - result := []ResourceAccess{} - for _, resource := range resources { - result = append(result, NewResourceAccess(c, resource.Access, resource.ResourceMeta)) - } - return result, nil -} - -func (c *componentVersionAccessView) GetResources() []ResourceAccess { - result := []ResourceAccess{} - for _, r := range c.GetDescriptor().Resources { - result = append(result, NewResourceAccess(c, r.Access, r.ResourceMeta)) - } - return result -} - -// GetResourcesByIdentitySelectors returns resources that match the given identity selectors. -func (c *componentVersionAccessView) GetResourcesByIdentitySelectors(selectors ...compdesc.IdentitySelector) ([]ResourceAccess, error) { - return c.GetResourcesBySelectors(selectors, nil) -} - -// GetResourcesByResourceSelectors returns resources that match the given resource selectors. -func (c *componentVersionAccessView) GetResourcesByResourceSelectors(selectors ...compdesc.ResourceSelector) ([]ResourceAccess, error) { - return c.GetResourcesBySelectors(nil, selectors) -} - -// GetResourcesBySelectors returns resources that match the given selector. -func (c *componentVersionAccessView) GetResourcesBySelectors(selectors []compdesc.IdentitySelector, resourceSelectors []compdesc.ResourceSelector) ([]ResourceAccess, error) { - resources := make([]ResourceAccess, 0) - rscs := c.GetDescriptor().Resources - for i := range rscs { - selctx := compdesc.NewResourceSelectionContext(i, rscs) - if len(selectors) > 0 { - ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - } - ok, err := compdesc.MatchResourceByResourceSelector(selctx, resourceSelectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - r, err := c.GetResourceByIndex(i) - if err != nil { - return nil, err - } - resources = append(resources, r) - } - if len(resources) == 0 { - return resources, compdesc.NotFound - } - return resources, nil -} - -func (c *componentVersionAccessView) GetSource(id metav1.Identity) (SourceAccess, error) { - r, err := c.GetDescriptor().GetSourceByIdentity(id) - if err != nil { - return nil, err - } - return NewSourceAccess(c, r.Access, r.SourceMeta), nil -} - -func (c *componentVersionAccessView) GetSourceIndex(id metav1.Identity) int { - return c.GetDescriptor().GetSourceIndexByIdentity(id) -} - -func (c *componentVersionAccessView) GetSourceByIndex(i int) (SourceAccess, error) { - if i < 0 || i >= len(c.GetDescriptor().Sources) { - return nil, errors.ErrInvalid("source index", strconv.Itoa(i)) - } - r := c.GetDescriptor().Sources[i] - return NewSourceAccess(c, r.Access, r.SourceMeta), nil -} - -func (c *componentVersionAccessView) GetSources() []SourceAccess { - result := []SourceAccess{} - for _, r := range c.GetDescriptor().Sources { - result = append(result, NewSourceAccess(c, r.Access, r.SourceMeta)) - } - return result -} - -func (c *componentVersionAccessView) GetReferences() compdesc.References { - return c.GetDescriptor().References -} - -func (c *componentVersionAccessView) GetReference(id metav1.Identity) (ComponentReference, error) { - return c.GetDescriptor().GetReferenceByIdentity(id) -} - -func (c *componentVersionAccessView) GetReferenceIndex(id metav1.Identity) int { - return c.GetDescriptor().GetReferenceIndexByIdentity(id) -} - -func (c *componentVersionAccessView) GetReferenceByIndex(i int) (ComponentReference, error) { - if i < 0 || i > len(c.GetDescriptor().References) { - return ComponentReference{}, errors.ErrInvalid("reference index", strconv.Itoa(i)) - } - return c.GetDescriptor().References[i], nil -} - -func (c *componentVersionAccessView) GetReferencesByName(name string, selectors ...compdesc.IdentitySelector) (compdesc.References, error) { - return c.GetDescriptor().GetReferencesByName(name, selectors...) -} - -// GetReferencesByIdentitySelectors returns references that match the given identity selectors. -func (c *componentVersionAccessView) GetReferencesByIdentitySelectors(selectors ...compdesc.IdentitySelector) (compdesc.References, error) { - return c.GetReferencesBySelectors(selectors, nil) -} - -// GetReferencesByReferenceSelectors returns references that match the given resource selectors. -func (c *componentVersionAccessView) GetReferencesByReferenceSelectors(selectors ...compdesc.ReferenceSelector) (compdesc.References, error) { - return c.GetReferencesBySelectors(nil, selectors) -} - -// GetReferencesBySelectors returns references that match the given selector. -func (c *componentVersionAccessView) GetReferencesBySelectors(selectors []compdesc.IdentitySelector, referenceSelectors []compdesc.ReferenceSelector) (compdesc.References, error) { - references := make(compdesc.References, 0) - refs := c.GetDescriptor().References - for i := range refs { - selctx := compdesc.NewReferenceSelectionContext(i, refs) - if len(selectors) > 0 { - ok, err := selector.MatchSelectors(selctx.Identity(), selectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - } - ok, err := compdesc.MatchReferencesByReferenceSelector(selctx, referenceSelectors...) - if err != nil { - return nil, fmt.Errorf("unable to match selector for resource %s: %w", selctx.Name, err) - } - if !ok { - continue - } - references = append(references, *selctx.ComponentReference) - } - if len(references) == 0 { - return references, compdesc.NotFound - } - return references, nil -} diff --git a/pkg/contexts/ocm/cpi/view_rsc.go b/pkg/contexts/ocm/cpi/view_rsc.go index 4b8868dafb..a88c2a5f40 100644 --- a/pkg/contexts/ocm/cpi/view_rsc.go +++ b/pkg/contexts/ocm/cpi/view_rsc.go @@ -158,6 +158,9 @@ func (b *accessAccessProvider) GetOCMContext() cpi.Context { } func (b *accessAccessProvider) ReferenceHint() string { + if h, ok := b.spec.(HintProvider); ok { + return h.GetReferenceHint(&DummyComponentVersionAccess{b.ctx}) + } return "" } @@ -180,32 +183,24 @@ func (b *accessAccessProvider) BlobAccess() (blobaccess.BlobAccess, error) { //////////////////////////////////////////////////////////////////////////////// type ( - accessProvider = AccessProvider - componentVersionProvider = ComponentVersionProvider + accessProvider = AccessProvider ) type artifactAccessProvider[M any] struct { accessProvider - meta *M + componentVersionProvider ComponentVersionProvider + meta *M } var _ credentials.ConsumerIdentityProvider = (*artifactAccessProvider[any])(nil) -type artifactCVAccessProvider[M any] struct { - artifactAccessProvider[M] - componentVersionProvider -} - func NewArtifactAccessForProvider[M any](meta *M, prov AccessProvider) cpi.ArtifactAccess[M] { aa := &artifactAccessProvider[M]{ accessProvider: prov, meta: meta, } if p, ok := prov.(ComponentVersionProvider); ok { - return &artifactCVAccessProvider[M]{ - artifactAccessProvider: *aa, - componentVersionProvider: p, - } + aa.componentVersionProvider = p } return aa } @@ -232,6 +227,13 @@ func (b *artifactAccessProvider[M]) GetIdentityMatcher() string { return credentials.GetProvidedIdentityMatcher(m) } +func (b *artifactAccessProvider[M]) GetComponentVersion() (ComponentVersionAccess, error) { + if b.componentVersionProvider != nil { + return b.componentVersionProvider.GetComponentVersion() + } + return nil, nil +} + //////////////////////////////////////////////////////////////////////////////// var _ ResourceAccess = (*artifactAccessProvider[ResourceMeta])(nil) diff --git a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go b/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go index 963bbb27bf..34b7f4bb46 100644 --- a/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go +++ b/pkg/contexts/ocm/elements/artifactaccess/genericaccess/resource_test.go @@ -46,7 +46,7 @@ var _ = Describe("dir tree resource access", func() { acc := Must(me.ResourceAccess(env.OCMContext(), compdesc.NewResourceMeta("test", resourcetypes.OCI_IMAGE, compdesc.LocalRelation), spec)) - Expect(acc.ReferenceHint()).To(Equal("")) + Expect(acc.ReferenceHint()).To(Equal(OCINAMESPACE + ":" + OCIVERSION)) Expect(acc.GlobalAccess()).To(BeNil()) Expect(acc.Meta().Type).To(Equal(resourcetypes.OCI_IMAGE)) diff --git a/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go b/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go index cbb3130654..29c039f900 100644 --- a/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go +++ b/pkg/contexts/ocm/elements/artifactblob/externalblob/resource.go @@ -8,7 +8,8 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/elements/artifactaccess/genericaccess" + "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/generics" "github.com/open-component-model/ocm/pkg/optionutils" ) @@ -24,43 +25,41 @@ func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, a global = ocm.GlobalAccess(access, ctx) } - a, err := genericaccess.Access(ctx, meta, access) + prov, err := cpi.NewAccessProviderForExternalAccessSpec(ctx, access) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "invalid external access method %q", access.GetKind()) } - return newAccessProvider[M](a, hint, global), nil + return cpi.NewArtifactAccessForProvider(generics.As[*M](meta), newAccessProvider(prov, hint, global)), nil } -type accessProvider[M any] struct { - cpi.ArtifactAccess[M] +type _accessProvider = cpi.AccessProvider + +type accessProvider struct { + _accessProvider hint string global cpi.AccessSpec } -func newAccessProvider[M any](prov cpi.ArtifactAccess[M], hint string, global cpi.AccessSpec) cpi.ArtifactAccess[M] { - return &accessProvider[M]{ - ArtifactAccess: prov, - hint: hint, - global: global, +func newAccessProvider(prov cpi.AccessProvider, hint string, global cpi.AccessSpec) cpi.AccessProvider { + return &accessProvider{ + _accessProvider: prov, + hint: hint, + global: global, } } -func (p *accessProvider[M]) AccessSpec() cpi.AccessSpec { - return nil -} - -func (p *accessProvider[M]) ReferenceHint() string { +func (p *accessProvider) ReferenceHint() string { if p.hint != "" { return p.hint } - return p.ArtifactAccess.ReferenceHint() + return p._accessProvider.ReferenceHint() } -func (p *accessProvider[M]) GlobalAccess() cpi.AccessSpec { +func (p *accessProvider) GlobalAccess() cpi.AccessSpec { if p.global != nil { return p.global } - return p.ArtifactAccess.GlobalAccess() + return p._accessProvider.GlobalAccess() } func ResourceAccess(ctx ocm.Context, meta *cpi.ResourceMeta, access cpi.AccessSpec, opts ...Option) (cpi.ResourceAccess, error) { diff --git a/pkg/contexts/ocm/interface.go b/pkg/contexts/ocm/interface.go index 4ad64ec641..3548bced5c 100644 --- a/pkg/contexts/ocm/interface.go +++ b/pkg/contexts/ocm/interface.go @@ -12,13 +12,14 @@ import ( metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/internal" "github.com/open-component-model/ocm/pkg/runtime" ) // ErrTempVersion indicates an ignored update in the backend because the // current version has not yet been added to the repository. -var ErrTempVersion = cpi.ErrTempVersion +var ErrTempVersion = repocpi.ErrTempVersion const ( KIND_COMPONENTVERSION = internal.KIND_COMPONENTVERSION diff --git a/pkg/contexts/ocm/internal/accessspecref.go b/pkg/contexts/ocm/internal/accessspecref.go index 5f30c43b39..c61665b96d 100644 --- a/pkg/contexts/ocm/internal/accessspecref.go +++ b/pkg/contexts/ocm/internal/accessspecref.go @@ -45,6 +45,16 @@ func NewRawAccessSpecRef(data []byte, unmarshaler runtime.Unmarshaler) (*AccessS return &AccessSpecRef{generic: &spec}, nil } +func (a *AccessSpecRef) Get() AccessSpec { + if a == nil { + return nil + } + if a.evaluated != nil { + return a.evaluated + } + return a.generic +} + func (a *AccessSpecRef) Set(spec AccessSpec) { if g, ok := spec.(*GenericAccessSpec); ok { a.evaluated = nil diff --git a/pkg/contexts/ocm/internal/modopts.go b/pkg/contexts/ocm/internal/modopts.go index 4c691a9360..c509cbba1c 100644 --- a/pkg/contexts/ocm/internal/modopts.go +++ b/pkg/contexts/ocm/internal/modopts.go @@ -5,6 +5,7 @@ package internal import ( + "github.com/open-component-model/ocm/pkg/optionutils" "github.com/open-component-model/ocm/pkg/utils" ) @@ -18,7 +19,7 @@ type BlobOptionImpl interface { } type BlobUploadOptions struct { - UseNoDefaultIfNotSet bool `json:"noDefaultUpload,omitempty"` + UseNoDefaultIfNotSet *bool `json:"noDefaultUpload,omitempty"` BlobHandlerProvider BlobHandlerProvider `json:"-"` } @@ -43,13 +44,31 @@ func (o *BlobUploadOptions) ApplyBlobModificationOption(opts *BlobModificationOp } func (o *BlobUploadOptions) ApplyBlobUploadOption(opts *BlobUploadOptions) { + optionutils.ApplyOption(o.UseNoDefaultIfNotSet, &opts.UseNoDefaultIfNotSet) if o.BlobHandlerProvider != nil { opts.BlobHandlerProvider = o.BlobHandlerProvider - } else { - opts.UseNoDefaultIfNotSet = true + opts.UseNoDefaultIfNotSet = utils.BoolP(true) } } +//////////////////////////////////////////////////////////////////////////////// + +type nodefaulthandler bool + +func (o nodefaulthandler) ApplyBlobModificationOption(opts *BlobModificationOptions) { + o.ApplyBlobUploadOption(&opts.BlobUploadOptions) +} + +func (o nodefaulthandler) ApplyBlobUploadOption(opts *BlobUploadOptions) { + opts.UseNoDefaultIfNotSet = optionutils.PointerTo(bool(o)) +} + +func UseNoDefaultBlobHandlers(b ...bool) BlobOptionImpl { + return nodefaulthandler(utils.OptionalDefaultedBool(true, b...)) +} + +//////////////////////////////////////////////////////////////////////////////// + type handler struct { blobHandlerProvider BlobHandlerProvider } @@ -131,10 +150,10 @@ func (m *ModificationOptions) ApplyBlobModificationOption(opts *BlobModification } func (m *ModificationOptions) ApplyModificationOption(opts *ModificationOptions) { - applyBool(m.ModifyResource, &opts.ModifyResource) - applyBool(m.AcceptExistentDigests, &opts.AcceptExistentDigests) - applyBool(m.SkipDigest, &opts.SkipDigest) - applyBool(m.SkipVerify, &opts.SkipVerify) + optionutils.ApplyOption(m.ModifyResource, &opts.ModifyResource) + optionutils.ApplyOption(m.AcceptExistentDigests, &opts.AcceptExistentDigests) + optionutils.ApplyOption(m.SkipDigest, &opts.SkipDigest) + optionutils.ApplyOption(m.SkipVerify, &opts.SkipVerify) if m.HasherProvider != nil { opts.HasherProvider = m.HasherProvider } @@ -143,12 +162,6 @@ func (m *ModificationOptions) ApplyModificationOption(opts *ModificationOptions) } } -func applyBool(m *bool, t **bool) { - if m != nil { - *t = utils.BoolP(*m) - } -} - func (m *ModificationOptions) GetHasher(algo ...string) Hasher { return m.HasherProvider.GetHasher(utils.OptionalDefaulted(m.DefaultHashAlgorithm, algo...)) } @@ -299,3 +312,48 @@ func (o *BlobModificationOptions) ApplyBlobUploadOption(opts *BlobUploadOptions) func (o *BlobModificationOptions) ApplyModificationOption(opts *ModificationOptions) { o.ModificationOptions.ApplyModificationOption(opts) } + +/////////////////////////////////////////////////////////////////////////////// + +// BlobModificationOption is used for option list allowing both, +// blob upload and modification options. +type AddVersionOption interface { + ApplyAddVersionOption(*AddVersionOptions) +} + +type AddVersionOptions struct { + Overwrite *bool + BlobUploadOptions +} + +func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { + var m AddVersionOptions + m.ApplyAddVersionOptions(list...) + return &m +} + +func (m *AddVersionOptions) ApplyAddVersionOptions(list ...AddVersionOption) { + for _, o := range list { + if o != nil { + o.ApplyAddVersionOption(m) + } + } +} + +func (o *AddVersionOptions) ApplyAddVersionOption(opts *AddVersionOptions) { + optionutils.ApplyOption(o.Overwrite, &opts.Overwrite) + o.BlobUploadOptions.ApplyBlobUploadOption(&opts.BlobUploadOptions) +} + +//////////////////////////////////////////////////////////////////////////////// + +type overwrite bool + +func (m overwrite) ApplyAddVersionOption(opts *AddVersionOptions) { + opts.Overwrite = utils.BoolP(m) +} + +// Overwrite enabled the overwrite mode for adding a component version. +func Overwrite(flag ...bool) AddVersionOption { + return overwrite(utils.OptionalDefaultedBool(true, flag...)) +} diff --git a/pkg/contexts/ocm/internal/repository.go b/pkg/contexts/ocm/internal/repository.go index 656fe981d8..e87fc06d04 100644 --- a/pkg/contexts/ocm/internal/repository.go +++ b/pkg/contexts/ocm/internal/repository.go @@ -51,7 +51,9 @@ type ( MimeType = blobaccess.MimeType ) -type ComponentAccessImpl interface { +type ComponentAccess interface { + resource.ResourceView[ComponentAccess] + GetContext() Context GetName() string @@ -59,15 +61,10 @@ type ComponentAccessImpl interface { LookupVersion(version string) (ComponentVersionAccess, error) HasVersion(vers string) (bool, error) NewVersion(version string, overrides ...bool) (ComponentVersionAccess, error) - - Close() error -} - -type ComponentAccess interface { - resource.ResourceView[ComponentAccess] - - ComponentAccessImpl AddVersion(cv ComponentVersionAccess, overrides ...bool) error + AddVersionOpt(cv ComponentVersionAccess, opts ...AddVersionOption) error + + io.Closer } // AccessProvider assembled methods provided @@ -92,6 +89,7 @@ type AccessProvider interface { type ArtifactAccess[M any] interface { Meta() *M + GetComponentVersion() (ComponentVersionAccess, error) AccessProvider } diff --git a/pkg/contexts/ocm/modopts.go b/pkg/contexts/ocm/modopts.go index b65f0f06dd..a03eba5cc6 100644 --- a/pkg/contexts/ocm/modopts.go +++ b/pkg/contexts/ocm/modopts.go @@ -20,10 +20,24 @@ type ( BlobUploadOption = internal.BlobUploadOption BlobUploadOptions = internal.BlobUploadOptions + + AddVersionOption = internal.AddVersionOption + AddVersionOptions = internal.AddVersionOptions ) //////////////////////////////////////////////////////////////////////////////// +func NewAddVersionOptions(list ...AddVersionOption) *AddVersionOptions { + return internal.NewAddVersionOptions(list...) +} + +// Overwrite enabled the overwrite mode for adding a component version. +func Overwrite(flag ...bool) AddVersionOption { + return internal.Overwrite(flag...) +} + +//////////////////////////////////////////////////////////////////////////////// + func NewBlobModificationOptions(list ...BlobModificationOption) *BlobModificationOptions { return internal.NewBlobModificationOptions(list...) } diff --git a/pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go b/pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go index f980c949fc..db622c078d 100644 --- a/pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go +++ b/pkg/contexts/ocm/repositories/comparch/accessmethod_localfs.go @@ -13,7 +13,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/refmgmt" ) @@ -23,14 +23,14 @@ type localFilesystemBlobAccessMethod struct { sync.Mutex closed bool spec *localblob.AccessSpec - base support.ComponentVersionContainer + base repocpi.ComponentVersionAccessImpl err error blobAccess blobaccess.BlobAccess } var _ accspeccpi.AccessMethodImpl = (*localFilesystemBlobAccessMethod)(nil) -func newLocalFilesystemBlobAccessMethod(a *localblob.AccessSpec, base support.ComponentVersionContainer, ref refmgmt.ExtendedAllocatable) (accspeccpi.AccessMethod, error) { +func newLocalFilesystemBlobAccessMethod(a *localblob.AccessSpec, base repocpi.ComponentVersionAccessImpl, ref refmgmt.ExtendedAllocatable) (accspeccpi.AccessMethod, error) { m := &localFilesystemBlobAccessMethod{ spec: a, base: base, @@ -86,7 +86,7 @@ func (m *localFilesystemBlobAccessMethod) Reader() (io.ReadCloser, error) { func (m *localFilesystemBlobAccessMethod) getBlob() (blobaccess.BlobAccess, error) { if m.blobAccess == nil { - data, err := m.base.GetBlobData(m.spec.LocalReference) + data, err := m.base.GetBlob(m.spec.LocalReference) if err != nil { return nil, err } diff --git a/pkg/contexts/ocm/repositories/comparch/componentarchive.go b/pkg/contexts/ocm/repositories/comparch/componentarchive.go index b05d267be9..1ee8f4cf25 100644 --- a/pkg/contexts/ocm/repositories/comparch/componentarchive.go +++ b/pkg/contexts/ocm/repositories/comparch/componentarchive.go @@ -16,20 +16,22 @@ import ( ocmhdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/refmgmt" ) //////////////////////////////////////////////////////////////////////////////// +type _componentVersionAccess = cpi.ComponentVersionAccess + // ComponentArchive is the go representation for a component artifact. type ComponentArchive struct { + _componentVersionAccess spec *RepositorySpec container *componentArchiveContainer main cpi.Repository nonref cpi.Repository - cpi.ComponentVersionAccess } // New returns a new representation based element. @@ -47,19 +49,20 @@ func _Wrap(ctx cpi.ContextProvider, obj *accessobj.AccessObject, spec *Repositor return nil, err } s := &componentArchiveContainer{ - ctx: ctx.OCMContext(), - base: accessobj.NewFileSystemBlobAccess(obj), + ctx: ctx.OCMContext(), + fsacc: accessobj.NewFileSystemBlobAccess(obj), + spec: spec, } - impl, err := support.NewComponentVersionAccessImpl(s.GetDescriptor().GetName(), s.GetDescriptor().GetVersion(), s, false, true, true) + cv, err := repocpi.NewComponentVersionAccess(s.GetDescriptor().GetName(), s.GetDescriptor().GetVersion(), s, false, true, true) if err != nil { return nil, err } - s.spec = spec + arch := &ComponentArchive{ spec: spec, container: s, } - arch.ComponentVersionAccess = cpi.NewComponentVersionAccess(impl) + arch._componentVersionAccess = cv arch.main, arch.nonref = newRepository(arch) s.repo = arch.nonref return arch, nil @@ -99,26 +102,26 @@ func (c *ComponentArchive) SetVersion(v string) { //////////////////////////////////////////////////////////////////////////////// type componentArchiveContainer struct { - ctx cpi.Context - impl support.ComponentVersionAccessImpl - base *accessobj.FileSystemBlobAccess - spec *RepositorySpec - repo cpi.Repository + ctx cpi.Context + base repocpi.ComponentVersionAccessBridge + fsacc *accessobj.FileSystemBlobAccess + spec *RepositorySpec + repo cpi.Repository } -var _ support.ComponentVersionContainer = (*componentArchiveContainer)(nil) +var _ repocpi.ComponentVersionAccessImpl = (*componentArchiveContainer)(nil) -func (c *componentArchiveContainer) SetImplementation(impl support.ComponentVersionAccessImpl) { - c.impl = impl +func (c *componentArchiveContainer) SetBridge(base repocpi.ComponentVersionAccessBridge) { + c.base = base } -func (c *componentArchiveContainer) GetParentViewManager() cpi.ComponentAccessViewManager { +func (c *componentArchiveContainer) GetParentBridge() repocpi.ComponentAccessBridge { return nil } func (c *componentArchiveContainer) Close() error { var list errors.ErrorList - return list.Add(c.Update(), c.base.Close()).Result() + return list.Add(c.Update(), c.fsacc.Close()).Result() } func (c *componentArchiveContainer) GetContext() cpi.Context { @@ -130,26 +133,35 @@ func (c *componentArchiveContainer) Repository() cpi.Repository { } func (c *componentArchiveContainer) IsReadOnly() bool { - return c.base.IsReadOnly() + return c.fsacc.IsReadOnly() } func (c *componentArchiveContainer) Update() error { - return c.base.Update() + return c.fsacc.Update() +} + +func (c *componentArchiveContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + if c.fsacc.IsReadOnly() { + return accessobj.ErrReadOnly + } + cur := c.fsacc.GetState().GetState().(*compdesc.ComponentDescriptor) + *cur = *cd.Copy() + return c.fsacc.Update() } func (c *componentArchiveContainer) GetDescriptor() *compdesc.ComponentDescriptor { - if c.base.IsReadOnly() { - return c.base.GetState().GetOriginalState().(*compdesc.ComponentDescriptor) + if c.fsacc.IsReadOnly() { + return c.fsacc.GetState().GetOriginalState().(*compdesc.ComponentDescriptor) } - return c.base.GetState().GetState().(*compdesc.ComponentDescriptor) + return c.fsacc.GetState().GetState().(*compdesc.ComponentDescriptor) } -func (c *componentArchiveContainer) GetBlobData(name string) (cpi.DataAccess, error) { - return c.base.GetBlobDataByName(name) +func (c *componentArchiveContainer) GetBlob(name string) (cpi.DataAccess, error) { + return c.fsacc.GetBlobDataByName(name) } func (c *componentArchiveContainer) GetStorageContext() cpi.StorageContext { - return ocmhdlr.New(c.Repository(), c.impl.GetName(), &BlobSink{c.base}, Type) + return ocmhdlr.New(c.Repository(), c.base.GetName(), &BlobSink{c.fsacc}, Type) } type BlobSink struct { @@ -164,11 +176,11 @@ func (s *BlobSink) AddBlob(blob blobaccess.BlobAccess) (string, error) { return blob.Digest().String(), nil } -func (c *componentArchiveContainer) AddBlobFor(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { +func (c *componentArchiveContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { if blob == nil { return nil, errors.New("a resource has to be defined") } - err := c.base.AddBlob(blob) + err := c.fsacc.AddBlob(blob) if err != nil { return nil, err } diff --git a/pkg/contexts/ocm/repositories/comparch/format.go b/pkg/contexts/ocm/repositories/comparch/format.go index 373d161f31..f4e7bb653b 100644 --- a/pkg/contexts/ocm/repositories/comparch/format.go +++ b/pkg/contexts/ocm/repositories/comparch/format.go @@ -135,5 +135,5 @@ func (h *formatHandler) Create(ctx cpi.ContextProvider, path string, opts access // WriteToFilesystem writes the current object to a filesystem. func (h *formatHandler) Write(obj *Object, path string, opts accessio.Options, mode vfs.FileMode) error { - return h.FormatHandler.Write(obj.container.base.Access(), path, opts, mode) + return h.FormatHandler.Write(obj.container.fsacc.Access(), path, opts, mode) } diff --git a/pkg/contexts/ocm/repositories/comparch/repository.go b/pkg/contexts/ocm/repositories/comparch/repository.go index 604e3bdeb2..7ab49eea54 100644 --- a/pkg/contexts/ocm/repositories/comparch/repository.go +++ b/pkg/contexts/ocm/repositories/comparch/repository.go @@ -15,25 +15,23 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/datacontext/attrs/vfsattr" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" ocmhdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/refmgmt" "github.com/open-component-model/ocm/pkg/utils" ) -type _RepositoryImplBase = cpi.RepositoryImplBase - type RepositoryImpl struct { - _RepositoryImplBase - lock sync.RWMutex - arch *ComponentArchive + lock sync.RWMutex + bridge repocpi.RepositoryBridge + arch *ComponentArchive + nonref cpi.Repository } -var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) +var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) func NewRepository(ctx cpi.Context, s *RepositorySpec) (cpi.Repository, error) { if s.GetPathFileSystem() == nil { @@ -46,14 +44,26 @@ func NewRepository(ctx cpi.Context, s *RepositorySpec) (cpi.Repository, error) { return a.AsRepository(), nil } -func newRepository(a *ComponentArchive) (main cpi.Repository, nonref cpi.Repository) { +func newRepository(a *ComponentArchive) (main, nonref cpi.Repository) { // close main cv abstraction on repository close -------v - base := cpi.NewRepositoryImplBase(a.GetContext(), a.ComponentVersionAccess) impl := &RepositoryImpl{ - _RepositoryImplBase: *base, - arch: a, + arch: a, } - return cpi.NewRepository(impl), cpi.NewNoneRefRepositoryView(impl) + r := repocpi.NewRepository(impl, "comparch") + return r, impl.nonref +} + +func (r *RepositoryImpl) Close() error { + return r.arch.container.Close() +} + +func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { + r.bridge = base + r.nonref = repocpi.NewNoneRefRepositoryView(base) +} + +func (r *RepositoryImpl) GetContext() cpi.Context { + return r.arch.GetContext() } func (r *RepositoryImpl) ComponentLister() cpi.ComponentLister { @@ -118,25 +128,7 @@ func (r *RepositoryImpl) ExistsComponentVersion(name string, ref string) (bool, return r.arch.GetName() == name && r.arch.GetVersion() == ref, nil } -func (r *RepositoryImpl) LookupComponentVersion(name string, version string) (cpi.ComponentVersionAccess, error) { - r.lock.RLock() - defer r.lock.RUnlock() - ok, err := r.ExistsComponentVersion(name, version) - if !ok { - if err == nil { - err = errors.ErrNotFound(cpi.KIND_COMPONENTVERSION, common.NewNameVersion(name, version).String(), Type) - } - return nil, err - } - c, err := newComponentAccess(r) - if err != nil { - return nil, err - } - defer refmgmt.PropagateCloseTemporary(&err, c) // temporary component object not exposed. - return c.LookupVersion(version) -} - -func (r *RepositoryImpl) LookupComponent(name string) (cpi.ComponentAccess, error) { +func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { r.lock.RLock() defer r.lock.RUnlock() if r.arch == nil { @@ -150,25 +142,38 @@ func (r *RepositoryImpl) LookupComponent(name string) (cpi.ComponentAccess, erro //////////////////////////////////////////////////////////////////////////////// -type _ComponentAccessImplBase = cpi.ComponentAccessImplBase - type ComponentAccessImpl struct { - _ComponentAccessImplBase + base repocpi.ComponentAccessBridge repo *RepositoryImpl } -var _ cpi.ComponentAccessImpl = (*ComponentAccessImpl)(nil) +var _ repocpi.ComponentAccessImpl = (*ComponentAccessImpl)(nil) -func newComponentAccess(r *RepositoryImpl) (cpi.ComponentAccess, error) { - base, err := cpi.NewComponentAccessImplBase(r.GetContext(), r.arch.GetName(), r) - if err != nil { - return nil, err - } +func newComponentAccess(r *RepositoryImpl) (*repocpi.ComponentAccessInfo, error) { impl := &ComponentAccessImpl{ - _ComponentAccessImplBase: *base, - repo: r, + repo: r, } - return cpi.NewComponentAccess(impl, "component archive"), nil + return &repocpi.ComponentAccessInfo{impl, "component archive", true}, nil +} + +func (c *ComponentAccessImpl) Close() error { + return nil +} + +func (c *ComponentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { + c.base = base +} + +func (c *ComponentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { + return c.repo.bridge +} + +func (c *ComponentAccessImpl) GetContext() cpi.Context { + return c.repo.GetContext() +} + +func (c *ComponentAccessImpl) GetName() string { + return c.repo.arch.GetName() } func (c *ComponentAccessImpl) IsReadOnly() bool { @@ -183,37 +188,14 @@ func (c *ComponentAccessImpl) HasVersion(vers string) (bool, error) { return vers == c.repo.arch.GetVersion(), nil } -func (c *ComponentAccessImpl) LookupVersion(version string) (cpi.ComponentVersionAccess, error) { +func (c *ComponentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { if version != c.repo.arch.GetVersion() { return nil, errors.ErrNotFound(cpi.KIND_COMPONENTVERSION, fmt.Sprintf("%s:%s", c.GetName(), c.repo.arch.GetVersion())) } return newComponentVersionAccess(c, version, false) } -func (c *ComponentAccessImpl) container(access cpi.ComponentVersionAccess) *componentArchiveContainer { - mine, _ := support.GetComponentVersionContainer[*ComponentVersionContainer](access) - if mine == nil || mine.comp != c { - return nil - } - return mine.comp.repo.arch.container -} - -func (c *ComponentAccessImpl) IsOwned(access cpi.ComponentVersionAccess) bool { - return c.container(access) == c.repo.arch.container -} - -func (c *ComponentAccessImpl) AddVersion(access cpi.ComponentVersionAccess) error { - if access.GetName() != c.GetName() { - return errors.ErrInvalid("component name", access.GetName()) - } - mine := c.container(access) - if mine == nil { - return errors.Newf("component version not owned by component archive") - } - return nil -} - -func (c *ComponentAccessImpl) NewVersion(version string, overrides ...bool) (cpi.ComponentVersionAccess, error) { +func (c *ComponentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { if version != c.repo.arch.GetVersion() { return nil, errors.ErrNotSupported(cpi.KIND_COMPONENTVERSION, version, fmt.Sprintf("component archive %s:%s", c.GetName(), c.repo.arch.GetVersion())) } @@ -226,26 +208,21 @@ func (c *ComponentAccessImpl) NewVersion(version string, overrides ...bool) (cpi //////////////////////////////////////////////////////////////////////////////// type ComponentVersionContainer struct { - impl support.ComponentVersionAccessImpl + impl repocpi.ComponentVersionAccessBridge comp *ComponentAccessImpl descriptor *compdesc.ComponentDescriptor } -var _ support.ComponentVersionContainer = (*ComponentVersionContainer)(nil) +var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) -func newComponentVersionAccess(comp *ComponentAccessImpl, version string, persistent bool) (cpi.ComponentVersionAccess, error) { +func newComponentVersionAccess(comp *ComponentAccessImpl, version string, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { c, err := newComponentVersionContainer(comp) if err != nil { return nil, err } - impl, err := support.NewComponentVersionAccessImpl(comp.GetName(), version, c, true, persistent, !compositionmodeattr.Get(comp.GetContext())) - if err != nil { - c.Close() - return nil, err - } - return cpi.NewComponentVersionAccess(impl), nil + return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil } func newComponentVersionContainer(comp *ComponentAccessImpl) (*ComponentVersionContainer, error) { @@ -255,12 +232,12 @@ func newComponentVersionContainer(comp *ComponentAccessImpl) (*ComponentVersionC }, nil } -func (c *ComponentVersionContainer) SetImplementation(impl support.ComponentVersionAccessImpl) { +func (c *ComponentVersionContainer) SetBridge(impl repocpi.ComponentVersionAccessBridge) { c.impl = impl } -func (c *ComponentVersionContainer) GetParentViewManager() cpi.ComponentAccessViewManager { - return c.comp +func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { + return c.comp.base } func (c *ComponentVersionContainer) Close() error { @@ -285,23 +262,28 @@ func (c *ComponentVersionContainer) Update() error { return c.comp.repo.arch.container.Update() } +func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + *c.descriptor = *cd + return c.Update() +} + func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { return c.descriptor } -func (c *ComponentVersionContainer) GetBlobData(name string) (cpi.DataAccess, error) { - return c.comp.repo.arch.container.GetBlobData(name) +func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { + return c.comp.repo.arch.container.GetBlob(name) } func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { - return ocmhdlr.New(c.Repository(), c.comp.GetName(), &BlobSink{c.comp.repo.arch.container.base}, Type) + return ocmhdlr.New(c.Repository(), c.comp.GetName(), &BlobSink{c.comp.repo.arch.container.fsacc}, Type) } -func (c *ComponentVersionContainer) AddBlobFor(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { +func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { if blob == nil { return nil, errors.New("a resource has to be defined") } - err := c.comp.repo.arch.container.base.AddBlob(blob) + err := c.comp.repo.arch.container.fsacc.AddBlob(blob) if err != nil { return nil, err } diff --git a/pkg/contexts/ocm/repositories/ctf/format.go b/pkg/contexts/ocm/repositories/ctf/format.go index 5b1bc27e76..f2bc385214 100644 --- a/pkg/contexts/ocm/repositories/ctf/format.go +++ b/pkg/contexts/ocm/repositories/ctf/format.go @@ -47,7 +47,7 @@ func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode v if err != nil { return nil, err } - return genericocireg.NewRepository(cpi.FromProvider(ctx), nil, r) + return genericocireg.NewRepository(cpi.FromProvider(ctx), nil, r), nil } func Create(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (cpi.Repository, error) { @@ -55,7 +55,7 @@ func Create(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode if err != nil { return nil, err } - return genericocireg.NewRepository(cpi.FromProvider(ctx), nil, r) + return genericocireg.NewRepository(cpi.FromProvider(ctx), nil, r), nil } //////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/contexts/ocm/repositories/ctf/repo_test.go b/pkg/contexts/ocm/repositories/ctf/repo_test.go index ee9651615d..6553239d65 100644 --- a/pkg/contexts/ocm/repositories/ctf/repo_test.go +++ b/pkg/contexts/ocm/repositories/ctf/repo_test.go @@ -23,6 +23,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/ctf" "github.com/open-component-model/ocm/pkg/contexts/ocm/resourcetypes" "github.com/open-component-model/ocm/pkg/mime" + "github.com/open-component-model/ocm/pkg/refmgmt" ) const COMPONENT = "github.com/mandelsoft/ocm" @@ -36,6 +37,64 @@ var _ = Describe("access method", func() { fs = memoryfs.New() }) + It("adds naked component version and later lookup", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a, "repository") + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c, "component") + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv, "version") + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(final.Finalize()) + + refmgmt.AllocLog.Trace("opening ctf") + a = Must(ctf.Open(ctx, accessobj.ACC_READONLY, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + + refmgmt.AllocLog.Trace("lookup component") + c = Must(a.LookupComponent(COMPONENT)) + final.Close(c) + + refmgmt.AllocLog.Trace("lookup version") + cv = Must(c.LookupVersion(VERSION)) + final.Close(cv) + + refmgmt.AllocLog.Trace("closing") + MustBeSuccessful(final.Finalize()) + }) + + It("adds naked component version and later shortcut lookup", func() { + final := Finalizer{} + defer Defer(final.Finalize) + + a := Must(ctf.Create(ctx, accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a, "repository") + c := Must(a.LookupComponent(COMPONENT)) + final.Close(c, "component") + + cv := Must(c.NewVersion(VERSION)) + final.Close(cv, "version") + + MustBeSuccessful(c.AddVersion(cv)) + MustBeSuccessful(final.Finalize()) + + refmgmt.AllocLog.Trace("opening ctf") + a = Must(ctf.Open(ctx, accessobj.ACC_READONLY, "ctf", 0o700, accessio.PathFileSystem(fs))) + final.Close(a) + + refmgmt.AllocLog.Trace("lookup component version") + cv = Must(a.LookupComponentVersion(COMPONENT, VERSION)) + final.Close(cv) + + refmgmt.AllocLog.Trace("closing") + MustBeSuccessful(final.Finalize()) + }) + It("adds component version", func() { final := Finalizer{} defer Defer(final.Finalize) diff --git a/pkg/contexts/ocm/repositories/genericocireg/component.go b/pkg/contexts/ocm/repositories/genericocireg/component.go index aacadd39f1..9343493847 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/component.go +++ b/pkg/contexts/ocm/repositories/genericocireg/component.go @@ -5,18 +5,17 @@ package genericocireg import ( - "fmt" "strings" "github.com/Masterminds/semver/v3" - "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/oci" "github.com/open-component-model/ocm/pkg/contexts/oci/artdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/errors" + "github.com/open-component-model/ocm/pkg/refmgmt" "github.com/open-component-model/ocm/pkg/utils" ) @@ -24,41 +23,51 @@ const META_SEPARATOR = ".build-" //////////////////////////////////////////////////////////////////////////////// -type _ComponentAccessImplBase = cpi.ComponentAccessImplBase - type componentAccessImpl struct { - _ComponentAccessImplBase + bridge repocpi.ComponentAccessBridge repo *RepositoryImpl name string namespace oci.NamespaceAccess } -func newComponentAccess(repo *RepositoryImpl, name string, main bool) (cpi.ComponentAccess, error) { +func newComponentAccess(repo *RepositoryImpl, name string, main bool) (*repocpi.ComponentAccessInfo, error) { mapped, err := repo.MapComponentNameToNamespace(name) if err != nil { return nil, err } - - base, err := cpi.NewComponentAccessImplBase(repo.GetContext(), name, repo) - if err != nil { - return nil, err - } namespace, err := repo.ocirepo.LookupNamespace(mapped) if err != nil { - base.Close() return nil, err } impl := &componentAccessImpl{ - _ComponentAccessImplBase: *base, - repo: repo, - name: name, - namespace: namespace, + repo: repo, + name: name, + namespace: namespace, } - return cpi.NewComponentAccess(impl, "OCM component[OCI]"), nil + return &repocpi.ComponentAccessInfo{impl, "OCM component[OCI]", main}, nil } func (c *componentAccessImpl) Close() error { - return accessio.Close(c.namespace, c._ComponentAccessImplBase) + refmgmt.AllocLog.Trace("closing component [OCI]", "name", c.name) + err := c.namespace.Close() + refmgmt.AllocLog.Trace("closed component [OCI]", "name", c.name) + return err +} + +func (c *componentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { + c.bridge = base +} + +func (c *componentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { + return c.repo.bridge +} + +func (c *componentAccessImpl) GetContext() cpi.Context { + return c.repo.GetContext() +} + +func (c *componentAccessImpl) GetName() string { + return c.name } //////////////////////////////////////////////////////////////////////////////// @@ -122,13 +131,7 @@ func (c *componentAccessImpl) HasVersion(vers string) (bool, error) { return false, err } -func (c *componentAccessImpl) LookupVersion(version string) (cpi.ComponentVersionAccess, error) { - v, err := c.repo.View() - if err != nil { - return nil, err - } - defer v.Close() - +func (c *componentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { acc, err := c.namespace.GetArtifact(toTag(version)) if err != nil { if errors.IsErrNotFound(err) { @@ -139,41 +142,7 @@ func (c *componentAccessImpl) LookupVersion(version string) (cpi.ComponentVersio return newComponentVersionAccess(accessobj.ACC_WRITABLE, c, version, acc, true) } -func (c *componentAccessImpl) versionContainer(access cpi.ComponentVersionAccess) *ComponentVersionContainer { - mine, _ := support.GetComponentVersionContainer[*ComponentVersionContainer](access) - if mine == nil || mine.comp != c { - return nil - } - return mine -} - -func (c *componentAccessImpl) IsOwned(access cpi.ComponentVersionAccess) bool { - return c.versionContainer(access) != nil -} - -func (c *componentAccessImpl) AddVersion(access cpi.ComponentVersionAccess) error { - if access.GetName() != c.GetName() { - return errors.ErrInvalid("component name", access.GetName()) - } - mine := c.versionContainer(access) - if mine == nil { - return fmt.Errorf("cannot add component version: component version access %s not created for target", access.GetName()+":"+access.GetVersion()) - } - ok := mine.impl.EnablePersistence() - if !ok { - return fmt.Errorf("version has been discarded") - } - // delayed update in close is not done for composition mode - return mine.impl.Update(!mine.impl.UseDirectAccess()) -} - -func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (cpi.ComponentVersionAccess, error) { - v, err := c.View(false) - if err != nil { - return nil, err - } - defer v.Close() - +func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { override := utils.Optional(overrides...) acc, err := c.namespace.GetArtifact(toTag(version)) if err == nil { diff --git a/pkg/contexts/ocm/repositories/genericocireg/componentversion.go b/pkg/contexts/ocm/repositories/genericocireg/componentversion.go index 9ea2eb38fa..808f737f2b 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/componentversion.go +++ b/pkg/contexts/ocm/repositories/genericocireg/componentversion.go @@ -22,35 +22,29 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/ociartifact" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/relativeociref" "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compatattr" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" ocihdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/oci" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/generics" "github.com/open-component-model/ocm/pkg/refmgmt" ) -// newComponentVersionAccess creates an component access for the artifact access, if this fails the artifact acess is closed. -func newComponentVersionAccess(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess, persistent bool) (cpi.ComponentVersionAccess, error) { +// newComponentVersionAccess creates a component access for the artifact access, if this fails the artifact acess is closed. +func newComponentVersionAccess(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { c, err := newComponentVersionContainer(mode, comp, version, access) if err != nil { return nil, err } - impl, err := support.NewComponentVersionAccessImpl(comp.GetName(), version, c, true, persistent, !compositionmodeattr.Get(comp.GetContext())) - if err != nil { - c.Close() - return nil, err - } - return cpi.NewComponentVersionAccess(impl), nil + return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil } // ////////////////////////////////////////////////////////////////////////////// type ComponentVersionContainer struct { - impl support.ComponentVersionAccessImpl + bridge repocpi.ComponentVersionAccessBridge comp *componentAccessImpl version string @@ -59,7 +53,7 @@ type ComponentVersionContainer struct { state accessobj.State } -var _ support.ComponentVersionContainer = (*ComponentVersionContainer)(nil) +var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) func newComponentVersionContainer(mode accessobj.AccessMode, comp *componentAccessImpl, version string, access oci.ArtifactAccess) (*ComponentVersionContainer, error) { m := access.ManifestAccess() @@ -81,12 +75,12 @@ func newComponentVersionContainer(mode accessobj.AccessMode, comp *componentAcce }, nil } -func (c *ComponentVersionContainer) SetImplementation(impl support.ComponentVersionAccessImpl) { - c.impl = impl +func (c *ComponentVersionContainer) SetBridge(impl repocpi.ComponentVersionAccessBridge) { + c.bridge = impl } -func (c *ComponentVersionContainer) GetParentViewManager() cpi.ComponentAccessViewManager { - return c.comp +func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { + return c.comp.bridge } func (c *ComponentVersionContainer) Close() error { @@ -165,6 +159,12 @@ func (c *ComponentVersionContainer) GetInexpensiveContentVersionIdentity(a cpi.A return "" } +func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + cur := c.GetDescriptor() + *cur = *cd + return c.Update() +} + func (c *ComponentVersionContainer) Update() error { logger := Logger(c.GetContext()).WithValues("cv", common.NewNameVersion(c.comp.name, c.version)) err := c.Check() @@ -264,7 +264,7 @@ func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescripto return c.state.GetState().(*compdesc.ComponentDescriptor) } -func (c *ComponentVersionContainer) GetBlobData(name string) (cpi.DataAccess, error) { +func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { return c.manifest.GetBlob(digest.Digest(name)) } @@ -272,7 +272,7 @@ func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { return ocihdlr.New(c.comp.GetName(), c.Repository(), c.comp.repo.ocirepo.GetSpecification().GetKind(), c.comp.repo.ocirepo, c.comp.namespace, c.manifest) } -func (c *ComponentVersionContainer) AddBlobFor(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { +func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { if blob == nil { return nil, errors.New("a resource has to be defined") } diff --git a/pkg/contexts/ocm/repositories/genericocireg/repo_test.go b/pkg/contexts/ocm/repositories/genericocireg/repo_test.go index 27224863c5..7d96fc9b32 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/repo_test.go +++ b/pkg/contexts/ocm/repositories/genericocireg/repo_test.go @@ -39,6 +39,7 @@ import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" metav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/digester/digesters/artifact" "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg" "github.com/open-component-model/ocm/pkg/contexts/ocm/repositories/genericocireg/componentmapping" @@ -83,7 +84,7 @@ var _ = Describe("component repository mapping", func() { defer Defer(finalize.Finalize) repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - impl := Must(cpi.GetRepositoryImplementation(repo)) + impl := Must(repocpi.GetRepositoryImplementation(repo)) Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) @@ -123,7 +124,7 @@ var _ = Describe("component repository mapping", func() { // create repository repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - impl := Must(cpi.GetRepositoryImplementation(repo)) + impl := Must(repocpi.GetRepositoryImplementation(repo)) Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) @@ -172,7 +173,7 @@ var _ = Describe("component repository mapping", func() { // create repository repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) - impl := Must(cpi.GetRepositoryImplementation(repo)) + impl := Must(repocpi.GetRepositoryImplementation(repo)) Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) comp := finalizer.ClosingWith(&finalize, Must(repo.LookupComponent(COMPONENT))) @@ -222,7 +223,7 @@ var _ = Describe("component repository mapping", func() { // create repository repo := finalizer.ClosingWith(&finalize, Must(ctx.RepositoryForSpec(spec))) - impl := Must(cpi.GetRepositoryImplementation(repo)) + impl := Must(repocpi.GetRepositoryImplementation(repo)) Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) ocirepo := genericocireg.GetOCIRepository(repo) Expect(ocirepo).NotTo(BeNil()) @@ -278,7 +279,7 @@ var _ = Describe("component repository mapping", func() { defer Defer(finalize.Finalize, "finalize open elements") repo := finalizer.ClosingWith(&finalize, Must(DefaultContext.RepositoryForSpec(spec))) - impl := Must(cpi.GetRepositoryImplementation(repo)) + impl := Must(repocpi.GetRepositoryImplementation(repo)) Expect(reflect.TypeOf(impl).String()).To(Equal("*genericocireg.RepositoryImpl")) nested := finalize.Nested() diff --git a/pkg/contexts/ocm/repositories/genericocireg/repository.go b/pkg/contexts/ocm/repositories/genericocireg/repository.go index 05ec84b968..88a5824eb2 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/repository.go +++ b/pkg/contexts/ocm/repositories/genericocireg/repository.go @@ -15,9 +15,9 @@ import ( "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/cpi/repocpi" "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/refmgmt" "github.com/open-component-model/ocm/pkg/utils" ) @@ -29,7 +29,7 @@ func GetOCIRepository(r cpi.Repository) ocicpi.Repository { if o, ok := r.(OCIBasedRepository); ok { return o.OCIRepository() } - impl, err := cpi.GetRepositoryImplementation(r) + impl, err := repocpi.GetRepositoryImplementation(r) if err != nil { return nil } @@ -39,29 +39,39 @@ func GetOCIRepository(r cpi.Repository) ocicpi.Repository { return nil } -type _RepositoryImplBase = cpi.RepositoryImplBase - type RepositoryImpl struct { - _RepositoryImplBase + bridge repocpi.RepositoryBridge + ctx cpi.Context meta ComponentRepositoryMeta nonref cpi.Repository ocirepo oci.Repository } var ( - _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) + _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) _ credentials.ConsumerIdentityProvider = (*RepositoryImpl)(nil) ) -func NewRepository(ctx cpi.Context, meta *ComponentRepositoryMeta, ocirepo oci.Repository) (cpi.Repository, error) { +func NewRepository(ctx cpi.Context, meta *ComponentRepositoryMeta, ocirepo oci.Repository) cpi.Repository { impl := &RepositoryImpl{ - _RepositoryImplBase: *cpi.NewRepositoryImplBase(ctx.OCMContext()), - meta: *DefaultComponentRepositoryMeta(meta), - ocirepo: ocirepo, + ctx: ctx, + meta: *DefaultComponentRepositoryMeta(meta), + ocirepo: ocirepo, } - impl.nonref = cpi.NewNoneRefRepositoryView(impl) - r := cpi.NewRepository(impl, "OCM repo[OCI]") - return r, nil + return repocpi.NewRepository(impl, "OCM repo[OCI]") +} + +func (r *RepositoryImpl) Close() error { + return r.ocirepo.Close() +} + +func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { + r.bridge = base + r.nonref = repocpi.NewNoneRefRepositoryView(base) +} + +func (r *RepositoryImpl) GetContext() cpi.Context { + return r.ctx } func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { @@ -82,10 +92,6 @@ func (r *RepositoryImpl) GetIdentityMatcher() string { return "" } -func (r *RepositoryImpl) Close() error { - return r.ocirepo.Close() -} - func (r *RepositoryImpl) OCIRepository() ocicpi.Repository { return r.ocirepo } @@ -169,19 +175,10 @@ func (r *RepositoryImpl) ExistsComponentVersion(name string, version string) (bo return false, nil } -func (r *RepositoryImpl) LookupComponent(name string) (cpi.ComponentAccess, error) { +func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { return newComponentAccess(r, name, true) } -func (r *RepositoryImpl) LookupComponentVersion(name string, version string) (cpi.ComponentVersionAccess, error) { - c, err := newComponentAccess(r, name, false) - if err != nil { - return nil, err - } - defer refmgmt.PropagateCloseTemporary(&err, c) // temporary component object not exposed. - return c.LookupVersion(version) -} - func (r *RepositoryImpl) MapComponentNameToNamespace(name string) (string, error) { switch r.meta.ComponentNameMapping { case OCIRegistryURLPathMapping, "": diff --git a/pkg/contexts/ocm/repositories/genericocireg/type.go b/pkg/contexts/ocm/repositories/genericocireg/type.go index b90282880d..016cee5c51 100644 --- a/pkg/contexts/ocm/repositories/genericocireg/type.go +++ b/pkg/contexts/ocm/repositories/genericocireg/type.go @@ -162,7 +162,7 @@ func (s *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentia if err != nil { return nil, err } - return NewRepository(ctx, &s.ComponentRepositoryMeta, r) + return NewRepository(ctx, &s.ComponentRepositoryMeta, r), nil } func DefaultComponentRepositoryMeta(meta *ComponentRepositoryMeta) *ComponentRepositoryMeta { diff --git a/pkg/contexts/ocm/repositories/virtual/component.go b/pkg/contexts/ocm/repositories/virtual/component.go index 3cd11b627e..b09ad99ee2 100644 --- a/pkg/contexts/ocm/repositories/virtual/component.go +++ b/pkg/contexts/ocm/repositories/virtual/component.go @@ -5,33 +5,47 @@ package virtual import ( - "fmt" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/utils" ) -type _ComponentAccessImplBase = cpi.ComponentAccessImplBase - type componentAccessImpl struct { - _ComponentAccessImplBase + bridge repocpi.ComponentAccessBridge + repo *RepositoryImpl name string } -func newComponentAccess(repo *RepositoryImpl, name string, main bool) (cpi.ComponentAccess, error) { - base, err := cpi.NewComponentAccessImplBase(repo.GetContext(), name, repo) - if err != nil { - return nil, err - } +var _ repocpi.ComponentAccessImpl = (*componentAccessImpl)(nil) + +func newComponentAccess(repo *RepositoryImpl, name string, main bool) (*repocpi.ComponentAccessInfo, error) { impl := &componentAccessImpl{ - _ComponentAccessImplBase: *base, - repo: repo, - name: name, + repo: repo, + name: name, } - return cpi.NewComponentAccess(impl, "OCM component[Simple]"), nil + return &repocpi.ComponentAccessInfo{impl, "OCM component[Simple]", main}, nil +} + +func (c *componentAccessImpl) Close() error { + return nil +} + +func (c *componentAccessImpl) SetBridge(base repocpi.ComponentAccessBridge) { + c.bridge = base +} + +func (c *componentAccessImpl) GetParentBridge() repocpi.RepositoryViewManager { + return c.repo.bridge +} + +func (c *componentAccessImpl) GetContext() cpi.Context { + return c.repo.GetContext() +} + +func (c *componentAccessImpl) GetName() string { + return c.name } func (c *componentAccessImpl) ListVersions() ([]string, error) { @@ -46,7 +60,7 @@ func (c *componentAccessImpl) IsReadOnly() bool { return c.repo.access.IsReadOnly() } -func (c *componentAccessImpl) LookupVersion(version string) (cpi.ComponentVersionAccess, error) { +func (c *componentAccessImpl) LookupVersion(version string) (*repocpi.ComponentVersionAccessInfo, error) { ok, err := c.HasVersion(version) if err != nil { return nil, err @@ -54,48 +68,10 @@ func (c *componentAccessImpl) LookupVersion(version string) (cpi.ComponentVersio if !ok { return nil, cpi.ErrComponentVersionNotFoundWrap(err, c.name, version) } - v, err := c._ComponentAccessImplBase.View() - if err != nil { - return nil, err - } - defer v.Close() - return newComponentVersionAccess(c, version, true) } -func (c *componentAccessImpl) versionContainer(access cpi.ComponentVersionAccess) *ComponentVersionContainer { - mine, _ := support.GetComponentVersionContainer[*ComponentVersionContainer](access) - if mine == nil || mine.comp != c { - return nil - } - return mine -} - -func (c *componentAccessImpl) IsOwned(access cpi.ComponentVersionAccess) bool { - return c.versionContainer(access) != nil -} - -func (c *componentAccessImpl) AddVersion(access cpi.ComponentVersionAccess) error { - if access.GetName() != c.GetName() { - return errors.ErrInvalid("component name", access.GetName()) - } - mine := c.versionContainer(access) - if mine == nil { - return fmt.Errorf("cannot add component version: component version access %s not created for target", access.GetName()+":"+access.GetVersion()) - } - mine.impl.EnablePersistence() - - // delayed update in close is not done for composition mode - return mine.impl.Update(!mine.impl.UseDirectAccess()) -} - -func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (cpi.ComponentVersionAccess, error) { - v, err := c.View(false) - if err != nil { - return nil, err - } - defer v.Close() - +func (c *componentAccessImpl) NewVersion(version string, overrides ...bool) (*repocpi.ComponentVersionAccessInfo, error) { override := utils.Optional(overrides...) ok, err := c.HasVersion(version) if err == nil && ok { diff --git a/pkg/contexts/ocm/repositories/virtual/componentversion.go b/pkg/contexts/ocm/repositories/virtual/componentversion.go index 0e755e0a7f..a74f4c1dbd 100644 --- a/pkg/contexts/ocm/repositories/virtual/componentversion.go +++ b/pkg/contexts/ocm/repositories/virtual/componentversion.go @@ -8,18 +8,17 @@ import ( "github.com/open-component-model/ocm/pkg/common/accessio" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localblob" "github.com/open-component-model/ocm/pkg/contexts/ocm/accessmethods/localfsblob" - "github.com/open-component-model/ocm/pkg/contexts/ocm/attrs/compositionmodeattr" ocmhdlr "github.com/open-component-model/ocm/pkg/contexts/ocm/blobhandler/handlers/ocm" "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/accspeccpi" - "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/support" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" "github.com/open-component-model/ocm/pkg/errors" "github.com/open-component-model/ocm/pkg/refmgmt" ) // newComponentVersionAccess creates a component access for the artifact access, if this fails the artifact acess is closed. -func newComponentVersionAccess(comp *componentAccessImpl, version string, persistent bool) (cpi.ComponentVersionAccess, error) { +func newComponentVersionAccess(comp *componentAccessImpl, version string, persistent bool) (*repocpi.ComponentVersionAccessInfo, error) { access, err := comp.repo.access.GetComponentVersion(comp.GetName(), version) if err != nil { return nil, err @@ -28,25 +27,20 @@ func newComponentVersionAccess(comp *componentAccessImpl, version string, persis if err != nil { return nil, err } - impl, err := support.NewComponentVersionAccessImpl(comp.GetName(), version, c, true, persistent, !compositionmodeattr.Get(comp.GetContext())) - if err != nil { - c.Close() - return nil, err - } - return cpi.NewComponentVersionAccess(impl), nil + return &repocpi.ComponentVersionAccessInfo{c, true, persistent}, nil } // ////////////////////////////////////////////////////////////////////////////// type ComponentVersionContainer struct { - impl support.ComponentVersionAccessImpl + bridge repocpi.ComponentVersionAccessBridge comp *componentAccessImpl version string access VersionAccess } -var _ support.ComponentVersionContainer = (*ComponentVersionContainer)(nil) +var _ repocpi.ComponentVersionAccessImpl = (*ComponentVersionContainer)(nil) func newComponentVersionContainer(comp *componentAccessImpl, version string, access VersionAccess) (*ComponentVersionContainer, error) { return &ComponentVersionContainer{ @@ -56,12 +50,12 @@ func newComponentVersionContainer(comp *componentAccessImpl, version string, acc }, nil } -func (c *ComponentVersionContainer) SetImplementation(impl support.ComponentVersionAccessImpl) { - c.impl = impl +func (c *ComponentVersionContainer) SetBridge(base repocpi.ComponentVersionAccessBridge) { + c.bridge = base } -func (c *ComponentVersionContainer) GetParentViewManager() cpi.ComponentAccessViewManager { - return c.comp +func (c *ComponentVersionContainer) GetParentBridge() repocpi.ComponentAccessBridge { + return c.comp.bridge } func (c *ComponentVersionContainer) Close() error { @@ -140,11 +134,17 @@ func (c *ComponentVersionContainer) Update() error { return c.access.Update() } +func (c *ComponentVersionContainer) SetDescriptor(cd *compdesc.ComponentDescriptor) error { + cur := c.access.GetDescriptor() + *cur = *cd + return c.access.Update() +} + func (c *ComponentVersionContainer) GetDescriptor() *compdesc.ComponentDescriptor { return c.access.GetDescriptor() } -func (c *ComponentVersionContainer) GetBlobData(name string) (cpi.DataAccess, error) { +func (c *ComponentVersionContainer) GetBlob(name string) (cpi.DataAccess, error) { return c.access.GetBlob(name) } @@ -152,7 +152,7 @@ func (c *ComponentVersionContainer) GetStorageContext() cpi.StorageContext { return ocmhdlr.New(c.Repository(), c.comp.GetName(), c.access, Type, c.access) } -func (c *ComponentVersionContainer) AddBlobFor(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { +func (c *ComponentVersionContainer) AddBlob(blob cpi.BlobAccess, refName string, global cpi.AccessSpec) (cpi.AccessSpec, error) { if c.IsReadOnly() { return nil, accessio.ErrReadOnly } diff --git a/pkg/contexts/ocm/repositories/virtual/repository.go b/pkg/contexts/ocm/repositories/virtual/repository.go index 69f5465161..a1cc262e70 100644 --- a/pkg/contexts/ocm/repositories/virtual/repository.go +++ b/pkg/contexts/ocm/repositories/virtual/repository.go @@ -6,41 +6,37 @@ package virtual import ( "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi" - "github.com/open-component-model/ocm/pkg/refmgmt" + "github.com/open-component-model/ocm/pkg/contexts/ocm/cpi/repocpi" ) -type _RepositoryImplBase = cpi.RepositoryImplBase - type RepositoryImpl struct { - _RepositoryImplBase + bridge repocpi.RepositoryBridge + ctx cpi.Context access Access nonref cpi.Repository } -var _ cpi.RepositoryImpl = (*RepositoryImpl)(nil) +var _ repocpi.RepositoryImpl = (*RepositoryImpl)(nil) func NewRepository(ctx cpi.Context, acc Access) cpi.Repository { impl := &RepositoryImpl{ - _RepositoryImplBase: *cpi.NewRepositoryImplBase(ctx.OCMContext()), - access: acc, + ctx: ctx, + access: acc, } - impl.nonref = cpi.NewNoneRefRepositoryView(impl) - r := cpi.NewRepository(impl, "OCM repo[Simple]") - return r + return repocpi.NewRepository(impl, "OCM repo[Simple]") } -/* -func (r *RepositoryImpl) GetConsumerId(uctx ...credentials.UsageContext) credentials.ConsumerIdentity { - return nil +func (r *RepositoryImpl) Close() error { + return r.access.Close() } -func (r *RepositoryImpl) GetIdentityMatcher() string { - return "" +func (r *RepositoryImpl) SetBridge(base repocpi.RepositoryBridge) { + r.bridge = base + r.nonref = repocpi.NewNoneRefRepositoryView(base) } -*/ -func (r *RepositoryImpl) Close() error { - return r.access.Close() +func (r *RepositoryImpl) GetContext() cpi.Context { + return r.ctx } func (r *RepositoryImpl) GetSpecification() cpi.RepositorySpec { @@ -58,15 +54,6 @@ func (r *RepositoryImpl) ExistsComponentVersion(name string, version string) (bo return r.access.ExistsComponentVersion(name, version) } -func (r *RepositoryImpl) LookupComponent(name string) (cpi.ComponentAccess, error) { +func (r *RepositoryImpl) LookupComponent(name string) (*repocpi.ComponentAccessInfo, error) { return newComponentAccess(r, name, true) } - -func (r *RepositoryImpl) LookupComponentVersion(name string, version string) (cpi.ComponentVersionAccess, error) { - c, err := newComponentAccess(r, name, false) - if err != nil { - return nil, err - } - defer refmgmt.PropagateCloseTemporary(&err, c) // temporary component object not exposed. - return c.LookupVersion(version) -} diff --git a/pkg/optionutils/pointer.go b/pkg/optionutils/pointer.go index a982b1c6b6..ae5298b76d 100644 --- a/pkg/optionutils/pointer.go +++ b/pkg/optionutils/pointer.go @@ -16,3 +16,9 @@ func AsValue[T any](p *T) T { } return r } + +func ApplyOption[T any](opt *T, tgt **T) { + if opt != nil { + *tgt = opt + } +} diff --git a/pkg/refmgmt/refcloser.go b/pkg/refmgmt/refcloser.go index 904c5ad40b..4b1e1fea20 100644 --- a/pkg/refmgmt/refcloser.go +++ b/pkg/refmgmt/refcloser.go @@ -115,6 +115,7 @@ func CloseTemporary(c io.Closer) error { if !Lazy(c) { return errors.ErrNotSupported("lazy mode") } + AllocLog.Trace("close temporary ref") return c.Close() } diff --git a/pkg/refmgmt/refmgmt.go b/pkg/refmgmt/refmgmt.go index 5326965b4d..211d4b1654 100644 --- a/pkg/refmgmt/refmgmt.go +++ b/pkg/refmgmt/refmgmt.go @@ -14,7 +14,7 @@ import ( var ALLOC_REALM = logging.DefineSubRealm("reference counting", "refcnt") -var allocLog = logging.DynamicLogger(ALLOC_REALM) +var AllocLog = logging.DynamicLogger(ALLOC_REALM) type Allocatable interface { Ref() error @@ -83,7 +83,7 @@ func (c *refMgmt) Ref() error { return ErrClosed } c.refcount++ - allocLog.Trace("ref", "name", c.name, "refcnt", c.refcount) + AllocLog.Trace("ref", "name", c.name, "refcnt", c.refcount) return nil } @@ -97,7 +97,7 @@ func (c *refMgmt) Unref() error { var err error c.refcount-- - allocLog.Trace("unref", "name", c.name, "refcnt", c.refcount) + AllocLog.Trace("unref", "name", c.name, "refcnt", c.refcount) if c.refcount <= 0 { for _, f := range c.before { f.Cleanup() @@ -135,13 +135,14 @@ func (c *refMgmt) UnrefLast() error { } if c.refcount > 1 { + AllocLog.Trace("unref last failed", "name", c.name, "pending", c.refcount) return errors.ErrStillInUseWrap(errors.Newf("%d reference(s) pending", c.refcount), c.name) } var err error c.refcount-- - allocLog.Trace("unref last", "name", c.name, "refcnt", c.refcount) + AllocLog.Trace("unref last", "name", c.name, "refcnt", c.refcount) if c.refcount <= 0 { for _, f := range c.before { f.Cleanup() @@ -154,7 +155,7 @@ func (c *refMgmt) UnrefLast() error { } if err != nil { - allocLog.Trace("cleanup last failed", "name", c.name, "error", err.Error()) + AllocLog.Trace("cleanup last failed", "name", c.name, "error", err.Error()) return errors.Wrapf(err, "unable to cleanup %s while unref last", c.name) } diff --git a/pkg/refmgmt/resource/resource.go b/pkg/refmgmt/resource/resource.go index 9b4f53c6fd..80626b44b7 100644 --- a/pkg/refmgmt/resource/resource.go +++ b/pkg/refmgmt/resource/resource.go @@ -244,6 +244,12 @@ func NewResourceImplBase[T any, M io.Closer](m ViewManager[M], closer ...io.Clos }, nil } +func NewSimpleResourceImplBase[T any](closer ...io.Closer) *ResourceImplBase[T] { + return &ResourceImplBase[T]{ + closer: closer, + } +} + func (b *ResourceImplBase[T]) SetViewManager(m ViewManager[T]) { b.refs = m } @@ -256,6 +262,10 @@ func (b *ResourceImplBase[T]) Allocatable() refmgmt.ExtendedAllocatable { return b.refs.Allocatable() } +func (b *ResourceImplBase[T]) ViewManager() ViewManager[T] { + return b.refs +} + func (b *ResourceImplBase[T]) View(main ...bool) (T, error) { return b.refs.View(main...) }