Skip to content

Commit

Permalink
Add distribution spec to API
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
  • Loading branch information
stefanprodan committed May 28, 2024
1 parent e92bd36 commit 5e6b789
Show file tree
Hide file tree
Showing 29 changed files with 363 additions and 95 deletions.
31 changes: 30 additions & 1 deletion api/v1alpha1/fluxinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,40 @@ var (
Finalizer = fmt.Sprintf("%s/finalizer", GroupVersion.Group)
ReconcileAnnotation = fmt.Sprintf("%s/reconcile", GroupVersion.Group)
ReconcileEveryAnnotation = fmt.Sprintf("%s/reconcileEvery", GroupVersion.Group)
RevisionAnnotation = fmt.Sprintf("%s/revision", GroupVersion.Group)
)

// FluxInstanceSpec defines the desired state of FluxInstance
type FluxInstanceSpec struct {
// Distribution specifies the version and container registry to pull images from.
// +required
Distribution Distribution `json:"distribution"`

// Kustomize holds a set of patches that can be applied to the
// Flux installation, to customize the way Flux operates.
// +optional
Kustomize *Kustomize `json:"kustomize,omitempty"`
}

// Kustomize specification.
// Distribution specifies the version and container registry to pull images from.
type Distribution struct {
// Version semver expression e.g. '2.x', '2.3.x'.
// +required
Version string `json:"version"`

// Registry address to pull the distribution images from
// e.g. 'ghcr.io/fluxcd'.
// +required
Registry string `json:"registry"`

// ImagePullSecret is the name of the Kubernetes secret
// to use for pulling images.
// +optional
ImagePullSecret string `json:"imagePullSecret,omitempty"`
}

// Kustomize holds a set of patches that can be applied to the
// Flux installation, to customize the way Flux operates.
type Kustomize struct {
// Strategic merge and JSON patches, defined as inline YAML objects,
// capable of targeting objects based on kind, label and annotation selectors.
Expand All @@ -50,8 +73,14 @@ type FluxInstanceStatus struct {
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`

// Conditions contains the readiness conditions of the object.
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`

// LastAttemptedRevision is the version and digest of the
// distribution config that was last attempted to reconcile.
// +optional
LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"`
}

// GetConditions returns the status conditions of the object.
Expand Down
16 changes: 16 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

29 changes: 29 additions & 0 deletions config/crd/bases/fluxcd.controlplane.io_fluxinstances.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,27 @@ spec:
spec:
description: FluxInstanceSpec defines the desired state of FluxInstance
properties:
distribution:
description: Distribution specifies the version and container registry
to pull images from.
properties:
imagePullSecret:
description: |-
ImagePullSecret is the name of the Kubernetes secret
to use for pulling images.
type: string
registry:
description: |-
Registry address to pull the distribution images from
e.g. 'ghcr.io/fluxcd'.
type: string
version:
description: Version semver expression e.g. '2.x', '2.3.x'.
type: string
required:
- registry
- version
type: object
kustomize:
description: |-
Kustomize holds a set of patches that can be applied to the
Expand Down Expand Up @@ -115,13 +136,16 @@ spec:
type: object
type: array
type: object
required:
- distribution
type: object
status:
default:
observedGeneration: -1
description: FluxInstanceStatus defines the observed state of FluxInstance
properties:
conditions:
description: Conditions contains the readiness conditions of the object.
items:
description: "Condition contains details for one aspect of the current
state of this API Resource.\n---\nThis struct is intended for
Expand Down Expand Up @@ -190,6 +214,11 @@ spec:
- type
type: object
type: array
lastAttemptedRevision:
description: |-
LastAttemptedRevision is the version and digest of the
distribution config that was last attempted to reconcile.
type: string
lastHandledReconcileAt:
description: |-
LastHandledReconcileAt holds the value of the most recent
Expand Down
17 changes: 12 additions & 5 deletions config/samples/fluxcd_v1alpha1_fluxinstance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ kind: FluxInstance
metadata:
name: flux
spec:
distribution:
version: "2.x"
registry: "ghcr.io/fluxcd"
kustomize:
patches:
- patch: |
- op: replace
path: /spec/replicas
value: 0
target:
- target:
kind: Deployment
labelSelector: "app.kubernetes.io/component in (kustomize-controller, helm-controller)"
patch: |
- op: add
path: /spec/template/spec/containers/0/args/-
value: --concurrent=10
- op: add
path: /spec/template/spec/containers/0/args/-
value: --requeue-dependency=10s
2 changes: 2 additions & 0 deletions 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/Masterminds/semver/v3 v3.2.1
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
Expand All @@ -11,6 +12,7 @@ require (
github.com/fluxcd/pkg/ssa v0.39.1
github.com/onsi/ginkgo/v2 v2.19.0
github.com/onsi/gomega v1.33.1
github.com/opencontainers/go-digest v1.0.0
github.com/otiai10/copy v1.14.0
github.com/spf13/pflag v1.0.5
k8s.io/api v0.30.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
Expand Down Expand Up @@ -137,6 +139,8 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
Expand Down
40 changes: 14 additions & 26 deletions internal/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import (
"github.com/fluxcd/pkg/kustomize"
"github.com/fluxcd/pkg/ssa"
ssautil "github.com/fluxcd/pkg/ssa/utils"
"github.com/opencontainers/go-digest"
cp "github.com/otiai10/copy"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// Build copies the source directory to a temporary directory, generates the
// required manifests and runs kustomize to build the final resources.
// The function returns a slice of unstructured objects.
func Build(srcDir, tmpDir string, options Options) ([]*unstructured.Unstructured, error) {
func Build(srcDir, tmpDir string, options Options) (*Result, error) {
if err := cp.Copy(srcDir, tmpDir); err != nil {
return nil, err
}
Expand All @@ -46,7 +46,14 @@ func Build(srcDir, tmpDir string, options Options) ([]*unstructured.Unstructured
}
sort.Sort(ssa.SortableUnstructureds(objects))

return objects, nil
d := digest.FromBytes(data)

return &Result{
Version: options.Version,
Objects: objects,
Digest: d.String(),
Revision: fmt.Sprintf("%s@%s", options.Version, d.String()),
}, nil
}

func generate(base string, options Options) error {
Expand All @@ -70,19 +77,15 @@ func generate(base string, options Options) error {
return fmt.Errorf("generate kustomization failed: %w", err)
}

if err := os.MkdirAll(path.Join(base, "roles"), os.ModePerm); err != nil {
return fmt.Errorf("generate roles failed: %w", err)
rbacFile := filepath.Join(base, "roles", "rbac.yaml")
if err := cp.Copy(filepath.Join(base, "rbac.yaml"), rbacFile); err != nil {
return fmt.Errorf("generate rbac failed: %w", err)
}

if err := execTemplate(options, kustomizationRolesTmpl, path.Join(base, "roles/kustomization.yaml")); err != nil {
if err := execTemplate(options, kustomizationRolesTmpl, path.Join(base, "roles", "kustomization.yaml")); err != nil {
return fmt.Errorf("generate roles kustomization failed: %w", err)
}

rbacFile := filepath.Join(base, "roles", "rbac.yaml")
if err := copyFile(filepath.Join(base, "rbac.yaml"), rbacFile); err != nil {
return fmt.Errorf("generate rbac failed: %w", err)
}

// workaround for kustomize not being able to patch the SA in ClusterRoleBindings
defaultNS := MakeDefaultOptions().Namespace
if defaultNS != options.Namespace {
Expand All @@ -97,18 +100,3 @@ func generate(base string, options Options) error {
}
return nil
}

// MkdirTempAbs creates a tmp dir and returns the absolute path to the dir.
// This is required since certain OSes like MacOS create temporary files in
// e.g. `/private/var`, to which `/var` is a symlink.
func MkdirTempAbs(dir, pattern string) (string, error) {
tmpDir, err := os.MkdirTemp(dir, pattern)
if err != nil {
return "", err
}
tmpDir, err = filepath.EvalSymlinks(tmpDir)
if err != nil {
return "", fmt.Errorf("error evaluating symlink: %w", err)
}
return tmpDir, nil
}
17 changes: 9 additions & 8 deletions internal/builder/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

func TestBuild_Defaults(t *testing.T) {
g := NewWithT(t)
const version = "v1.3.0"
const version = "v2.3.0"
options := MakeDefaultOptions()
options.Version = version

Expand All @@ -27,9 +27,9 @@ func TestBuild_Defaults(t *testing.T) {
dstDir, err := testTempDir(t)
g.Expect(err).NotTo(HaveOccurred())

objects, err := Build(srcDir, dstDir, options)
result, err := Build(srcDir, dstDir, options)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(objects).NotTo(BeEmpty())
g.Expect(result.Objects).NotTo(BeEmpty())

if os.Getenv("GEN_GOLDEN") == "true" {
err = cp.Copy(filepath.Join(dstDir, "kustomization.yaml"), goldenFile)
Expand All @@ -47,7 +47,7 @@ func TestBuild_Defaults(t *testing.T) {

func TestBuild_Patches(t *testing.T) {
g := NewWithT(t)
const version = "v1.3.0"
const version = "v2.3.0"
options := MakeDefaultOptions()
options.Version = version

Expand All @@ -74,9 +74,10 @@ func TestBuild_Patches(t *testing.T) {
g.Expect(err).NotTo(HaveOccurred())
options.Patches = string(patchesData)

objects, err := Build(srcDir, dstDir, options)
result, err := Build(srcDir, dstDir, options)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(objects).NotTo(BeEmpty())
g.Expect(result.Objects).NotTo(BeEmpty())
g.Expect(result.Revision).To(HavePrefix(version + "@sha256:"))

if os.Getenv("GEN_GOLDEN") == "true" {
err = cp.Copy(filepath.Join(dstDir, "kustomization.yaml"), goldenFile)
Expand All @@ -92,7 +93,7 @@ func TestBuild_Patches(t *testing.T) {
g.Expect(string(genK)).To(Equal(string(goldenK)))

found := false
for _, obj := range objects {
for _, obj := range result.Objects {
if obj.GetKind() == "Namespace" {
found = true
labels := obj.GetLabels()
Expand All @@ -106,7 +107,7 @@ func TestBuild_Patches(t *testing.T) {

func TestBuild_InvalidPatches(t *testing.T) {
g := NewWithT(t)
const version = "v1.3.0"
const version = "v2.3.0"
options := MakeDefaultOptions()
options.Version = version
srcDir := filepath.Join("testdata", version)
Expand Down
10 changes: 3 additions & 7 deletions internal/builder/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@

package builder

import (
"time"
)

// Options defines the builder configuration.
type Options struct {
Version string
Namespace string
Expand All @@ -18,15 +15,15 @@ type Options struct {
NetworkPolicy bool
LogLevel string
NotificationController string
Timeout time.Duration
ClusterDomain string
TolerationKeys []string
Patches string
}

// MakeDefaultOptions returns the default builder configuration.
func MakeDefaultOptions() Options {
return Options{
Version: "latest",
Version: "*",
Namespace: "flux-system",
Components: []string{
"source-controller",
Expand All @@ -43,7 +40,6 @@ func MakeDefaultOptions() Options {
NetworkPolicy: true,
LogLevel: "info",
NotificationController: "notification-controller",
Timeout: time.Minute,
ClusterDomain: "cluster.local",
}
}
14 changes: 14 additions & 0 deletions internal/builder/result.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 Stefan Prodan.
// SPDX-License-Identifier: AGPL-3.0

package builder

import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

// Result holds the build result.
type Result struct {
Version string
Digest string
Revision string
Objects []*unstructured.Unstructured
}
Loading

0 comments on commit 5e6b789

Please sign in to comment.