Skip to content

Commit

Permalink
Init local storage for manifests
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Prodan <[email protected]>
  • Loading branch information
stefanprodan committed May 28, 2024
1 parent 5070ff9 commit e92bd36
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 22 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@

# Go workspace file
go.work
bin/
bin/
data/
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o fl
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/fluxcd-operator .
COPY data/ /data/
USER 65532:65532

ENTRYPOINT ["/fluxcd-operator"]
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ build: manifests generate fmt vet ## Build manager binary.
run: manifests generate fmt vet ## Run a controller from your host.
go run ./cmd/main.go

.PHONY: data
data: ## Download Flux manifests to data dir.
hack/data.sh v2.3.0 v2.2.3

# If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
Expand Down
5 changes: 5 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main
import (
"os"

"github.com/fluxcd/cli-utils/pkg/kstatus/polling"
runtimeCtrl "github.com/fluxcd/pkg/runtime/controller"
"github.com/fluxcd/pkg/runtime/logger"
"github.com/fluxcd/pkg/runtime/pprof"
Expand Down Expand Up @@ -49,11 +50,13 @@ func main() {
enableLeaderElection bool
logOptions logger.Options
rateLimiterOptions runtimeCtrl.RateLimiterOptions
storagePath string
)

flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent kustomize reconciles.")
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&healthAddr, "health-addr", ":8081", "The address the health endpoint binds to.")
flag.StringVar(&storagePath, "storage-path", "/data", "The local storage path.")
flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
Expand Down Expand Up @@ -93,6 +96,8 @@ func main() {
if err = (&controller.FluxInstanceReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
StatusPoller: polling.NewStatusPoller(mgr.GetClient(), mgr.GetRESTMapper(), polling.Options{}),
StoragePath: storagePath,
StatusManager: controllerName,
EventRecorder: mgr.GetEventRecorderFor(controllerName),
}).SetupWithManager(mgr,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/controlplaneio-fluxcd/fluxcd-operator
go 1.22.0

require (
github.com/fluxcd/cli-utils v0.36.0-flux.7
github.com/fluxcd/pkg/apis/kustomize v1.5.0
github.com/fluxcd/pkg/apis/meta v1.5.0
github.com/fluxcd/pkg/kustomize v1.11.0
Expand Down Expand Up @@ -33,7 +34,6 @@ require (
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fluxcd/cli-utils v0.36.0-flux.7 // indirect
github.com/fluxcd/pkg/envsubst v1.1.0 // indirect
github.com/fluxcd/pkg/sourceignore v0.7.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
Expand Down
33 changes: 33 additions & 0 deletions hack/data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash

set -euo pipefail

REPOSITORY_ROOT=$(git rev-parse --show-toplevel)

info() {
echo '[INFO] ' "$@"
}

fatal() {
echo '[ERROR] ' "$@" >&2
exit 1
}

build() {
info "extracting manifests for Flux ${1}"
curl -sLO https://github.com/fluxcd/flux2/releases/download/${1}/manifests.tar.gz
mkdir -p "${REPOSITORY_ROOT}/data/flux/${1}"
tar xzf manifests.tar.gz -C "${REPOSITORY_ROOT}/data/flux/${1}"
rm -rf manifests.tar.gz
}

if ! [ -x "$(command -v flux)" ]; then
fatal 'flux is not installed'
fi

for var in "$@"
do
build "$var"
done

info "all manifests extracted to ${REPOSITORY_ROOT}/data/flux/"
28 changes: 16 additions & 12 deletions internal/builder/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ import (

func TestBuild_Defaults(t *testing.T) {
g := NewWithT(t)
srcDir := filepath.Join("testdata", "v1.3.0")
goldenFile := filepath.Join("testdata", "v1.3.0-golden", "default.kustomization.yaml")
const version = "v1.3.0"
options := MakeDefaultOptions()
options.Version = version

srcDir := filepath.Join("testdata", version)
goldenFile := filepath.Join("testdata", version+"-golden", "default.kustomization.yaml")

dstDir, err := testTempDir(t)
g.Expect(err).NotTo(HaveOccurred())

options := MakeDefaultOptions()
options.Version = "v1.3.0"

objects, err := Build(srcDir, dstDir, options)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(objects).NotTo(BeEmpty())
Expand All @@ -46,14 +47,16 @@ func TestBuild_Defaults(t *testing.T) {

func TestBuild_Patches(t *testing.T) {
g := NewWithT(t)
srcDir := filepath.Join("testdata", "v1.3.0")
goldenFile := filepath.Join("testdata", "v1.3.0-golden", "patches.kustomization.yaml")
const version = "v1.3.0"
options := MakeDefaultOptions()
options.Version = version

srcDir := filepath.Join("testdata", version)
goldenFile := filepath.Join("testdata", version+"-golden", "patches.kustomization.yaml")

dstDir, err := testTempDir(t)
g.Expect(err).NotTo(HaveOccurred())

options := MakeDefaultOptions()
options.Version = "v1.3.0"
patches := []kustomize.Patch{
{
Patch: `
Expand Down Expand Up @@ -103,13 +106,14 @@ func TestBuild_Patches(t *testing.T) {

func TestBuild_InvalidPatches(t *testing.T) {
g := NewWithT(t)
srcDir := filepath.Join("testdata", "v1.3.0")
const version = "v1.3.0"
options := MakeDefaultOptions()
options.Version = version
srcDir := filepath.Join("testdata", version)

dstDir, err := testTempDir(t)
g.Expect(err).NotTo(HaveOccurred())

options := MakeDefaultOptions()
options.Version = "v1.3.0"
patches := []kustomize.Patch{
{
Patch: `
Expand Down
37 changes: 29 additions & 8 deletions internal/controller/fluxinstance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ package controller
import (
"context"
"fmt"
"os"
"time"

"github.com/fluxcd/cli-utils/pkg/kstatus/polling"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/runtime/patch"
Expand All @@ -29,7 +31,9 @@ type FluxInstanceReconciler struct {
kuberecorder.EventRecorder

Scheme *runtime.Scheme
StatusPoller *polling.StatusPoller
StatusManager string
StoragePath string
}

// +kubebuilder:rbac:groups=fluxcd.controlplane.io,resources=fluxinstances,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -93,6 +97,23 @@ func (r *FluxInstanceReconciler) reconcile(ctx context.Context,
}
r.EventRecorder.Event(obj, corev1.EventTypeNormal, meta.ProgressingReason, msg)

// Verify the storage path exists.
if _, err := os.Stat(r.StoragePath); os.IsNotExist(err) {
msg := fmt.Sprintf("Storage path %s does not exist", r.StoragePath)
conditions.MarkFalse(obj,
meta.ReadyCondition,
meta.BuildFailedReason,
msg)
conditions.MarkTrue(obj,
meta.StalledCondition,
meta.BuildFailedReason,
msg)
log.Error(err, msg)
r.EventRecorder.Event(obj, corev1.EventTypeWarning, meta.BuildFailedReason, msg)
return ctrl.Result{}, nil
}

// Mark the object as ready.
msg = fmt.Sprintf("Reconciliation finished in %s", time.Since(reconcileStart).String())
conditions.MarkTrue(obj,
meta.ReadyCondition,
Expand All @@ -101,6 +122,7 @@ func (r *FluxInstanceReconciler) reconcile(ctx context.Context,
log.Info(msg, "generation", obj.GetGeneration())
r.EventRecorder.Event(obj, corev1.EventTypeNormal, meta.ReconciliationSucceededReason, msg)

// Requeue the reconciliation if the interval is set in annotations.
result := ctrl.Result{}
if obj.GetInterval() > 0 {
result.RequeueAfter = obj.GetInterval()
Expand All @@ -120,7 +142,7 @@ func (r *FluxInstanceReconciler) finalize(ctx context.Context,
msg := fmt.Sprintf("Uninstallation completed in %v", time.Since(reconcileStart).String())
log.Info(msg)

// Stop reconciliation as the object is being deleted
// Stop reconciliation as the object is being deleted.
return ctrl.Result{}, nil
}

Expand All @@ -132,12 +154,6 @@ func (r *FluxInstanceReconciler) finalizeStatus(ctx context.Context,
obj.Status.LastHandledReconcileAt = v
}

// Remove the Reconciling condition if the reconciliation was successful.
if conditions.IsTrue(obj, meta.ReadyCondition) {
conditions.Delete(obj, meta.ReconcilingCondition)
obj.Status.ObservedGeneration = obj.Generation
}

// Set the Reconciling reason to ProgressingWithRetry if the
// reconciliation has failed.
if conditions.IsFalse(obj, meta.ReadyCondition) &&
Expand All @@ -147,6 +163,11 @@ func (r *FluxInstanceReconciler) finalizeStatus(ctx context.Context,
conditions.Set(obj, rc)
}

// Remove the Reconciling condition.
if conditions.IsTrue(obj, meta.ReadyCondition) || conditions.IsTrue(obj, meta.StalledCondition) {
conditions.Delete(obj, meta.ReconcilingCondition)
}

// Patch finalizers, status and conditions.
return r.patch(ctx, obj, patcher)
}
Expand All @@ -166,7 +187,7 @@ func (r *FluxInstanceReconciler) patch(ctx context.Context,
patch.WithFieldOwner(r.StatusManager),
}

if conditions.IsTrue(obj, meta.ReadyCondition) {
if conditions.IsTrue(obj, meta.ReadyCondition) || conditions.IsTrue(obj, meta.StalledCondition) {
patchOpts = append(patchOpts, patch.WithStatusObservedGeneration{})
}

Expand Down
67 changes: 67 additions & 0 deletions internal/controller/fluxinstance_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,70 @@ func TestFluxInstanceReconciler_Install(t *testing.T) {
g.Expect(err).To(HaveOccurred())
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
}

func TestFluxInstanceReconciler_InstallFail(t *testing.T) {
g := NewWithT(t)
reconciler := getFluxInstanceReconciler()
reconciler.StoragePath = "notfound"
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

ns, err := testEnv.CreateNamespace(ctx, "test")
g.Expect(err).ToNot(HaveOccurred())

obj := &fluxcdv1alpha1.FluxInstance{
ObjectMeta: metav1.ObjectMeta{
Name: ns.Name,
Namespace: ns.Name,
},
}

err = testEnv.Create(ctx, obj)
g.Expect(err).ToNot(HaveOccurred())

// Initialize the instance.
r, err := reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(r.Requeue).To(BeTrue())

// Try to install the instance.
r, err = reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(r.IsZero()).To(BeTrue())

// Check if the instance was marked as failed.
result := &fluxcdv1alpha1.FluxInstance{}
err = testEnv.Get(ctx, client.ObjectKeyFromObject(obj), result)
g.Expect(err).ToNot(HaveOccurred())

logObjectStatus(t, result)
g.Expect(conditions.IsStalled(result)).To(BeTrue())
g.Expect(conditions.GetReason(result, meta.ReadyCondition)).To(BeIdenticalTo(meta.BuildFailedReason))
g.Expect(conditions.GetMessage(result, meta.ReadyCondition)).To(ContainSubstring("Storage path"))

// Check if events were recorded for each step.
events := getEvents(result.Name)
g.Expect(events).To(HaveLen(2))
g.Expect(events[0].Reason).To(Equal(meta.ProgressingReason))
g.Expect(events[1].Reason).To(Equal(meta.BuildFailedReason))
g.Expect(events[1].Message).To(ContainSubstring(reconciler.StoragePath))

err = testClient.Delete(ctx, obj)
g.Expect(err).ToNot(HaveOccurred())

r, err = reconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: client.ObjectKeyFromObject(obj),
})
g.Expect(err).ToNot(HaveOccurred())
g.Expect(r.IsZero()).To(BeTrue())

// Check if the instance was uninstalled.
result = &fluxcdv1alpha1.FluxInstance{}
err = testEnv.Get(ctx, client.ObjectKeyFromObject(obj), result)
g.Expect(err).To(HaveOccurred())
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
}
3 changes: 3 additions & 0 deletions internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/fluxcd/cli-utils/pkg/kstatus/polling"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
kcheck "github.com/fluxcd/pkg/runtime/conditions/check"
Expand Down Expand Up @@ -83,6 +84,8 @@ func getFluxInstanceReconciler() *FluxInstanceReconciler {
return &FluxInstanceReconciler{
Client: testClient,
Scheme: NewTestScheme(),
StatusPoller: polling.NewStatusPoller(testClient, testEnv.GetRESTMapper(), polling.Options{}),
StoragePath: filepath.Join("..", "..", "data"),
StatusManager: controllerName,
EventRecorder: testEnv.GetEventRecorderFor(controllerName),
}
Expand Down

0 comments on commit e92bd36

Please sign in to comment.