diff --git a/.github/config/wordlist.txt b/.github/config/wordlist.txt index 9b99188932..7c36ab7707 100644 --- a/.github/config/wordlist.txt +++ b/.github/config/wordlist.txt @@ -11,6 +11,7 @@ anchore api archlinux artifactid +attr aur auth autoconfigure diff --git a/api/oci/extensions/repositories/ctf/ctf_test.go b/api/oci/extensions/repositories/ctf/ctf_test.go index 405bad9231..305a3afbfa 100644 --- a/api/oci/extensions/repositories/ctf/ctf_test.go +++ b/api/oci/extensions/repositories/ctf/ctf_test.go @@ -84,7 +84,7 @@ var _ = Describe("ctf management", func() { }) It("instantiate filesystem ctf", func() { - r, err := spec.Repository(nil, nil) + r, err := spec.Repository(cpi.DefaultContext(), nil) Expect(err).To(Succeed()) Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) @@ -111,7 +111,7 @@ var _ = Describe("ctf management", func() { It("instantiate tgz artifact", func() { ctf.FormatTGZ.ApplyOption(&spec.StandardOptions) spec.FilePath = "test.tgz" - r, err := spec.Repository(nil, nil) + r, err := spec.Repository(cpi.DefaultContext(), nil) Expect(err).To(Succeed()) n, err := r.LookupNamespace("mandelsoft/test") @@ -156,7 +156,7 @@ var _ = Describe("ctf management", func() { Context("manifest", func() { It("read from filesystem ctf", func() { - r, err := spec.Repository(nil, nil) + r, err := spec.Repository(cpi.DefaultContext(), nil) Expect(err).To(Succeed()) Expect(vfs.DirExists(tempfs, "test/"+ctf.BlobsDirectoryName)).To(BeTrue()) n, err := r.LookupNamespace("mandelsoft/test") @@ -165,7 +165,7 @@ var _ = Describe("ctf management", func() { Expect(n.Close()).To(Succeed()) Expect(r.Close()).To(Succeed()) - r, err = ctf.Open(nil, accessobj.ACC_READONLY, "test", 0, accessio.PathFileSystem(tempfs)) + r, err = ctf.Open(cpi.DefaultContext(), accessobj.ACC_READONLY, "test", 0, accessio.PathFileSystem(tempfs)) Expect(err).To(Succeed()) defer r.Close() diff --git a/api/oci/extensions/repositories/ctf/format.go b/api/oci/extensions/repositories/ctf/format.go index 90ed959b71..c8e972eda1 100644 --- a/api/oci/extensions/repositories/ctf/format.go +++ b/api/oci/extensions/repositories/ctf/format.go @@ -8,6 +8,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" "ocm.software/ocm/api/oci/cpi" "ocm.software/ocm/api/oci/extensions/repositories/ctf/format" "ocm.software/ocm/api/oci/extensions/repositories/ctf/index" @@ -126,8 +127,12 @@ func OpenFromBlob(ctx cpi.ContextProvider, acc accessobj.AccessMode, blob blobac return Open(ctx, acc&accessobj.ACC_READONLY, "", 0, o) } -func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { - o, create, err := accessobj.HandleAccessMode(acc, path, nil, opts...) +func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, olist ...accessio.Option) (*Object, error) { + opts, err := accessio.AccessOptions(&accessio.StandardOptions{PathFileSystem: vfsattr.Get(ctx.OCIContext())}, olist...) + if err != nil { + return nil, err + } + o, create, err := accessobj.HandleAccessMode(acc, path, opts) if err != nil { return nil, err } diff --git a/api/oci/extensions/repositories/ctf/type.go b/api/oci/extensions/repositories/ctf/type.go index 67d782aeb5..0cfd4906ba 100644 --- a/api/oci/extensions/repositories/ctf/type.go +++ b/api/oci/extensions/repositories/ctf/type.go @@ -80,7 +80,10 @@ func (s *RepositorySpec) UniformRepositorySpec() *cpi.UniformRepositorySpec { } func (a *RepositorySpec) Repository(ctx cpi.Context, creds credentials.Credentials) (cpi.Repository, error) { - return Open(ctx, a.AccessMode, a.FilePath, 0o700, &a.StandardOptions) + opts := a.StandardOptions + opts.Default(vfsattr.Get(ctx)) + + return Open(ctx, a.AccessMode, a.FilePath, 0o700, &opts) } func (a *RepositorySpec) Validate(ctx cpi.Context, creds credentials.Credentials, context ...credentials.UsageContext) error { diff --git a/api/ocm/add_test.go b/api/ocm/add_test.go index af008f6e02..7890d50161 100644 --- a/api/ocm/add_test.go +++ b/api/ocm/add_test.go @@ -15,6 +15,11 @@ import ( "ocm.software/ocm/api/utils/mime" ) +const ( + COMPONENT = "acme.org/test" + VERSION = "v1" +) + var _ = Describe("add resources", func() { var ctx ocm.Context var cv ocm.ComponentVersionAccess diff --git a/api/ocm/compdesc/meta/v1/resourceref.go b/api/ocm/compdesc/meta/v1/resourceref.go index 4630ecfab3..ef2a752166 100644 --- a/api/ocm/compdesc/meta/v1/resourceref.go +++ b/api/ocm/compdesc/meta/v1/resourceref.go @@ -7,8 +7,8 @@ type ResourceReference struct { ReferencePath []Identity `json:"referencePath,omitempty"` } -func NewResourceRef(id Identity) ResourceReference { - return ResourceReference{Resource: id} +func NewResourceRef(id Identity, path ...Identity) ResourceReference { + return ResourceReference{Resource: id, ReferencePath: path} } func NewNestedResourceRef(id Identity, path []Identity) ResourceReference { diff --git a/api/ocm/cpi/accspeccpi/interface.go b/api/ocm/cpi/accspeccpi/interface.go index 2334058bd7..044fe9b2cd 100644 --- a/api/ocm/cpi/accspeccpi/interface.go +++ b/api/ocm/cpi/accspeccpi/interface.go @@ -21,6 +21,7 @@ type ( CosumerIdentityProvider = credentials.ConsumerIdentityProvider ComponentVersionAccess = internal.ComponentVersionAccess + DigestSpecProvider = internal.DigestSpecProvider ) var ( diff --git a/api/ocm/cpi/accspeccpi/methodview.go b/api/ocm/cpi/accspeccpi/methodview.go index 670d258f0d..7ef1eeaa6c 100644 --- a/api/ocm/cpi/accspeccpi/methodview.go +++ b/api/ocm/cpi/accspeccpi/methodview.go @@ -7,6 +7,7 @@ import ( "github.com/opencontainers/go-digest" "ocm.software/ocm/api/credentials" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/utils" "ocm.software/ocm/api/utils/blobaccess/blobaccess" "ocm.software/ocm/api/utils/refmgmt" @@ -47,6 +48,9 @@ func BlobAccessForAccessSpec(spec AccessSpec, cv ComponentVersionAccess) (blobac } func accessMethodViewCreator(impl AccessMethodImpl, view *refmgmt.View[AccessMethod]) AccessMethod { + if _, ok := impl.(DigestSpecProvider); ok { + return &accessMethodViewWithDigest{accessMethodView{view, impl}} + } return &accessMethodView{view, impl} } @@ -55,11 +59,20 @@ type accessMethodView struct { methodimpl AccessMethodImpl } +type accessMethodViewWithDigest struct { + accessMethodView +} + var ( _ AccessMethodView = (*accessMethodView)(nil) _ credentials.ConsumerIdentityProvider = (*accessMethodView)(nil) + _ DigestSpecProvider = (*accessMethodViewWithDigest)(nil) ) +func (a *accessMethodViewWithDigest) GetDigestSpec() (*metav1.DigestSpec, error) { + return a.methodimpl.(DigestSpecProvider).GetDigestSpec() +} + func (a *accessMethodView) Unwrap() interface{} { return a.methodimpl } diff --git a/api/ocm/cpi/interface.go b/api/ocm/cpi/interface.go index cfe0077b09..cfda6966a1 100644 --- a/api/ocm/cpi/interface.go +++ b/api/ocm/cpi/interface.go @@ -69,9 +69,12 @@ type ( GenericRepositorySpec = internal.GenericRepositorySpec RepositoryType = internal.RepositoryType ComponentReference = internal.ComponentReference + DigestSpecProvider = internal.DigestSpecProvider ) -type ArtifactAccess[M any] internal.ArtifactAccess[M] +type ArtifactAccess[M any] interface { + internal.ArtifactAccess[M] +} type ( BlobHandler = internal.BlobHandler @@ -194,6 +197,8 @@ const ( KIND_RESOURCE = internal.KIND_RESOURCE KIND_SOURCE = internal.KIND_SOURCE KIND_REFERENCE = internal.KIND_REFERENCE + KIND_REPOSITORYSPEC = internal.KIND_REPOSITORYSPEC + KIND_OCM_REFERENCE = internal.KIND_OCM_REFERENCE ) func ErrComponentVersionNotFound(name, version string) error { diff --git a/api/ocm/cpi/ref.go b/api/ocm/cpi/ref.go index c281602374..9d654f2761 100644 --- a/api/ocm/cpi/ref.go +++ b/api/ocm/cpi/ref.go @@ -1,7 +1,13 @@ package cpi import ( + "strings" "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/general" + + "ocm.software/ocm/api/ocm/grammar" ) type ParseHandler func(u *UniformRepositorySpec) error @@ -45,3 +51,89 @@ func GetRefParseHandler(ty string, h ParseHandler) { func HandleRef(u UniformRepositorySpec) (UniformRepositorySpec, error) { return parseregistry.Handle(u) } + +//////////////////////////////////////////////////////////////////////////////// + +// ParseRepo parses a standard ocm repository reference into a internal representation. +func ParseRepo(ref string) (UniformRepositorySpec, error) { + create := false + if strings.HasPrefix(ref, "+") { + create = true + ref = ref[1:] + } + if strings.HasPrefix(ref, ".") || strings.HasPrefix(ref, "/") { + return HandleRef(UniformRepositorySpec{ + Info: ref, + CreateIfMissing: create, + }) + } + match := grammar.AnchoredRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredSchemedHostPortRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredHostWithPortRepositoryRegexp.FindSubmatch([]byte(ref)) + if match != nil { + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Scheme: string(match[2]), + Host: string(match[3]), + SubPath: string(match[4]), + CreateIfMissing: create, + }) + } + + match = grammar.AnchoredGenericRepositoryRegexp.FindSubmatch([]byte(ref)) + if match == nil { + return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) + } + h := string(match[1]) + t, _ := grammar.SplitTypeSpec(h) + return HandleRef(UniformRepositorySpec{ + Type: t, + TypeHint: h, + Info: string(match[2]), + CreateIfMissing: create, + }) +} + +func ParseRepoToSpec(ctx Context, ref string, create ...bool) (RepositorySpec, error) { + uni, err := ParseRepo(ref) + if err != nil { + return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) + } + if !uni.CreateIfMissing { + uni.CreateIfMissing = general.Optional(create...) + } + repoSpec, err := ctx.MapUniformRepositorySpec(&uni) + if err != nil { + return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) + } + return repoSpec, nil +} diff --git a/api/ocm/cpi/repocpi/interface.go b/api/ocm/cpi/repocpi/interface.go index f3b8be0f49..65da12e456 100644 --- a/api/ocm/cpi/repocpi/interface.go +++ b/api/ocm/cpi/repocpi/interface.go @@ -5,6 +5,7 @@ import ( ) type ( - Context = internal.Context - Repository = internal.Repository + Context = internal.Context + Repository = internal.Repository + DigestSpecProvider = internal.DigestSpecProvider ) diff --git a/api/ocm/cpi/repocpi/view_cv.go b/api/ocm/cpi/repocpi/view_cv.go index 40b9c0fced..43916063ba 100644 --- a/api/ocm/cpi/repocpi/view_cv.go +++ b/api/ocm/cpi/repocpi/view_cv.go @@ -401,14 +401,30 @@ func (c *componentVersionAccessView) SetResource(meta *cpi.ResourceMeta, acc com // evaluate given digesting constraints and settings hashAlgo, digester, digest := c.evaluateResourceDigest(res, old, *opts) + digestForwarded := false + if digest == "" { + if p, ok := meth.(DigestSpecProvider); ok { + dig, err := p.GetDigestSpec() + if dig != nil && err == nil { + // always prefer already known digest with its method + // if no concrete digest value is given by the caller + digest = dig.Value + hashAlgo = dig.HashAlgorithm + digester.HashAlgorithm = hashAlgo + digester.NormalizationAlgorithm = dig.NormalisationAlgorithm + digestForwarded = true + } + } + } + hasher := opts.GetHasher(hashAlgo) - if digester.HashAlgorithm == "" && hasher == nil { + if 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 == "") { + if (!opts.IsSkipVerify() && !digestForwarded && digest != "") || (!opts.IsSkipDigest() && digest == "") { dig, err := ctx.BlobDigesters().DetermineDigests(res.Type, hasher, opts.HasherProvider, meth, digester) if err != nil { return err @@ -417,11 +433,11 @@ func (c *componentVersionAccessView) SetResource(meta *cpi.ResourceMeta, acc com 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 digest != "" && !opts.IsSkipVerify() { + if digest != calculatedDigest.Value { + return fmt.Errorf("digest mismatch: %s != %s", calculatedDigest.Value, digest) + } } } diff --git a/api/ocm/elements/artifactaccess/ocmaccess/forward.go b/api/ocm/elements/artifactaccess/ocmaccess/forward.go new file mode 100644 index 0000000000..b6fcfc4569 --- /dev/null +++ b/api/ocm/elements/artifactaccess/ocmaccess/forward.go @@ -0,0 +1,38 @@ +package ocmaccess + +import ( + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/utils/blobaccess/ocm" +) + +//////////////////////////////////////////////////////////////////////////////// +// Component Version + +func ByComponentVersion(cv cpi.ComponentVersionAccess) ocm.ComponentVersionProvider { + return ocm.ByComponentVersion(cv) +} + +func ByResolverAndName(resolver cpi.ComponentVersionResolver, comp, vers string) ocm.ComponentVersionProvider { + return ocm.ByResolverAndName(resolver, comp, vers) +} + +func ByRepositorySpecAndName(ctx cpi.ContextProvider, spec cpi.RepositorySpec, comp, vers string) ocm.ComponentVersionProvider { + return ocm.ByRepositorySpecAndName(ctx, spec, comp, vers) +} + +//////////////////////////////////////////////////////////////////////////////// +// Resource + +func ByResourceId(id metav1.Identity) ocm.ResourceProvider { + return ocm.ByResourceId(id) +} + +func ByResourcePath(id metav1.Identity, path ...metav1.Identity) ocm.ResourceProvider { + return ocm.ByResourcePath(id, path...) +} + +func ByResourceSelector(sel ...rscsel.Selector) ocm.ResourceProvider { + return ocm.ByResourceSelector(sel...) +} diff --git a/api/ocm/elements/artifactaccess/ocmaccess/resource.go b/api/ocm/elements/artifactaccess/ocmaccess/resource.go new file mode 100644 index 0000000000..48dc7e96c5 --- /dev/null +++ b/api/ocm/elements/artifactaccess/ocmaccess/resource.go @@ -0,0 +1,27 @@ +package ocmaccess + +import ( + "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactaccess/genericaccess" + access "ocm.software/ocm/api/ocm/extensions/accessmethods/ocm" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx ocm.Context, meta P, comp, vers string, repo cpi.RepositorySpec, id metav1.Identity, path ...metav1.Identity) (cpi.ArtifactAccess[M], error) { + spec, err := access.New(comp, vers, repo, id, path...) + if err != nil { + return nil, err + } + // is global access, must work, otherwise there is an error in the lib. + return genericaccess.MustAccess(ctx, meta, spec), nil +} + +func ResourceAccess(ctx ocm.Context, meta *cpi.ResourceMeta, comp, vers string, repo cpi.RepositorySpec, id metav1.Identity, path ...metav1.Identity) (cpi.ResourceAccess, error) { + return Access(ctx, meta, comp, vers, repo, id, path...) +} + +func SourceAccess(ctx ocm.Context, meta *cpi.SourceMeta, comp, vers string, repo cpi.RepositorySpec, id metav1.Identity, path ...metav1.Identity) (cpi.SourceAccess, error) { + return Access(ctx, meta, comp, vers, repo, id, path...) +} diff --git a/api/ocm/elements/artifactblob/ocmblob/access_test.go b/api/ocm/elements/artifactblob/ocmblob/access_test.go new file mode 100644 index 0000000000..b118229b93 --- /dev/null +++ b/api/ocm/elements/artifactblob/ocmblob/access_test.go @@ -0,0 +1,71 @@ +package ocmblob_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + "ocm.software/ocm/api/ocm/elements" + me "ocm.software/ocm/api/ocm/elements/artifactblob/mavenblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/composition" + "ocm.software/ocm/api/tech/maven" + "ocm.software/ocm/api/tech/maven/maventest" +) + +const ( + MAVEN_PATH = "/testdata/.m2/repository" + FAIL_PATH = "/testdata/.m2/fail" + MAVEN_CENTRAL_ADDRESS = "repo.maven.apache.org:443" + MAVEN_CENTRAL = "https://repo.maven.apache.org/maven2/" + MAVEN_GROUP_ID = "maven" + MAVEN_ARTIFACT_ID = "maven" + MAVEN_VERSION = "1.1" +) + +var _ = Describe("blobaccess for maven", func() { + Context("maven filesystem repository", func() { + var env *Builder + var repo *maven.Repository + + BeforeEach(func() { + env = NewBuilder(maventest.TestData()) + repo = maven.NewFileRepository(MAVEN_PATH, env.FileSystem()) + }) + + AfterEach(func() { + MustBeSuccessful(env.Cleanup()) + }) + + It("blobaccess for a single file with classifier and extension", func() { + cv := composition.NewComponentVersion(env.OCMContext(), "acme.org/test", "1.0.0") + defer Close(cv) + + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0", + maven.WithClassifier("random-content"), maven.WithExtension("json")) + + a := me.ResourceAccessForMavenCoords(env.OCMContext(), Must(elements.ResourceMeta("mavenblob", resourcetypes.OCM_JSON, elements.WithLocalRelation())), repo, coords, me.WithCachingFileSystem(env.FileSystem())) + Expect(a.ReferenceHint()).To(Equal("")) + b := Must(a.BlobAccess()) + defer Close(b) + Expect(string(Must(b.Get()))).To(Equal(`{"some": "test content"}`)) + + MustBeSuccessful(cv.SetResourceByAccess(a)) + r := Must(cv.GetResourceByIndex(0)) + m := Must(r.AccessMethod()) + defer Close(m) + Expect(string(Must(m.Get()))).To(Equal(`{"some": "test content"}`)) + }) + + It("blobaccess for package", func() { + cv := composition.NewComponentVersion(env.OCMContext(), "acme.org/test", "1.0.0") + defer Close(cv) + + coords := maven.NewCoordinates("com.sap.cloud.sdk", "sdk-modules-bom", "5.7.0") + + a := me.ResourceAccessForMavenCoords(env.OCMContext(), Must(elements.ResourceMeta("mavenblob", resourcetypes.OCM_JSON, elements.WithLocalRelation())), repo, coords, me.WithCachingFileSystem(env.FileSystem())) + Expect(a.ReferenceHint()).To(Equal(coords.GAV())) + }) + }) +}) diff --git a/api/ocm/elements/artifactblob/ocmblob/forward.go b/api/ocm/elements/artifactblob/ocmblob/forward.go new file mode 100644 index 0000000000..35b0517e11 --- /dev/null +++ b/api/ocm/elements/artifactblob/ocmblob/forward.go @@ -0,0 +1,38 @@ +package ocmblob + +import ( + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/utils/blobaccess/ocm" +) + +//////////////////////////////////////////////////////////////////////////////// +// Component Version + +func ByComponentVersion(cv cpi.ComponentVersionAccess) ocm.ComponentVersionProvider { + return ocm.ByComponentVersion(cv) +} + +func ByResolverAndName(resolver cpi.ComponentVersionResolver, comp, vers string) ocm.ComponentVersionProvider { + return ocm.ByResolverAndName(resolver, comp, vers) +} + +func ByRepositorySpecAndName(ctx cpi.ContextProvider, spec cpi.RepositorySpec, comp, vers string) ocm.ComponentVersionProvider { + return ocm.ByRepositorySpecAndName(ctx, spec, comp, vers) +} + +//////////////////////////////////////////////////////////////////////////////// +// Resource + +func ByResourceId(id metav1.Identity) ocm.ResourceProvider { + return ocm.ByResourceId(id) +} + +func ByResourcePath(id metav1.Identity, path ...metav1.Identity) ocm.ResourceProvider { + return ocm.ByResourcePath(id, path...) +} + +func ByResourceSelector(sel ...rscsel.Selector) ocm.ResourceProvider { + return ocm.ByResourceSelector(sel...) +} diff --git a/api/ocm/elements/artifactblob/ocmblob/options.go b/api/ocm/elements/artifactblob/ocmblob/options.go new file mode 100644 index 0000000000..407177e3d3 --- /dev/null +++ b/api/ocm/elements/artifactblob/ocmblob/options.go @@ -0,0 +1,21 @@ +package ocmblob + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/elements/artifactblob/api" +) + +type Option = api.Option + +type Options = api.Options + +//////////////////////////////////////////////////////////////////////////////// +// General Options + +func WithHint(h string) Option { + return api.WrapHint[Options](h) +} + +func WithGlobalAccess(a cpi.AccessSpec) Option { + return api.WrapGlobalAccess[Options](a) +} diff --git a/api/ocm/elements/artifactblob/ocmblob/resource.go b/api/ocm/elements/artifactblob/ocmblob/resource.go new file mode 100644 index 0000000000..9949f65a1b --- /dev/null +++ b/api/ocm/elements/artifactblob/ocmblob/resource.go @@ -0,0 +1,26 @@ +package ocmblob + +import ( + "github.com/mandelsoft/goutils/generics" + "github.com/mandelsoft/goutils/optionutils" + + "ocm.software/ocm/api/ocm/compdesc" + "ocm.software/ocm/api/ocm/cpi" + base "ocm.software/ocm/api/utils/blobaccess/ocm" +) + +func Access[M any, P compdesc.ArtifactMetaPointer[M]](ctx cpi.Context, meta P, cvp base.ComponentVersionProvider, res base.ResourceProvider, opts ...Option) cpi.ArtifactAccess[M] { + eff := optionutils.EvalOptions(opts...) + blobprov := base.Provider(cvp, res) + accprov := cpi.NewAccessProviderForBlobAccessProvider(ctx, blobprov, eff.Hint, eff.Global) + // strange type cast is required by Go compiler, meta has the correct type. + return cpi.NewArtifactAccessForProvider(generics.Cast[*M](meta), accprov) +} + +func ResourceAccess(ctx cpi.Context, meta *cpi.ResourceMeta, cvp base.ComponentVersionProvider, res base.ResourceProvider, opts ...Option) cpi.ResourceAccess { + return Access(ctx, meta, cvp, res, opts...) +} + +func SourceAccess(ctx cpi.Context, meta *cpi.SourceMeta, cvp base.ComponentVersionProvider, res base.ResourceProvider, opts ...Option) cpi.SourceAccess { + return Access(ctx, meta, cvp, res, opts...) +} diff --git a/api/ocm/elements/artifactblob/ocmblob/suite_test.go b/api/ocm/elements/artifactblob/ocmblob/suite_test.go new file mode 100644 index 0000000000..bca94d33a2 --- /dev/null +++ b/api/ocm/elements/artifactblob/ocmblob/suite_test.go @@ -0,0 +1,13 @@ +package ocmblob_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM Blob Access Test Suite") +} diff --git a/api/ocm/extensions/accessmethods/helm/method.go b/api/ocm/extensions/accessmethods/helm/method.go index c4e9807138..715c640103 100644 --- a/api/ocm/extensions/accessmethods/helm/method.go +++ b/api/ocm/extensions/accessmethods/helm/method.go @@ -41,7 +41,7 @@ func New(chart string, repourl string) *AccessSpec { type AccessSpec struct { runtime.ObjectVersionedType `json:",inline"` - // HelmRepository is the URL og the helm repository to load the chart from. + // HelmRepository is the URL of the helm repository to load the chart from. HelmRepository string `json:"helmRepository"` // HelmChart if the name of the helm chart and its version separated by a colon. diff --git a/api/ocm/extensions/accessmethods/init.go b/api/ocm/extensions/accessmethods/init.go index d7db2ddb76..5f4094d3d5 100644 --- a/api/ocm/extensions/accessmethods/init.go +++ b/api/ocm/extensions/accessmethods/init.go @@ -11,6 +11,7 @@ import ( _ "ocm.software/ocm/api/ocm/extensions/accessmethods/npm" _ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" _ "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + _ "ocm.software/ocm/api/ocm/extensions/accessmethods/ocm" _ "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" _ "ocm.software/ocm/api/ocm/extensions/accessmethods/s3" _ "ocm.software/ocm/api/ocm/extensions/accessmethods/wget" diff --git a/api/ocm/extensions/accessmethods/ocm/cli.go b/api/ocm/extensions/accessmethods/ocm/cli.go new file mode 100644 index 0000000000..3c51b4ef3f --- /dev/null +++ b/api/ocm/extensions/accessmethods/ocm/cli.go @@ -0,0 +1,54 @@ +package ocm + +import ( + "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + Type, AddConfig, + options.RepositoryOption, + options.ComponentOption, + options.VersionOption, + options.IdentityPathOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByMappedOptionP(opts, options.RepositoryOption, config, options.MapRepository, "ocmRepository") + flagsets.AddFieldByOptionP(opts, options.ComponentOption, config, "component") + flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") + flagsets.AddFieldByMappedOptionP(opts, options.IdentityPathOption, config, options.MapResourceRef, "resourceRef") + return nil +} + +var usage = ` +This method implements the access of any resource artifact stored in an OCM +repository. Only repository types supporting remote access should be used. +` + +var formatV1 = ` +The type specific specification fields are: + +- **ocmRepository** *json* + + The repository spec for the OCM repository + +- **component** *string* + + *(Optional)* The name of the component. The default is the + own component. + +- **version** *string* + + *(Optional)* The version of the component. The default is the + own component version. + +- **resourceRef** *relative resource ref* + + The resource reference of the denoted resource relative to the + given component version. + +It uses the consumer identity and credentials for the intermediate repositories +and the final resource access.` diff --git a/api/ocm/extensions/accessmethods/ocm/cli_test.go b/api/ocm/extensions/accessmethods/ocm/cli_test.go new file mode 100644 index 0000000000..e79ba4bc65 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ocm/cli_test.go @@ -0,0 +1,75 @@ +package ocm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/sliceutils" + "github.com/mandelsoft/goutils/transformer" + "github.com/spf13/pflag" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ocm" + "ocm.software/ocm/api/utils/cobrautils/flagsets" +) + +var _ = Describe("OCM access CLI Test Environment", func() { + ctx := cpi.DefaultContext() + + Context("cli options", func() { + It("handles access options", func() { + at := ctx.AccessMethods().GetType(ocm.TypeV1) + Expect(at).NotTo(BeNil()) + + h := at.ConfigOptionTypeSetHandler() + Expect(h).NotTo(BeNil()) + Expect(h.GetName()).To(Equal(ocm.Type)) + + ot := h.OptionTypes() + Expect(len(ot)).To(Equal(4)) + + opts := h.CreateOptions() + Expect(sliceutils.Transform(opts.Options(), transformer.GetName[flagsets.Option, string])).To(ConsistOf( + "accessComponent", "accessVersion", "accessRepository", "identityPath")) + + fs := &pflag.FlagSet{} + fs.SortFlags = true + opts.AddFlags(fs) + + Expect("\n" + fs.FlagUsages()).To(Equal(` + --accessComponent string component for access specification + --accessRepository string repository URL + --accessVersion string version for access specification + --identityPath {=} identity path for specification +`)) + + MustBeSuccessful(fs.Parse([]string{ + "--accessRepository", "ghcr.io/open-component-model/ocm", + "--accessComponent", COMP1, + "--accessVersion", VERS, + "--identityPath", "name=rsc1", + "--identityPath", "other=value", + "--identityPath", "name=rsc2", + })) + + cfg := flagsets.Config{} + MustBeSuccessful(h.ApplyConfig(opts, cfg)) + Expect(cfg).To(YAMLEqual(` + component: acme.org/test1 + version: v1 + ocmRepository: + type: OCIRegistry + baseUrl: ghcr.io + componentNameMapping: urlPath + subPath: open-component-model/ocm + resourceRef: + resource: + name: rsc1 + other: value + referencePath: + - name: rsc2 +`)) + }) + }) +}) diff --git a/api/ocm/extensions/accessmethods/ocm/method.go b/api/ocm/extensions/accessmethods/ocm/method.go new file mode 100644 index 0000000000..f48edac666 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ocm/method.go @@ -0,0 +1,236 @@ +package ocm + +import ( + "fmt" + "io" + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/finalizer" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/resourcerefs" + "ocm.software/ocm/api/tech/helm" + "ocm.software/ocm/api/utils/blobaccess/blobaccess" + "ocm.software/ocm/api/utils/refmgmt" + "ocm.software/ocm/api/utils/runtime" +) + +// Type is the access type for a blob in an OCI repository. +const ( + Type = "ocm" + TypeV1 = Type + runtime.VersionSeparator + "v1" +) + +func init() { + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](Type, accspeccpi.WithDescription(usage))) + accspeccpi.RegisterAccessType(accspeccpi.NewAccessSpecType[*AccessSpec](TypeV1, accspeccpi.WithFormatSpec(formatV1), accspeccpi.WithConfigHandler(ConfigHandler()))) +} + +// New creates a new Helm Chart accessor for helm repositories. +func New(comp, vers string, repo cpi.RepositorySpec, id metav1.Identity, path ...metav1.Identity) (*AccessSpec, error) { + spec, err := cpi.ToGenericRepositorySpec(repo) + if err != nil { + return nil, err + } + return &AccessSpec{ + ObjectVersionedType: runtime.NewVersionedTypedObject(Type), + OCMRepository: spec, + Component: comp, + Version: vers, + ResourceRef: metav1.NewNestedResourceRef(id, path), + }, nil +} + +// AccessSpec describes the access for an OCM repository. +type AccessSpec struct { + runtime.ObjectVersionedType `json:",inline"` + + // OCMRepository is the URL of the OCM repository to load the chart from. + OCMRepository *cpi.GenericRepositorySpec `json:"ocmRepository,omitempty"` + + // Component if the name of the root component used to lookup the resource. + Component string `json:"component,omitempty"` + + // Version is the version og the root component. + Version string `json:"version,omitempty,"` + + ResourceRef metav1.ResourceReference `json:"resourceRef"` +} + +var _ accspeccpi.AccessSpec = (*AccessSpec)(nil) + +func (a *AccessSpec) Describe(ctx accspeccpi.Context) string { + comp := a.Component + if a.Version != "" { + comp += ":" + a.Version + } + if comp != "" { + comp = " in " + comp + } + raw, _ := a.OCMRepository.GetRaw() + if a.OCMRepository != nil { + return fmt.Sprintf("OCM resource %s%s in repository %s", a.ResourceRef.String(), comp, string(raw)) + } + return fmt.Sprintf("OCM resource %s%s", a.ResourceRef.String(), comp) +} + +func (a *AccessSpec) IsLocal(ctx accspeccpi.Context) bool { + return false +} + +func (a *AccessSpec) GlobalAccessSpec(ctx accspeccpi.Context) accspeccpi.AccessSpec { + return a +} + +func (a *AccessSpec) AccessMethod(access accspeccpi.ComponentVersionAccess) (accspeccpi.AccessMethod, error) { + return accspeccpi.AccessMethodForImplementation(&accessMethod{comp: access, spec: a}, nil) +} + +/////////////////// + +func (a *AccessSpec) GetVersion() string { + return a.Version +} + +func (a *AccessSpec) GetComponent() string { + return a.Component +} + +//////////////////////////////////////////////////////////////////////////////// + +type accessMethod struct { + lock sync.Mutex + blob blobaccess.BlobAccess + repo cpi.Repository + comp accspeccpi.ComponentVersionAccess + spec *AccessSpec + acc cpi.ResourceAccess +} + +var ( + _ accspeccpi.AccessMethodImpl = (*accessMethod)(nil) + _ accspeccpi.DigestSpecProvider = (*accessMethod)(nil) +) + +func (_ *accessMethod) IsLocal() bool { + return false +} + +func (m *accessMethod) GetKind() string { + return Type +} + +func (m *accessMethod) AccessSpec() accspeccpi.AccessSpec { + return m.spec +} + +func (m *accessMethod) Close() error { + m.lock.Lock() + defer m.lock.Unlock() + if m.blob != nil { + m.blob.Close() + m.blob = nil + } + if m.repo != nil { + m.repo.Close() + m.repo = nil + } + return nil +} + +func (m *accessMethod) Get() ([]byte, error) { + return blobaccess.BlobData(m.getBlob()) +} + +func (m *accessMethod) Reader() (io.ReadCloser, error) { + return blobaccess.BlobReader(m.getBlob()) +} + +func (m *accessMethod) MimeType() string { + return helm.ChartMediaType +} + +func (m *accessMethod) getBlob() (bacc blobaccess.BlobAccess, efferr error) { + m.lock.Lock() + defer m.lock.Unlock() + + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&efferr) + + if m.blob != nil { + return m.blob, nil + } + + vers := m.spec.GetVersion() + name := m.spec.GetComponent() + + if vers == "" { + vers = m.comp.GetVersion() + } + if name == "" { + vers = m.comp.GetName() + } + + var err error + + var cv cpi.ComponentVersionAccess + if name == m.comp.GetName() && vers == m.comp.GetVersion() { + cv = m.comp + if m.repo == nil { + m.repo, err = cv.Repository().Dup() + if err != nil { + return nil, err + } + } + } else { + if m.repo == nil { + if m.spec.OCMRepository == nil { + m.repo, err = m.comp.Repository().Dup() + if err != nil { + return nil, err + } + } else { + m.repo, err = refmgmt.ToLazy(m.comp.GetContext().RepositoryForSpec(m.spec.OCMRepository)) + if err != nil { + return nil, err + } + } + } + + cv, err = refmgmt.ToLazy(m.repo.LookupComponentVersion(name, vers)) + if errors.IsErrNotFound(err) || cv == nil { + r := m.comp.GetContext().GetResolver() + if r != nil { + cv, err = refmgmt.ToLazy(r.LookupComponentVersion(name, vers)) + } + } + if err != nil { + return nil, err + } + finalize.Close(cv) + } + if cv == nil { + return nil, errors.ErrNotFound(cpi.KIND_COMPONENTVERSION, name+":"+vers) + } + + r, eff, err := resourcerefs.ResolveResourceReference(cv, m.spec.ResourceRef, m.comp.GetContext().GetResolver()) + if err != nil { + return nil, err + } + finalize.Close(refmgmt.AsLazy(eff)) + + m.blob, err = r.BlobAccess() + m.acc = r + return m.blob, err +} + +func (m *accessMethod) GetDigestSpec() (*metav1.DigestSpec, error) { + _, err := m.getBlob() + if err != nil { + return nil, err + } + return m.acc.Meta().Digest, nil +} diff --git a/api/ocm/extensions/accessmethods/ocm/method_test.go b/api/ocm/extensions/accessmethods/ocm/method_test.go new file mode 100644 index 0000000000..0ebe2d5814 --- /dev/null +++ b/api/ocm/extensions/accessmethods/ocm/method_test.go @@ -0,0 +1,112 @@ +package ocm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/ocm" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" +) + +const ( + ARCH = "/tmp/ctf" + COMP1 = "acme.org/test1" + COMP2 = "acme.org/test2" + VERS = "v1" + RSC1 = "resource1" + RSC2 = "resource2" + REF1 = "reference1" + + DATA = "some test data" +) + +var _ = Describe("Method", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + }) + + AfterEach(func() { + env.Cleanup() + }) + + Context("remote access", func() { + BeforeEach(func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP1, VERS, func() { + env.Resource(RSC1, VERS, resourcetypes.PLAIN_TEXT, v1.LocalRelation, func() { + env.ExtraIdentity("other", "value") + env.BlobStringData(mime.MIME_TEXT, DATA) + }) + }) + + env.ComponentVersion(COMP2, VERS, func() { + env.Reference(REF1, COMP1, VERS, func() { + env.ExtraIdentity("purpose", "test") + }) + }) + }) + }) + + It("accesses artifact", func() { + spec := Must(ocm.New(COMP1, VERS, Must(ctf.NewRepositorySpec(accessobj.ACC_READONLY, ARCH, env)), v1.NewIdentity(RSC1, "other", "value"))) + + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()})) + defer Close(m) + data := Must(m.Get()) + Expect(string(data)).To(Equal(DATA)) + }) + + It("accesses indirect artifact", func() { + spec := Must(ocm.New(COMP2, VERS, Must(ctf.NewRepositorySpec(accessobj.ACC_READONLY, ARCH, env)), v1.NewIdentity(RSC1, "other", "value"), v1.NewIdentity(REF1, "purpose", "test"))) + + m := Must(spec.AccessMethod(&cpi.DummyComponentVersionAccess{env.OCMContext()})) + defer Close(m) + data := Must(m.Get()) + Expect(string(data)).To(Equal(DATA)) + }) + }) + + Context("local access", func() { + BeforeEach(func() { + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP1, VERS, func() { + env.Resource(RSC1, VERS, resourcetypes.PLAIN_TEXT, v1.LocalRelation, func() { + env.ExtraIdentity("other", "value") + env.BlobStringData(mime.MIME_TEXT, DATA) + }) + }) + + env.ComponentVersion(COMP2, VERS, func() { + env.Resource(RSC2, VERS, resourcetypes.PLAIN_TEXT, v1.LocalRelation, func() { + env.Access(Must(ocm.New(COMP1, VERS, nil, v1.NewIdentity(RSC1, "other", "value")))) + }) + }) + }) + }) + + It("accesses artifact", func() { + repo := Must(ctf.Open(env, accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(repo, "repo") + + cv := Must(repo.LookupComponentVersion(COMP2, VERS)) + defer Close(cv, "cv") + + ra := Must(cv.GetResourceByIndex(0)) + + m := Must(ra.AccessMethod()) + defer Close(m) + data := Must(m.Get()) + Expect(string(data)).To(Equal(DATA)) + }) + }) +}) diff --git a/api/ocm/extensions/accessmethods/ocm/suite_test.go b/api/ocm/extensions/accessmethods/ocm/suite_test.go new file mode 100644 index 0000000000..cc788df8ec --- /dev/null +++ b/api/ocm/extensions/accessmethods/ocm/suite_test.go @@ -0,0 +1,13 @@ +package ocm_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM Access Test Suite") +} diff --git a/api/ocm/extensions/accessmethods/options/init.go b/api/ocm/extensions/accessmethods/options/init.go index 8de3a5fdd9..7e815b8f30 100644 --- a/api/ocm/extensions/accessmethods/options/init.go +++ b/api/ocm/extensions/accessmethods/options/init.go @@ -12,6 +12,7 @@ const ( TYPE_STRING2STRINGSLICE = "string=string,string" TYPE_STRINGCOLONSTRINGSLICE = "string:string,string" TYPE_BYTES = "[]byte" + TYPE_IDENTITYPATH = "[]identity" ) func init() { @@ -26,6 +27,7 @@ func init() { DefaultRegistry.RegisterValueType(TYPE_STRING2STRINGSLICE, NewStringSliceMapOptionType, "string map defined by dedicated assignment of comma separated strings") DefaultRegistry.RegisterValueType(TYPE_STRINGCOLONSTRINGSLICE, NewStringSliceMapColonOptionType, "string map defined by dedicated assignment of comma separated strings") DefaultRegistry.RegisterValueType(TYPE_BYTES, NewBytesOptionType, "byte value") + DefaultRegistry.RegisterValueType(TYPE_IDENTITYPATH, NewIdentityPathOptionType, "identity path") } func RegisterOption(o OptionType) OptionType { diff --git a/api/ocm/extensions/accessmethods/options/mapping.go b/api/ocm/extensions/accessmethods/options/mapping.go new file mode 100644 index 0000000000..e98e1449f0 --- /dev/null +++ b/api/ocm/extensions/accessmethods/options/mapping.go @@ -0,0 +1,32 @@ +package options + +import ( + "fmt" + + "github.com/mandelsoft/goutils/errors" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" +) + +func MapRepository(in any) (any, error) { + uni, err := cpi.ParseRepo(in.(string)) + if err != nil { + return nil, errors.ErrInvalidWrap(err, cpi.KIND_REPOSITORYSPEC, in.(string)) + } + + // TODO: basically a context is required, here. + spec, err := cpi.DefaultContext().MapUniformRepositorySpec(&uni) + if err != nil { + return nil, err + } + return cpi.ToGenericRepositorySpec(spec) +} + +func MapResourceRef(in any) (any, error) { + list := in.([]v1.Identity) + if len(list) == 0 { + return nil, fmt.Errorf("empty resource reference") + } + return v1.NewResourceRef(list[0], list[1:]...), nil +} diff --git a/api/ocm/extensions/accessmethods/options/standard.go b/api/ocm/extensions/accessmethods/options/standard.go index b207daf70b..70c5ad0009 100644 --- a/api/ocm/extensions/accessmethods/options/standard.go +++ b/api/ocm/extensions/accessmethods/options/standard.go @@ -48,7 +48,13 @@ var BucketOption = RegisterOption(NewStringOptionType("bucket", "bucket name")) // VersionOption . var VersionOption = RegisterOption(NewStringOptionType("accessVersion", "version for access specification")) -// URLOption . +// ComponentOption. +var ComponentOption = RegisterOption(NewStringOptionType("accessComponent", "component for access specification")) + +// IdentityPathOption. +var IdentityPathOption = RegisterOption(NewIdentityPathOptionType("identityPath", "identity path for specification")) + +// URLOption. var URLOption = RegisterOption(NewStringOptionType("url", "artifact or server url")) var HTTPHeaderOption = RegisterOption(NewStringSliceMapColonOptionType("header", "http headers")) @@ -76,3 +82,6 @@ var NPMPackageOption = RegisterOption(NewStringOptionType("package", "npm packag // NPMVersionOption sets the version of the npm package. var NPMVersionOption = RegisterOption(NewStringOptionType("version", "npm package version")) + +// IdPathOption is a path of identity specs. +var IdPathOption = RegisterOption(NewStringArrayOptionType("idpath", "identity path (attr=value{,attr=value}")) diff --git a/api/ocm/extensions/accessmethods/options/types.go b/api/ocm/extensions/accessmethods/options/types.go index 1788c3b808..f02ecd03f7 100644 --- a/api/ocm/extensions/accessmethods/options/types.go +++ b/api/ocm/extensions/accessmethods/options/types.go @@ -116,3 +116,10 @@ func NewBytesOptionType(name, desc string) OptionType { valueType: TYPE_BYTES, } } + +func NewIdentityPathOptionType(name, desc string) OptionType { + return &option{ + base: flagsets.NewIdentityPathOptionType(name, desc), + valueType: TYPE_IDENTITYPATH, + } +} diff --git a/api/ocm/extensions/repositories/comparch/format.go b/api/ocm/extensions/repositories/comparch/format.go index 0946f33e97..de3548141d 100644 --- a/api/ocm/extensions/repositories/comparch/format.go +++ b/api/ocm/extensions/repositories/comparch/format.go @@ -6,6 +6,7 @@ import ( "github.com/mandelsoft/goutils/errors" "github.com/mandelsoft/vfs/pkg/vfs" + "ocm.software/ocm/api/datacontext/attrs/vfsattr" "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/api/utils/accessio" @@ -87,8 +88,12 @@ func GetFormat(name accessio.FileFormat) FormatHandler { //////////////////////////////////////////////////////////////////////////////// -func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, opts ...accessio.Option) (*Object, error) { - o, create, err := accessobj.HandleAccessMode(acc, path, nil, opts...) +func Open(ctx cpi.ContextProvider, acc accessobj.AccessMode, path string, mode vfs.FileMode, olist ...accessio.Option) (*Object, error) { + opts, err := accessio.AccessOptions(&accessio.StandardOptions{PathFileSystem: vfsattr.Get(ctx.OCMContext())}, olist...) + if err != nil { + return nil, err + } + o, create, err := accessobj.HandleAccessMode(acc, path, opts) if err != nil { return nil, err } diff --git a/api/ocm/interface.go b/api/ocm/interface.go index 9ff49258a9..b4c5f93d89 100644 --- a/api/ocm/interface.go +++ b/api/ocm/interface.go @@ -25,6 +25,7 @@ const ( KIND_SOURCE = internal.KIND_SOURCE KIND_REFERENCE = internal.KIND_REFERENCE KIND_REPOSITORYSPEC = internal.KIND_REPOSITORYSPEC + KIND_OCM_REFERENCE = internal.KIND_OCM_REFERENCE ) const CONTEXT_TYPE = internal.CONTEXT_TYPE diff --git a/api/ocm/internal/accesstypes.go b/api/ocm/internal/accesstypes.go index 03613d9abb..707c223e64 100644 --- a/api/ocm/internal/accesstypes.go +++ b/api/ocm/internal/accesstypes.go @@ -11,6 +11,7 @@ import ( "github.com/modern-go/reflect2" "ocm.software/ocm/api/ocm/compdesc" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/utils/cobrautils/flagsets/flagsetscheme" "ocm.software/ocm/api/utils/errkind" "ocm.software/ocm/api/utils/refmgmt" @@ -59,6 +60,12 @@ type GlobalAccessProvider interface { GlobalAccessSpec(ctx Context) AccessSpec } +// DigestSpecProvider is an optional interface for an access method +// to provide am Digest Specification. +type DigestSpecProvider interface { + GetDigestSpec() (*metav1.DigestSpec, error) +} + // AccessMethodImpl is the implementation interface // for access methods provided by access types. It describes // the access to a dedicated resource diff --git a/api/ocm/internal/errors.go b/api/ocm/internal/errors.go index aa4888505a..8c52e130db 100644 --- a/api/ocm/internal/errors.go +++ b/api/ocm/internal/errors.go @@ -17,6 +17,7 @@ const ( KIND_SOURCE = "component source" KIND_REFERENCE = compdesc.KIND_REFERENCE KIND_REPOSITORYSPEC = "repository specification" + KIND_OCM_REFERENCE = "ocm reference" ) func ErrComponentVersionNotFound(name, version string) error { diff --git a/api/ocm/ocmutils/localize/config.go b/api/ocm/ocmutils/localize/config.go index 5c89b9a1bf..08d55cb557 100644 --- a/api/ocm/ocmutils/localize/config.go +++ b/api/ocm/ocmutils/localize/config.go @@ -9,7 +9,7 @@ import ( "ocm.software/ocm/api/ocm" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" - utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/utils/runtime" "ocm.software/ocm/api/utils/spiff" ) @@ -38,7 +38,7 @@ func Configure( stubs := spiff.Options{} for i, lib := range libraries { opt, err := func() (spiff.OptionFunction, error) { - res, eff, err := utils.ResolveResourceReference(cv, lib, resolver) + res, eff, err := resourcerefs.ResolveResourceReference(cv, lib, resolver) if err != nil { return nil, errors.ErrNotFound("library resource %s not found", lib.String()) } diff --git a/api/ocm/ocmutils/localize/format.go b/api/ocm/ocmutils/localize/format.go index b84df070b0..04404c9e75 100644 --- a/api/ocm/ocmutils/localize/format.go +++ b/api/ocm/ocmutils/localize/format.go @@ -10,6 +10,7 @@ import ( "ocm.software/ocm/api/ocm" v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/utils/runtime" ) @@ -53,7 +54,7 @@ func (m *ImageMapping) Evaluate(idx int, cv ocm.ComponentVersionAccess, resolver name = fmt.Sprintf("%s %d", name, idx+1) } } - acc, rcv, err := utils.ResolveResourceReference(cv, m.ResourceReference, resolver) + acc, rcv, err := resourcerefs.ResolveResourceReference(cv, m.ResourceReference, resolver) if err != nil { return nil, errors.Wrapf(err, "mapping", fmt.Sprintf("%s (%s)", name, &m.ResourceReference)) } diff --git a/api/ocm/ocmutils/localize/instantiate.go b/api/ocm/ocmutils/localize/instantiate.go index a510ca8000..ae0d8594cb 100644 --- a/api/ocm/ocmutils/localize/instantiate.go +++ b/api/ocm/ocmutils/localize/instantiate.go @@ -7,7 +7,7 @@ import ( "ocm.software/ocm/api/ocm" resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" "ocm.software/ocm/api/ocm/extensions/download" - utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" common "ocm.software/ocm/api/utils/misc" ) @@ -22,7 +22,7 @@ func Instantiate(rules *InstantiationRules, cv ocm.ComponentVersionAccess, resol return errors.Wrapf(err, "applying instance configuration") } - template, rcv, err := utils.ResolveResourceReference(cv, rules.Template, resolver) + template, rcv, err := resourcerefs.ResolveResourceReference(cv, rules.Template, resolver) if err != nil { return errors.Wrapf(err, "resolving template resource %s", rules.Template) } diff --git a/api/ocm/ocmutils/resource.go b/api/ocm/ocmutils/resource.go index 9b4df7e262..40095df37c 100644 --- a/api/ocm/ocmutils/resource.go +++ b/api/ocm/ocmutils/resource.go @@ -5,6 +5,8 @@ import ( "ocm.software/ocm/api/ocm" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/resolvers" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/utils/blobaccess/blobaccess" "ocm.software/ocm/api/utils/iotools" ) @@ -17,12 +19,12 @@ func GetResourceDataForPath(cv ocm.ComponentVersionAccess, id metav1.Identity, p return GetResourceDataForRef(cv, metav1.NewNestedResourceRef(id, path), resolvers...) } -func GetResourceDataForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolvers ...ocm.ComponentVersionResolver) ([]byte, error) { - var res ocm.ComponentVersionResolver - if len(resolvers) > 0 { - res = ocm.NewCompoundResolver(resolvers...) +func GetResourceDataForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, reslist ...ocm.ComponentVersionResolver) ([]byte, error) { + var res resolvers.ComponentVersionResolver + if len(reslist) > 0 { + res = resolvers.NewCompoundResolver(reslist...) } - a, c, err := ResolveResourceReference(cv, ref, res) + a, c, err := resourcerefs.ResolveResourceReference(cv, ref, res) if err != nil { return nil, err } @@ -39,12 +41,12 @@ func GetResourceReaderForPath(cv ocm.ComponentVersionAccess, id metav1.Identity, return GetResourceReaderForRef(cv, metav1.NewNestedResourceRef(id, path), resolvers...) } -func GetResourceReaderForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolvers ...ocm.ComponentVersionResolver) (io.ReadCloser, error) { - var res ocm.ComponentVersionResolver - if len(resolvers) > 0 { - res = ocm.NewCompoundResolver(resolvers...) +func GetResourceReaderForRef(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, reslist ...ocm.ComponentVersionResolver) (io.ReadCloser, error) { + var res resolvers.ComponentVersionResolver + if len(reslist) > 0 { + res = resolvers.NewCompoundResolver(reslist...) } - a, c, err := ResolveResourceReference(cv, ref, res) + a, c, err := resourcerefs.ResolveResourceReference(cv, ref, res) if err != nil { return nil, err } diff --git a/api/ocm/ocmutils/resourceref.go b/api/ocm/ocmutils/resourceref.go index ec2b86b2fc..40aa8880e2 100644 --- a/api/ocm/ocmutils/resourceref.go +++ b/api/ocm/ocmutils/resourceref.go @@ -1,103 +1,22 @@ package ocmutils import ( - "fmt" - - . "github.com/mandelsoft/goutils/finalizer" - - "github.com/mandelsoft/goutils/errors" - - "ocm.software/ocm/api/ocm" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" - common "ocm.software/ocm/api/utils/misc" + ocm "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/resourcerefs" ) +// Deprectated: use resourcerefs.ResolveReferencePath. func ResolveReferencePath(cv ocm.ComponentVersionAccess, path []metav1.Identity, resolver ocm.ComponentVersionResolver) (ocm.ComponentVersionAccess, error) { - if cv == nil { - return nil, fmt.Errorf("no component version specified") - } - eff, err := cv.Dup() - if err != nil { - return nil, errors.Wrapf(err, "component version already closed") - } - - var final Finalizer - defer final.Finalize() - - for _, cr := range path { - final.Close(eff) - cref, err := eff.GetReference(cr) - if err != nil { - return nil, errors.Wrapf(err, "%s", common.VersionedElementKey(cv)) - } - - compoundResolver := ocm.NewCompoundResolver(eff.Repository(), resolver) - eff, err = compoundResolver.LookupComponentVersion(cref.GetComponentName(), cref.GetVersion()) - if err != nil { - return nil, errors.Wrapf(err, "cannot resolve component version for reference %s", cr.String()) - } - if eff == nil { - return nil, errors.ErrNotFound(ocm.KIND_COMPONENTVERSION, cref.String()) - } - final.Finalize() - } - return eff, nil + return resourcerefs.ResolveReferencePath(cv, path, resolver) } +// Deprecated: use resourcerefs.MatchResourceReference. func MatchResourceReference(cv ocm.ComponentVersionAccess, typ string, ref metav1.ResourceReference, resolver ocm.ComponentVersionResolver) (ocm.ResourceAccess, ocm.ComponentVersionAccess, error) { - eff, err := ResolveReferencePath(cv, ref.ReferencePath, resolver) - if err != nil { - return nil, nil, err - } - - if len(eff.GetDescriptor().Resources) == 0 && len(ref.Resource) == 0 { - return nil, nil, errors.ErrNotFound(ocm.KIND_RESOURCE) - } -outer: - for i, r := range eff.GetDescriptor().Resources { - if r.Type != typ && typ != "" { - continue - } - for k, v := range ref.Resource { - switch k { - case metav1.SystemIdentityName: - if v != r.Name { - continue outer - } - case metav1.SystemIdentityVersion: - if v != r.Version { - continue outer - } - default: - if r.ExtraIdentity == nil || r.ExtraIdentity[k] != v { - continue outer - } - } - } - res, err := eff.GetResourceByIndex(i) - if err != nil { - eff.Close() - return nil, nil, err - } - return res, eff, nil - } - eff.Close() - return nil, nil, errors.ErrNotFound(ocm.KIND_RESOURCE, ref.Resource.String()) + return resourcerefs.MatchResourceReference(cv, typ, ref, resolver) } +// Deprecated: use resourcerefs.ResolveResourceReference. func ResolveResourceReference(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolver ocm.ComponentVersionResolver) (ocm.ResourceAccess, ocm.ComponentVersionAccess, error) { - if len(ref.Resource) == 0 || len(ref.Resource["name"]) == 0 { - return nil, nil, errors.Newf("at least resource name must be specified for resource reference") - } - - eff, err := ResolveReferencePath(cv, ref.ReferencePath, resolver) - if err != nil { - return nil, nil, err - } - r, err := eff.GetResource(ref.Resource) - if err != nil { - eff.Close() - return nil, nil, err - } - return r, eff, nil + return resourcerefs.ResolveResourceReference(cv, ref, resolver) } diff --git a/api/ocm/ref.go b/api/ocm/ref.go index cf9de6550c..72f0b08e84 100644 --- a/api/ocm/ref.go +++ b/api/ocm/ref.go @@ -5,99 +5,19 @@ import ( "strings" "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/general" "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/api/ocm/grammar" common "ocm.software/ocm/api/utils/misc" ) -const ( - KIND_OCM_REFERENCE = "ocm reference" -) - // ParseRepo parses a standard ocm repository reference into a internal representation. func ParseRepo(ref string) (UniformRepositorySpec, error) { - create := false - if strings.HasPrefix(ref, "+") { - create = true - ref = ref[1:] - } - if strings.HasPrefix(ref, ".") || strings.HasPrefix(ref, "/") { - return cpi.HandleRef(UniformRepositorySpec{ - Info: ref, - CreateIfMissing: create, - }) - } - match := grammar.AnchoredRepositoryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }) - } - - match = grammar.AnchoredSchemedHostPortRepositoryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }) - } - - match = grammar.AnchoredHostWithPortRepositoryRegexp.FindSubmatch([]byte(ref)) - if match != nil { - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Scheme: string(match[2]), - Host: string(match[3]), - SubPath: string(match[4]), - CreateIfMissing: create, - }) - } - - match = grammar.AnchoredGenericRepositoryRegexp.FindSubmatch([]byte(ref)) - if match == nil { - return UniformRepositorySpec{}, errors.ErrInvalid(KIND_OCM_REFERENCE, ref) - } - h := string(match[1]) - t, _ := grammar.SplitTypeSpec(h) - return cpi.HandleRef(UniformRepositorySpec{ - Type: t, - TypeHint: h, - Info: string(match[2]), - CreateIfMissing: create, - }) + return cpi.ParseRepo(ref) } func ParseRepoToSpec(ctx Context, ref string, create ...bool) (RepositorySpec, error) { - uni, err := ParseRepo(ref) - if err != nil { - return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) - } - if !uni.CreateIfMissing { - uni.CreateIfMissing = general.Optional(create...) - } - repoSpec, err := ctx.MapUniformRepositorySpec(&uni) - if err != nil { - return nil, errors.ErrInvalidWrap(err, KIND_REPOSITORYSPEC, ref) - } - return repoSpec, nil + return cpi.ParseRepoToSpec(ctx, ref, create...) } // RefSpec is a go internal representation of a oci reference. diff --git a/api/ocm/resolver.go b/api/ocm/resolver.go index 43f078b8bb..5351623ffa 100644 --- a/api/ocm/resolver.go +++ b/api/ocm/resolver.go @@ -1,151 +1,35 @@ package ocm import ( - "sync" - - "github.com/mandelsoft/goutils/errors" - "github.com/mandelsoft/goutils/sliceutils" "golang.org/x/exp/slices" "ocm.software/ocm/api/ocm/internal" - common "ocm.software/ocm/api/utils/misc" + "ocm.software/ocm/api/ocm/resolvers" ) -type DedicatedResolver []ComponentVersionAccess - -var ( - _ ComponentVersionResolver = (*DedicatedResolver)(nil) - _ ComponentResolver = (*DedicatedResolver)(nil) -) +// Deprecated: use resolvers.DedicatedResolver. +type DedicatedResolver = resolvers.DedicatedResolver +// Deprecated: use resolvers.NewDedicatedResolver. func NewDedicatedResolver(cv ...ComponentVersionAccess) ComponentVersionResolver { - return DedicatedResolver(slices.Clone(cv)) -} - -func (d DedicatedResolver) Repository() (Repository, error) { - return nil, nil -} - -func (d DedicatedResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { - for _, cv := range d { - if cv.GetName() == name && cv.GetVersion() == version { - return cv.Dup() - } - } - return nil, nil -} - -func (d DedicatedResolver) LookupComponentProviders(name string) []ResolvedComponentProvider { - for _, c := range d { - if c.GetName() == name { - return []ResolvedComponentProvider{d} - } - } - return nil -} - -func (d DedicatedResolver) LookupComponent(name string) (ResolvedComponentVersionProvider, error) { - return &versionProvider{name, d}, nil -} - -type versionProvider struct { - name string - resolver DedicatedResolver -} - -func (p *versionProvider) GetName() string { - return p.name -} - -func (p *versionProvider) LookupVersion(vers string) (ComponentVersionAccess, error) { - return p.resolver.LookupComponentVersion(p.name, vers) + return resolvers.DedicatedResolver(slices.Clone(cv)) } -func (p *versionProvider) ListVersions() ([]string, error) { - var vers []string - for _, c := range p.resolver { - if c.GetName() == p.name { - vers = sliceutils.AppendUnique(vers, c.GetVersion()) - } - } - return vers, nil -} - -//////////////////////////////////////////////////////////////////////////////// - -type CompoundResolver struct { - lock sync.RWMutex - resolvers []ComponentVersionResolver -} - -var ( - _ ComponentVersionResolver = (*CompoundResolver)(nil) - _ ComponentResolver = (*CompoundResolver)(nil) -) +// Deprecated: use resolvers.CompoundResolver. +type CompoundResolver = resolvers.CompoundResolver +// Deprecated: use resolvers.NewCompoundResolver. func NewCompoundResolver(res ...ComponentVersionResolver) ComponentVersionResolver { - for i := 0; i < len(res); i++ { - if res[i] == nil { - res = append(res[:i], res[i+1:]...) - i-- - } - } - if len(res) == 1 { - return res[0] - } - return &CompoundResolver{resolvers: res} -} - -func (c *CompoundResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { - c.lock.RLock() - defer c.lock.RUnlock() - for _, r := range c.resolvers { - if r == nil { - continue - } - cv, err := r.LookupComponentVersion(name, version) - if err == nil && cv != nil { - return cv, nil - } - if !errors.IsErrNotFoundKind(err, KIND_COMPONENTVERSION) && !errors.IsErrNotFoundKind(err, KIND_COMPONENT) { - return nil, err - } - } - return nil, errors.ErrNotFound(KIND_OCM_REFERENCE, common.NewNameVersion(name, version).String()) + return resolvers.NewCompoundResolver(res...) } -func (c *CompoundResolver) LookupComponentProviders(name string) []ResolvedComponentProvider { - c.lock.RLock() - defer c.lock.RUnlock() - - var result []RepositoryProvider - - for _, r := range c.resolvers { - if cr, ok := r.(ComponentResolver); ok { - result = append(result, cr.LookupComponentProviders(name)...) - } - } - return result -} - -func (c *CompoundResolver) AddResolver(r ComponentVersionResolver) { - c.lock.Lock() - defer c.lock.Unlock() - c.resolvers = append(c.resolvers, r) -} - -//////////////////////////////////////////////////////////////////////////////// - -type MatchingResolver interface { - ComponentVersionResolver - ContextProvider - - AddRule(prefix string, spec RepositorySpec, prio ...int) - Finalize() error -} +// Deprecated: use resolvers.MatchingResolver. +type MatchingResolver = resolvers.MatchingResolver -func NewMatchingResolver(ctx ContextProvider) MatchingResolver { +// Deprecated: use resolvers.NewMatchingResolver. +func NewMatchingResolver(ctx ContextProvider) resolvers.MatchingResolver { return internal.NewMatchingResolver(ctx.OCMContext()) } +// Deprecated: use resolvers.ResolverRule. type ResolverRule = internal.ResolverRule diff --git a/api/ocm/resolvers/forward.go b/api/ocm/resolvers/forward.go new file mode 100644 index 0000000000..4bdd370ddd --- /dev/null +++ b/api/ocm/resolvers/forward.go @@ -0,0 +1,23 @@ +package resolvers + +import ( + "ocm.software/ocm/api/ocm/internal" +) + +type ( + ContextProvider = internal.ContextProvider + RepositorySpec = internal.RepositorySpec + ComponentVersionAccess = internal.ComponentVersionAccess + ComponentVersionResolver = internal.ComponentVersionResolver + ComponentResolver = internal.ComponentResolver + Repository = internal.Repository + ResolvedComponentVersionProvider = internal.ResolvedComponentVersionProvider + ResolvedComponentProvider = internal.ResolvedComponentProvider + ResolvedRepositoryProvider = internal.ResolvedComponentProvider +) + +const ( + KIND_COMPONENTVERSION = internal.KIND_COMPONENTVERSION + KIND_COMPONENT = internal.KIND_COMPONENT + KIND_OCM_REFERENCE = internal.KIND_OCM_REFERENCE +) diff --git a/api/ocm/resolvers/resolver.go b/api/ocm/resolvers/resolver.go new file mode 100644 index 0000000000..a500339b86 --- /dev/null +++ b/api/ocm/resolvers/resolver.go @@ -0,0 +1,151 @@ +package resolvers + +import ( + "sync" + + "github.com/mandelsoft/goutils/errors" + "github.com/mandelsoft/goutils/sliceutils" + "golang.org/x/exp/slices" + + "ocm.software/ocm/api/ocm/internal" + common "ocm.software/ocm/api/utils/misc" +) + +type DedicatedResolver []ComponentVersionAccess + +var ( + _ ComponentVersionResolver = (*DedicatedResolver)(nil) + _ ComponentResolver = (*DedicatedResolver)(nil) +) + +func NewDedicatedResolver(cv ...ComponentVersionAccess) ComponentVersionResolver { + return DedicatedResolver(slices.Clone(cv)) +} + +func (d DedicatedResolver) Repository() (Repository, error) { + return nil, nil +} + +func (d DedicatedResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { + for _, cv := range d { + if cv.GetName() == name && cv.GetVersion() == version { + return cv.Dup() + } + } + return nil, nil +} + +func (d DedicatedResolver) LookupComponentProviders(name string) []ResolvedComponentProvider { + for _, c := range d { + if c.GetName() == name { + return []ResolvedComponentProvider{d} + } + } + return nil +} + +func (d DedicatedResolver) LookupComponent(name string) (ResolvedComponentVersionProvider, error) { + return &versionProvider{name, d}, nil +} + +type versionProvider struct { + name string + resolver DedicatedResolver +} + +func (p *versionProvider) GetName() string { + return p.name +} + +func (p *versionProvider) LookupVersion(vers string) (ComponentVersionAccess, error) { + return p.resolver.LookupComponentVersion(p.name, vers) +} + +func (p *versionProvider) ListVersions() ([]string, error) { + var vers []string + for _, c := range p.resolver { + if c.GetName() == p.name { + vers = sliceutils.AppendUnique(vers, c.GetVersion()) + } + } + return vers, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type CompoundResolver struct { + lock sync.RWMutex + resolvers []ComponentVersionResolver +} + +var ( + _ ComponentVersionResolver = (*CompoundResolver)(nil) + _ ComponentResolver = (*CompoundResolver)(nil) +) + +func NewCompoundResolver(res ...ComponentVersionResolver) ComponentVersionResolver { + for i := 0; i < len(res); i++ { + if res[i] == nil { + res = append(res[:i], res[i+1:]...) + i-- + } + } + if len(res) == 1 { + return res[0] + } + return &CompoundResolver{resolvers: res} +} + +func (c *CompoundResolver) LookupComponentVersion(name string, version string) (ComponentVersionAccess, error) { + c.lock.RLock() + defer c.lock.RUnlock() + for _, r := range c.resolvers { + if r == nil { + continue + } + cv, err := r.LookupComponentVersion(name, version) + if err == nil && cv != nil { + return cv, nil + } + if !errors.IsErrNotFoundKind(err, KIND_COMPONENTVERSION) && !errors.IsErrNotFoundKind(err, KIND_COMPONENT) { + return nil, err + } + } + return nil, errors.ErrNotFound(KIND_OCM_REFERENCE, common.NewNameVersion(name, version).String()) +} + +func (c *CompoundResolver) LookupComponentProviders(name string) []ResolvedComponentProvider { + c.lock.RLock() + defer c.lock.RUnlock() + + var result []ResolvedRepositoryProvider + + for _, r := range c.resolvers { + if cr, ok := r.(ComponentResolver); ok { + result = append(result, cr.LookupComponentProviders(name)...) + } + } + return result +} + +func (c *CompoundResolver) AddResolver(r ComponentVersionResolver) { + c.lock.Lock() + defer c.lock.Unlock() + c.resolvers = append(c.resolvers, r) +} + +//////////////////////////////////////////////////////////////////////////////// + +type MatchingResolver interface { + ComponentVersionResolver + ContextProvider + + AddRule(prefix string, spec RepositorySpec, prio ...int) + Finalize() error +} + +func NewMatchingResolver(ctx ContextProvider) MatchingResolver { + return internal.NewMatchingResolver(ctx.OCMContext()) +} + +type ResolverRule = internal.ResolverRule diff --git a/api/ocm/resolver_test.go b/api/ocm/resolvers/resolver_test.go similarity index 93% rename from api/ocm/resolver_test.go rename to api/ocm/resolvers/resolver_test.go index 321bd952b3..47a20d0c79 100644 --- a/api/ocm/resolver_test.go +++ b/api/ocm/resolvers/resolver_test.go @@ -1,4 +1,4 @@ -package ocm_test +package resolvers_test import ( "fmt" @@ -13,6 +13,7 @@ import ( "ocm.software/ocm/api/ocm/extensions/repositories/ctf" "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" "ocm.software/ocm/api/ocm/internal" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/accessobj" ) @@ -96,13 +97,13 @@ var _ = Describe("resolver", func() { }) }) -func Check(r ocm.ResolverRule, prefix string, spec ocm.RepositorySpec, prio int) { +func Check(r resolvers.ResolverRule, prefix string, spec ocm.RepositorySpec, prio int) { ExpectWithOffset(1, r.GetPrefix()).To(Equal(prefix)) ExpectWithOffset(1, r.GetPriority()).To(Equal(prio)) ExpectWithOffset(1, r.GetSpecification()).To(BeIdenticalTo(spec)) } -func Print(rules []ocm.ResolverRule) { +func Print(rules []resolvers.ResolverRule) { list := []string{} for _, r := range rules { list = append(list, fmt.Sprintf("[%d]%s", r.GetPriority(), r.GetPrefix())) diff --git a/api/ocm/resolvers/suite_test.go b/api/ocm/resolvers/suite_test.go new file mode 100644 index 0000000000..71105f21c3 --- /dev/null +++ b/api/ocm/resolvers/suite_test.go @@ -0,0 +1,13 @@ +package resolvers_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM Resolvers Test Suite") +} diff --git a/api/ocm/resourcerefs/resourceref.go b/api/ocm/resourcerefs/resourceref.go new file mode 100644 index 0000000000..67aa27afd5 --- /dev/null +++ b/api/ocm/resourcerefs/resourceref.go @@ -0,0 +1,104 @@ +package resourcerefs + +import ( + "fmt" + + . "github.com/mandelsoft/goutils/finalizer" + + "github.com/mandelsoft/goutils/errors" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + ocm "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/resolvers" + common "ocm.software/ocm/api/utils/misc" +) + +func ResolveReferencePath(cv ocm.ComponentVersionAccess, path []metav1.Identity, resolver ocm.ComponentVersionResolver) (ocm.ComponentVersionAccess, error) { + if cv == nil { + return nil, fmt.Errorf("no component version specified") + } + eff, err := cv.Dup() + if err != nil { + return nil, errors.Wrapf(err, "component version already closed") + } + + var final Finalizer + defer final.Finalize() + + for _, cr := range path { + final.Close(eff) + cref, err := eff.GetReference(cr) + if err != nil { + return nil, errors.Wrapf(err, "%s", common.VersionedElementKey(cv)) + } + + compoundResolver := resolvers.NewCompoundResolver(eff.Repository(), resolver) + eff, err = compoundResolver.LookupComponentVersion(cref.GetComponentName(), cref.GetVersion()) + if err != nil { + return nil, errors.Wrapf(err, "cannot resolve component version for reference %s", cr.String()) + } + if eff == nil { + return nil, errors.ErrNotFound(ocm.KIND_COMPONENTVERSION, cref.String()) + } + final.Finalize() + } + return eff, nil +} + +func MatchResourceReference(cv ocm.ComponentVersionAccess, typ string, ref metav1.ResourceReference, resolver resolvers.ComponentVersionResolver) (ocm.ResourceAccess, ocm.ComponentVersionAccess, error) { + eff, err := ResolveReferencePath(cv, ref.ReferencePath, resolver) + if err != nil { + return nil, nil, err + } + + if len(eff.GetDescriptor().Resources) == 0 && len(ref.Resource) == 0 { + return nil, nil, errors.ErrNotFound(ocm.KIND_RESOURCE) + } +outer: + for i, r := range eff.GetDescriptor().Resources { + if r.Type != typ && typ != "" { + continue + } + for k, v := range ref.Resource { + switch k { + case metav1.SystemIdentityName: + if v != r.Name { + continue outer + } + case metav1.SystemIdentityVersion: + if v != r.Version { + continue outer + } + default: + if r.ExtraIdentity == nil || r.ExtraIdentity[k] != v { + continue outer + } + } + } + res, err := eff.GetResourceByIndex(i) + if err != nil { + eff.Close() + return nil, nil, err + } + return res, eff, nil + } + eff.Close() + return nil, nil, errors.ErrNotFound(ocm.KIND_RESOURCE, ref.Resource.String()) +} + +func ResolveResourceReference(cv ocm.ComponentVersionAccess, ref metav1.ResourceReference, resolver ocm.ComponentVersionResolver) (ocm.ResourceAccess, ocm.ComponentVersionAccess, error) { + if len(ref.Resource) == 0 || len(ref.Resource["name"]) == 0 { + return nil, nil, errors.Newf("at least resource name must be specified for resource reference") + } + + eff, err := ResolveReferencePath(cv, ref.ReferencePath, resolver) + if err != nil { + return nil, nil, err + } + r, err := eff.GetResource(ref.Resource) + if err != nil { + eff.Close() + return nil, nil, err + } + return r, eff, nil +} diff --git a/api/ocm/ocmutils/resourceref_test.go b/api/ocm/resourcerefs/resourceref_test.go similarity index 95% rename from api/ocm/ocmutils/resourceref_test.go rename to api/ocm/resourcerefs/resourceref_test.go index b85c26fb6b..f8a0488dbc 100644 --- a/api/ocm/ocmutils/resourceref_test.go +++ b/api/ocm/resourcerefs/resourceref_test.go @@ -1,4 +1,4 @@ -package ocmutils_test +package resourcerefs_test import ( "io" @@ -8,10 +8,11 @@ import ( . "github.com/onsi/gomega" . "ocm.software/ocm/api/helper/builder" - "ocm.software/ocm/api/ocm" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + ocm "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/tech/signing/handlers/rsa" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/accessobj" @@ -131,7 +132,7 @@ var _ = Describe("resolving local resource references", func() { Close(dup) ref := metav1.NewResourceRef(metav1.NewIdentity("topdata")) - _, _, err := utils.ResolveResourceReference(dup, ref, nil) + _, _, err := resourcerefs.ResolveResourceReference(dup, ref, nil) MustFailWithMessage(err, "component version already closed: closed") }) }) diff --git a/api/ocm/resourcerefs/suite_test.go b/api/ocm/resourcerefs/suite_test.go new file mode 100644 index 0000000000..297380802e --- /dev/null +++ b/api/ocm/resourcerefs/suite_test.go @@ -0,0 +1,13 @@ +package resourcerefs_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM ResourceRefs Test Suite") +} diff --git a/api/ocm/tools/signing/options.go b/api/ocm/tools/signing/options.go index ba735785ee..b7fe9a72e7 100644 --- a/api/ocm/tools/signing/options.go +++ b/api/ocm/tools/signing/options.go @@ -12,6 +12,7 @@ import ( "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/compdesc" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/tech/signing" "ocm.software/ocm/api/tech/signing/hasher/sha256" "ocm.software/ocm/api/tech/signing/signutils" @@ -221,7 +222,7 @@ func Resolver(h ...ocm.ComponentVersionResolver) Option { } func (o *resolver) ApplySigningOption(opts *Options) { - opts.Resolver = ocm.NewCompoundResolver(append([]ocm.ComponentVersionResolver{opts.Resolver}, o.resolver...)...) + opts.Resolver = resolvers.NewCompoundResolver(append([]ocm.ComponentVersionResolver{opts.Resolver}, o.resolver...)...) } //////////////////////////////////////////////////////////////////////////////// diff --git a/api/ocm/tools/signing/signing_test.go b/api/ocm/tools/signing/signing_test.go index 1fcb2a9013..32c988daaf 100644 --- a/api/ocm/tools/signing/signing_test.go +++ b/api/ocm/tools/signing/signing_test.go @@ -26,6 +26,7 @@ import ( "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" "ocm.software/ocm/api/ocm/extensions/repositories/composition" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/ocm/tools/signing/signingtest" "ocm.software/ocm/api/tech/signing" "ocm.software/ocm/api/tech/signing/handlers/rsa" @@ -137,7 +138,7 @@ var _ = Describe("access method", func() { src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv := Must(resolver.LookupComponentVersion(COMPONENTA, VERSION)) closer := session.AddCloser(cv) @@ -226,7 +227,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) Expect(err).To(Succeed()) archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) Expect(err).To(Succeed()) @@ -305,7 +306,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) Expect(err).To(Succeed()) archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) Expect(err).To(Succeed()) @@ -356,7 +357,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) Expect(err).To(Succeed()) archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv, err := resolver.LookupComponentVersion(COMPONENTB, VERSION) Expect(err).To(Succeed()) @@ -435,7 +436,7 @@ applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1] src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) Expect(err).To(Succeed()) session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv, err := resolver.LookupComponentVersion(COMPONENTA, VERSION) Expect(err).To(Succeed()) @@ -593,7 +594,7 @@ applying to version "github.com/mandelsoft/ref:v1"[github.com/mandelsoft/ref:v1] Expect(err).To(Succeed()) arch.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) log := HashComponent(resolver, COMPONENTD, D_COMPD, DigestMode(c.Mode())) @@ -677,7 +678,7 @@ applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1] Expect(err).To(Succeed()) arch.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) log := SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, DigestMode(c.Mode())) @@ -755,7 +756,7 @@ applying to version "github.com/mandelsoft/top:v1"[github.com/mandelsoft/top:v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) arch.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) log := SignComponent(resolver, SIGNATURE, COMPONENTB, subst["D_COMPB_X"], DigestMode(DIGESTMODE_TOP), HashByAlgo(sha512.Algorithm)) Expect(log).To(StringEqualTrimmedWithContext(` @@ -847,7 +848,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) arch.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) fmt.Printf("SIGN D\n") log := SignComponent(resolver, SIGNATURE, COMPONENTD, digestD, DigestMode(DIGESTMODE_TOP)) @@ -922,7 +923,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) finalizer.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) fmt.Printf("SIGN B\n") _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_LOCAL)) @@ -944,7 +945,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) finalizer.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) fmt.Printf("RESIGN B\n") _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) @@ -982,7 +983,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) finalizer.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) fmt.Printf("SIGN B\n") _ = SignComponent(resolver, SIGNATURE, COMPONENTB, D_COMPB, DigestMode(DIGESTMODE_TOP), SignatureName(SIGNATURE2, true)) @@ -1004,7 +1005,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) finalizer.Close(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) fmt.Printf("SIGN D\n") _ = SignComponent(resolver, SIGNATURE, COMPONENTD, D_COMPD, Recursive(), DigestMode(DIGESTMODE_LOCAL)) @@ -1066,7 +1067,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env)) defer Close(src, "ctf") - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv := Must(resolver.LookupComponentVersion(COMPONENTC, VERSION)) defer cv.Close() @@ -1123,7 +1124,7 @@ github.com/mandelsoft/test:v1: SHA-256:${D_COMPA}[jsonNormalisation/v1] It("signs with certificate and default issuer", func() { digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) + res := resolvers.NewDedicatedResolver(cv) buf := SignComponent(res, PROVIDER, COMPONENTA, digest, PrivateKey(PROVIDER, priv), PublicKey(PROVIDER, pemBytes), RootCertificates(ca)) Expect(buf).To(StringEqualTrimmedWithContext(` @@ -1143,7 +1144,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v It("signs with certificate and explicit CN issuer", func() { digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) + res := resolvers.NewDedicatedResolver(cv) buf := SignComponent(res, SIGNATURE, COMPONENTA, digest, PrivateKey(SIGNATURE, priv), PublicKey(SIGNATURE, pemBytes), RootCertificates(ca), Issuer(PROVIDER)) Expect(buf).To(StringEqualTrimmedWithContext(` @@ -1167,7 +1168,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v It("signs with certificate and issuer", func() { digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) + res := resolvers.NewDedicatedResolver(cv) issuer := &pkix.Name{ CommonName: PROVIDER, Country: []string{"DE"}, @@ -1198,7 +1199,7 @@ applying to version "github.com/mandelsoft/test:v1"[github.com/mandelsoft/test:v It("signs with certificate, issuer and tsa", func() { digest := "9cf14695c864411cad03071a8766e6769bb00373bdd8c65887e4644cc285dc78" - res := ocm.NewDedicatedResolver(cv) + res := resolvers.NewDedicatedResolver(cv) issuer := &pkix.Name{ CommonName: "mandelsoft", Country: []string{"DE"}, diff --git a/api/ocm/tools/toi/install/action.go b/api/ocm/tools/toi/install/action.go index e901b97e26..20076c8842 100644 --- a/api/ocm/tools/toi/install/action.go +++ b/api/ocm/tools/toi/install/action.go @@ -22,6 +22,7 @@ import ( resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/ocm/tools/toi" "ocm.software/ocm/api/ocm/tools/transfer" "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" @@ -96,7 +97,7 @@ func DetermineExecutor(executor *toi.Executor, octx ocm.Context, cv ocm.Componen if executor.ResourceRef == nil { return nil, errors.Newf("executor resource reference required for toi package executor") } - res, eff, err := utils.ResolveResourceReference(cv, *executor.ResourceRef, resolver) + res, eff, err := resourcerefs.ResolveResourceReference(cv, *executor.ResourceRef, resolver) if err != nil { return nil, errors.ErrNotFoundWrap(err, "executor resource", executor.ResourceRef.String()) } @@ -121,7 +122,7 @@ func DetermineExecutor(executor *toi.Executor, octx ocm.Context, cv ocm.Componen return nil, errors.Newf("executor image reference required for toi executor") } var eff2 ocm.ComponentVersionAccess - res, eff2, err = utils.ResolveResourceReference(eff, *espec.Spec.ImageRef, resolver) + res, eff2, err = resourcerefs.ResolveResourceReference(eff, *espec.Spec.ImageRef, resolver) if err != nil { return nil, errors.ErrNotFoundWrap(err, "executor resource", executor.ResourceRef.String()) } @@ -240,7 +241,7 @@ func ProcessConfig(name string, octx ocm.Context, cv ocm.ComponentVersionAccess, stubs := spiff.Options{} for i, lib := range libraries { - res, eff, err := utils.ResolveResourceReference(cv, lib, resolver) + res, eff, err := resourcerefs.ResolveResourceReference(cv, lib, resolver) if err != nil { return nil, errors.ErrNotFound("library resource %s not found", lib.String()) } diff --git a/api/ocm/tools/toi/install/execute.go b/api/ocm/tools/toi/install/execute.go index 31405ae9a8..25b466400a 100644 --- a/api/ocm/tools/toi/install/execute.go +++ b/api/ocm/tools/toi/install/execute.go @@ -5,7 +5,7 @@ import ( "ocm.software/ocm/api/ocm" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" - utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/ocm/tools/toi" "ocm.software/ocm/api/utils/blobaccess/blobaccess" common "ocm.software/ocm/api/utils/misc" @@ -33,7 +33,7 @@ func Execute(p common.Printer, d Driver, name string, rid metav1.Identity, creds } } - ires, _, err := utils.MatchResourceReference(cv, toi.TypeTOIPackage, metav1.NewResourceRef(rid), nil) + ires, _, err := resourcerefs.MatchResourceReference(cv, toi.TypeTOIPackage, metav1.NewResourceRef(rid), nil) if err != nil { return nil, errors.Wrapf(err, "package resource in %s", common.VersionedElementKey(cv).String()) } diff --git a/api/ocm/tools/transfer/transferhandler/standard/handler.go b/api/ocm/tools/transfer/transferhandler/standard/handler.go index 3b871bfcab..06020464e3 100644 --- a/api/ocm/tools/transfer/transferhandler/standard/handler.go +++ b/api/ocm/tools/transfer/transferhandler/standard/handler.go @@ -10,6 +10,7 @@ import ( metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/cpi" "ocm.software/ocm/api/ocm/cpi/accspeccpi" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" "ocm.software/ocm/api/utils/accessio" ) @@ -53,7 +54,7 @@ func (h *Handler) TransferVersion(repo ocm.Repository, src ocm.ComponentVersionA return nil, nil, errors.Wrapf(err, "failed looking up in target") } } - compoundResolver := ocm.NewCompoundResolver(repo, h.opts.GetResolver()) + compoundResolver := resolvers.NewCompoundResolver(repo, h.opts.GetResolver()) cv, err := compoundResolver.LookupComponentVersion(meta.GetComponentName(), meta.Version) return cv, h, err } diff --git a/api/ocm/tools/transfer/transferhandler/standard/handler_test.go b/api/ocm/tools/transfer/transferhandler/standard/handler_test.go index 2a4a7936c9..6ee3d032a4 100644 --- a/api/ocm/tools/transfer/transferhandler/standard/handler_test.go +++ b/api/ocm/tools/transfer/transferhandler/standard/handler_test.go @@ -25,6 +25,7 @@ import ( "ocm.software/ocm/api/ocm/extensions/attrs/compositionmodeattr" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/resolvers" ocmsign "ocm.software/ocm/api/ocm/tools/signing" "ocm.software/ocm/api/ocm/tools/transfer" "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" @@ -488,7 +489,7 @@ warning: version "github.com/mandelsoft/test:v1" already present, but differs cv, err := src.LookupComponentVersion(COMPONENT, VERSION) Expect(err).To(Succeed()) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) opts := ocmsign.NewOptions( ocmsign.Sign(signingattr.Get(env.OCMContext()).GetSigner(SIGN_ALGO), SIGNATURE), @@ -513,7 +514,7 @@ warning: version "github.com/mandelsoft/test:v1" already present, but differs Expect(err).To(Succeed()) Expect(env.DirExists(OUT)).To(BeTrue()) - resolver = ocm.NewCompoundResolver(tgt) + resolver = resolvers.NewCompoundResolver(tgt) opts = ocmsign.NewOptions( ocmsign.Resolver(resolver), diff --git a/api/utils/blobaccess/ocm/access.go b/api/utils/blobaccess/ocm/access.go new file mode 100644 index 0000000000..394218f06b --- /dev/null +++ b/api/utils/blobaccess/ocm/access.go @@ -0,0 +1,38 @@ +package ocm + +import ( + "github.com/mandelsoft/goutils/finalizer" + + "ocm.software/ocm/api/utils/blobaccess/bpi" + "ocm.software/ocm/api/utils/refmgmt" +) + +func DataAccess(cvp ComponentVersionProvider, res ResourceProvider) (bpi.DataAccess, error) { + return BlobAccess(cvp, res) +} + +func BlobAccess(cvp ComponentVersionProvider, res ResourceProvider) (blob bpi.BlobAccess, rerr error) { + var finalize finalizer.Finalizer + defer finalize.FinalizeWithErrorPropagation(&rerr) + + cv, err := refmgmt.ToLazy(cvp.GetComponentVersionAccess()) + if err != nil { + return nil, err + } + finalize.Close(cv) + + r, eff, err := res.GetResource(cv) + if eff != nil { + finalize.Close(refmgmt.AsLazy(eff)) + } + if err != nil { + return nil, err + } + return r.BlobAccess() +} + +func Provider(cvp ComponentVersionProvider, res ResourceProvider) bpi.BlobAccessProvider { + return bpi.BlobAccessProviderFunction(func() (bpi.BlobAccess, error) { + return BlobAccess(cvp, res) + }) +} diff --git a/api/utils/blobaccess/ocm/access_test.go b/api/utils/blobaccess/ocm/access_test.go new file mode 100644 index 0000000000..8680f04fd0 --- /dev/null +++ b/api/utils/blobaccess/ocm/access_test.go @@ -0,0 +1,69 @@ +package ocm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/api/helper/builder" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/blobaccess/ocm" + me "ocm.software/ocm/api/utils/blobaccess/ocm" + "ocm.software/ocm/api/utils/mime" +) + +const ( + ARCH = "/arch.ctf" + COMP1 = "acme.org/test1" + COMP2 = "acme.org/test2" + VERS = "v1" +) + +var _ = Describe("blobaccess for ocm", func() { + Context("maven filesystem repository", func() { + var env *Builder + + BeforeEach(func() { + env = NewBuilder() + + env.OCMCommonTransport(ARCH, accessio.FormatDirectory, func() { + env.ComponentVersion(COMP1, VERS, func() { + env.Resource("test", VERS, resourcetypes.PLAIN_TEXT, v1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "test data") + }) + }) + + env.ComponentVersion(COMP2, VERS, func() { + env.Reference("ref", COMP1, VERS) + }) + }) + }) + + AfterEach(func() { + MustBeSuccessful(env.Cleanup()) + }) + + It("blobaccess for selector", func() { + b := Must(me.BlobAccess(ocm.ByRepositorySpecAndName(env.OCMContext(), Must(ctf.NewRepositorySpec(accessobj.ACC_READONLY, ARCH, accessio.PathFileSystem(env.FileSystem()))), COMP1, VERS), + ocm.ByResourceSelector(rscsel.Name("test")), + )) + defer Close(b, "blobaccess") + + Expect(string(Must(b.Get()))).To(Equal("test data")) + }) + + It("blobaccess for ref path", func() { + b := Must(me.BlobAccess(ocm.ByRepositorySpecAndName(env.OCMContext(), Must(ctf.NewRepositorySpec(accessobj.ACC_READONLY, ARCH, accessio.PathFileSystem(env.FileSystem()))), COMP2, VERS), + ocm.ByResourcePath(v1.NewIdentity("test"), v1.NewIdentity("ref")), + )) + defer Close(b, "blobaccess") + + Expect(string(Must(b.Get()))).To(Equal("test data")) + }) + }) +}) diff --git a/api/utils/blobaccess/ocm/compvers.go b/api/utils/blobaccess/ocm/compvers.go new file mode 100644 index 0000000000..39c1d6dc69 --- /dev/null +++ b/api/utils/blobaccess/ocm/compvers.go @@ -0,0 +1,77 @@ +package ocm + +import ( + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/refmgmt" +) + +// ComponentVersionProvider is a factory for component versions. +// Every call provides a separately closeable component versuion access. +// An implementation should not hold private views of objects, +// The life cycle of all those objects should be left to the +// creator of ComponentVersionProvider implementation. Therefore, it does not +// have a close method. +type ComponentVersionProvider interface { + GetComponentVersionAccess() (cpi.ComponentVersionAccess, error) +} + +//////////////////////////////////////////////////////////////////////////////// + +type bycv struct { + cv cpi.ComponentVersionAccess +} + +var _ ComponentVersionProvider = (*bycv)(nil) + +func ByComponentVersion(cv cpi.ComponentVersionAccess) ComponentVersionProvider { + return &bycv{cv} +} + +func (c *bycv) GetComponentVersionAccess() (cpi.ComponentVersionAccess, error) { + return c.cv.Dup() +} + +//////////////////////////////////////////////////////////////////////////////// + +type byresolver struct { + resolver cpi.ComponentVersionResolver + comp string + vers string +} + +var _ ComponentVersionProvider = (*byresolver)(nil) + +func ByResolverAndName(resolver cpi.ComponentVersionResolver, comp, vers string) ComponentVersionProvider { + return &byresolver{resolver, comp, vers} +} + +func (c *byresolver) GetComponentVersionAccess() (cpi.ComponentVersionAccess, error) { + return c.resolver.LookupComponentVersion(c.comp, c.vers) +} + +//////////////////////////////////////////////////////////////////////////////// + +type byrepospec struct { + ctx cpi.Context + spec cpi.RepositorySpec + comp string + vers string +} + +var _ ComponentVersionProvider = (*byrepospec)(nil) + +func ByRepositorySpecAndName(ctx cpi.ContextProvider, spec cpi.RepositorySpec, comp, vers string) ComponentVersionProvider { + if ctx == nil { + ctx = cpi.DefaultContext() + } + return &byrepospec{ctx.OCMContext(), spec, comp, vers} +} + +func (c *byrepospec) GetComponentVersionAccess() (cpi.ComponentVersionAccess, error) { + repo, err := refmgmt.ToLazy(c.ctx.RepositoryForSpec(c.spec)) + if err != nil { + return nil, err + } + defer repo.Close() + return repo.LookupComponentVersion(c.comp, c.vers) +} diff --git a/api/utils/blobaccess/ocm/ref.go b/api/utils/blobaccess/ocm/ref.go new file mode 100644 index 0000000000..e037e1c800 --- /dev/null +++ b/api/utils/blobaccess/ocm/ref.go @@ -0,0 +1,93 @@ +package ocm + +import ( + "github.com/mandelsoft/goutils/errors" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/resourcerefs" + "ocm.software/ocm/api/ocm/selectors/rscsel" + "ocm.software/ocm/api/utils" +) + +// ResourceProvider selects a resource from a component version. +// It should not hold any separately closeabvle view on +// an object. The lifecycle of those objects should be left +// to the creator of the implementation of this interface. +type ResourceProvider interface { + GetResource(cv cpi.ComponentVersionAccess) (cpi.ResourceAccess, cpi.ComponentVersionAccess, error) +} + +//////////////////////////////////////////////////////////////////////////////// + +type byid struct { + id metav1.Identity +} + +var _ ResourceProvider = (*byid)(nil) + +func ByResourceId(id metav1.Identity) ResourceProvider { + return &byid{id} +} + +func (r *byid) GetResource(cv cpi.ComponentVersionAccess) (cpi.ResourceAccess, cpi.ComponentVersionAccess, error) { + cv, err := cv.Dup() + if err != nil { + return nil, nil, err + } + res, err := cv.GetResource(r.id) + if err != nil { + cv.Close() + return nil, nil, err + } + return res, cv, nil +} + +//////////////////////////////////////////////////////////////////////////////// + +type byref struct { + resolver cpi.ComponentVersionResolver + ref metav1.ResourceReference +} + +var _ ResourceProvider = (*byref)(nil) + +func ByResourcePath(id metav1.Identity, path ...metav1.Identity) ResourceProvider { + return &byref{nil, metav1.NewNestedResourceRef(id, path)} +} + +func ByResourceRef(ref metav1.ResourceReference, res ...cpi.ComponentVersionResolver) ResourceProvider { + return &byref{utils.Optional(res...), ref} +} + +func (r *byref) GetResource(cv cpi.ComponentVersionAccess) (cpi.ResourceAccess, cpi.ComponentVersionAccess, error) { + return resourcerefs.ResolveResourceReference(cv, r.ref, r.resolver) +} + +//////////////////////////////////////////////////////////////////////////////// + +type bysel struct { + sel []rscsel.Selector +} + +var _ ResourceProvider = (*bysel)(nil) + +func ByResourceSelector(sel ...rscsel.Selector) ResourceProvider { + return &bysel{sel} +} + +func (r *bysel) GetResource(cv cpi.ComponentVersionAccess) (cpi.ResourceAccess, cpi.ComponentVersionAccess, error) { + res, err := cv.SelectResources(r.sel...) + if err != nil { + return nil, nil, err + } + if len(res) == 0 { + return nil, nil, errors.ErrNotFound(cpi.KIND_RESOURCE) + } + + cv, err = cv.Dup() + if err != nil { + return nil, nil, err + } + return res[0], cv, nil +} diff --git a/api/utils/blobaccess/ocm/suite_test.go b/api/utils/blobaccess/ocm/suite_test.go new file mode 100644 index 0000000000..cc788df8ec --- /dev/null +++ b/api/utils/blobaccess/ocm/suite_test.go @@ -0,0 +1,13 @@ +package ocm_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM Access Test Suite") +} diff --git a/api/utils/clisupport/identity.go b/api/utils/clisupport/identity.go new file mode 100644 index 0000000000..977ddb6260 --- /dev/null +++ b/api/utils/clisupport/identity.go @@ -0,0 +1,50 @@ +package clisupport + +import ( + "fmt" + "strings" + + "github.com/mandelsoft/goutils/errors" + + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" +) + +func ParseIdentityPath(ids ...string) ([]metav1.Identity, error) { + var err error + result := []metav1.Identity{} + + var id metav1.Identity + for _, l := range ids { + name, value, err := ParseIdentityAttribute(l) + if err != nil { + return nil, err + } + if name != "name" { + if id == nil { + return nil, fmt.Errorf("first attribute must be the name attribute") + } + if id[name] != "" { + return nil, fmt.Errorf("attribute %q already set", name) + } + id[name] = value + } else { + if id != nil { + result = append(result, id) + } + id = metav1.Identity{name: value} + } + } + result = append(result, id) + return result, err +} + +func ParseIdentityAttribute(a string) (string, string, error) { + i := strings.Index(a, "=") + if i < 0 { + return "", "", errors.ErrInvalid("identity attribute", a) + } + name := a[:i] + value := a[i+1:] + + return name, value, nil +} diff --git a/api/utils/clisupport/identity_test.go b/api/utils/clisupport/identity_test.go new file mode 100644 index 0000000000..eb50748f39 --- /dev/null +++ b/api/utils/clisupport/identity_test.go @@ -0,0 +1,47 @@ +package clisupport_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/utils/clisupport" +) + +var _ = Describe("IdentityPath Parsing", func() { + Context("", func() { + It("handles simple identity", func() { + value := `name=alice` + flag := Must(clisupport.ParseIdentityPath(value)) + Expect(flag).To(Equal([]v1.Identity{{"name": "alice"}})) + }) + + It("handles simple path", func() { + value1 := `name=alice` + value2 := `husband=bob` + flag := Must(clisupport.ParseIdentityPath(value1, value2)) + Expect(flag).To(Equal([]v1.Identity{{"name": "alice", "husband": "bob"}})) + }) + + It("handles mulki path", func() { + value1 := `name=alice` + value2 := `husband=bob` + value3 := "name=bob" + value4 := "wife=alice" + value5 := "name=other" + flag := Must(clisupport.ParseIdentityPath(value1, value2, value3, value4, value5)) + Expect(flag).To(Equal([]v1.Identity{{"name": "alice", "husband": "bob"}, {"name": "bob", "wife": "alice"}, {"name": "other"}})) + }) + + It("rejects invalid value", func() { + value := `a=b` + ExpectError(clisupport.ParseIdentityPath(value)).To(MatchError("first attribute must be the name attribute")) + }) + + It("rejects invalid assignment", func() { + value := `a` + ExpectError(clisupport.ParseIdentityPath(value)).To(MatchError("identity attribute \"a\" is invalid")) + }) + }) +}) diff --git a/api/utils/clisupport/suite_test.go b/api/utils/clisupport/suite_test.go new file mode 100644 index 0000000000..f8092d2971 --- /dev/null +++ b/api/utils/clisupport/suite_test.go @@ -0,0 +1,13 @@ +package clisupport_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "clisupport Test Suite") +} diff --git a/api/utils/cobrautils/flag/identity_path.go b/api/utils/cobrautils/flag/identity_path.go new file mode 100644 index 0000000000..ddf823c55b --- /dev/null +++ b/api/utils/cobrautils/flag/identity_path.go @@ -0,0 +1,87 @@ +package flag + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/spf13/pflag" +) + +type identityPath struct { + value *[]map[string]string + changed bool +} + +func newIdentityPathValue(val []map[string]string, p *[]map[string]string) *identityPath { + ssv := new(identityPath) + ssv.value = p + *ssv.value = val + return ssv +} + +func (s *identityPath) Set(val string) error { + k, v, err := parseAssignment(val) + if err != nil { + return err + } + if !s.changed { + if k != "name" { + return fmt.Errorf("first identity attribute must be the name attribute") + } + *s.value = []map[string]string{{k: v}} + } else { + if k == "name" { + *s.value = append(*s.value, map[string]string{k: v}) + } else { + (*s.value)[len(*s.value)-1][k] = v + } + } + s.changed = true + return nil +} + +func (s *identityPath) Type() string { + return "{=}" +} + +func (s *identityPath) String() string { + if *s.value == nil { + return "" + } + var list []string + for _, v := range *s.value { + //nolint: errchkjson // initialized by unmarshal + s, _ := json.Marshal(v) + list = append(list, string(s)) + } + return "[" + strings.Join(list, ", ") + "]" +} + +func (s *identityPath) GetPath() []map[string]string { + return *s.value +} + +func IdentityPathVar(f *pflag.FlagSet, p *[]map[string]string, name string, value []map[string]string, usage string) { + f.VarP(newIdentityPathValue(value, p), name, "", usage) +} + +func IdentityPathVarP(f *pflag.FlagSet, p *[]map[string]string, name, shorthand string, value []map[string]string, usage string) { + f.VarP(newIdentityPathValue(value, p), name, shorthand, usage) +} + +func IdentityPathVarPF(f *pflag.FlagSet, p *[]map[string]string, name, shorthand string, value []map[string]string, usage string) *pflag.Flag { + return f.VarPF(newIdentityPathValue(value, p), name, shorthand, usage) +} + +func IdentityPath(f *pflag.FlagSet, name string, value []map[string]string, usage string) *[]map[string]string { + p := []map[string]string{} + IdentityPathVarP(f, &p, name, "", value, usage) + return &p +} + +func IdentityPathP(f *pflag.FlagSet, name, shorthand string, value []map[string]string, usage string) *[]map[string]string { + p := []map[string]string{} + IdentityPathVarP(f, &p, name, shorthand, value, usage) + return &p +} diff --git a/api/utils/cobrautils/flag/identity_path_test.go b/api/utils/cobrautils/flag/identity_path_test.go new file mode 100644 index 0000000000..4d96582aac --- /dev/null +++ b/api/utils/cobrautils/flag/identity_path_test.go @@ -0,0 +1,91 @@ +package flag + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/mandelsoft/goutils/testutils" + "github.com/spf13/pflag" +) + +var _ = Describe("identity path", func() { + var flags *pflag.FlagSet + + BeforeEach(func() { + flags = pflag.NewFlagSet("test", pflag.ContinueOnError) + }) + + It("handles simple identity", func() { + var flag []map[string]string + IdentityPathVarP(flags, &flag, "flag", "", nil, "test flag") + + value := `name=alice` + + Expect(flags.Parse([]string{"--flag", value})).To(Succeed()) + Expect(flag).To(Equal([]map[string]string{{"name": "alice"}})) + }) + + It("handles simple path", func() { + var flag []map[string]string + IdentityPathVarP(flags, &flag, "flag", "", nil, "test flag") + + value1 := `name=alice` + value2 := `husband=bob` + + Expect(flags.Parse([]string{"--flag", value1, "--flag", value2})).To(Succeed()) + Expect(flag).To(Equal([]map[string]string{{"name": "alice", "husband": "bob"}})) + }) + + It("handles multi path", func() { + var flag []map[string]string + IdentityPathVarP(flags, &flag, "flag", "", nil, "test flag") + + value1 := `name=alice` + value2 := `husband=bob` + value3 := "name=bob" + value4 := "wife=alice" + value5 := "name=other" + Expect(flags.Parse([]string{"--flag", value1, "--flag", value2, "--flag", value3, "--flag", value4, "--flag", value5})).To(Succeed()) + Expect(flag).To(Equal([]map[string]string{{"name": "alice", "husband": "bob"}, {"name": "bob", "wife": "alice"}, {"name": "other"}})) + }) + + It("shows default", func() { + var flag []map[string]string + IdentityPathVarP(flags, &flag, "flag", "", []map[string]string{{"name": "alice"}}, "test flag") + + Expect(flags.FlagUsages()).To(testutils.StringEqualTrimmedWithContext(`--flag {=} test flag (default [{"name":"alice"}])`)) + }) + + It("handles replaces default content", func() { + var flag []map[string]string + IdentityPathVarP(flags, &flag, "flag", "", []map[string]string{{"name": "other"}}, "test flag") + + value1 := `name=alice` + value2 := `husband=bob` + + Expect(flags.Parse([]string{"--flag", value1, "--flag", value2})).To(Succeed()) + Expect(flag).To(Equal([]map[string]string{{"name": "alice", "husband": "bob"}})) + }) + + It("rejects invalid value", func() { + var flag []map[string]string + IdentityPathVarP(flags, &flag, "flag", "", nil, "test flag") + + value := `a=b` + + err := flags.Parse([]string{"--flag", value}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("invalid argument \"a=b\" for \"--flag\" flag: first identity attribute must be the name attribute")) + }) + + It("rejects invalid assignment", func() { + var flag map[string]interface{} + StringToValueVarP(flags, &flag, "flag", "", nil, "test flag") + + value := `a` + + err := flags.Parse([]string{"--flag", value}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(Equal("invalid argument \"a\" for \"--flag\" flag: expected =")) + }) +}) diff --git a/api/utils/cobrautils/flagsets/types.go b/api/utils/cobrautils/flagsets/types.go index 5863a8e371..b3aaacf097 100644 --- a/api/utils/cobrautils/flagsets/types.go +++ b/api/utils/cobrautils/flagsets/types.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/pflag" + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/utils/cobrautils/flag" "ocm.software/ocm/api/utils/cobrautils/groups" ) @@ -551,3 +552,44 @@ func (o *StringSliceMapColonOption) AddFlags(fs *pflag.FlagSet) { func (o *StringSliceMapColonOption) Value() interface{} { return o.value } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +type IdentityPathOptionType struct { + TypeOptionBase +} + +func NewIdentityPathOptionType(name string, description string) ConfigOptionType { + return &IdentityPathOptionType{ + TypeOptionBase: TypeOptionBase{name, description}, + } +} + +func (s *IdentityPathOptionType) Equal(optionType ConfigOptionType) bool { + return reflect.DeepEqual(s, optionType) +} + +func (s *IdentityPathOptionType) Create() Option { + return &IdentityPathOption{ + OptionBase: NewOptionBase(s), + } +} + +type IdentityPathOption struct { + OptionBase + value []map[string]string +} + +var _ Option = (*IdentityPathOption)(nil) + +func (o *IdentityPathOption) AddFlags(fs *pflag.FlagSet) { + o.TweakFlag(flag.IdentityPathVarPF(fs, &o.value, o.otyp.GetName(), "", nil, o.otyp.GetDescription())) +} + +func (o *IdentityPathOption) Value() interface{} { + var result []v1.Identity + for _, v := range o.value { + result = append(result, v1.Identity(v)) + } + return result +} diff --git a/api/utils/testutils/doc.go b/api/utils/testutils/doc.go deleted file mode 100644 index 546124a922..0000000000 --- a/api/utils/testutils/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Deprecated: This package is deprecated and will be removed in a future release. Please use the testutils package -// provided by github.com/mandelsoft/goutils. -package testutils diff --git a/api/utils/testutils/object.go b/api/utils/testutils/object.go deleted file mode 100644 index 1bdbbe94a9..0000000000 --- a/api/utils/testutils/object.go +++ /dev/null @@ -1,37 +0,0 @@ -package testutils - -import ( - "fmt" - "strings" - - "github.com/go-test/deep" - "github.com/onsi/gomega/format" - "github.com/onsi/gomega/types" -) - -// DeepEqual compares two objects and shows diff on failure. -func DeepEqual(expected interface{}) types.GomegaMatcher { - return &DeepEqualMatcher{ - Expected: expected, - } -} - -type DeepEqualMatcher struct { - Expected interface{} -} - -func (matcher *DeepEqualMatcher) Match(actual interface{}) (success bool, err error) { - return len(deep.Equal(actual, matcher.Expected)) == 0, nil -} - -func (matcher *DeepEqualMatcher) FailureMessage(actual interface{}) (message string) { - diff := deep.Equal(actual, matcher.Expected) - if len(diff) > 0 { - return fmt.Sprintf("unexpected diff in deep equal: \n %s\n", strings.Join(diff, "\n ")) - } - return format.Message(actual, "to equal", matcher.Expected) -} - -func (matcher *DeepEqualMatcher) NegatedFailureMessage(actual interface{}) (message string) { - return format.Message(actual, "not to equal", matcher.Expected) -} diff --git a/api/utils/testutils/package.go b/api/utils/testutils/package.go deleted file mode 100644 index a3ec75d19d..0000000000 --- a/api/utils/testutils/package.go +++ /dev/null @@ -1,77 +0,0 @@ -package testutils - -import ( - "fmt" - "strings" - - "github.com/mandelsoft/filepath/pkg/filepath" - "github.com/mandelsoft/goutils/general" - "github.com/mandelsoft/goutils/pkgutils" - "github.com/mandelsoft/vfs/pkg/osfs" - "github.com/mandelsoft/vfs/pkg/vfs" - "golang.org/x/mod/modfile" -) - -const GO_MOD = "go.mod" - -func GetPackagePathFromProjectRoot(i ...interface{}) (string, error) { - pkg, err := pkgutils.GetPackageName(i...) - if err != nil { - return "", err - } - mod, err := GetModuleName() - if err != nil { - return "", err - } - path, ok := strings.CutPrefix(pkg, mod+"/") - if !ok { - return "", fmt.Errorf("prefix %q not found in %q", mod, pkg) - } - return path, nil -} - -// GetModuleName returns a go modules module name by finding and parsing the go.mod file. -func GetModuleName() (string, error) { - pathToRoot, err := GetRelativePathToProjectRoot() - if err != nil { - return "", err - } - pathToGoMod := filepath.Join(pathToRoot, GO_MOD) - // Read the content of the go.mod file - data, err := vfs.ReadFile(osfs.OsFs, pathToGoMod) - if err != nil { - return "", err - } - - // Parse the go.mod file - modFile, err := modfile.Parse(GO_MOD, data, nil) - if err != nil { - return "", fmt.Errorf("error parsing %s file: %w", GO_MOD, err) - } - - // Print the module path - return modFile.Module.Mod.Path, nil -} - -// GetRelativePathToProjectRoot calculates the relative path to a go projects root directory. -// It therefore assumes that the project root is the directory containing the go.mod file. -// The optional parameter i determines how many directories the function will step up through, attempting to find a -// go.mod file. If it cannot find a directory with a go.mod file within i iterations, the function throws an error. -func GetRelativePathToProjectRoot(i ...int) (string, error) { - iterations := general.OptionalDefaulted(20, i...) - - path := "." - for count := 0; count < iterations; count++ { - if ok, err := vfs.FileExists(osfs.OsFs, filepath.Join(path, GO_MOD)); err != nil || ok { - if err != nil { - return "", fmt.Errorf("failed to check if %s exists: %w", GO_MOD, err) - } - return path, nil - } - if count == iterations { - return "", fmt.Errorf("could not find %s (within %d steps)", GO_MOD, iterations) - } - path = filepath.Join(path, "..") - } - return "", nil -} diff --git a/api/utils/testutils/package_test.go b/api/utils/testutils/package_test.go deleted file mode 100644 index 13cd3a28bd..0000000000 --- a/api/utils/testutils/package_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package testutils_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - me "ocm.software/ocm/api/utils/testutils" -) - -var _ = Describe("package tests", func() { - It("go module name", func() { - mod := me.Must(me.GetModuleName()) - Expect(mod).To(Equal("ocm.software/ocm")) - }) -}) diff --git a/api/utils/testutils/signing_test.go b/api/utils/testutils/signing_test.go deleted file mode 100644 index ad88a136b3..0000000000 --- a/api/utils/testutils/signing_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package testutils_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - common "ocm.software/ocm/api/utils/misc" - "ocm.software/ocm/api/utils/testutils" -) - -var _ = Describe("normalization", func() { - It("compares with substitution variables", func() { - exp := "A ${TEST}." - res := "A testcase." - vars := common.Properties{ - "TEST": "testcase", - } - Expect(res).To(testutils.StringEqualTrimmedWithContext(exp, common.Properties{}, vars)) - Expect(res).To(testutils.StringEqualTrimmedWithContext(exp, vars, common.Properties{})) - }) -}) diff --git a/api/utils/testutils/string.go b/api/utils/testutils/string.go deleted file mode 100644 index b8af087548..0000000000 --- a/api/utils/testutils/string.go +++ /dev/null @@ -1,213 +0,0 @@ -package testutils - -import ( - "encoding/json" - "fmt" - "regexp" - "strings" - - "github.com/drone/envsubst" - "github.com/mandelsoft/goutils/errors" - "github.com/onsi/gomega/format" - "github.com/onsi/gomega/types" -) - -type Substitutions = map[string]string - -func SubstList(values ...string) map[string]string { - r := map[string]string{} - for i := 0; i+1 < len(values); i += 2 { - r[values[i]] = values[i+1] - } - return r -} - -func SubstFrom(v interface{}, prefix ...string) map[string]string { - data, err := json.Marshal(v) - if err != nil { - panic(err) - } - var values map[string]string - err = json.Unmarshal(data, &values) - if err != nil { - panic(err) - } - if len(prefix) > 0 { - p := strings.Join(prefix, "") - n := map[string]string{} - for k, v := range values { - n[p+k] = v - } - values = n - } - return values -} - -func MergeSubst(subst ...map[string]string) map[string]string { - r := map[string]string{} - for _, s := range subst { - for k, v := range s { - r[k] = v - } - } - return r -} - -// StringEqualTrimmedWithContext compares two trimmed strings and provides the complete actual value -// as error context. -// If value mappings are given, the expected string is evaluated by envsubst, first. -// It is an error for actual to be nil. Use BeNil() instead. -func StringEqualTrimmedWithContext(expected string, subst ...Substitutions) types.GomegaMatcher { - var err error - expected, err = eval(expected, subst...) - if err != nil { - return &reportError{err} - } - return &StringEqualMatcher{ - Expected: expected, - Trim: true, - } -} - -// StringMatchTrimmedWithContext matches a trimmed string by a regular -// expression and provides the complete actual value as error context. -// If value mappings are given, the expected string is evaluated by envsubst, first. -// It is an error for actual to be nil. Use BeNil() instead. -func StringMatchTrimmedWithContext(expected string, subst ...Substitutions) types.GomegaMatcher { - var err error - expected, err = eval(expected, subst...) - if err != nil { - return &reportError{err} - } - return &StringEqualMatcher{ - Expected: expected, - Trim: true, - Regex: true, - } -} - -// StringEqualWithContext compares two strings and provides the complete actual value -// as error context. -// If value mappings are given, the expected string is evaluated by envsubst, first. -// It is an error for actual to be nil. Use BeNil() instead. -func StringEqualWithContext(expected string, subst ...Substitutions) types.GomegaMatcher { - var err error - expected, err = eval(expected, subst...) - if err != nil { - return &reportError{err} - } - return &StringEqualMatcher{ - Expected: expected, - } -} - -// StringMatchWithContext matches a string by a regular expression and provides -// the complete actual value as error context. -// If value mappings are given, the expected string is evaluated by envsubst, first. -// It is an error for actual to be nil. Use BeNil() instead. -func StringMatchWithContext(expected string, subst ...Substitutions) types.GomegaMatcher { - var err error - expected, err = eval(expected, subst...) - if err != nil { - return &reportError{err} - } - return &StringEqualMatcher{ - Expected: expected, - Regex: true, - } -} - -type StringEqualMatcher struct { - Expected string - Trim bool - Regex bool -} - -func (matcher *StringEqualMatcher) Match(actual interface{}) (success bool, err error) { - if actual == nil { - return false, fmt.Errorf("Refusing to compare to .") - } - - s, err := AsString(actual) - if err != nil { - return false, err - } - if matcher.Regex { - expected := matcher.Expected - if matcher.Trim { - expected = strings.TrimSpace(expected) - } - r, err := regexp.Compile(expected) - if err != nil { - return false, errors.Wrapf(err, "Invalid regular expression %q", matcher.Regex) - } - if matcher.Trim { - return r.MatchString(strings.TrimSpace(s)), nil - } - return r.MatchString(s), nil - } else { - if matcher.Trim { - return strings.TrimSpace(s) == strings.TrimSpace(matcher.Expected), nil - } - return s == matcher.Expected, nil - } -} - -func (matcher *StringEqualMatcher) FailureMessage(actual interface{}) (message string) { - actualString, err := AsString(actual) - if err == nil { - compare, expected := actualString, matcher.Expected - if matcher.Trim { - compare = strings.TrimSpace(actualString) - expected = strings.TrimSpace(matcher.Expected) - } - return fmt.Sprintf( - "Found\n%s\n%s", - actualString, - format.MessageWithDiff(compare, "to equal", expected), - ) - } - return format.Message(actual, "to equal", matcher.Expected) -} - -func (matcher *StringEqualMatcher) NegatedFailureMessage(actual interface{}) (message string) { - actualString, err := AsString(actual) - if err == nil { - return format.Message(actualString, "not to equal", matcher.Expected) - } - return format.Message(actual, "not to equal", matcher.Expected) -} - -func eval(expected string, subst ...Substitutions) (string, error) { - if len(subst) > 0 { - return envsubst.Eval(expected, stringmapping(subst...)) - } - return expected, nil -} - -func stringmapping(values ...Substitutions) func(variable string) string { - return func(variable string) string { - for _, m := range values { - if v, ok := m[variable]; ok { - return v - } - } - return "${" + variable + "}" - } -} - -type reportError struct { - err error -} - -func (r *reportError) Match(actual interface{}) (success bool, err error) { - return false, err -} - -func (r *reportError) FailureMessage(actual interface{}) (message string) { - return r.err.Error() -} - -func (r *reportError) NegatedFailureMessage(actual interface{}) (message string) { - return r.err.Error() -} diff --git a/api/utils/testutils/tcp.go b/api/utils/testutils/tcp.go deleted file mode 100644 index 05286141e0..0000000000 --- a/api/utils/testutils/tcp.go +++ /dev/null @@ -1,30 +0,0 @@ -package testutils - -import ( - "context" - "net" - "time" - - "github.com/mandelsoft/goutils/errors" -) - -func PingTCPServer(address string, dur time.Duration) error { - var conn net.Conn - var d net.Dialer - - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - end := time.Now().Add(dur) - err := errors.New("timed out waiting for server to start") - for time.Now().Before(end) { - conn, err = d.DialContext(ctx, "tcp", address) - if err != nil { - time.Sleep(time.Second) - continue - } - conn.Close() - break - } - return err -} diff --git a/api/utils/testutils/utils.go b/api/utils/testutils/utils.go deleted file mode 100644 index 5fab786702..0000000000 --- a/api/utils/testutils/utils.go +++ /dev/null @@ -1,157 +0,0 @@ -package testutils - -import ( - "encoding/json" - "fmt" - "io" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/onsi/gomega/types" - - "ocm.software/ocm/api/utils" - "ocm.software/ocm/api/utils/runtime" -) - -func Close(c io.Closer, msg ...interface{}) { - DeferWithOffset(1, c.Close, msg...) -} - -func Defer(f func() error, msg ...interface{}) { - DeferWithOffset(1, f, msg...) -} - -func DeferWithOffset(o int, f func() error, msg ...interface{}) { - err := f() - if err != nil { - switch len(msg) { - case 0: - ExpectWithOffset(1+o, err).To(Succeed()) - case 1: - Fail(fmt.Sprintf("%s: %s", msg[0], err), 1+o) - default: - Fail(fmt.Sprintf("%s: %s", fmt.Sprintf(msg[0].(string), msg[1:]...), err), 1+o) - } - } -} - -func NotNil[T any](o T, extra ...interface{}) T { - ExpectWithOffset(1, o, extra...).NotTo(BeNil()) - return o -} - -func Must[T any](o T, err error) T { - ExpectWithOffset(1, err).To(Succeed()) - return o -} - -func Must2[T any, V any](a T, b V, err error) (T, V) { - ExpectWithOffset(1, err).To(Succeed()) - return a, b -} - -func Must3[T, U, V any](a T, b U, c V, err error) (T, U, V) { - ExpectWithOffset(1, err).To(Succeed()) - return a, b, c -} - -type result[T any] struct { - res T - err error -} - -func (r result[T]) Must(offset ...int) T { - ExpectWithOffset(utils.Optional(offset...)+1, r.err).To(Succeed()) - return r.res -} - -func R[T any](o T, err error) result[T] { - return Calling(o, err) -} - -func Calling[T any](o T, err error) result[T] { - return result[T]{o, err} -} - -func MustWithOffset[T any](offset int, res result[T]) T { - ExpectWithOffset(offset+1, res.err).To(Succeed()) - return res.res -} - -func MustBeNonNil[T any](o T) T { - ExpectWithOffset(1, o).NotTo(BeNil()) - return o -} - -func MustBeSuccessful(actual ...interface{}) { - if actual[len(actual)-1] == nil { - return - } - err, ok := actual[len(actual)-1].(error) - if !ok { - Fail("no errors return", 1) - } - ExpectWithOffset(1, err).To(Succeed()) -} - -func MustBeSuccessfulWithOffset(offset int, err error) { - ExpectWithOffset(offset+1, err).To(Succeed()) -} - -func MustFailWithMessage(err error, msg string) { - ExpectWithOffset(1, err).To(HaveOccurred()) - ExpectWithOffset(1, err.Error()).To(Equal(msg)) -} - -func ErrorFrom(args ...interface{}) error { - e, ok := args[len(args)-1].(error) - if !ok { - Fail("no errors return", 1) - } - return e -} - -func ExpectError(values ...interface{}) types.Assertion { - return Expect(values[len(values)-1]) -} - -func AsString(actual interface{}) (string, error) { - s, ok := actual.(string) - if !ok { - b, ok := actual.([]byte) - if !ok { - return "", fmt.Errorf("Actual value is no string (or byte array), but a %T.", actual) - } - s = string(b) - } - return s, nil -} - -func AsStructure(actual interface{}, substs ...Substitutions) (interface{}, error) { - var err error - - s, ok := actual.(string) - if !ok { - b, ok := actual.([]byte) - if !ok { - b, err = json.Marshal(actual) - if err != nil { - return "", fmt.Errorf("Actual value (%T) is no string, byte array, or serializable object.", actual) - } - } - s = string(b) - } - if subst := MergeSubst(substs...); len(subst) != 0 { - s, err = eval(s, subst) - if err != nil { - return nil, err - } - } - var value interface{} - err = runtime.DefaultYAMLEncoding.Unmarshal([]byte(s), &value) - if err != nil { - return nil, err - } - return value, nil -} diff --git a/api/utils/testutils/yaml.go b/api/utils/testutils/yaml.go deleted file mode 100644 index f4ab69bd2e..0000000000 --- a/api/utils/testutils/yaml.go +++ /dev/null @@ -1,63 +0,0 @@ -package testutils - -import ( - "fmt" - "reflect" - "strings" - - "github.com/go-test/deep" - "github.com/onsi/gomega/format" - "github.com/onsi/gomega/types" - - "ocm.software/ocm/api/utils/runtime" -) - -// YAMLEqual compares two yaml structures. -// If value mappings are given, the expected string is evaluated by envsubst, first. -// It is an error for actual to be nil. Use BeNil() instead. -func YAMLEqual(expected interface{}, subst ...Substitutions) types.GomegaMatcher { - data, err := AsStructure(expected, subst...) - if err != nil { - return &reportError{err} - } - - return &YAMLEqualMatcher{ - Expected: data, - } -} - -type YAMLEqualMatcher struct { - Expected interface{} -} - -func (matcher *YAMLEqualMatcher) Match(actual interface{}) (success bool, err error) { - if actual == nil { - return false, fmt.Errorf("Refusing to compare to .") - } - - data, err := AsStructure(actual) - if err != nil { - return false, err - } - return reflect.DeepEqual(data, matcher.Expected), nil -} - -func (matcher *YAMLEqualMatcher) FailureMessage(actual interface{}) (message string) { - data, err := AsStructure(actual) - if err == nil { - diff := deep.Equal(data, matcher.Expected) - if len(diff) > 0 { - eff, _ := runtime.DefaultYAMLEncoding.Marshal(data) - return fmt.Sprintf( - "Found\n%s\n%s", - string(eff), - fmt.Sprintf("unexpected diff in YAML: \n %s\n", strings.Join(diff, "\n "))) - } - return "identical" - } - return format.Message(actual, "to equal", matcher.Expected, err.Error()) -} - -func (matcher *YAMLEqualMatcher) NegatedFailureMessage(actual interface{}) (message string) { - return format.Message(actual, "not to equal", matcher.Expected) -} diff --git a/cmds/helminstaller/app/execute.go b/cmds/helminstaller/app/execute.go index 0e9e4748b2..786515be1c 100644 --- a/cmds/helminstaller/app/execute.go +++ b/cmds/helminstaller/app/execute.go @@ -17,6 +17,7 @@ import ( resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" "ocm.software/ocm/api/ocm/extensions/download" utils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/ocm/tools/toi/support" "ocm.software/ocm/api/tech/helm/loader" "ocm.software/ocm/api/utils/compression" @@ -99,7 +100,7 @@ func (e *Execution) addSubCharts(finalize *Finalizer, subCharts map[string]v1.Re e.outf("Loading %d sub charts into %s...\n", len(subCharts), charts) for n, r := range subCharts { e.outf(" Loading sub chart %q from resource %s@%s\n", n, r, common.VersionedElementKey(e.ComponentVersion)) - acc, rcv := Must2f(R2(utils.ResolveResourceReference(e.ComponentVersion, r, nil)), "chart reference", r.String()) + acc, rcv := Must2f(R2(resourcerefs.ResolveResourceReference(e.ComponentVersion, r, nil)), "chart reference", r.String()) loop.Close(rcv) if acc.Meta().Type != resourcetypes.HELM_CHART { @@ -158,7 +159,7 @@ func (e *Execution) Execute(cfg *Config, values map[string]interface{}, kubeconf values = Merge(Must1(cfg.GetValues()), values) e.outf("Loading helm chart from resource %s@%s\n", cfg.Chart, common.VersionedElementKey(e.ComponentVersion)) - acc, rcv := Must2f(R2(utils.ResolveResourceReference(e.ComponentVersion, cfg.Chart, nil)), "chart reference", cfg.Chart.String()) + acc, rcv := Must2f(R2(resourcerefs.ResolveResourceReference(e.ComponentVersion, cfg.Chart, nil)), "chart reference", cfg.Chart.String()) finalize.Close(rcv) if acc.Meta().Type != resourcetypes.HELM_CHART { @@ -189,7 +190,7 @@ func (e *Execution) Execute(cfg *Config, values map[string]interface{}, kubeconf e.outf("Localizing helm chart...\n") e.Logger.Debug("Localizing helm chart") for i, v := range cfg.ImageMapping { - acc, rcv := Must2f(R2(utils.ResolveResourceReference(e.ComponentVersion, v.ResourceReference, nil)), "mapping", fmt.Sprintf("%d (%s)", i+1, &v.ResourceReference)) + acc, rcv := Must2f(R2(resourcerefs.ResolveResourceReference(e.ComponentVersion, v.ResourceReference, nil)), "mapping", fmt.Sprintf("%d (%s)", i+1, &v.ResourceReference)) rcv.Close() ref := Must1f(R1(utils.GetOCIArtifactRef(e.Context, acc)), "mapping %d: cannot resolve resource %s to an OCI Reference", i+1, v) ix := strings.Index(ref, ":") diff --git a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go index 160b2ee849..3cbfafc31b 100644 --- a/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go +++ b/cmds/ocm/commands/ocmcmds/common/cmds/signing/cmd.go @@ -10,6 +10,7 @@ import ( "ocm.software/ocm/api/ocm" "ocm.software/ocm/api/ocm/compdesc" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/ocm/tools/signing" common "ocm.software/ocm/api/utils/misc" ocmcommon "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common" @@ -130,7 +131,7 @@ func NewAction(desc []string, ctx ocm.Context, p common.Printer, sopts *signing. func (a *action) Digest(o *comphdlr.Object) (*metav1.DigestSpec, *compdesc.ComponentDescriptor, error) { sopts := *a.sopts - sopts.Resolver = ocm.NewCompoundResolver(o.Repository, a.sopts.Resolver) + sopts.Resolver = resolvers.NewCompoundResolver(o.Repository, a.sopts.Resolver) d, err := signing.Apply(a.printer, &a.state, o.ComponentVersion, &sopts) var cd *compdesc.ComponentDescriptor nv := common.VersionedElementKey(o.ComponentVersion) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go index 6efae4f9dd..40085d4af5 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/options/standard.go @@ -24,6 +24,8 @@ var ( RegistryOption = options.NPMRegistryOption PackageOption = options.NPMPackageOption PackageVersionOption = options.NPMVersionOption + + IdentityPathOption = options.IdentityPathOption ) // string options. @@ -68,4 +70,7 @@ var ( FormattedJSONOption = flagsets.NewYAMLOptionType("inputFormattedJson", "JSON formatted text") ) -var ValuesOption = flagsets.NewValueMapYAMLOptionType("inputValues", "YAML based generic values for inputs") +var ( + ValuesOption = flagsets.NewValueMapYAMLOptionType("inputValues", "YAML based generic values for inputs") + ComponentOption = flagsets.NewStringOptionType("inputComponent", "component name") +) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go index 2368661be0..a692cd100f 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/init.go @@ -9,6 +9,7 @@ import ( _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/helm" _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/maven" _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ociartifact" + _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm" _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/spiff" _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/utf8" _ "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/wget" diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go index 4ffb28085c..1e443f514e 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/maven/spec.go @@ -63,7 +63,7 @@ func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath s } if s.Version == "" { pathField := fldPath.Child("version") - allErrs = append(allErrs, field.Invalid(pathField, s.GroupId, "no group id")) + allErrs = append(allErrs, field.Invalid(pathField, s.GroupId, "no version")) } return allErrs diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/cli.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/cli.go new file mode 100644 index 0000000000..1ad97c5fc9 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/cli.go @@ -0,0 +1,25 @@ +package ocm + +import ( + acc "ocm.software/ocm/api/ocm/extensions/accessmethods/options" + "ocm.software/ocm/api/utils/cobrautils/flagsets" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" +) + +func ConfigHandler() flagsets.ConfigOptionTypeSetHandler { + return flagsets.NewConfigOptionTypeSetHandler( + TYPE, AddConfig, + options.RepositoryOption, + options.ComponentOption, + options.VersionOption, + options.IdentityPathOption, + ) +} + +func AddConfig(opts flagsets.ConfigOptions, config flagsets.Config) error { + flagsets.AddFieldByMappedOptionP(opts, options.RepositoryOption, config, acc.MapRepository, "ocmRepository") + flagsets.AddFieldByOptionP(opts, options.ComponentOption, config, "component") + flagsets.AddFieldByOptionP(opts, options.VersionOption, config, "version") + flagsets.AddFieldByMappedOptionP(opts, options.IdentityPathOption, config, acc.MapResourceRef, "resourceRef") + return nil +} diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/input_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/input_test.go new file mode 100644 index 0000000000..10bbca8a9c --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/input_test.go @@ -0,0 +1,93 @@ +package ocm_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/testutils" + . "ocm.software/ocm/cmds/ocm/testhelper" + + "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/ocm/extensions/accessmethods/localblob" + resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" + "ocm.software/ocm/api/ocm/extensions/repositories/comparch" + "ocm.software/ocm/api/ocm/extensions/repositories/ocireg" + "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/utils/accessio" + "ocm.software/ocm/api/utils/accessobj" + "ocm.software/ocm/api/utils/mime" + + v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/options" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm" +) + +const ( + COMP = "acme.org/test" + VERS = "v1" + ARCH = "/ca" + + REPO = "/ctf" + REFCOMP = "acme.org/ref" + REFRSC = "res" +) + +var _ = Describe("Input Type", func() { + Context("spec", func() { + var env *InputTest + + BeforeEach(func() { + env = NewInputTest(ocm.TYPE) + }) + + It("simple spec", func() { + env.Set(options.RepositoryOption, "ghcr.io/open-component-model") + env.Set(options.ComponentOption, COMP) + env.Set(options.VersionOption, VERS) + env.Set(options.IdentityPathOption, "name=test") + env.Check(&ocm.Spec{ + InputSpecBase: inputs.InputSpecBase{}, + OCMRepository: Must(cpi.ToGenericRepositorySpec(ocireg.NewRepositorySpec("ghcr.io/open-component-model"))), + Component: COMP, + Version: VERS, + ResourceRef: v1.NewResourceRef(v1.NewIdentity("test")), + }) + }) + }) + + Context("compose", func() { + var env *TestEnv + + BeforeEach(func() { + env = NewTestEnv(TestData()) + + env.OCMCommonTransport(REPO, accessio.FormatDirectory, func() { + env.ComponentVersion(REFCOMP, VERS, func() { + env.Resource(REFRSC, VERS, resourcetypes.PLAIN_TEXT, v1.LocalRelation, func() { + env.BlobStringData(mime.MIME_TEXT, "test data") + }) + }) + }) + + Expect(env.Execute("create", "ca", "-ft", "directory", COMP, VERS, "--provider", "acme.org", "--file", ARCH)).To(Succeed()) + }) + + AfterEach(func() { + env.Cleanup() + }) + + It("adds ocm resource", func() { + Expect(env.Execute("add", "resources", "--file", ARCH, "/testdata/resources1.yaml")).To(Succeed()) + + ca := Must(comparch.Open(env, accessobj.ACC_READONLY, ARCH, 0, env)) + defer Close(ca) + + r := Must(ca.GetResourceByIndex(0)) + Expect(Must(r.Access()).GetKind()).To(Equal(localblob.Type)) + + data := Must(ocmutils.GetResourceData(r)) + Expect(string(data)).To(Equal("test data")) + }) + }) +}) diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/spec.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/spec.go new file mode 100644 index 0000000000..1876bae5bc --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/spec.go @@ -0,0 +1,74 @@ +package ocm + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + + "ocm.software/ocm/api/credentials" + metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" + cpi2 "ocm.software/ocm/api/ocm/cpi" + "ocm.software/ocm/api/utils/blobaccess" + "ocm.software/ocm/api/utils/blobaccess/ocm" + "ocm.software/ocm/api/utils/runtime" + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" +) + +type Spec struct { + inputs.InputSpecBase `json:",inline"` + + // OCMRepository is the URL of the OCM repository to load the chart from. + OCMRepository *cpi2.GenericRepositorySpec `json:"ocmRepository,omitempty"` + + // Component if the name of the root component used to lookup the resource. + Component string `json:"component,omitempty"` + + // Version is the version og the root component. + Version string `json:"version,omitempty,"` + + ResourceRef metav1.ResourceReference `json:"resourceRef"` +} + +var _ inputs.InputSpec = (*Spec)(nil) + +func New(comp, vers string, repo cpi2.RepositorySpec, id metav1.Identity, path ...metav1.Identity) (inputs.InputSpec, error) { + spec, err := cpi2.ToGenericRepositorySpec(repo) + if err != nil { + return nil, err + } + return &Spec{ + InputSpecBase: inputs.InputSpecBase{ + ObjectVersionedType: runtime.ObjectVersionedType{ + Type: TYPE, + }, + }, + OCMRepository: spec, + Component: comp, + Version: vers, + ResourceRef: metav1.NewNestedResourceRef(id, path), + }, nil +} + +func (s *Spec) Validate(fldPath *field.Path, ctx inputs.Context, inputFilePath string) field.ErrorList { + var allErrs field.ErrorList + + err := s.OCMRepository.Validate(ctx.OCMContext(), nil, credentials.StringUsageContext(s.Component)) + if err != nil { + data, _ := s.OCMRepository.MarshalJSON() + pathField := fldPath.Child("ocmRepository") + allErrs = append(allErrs, field.Invalid(pathField, string(data), err.Error())) + } + if s.Component == "" { + pathField := fldPath.Child("component") + allErrs = append(allErrs, field.Invalid(pathField, s.Component, "no component name")) + } + if s.Version == "" { + pathField := fldPath.Child("version") + allErrs = append(allErrs, field.Invalid(pathField, s.Version, "no component version")) + } + + return allErrs +} + +func (s *Spec) GetBlob(ctx inputs.Context, info inputs.InputResourceInfo) (blobaccess.BlobAccess, string, error) { + b, err := ocm.BlobAccess(ocm.ByRepositorySpecAndName(ctx.OCMContext(), s.OCMRepository, s.Component, s.Version), ocm.ByResourceRef(s.ResourceRef)) + return b, "", err +} diff --git a/api/utils/testutils/suite_test.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/suite_test.go similarity index 74% rename from api/utils/testutils/suite_test.go rename to cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/suite_test.go index c47f9dd5f9..450f29ace8 100644 --- a/api/utils/testutils/suite_test.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/suite_test.go @@ -1,4 +1,4 @@ -package testutils_test +package ocm_test import ( "testing" @@ -9,5 +9,5 @@ import ( func TestConfig(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Testutils") + RunSpecs(t, "Input Type ocm") } diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/testdata/resources1.yaml b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/testdata/resources1.yaml new file mode 100644 index 0000000000..8518822909 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/testdata/resources1.yaml @@ -0,0 +1,12 @@ +name: myblob +type: plainText +input: + type: ocm + ocmRepository: + type: CommonTransportFormat + filePath: /ctf + component: acme.org/ref + version: v1 + resourceRef: + resource: + name: res \ No newline at end of file diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/type.go b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/type.go new file mode 100644 index 0000000000..fb50a4a826 --- /dev/null +++ b/cmds/ocm/commands/ocmcmds/common/inputs/types/ocm/type.go @@ -0,0 +1,33 @@ +package ocm + +import ( + "ocm.software/ocm/cmds/ocm/commands/ocmcmds/common/inputs" +) + +const TYPE = "ocm" + +func init() { + inputs.DefaultInputTypeScheme.Register(inputs.NewInputType(TYPE, &Spec{}, usage, ConfigHandler())) +} + +const usage = ` +This input type allows to get a resource artifact from an OCM repository. + +This blob type specification supports the following fields: +- **ocmRepository** *repository specification* + + This REQUIRED property describes the OCM repository specification + +- **component** *string* + + This REQUIRED property describes the component na,e + +- **version** *string* + + This REQUIRED property describes the version of a maven artifact. + +- **resourceRef** *relative resource reference* + + This REQUIRED property describes the resource reference for the desired + resource relative to the given component version . +` diff --git a/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go b/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go index 02afe53871..8b3d00006a 100644 --- a/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go +++ b/cmds/ocm/commands/ocmcmds/common/options/lookupoption/option.go @@ -5,6 +5,7 @@ import ( clictx "ocm.software/ocm/api/cli" "ocm.software/ocm/api/ocm" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/ocm/tools/transfer/transferhandler" "ocm.software/ocm/api/ocm/tools/transfer/transferhandler/standard" "ocm.software/ocm/cmds/ocm/common/options" @@ -49,15 +50,15 @@ func (o *Option) CompleteWithSession(octx clictx.OCM, session ocm.Session) error func (o *Option) getResolver(ctx clictx.OCM, session ocm.Session) (ocm.ComponentVersionResolver, error) { if len(o.RepoSpecs) != 0 { - resolvers := []ocm.ComponentVersionResolver{} + resolver := []ocm.ComponentVersionResolver{} for _, s := range o.RepoSpecs { r, _, err := session.DetermineRepository(ctx.Context(), s) if err != nil { return nil, err } - resolvers = append(resolvers, r) + resolver = append(resolver, r) } - return ocm.NewCompoundResolver(append(resolvers, ctx.Context().GetResolver())...), nil + return resolvers.NewCompoundResolver(append(resolver, ctx.Context().GetResolver())...), nil } return ctx.Context().GetResolver(), nil } diff --git a/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go b/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go index 14e1dd4091..2633d17229 100644 --- a/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go +++ b/cmds/ocm/commands/ocmcmds/components/verify/cmd_test.go @@ -22,6 +22,7 @@ import ( resourcetypes "ocm.software/ocm/api/ocm/extensions/artifacttypes" "ocm.software/ocm/api/ocm/extensions/attrs/signingattr" "ocm.software/ocm/api/ocm/extensions/repositories/ctf" + "ocm.software/ocm/api/ocm/resolvers" "ocm.software/ocm/api/tech/signing/handlers/rsa" "ocm.software/ocm/api/utils/accessio" "ocm.software/ocm/api/utils/accessobj" @@ -123,7 +124,7 @@ var _ = Describe("access method", func() { src, err := ctf.Open(env.OCMContext(), accessobj.ACC_WRITABLE, ARCH, 0, env) Expect(err).To(Succeed()) archcloser := session.AddCloser(src) - resolver := ocm.NewCompoundResolver(src) + resolver := resolvers.NewCompoundResolver(src) cv, err := resolver.LookupComponentVersion(COMPONENTB, VERSION) Expect(err).To(Succeed()) diff --git a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go index 8f2082d02e..17dd8d042a 100644 --- a/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go +++ b/cmds/ocm/commands/toicmds/config/bootstrap/cmd.go @@ -16,7 +16,7 @@ import ( metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" "ocm.software/ocm/api/ocm/extensions/download" - utils2 "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/ocm/tools/toi" "ocm.software/ocm/api/ocm/tools/toi/install" utils3 "ocm.software/ocm/api/utils" @@ -161,7 +161,7 @@ func (a *action) Out() error { rid := metav1.NewResourceRef(a.cmd.Id) resolver := lookupoption.From(a.cmd) - ires, eff, err := utils2.MatchResourceReference(cv, toi.TypeTOIPackage, rid, resolver) + ires, eff, err := resourcerefs.MatchResourceReference(cv, toi.TypeTOIPackage, rid, resolver) if err != nil { return errors.Wrapf(err, "package resource in %s", nv) } @@ -243,7 +243,7 @@ func (a *action) handle(kind, path string, cv ocm.ComponentVersionAccess, spec * } func (a *action) download(kind, path string, cv ocm.ComponentVersionAccess, spec *metav1.ResourceReference, resolver ocm.ComponentVersionResolver) error { - res, _, err := utils2.MatchResourceReference(cv, toi.TypeYAML, *spec, resolver) + res, _, err := resourcerefs.MatchResourceReference(cv, toi.TypeYAML, *spec, resolver) if err != nil { return errors.Wrapf(err, "%s resource", kind) } diff --git a/cmds/ocm/commands/toicmds/package/describe/cmd.go b/cmds/ocm/commands/toicmds/package/describe/cmd.go index 98a3e853b7..73ceb1f60b 100644 --- a/cmds/ocm/commands/toicmds/package/describe/cmd.go +++ b/cmds/ocm/commands/toicmds/package/describe/cmd.go @@ -12,7 +12,7 @@ import ( "ocm.software/ocm/api/ocm" metav1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/extensions/attrs/ociuploadattr" - utils2 "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/ocm/tools/toi" "ocm.software/ocm/api/ocm/tools/toi/install" utils3 "ocm.software/ocm/api/utils" @@ -164,7 +164,7 @@ func (a *action) describe(cv ocm.ComponentVersionAccess) error { rid := metav1.NewResourceRef(a.cmd.Id) resolver := lookupoption.From(a.cmd) - ires, eff, err := utils2.MatchResourceReference(cv, toi.TypeTOIPackage, rid, resolver) + ires, eff, err := resourcerefs.MatchResourceReference(cv, toi.TypeTOIPackage, rid, resolver) if err != nil { return errors.Wrapf(err, "package resource in %s", nv) } diff --git a/docs/pluginreference/plugin_accessmethod_compose.md b/docs/pluginreference/plugin_accessmethod_compose.md index bbe1bf2aa5..75b5291f46 100644 --- a/docs/pluginreference/plugin_accessmethod_compose.md +++ b/docs/pluginreference/plugin_accessmethod_compose.md @@ -34,6 +34,7 @@ by the plugin name. The following predefined option types can be used: + - accessComponent: [*string*] component for access specification - accessHostname: [*string*] hostname used for access - accessPackage: [*string*] package or object name - accessRegistry: [*string*] registry base URL @@ -51,6 +52,8 @@ The following predefined option types can be used: - groupId: [*string*] maven group id - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts + - identityPath: [*[]identity*] identity path for specification + - idpath: [*[]string*] identity path (attr=value{,attr=value} - mediaType: [*string*] media type for artifact blob representation - noredirect: [*bool*] http redirect behavior - package: [*string*] npm package name @@ -67,6 +70,7 @@ The following predefined value types are supported: - YAML: JSON or YAML document string - []byte: byte value + - []identity: identity path - []string: list of string values - bool: boolean flag - int: integer value diff --git a/docs/pluginreference/plugin_descriptor.md b/docs/pluginreference/plugin_descriptor.md index 2d4cb97ba3..ac7ffdcb5b 100644 --- a/docs/pluginreference/plugin_descriptor.md +++ b/docs/pluginreference/plugin_descriptor.md @@ -119,6 +119,7 @@ It uses the following fields: The following predefined option types can be used: + - accessComponent: [*string*] component for access specification - accessHostname: [*string*] hostname used for access - accessPackage: [*string*] package or object name - accessRegistry: [*string*] registry base URL @@ -136,6 +137,8 @@ The following predefined option types can be used: - groupId: [*string*] maven group id - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts + - identityPath: [*[]identity*] identity path for specification + - idpath: [*[]string*] identity path (attr=value{,attr=value} - mediaType: [*string*] media type for artifact blob representation - noredirect: [*bool*] http redirect behavior - package: [*string*] npm package name @@ -152,6 +155,7 @@ The following predefined value types are supported: - YAML: JSON or YAML document string - []byte: byte value + - []identity: identity path - []string: list of string values - bool: boolean flag - int: integer value diff --git a/docs/pluginreference/plugin_valueset_compose.md b/docs/pluginreference/plugin_valueset_compose.md index af7c03f30f..16e93871d9 100644 --- a/docs/pluginreference/plugin_valueset_compose.md +++ b/docs/pluginreference/plugin_valueset_compose.md @@ -34,6 +34,7 @@ by the plugin name. The following predefined option types can be used: + - accessComponent: [*string*] component for access specification - accessHostname: [*string*] hostname used for access - accessPackage: [*string*] package or object name - accessRegistry: [*string*] registry base URL @@ -51,6 +52,8 @@ The following predefined option types can be used: - groupId: [*string*] maven group id - header: [*string:string,string*] http headers - hint: [*string*] (repository) hint for local artifacts + - identityPath: [*[]identity*] identity path for specification + - idpath: [*[]string*] identity path (attr=value{,attr=value} - mediaType: [*string*] media type for artifact blob representation - noredirect: [*bool*] http redirect behavior - package: [*string*] npm package name @@ -67,6 +70,7 @@ The following predefined value types are supported: - YAML: JSON or YAML document string - []byte: byte value + - []identity: identity path - []string: list of string values - bool: boolean flag - int: integer value diff --git a/docs/reference/ocm_add_componentversions.md b/docs/reference/ocm_add_componentversions.md index 56595a49f8..e5078c492e 100644 --- a/docs/reference/ocm_add_componentversions.md +++ b/docs/reference/ocm_add_componentversions.md @@ -184,27 +184,27 @@ The uploader name may be a path expression with the following possibilities: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the maven repository. + 'url': the URL of the npm repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the npm repository. + 'url': the URL of the maven repository. diff --git a/docs/reference/ocm_add_resource-configuration.md b/docs/reference/ocm_add_resource-configuration.md index f318b0885d..27d8aa09b4 100644 --- a/docs/reference/ocm_add_resource-configuration.md +++ b/docs/reference/ocm_add_resource-configuration.md @@ -24,6 +24,7 @@ resource-configuration, resourceconfig, rsccfg, rcfg ```text --access YAML blob access specification (YAML) + --accessComponent string component for access specification --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL @@ -41,6 +42,7 @@ resource-configuration, resourceconfig, rsccfg, rcfg --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --mediaType string media type for artifact blob representation --noredirect http redirect behavior --reference string reference name @@ -54,6 +56,7 @@ resource-configuration, resourceconfig, rsccfg, rcfg #### Input Specification Options ```text + --accessRepository string repository URL --artifactId string maven artifact id --body string body of a http request --classifier string maven classifier @@ -61,7 +64,9 @@ resource-configuration, resourceconfig, rsccfg, rcfg --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --input YAML blob input specification (YAML) + --inputComponent string component name --inputCompress compress option for input --inputData !bytesBase64 data (string, !!string or ! --inputExcludes stringArray excludes (path) for inputs @@ -438,6 +443,30 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputPath, --inputPlatforms, --mediaType +- Input type ocm + + This input type allows to get a resource artifact from an OCM repository. + + This blob type specification supports the following fields: + - **ocmRepository** *repository specification* + + This REQUIRED property describes the OCM repository specification + + - **component** *string* + + This REQUIRED property describes the component na,e + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **resourceRef** *relative resource reference* + + This REQUIRED property describes the resource reference for the desired + resource relative to the given component version . + + Options used to configure fields: --accessRepository, --identityPath, --inputComponent, --inputVersion + - Input type spiff The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the resources file. @@ -830,6 +859,40 @@ shown below. Options used to configure fields: --digest, --mediaType, --reference, --size +- Access type ocm + + This method implements the access of any resource artifact stored in an OCM + repository. Only repository types supporting remote access should be used. + + The following versions are supported: + - Version v1 + + The type specific specification fields are: + + - **ocmRepository** *json* + + The repository spec for the OCM repository + + - **component** *string* + + *(Optional)* The name of the component. The default is the + own component. + + - **version** *string* + + *(Optional)* The version of the component. The default is the + own component version. + + - **resourceRef** *relative resource ref* + + The resource reference of the denoted resource relative to the + given component version. + + It uses the consumer identity and credentials for the intermediate repositories + and the final resource access. + + Options used to configure fields: --accessComponent, --accessRepository, --accessVersion, --identityPath + - Access type s3 This method implements the access of a blob stored in an S3 bucket. diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md index 058d9dda34..f26dee4cc4 100644 --- a/docs/reference/ocm_add_resources.md +++ b/docs/reference/ocm_add_resources.md @@ -31,6 +31,7 @@ resources, resource, res, r ```text --access YAML blob access specification (YAML) + --accessComponent string component for access specification --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL @@ -48,6 +49,7 @@ resources, resource, res, r --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --mediaType string media type for artifact blob representation --noredirect http redirect behavior --reference string reference name @@ -61,6 +63,7 @@ resources, resource, res, r #### Input Specification Options ```text + --accessRepository string repository URL --artifactId string maven artifact id --body string body of a http request --classifier string maven classifier @@ -68,7 +71,9 @@ resources, resource, res, r --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --input YAML blob input specification (YAML) + --inputComponent string component name --inputCompress compress option for input --inputData !bytesBase64 data (string, !!string or ! --inputExcludes stringArray excludes (path) for inputs @@ -449,6 +454,30 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputPath, --inputPlatforms, --mediaType +- Input type ocm + + This input type allows to get a resource artifact from an OCM repository. + + This blob type specification supports the following fields: + - **ocmRepository** *repository specification* + + This REQUIRED property describes the OCM repository specification + + - **component** *string* + + This REQUIRED property describes the component na,e + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **resourceRef** *relative resource reference* + + This REQUIRED property describes the resource reference for the desired + resource relative to the given component version . + + Options used to configure fields: --accessRepository, --identityPath, --inputComponent, --inputVersion + - Input type spiff The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the resources file. @@ -841,6 +870,40 @@ shown below. Options used to configure fields: --digest, --mediaType, --reference, --size +- Access type ocm + + This method implements the access of any resource artifact stored in an OCM + repository. Only repository types supporting remote access should be used. + + The following versions are supported: + - Version v1 + + The type specific specification fields are: + + - **ocmRepository** *json* + + The repository spec for the OCM repository + + - **component** *string* + + *(Optional)* The name of the component. The default is the + own component. + + - **version** *string* + + *(Optional)* The version of the component. The default is the + own component version. + + - **resourceRef** *relative resource ref* + + The resource reference of the denoted resource relative to the + given component version. + + It uses the consumer identity and credentials for the intermediate repositories + and the final resource access. + + Options used to configure fields: --accessComponent, --accessRepository, --accessVersion, --identityPath + - Access type s3 This method implements the access of a blob stored in an S3 bucket. diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index b190c03bc2..108dd21a38 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -24,6 +24,7 @@ source-configuration, sourceconfig, srccfg, scfg ```text --access YAML blob access specification (YAML) + --accessComponent string component for access specification --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL @@ -41,6 +42,7 @@ source-configuration, sourceconfig, srccfg, scfg --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --mediaType string media type for artifact blob representation --noredirect http redirect behavior --reference string reference name @@ -54,6 +56,7 @@ source-configuration, sourceconfig, srccfg, scfg #### Input Specification Options ```text + --accessRepository string repository URL --artifactId string maven artifact id --body string body of a http request --classifier string maven classifier @@ -61,7 +64,9 @@ source-configuration, sourceconfig, srccfg, scfg --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --input YAML blob input specification (YAML) + --inputComponent string component name --inputCompress compress option for input --inputData !bytesBase64 data (string, !!string or ! --inputExcludes stringArray excludes (path) for inputs @@ -438,6 +443,30 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputPath, --inputPlatforms, --mediaType +- Input type ocm + + This input type allows to get a resource artifact from an OCM repository. + + This blob type specification supports the following fields: + - **ocmRepository** *repository specification* + + This REQUIRED property describes the OCM repository specification + + - **component** *string* + + This REQUIRED property describes the component na,e + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **resourceRef** *relative resource reference* + + This REQUIRED property describes the resource reference for the desired + resource relative to the given component version . + + Options used to configure fields: --accessRepository, --identityPath, --inputComponent, --inputVersion + - Input type spiff The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the resources file. @@ -830,6 +859,40 @@ shown below. Options used to configure fields: --digest, --mediaType, --reference, --size +- Access type ocm + + This method implements the access of any resource artifact stored in an OCM + repository. Only repository types supporting remote access should be used. + + The following versions are supported: + - Version v1 + + The type specific specification fields are: + + - **ocmRepository** *json* + + The repository spec for the OCM repository + + - **component** *string* + + *(Optional)* The name of the component. The default is the + own component. + + - **version** *string* + + *(Optional)* The version of the component. The default is the + own component version. + + - **resourceRef** *relative resource ref* + + The resource reference of the denoted resource relative to the + given component version. + + It uses the consumer identity and credentials for the intermediate repositories + and the final resource access. + + Options used to configure fields: --accessComponent, --accessRepository, --accessVersion, --identityPath + - Access type s3 This method implements the access of a blob stored in an S3 bucket. diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md index 4e1375597c..276deb2467 100644 --- a/docs/reference/ocm_add_sources.md +++ b/docs/reference/ocm_add_sources.md @@ -30,6 +30,7 @@ sources, source, src, s ```text --access YAML blob access specification (YAML) + --accessComponent string component for access specification --accessHostname string hostname used for access --accessPackage string package or object name --accessRegistry string registry base URL @@ -47,6 +48,7 @@ sources, source, src, s --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --mediaType string media type for artifact blob representation --noredirect http redirect behavior --reference string reference name @@ -60,6 +62,7 @@ sources, source, src, s #### Input Specification Options ```text + --accessRepository string repository URL --artifactId string maven artifact id --body string body of a http request --classifier string maven classifier @@ -67,7 +70,9 @@ sources, source, src, s --groupId string maven group id --header :,,... http headers (default {}) --hint string (repository) hint for local artifacts + --identityPath {=} identity path for specification --input YAML blob input specification (YAML) + --inputComponent string component name --inputCompress compress option for input --inputData !bytesBase64 data (string, !!string or ! --inputExcludes stringArray excludes (path) for inputs @@ -447,6 +452,30 @@ with the field type in the input field: Options used to configure fields: --hint, --inputCompress, --inputPath, --inputPlatforms, --mediaType +- Input type ocm + + This input type allows to get a resource artifact from an OCM repository. + + This blob type specification supports the following fields: + - **ocmRepository** *repository specification* + + This REQUIRED property describes the OCM repository specification + + - **component** *string* + + This REQUIRED property describes the component na,e + + - **version** *string* + + This REQUIRED property describes the version of a maven artifact. + + - **resourceRef** *relative resource reference* + + This REQUIRED property describes the resource reference for the desired + resource relative to the given component version . + + Options used to configure fields: --accessRepository, --identityPath, --inputComponent, --inputVersion + - Input type spiff The path must denote a [spiff](https://github.com/mandelsoft/spiff) template relative the resources file. @@ -839,6 +868,40 @@ shown below. Options used to configure fields: --digest, --mediaType, --reference, --size +- Access type ocm + + This method implements the access of any resource artifact stored in an OCM + repository. Only repository types supporting remote access should be used. + + The following versions are supported: + - Version v1 + + The type specific specification fields are: + + - **ocmRepository** *json* + + The repository spec for the OCM repository + + - **component** *string* + + *(Optional)* The name of the component. The default is the + own component. + + - **version** *string* + + *(Optional)* The version of the component. The default is the + own component version. + + - **resourceRef** *relative resource ref* + + The resource reference of the denoted resource relative to the + given component version. + + It uses the consumer identity and credentials for the intermediate repositories + and the final resource access. + + Options used to configure fields: --accessComponent, --accessRepository, --accessVersion, --identityPath + - Access type s3 This method implements the access of a blob stored in an S3 bucket. diff --git a/docs/reference/ocm_ocm-accessmethods.md b/docs/reference/ocm_ocm-accessmethods.md index b4890122e3..6f05c4f706 100644 --- a/docs/reference/ocm_ocm-accessmethods.md +++ b/docs/reference/ocm_ocm-accessmethods.md @@ -289,6 +289,40 @@ shown below. Options used to configure fields: --digest, --mediaType, --reference, --size +- Access type ocm + + This method implements the access of any resource artifact stored in an OCM + repository. Only repository types supporting remote access should be used. + + The following versions are supported: + - Version v1 + + The type specific specification fields are: + + - **ocmRepository** *json* + + The repository spec for the OCM repository + + - **component** *string* + + *(Optional)* The name of the component. The default is the + own component. + + - **version** *string* + + *(Optional)* The version of the component. The default is the + own component version. + + - **resourceRef** *relative resource ref* + + The resource reference of the denoted resource relative to the + given component version. + + It uses the consumer identity and credentials for the intermediate repositories + and the final resource access. + + Options used to configure fields: --accessComponent, --accessRepository, --accessVersion, --identityPath + - Access type s3 This method implements the access of a blob stored in an S3 bucket. diff --git a/docs/reference/ocm_ocm-uploadhandlers.md b/docs/reference/ocm_ocm-uploadhandlers.md index a0f9cdf854..1205c6333c 100644 --- a/docs/reference/ocm_ocm-uploadhandlers.md +++ b/docs/reference/ocm_ocm-uploadhandlers.md @@ -59,27 +59,27 @@ The following handler names are possible: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the maven repository. + 'url': the URL of the npm repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the npm repository. + 'url': the URL of the maven repository. diff --git a/docs/reference/ocm_transfer_commontransportarchive.md b/docs/reference/ocm_transfer_commontransportarchive.md index 8134517587..0c7f28b12f 100644 --- a/docs/reference/ocm_transfer_commontransportarchive.md +++ b/docs/reference/ocm_transfer_commontransportarchive.md @@ -134,27 +134,27 @@ The uploader name may be a path expression with the following possibilities: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the maven repository. + 'url': the URL of the npm repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the npm repository. + 'url': the URL of the maven repository. diff --git a/docs/reference/ocm_transfer_componentversions.md b/docs/reference/ocm_transfer_componentversions.md index fe4741b3b5..ca577a8c10 100644 --- a/docs/reference/ocm_transfer_componentversions.md +++ b/docs/reference/ocm_transfer_componentversions.md @@ -191,27 +191,27 @@ The uploader name may be a path expression with the following possibilities: Alternatively, a single string value can be given representing an OCI repository reference. - - ocm/mavenPackage: uploading maven artifacts + - ocm/npmPackage: uploading npm artifacts - The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) - as artifact archive according to the maven artifact spec. + The ocm/npmPackage uploader is able to upload npm artifacts + as artifact archive according to the npm package spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the maven repository. + 'url': the URL of the npm repository. - plugin: [downloaders provided by plugins] sub namespace of the form <plugin name>/<handler> - - ocm/npmPackage: uploading npm artifacts + - ocm/mavenPackage: uploading maven artifacts - The ocm/npmPackage uploader is able to upload npm artifacts - as artifact archive according to the npm package spec. + The ocm/mavenPackage uploader is able to upload maven artifacts (whole GAV only!) + as artifact archive according to the maven artifact spec. If registered the default mime type is: application/x-tgz It accepts a plain string for the URL or a config with the following field: - 'url': the URL of the npm repository. + 'url': the URL of the maven repository. diff --git a/examples/lib/comparison-scenario/09-localize.go b/examples/lib/comparison-scenario/09-localize.go index 77337a4f6c..4307a5df71 100644 --- a/examples/lib/comparison-scenario/09-localize.go +++ b/examples/lib/comparison-scenario/09-localize.go @@ -14,8 +14,9 @@ import ( "ocm.software/ocm/api/ocm/compdesc" v1 "ocm.software/ocm/api/ocm/compdesc/meta/v1" "ocm.software/ocm/api/ocm/extensions/download" - ocmutils "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/ocm/ocmutils" "ocm.software/ocm/api/ocm/ocmutils/localize" + "ocm.software/ocm/api/ocm/resourcerefs" "ocm.software/ocm/api/tech/helm/loader" "ocm.software/ocm/api/utils" "ocm.software/ocm/api/utils/runtime" @@ -82,7 +83,7 @@ func EvaluateDeployDescriptor(cv ocm.ComponentVersionAccess, res ocm.ResourceAcc } // fourth, get the helm chart - cres, ccv, err := ocmutils.ResolveResourceReference(cv, desc.ChartResource, resolver) + cres, ccv, err := resourcerefs.ResolveResourceReference(cv, desc.ChartResource, resolver) if err != nil { return evalErr(err, "cannot resolve chart resource %s", desc.ChartResource) } diff --git a/go.mod b/go.mod index 2b16a99ed9..91d1eb8eac 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,6 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 - golang.org/x/mod v0.20.0 golang.org/x/net v0.28.0 golang.org/x/oauth2 v0.22.0 golang.org/x/text v0.17.0 @@ -340,6 +339,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.26.0 // indirect + golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect