diff --git a/api/config/utils.go b/api/config/utils.go new file mode 100644 index 0000000000..ea98da110b --- /dev/null +++ b/api/config/utils.go @@ -0,0 +1,18 @@ +package config + +import ( + cfgcpi "ocm.software/ocm/api/config/cpi" +) + +// GetConfigured applies config objects of a config context +// to a configuration struct of type T. +// A pointer to the configured struct is returned. +// Attention: T must be a struct type. +func GetConfigured[T any](ctxp ContextProvider) (*T, error) { + var c T + err := cfgcpi.NewUpdater(ctxp.ConfigContext(), &c).Update() + if err != nil { + return nil, err + } + return &c, nil +} diff --git a/api/oci/interface.go b/api/oci/interface.go index 4c78a52b40..b496a619ab 100644 --- a/api/oci/interface.go +++ b/api/oci/interface.go @@ -38,6 +38,8 @@ type ( BlobAccess = internal.BlobAccess DataAccess = internal.DataAccess ConsumerIdentityProvider = internal.ConsumerIdentityProvider + + UniformRepositorySpecProvider = internal.UniformRepositorySpecProvider ) func DefaultContext() internal.Context { diff --git a/api/oci/internal/repotypes.go b/api/oci/internal/repotypes.go index cb2024aeb8..e8415d9053 100644 --- a/api/oci/internal/repotypes.go +++ b/api/oci/internal/repotypes.go @@ -20,11 +20,15 @@ type IntermediateRepositorySpecAspect interface { IsIntermediate() bool } +type UniformRepositorySpecProvider interface { + UniformRepositorySpec() *UniformRepositorySpec +} + type RepositorySpec interface { runtime.VersionedTypedObject + UniformRepositorySpecProvider Name() string - UniformRepositorySpec() *UniformRepositorySpec Repository(Context, credentials.Credentials) (Repository, error) Validate(Context, credentials.Credentials, ...credentials.UsageContext) error diff --git a/api/ocm/extensions/accessmethods/relativeociref/method.go b/api/ocm/extensions/accessmethods/relativeociref/method.go index 69a28664a9..a7a3d8699f 100644 --- a/api/ocm/extensions/accessmethods/relativeociref/method.go +++ b/api/ocm/extensions/accessmethods/relativeociref/method.go @@ -24,7 +24,7 @@ func init() { var _ accspeccpi.HintProvider = (*AccessSpec)(nil) -// New creates a new localFilesystemBlob accessor. +// New creates a new relativeOciReference accessor. func New(ref string) *AccessSpec { return &AccessSpec{ ObjectVersionedType: runtime.NewVersionedObjectType(Type), @@ -82,7 +82,17 @@ func (a *AccessSpec) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (stri defer m.Close() if o, ok := accspeccpi.GetAccessMethodImplementation(m).(ociartifact.OCIArtifactReferenceProvider); ok { - return o.GetOCIReference(nil) + im, err := o.GetOCIReference(cv) + if err == nil { + if s, ok := cv.Repository().GetSpecification().(oci.UniformRepositorySpecProvider); ok { + host := s.UniformRepositorySpec().Host + // not supported for fake repos + if host != "" { + im = host + "/" + im + } + } + } + return im, err } return "", nil } diff --git a/api/ocm/extensions/attrs/preferrelativeattr/attr.go b/api/ocm/extensions/attrs/preferrelativeattr/attr.go new file mode 100644 index 0000000000..00dcfad7d4 --- /dev/null +++ b/api/ocm/extensions/attrs/preferrelativeattr/attr.go @@ -0,0 +1,66 @@ +package preferrelativeattr + +import ( + "fmt" + + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ATTR_KEY = "github.com/mandelsoft/ocm/preferrelativeaccess" + ATTR_SHORT = "preferrelativeaccess" +) + +func init() { + datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT) +} + +type AttributeType struct{} + +func (a AttributeType) Name() string { + return ATTR_KEY +} + +func (a AttributeType) Description() string { + return ` +*bool* +If an artifact blob is uploaded to the technical repository +used as OCM repository, the uploader should prefer to return +a relative access method. +` +} + +func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) { + if _, ok := v.(bool); !ok { + return nil, fmt.Errorf("boolean required") + } + return marshaller.Marshal(v) +} + +func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) { + var value bool + err := unmarshaller.Unmarshal(data, &value) + return value, err +} + +//////////////////////////////////////////////////////////////////////////////// + +func Get(ctx datacontext.Context) bool { + a := ctx.GetAttributes().GetAttribute(ATTR_KEY) + if a == nil { + return false + } + return a.(bool) +} + +func Set(ctx datacontext.Context, flag bool) error { + return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag) +} + +func ApplyTo(ctx datacontext.Context, flag *bool) bool { + if a := ctx.GetAttributes().GetAttribute(ATTR_KEY); a != nil { + *flag = a.(bool) + } + return *flag +} diff --git a/api/ocm/extensions/attrs/preferrelativeattr/attr_test.go b/api/ocm/extensions/attrs/preferrelativeattr/attr_test.go new file mode 100644 index 0000000000..6bf9e3c4b0 --- /dev/null +++ b/api/ocm/extensions/attrs/preferrelativeattr/attr_test.go @@ -0,0 +1,41 @@ +package preferrelativeattr_test + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "ocm.software/ocm/api/config" + "ocm.software/ocm/api/credentials" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/oci" + "ocm.software/ocm/api/ocm" + me "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" + "ocm.software/ocm/api/utils/runtime" +) + +var _ = Describe("attribute", func() { + var ctx ocm.Context + var cfgctx config.Context + + BeforeEach(func() { + cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New() + credctx := credentials.WithConfigs(cfgctx).New() + ocictx := oci.WithCredentials(credctx).New() + ctx = ocm.WithOCIRepositories(ocictx).New() + }) + It("local setting", func() { + Expect(me.Get(ctx)).To(BeFalse()) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + }) + + It("global setting", func() { + Expect(me.Get(cfgctx)).To(BeFalse()) + Expect(me.Set(ctx, true)).To(Succeed()) + Expect(me.Get(ctx)).To(BeTrue()) + }) + + It("parses string", func() { + Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue()) + }) +}) diff --git a/api/ocm/extensions/attrs/preferrelativeattr/suite_test.go b/api/ocm/extensions/attrs/preferrelativeattr/suite_test.go new file mode 100644 index 0000000000..6edf1ac870 --- /dev/null +++ b/api/ocm/extensions/attrs/preferrelativeattr/suite_test.go @@ -0,0 +1,13 @@ +package preferrelativeattr_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCM Prefer Relative Access Attribute") +} diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go index fc55f64e74..85ecff7c37 100644 --- a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go @@ -11,6 +11,7 @@ import ( "github.com/mandelsoft/goutils/sliceutils" "github.com/opencontainers/go-digest" + cfgctx "ocm.software/ocm/api/config" "ocm.software/ocm/api/oci" "ocm.software/ocm/api/oci/artdesc" "ocm.software/ocm/api/oci/extensions/repositories/artifactset" @@ -23,10 +24,13 @@ import ( "ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob" "ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact" "ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob" + "ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref" "ocm.software/ocm/api/ocm/extensions/attrs/compatattr" "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr" "ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr" + "ocm.software/ocm/api/ocm/extensions/attrs/preferrelativeattr" storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config" "ocm.software/ocm/api/utils/accessobj" "ocm.software/ocm/api/utils/blobaccess/blobaccess" ) @@ -226,6 +230,14 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g keep := keepblobattr.Get(ctx.GetContext()) + opts, _ := cfgctx.GetConfigured[config.UploadOptions](ctx.GetContext()) + if opts == nil { + opts = &config.UploadOptions{} + } + // this attribute (only if set) overrides the enabling set in the + // config. + preferrelativeattr.ApplyTo(ctx.GetContext(), &opts.PreferRelativeAccess) + if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok { // prepare for optimized point to point implementation log.Debug("oci artifact handler with ocm access source", @@ -340,6 +352,10 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g if tag != "" { tag = ":" + tag } + if opts.PreferRelativeAccessFor(base) { + ref := namespace.GetNamespace() + tag + version + return relativeociref.New(ref), nil + } ref := scheme + path.Join(base, namespace.GetNamespace()) + tag + version return ociartifact.New(ref), nil } diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/option.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/option.go new file mode 100644 index 0000000000..2944169fb1 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/option.go @@ -0,0 +1,49 @@ +package config + +import ( + "net" +) + +// UploadOptions is used to configure +// the implicit OCI uploader for a local OCM repository. +// It can be used to request the generation of relative +// OCI access methods, generally or for dedicated targets. +type UploadOptions struct { + // PreferRelativeAccess enables or disables the settings. + PreferRelativeAccess bool `json:"preferRelativeAccess,omitempty"` + // Repositories is list of repository specs, with or without port. + // If no filters are configured all repos are matched. + Repositories []string `json:"repositories,omitempty"` +} + +// PreferRelativeAccessFor checks a repo spec for using +// a relative access method instead of an absolute one. +// It checks hostname and optionally a port name. +// The most specific configuration wins. +func (o *UploadOptions) PreferRelativeAccessFor(repo string) bool { + if len(o.Repositories) == 0 || !o.PreferRelativeAccess { + return o.PreferRelativeAccess + } + + fallback := false + + host, port, err := net.SplitHostPort(repo) + if err != nil { + host = repo + } + for _, r := range o.Repositories { + rhost, rport, err := net.SplitHostPort(r) + if err != nil { + rhost = r + } + if host == rhost { + if rport == "" { + fallback = true + } + if port == rport { + return true + } + } + } + return fallback +} diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/suite_test.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/suite_test.go new file mode 100644 index 0000000000..85caedabe8 --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/suite_test.go @@ -0,0 +1,13 @@ +package config_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestConfig(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "OCI Uploader forOCI OCM Repositories Test Suite") +} diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/type.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/type.go new file mode 100644 index 0000000000..1520dbdb1c --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/type.go @@ -0,0 +1,64 @@ +package config + +import ( + "ocm.software/ocm/api/config" + cfgcpi "ocm.software/ocm/api/config/cpi" + "ocm.software/ocm/api/ocm/extensions/attrs/preferrelativeattr" + "ocm.software/ocm/api/utils/runtime" +) + +const ( + ConfigType = "local.oci.uploader" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX + ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1" +) + +func init() { + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage)) + cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1)) +} + +// Config describes a memory based config interface. +type Config struct { + runtime.ObjectVersionedType `json:",inline"` + UploadOptions +} + +// New creates a new memory ConfigSpec. +func New() *Config { + return &Config{ + ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType), + } +} + +func (a *Config) ApplyTo(ctx config.Context, target interface{}) error { + t, ok := target.(*UploadOptions) + if !ok { + return config.ErrNoContext(ConfigType) + } + t.Repositories = append(t.Repositories, a.Repositories...) + t.PreferRelativeAccess = a.PreferRelativeAccess + return nil +} + +const usage = ` +The config type ` + ConfigType + ` can be used to set some +configurations for the implicit OCI artifact upload for OCI based OCM repositories. + +
+    type: ` + ConfigType + `
+    preferRelativeAccess: true # use relative access methods for given target repositories.
+    repositories:
+	- localhost:5000
+
+ +If preferRelativeAccess is set to true the +OCI uploader for OCI based OCM repositories does not use the +OCI repository to create absolute OCI access methods +if the target repository is in the repositories list. +Instead, a relative relativeOciReference access method +is created. +If this list is empty, all uploads are handled this way. + +If the global attribute ` + preferrelativeattr.ATTR_SHORT + ` +is configured, it overrides the preferRelativeAccess setting. +` diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/type_test.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/type_test.go new file mode 100644 index 0000000000..97cccc36df --- /dev/null +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config/type_test.go @@ -0,0 +1,76 @@ +package config_test + +import ( + . "github.com/mandelsoft/goutils/testutils" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + cfgctx "ocm.software/ocm/api/config" + "ocm.software/ocm/api/datacontext" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config" +) + +var _ = Describe("Test Environment", func() { + var opts *config.UploadOptions + + BeforeEach(func() { + opts = &config.UploadOptions{ + PreferRelativeAccess: true, + Repositories: []string{ + "localhost", + "localhost:5000", + "host", + "other:5000", + }, + } + }) + + Context("options", func() { + It("check without repos", func() { + Expect((&config.UploadOptions{PreferRelativeAccess: true}).PreferRelativeAccessFor("host")).To(BeTrue()) + Expect((&config.UploadOptions{PreferRelativeAccess: false}).PreferRelativeAccessFor("host")).To(BeFalse()) + }) + + It("check for host", func() { + Expect(opts.PreferRelativeAccessFor("localhost")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("localhost:5000")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("localhost:6000")).To(BeTrue()) + + Expect(opts.PreferRelativeAccessFor("host")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("host:5000")).To(BeTrue()) + + Expect(opts.PreferRelativeAccessFor("other:5000")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("other")).To(BeFalse()) + + Expect(opts.PreferRelativeAccessFor("any")).To(BeFalse()) + }) + }) + + Context("config object", func() { + var cfg cfgctx.Context + + BeforeEach(func() { + cfg = cfgctx.New(datacontext.MODE_DEFAULTED) + }) + + It("configures", func() { + o := config.New() + o.UploadOptions = *opts + + MustBeSuccessful(cfg.ApplyConfig(o, "manual")) + + opts = Must(cfgctx.GetConfigured[config.UploadOptions](cfg)) + + Expect(opts.PreferRelativeAccessFor("localhost")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("localhost:5000")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("localhost:6000")).To(BeTrue()) + + Expect(opts.PreferRelativeAccessFor("host")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("host:5000")).To(BeTrue()) + + Expect(opts.PreferRelativeAccessFor("other:5000")).To(BeTrue()) + Expect(opts.PreferRelativeAccessFor("other")).To(BeFalse()) + + Expect(opts.PreferRelativeAccessFor("any")).To(BeFalse()) + }) + }) +}) diff --git a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go index 6fc5155f25..4099b24349 100644 --- a/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go +++ b/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/handler_test.go @@ -10,6 +10,11 @@ import ( . "ocm.software/ocm/api/helper/builder" . "ocm.software/ocm/api/oci/testhelper" + "ocm.software/ocm/api/oci/extensions/repositories/artifactset" + "ocm.software/ocm/api/ocm/extensions/attrs/preferrelativeattr" + "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config" + "ocm.software/ocm/api/ocm/ocmutils" + "ocm.software/ocm/api/oci" "ocm.software/ocm/api/oci/artdesc" ocictf "ocm.software/ocm/api/oci/extensions/repositories/ctf" @@ -101,6 +106,124 @@ var _ = Describe("oci artifact transfer", func() { }) }) + It("it should copy a resource by value and export the OCI image using a relative OCI access method", func() { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + preferrelativeattr.Set(env.OCMContext(), true) + + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + Expect(data).To(YAMLEqual(`{"reference":"` + OCINAMESPACE + ":" + OCIVERSION + `@sha256:` + D_OCIMANIFEST1 + `","type":"relativeOciReference"}`)) + ocirepo := genericocireg.GetOCIRepository(tgt) + Expect(ocirepo).NotTo(BeNil()) + + res := Must(comp.GetResourceByIndex(1)) + s := Must(ocmutils.GetOCIArtifactRef(comp.GetContext(), res)) + Expect(s).To(Equal("ocm/value:v2.0@sha256:" + D_OCIMANIFEST1)) + art := Must(ocirepo.LookupArtifact(OCINAMESPACE, OCIVERSION)) + defer Close(art, "artifact") + + man := MustBeNonNil(art.ManifestAccess()) + Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) + Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) + + blob := Must(man.GetBlob(ldesc.Digest)) + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + + b := Must(res.BlobAccess()) + defer Close(b, "blob") + + set := Must(artifactset.OpenFromBlob(accessobj.ACC_READONLY, b, env)) + defer Close(set, "artifact") + Expect(set.GetAnnotation(artifactset.MAINARTIFACT_ANNOTATION)).To(Equal("sha256:" + D_OCIMANIFEST1)) + }) + + DescribeTable("it should copy a resource by value and export the OCI image using a relative OCI access method", func(prefer bool, repos []string, relative bool) { + env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), + cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) + + o := config.New() + o.UploadOptions = config.UploadOptions{PreferRelativeAccess: prefer, Repositories: repos} + env.ConfigContext().ApplyConfig(o, "manual") + src := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, ARCH, 0, env)) + cv := Must(src.LookupComponentVersion(COMPONENT, VERSION)) + tgt := Must(ctf.Create(env.OCMContext(), accessobj.ACC_WRITABLE|accessobj.ACC_CREATE, OUT, 0o700, accessio.FormatDirectory, env)) + defer tgt.Close() + + opts := &standard.Options{} + opts.SetResourcesByValue(true) + handler := standard.NewDefaultHandler(opts) + + MustBeSuccessful(transfer.TransferVersion(nil, nil, cv, tgt, handler)) + Expect(env.DirExists(OUT)).To(BeTrue()) + + list := Must(tgt.ComponentLister().GetComponents("", true)) + Expect(list).To(Equal([]string{COMPONENT})) + comp := Must(tgt.LookupComponentVersion(COMPONENT, VERSION)) + Expect(len(comp.GetDescriptor().Resources)).To(Equal(2)) + data := Must(json.Marshal(comp.GetDescriptor().Resources[1].Access)) + + fmt.Printf("%s\n", string(data)) + if relative { + Expect(data).To(YAMLEqual(`{"reference":"` + OCINAMESPACE + ":" + OCIVERSION + `@sha256:` + D_OCIMANIFEST1 + `","type":"relativeOciReference"}`)) + + ocirepo := genericocireg.GetOCIRepository(tgt) + Expect(ocirepo).NotTo(BeNil()) + + res := Must(comp.GetResourceByIndex(1)) + s := Must(ocmutils.GetOCIArtifactRef(comp.GetContext(), res)) + if relative { + // cannot be faked + Expect(s).To(Equal("ocm/value:v2.0@sha256:" + D_OCIMANIFEST1)) + } else { + Expect(s).To(Equal("baseurl.io/ocm/value:v2.0@sha256:" + D_OCIMANIFEST1)) + } + art := Must(ocirepo.LookupArtifact(OCINAMESPACE, OCIVERSION)) + defer Close(art, "artifact") + + man := MustBeNonNil(art.ManifestAccess()) + Expect(len(man.GetDescriptor().Layers)).To(Equal(1)) + Expect(man.GetDescriptor().Layers[0].Digest).To(Equal(ldesc.Digest)) + + blob := Must(man.GetBlob(ldesc.Digest)) + data = Must(blob.Get()) + Expect(string(data)).To(Equal(OCILAYER)) + + b := Must(res.BlobAccess()) + defer Close(b, "blob") + + set := Must(artifactset.OpenFromBlob(accessobj.ACC_READONLY, b, env)) + defer Close(set, "artifact") + Expect(set.GetAnnotation(artifactset.MAINARTIFACT_ANNOTATION)).To(Equal("sha256:" + D_OCIMANIFEST1)) + } else { + // cannot access faked local repository URL baseurl.io + Expect(data).To(YAMLEqual(`{"imageReference":"baseurl.io/` + OCINAMESPACE + ":" + OCIVERSION + `@sha256:` + D_OCIMANIFEST1 + `","type":"ociArtifact"}`)) + } + }, + Entry("none", false, nil, false), + Entry("baseurl.io", true, []string{"baseurl.io"}, true), + Entry("baseurl.de", true, []string{"baseurl.de"}, false), + Entry("all", true, nil, true), + ) + It("it should copy a resource by value and export the OCI image but keep the local blob", func() { env.OCMContext().BlobHandlers().Register(ocirepo.NewArtifactHandler(FakeOCIRegBaseFunction), cpi.ForRepo(oci.CONTEXT_TYPE, ocictf.Type), cpi.ForMimeType(artdesc.ToContentMediaType(artdesc.MediaTypeImageManifest))) diff --git a/docs/reference/ocm.md b/docs/reference/ocm.md index 3e49ba7bbf..9a7ee2dd76 100644 --- a/docs/reference/ocm.md +++ b/docs/reference/ocm.md @@ -238,6 +238,12 @@ The value can be a simple type or a JSON/YAML string for complex values Directory to look for OCM plugin executables. +- github.com/mandelsoft/ocm/preferrelativeaccess [preferrelativeaccess]: *bool* + + If an artifact blob is uploaded to the technical repository + used as OCM repository, the uploader should prefer to return + a relative access method. + - github.com/mandelsoft/ocm/rootcerts [rootcerts]: *JSON* General root certificate settings given as JSON document with the following diff --git a/docs/reference/ocm_attributes.md b/docs/reference/ocm_attributes.md index af981a5bf0..52fda14f8d 100644 --- a/docs/reference/ocm_attributes.md +++ b/docs/reference/ocm_attributes.md @@ -130,6 +130,12 @@ OCM library: Directory to look for OCM plugin executables. +- github.com/mandelsoft/ocm/preferrelativeaccess [preferrelativeaccess]: *bool* + + If an artifact blob is uploaded to the technical repository + used as OCM repository, the uploader should prefer to return + a relative access method. + - github.com/mandelsoft/ocm/rootcerts [rootcerts]: *JSON* General root certificate settings given as JSON document with the following diff --git a/docs/reference/ocm_configfile.md b/docs/reference/ocm_configfile.md index 7f9a01b7f2..8212cc267d 100644 --- a/docs/reference/ocm_configfile.md +++ b/docs/reference/ocm_configfile.md @@ -168,6 +168,27 @@ The following configuration types are supported: At least the given values must be present in the certificate to be accepted for a successful signature validation. +- local.oci.uploader.config.ocm.software + The config type local.oci.uploader.config.ocm.software can be used to set some + configurations for the implicit OCI artifact upload for OCI based OCM repositories. + +
+      type: local.oci.uploader.config.ocm.software
+      preferRelativeAccess: true # use relative access methods for given target repositories.
+      repositories:
+  	- localhost:5000
+  
+ + If preferRelativeAccess is set to true the + OCI uploader for OCI based OCM repositories does not use the + OCI repository to create absolute OCI access methods + if the target repository is in the repositories list. + Instead, a relative relativeOciReference access method + is created. + If this list is empty, all uploads are handled this way. + + If the global attribute preferrelativeaccess + is configured, it overrides the preferRelativeAccess setting. - logging.config.ocm.software The config type logging.config.ocm.software can be used to configure the logging aspect of a dedicated context type: