Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC-0003] Select layer by OCI media type #871

Merged
merged 3 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api/v1beta2/ocirepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
stefanprodan marked this conversation as resolved.
Show resolved Hide resolved
// +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.
Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
36 changes: 35 additions & 1 deletion controllers/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand Down
14 changes: 9 additions & 5 deletions controllers/ocirepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
makkes marked this conversation as resolved.
Show resolved Hide resolved
assertArtifact: []artifactFixture{
{
expectedPath: "kustomize/deployment.yaml",
Expand Down Expand Up @@ -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}
Expand Down
64 changes: 64 additions & 0 deletions docs/api/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down