Skip to content

Commit 1a385f2

Browse files
committed
prefer relative access for uploader
- use relative access methods for OCI upload to local OCM repository - make preference configurable for repos
1 parent 7f7df38 commit 1a385f2

File tree

16 files changed

+531
-3
lines changed

16 files changed

+531
-3
lines changed

api/config/utils.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package config
2+
3+
import (
4+
cfgcpi "ocm.software/ocm/api/config/cpi"
5+
)
6+
7+
// GetConfigured applies config objects of a config context
8+
// to a configuration struct of type T.
9+
// A pointer to the configured struct is returned.
10+
// Attention: T must be a struct type.
11+
func GetConfigured[T any](ctxp ContextProvider) (*T, error) {
12+
var c T
13+
err := cfgcpi.NewUpdater(ctxp.ConfigContext(), &c).Update()
14+
if err != nil {
15+
return nil, err
16+
}
17+
return &c, nil
18+
}

api/oci/interface.go

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type (
3838
BlobAccess = internal.BlobAccess
3939
DataAccess = internal.DataAccess
4040
ConsumerIdentityProvider = internal.ConsumerIdentityProvider
41+
42+
UniformRepositorySpecProvider = internal.UniformRepositorySpecProvider
4143
)
4244

4345
func DefaultContext() internal.Context {

api/oci/internal/repotypes.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ type IntermediateRepositorySpecAspect interface {
2020
IsIntermediate() bool
2121
}
2222

23+
type UniformRepositorySpecProvider interface {
24+
UniformRepositorySpec() *UniformRepositorySpec
25+
}
26+
2327
type RepositorySpec interface {
2428
runtime.VersionedTypedObject
29+
UniformRepositorySpecProvider
2530

2631
Name() string
27-
UniformRepositorySpec() *UniformRepositorySpec
2832
Repository(Context, credentials.Credentials) (Repository, error)
2933

3034
Validate(Context, credentials.Credentials, ...credentials.UsageContext) error

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

+12-2
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,17 @@ 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+
if s, ok := cv.Repository().GetSpecification().(oci.UniformRepositorySpecProvider); ok {
88+
host := s.UniformRepositorySpec().Host
89+
// not supported for fake repos
90+
if host != "" {
91+
im = host + "/" + im
92+
}
93+
}
94+
}
95+
return im, err
8696
}
8797
return "", nil
8898
}
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_KEY = "github.com/mandelsoft/ocm/preferrelativeaccess"
12+
ATTR_SHORT = "preferrelativeaccess"
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+
}
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+
})
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

+16
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+
cfgctx "ocm.software/ocm/api/config"
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,14 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g
226230

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

233+
opts, _ := cfgctx.GetConfigured[config.UploadOptions](ctx.GetContext())
234+
if opts == nil {
235+
opts = &config.UploadOptions{}
236+
}
237+
// this attribute (only if set) overrides the enabling set in the
238+
// config.
239+
preferrelativeattr.ApplyTo(ctx.GetContext(), &opts.PreferRelativeAccess)
240+
229241
if m, ok := blob.(blobaccess.AnnotatedBlobAccess[accspeccpi.AccessMethodView]); ok {
230242
// prepare for optimized point to point implementation
231243
log.Debug("oci artifact handler with ocm access source",
@@ -340,6 +352,10 @@ func (b *artifactHandler) StoreBlob(blob cpi.BlobAccess, artType, hint string, g
340352
if tag != "" {
341353
tag = ":" + tag
342354
}
355+
if opts.PreferRelativeAccessFor(base) {
356+
ref := namespace.GetNamespace() + tag + version
357+
return relativeociref.New(ref), nil
358+
}
343359
ref := scheme + path.Join(base, namespace.GetNamespace()) + tag + version
344360
return ociartifact.New(ref), nil
345361
}
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+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 = "local.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+
The config type <code>` + ConfigType + `</code> can be used to set some
45+
configurations for the implicit OCI artifact upload for OCI based OCM repositories.
46+
47+
<pre>
48+
type: ` + ConfigType + `
49+
preferRelativeAccess: true # use relative access methods for given target repositories.
50+
repositories:
51+
- localhost:5000
52+
</pre>
53+
54+
If <code>preferRelativeAccess</code> is set to <code>true</code> the
55+
OCI uploader for OCI based OCM repositories does not use the
56+
OCI repository to create absolute OCI access methods
57+
if the target repository is in the <code>repositories</code> list.
58+
Instead, a relative <code>relativeOciReference</code> access method
59+
is created.
60+
If this list is empty, all uploads are handled this way.
61+
62+
If the global attribute <code>` + preferrelativeattr.ATTR_SHORT + `</code>
63+
is configured, it overrides the <code>preferRelativeAccess</code> setting.
64+
`

0 commit comments

Comments
 (0)