Skip to content

Commit 11dc0a3

Browse files
committed
Select layer by OCI media type
Allow specifying the media type of the layer which should be extracted from the OCI artifact. Signed-off-by: Stefan Prodan <[email protected]>
1 parent 02be5de commit 11dc0a3

File tree

6 files changed

+160
-6
lines changed

6 files changed

+160
-6
lines changed

api/v1beta2/ocirepository_types.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ type OCIRepositorySpec struct {
6060
// +optional
6161
Reference *OCIRepositoryRef `json:"ref,omitempty"`
6262

63+
// LayerSelector specifies which layer should be extracted from the OCI artifact.
64+
// When not specified, the first layer found in the artifact is selected.
65+
// +optional
66+
LayerSelector *OCILayerSelector `json:"layerSelector,omitempty"`
67+
6368
// The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.
6469
// When not specified, defaults to 'generic'.
6570
// +kubebuilder:validation:Enum=generic;aws;azure;gcp
@@ -130,6 +135,14 @@ type OCIRepositoryRef struct {
130135
Tag string `json:"tag,omitempty"`
131136
}
132137

138+
// OCILayerSelector specifies which layer should be extracted from an OCI Artifact
139+
type OCILayerSelector struct {
140+
// MediaType specifies the OCI media type of the layer
141+
// which should be extracted from the OCI Artifact.
142+
// +optional
143+
MediaType string `json:"mediaType,omitempty"`
144+
}
145+
133146
// OCIRepositoryVerification verifies the authenticity of an OCI Artifact
134147
type OCIRepositoryVerification struct {
135148
// Provider specifies the technology used to sign the OCI Artifact.
@@ -192,6 +205,15 @@ func (in *OCIRepository) GetArtifact() *Artifact {
192205
return in.Status.Artifact
193206
}
194207

208+
// GetLayerMediaType returns the media type layer selector if found in spec.
209+
func (in *OCIRepository) GetLayerMediaType() string {
210+
if in.Spec.LayerSelector == nil {
211+
return ""
212+
}
213+
214+
return in.Spec.LayerSelector.MediaType
215+
}
216+
195217
// +genclient
196218
// +genclient:Namespaced
197219
// +kubebuilder:storageversion

api/v1beta2/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ spec:
7575
interval:
7676
description: The interval at which to check for image updates.
7777
type: string
78+
layerSelector:
79+
description: LayerSelector specifies which layer should be extracted
80+
from the OCI artifact. When not specified, the first layer found
81+
in the artifact is selected.
82+
properties:
83+
mediaType:
84+
description: MediaType specifies the OCI media type of the layer
85+
which should be extracted from the OCI Artifact.
86+
type: string
87+
type: object
7888
provider:
7989
default: generic
8090
description: The provider used for authentication, can be 'aws', 'azure',

controllers/ocirepository_controller.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/google/go-containerregistry/pkg/authn/k8schain"
3434
"github.com/google/go-containerregistry/pkg/crane"
3535
"github.com/google/go-containerregistry/pkg/name"
36+
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
3637
"github.com/google/go-containerregistry/pkg/v1/remote"
3738
corev1 "k8s.io/api/core/v1"
3839
"k8s.io/apimachinery/pkg/runtime"
@@ -433,7 +434,40 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
433434
return sreconcile.ResultEmpty, e
434435
}
435436

436-
blob, err := layers[0].Compressed()
437+
var layer gcrv1.Layer
438+
439+
switch {
440+
case obj.GetLayerMediaType() != "":
441+
var found bool
442+
for i, l := range layers {
443+
md, err := l.MediaType()
444+
if err != nil {
445+
e := serror.NewGeneric(
446+
fmt.Errorf("failed to determine the media type of layer[%v] from artifact: %w", i, err),
447+
sourcev1.OCILayerOperationFailedReason,
448+
)
449+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
450+
return sreconcile.ResultEmpty, e
451+
}
452+
if string(md) == obj.GetLayerMediaType() {
453+
layer = layers[i]
454+
found = true
455+
break
456+
}
457+
}
458+
if !found {
459+
e := serror.NewGeneric(
460+
fmt.Errorf("failed to find layer with media type '%s' in artifact: %w", obj.GetLayerMediaType(), err),
461+
sourcev1.OCILayerOperationFailedReason,
462+
)
463+
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
464+
return sreconcile.ResultEmpty, e
465+
}
466+
default:
467+
layer = layers[0]
468+
}
469+
470+
blob, err := layer.Compressed()
437471
if err != nil {
438472
e := serror.NewGeneric(
439473
fmt.Errorf("failed to extract the first layer from artifact: %w", err),

controllers/ocirepository_controller_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,15 @@ func TestOCIRepository_Reconcile(t *testing.T) {
8080
tag string
8181
semver string
8282
digest string
83+
mediaType string
8384
assertArtifact []artifactFixture
8485
}{
8586
{
86-
name: "public tag",
87-
url: podinfoVersions["6.1.6"].url,
88-
tag: podinfoVersions["6.1.6"].tag,
89-
digest: podinfoVersions["6.1.6"].digest.Hex,
87+
name: "public tag",
88+
url: podinfoVersions["6.1.6"].url,
89+
tag: podinfoVersions["6.1.6"].tag,
90+
digest: podinfoVersions["6.1.6"].digest.Hex,
91+
mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
9092
assertArtifact: []artifactFixture{
9193
{
9294
expectedPath: "kustomize/deployment.yaml",
@@ -142,7 +144,9 @@ func TestOCIRepository_Reconcile(t *testing.T) {
142144
if tt.semver != "" {
143145
obj.Spec.Reference.SemVer = tt.semver
144146
}
145-
147+
if tt.mediaType != "" {
148+
obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{MediaType: tt.mediaType}
149+
}
146150
g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
147151

148152
key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}

docs/api/source.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,21 @@ defaults to the latest tag.</p>
968968
</tr>
969969
<tr>
970970
<td>
971+
<code>layerSelector</code><br>
972+
<em>
973+
<a href="#source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">
974+
OCILayerSelector
975+
</a>
976+
</em>
977+
</td>
978+
<td>
979+
<em>(Optional)</em>
980+
<p>LayerSelector specifies which layer should be extracted from the OCI artifact.
981+
When not specified, the first layer found in the artifact is selected.</p>
982+
</td>
983+
</tr>
984+
<tr>
985+
<td>
971986
<code>provider</code><br>
972987
<em>
973988
string
@@ -2529,6 +2544,40 @@ string
25292544
</table>
25302545
</div>
25312546
</div>
2547+
<h3 id="source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">OCILayerSelector
2548+
</h3>
2549+
<p>
2550+
(<em>Appears on:</em>
2551+
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>)
2552+
</p>
2553+
<p>OCILayerSelector specifies which layer should be extracted from an OCI Artifact</p>
2554+
<div class="md-typeset__scrollwrap">
2555+
<div class="md-typeset__table">
2556+
<table>
2557+
<thead>
2558+
<tr>
2559+
<th>Field</th>
2560+
<th>Description</th>
2561+
</tr>
2562+
</thead>
2563+
<tbody>
2564+
<tr>
2565+
<td>
2566+
<code>mediaType</code><br>
2567+
<em>
2568+
string
2569+
</em>
2570+
</td>
2571+
<td>
2572+
<em>(Optional)</em>
2573+
<p>MediaType specifies the OCI media type of the layer
2574+
which should be extracted from the OCI Artifact.</p>
2575+
</td>
2576+
</tr>
2577+
</tbody>
2578+
</table>
2579+
</div>
2580+
</div>
25322581
<h3 id="source.toolkit.fluxcd.io/v1beta2.OCIRepositoryRef">OCIRepositoryRef
25332582
</h3>
25342583
<p>
@@ -2634,6 +2683,21 @@ defaults to the latest tag.</p>
26342683
</tr>
26352684
<tr>
26362685
<td>
2686+
<code>layerSelector</code><br>
2687+
<em>
2688+
<a href="#source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">
2689+
OCILayerSelector
2690+
</a>
2691+
</em>
2692+
</td>
2693+
<td>
2694+
<em>(Optional)</em>
2695+
<p>LayerSelector specifies which layer should be extracted from the OCI artifact.
2696+
When not specified, the first layer found in the artifact is selected.</p>
2697+
</td>
2698+
</tr>
2699+
<tr>
2700+
<td>
26372701
<code>provider</code><br>
26382702
<em>
26392703
string

0 commit comments

Comments
 (0)