diff --git a/.github/workflows/build_test_ci.yml b/.github/workflows/build_test_ci.yml index 6343cba26..947388ad9 100644 --- a/.github/workflows/build_test_ci.yml +++ b/.github/workflows/build_test_ci.yml @@ -76,7 +76,7 @@ jobs: - name: Docker cache uses: ScribeMD/docker-cache@0.3.7 with: - key: docker-${{ runner.os }}-${{ hashFiles('Makefile') }}} + key: docker-${{ runner.os }}-${{ hashFiles('go.sum') }}} - name: Lint run: make lint @@ -100,6 +100,7 @@ jobs: disable-sudo: true egress-policy: block allowed-endpoints: > + api.linode.com:443 api.github.com:443 github.com:443 gcr.io:443 @@ -130,7 +131,7 @@ jobs: - name: Docker cache uses: ScribeMD/docker-cache@0.3.7 with: - key: docker-${{ runner.os }}-${{ hashFiles('Makefile') }}} + key: docker-${{ runner.os }}-${{ hashFiles('go.sum') }}} - name: E2E test run: make e2etest @@ -173,7 +174,7 @@ jobs: - name: Docker cache uses: ScribeMD/docker-cache@0.3.7 with: - key: docker-${{ runner.os }}-${{ hashFiles('Makefile') }} + key: docker-${{ runner.os }}-${{ hashFiles('go.sum') }} - name: Build the Docker image run: make docker-build diff --git a/Makefile b/Makefile index 702e969b5..f9e5fa62a 100644 --- a/Makefile +++ b/Makefile @@ -89,11 +89,11 @@ test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test -race -timeout 60s ./... -coverprofile cover.out .PHONY: e2etest -e2etest: kind ctlptl tilt kuttl kustomize clusterctl manifests generate +e2etest: kind ctlptl tilt kuttl kustomize clusterctl envsubst manifests generate @echo -n "LINODE_TOKEN=$(LINODE_TOKEN)" > config/default/.env.linode $(CTLPTL) apply -f .tilt/ctlptl-config.yaml $(TILT) ci --timeout 180s -f Tiltfile - $(KUTTL) test --config e2e/kuttl-config.yaml + ROOT_DIR="$(shell pwd)" $(KUTTL) test --config e2e/kuttl-config.yaml ##@ Build @@ -180,6 +180,7 @@ TILT ?= $(LOCALBIN)/tilt KIND ?= $(LOCALBIN)/kind KUTTL ?= $(LOCALBIN)/kuttl ENVTEST ?= $(LOCALBIN)/setup-envtest +ENVSUBST ?= $(LOCALBIN)/envsubst HUSKY ?= $(LOCALBIN)/husky NILAWAY ?= $(LOCALBIN)/nilaway GOVULNC ?= $(LOCALBIN)/govulncheck @@ -192,6 +193,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.13.0 TILT_VERSION ?= 0.33.6 KIND_VERSION ?= 0.20.0 KUTTL_VERSION ?= 0.15.0 +ENVSUBST_VERSION ?= v1.4.2 HUSKY_VERSION ?= v0.2.16 NILAWAY_VERSION ?= latest GOVULNC_VERSION ?= v1.0.1 @@ -251,6 +253,12 @@ envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +.PHONY: envsubst +envsubst: $(ENVSUBST) ## Download envsubst locally if necessary. If wrong version is installed, it will be overwritten. +$(ENVSUBST): $(LOCALBIN) + test -s $(ENVSUBST) || \ + (cd $(LOCALBIN); curl -Lso ./envsubst https://github.com/a8m/envsubst/releases/download/$(ENVSUBST_VERSION)/envsubst-$(shell uname -s)-$(ARCH) && chmod +x envsubst) + .PHONY: husky husky: $(HUSKY) ## Download husky locally if necessary. @echo Execute install command to enable git hooks: ./bin/husky install diff --git a/cloud/scope/cluster.go b/cloud/scope/cluster.go index c61077530..a99557394 100644 --- a/cloud/scope/cluster.go +++ b/cloud/scope/cluster.go @@ -21,13 +21,13 @@ import ( "errors" "fmt" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - - infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" "github.com/linode/linodego" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/patch" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" ) // ClusterScopeParams defines the input parameters used to create a new Scope. diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 8fa75b04c..ab0520063 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -77,6 +77,8 @@ spec: secretKeyRef: name: token key: LINODE_TOKEN + - name: LINODE_API_VERSION + value: v4beta name: manager securityContext: allowPrivilegeEscalation: false diff --git a/controller/linodemachine_controller.go b/controller/linodemachine_controller.go index 5657c2769..5b9c702d7 100644 --- a/controller/linodemachine_controller.go +++ b/controller/linodemachine_controller.go @@ -18,7 +18,6 @@ package controller import ( "context" - b64 "encoding/base64" "errors" "fmt" "net/http" @@ -44,7 +43,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" ) var skippedMachinePhases = map[string]bool{ @@ -94,25 +93,31 @@ type LinodeMachineReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.0/pkg/reconcile +// +//nolint:gocyclo,cyclop // As simple as possible. func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultedLoopTimeout(r.ReconcileTimeout)) defer cancel() log := ctrl.LoggerFrom(ctx).WithName("LinodeMachineReconciler").WithValues("name", req.NamespacedName.String()) - linodeMachine := &infrav1.LinodeMachine{} + linodeMachine := &infrav1alpha1.LinodeMachine{} if err := r.Client.Get(ctx, req.NamespacedName, linodeMachine); err != nil { - log.Error(err, "Failed to fetch Linode machine") + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "Failed to fetch Linode machine") + } - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{}, err } machine, err := kutil.GetOwnerMachine(ctx, r.Client, linodeMachine.ObjectMeta) switch { case err != nil: - log.Error(err, "Failed to fetch owner machine") + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "Failed to fetch owner machine") + } - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{}, err case machine == nil: log.Info("Machine Controller has not yet set OwnerRef, skipping reconciliation") @@ -138,26 +143,39 @@ func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reques log = log.WithValues("Linode machine: ", machine.Name) cluster, err := kutil.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta) - if err != nil { - log.Info("Failed to fetch cluster by label") + switch { + case err != nil: + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "Failed to fetch cluster by label") + } + + return ctrl.Result{}, err + case cluster == nil: + err = errors.New("missing cluster") + + log.Error(err, "Missing cluster") + + return ctrl.Result{}, err + case cluster.Spec.InfrastructureRef == nil: + err = errors.New("missing infrastructure reference") - return ctrl.Result{}, client.IgnoreNotFound(err) - } else if cluster == nil { - log.Info("Failed to find cluster by label") + log.Error(err, "Missing infrastructure reference") - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{}, err } - linodeCluster := &infrav1.LinodeCluster{} + linodeCluster := &infrav1alpha1.LinodeCluster{} linodeClusterKey := client.ObjectKey{ Namespace: linodeMachine.Namespace, Name: cluster.Spec.InfrastructureRef.Name, } if err = r.Client.Get(ctx, linodeClusterKey, linodeCluster); err != nil { - log.Error(err, "Failed to fetch Linode cluster") + if err = client.IgnoreNotFound(err); err != nil { + log.Error(err, "Failed to fetch Linode cluster") + } - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{}, err } machineScope, err := scope.NewMachineScope( @@ -203,7 +221,7 @@ func (r *LinodeMachineReconciler) reconcile( } // Always close the scope when exiting this function so we can persist any LinodeMachine changes. - if patchErr := machineScope.Close(ctx); patchErr != nil && client.IgnoreNotFound(patchErr) != nil { + if patchErr := machineScope.Close(ctx); patchErr != nil && util.IgnoreKubeNotFound(patchErr) != nil { logger.Error(patchErr, "failed to patch LinodeMachine") err = errors.Join(err, patchErr) @@ -278,31 +296,13 @@ func (r *LinodeMachineReconciler) reconcileCreate(ctx context.Context, machineSc linodeInstance = &linodeInstances[0] case 0: - createConfig := linodeMachineSpecToInstanceCreateConfig(machineScope.LinodeMachine.Spec) - if createConfig == nil { - err = errors.New("failed to convert machine spec to create instance config") - - logger.Error(err, "Panic! Struct of LinodeMachineSpec is different than InstanceCreateOptions") - - return nil, err - } - createConfig.Tags = tags - createConfig.SwapSize = util.Pointer(0) - // get the bootstrap data for the Linode instance and set it for create config - bootstrapData, err := machineScope.GetBootstrapData(ctx) + createConfig, err := r.newCreateConfig(ctx, machineScope, tags, logger) if err != nil { - logger.Info("Failed to get bootstrap data", "error", err.Error()) + logger.Error(err, "Failed to create Linode machine create config") return nil, err } - createConfig.Metadata = &linodego.InstanceMetadataOptions{ - UserData: b64.StdEncoding.EncodeToString(bootstrapData), - } - - if createConfig.Label == "" { - createConfig.Label = util.RenderObjectLabel(machineScope.LinodeMachine.UID) - } if machineScope.LinodeCluster.Spec.VPCRef != nil { iface, err := r.getVPCInterfaceConfig(ctx, machineScope, createConfig.Interfaces, logger) @@ -428,7 +428,7 @@ func (r *LinodeMachineReconciler) reconcileDelete(ctx context.Context, logger lo machineScope.LinodeMachine.Spec.ProviderID = nil machineScope.LinodeMachine.Spec.InstanceID = nil - controllerutil.RemoveFinalizer(machineScope.LinodeMachine, infrav1.GroupVersion.String()) + controllerutil.RemoveFinalizer(machineScope.LinodeMachine, infrav1alpha1.GroupVersion.String()) return nil } @@ -436,13 +436,13 @@ func (r *LinodeMachineReconciler) reconcileDelete(ctx context.Context, logger lo // SetupWithManager sets up the controller with the Manager. func (r *LinodeMachineReconciler) SetupWithManager(mgr ctrl.Manager) error { controller, err := ctrl.NewControllerManagedBy(mgr). - For(&infrav1.LinodeMachine{}). + For(&infrav1alpha1.LinodeMachine{}). Watches( &clusterv1.Machine{}, - handler.EnqueueRequestsFromMapFunc(kutil.MachineToInfrastructureMapFunc(infrav1.GroupVersion.WithKind("LinodeMachine"))), + handler.EnqueueRequestsFromMapFunc(kutil.MachineToInfrastructureMapFunc(infrav1alpha1.GroupVersion.WithKind("LinodeMachine"))), ). Watches( - &infrav1.LinodeCluster{}, + &infrav1alpha1.LinodeCluster{}, handler.EnqueueRequestsFromMapFunc(r.linodeClusterToLinodeMachines(mgr.GetLogger())), ). WithEventFilter(predicates.ResourceNotPausedAndHasFilterLabel(mgr.GetLogger(), r.WatchFilterValue)). diff --git a/controller/linodemachine_controller_helpers.go b/controller/linodemachine_controller_helpers.go index 666858cf5..b96b2d375 100644 --- a/controller/linodemachine_controller_helpers.go +++ b/controller/linodemachine_controller_helpers.go @@ -19,13 +19,15 @@ package controller import ( "bytes" "context" + b64 "encoding/base64" "encoding/gob" "errors" "sort" "github.com/go-logr/logr" - infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" + "github.com/google/uuid" "github.com/linode/cluster-api-provider-linode/cloud/scope" + "github.com/linode/cluster-api-provider-linode/util" "github.com/linode/cluster-api-provider-linode/util/reconciler" "github.com/linode/linodego" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -35,8 +37,53 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/handler" + + infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" ) +func (*LinodeMachineReconciler) newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, tags []string, logger logr.Logger) (*linodego.InstanceCreateOptions, error) { + var err error + + createConfig := linodeMachineSpecToInstanceCreateConfig(machineScope.LinodeMachine.Spec) + if createConfig == nil { + err = errors.New("failed to convert machine spec to create instance config") + + logger.Error(err, "Panic! Struct of LinodeMachineSpec is different than InstanceCreateOptions") + + return nil, err + } + createConfig.SwapSize = util.Pointer(0) + + bootstrapData, err := machineScope.GetBootstrapData(ctx) + if err != nil { + logger.Info("Failed to get bootstrap data", "error", err.Error()) + + return nil, err + } + createConfig.Metadata = &linodego.InstanceMetadataOptions{ + UserData: b64.StdEncoding.EncodeToString(bootstrapData), + } + + if createConfig.Tags == nil { + createConfig.Tags = []string{} + } + createConfig.Tags = append(createConfig.Tags, tags...) + + if createConfig.Label == "" { + createConfig.Label = util.RenderObjectLabel(machineScope.LinodeMachine.UID) + } + + if createConfig.Image == "" { + createConfig.Image = reconciler.DefaultMachineControllerLinodeImage + } + + if createConfig.RootPass == "" { + createConfig.RootPass = uuid.NewString() + } + + return createConfig, nil +} + func (r *LinodeMachineReconciler) linodeClusterToLinodeMachines(logger logr.Logger) handler.MapFunc { logger = logger.WithName("LinodeMachineReconciler").WithName("linodeClusterToLinodeMachines") @@ -44,7 +91,7 @@ func (r *LinodeMachineReconciler) linodeClusterToLinodeMachines(logger logr.Logg ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout) defer cancel() - linodeCluster, ok := o.(*infrav1.LinodeCluster) + linodeCluster, ok := o.(*infrav1alpha1.LinodeCluster) if !ok { logger.Info("Failed to cast object to Cluster") @@ -142,7 +189,7 @@ func (r *LinodeMachineReconciler) getVPCInterfaceConfig(ctx context.Context, mac logger = logger.WithValues("vpcName", name, "vpcNamespace", namespace) - linodeVPC := infrav1.LinodeVPC{ + linodeVPC := infrav1alpha1.LinodeVPC{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: name, @@ -202,7 +249,7 @@ func (r *LinodeMachineReconciler) getVPCInterfaceConfig(ctx context.Context, mac }, nil } -func linodeMachineSpecToInstanceCreateConfig(machineSpec infrav1.LinodeMachineSpec) *linodego.InstanceCreateOptions { +func linodeMachineSpecToInstanceCreateConfig(machineSpec infrav1alpha1.LinodeMachineSpec) *linodego.InstanceCreateOptions { var buf bytes.Buffer enc := gob.NewEncoder(&buf) err := enc.Encode(machineSpec) diff --git a/controller/linodemachine_controller_helpers_test.go b/controller/linodemachine_controller_helpers_test.go index 3a00d3a6b..d3ce540f4 100644 --- a/controller/linodemachine_controller_helpers_test.go +++ b/controller/linodemachine_controller_helpers_test.go @@ -5,10 +5,11 @@ import ( "encoding/gob" "testing" - infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" "github.com/linode/linodego" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + infrav1alpha1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" ) func TestLinodeMachineSpecToCreateInstanceConfig(t *testing.T) { @@ -16,7 +17,7 @@ func TestLinodeMachineSpecToCreateInstanceConfig(t *testing.T) { subnetID := 1 - machineSpec := infrav1.LinodeMachineSpec{ + machineSpec := infrav1alpha1.LinodeMachineSpec{ Region: "region", Type: "type", Label: "label", @@ -28,14 +29,14 @@ func TestLinodeMachineSpecToCreateInstanceConfig(t *testing.T) { StackScriptData: map[string]string{"script": "data"}, BackupID: 1, Image: "image", - Interfaces: []infrav1.InstanceConfigInterfaceCreateOptions{ + Interfaces: []infrav1alpha1.InstanceConfigInterfaceCreateOptions{ { IPAMAddress: "address", Label: "label", Purpose: linodego.InterfacePurposePublic, Primary: true, SubnetID: &subnetID, - IPv4: &infrav1.VPCIPv4{ + IPv4: &infrav1alpha1.VPCIPv4{ VPC: "vpc", NAT1To1: "nat11", }, @@ -45,7 +46,7 @@ func TestLinodeMachineSpecToCreateInstanceConfig(t *testing.T) { BackupsEnabled: true, PrivateIP: true, Tags: []string{"tag"}, - Metadata: &infrav1.InstanceMetadataOptions{ + Metadata: &infrav1alpha1.InstanceMetadataOptions{ UserData: "userdata", }, FirewallID: 1, @@ -59,7 +60,7 @@ func TestLinodeMachineSpecToCreateInstanceConfig(t *testing.T) { err := enc.Encode(createConfig) require.NoError(t, err, "Failed to encode InstanceCreateOptions") - var actualMachineSpec infrav1.LinodeMachineSpec + var actualMachineSpec infrav1alpha1.LinodeMachineSpec dec := gob.NewDecoder(&buf) err = dec.Decode(&actualMachineSpec) require.NoError(t, err, "Failed to decode LinodeMachineSpec") diff --git a/controller/linodevpc_controller.go b/controller/linodevpc_controller.go index 8beb96abc..8683f5066 100644 --- a/controller/linodevpc_controller.go +++ b/controller/linodevpc_controller.go @@ -25,7 +25,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/client-go/tools/record" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" "sigs.k8s.io/cluster-api/util/conditions" @@ -121,7 +120,7 @@ func (r *LinodeVPCReconciler) reconcile( } // Always close the scope when exiting this function so we can persist any LinodeMachine changes. - if patchErr := vpcScope.Close(ctx); patchErr != nil && utilerrors.FilterOut(patchErr) != nil { + if patchErr := vpcScope.Close(ctx); patchErr != nil && util.IgnoreKubeNotFound(patchErr) != nil { logger.Error(patchErr, "failed to patch LinodeVPC") err = errors.Join(err, patchErr) diff --git a/e2e/Makefile b/e2e/Makefile new file mode 100644 index 000000000..81ae7d105 --- /dev/null +++ b/e2e/Makefile @@ -0,0 +1,24 @@ +MAKEFLAGS += -s + +runTestCase: + @T="$$(KUBECONFIG="$$ROOT_DIR/kubeconfig" kuttl test --timeout 300 --skip-delete --namespace "$$NAMESPACE" "$$TC" 2>&1)" ;\ + echo "$$T" |\ + grep -v harness.go |\ + grep -v ^=== |\ + grep -v "running without a 'kuttl-test.yaml" |\ + grep -v "creation of user-supplied namespace" |\ + grep -v "not match file name regexp" |\ + grep -v "kutt-test config testdirs is overridden" ;\ + echo "$$T" | grep '^PASS' + +renderTestCase: + @D="$$(mktemp -d)" ;\ + mkdir -p "$$D/step" ;\ + envsubst -i "$$TPL" -o "$$D/step/00-step.yaml" ;\ + echo -n "$$D" + +getKubeUid: + @kubectl get -o jsonpath='{.metadata.uid}' -n "$$NAMESPACE" "$$OBJ" + +callLinodeApiGet: + @curl -s -H "Authorization: Bearer $$LINODE_TOKEN" -H "X-Filter: $$FILTER" "https://api.linode.com/v4beta/$$URI" diff --git a/e2e/README.MD b/e2e/README.MD new file mode 100644 index 000000000..bfa27eb15 --- /dev/null +++ b/e2e/README.MD @@ -0,0 +1,83 @@ +# E2E Framework + +The E2E framework uses Kuttl under the hood. Kuttl is a simple and most importantly static tool. +That means there is no way to dynamically generate manifests. +The solution is KIK (Kuttl-In-Kuttl)! + +## Kuttl-In-Kuttl + +## How does it work + + - Instead of creating a static Kuttl manifest, you can create template. Template extension must be `.yml` otherwise Kuttl starts executing it. + - Template is rendered via `envsubst`, so you can use Bash variables for dynamic data injection. + - Variables always present + - `${KUBECONFIG}` + - `${NAMESPACE}` + - `${ROOT_DIR}` + + Example template +```yaml +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +kind: LinodeMachine +metadata: + ownerReferences: + - apiVersion: cluster.x-k8s.io/v1beta1 + kind: Machine + name: machine-sample + uid: ${MACHINE_UID} + name: linodemachine-sample +spec: + region: us-sea + type: g5-nanode-1 +``` + + - Helper tools are available, please create a Makefile in your test folder. In this way you can use built in helpers without including any script in the test, plus this file is a good place for test specific helpers. + + Example Makefile +```makefile +MAKEFLAGS += -s +include ../../Makefile +``` + + - Template is nothing without a runner, please use helpers provided in Makefile to render and run template. + + Example template runner +```yaml +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: |- + MACHINE_UID="$(OBJ=machines/machine-sample make getKubeUid)" \ + TC="$(TPL="$PWD/02-create-linodemachine.tpl.yml" make -s renderTestCase)" make runTestCase +``` + + - You can run testsuits (which can contain template rendering). + + Example testsuit runner: +```yaml +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: TC="$PWD/testsuit" make runTestCase +``` + + - Test execution order is not guaranteed. If a test depends on another resource it is strongly suggested to create `assert` files to wait for an event. Almost every test depends on CAPI providers, so there is a good chance, you have to create a `00-assert.yaml` in your test folder. + + Example `00-assert.yaml`: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: capi-controller-manager + namespace: capi-system +status: + availableReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-api-provider-linode-controller-manager + namespace: cluster-api-provider-linode-system +status: + availableReplicas: 1 +``` diff --git a/e2e/basic/cluster/00-assert.yaml b/e2e/basic/cluster/00-assert.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/e2e/basic/machine/00-assert.yaml b/e2e/basic/machine/00-assert.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/e2e/basic/vpc/00-assert.yaml b/e2e/basic/vpc/00-assert.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/e2e/kuttl-config.yaml b/e2e/kuttl-config.yaml index b06687408..a9abaa602 100644 --- a/e2e/kuttl-config.yaml +++ b/e2e/kuttl-config.yaml @@ -1,6 +1,7 @@ apiVersion: kuttl.dev/v1beta1 kind: TestSuite testDirs: -- e2e/basic -kindConfig: e2e/kind-config.yaml -timeout: 120 +- e2e/linodecluster-controller +- e2e/linodemachine-controller +- e2e/linodevpc-controller +timeout: 300 diff --git a/e2e/linodecluster-controller/minimal/00-assert.yaml b/e2e/linodecluster-controller/minimal/00-assert.yaml new file mode 100644 index 000000000..4107fd8f0 --- /dev/null +++ b/e2e/linodecluster-controller/minimal/00-assert.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: capi-controller-manager + namespace: capi-system +status: + availableReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-api-provider-linode-controller-manager + namespace: cluster-api-provider-linode-system +status: + availableReplicas: 1 diff --git a/e2e/linodemachine-controller/minimal/00-assert.yaml b/e2e/linodemachine-controller/minimal/00-assert.yaml new file mode 100644 index 000000000..4107fd8f0 --- /dev/null +++ b/e2e/linodemachine-controller/minimal/00-assert.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: capi-controller-manager + namespace: capi-system +status: + availableReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-api-provider-linode-controller-manager + namespace: cluster-api-provider-linode-system +status: + availableReplicas: 1 diff --git a/e2e/linodemachine-controller/minimal/01-assert.yaml b/e2e/linodemachine-controller/minimal/01-assert.yaml new file mode 100644 index 000000000..ca3986171 --- /dev/null +++ b/e2e/linodemachine-controller/minimal/01-assert.yaml @@ -0,0 +1,17 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + annotations: + cluster.x-k8s.io/paused: "true" + name: cluster-sample +spec: + paused: true +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Machine +metadata: + annotations: + cluster.x-k8s.io/paused: "true" + name: machine-sample +spec: + clusterName: cluster-sample diff --git a/e2e/linodemachine-controller/minimal/01-create-cluster.yaml b/e2e/linodemachine-controller/minimal/01-create-cluster.yaml new file mode 100644 index 000000000..c77670b2f --- /dev/null +++ b/e2e/linodemachine-controller/minimal/01-create-cluster.yaml @@ -0,0 +1,39 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +kind: LinodeCluster +metadata: + annotations: + cluster.x-k8s.io/paused: "true" + name: linodecluster-sample +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + annotations: + cluster.x-k8s.io/paused: "true" + name: cluster-sample +spec: + paused: true + infrastructureRef: + name: linodecluster-sample +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Machine +metadata: + annotations: + cluster.x-k8s.io/paused: "true" + name: machine-sample +spec: + clusterName: cluster-sample + bootstrap: + configRef: + apiVersion: v1 + kind: "ConfigMap" + name: "boostrap-sample" + dataSecretName: bootstrap-data-sample +--- +apiVersion: v1 +kind: Secret +metadata: + name: bootstrap-data-sample +data: + value: dG91Y2ggL29rCg== diff --git a/e2e/linodemachine-controller/minimal/02-assert.yaml b/e2e/linodemachine-controller/minimal/02-assert.yaml new file mode 100644 index 000000000..1cb149503 --- /dev/null +++ b/e2e/linodemachine-controller/minimal/02-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +kind: LinodeMachine +metadata: + name: linodemachine-sample +spec: + region: us-sea + type: g5-nanode-1 +status: + ready: true + instanceState: running diff --git a/e2e/linodemachine-controller/minimal/02-create-linodemachine.tpl.yml b/e2e/linodemachine-controller/minimal/02-create-linodemachine.tpl.yml new file mode 100644 index 000000000..0a4b23b4e --- /dev/null +++ b/e2e/linodemachine-controller/minimal/02-create-linodemachine.tpl.yml @@ -0,0 +1,12 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +kind: LinodeMachine +metadata: + ownerReferences: + - apiVersion: cluster.x-k8s.io/v1beta1 + kind: Machine + name: machine-sample + uid: ${MACHINE_UID} + name: linodemachine-sample +spec: + region: us-sea + type: g5-nanode-1 diff --git a/e2e/linodemachine-controller/minimal/02-create-linodemachine.yaml b/e2e/linodemachine-controller/minimal/02-create-linodemachine.yaml new file mode 100644 index 000000000..dd0a76e5e --- /dev/null +++ b/e2e/linodemachine-controller/minimal/02-create-linodemachine.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: |- + MACHINE_UID="$(OBJ=machines/machine-sample make getKubeUid)" \ + TC="$(TPL="$PWD/02-create-linodemachine.tpl.yml" make -s renderTestCase)" make runTestCase diff --git a/e2e/linodemachine-controller/minimal/03-delete-linodemachine.yaml b/e2e/linodemachine-controller/minimal/03-delete-linodemachine.yaml new file mode 100644 index 000000000..3bf5e64ad --- /dev/null +++ b/e2e/linodemachine-controller/minimal/03-delete-linodemachine.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 + kind: LinodeMachine + name: linodemachine-sample diff --git a/e2e/linodemachine-controller/minimal/03-error.yaml b/e2e/linodemachine-controller/minimal/03-error.yaml new file mode 100644 index 000000000..4ff1b308e --- /dev/null +++ b/e2e/linodemachine-controller/minimal/03-error.yaml @@ -0,0 +1,4 @@ +apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 +kind: LinodeMachine +metadata: + name: linodemachine-sample diff --git a/e2e/linodemachine-controller/minimal/04-verify-linode-instance.yaml b/e2e/linodemachine-controller/minimal/04-verify-linode-instance.yaml new file mode 100644 index 000000000..4515f8566 --- /dev/null +++ b/e2e/linodemachine-controller/minimal/04-verify-linode-instance.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: |- + URI="linode/instances" FILTER="{\"tags\":\"$(OBJ=linodemachines/linodemachine-sample make getKubeUid)\"}" make callLinodeApiGet | grep 'results": 0' diff --git a/e2e/linodemachine-controller/minimal/Makefile b/e2e/linodemachine-controller/minimal/Makefile new file mode 100644 index 000000000..3924bfdc1 --- /dev/null +++ b/e2e/linodemachine-controller/minimal/Makefile @@ -0,0 +1 @@ +include ../../Makefile diff --git a/e2e/linodevpc-controller/minimal/00-assert.yaml b/e2e/linodevpc-controller/minimal/00-assert.yaml new file mode 100644 index 000000000..4107fd8f0 --- /dev/null +++ b/e2e/linodevpc-controller/minimal/00-assert.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: capi-controller-manager + namespace: capi-system +status: + availableReplicas: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cluster-api-provider-linode-controller-manager + namespace: cluster-api-provider-linode-system +status: + availableReplicas: 1 diff --git a/go.mod b/go.mod index 7f1e3f59f..910126f15 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.21.5 require ( github.com/go-logr/logr v1.4.1 + github.com/google/uuid v1.3.1 github.com/linode/linodego v1.27.0 github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.31.1 @@ -41,7 +42,6 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.3.1 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/util/helpers.go b/util/helpers.go index 3607960d3..bf059f430 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -7,6 +7,8 @@ import ( "github.com/linode/linodego" "k8s.io/apimachinery/pkg/types" + utilerrors "k8s.io/apimachinery/pkg/util/errors" + "sigs.k8s.io/controller-runtime/pkg/client" ) // Pointer returns the pointer of any type @@ -49,3 +51,20 @@ func IgnoreLinodeAPIError(err error, code int) error { return err } + +// IgnoreKubeNotFound returns the error even if aggregated except not found +func IgnoreKubeNotFound(err error) error { + //nolint:errorlint // This is specific non wrapped error. + errs, ok := err.(utilerrors.Aggregate) + if !ok { + return client.IgnoreNotFound(err) + } + + for _, e := range errs.Errors() { + if client.IgnoreNotFound(e) != nil { + return err + } + } + + return nil +} diff --git a/util/reconciler/defaults.go b/util/reconciler/defaults.go index 5a13bf0e4..db9722be8 100644 --- a/util/reconciler/defaults.go +++ b/util/reconciler/defaults.go @@ -28,6 +28,8 @@ const ( // DefaultMachineControllerWaitForBootstrapDelay is the default requeue delay if bootstrap data is not ready. DefaultMachineControllerWaitForBootstrapDelay = 5 * time.Second + // DefaultMachineControllerLinodeImage default image. + DefaultMachineControllerLinodeImage = "linode/ubuntu22.04" // DefaultMachineControllerWaitForRunningDelay is the default requeue delay if instance is not running. DefaultMachineControllerWaitForRunningDelay = 5 * time.Second // DefaultMachineControllerWaitForRunningTimeout is the default timeout if instance is not running.