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 <stefan.prodan@gmail.com>
This commit is contained in:
parent
02be5deed7
commit
11dc0a3bc7
|
@ -60,6 +60,11 @@ type OCIRepositorySpec struct {
|
|||
// +optional
|
||||
Reference *OCIRepositoryRef `json:"ref,omitempty"`
|
||||
|
||||
// LayerSelector specifies which layer should be extracted from the OCI artifact.
|
||||
// When not specified, the first layer found in the artifact is selected.
|
||||
// +optional
|
||||
LayerSelector *OCILayerSelector `json:"layerSelector,omitempty"`
|
||||
|
||||
// The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.
|
||||
// When not specified, defaults to 'generic'.
|
||||
// +kubebuilder:validation:Enum=generic;aws;azure;gcp
|
||||
|
@ -130,6 +135,14 @@ type OCIRepositoryRef struct {
|
|||
Tag string `json:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// OCILayerSelector specifies which layer should be extracted from an OCI Artifact
|
||||
type OCILayerSelector struct {
|
||||
// MediaType specifies the OCI media type of the layer
|
||||
// which should be extracted from the OCI Artifact.
|
||||
// +optional
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
}
|
||||
|
||||
// OCIRepositoryVerification verifies the authenticity of an OCI Artifact
|
||||
type OCIRepositoryVerification struct {
|
||||
// Provider specifies the technology used to sign the OCI Artifact.
|
||||
|
@ -192,6 +205,15 @@ func (in *OCIRepository) GetArtifact() *Artifact {
|
|||
return in.Status.Artifact
|
||||
}
|
||||
|
||||
// GetLayerMediaType returns the media type layer selector if found in spec.
|
||||
func (in *OCIRepository) GetLayerMediaType() string {
|
||||
if in.Spec.LayerSelector == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return in.Spec.LayerSelector.MediaType
|
||||
}
|
||||
|
||||
// +genclient
|
||||
// +genclient:Namespaced
|
||||
// +kubebuilder:storageversion
|
||||
|
|
|
@ -622,6 +622,21 @@ func (in *LocalHelmChartSourceReference) DeepCopy() *LocalHelmChartSourceReferen
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OCILayerSelector) DeepCopyInto(out *OCILayerSelector) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCILayerSelector.
|
||||
func (in *OCILayerSelector) DeepCopy() *OCILayerSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OCILayerSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OCIRepository) DeepCopyInto(out *OCIRepository) {
|
||||
*out = *in
|
||||
|
@ -704,6 +719,11 @@ func (in *OCIRepositorySpec) DeepCopyInto(out *OCIRepositorySpec) {
|
|||
*out = new(OCIRepositoryRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.LayerSelector != nil {
|
||||
in, out := &in.LayerSelector, &out.LayerSelector
|
||||
*out = new(OCILayerSelector)
|
||||
**out = **in
|
||||
}
|
||||
if in.SecretRef != nil {
|
||||
in, out := &in.SecretRef, &out.SecretRef
|
||||
*out = new(meta.LocalObjectReference)
|
||||
|
|
|
@ -75,6 +75,16 @@ spec:
|
|||
interval:
|
||||
description: The interval at which to check for image updates.
|
||||
type: string
|
||||
layerSelector:
|
||||
description: LayerSelector specifies which layer should be extracted
|
||||
from the OCI artifact. When not specified, the first layer found
|
||||
in the artifact is selected.
|
||||
properties:
|
||||
mediaType:
|
||||
description: MediaType specifies the OCI media type of the layer
|
||||
which should be extracted from the OCI Artifact.
|
||||
type: string
|
||||
type: object
|
||||
provider:
|
||||
default: generic
|
||||
description: The provider used for authentication, can be 'aws', 'azure',
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/google/go-containerregistry/pkg/authn/k8schain"
|
||||
"github.com/google/go-containerregistry/pkg/crane"
|
||||
"github.com/google/go-containerregistry/pkg/name"
|
||||
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -433,7 +434,40 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
|
|||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
|
||||
blob, err := layers[0].Compressed()
|
||||
var layer gcrv1.Layer
|
||||
|
||||
switch {
|
||||
case obj.GetLayerMediaType() != "":
|
||||
var found bool
|
||||
for i, l := range layers {
|
||||
md, err := l.MediaType()
|
||||
if err != nil {
|
||||
e := serror.NewGeneric(
|
||||
fmt.Errorf("failed to determine the media type of layer[%v] from artifact: %w", i, err),
|
||||
sourcev1.OCILayerOperationFailedReason,
|
||||
)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
if string(md) == obj.GetLayerMediaType() {
|
||||
layer = layers[i]
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
e := serror.NewGeneric(
|
||||
fmt.Errorf("failed to find layer with media type '%s' in artifact: %w", obj.GetLayerMediaType(), err),
|
||||
sourcev1.OCILayerOperationFailedReason,
|
||||
)
|
||||
conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, e.Err.Error())
|
||||
return sreconcile.ResultEmpty, e
|
||||
}
|
||||
default:
|
||||
layer = layers[0]
|
||||
}
|
||||
|
||||
blob, err := layer.Compressed()
|
||||
if err != nil {
|
||||
e := serror.NewGeneric(
|
||||
fmt.Errorf("failed to extract the first layer from artifact: %w", err),
|
||||
|
|
|
@ -80,13 +80,15 @@ func TestOCIRepository_Reconcile(t *testing.T) {
|
|||
tag string
|
||||
semver string
|
||||
digest string
|
||||
mediaType string
|
||||
assertArtifact []artifactFixture
|
||||
}{
|
||||
{
|
||||
name: "public tag",
|
||||
url: podinfoVersions["6.1.6"].url,
|
||||
tag: podinfoVersions["6.1.6"].tag,
|
||||
digest: podinfoVersions["6.1.6"].digest.Hex,
|
||||
name: "public tag",
|
||||
url: podinfoVersions["6.1.6"].url,
|
||||
tag: podinfoVersions["6.1.6"].tag,
|
||||
digest: podinfoVersions["6.1.6"].digest.Hex,
|
||||
mediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
assertArtifact: []artifactFixture{
|
||||
{
|
||||
expectedPath: "kustomize/deployment.yaml",
|
||||
|
@ -142,7 +144,9 @@ func TestOCIRepository_Reconcile(t *testing.T) {
|
|||
if tt.semver != "" {
|
||||
obj.Spec.Reference.SemVer = tt.semver
|
||||
}
|
||||
|
||||
if tt.mediaType != "" {
|
||||
obj.Spec.LayerSelector = &sourcev1.OCILayerSelector{MediaType: tt.mediaType}
|
||||
}
|
||||
g.Expect(testEnv.Create(ctx, obj)).To(Succeed())
|
||||
|
||||
key := client.ObjectKey{Name: obj.Name, Namespace: obj.Namespace}
|
||||
|
|
|
@ -968,6 +968,21 @@ defaults to the latest tag.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>layerSelector</code><br>
|
||||
<em>
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">
|
||||
OCILayerSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LayerSelector specifies which layer should be extracted from the OCI artifact.
|
||||
When not specified, the first layer found in the artifact is selected.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>provider</code><br>
|
||||
<em>
|
||||
string
|
||||
|
@ -2529,6 +2544,40 @@ string
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">OCILayerSelector
|
||||
</h3>
|
||||
<p>
|
||||
(<em>Appears on:</em>
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>)
|
||||
</p>
|
||||
<p>OCILayerSelector specifies which layer should be extracted from an OCI Artifact</p>
|
||||
<div class="md-typeset__scrollwrap">
|
||||
<div class="md-typeset__table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>mediaType</code><br>
|
||||
<em>
|
||||
string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>MediaType specifies the OCI media type of the layer
|
||||
which should be extracted from the OCI Artifact.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="source.toolkit.fluxcd.io/v1beta2.OCIRepositoryRef">OCIRepositoryRef
|
||||
</h3>
|
||||
<p>
|
||||
|
@ -2634,6 +2683,21 @@ defaults to the latest tag.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>layerSelector</code><br>
|
||||
<em>
|
||||
<a href="#source.toolkit.fluxcd.io/v1beta2.OCILayerSelector">
|
||||
OCILayerSelector
|
||||
</a>
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>LayerSelector specifies which layer should be extracted from the OCI artifact.
|
||||
When not specified, the first layer found in the artifact is selected.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>provider</code><br>
|
||||
<em>
|
||||
string
|
||||
|
|
Loading…
Reference in New Issue