Skip to content

Commit 2b87daf

Browse files
feat(796): oci artifact spec (#1730)
1 parent d9f3a71 commit 2b87daf

17 files changed

Lines changed: 1888 additions & 17 deletions

bindings/go/oci/integration/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ require (
1111
github.com/testcontainers/testcontainers-go/modules/registry v0.40.0
1212
golang.org/x/crypto v0.46.0
1313
ocm.software/open-component-model/bindings/go/blob v0.0.11
14+
ocm.software/open-component-model/bindings/go/configuration v0.0.9
15+
ocm.software/open-component-model/bindings/go/credentials v0.0.7
1416
ocm.software/open-component-model/bindings/go/ctf v0.3.0
1517
ocm.software/open-component-model/bindings/go/descriptor/runtime v0.0.0-20260209114331-2c034a9e2912
1618
ocm.software/open-component-model/bindings/go/descriptor/v2 v2.0.1-alpha9
@@ -82,5 +84,6 @@ require (
8284
golang.org/x/text v0.33.0 // indirect
8385
golang.org/x/time v0.14.0 // indirect
8486
gopkg.in/yaml.v3 v3.0.1 // indirect
87+
ocm.software/open-component-model/bindings/go/dag v0.0.6 // indirect
8588
sigs.k8s.io/yaml v1.6.0 // indirect
8689
)

bindings/go/oci/integration/go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,14 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
181181
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
182182
ocm.software/open-component-model/bindings/go/blob v0.0.11 h1:ASalU+M+PMgLGI6vQ1ZIWC65OOtN2a49qIMNR6BNONA=
183183
ocm.software/open-component-model/bindings/go/blob v0.0.11/go.mod h1:pZzaYptwTVBhicYp6z9vDY2K6KTwd5c+MuYoC3jr6s8=
184+
ocm.software/open-component-model/bindings/go/configuration v0.0.9 h1:bfgPD2G3fgmcbRYw0WXO3Fnx7+0G4gOV54JnYXgQY7w=
185+
ocm.software/open-component-model/bindings/go/configuration v0.0.9/go.mod h1:VZ8jQ3a6oTWyOpY09C7V3L0CfzKe9i1/Z4uJjLAVjN4=
186+
ocm.software/open-component-model/bindings/go/credentials v0.0.7 h1:pxf8fKSwWFiuvCjFrEfMODE4Vkyi+QexM/8x+mNdhcQ=
187+
ocm.software/open-component-model/bindings/go/credentials v0.0.7/go.mod h1:nWi5y4VhkAGaxfy6wNoLCde0saCu8ViHvjqBXHbe04U=
184188
ocm.software/open-component-model/bindings/go/ctf v0.3.0 h1:u1B26OgUvR+gzTwY0hTVdZgFIXv5uwdI8reEvCcjNV4=
185189
ocm.software/open-component-model/bindings/go/ctf v0.3.0/go.mod h1:skMxA1l7rZFhKsjn8CysSVG31zjmV95WaFqMVKRjHug=
190+
ocm.software/open-component-model/bindings/go/dag v0.0.6 h1:To76QJAmFD88C101oB/HgYvtomp8mm0270ewDLcVncw=
191+
ocm.software/open-component-model/bindings/go/dag v0.0.6/go.mod h1:mQbO95zYvX59VXNJGer4+wGsKY0BVI4FKwlR5BlPugM=
186192
ocm.software/open-component-model/bindings/go/descriptor/runtime v0.0.0-20260209114331-2c034a9e2912 h1:vuMu/Cnc8YZTHmFGJbGIAgDGhL8cHfVmS+eu1GF1/Fg=
187193
ocm.software/open-component-model/bindings/go/descriptor/runtime v0.0.0-20260209114331-2c034a9e2912/go.mod h1:w8DLZVNfVWjXt1Z1wBW+0sbz0r87jj8mfrT5gHco+BA=
188194
ocm.software/open-component-model/bindings/go/descriptor/v2 v2.0.1-alpha9 h1:SdQm6w7Lj0g+d+MaNXQQkpKUqOhFwUSzoUjzRyeKL0I=

bindings/go/oci/integration/integration_test.go

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,27 @@ import (
3434

3535
"ocm.software/open-component-model/bindings/go/blob"
3636
"ocm.software/open-component-model/bindings/go/blob/filesystem"
37+
filesystemaccess "ocm.software/open-component-model/bindings/go/blob/filesystem/spec/access"
3738
"ocm.software/open-component-model/bindings/go/blob/inmemory"
39+
filesystemv1alpha1 "ocm.software/open-component-model/bindings/go/configuration/filesystem/v1alpha1/spec"
40+
"ocm.software/open-component-model/bindings/go/credentials"
3841
"ocm.software/open-component-model/bindings/go/ctf"
3942
descriptor "ocm.software/open-component-model/bindings/go/descriptor/runtime"
4043
v2 "ocm.software/open-component-model/bindings/go/descriptor/v2"
4144
"ocm.software/open-component-model/bindings/go/oci"
45+
ociinmemory "ocm.software/open-component-model/bindings/go/oci/cache/inmemory"
4246
ocictf "ocm.software/open-component-model/bindings/go/oci/ctf"
4347
"ocm.software/open-component-model/bindings/go/oci/repository/provider"
48+
"ocm.software/open-component-model/bindings/go/oci/repository/resource"
4449
urlresolver "ocm.software/open-component-model/bindings/go/oci/resolver/url"
4550
ocmoci "ocm.software/open-component-model/bindings/go/oci/spec/access"
4651
v1 "ocm.software/open-component-model/bindings/go/oci/spec/access/v1"
4752
"ocm.software/open-component-model/bindings/go/oci/spec/layout"
4853
ctfrepospecv1 "ocm.software/open-component-model/bindings/go/oci/spec/repository/v1/ctf"
4954
ocirepospecv1 "ocm.software/open-component-model/bindings/go/oci/spec/repository/v1/oci"
55+
"ocm.software/open-component-model/bindings/go/oci/spec/transformation/v1alpha1"
5056
"ocm.software/open-component-model/bindings/go/oci/tar"
57+
"ocm.software/open-component-model/bindings/go/oci/transformer"
5158
"ocm.software/open-component-model/bindings/go/repository"
5259
ocmruntime "ocm.software/open-component-model/bindings/go/runtime"
5360
)
@@ -422,6 +429,57 @@ func Test_Integration_CTF_Lister(t *testing.T) {
422429
require.Equal(t, expectedList, result)
423430
}
424431

432+
// Test_Integration_Transformers needs a different setup than the other tests.
433+
// Reason being that transformer.GetOCIArtifact requires repository.ResourceRepository.
434+
// Since oci.Repository does not implement repository.ResourceRepository yet, we could not reuse the setup with
435+
// urlresolver.Resolver. We will be able to unify the testing setup once we refactored the oci Repositories
436+
// https://github.com/open-component-model/ocm-project/issues/774
437+
func Test_Integration_Transformers(t *testing.T) {
438+
t.Parallel()
439+
ctx := t.Context()
440+
441+
t.Logf("Starting transformers integration test")
442+
443+
// Setup credentials and htpasswd
444+
password := generateRandomPassword(t, passwordLength)
445+
htpasswd := generateHtpasswd(t, testUsername, password)
446+
447+
// Start containerized registry
448+
t.Logf("Launching test registry (%s)...", distributionRegistryImage)
449+
registryContainer, err := registry.Run(ctx, distributionRegistryImage,
450+
registry.WithHtpasswd(htpasswd),
451+
testcontainers.WithEnv(map[string]string{
452+
"REGISTRY_VALIDATION_DISABLED": "true",
453+
"REGISTRY_LOG_LEVEL": "debug",
454+
}),
455+
testcontainers.WithLogger(log.TestLogger(t)),
456+
)
457+
r := require.New(t)
458+
r.NoError(err)
459+
t.Cleanup(func() {
460+
r.NoError(testcontainers.TerminateContainer(registryContainer))
461+
})
462+
t.Logf("Test registry started")
463+
464+
t.Run("ociArtifact", func(t *testing.T) {
465+
r := require.New(t)
466+
registryAddress, err := registryContainer.HostAddress(ctx)
467+
r.NoError(err)
468+
469+
reference := func(ref string) string {
470+
return fmt.Sprintf("%s/%s", registryAddress, ref)
471+
}
472+
473+
t.Run("get oci artifact", func(t *testing.T) {
474+
resourceRepo := resource.NewResourceRepository(ociinmemory.New(), ociinmemory.New(), &filesystemv1alpha1.Config{})
475+
476+
t.Run("get oci transformation", func(t *testing.T) {
477+
transformGetOCIArtifact(t, resourceRepo, testUsername, password, "ghcr.io/test:v1.0.0", reference("new-test:v1.0.0"))
478+
})
479+
})
480+
})
481+
}
482+
425483
func uploadDownloadLocalResourceOCILayout(t *testing.T, repo *oci.Repository, component string, version string) {
426484
ctx := t.Context()
427485
r := require.New(t)
@@ -946,3 +1004,98 @@ func getUsername(t *testing.T, gh string) (string, error) {
9461004

9471005
return structured["login"].(string), nil
9481006
}
1007+
1008+
func transformGetOCIArtifact(t *testing.T, repo repository.ResourceRepository, username, password, from, to string) {
1009+
ctx := t.Context()
1010+
r := require.New(t)
1011+
1012+
url, err := ocmruntime.ParseURLAndAllowNoScheme(to)
1013+
r.NoError(err)
1014+
1015+
toIdentity := ocmruntime.Identity{
1016+
"scheme": "http",
1017+
"hostname": url.Hostname(),
1018+
"port": url.Port(),
1019+
"type": "OCIRepository",
1020+
}
1021+
1022+
originalData := []byte("foobar")
1023+
1024+
data, access := createSingleLayerOCIImage(t, originalData, from)
1025+
r.NotNil(access)
1026+
1027+
access.Type = ocmruntime.Type{
1028+
Name: "ociArtifact",
1029+
Version: "v1",
1030+
}
1031+
1032+
blob := inmemory.New(bytes.NewReader(data))
1033+
1034+
resource := descriptor.Resource{
1035+
ElementMeta: descriptor.ElementMeta{
1036+
ObjectMeta: descriptor.ObjectMeta{
1037+
Name: "test-resource",
1038+
Version: "v1.0.0",
1039+
},
1040+
},
1041+
Type: "some-arbitrary-type-packed-in-image",
1042+
Access: access,
1043+
CreationTime: descriptor.CreationTime(time.Now()),
1044+
}
1045+
1046+
targetAccess := resource.Access.DeepCopyTyped()
1047+
targetAccess.(*v1.OCIImage).ImageReference = fmt.Sprintf("http://%s", to)
1048+
resource.Access = targetAccess
1049+
1050+
credsMap := map[string]map[string]string{
1051+
toIdentity.String(): {
1052+
"username": username,
1053+
"password": password,
1054+
},
1055+
}
1056+
credsResolver := credentials.NewStaticCredentialsResolver(credsMap)
1057+
creds := credsMap[toIdentity.String()]
1058+
r.NotNil(creds)
1059+
1060+
newRes, err := repo.UploadResource(ctx, &resource, blob, creds)
1061+
r.NoError(err)
1062+
resource = *newRes
1063+
1064+
combinedScheme := ocmruntime.NewScheme()
1065+
v2.MustAddToScheme(combinedScheme)
1066+
filesystemaccess.MustAddToScheme(combinedScheme)
1067+
combinedScheme.MustRegisterWithAlias(&v1alpha1.GetOCIArtifact{}, v1alpha1.GetOCIArtifactV1alpha1)
1068+
1069+
transform := transformer.GetOCIArtifact{
1070+
Scheme: combinedScheme,
1071+
Repository: repo,
1072+
CredentialProvider: credsResolver,
1073+
}
1074+
1075+
v2Resource, err := descriptor.ConvertToV2Resource(ocmruntime.NewScheme(ocmruntime.WithAllowUnknown()), newRes)
1076+
r.NoError(err)
1077+
1078+
spec := &v1alpha1.GetOCIArtifact{
1079+
Type: ocmruntime.NewVersionedType(v1alpha1.GetOCIArtifactType, v1alpha1.Version),
1080+
ID: "test-get-oci-transform",
1081+
Spec: &v1alpha1.GetOCIArtifactSpec{
1082+
Resource: v2Resource,
1083+
},
1084+
}
1085+
1086+
// Execute transformation
1087+
result, err := transform.Transform(ctx, spec)
1088+
require.NoError(t, err)
1089+
require.NotNil(t, result)
1090+
1091+
ociOutput, ok := result.(*v1alpha1.GetOCIArtifact)
1092+
require.True(t, ok)
1093+
require.NotNil(t, ociOutput)
1094+
1095+
require.NotNil(t, ociOutput.Output.File)
1096+
require.NotNil(t, ociOutput.Output.File.URI)
1097+
1098+
// must match pattern oci-artifact-%s.tar.gz
1099+
require.Regexp(t, `^oci-artifact-[a-f0-9]+\.tar\.gz$`, filepath.Base(ociOutput.Output.File.URI))
1100+
require.Equal(t, ociOutput.Output.File.MediaType, "application/vnd.ocm.software.oci.layout.v1+tar+gzip")
1101+
}

bindings/go/oci/repository.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ func (repo *Repository) processOCIImageDigest(ctx context.Context, res *descript
281281

282282
var desc ociImageSpecV1.Descriptor
283283
if pinnedDigest.String() == "" {
284-
if desc, err = src.Resolve(ctx, resolved.String()); err != nil {
284+
if desc, err = src.Resolve(ctx, resolved.ReferenceOrTag()); err != nil {
285285
return nil, fmt.Errorf("failed to resolve reference to process digest %q: %w", typed.ImageReference, err)
286286
}
287287
} else {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package v1alpha1
2+
3+
import (
4+
"ocm.software/open-component-model/bindings/go/blob/filesystem/spec/access/v1alpha1"
5+
v2 "ocm.software/open-component-model/bindings/go/descriptor/v2"
6+
"ocm.software/open-component-model/bindings/go/runtime"
7+
)
8+
9+
const GetOCIArtifactType = "GetOCIArtifact"
10+
11+
// GetOCIArtifact is a transformer specification to get an OCI artifact
12+
// from a remote OCI registry and buffer it to a file.
13+
// It contains the resource descriptor of the artifact to be retrieved and an optional output path.
14+
// If no output path is given, a temporary file will be created for buffering the artifact.
15+
// Spec: GetOCIArtifactSpec - the input specification of the transformation containing the resource descriptor and output path.
16+
// Output: GetOCIArtifactOutput - the output specification of the transformation containing the file access specification
17+
// for the downloaded artifact and the resource descriptor from the component.
18+
// +k8s:deepcopy-gen:interfaces=ocm.software/open-component-model/bindings/go/runtime.Typed
19+
// +k8s:deepcopy-gen=true
20+
// +ocm:typegen=true
21+
// +ocm:jsonschema-gen=true
22+
type GetOCIArtifact struct {
23+
// +ocm:jsonschema-gen:enum=GetOCIArtifact/v1alpha1
24+
Type runtime.Type `json:"type"`
25+
ID string `json:"id"`
26+
Spec *GetOCIArtifactSpec `json:"spec"`
27+
Output *GetOCIArtifactOutput `json:"output,omitempty"`
28+
}
29+
30+
// GetOCIArtifactOutput is the output specification of the
31+
// GetOCIArtifact transformation.
32+
// +k8s:deepcopy-gen=true
33+
// +ocm:jsonschema-gen=true
34+
type GetOCIArtifactOutput struct {
35+
// File is the file access specification for the downloaded artifact
36+
File v1alpha1.File `json:"file"`
37+
// Resource is the resource descriptor from the component
38+
Resource *v2.Resource `json:"resource"`
39+
}
40+
41+
// GetOCIArtifactSpec is the input specification for the
42+
// GetOCIArtifact transformation.
43+
// +k8s:deepcopy-gen=true
44+
// +ocm:jsonschema-gen=true
45+
type GetOCIArtifactSpec struct {
46+
// Resource is the resource descriptor to get the OCI artifact from.
47+
Resource *v2.Resource `json:"resource"`
48+
// OutputPath is the path where the artifact should be downloaded to.
49+
// If empty, a temporary file will be created.
50+
OutputPath string `json:"outputPath,omitempty"`
51+
}

0 commit comments

Comments
 (0)