Skip to content

Commit 392cf27

Browse files
mandelsoftjakobmoellerdevSkarlso
authored
feat: configurable preference for relative access creation for uploader (#1412)
<!-- markdownlint-disable MD041 --> #### What this PR does / why we need it By default the OCI uploader used to handle the implicit OCI uploads for OCI based OCM repositories creates an absolute access method. A new global attribute `preferrelativeaccess` (bool) can be used now to switch this behavior to prefer a relative access method. With the new config object `local.oci.uploader.config.ocm.software` it is possible to configure host/port combinations for which the relative access should be preferred. (For example `localhost`). ``` type: local.oci.uploader.config.ocm.software preferRelativeAccess: true repositories: - localhost - other:5000 ``` #### Which issue(s) this PR fixes Fixes #1410 --------- Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Co-authored-by: Jakob Möller <jakob.moeller@sap.com> Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
1 parent 4eed321 commit 392cf27

13 files changed

Lines changed: 509 additions & 2 deletions

File tree

api/ocm/extensions/accessmethods/relativeociref/method.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func init() {
2424

2525
var _ accspeccpi.HintProvider = (*AccessSpec)(nil)
2626

27-
// New creates a new localFilesystemBlob accessor.
27+
// New creates a new relativeOciReference accessor.
2828
func New(ref string) *AccessSpec {
2929
return &AccessSpec{
3030
ObjectVersionedType: runtime.NewVersionedObjectType(Type),
@@ -82,7 +82,15 @@ func (a *AccessSpec) GetOCIReference(cv accspeccpi.ComponentVersionAccess) (stri
8282
defer m.Close()
8383

8484
if o, ok := accspeccpi.GetAccessMethodImplementation(m).(ociartifact.OCIArtifactReferenceProvider); ok {
85-
return o.GetOCIReference(nil)
85+
im, err := o.GetOCIReference(cv)
86+
if err == nil {
87+
spec := cv.Repository().GetSpecification().AsUniformSpec(cv.GetContext())
88+
// not supported for fake repos
89+
if spec.Host != "" {
90+
im = spec.Host + "/" + im
91+
}
92+
}
93+
return im, err
8694
}
8795
return "", nil
8896
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package preferrelativeattr
2+
3+
import (
4+
"fmt"
5+
6+
"ocm.software/ocm/api/datacontext"
7+
"ocm.software/ocm/api/utils/runtime"
8+
)
9+
10+
const (
11+
ATTR_SHORT = "preferrelativeaccess"
12+
ATTR_KEY = "ocm.software/ocm/oci/" + ATTR_SHORT
13+
)
14+
15+
func init() {
16+
datacontext.RegisterAttributeType(ATTR_KEY, AttributeType{}, ATTR_SHORT)
17+
}
18+
19+
type AttributeType struct{}
20+
21+
func (a AttributeType) Name() string {
22+
return ATTR_KEY
23+
}
24+
25+
func (a AttributeType) Description() string {
26+
return `
27+
*bool*
28+
If an artifact blob is uploaded to the technical repository
29+
used as OCM repository, the uploader should prefer to return
30+
a relative access method.
31+
`
32+
}
33+
34+
func (a AttributeType) Encode(v interface{}, marshaller runtime.Marshaler) ([]byte, error) {
35+
if _, ok := v.(bool); !ok {
36+
return nil, fmt.Errorf("boolean required")
37+
}
38+
return marshaller.Marshal(v)
39+
}
40+
41+
func (a AttributeType) Decode(data []byte, unmarshaller runtime.Unmarshaler) (interface{}, error) {
42+
var value bool
43+
err := unmarshaller.Unmarshal(data, &value)
44+
return value, err
45+
}
46+
47+
////////////////////////////////////////////////////////////////////////////////
48+
49+
func Get(ctx datacontext.Context) bool {
50+
a := ctx.GetAttributes().GetAttribute(ATTR_KEY)
51+
if a == nil {
52+
return false
53+
}
54+
return a.(bool)
55+
}
56+
57+
func Set(ctx datacontext.Context, flag bool) error {
58+
return ctx.GetAttributes().SetAttribute(ATTR_KEY, flag)
59+
}
60+
61+
func ApplyTo(ctx datacontext.Context, flag *bool) bool {
62+
if a := ctx.GetAttributes().GetAttribute(ATTR_KEY); a != nil {
63+
*flag = a.(bool)
64+
}
65+
return *flag
66+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package preferrelativeattr_test
2+
3+
import (
4+
. "github.com/onsi/ginkgo/v2"
5+
. "github.com/onsi/gomega"
6+
7+
"ocm.software/ocm/api/config"
8+
"ocm.software/ocm/api/credentials"
9+
"ocm.software/ocm/api/datacontext"
10+
"ocm.software/ocm/api/oci"
11+
"ocm.software/ocm/api/ocm"
12+
me "ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr"
13+
"ocm.software/ocm/api/utils/runtime"
14+
)
15+
16+
var _ = Describe("attribute", func() {
17+
var ctx ocm.Context
18+
var cfgctx config.Context
19+
20+
BeforeEach(func() {
21+
cfgctx = config.WithSharedAttributes(datacontext.New(nil)).New()
22+
credctx := credentials.WithConfigs(cfgctx).New()
23+
ocictx := oci.WithCredentials(credctx).New()
24+
ctx = ocm.WithOCIRepositories(ocictx).New()
25+
})
26+
It("local setting", func() {
27+
Expect(me.Get(ctx)).To(BeFalse())
28+
Expect(me.Set(ctx, true)).To(Succeed())
29+
Expect(me.Get(ctx)).To(BeTrue())
30+
})
31+
32+
It("global setting", func() {
33+
Expect(me.Get(cfgctx)).To(BeFalse())
34+
Expect(me.Set(ctx, true)).To(Succeed())
35+
Expect(me.Get(ctx)).To(BeTrue())
36+
})
37+
38+
It("parses string", func() {
39+
Expect(me.AttributeType{}.Decode([]byte("true"), runtime.DefaultJSONEncoding)).To(BeTrue())
40+
})
41+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package preferrelativeattr_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestConfig(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "OCM Prefer Relative Access Attribute")
13+
}

api/ocm/extensions/blobhandler/handlers/oci/ocirepo/blobhandler.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/mandelsoft/goutils/sliceutils"
1212
"github.com/opencontainers/go-digest"
1313

14+
cfgcpi "ocm.software/ocm/api/config/cpi"
1415
"ocm.software/ocm/api/oci"
1516
"ocm.software/ocm/api/oci/artdesc"
1617
"ocm.software/ocm/api/oci/extensions/repositories/artifactset"
@@ -23,10 +24,13 @@ import (
2324
"ocm.software/ocm/api/ocm/extensions/accessmethods/localociblob"
2425
"ocm.software/ocm/api/ocm/extensions/accessmethods/ociartifact"
2526
"ocm.software/ocm/api/ocm/extensions/accessmethods/ociblob"
27+
"ocm.software/ocm/api/ocm/extensions/accessmethods/relativeociref"
2628
"ocm.software/ocm/api/ocm/extensions/attrs/compatattr"
2729
"ocm.software/ocm/api/ocm/extensions/attrs/keepblobattr"
2830
"ocm.software/ocm/api/ocm/extensions/attrs/mapocirepoattr"
31+
"ocm.software/ocm/api/ocm/extensions/attrs/preferrelativeattr"
2932
storagecontext "ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci"
33+
"ocm.software/ocm/api/ocm/extensions/blobhandler/handlers/oci/ocirepo/config"
3034
"ocm.software/ocm/api/utils/accessobj"
3135
"ocm.software/ocm/api/utils/blobaccess/blobaccess"
3236
)
@@ -226,6 +230,15 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g
226230

227231
keep := keepblobattr.Get(ctx.GetContext())
228232

233+
opts := &config.UploadOptions{}
234+
if err := cfgcpi.NewUpdater(ctx.GetContext().ConfigContext(), opts).Update(); err != nil {
235+
return nil, err
236+
}
237+
238+
// this attribute (only if set) overrides the enabling set in the
239+
// config.
240+
preferrelativeattr.ApplyTo(ctx.GetContext(), &opts.PreferRelativeAccess)
241+
229242
if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok {
230243
// prepare for optimized point to point implementation
231244
log.Debug("oci artifact handler with ocm access source",
@@ -340,6 +353,10 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g
340353
if tag != "" {
341354
tag = ":" + tag
342355
}
356+
if opts.PreferRelativeAccessFor(base) {
357+
ref := namespace.GetNamespace() + tag + version
358+
return relativeociref.New(ref), nil
359+
}
343360
ref := scheme + path.Join(base, namespace.GetNamespace()) + tag + version
344361
return ociartifact.New(ref), nil
345362
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package config
2+
3+
import (
4+
"net"
5+
)
6+
7+
// UploadOptions is used to configure
8+
// the implicit OCI uploader for a local OCM repository.
9+
// It can be used to request the generation of relative
10+
// OCI access methods, generally or for dedicated targets.
11+
type UploadOptions struct {
12+
// PreferRelativeAccess enables or disables the settings.
13+
PreferRelativeAccess bool `json:"preferRelativeAccess,omitempty"`
14+
// Repositories is list of repository specs, with or without port.
15+
// If no filters are configured all repos are matched.
16+
Repositories []string `json:"repositories,omitempty"`
17+
}
18+
19+
// PreferRelativeAccessFor checks a repo spec for using
20+
// a relative access method instead of an absolute one.
21+
// It checks hostname and optionally a port name.
22+
// The most specific configuration wins.
23+
func (o *UploadOptions) PreferRelativeAccessFor(repo string) bool {
24+
if len(o.Repositories) == 0 || !o.PreferRelativeAccess {
25+
return o.PreferRelativeAccess
26+
}
27+
28+
fallback := false
29+
30+
host, port, err := net.SplitHostPort(repo)
31+
if err != nil {
32+
host = repo
33+
}
34+
for _, r := range o.Repositories {
35+
rhost, rport, err := net.SplitHostPort(r)
36+
if err != nil {
37+
rhost = r
38+
}
39+
if host == rhost {
40+
if rport == "" {
41+
fallback = true
42+
}
43+
if port == rport {
44+
return true
45+
}
46+
}
47+
}
48+
return fallback
49+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package config_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo/v2"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestConfig(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "OCI Uploader forOCI OCM Repositories Test Suite")
13+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package config
2+
3+
import (
4+
"ocm.software/ocm/api/config"
5+
cfgcpi "ocm.software/ocm/api/config/cpi"
6+
"ocm.software/ocm/api/ocm/extensions/attrs/preferrelativeattr"
7+
"ocm.software/ocm/api/utils/runtime"
8+
)
9+
10+
const (
11+
ConfigType = "oci.uploader" + cfgcpi.OCM_CONFIG_TYPE_SUFFIX
12+
ConfigTypeV1 = ConfigType + runtime.VersionSeparator + "v1"
13+
)
14+
15+
func init() {
16+
cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigType, usage))
17+
cfgcpi.RegisterConfigType(cfgcpi.NewConfigType[*Config](ConfigTypeV1))
18+
}
19+
20+
// Config describes a memory based config interface.
21+
type Config struct {
22+
runtime.ObjectVersionedType `json:",inline"`
23+
UploadOptions
24+
}
25+
26+
// New creates a new memory ConfigSpec.
27+
func New() *Config {
28+
return &Config{
29+
ObjectVersionedType: runtime.NewVersionedTypedObject(ConfigType),
30+
}
31+
}
32+
33+
func (a *Config) ApplyTo(ctx config.Context, target interface{}) error {
34+
t, ok := target.(*UploadOptions)
35+
if !ok {
36+
return config.ErrNoContext(ConfigType)
37+
}
38+
t.Repositories = append(t.Repositories, a.Repositories...)
39+
t.PreferRelativeAccess = a.PreferRelativeAccess
40+
return nil
41+
}
42+
43+
const usage = `
44+
WARNING: This is an experimental feature. Will be replaced with native local
45+
blob support in OCM in the future.
46+
47+
The config type <code>` + ConfigType + `</code> can be used to set some
48+
configurations for the implicit OCI artifact upload for OCI based OCM repositories.
49+
50+
<pre>
51+
type: ` + ConfigType + `
52+
preferRelativeAccess: true # use relative access methods for given target repositories.
53+
repositories:
54+
- localhost:5000
55+
</pre>
56+
57+
If <code>preferRelativeAccess</code> is set to <code>true</code> the
58+
OCI uploader for OCI based OCM repositories does not use the
59+
OCI repository to create absolute OCI access methods
60+
if the target repository is in the <code>repositories</code> list.
61+
Instead, a relative <code>relativeOciReference</code> access method
62+
is created.
63+
If this list is empty, all uploads are handled this way.
64+
65+
If the global attribute <code>` + preferrelativeattr.ATTR_SHORT + `</code>
66+
is configured, it overrides the <code>preferRelativeAccess</code> setting.
67+
`

0 commit comments

Comments
 (0)