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: