Skip to content

Commit

Permalink
feat: add a check for helm chart resources existing before creating h…
Browse files Browse the repository at this point in the history
…elm resources (#413)

## Description

Closes open-component-model/ocm-project#64


## What type of PR is this? (check all applicable)

- [ ] 🍕 Feature
- [ ] 🐛 Bug Fix
- [ ] 📝 Documentation Update
- [ ] 🎨 Style
- [ ] 🧑‍💻 Code Refactor
- [ ] 🔥 Performance Improvements
- [ ] ✅ Test
- [ ] 🤖 Build
- [ ] 🔁 CI
- [ ] 📦 Chore (Release)
- [ ] ⏩ Revert

## Related Tickets & Documents

<!-- 
Please use this format link issue numbers: Fixes #123

https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword
-->
- Related Issue # (issue)
- Closes # (issue)
- Fixes # (issue)
> Remove if not applicable

## Screenshots

<!-- Visual changes require screenshots -->


## Added tests?

- [ ] 👍 yes
- [ ] 🙅 no, because they aren't needed
- [ ] 🙋 no, because I need help
- [ ] Separate ticket for tests # (issue/pr)

Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration


## Added to documentation?

- [ ] 📜 README.md
- [ ] 🙅 no documentation needed

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
  • Loading branch information
Skarlso authored May 3, 2024
1 parent 45e03da commit 4294295
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 3 deletions.
79 changes: 76 additions & 3 deletions controllers/fluxdeployer_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
package controllers

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

"github.com/containers/image/v5/pkg/compression"
helmv1 "github.com/fluxcd/helm-controller/api/v2beta1"
"github.com/mandelsoft/vfs/pkg/osfs"
"github.com/open-component-model/ocm-controller/pkg/cache"
"github.com/open-component-model/ocm-controller/pkg/metrics"
"github.com/open-component-model/ocm-controller/pkg/status"
"github.com/open-component-model/ocm/pkg/utils/tarutils"
mh "github.com/open-component-model/pkg/metrics"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -55,14 +63,13 @@ type FluxDeployerReconciler struct {
DynamicClient dynamic.Interface

CertSecretName string
Cache cache.Cache
}

// +kubebuilder:rbac:groups=delivery.ocm.software,resources=fluxdeployers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=fluxdeployers/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=delivery.ocm.software,resources=fluxdeployers/finalizers,verbs=update

// +kubebuilder:rbac:groups=delivery.ocm.software,resources=snapshots,verbs=get;list;watch;create;update;patch;delete

// +kubebuilder:rbac:groups=source.toolkit.fluxcd.io,resources=ocirepositories;helmrepositories,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=kustomize.toolkit.fluxcd.io,resources=kustomizations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -180,7 +187,7 @@ func (r *FluxDeployerReconciler) reconcile(

// create kustomization
if obj.Spec.KustomizationTemplate != nil {
// create oci registry
// can't check for helm content as we don't know where things are or what content to check for
if err := r.createKustomizationSources(ctx, obj, snapshotURL, snapshot.Spec.Tag); err != nil {
msg := "failed to create kustomization sources"
logger.Error(err, msg)
Expand All @@ -202,6 +209,15 @@ func (r *FluxDeployerReconciler) reconcile(
}

if obj.Spec.HelmReleaseTemplate != nil {
ok, err := r.checkForHelmContent(ctx, obj, snapshot)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to check snapshot content for helm resources: %w", err)
}

if !ok {
return ctrl.Result{}, fmt.Errorf("no helm chart found for helm content")
}

if err := r.createHelmSources(ctx, obj, snapshotURL); err != nil {
msg := "failed to create helm sources"
logger.Error(err, msg)
Expand Down Expand Up @@ -247,6 +263,63 @@ func (r *FluxDeployerReconciler) createKustomizationSources(
return nil
}

func (r *FluxDeployerReconciler) checkForHelmContent(
ctx context.Context,
deployer *v1alpha1.FluxDeployer,
snapshot *v1alpha1.Snapshot,
) (bool, error) {
data, err := r.getSnapshotBytes(ctx, snapshot)
if err != nil {
return false, fmt.Errorf("failed to get snapshot bytes: %w", err)
}

virtualFS, err := osfs.NewTempFileSystem()
if err != nil {
return false, fmt.Errorf("fs error: %w", err)
}
defer func() {
_ = virtualFS.RemoveAll("/")
}()

if err := tarutils.ExtractTarToFs(virtualFS, bytes.NewBuffer(data)); err != nil {
return false, fmt.Errorf("extract tar error: %w", err)
}

if deployer.Spec.HelmReleaseTemplate == nil {
return false, fmt.Errorf("no helm release template")
}

chartName := deployer.Spec.HelmReleaseTemplate.Chart.Spec.Chart

if _, err := virtualFS.Stat(filepath.Join(chartName, "Chart.yaml")); err != nil && !os.IsNotExist(err) {
return false, fmt.Errorf("failed to check for chart yaml: %w", err)
}

return true, nil
}

// This might be problematic if the resource is too large in the snapshot. ReadAll will read it into memory.
func (r *FluxDeployerReconciler) getSnapshotBytes(ctx context.Context, snapshot *v1alpha1.Snapshot) ([]byte, error) {
name, err := ocm.ConstructRepositoryName(snapshot.Spec.Identity)
if err != nil {
return nil, fmt.Errorf("failed to construct name: %w", err)
}

reader, err := r.Cache.FetchDataByDigest(ctx, name, snapshot.Status.LastReconciledDigest)
if err != nil {
return nil, fmt.Errorf("failed to fetch data: %w", err)
}

uncompressed, _, err := compression.AutoDecompress(reader)
if err != nil {
return nil, fmt.Errorf("failed to auto decompress: %w", err)
}
defer uncompressed.Close()

// We don't decompress snapshots because those are archives and are decompressed by the caching layer already.
return io.ReadAll(uncompressed)
}

func (r *FluxDeployerReconciler) createHelmSources(
ctx context.Context,
obj *v1alpha1.FluxDeployer,
Expand Down
117 changes: 117 additions & 0 deletions controllers/fluxdeployer_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package controllers

import (
"context"
"os"
"path/filepath"
"testing"

helmv1 "github.com/fluxcd/helm-controller/api/v2beta1"
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
sourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"

"github.com/open-component-model/ocm-controller/api/v1alpha1"
"github.com/open-component-model/ocm-controller/pkg/cache/fakes"
ocmmetav1 "github.com/open-component-model/ocm/pkg/contexts/ocm/compdesc/meta/v1"
)

func TestFluxDeployerReconcile(t *testing.T) {
resourceV1 := &v1alpha1.Resource{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: "test-resource",
Namespace: "default",
},
Status: v1alpha1.ResourceStatus{
SnapshotName: "test-snapshot",
},
}
deployer := &v1alpha1.FluxDeployer{
ObjectMeta: metav1.ObjectMeta{
Name: "deployer",
Namespace: "default",
},
Spec: v1alpha1.FluxDeployerSpec{
SourceRef: v1alpha1.ObjectReference{
NamespacedObjectKindReference: meta.NamespacedObjectKindReference{
Name: "test-resource",
Namespace: "default",
Kind: "Resource",
},
},
HelmReleaseTemplate: &helmv1.HelmReleaseSpec{
Chart: helmv1.HelmChartTemplate{
Spec: helmv1.HelmChartTemplateSpec{
Chart: "podinfo",
Version: "6.3.5",
},
},
},
},
}
conditions.MarkTrue(deployer, meta.ReadyCondition, meta.SucceededReason, "done")
snapshot := &v1alpha1.Snapshot{
ObjectMeta: metav1.ObjectMeta{
Name: "test-snapshot",
Namespace: "default",
},
Spec: v1alpha1.SnapshotSpec{
Identity: ocmmetav1.Identity{
v1alpha1.ComponentNameKey: "component-name",
v1alpha1.ComponentVersionKey: "v0.0.1",
v1alpha1.ResourceNameKey: "resource-name",
v1alpha1.ResourceVersionKey: "v0.0.5",
v1alpha1.ResourceHelmChartNameKey: "podinfo",
},
Digest: "digest-1",
Tag: "1234",
},
Status: v1alpha1.SnapshotStatus{
LastReconciledDigest: "digest-1",
LastReconciledTag: "1234",
},
}
conditions.MarkTrue(snapshot, meta.ReadyCondition, meta.SucceededReason, "Snapshot with name '%s' is ready", snapshot.Name)

client := env.FakeKubeClient(
WithAddToScheme(helmv1.AddToScheme),
WithAddToScheme(sourcev1beta2.AddToScheme),
WithAddToScheme(kustomizev1.AddToScheme),
WithObjects(snapshot, deployer, resourceV1),
)
fakeCache := &fakes.FakeCache{}
content, err := os.Open(filepath.Join("testdata", "podinfo-6.3.5.tgz"))
require.NoError(t, err)
fakeCache.FetchDataByDigestReturns(content, nil)
recorder := record.NewFakeRecorder(32)
dc := env.FakeDynamicKubeClient(WithObjects(snapshot, deployer, resourceV1))

sr := FluxDeployerReconciler{
Client: client,
Scheme: env.scheme,
EventRecorder: recorder,
RegistryServiceName: "127.0.0.1:5000",
RetryInterval: 0,
DynamicClient: dc,
Cache: fakeCache,
}

result, err := sr.Reconcile(context.Background(), ctrl.Request{
NamespacedName: types.NamespacedName{
Name: deployer.Name,
Namespace: deployer.Namespace,
},
})
require.NoError(t, err)
assert.Equal(t, ctrl.Result{}, result)

close(recorder.Events)
}
Binary file added controllers/testdata/podinfo-6.3.5.tgz
Binary file not shown.
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func setupManagers(
DynamicClient: dynClient,
RegistryServiceName: ociRegistryAddr,
CertSecretName: ociRegistryCertSecretName,
Cache: cache,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FluxDeployer")
os.Exit(1)
Expand Down

0 comments on commit 4294295

Please sign in to comment.