diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..59bc948 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,30 @@ +name-template: 'v$RESOLVED_VERSION' +tag-template: 'v$RESOLVED_VERSION' + +template: | + ## General Changes + + $CHANGES + +categories: +- title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' +- title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml new file mode 100644 index 0000000..411c779 --- /dev/null +++ b/.github/workflows/docker.yaml @@ -0,0 +1,44 @@ +--- +name: Docker Build Action +on: + pull_request: + branches: + - main + release: + types: + - published + push: + branches: + - main + +env: + REGISTRY: ghcr.io + +jobs: + build: + name: Docker Build + runs-on: ubuntu-latest + + steps: + - name: Log in to the container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_REGISTRY_USER }} + password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} + + - name: Checkout + uses: actions/checkout@v4 + + - name: Make tag + run: | + [ "${GITHUB_EVENT_NAME}" == 'pull_request' ] && echo "tag=${GITHUB_HEAD_REF##*/}" >> $GITHUB_ENV || true + [ "${GITHUB_EVENT_NAME}" == 'release' ] && echo "tag=${GITHUB_REF##*/}" >> $GITHUB_ENV || true + [ "${GITHUB_EVENT_NAME}" == 'push' ] && echo "tag=latest" >> $GITHUB_ENV || true + + - name: Build and push image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ env.REGISTRY }}/metal-stack/capms-controller:${{ env.tag }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 58c0360..ae48e5f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,7 +2,11 @@ name: Lint on: push: + branches: + - main pull_request: + branches: + - main jobs: lint: @@ -15,9 +19,8 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '~1.23' + go-version-file: 'go.mod' + cache: false - - name: Run linter + - name: Run Linter uses: golangci/golangci-lint-action@v6 - with: - version: v1.59 diff --git a/.github/workflows/release-drafter.yaml b/.github/workflows/release-drafter.yaml new file mode 100644 index 0000000..44f017a --- /dev/null +++ b/.github/workflows/release-drafter.yaml @@ -0,0 +1,15 @@ +--- +name: Release Drafter Action + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index a86ebdd..e56a6fa 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -2,7 +2,11 @@ name: E2E Tests on: push: + branches: + - main pull_request: + branches: + - main jobs: test-e2e: @@ -15,7 +19,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '~1.23' + go-version-file: 'go.mod' - name: Install the latest version of kind run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5781f67..a88f002 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,11 @@ name: Tests on: push: + branches: + - main pull_request: + branches: + - main jobs: test: @@ -15,7 +19,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: '~1.23' + go-version-file: 'go.mod' - name: Running Tests run: | diff --git a/Makefile b/Makefile index 9843926..b86db1e 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,6 @@ vet: ## Run go vet against code. test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out -# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'. # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. # Prometheus and CertManager are installed by default; skip with: # - PROMETHEUS_INSTALL_SKIP=true @@ -193,7 +192,7 @@ GOLANGCI_LINT = $(LOCALBIN)/golangci-lint KUSTOMIZE_VERSION ?= v5.4.3 CONTROLLER_TOOLS_VERSION ?= v0.16.4 ENVTEST_VERSION ?= release-0.19 -GOLANGCI_LINT_VERSION ?= v1.59.1 +GOLANGCI_LINT_VERSION ?= v1.61.0 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 7ea71be..7f7a13c 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -18,9 +18,9 @@ resources: # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. #- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. -#- ../prometheus +- ../prometheus # [METRICS] Expose the controller manager metrics service. -# - metrics_service.yaml +- metrics_service.yaml # [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. # Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics. # Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will @@ -28,12 +28,12 @@ resources: #- ../network-policy # Uncomment the patches line if you enable Metrics, and/or are using webhooks and cert-manager -# patches: +patches: # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. # More info: https://book.kubebuilder.io/reference/metrics -# - path: manager_metrics_patch.yaml -# target: -# kind: Deployment +- path: manager_metrics_patch.yaml + target: + kind: Deployment # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml diff --git a/internal/controller/metalstackcluster_controller.go b/internal/controller/metalstackcluster_controller.go index a80a538..085d8e7 100644 --- a/internal/controller/metalstackcluster_controller.go +++ b/internal/controller/metalstackcluster_controller.go @@ -388,7 +388,7 @@ func (r *MetalStackClusterReconciler) status(ctx context.Context, infraCluster * conditionUpdates <- func() { if err != nil { - conditions.MarkFalse(infraCluster, v1alpha1.ClusterNodeNetworkEnsured, "InternalError", clusterv1.ConditionSeverityError, err.Error()) + conditions.MarkFalse(infraCluster, v1alpha1.ClusterNodeNetworkEnsured, "InternalError", clusterv1.ConditionSeverityError, "%s", err.Error()) return } @@ -417,7 +417,7 @@ func (r *MetalStackClusterReconciler) status(ctx context.Context, infraCluster * conditionUpdates <- func() { if err != nil && !apierrors.IsNotFound(err) { - conditions.MarkFalse(infraCluster, v1alpha1.ClusterFirewallDeploymentReady, "InternalError", clusterv1.ConditionSeverityError, err.Error()) + conditions.MarkFalse(infraCluster, v1alpha1.ClusterFirewallDeploymentReady, "InternalError", clusterv1.ConditionSeverityError, "%s", err.Error()) return } diff --git a/internal/controller/metalstackmachine_controller.go b/internal/controller/metalstackmachine_controller.go index a8f29fa..a981949 100644 --- a/internal/controller/metalstackmachine_controller.go +++ b/internal/controller/metalstackmachine_controller.go @@ -167,13 +167,13 @@ func (r *MetalStackMachineReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *MetalStackMachineReconciler) create(ctx context.Context, log logr.Logger, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { +func (r *MetalStackMachineReconciler) create(ctx context.Context, _ logr.Logger, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { helper, err := patch.NewHelper(infraMachine, r.Client) if err != nil { return err } - //TODO: Find any existing machine by tag first + // TODO: Find any existing machine by tag first const TagInfraMachineID = "machine.metal-stack.infrastructure.cluster.x-k8s.io/id" infraMachineOwnerTag := fmt.Sprintf("%s=%s", TagInfraMachineID, infraMachine.GetUID()) @@ -218,8 +218,8 @@ func (r *MetalStackMachineReconciler) create(ctx context.Context, log logr.Logge return nil } -func (r *MetalStackMachineReconciler) reconcile(ctx context.Context, log logr.Logger, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { - _, err := r.findProviderMachine(ctx, infraMachine, infraCluster) +func (r *MetalStackMachineReconciler) reconcile(ctx context.Context, _ logr.Logger, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { + err := r.findProviderMachine(ctx, infraMachine, infraCluster) if err != nil && !errors.Is(err, errProviderMachineNotFound) { return err } @@ -231,7 +231,7 @@ func (r *MetalStackMachineReconciler) reconcile(ctx context.Context, log logr.Lo } func (r *MetalStackMachineReconciler) delete(ctx context.Context, log logr.Logger, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { - _, err := r.findProviderMachine(ctx, infraMachine, infraCluster) + err := r.findProviderMachine(ctx, infraMachine, infraCluster) if errors.Is(err, errProviderMachineNotFound) { // metal-stack machine already freed return nil @@ -248,27 +248,29 @@ func (r *MetalStackMachineReconciler) delete(ctx context.Context, log logr.Logge return nil } -func (r *MetalStackMachineReconciler) status(ctx context.Context, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { +func (r *MetalStackMachineReconciler) status(_ context.Context, _ *v1alpha1.MetalStackMachine, _ *v1alpha1.MetalStackCluster) error { return nil } -func (r *MetalStackMachineReconciler) findProviderMachine(ctx context.Context, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) (*models.V1MachineResponse, error) { +func (r *MetalStackMachineReconciler) findProviderMachine(ctx context.Context, infraMachine *v1alpha1.MetalStackMachine, infraCluster *v1alpha1.MetalStackCluster) error { mfr := &models.V1MachineFindRequest{ ID: infraMachine.Spec.ProviderID, AllocationProject: infraCluster.Spec.ProjectID, Tags: []string{fmt.Sprintf("%s%s", tag.ClusterID, infraCluster.GetUID())}, } + resp, err := r.MetalClient.Machine().FindMachines(metalmachine.NewFindMachinesParamsWithContext(ctx).WithBody(mfr), nil) if err != nil { - return nil, err + return err } + switch len(resp.Payload) { case 0: // metal-stack machine already freed - return nil, errProviderMachineNotFound + return errProviderMachineNotFound case 1: - return resp.Payload[0], nil + return nil default: - return nil, errProviderMachineTooManyFound + return errProviderMachineTooManyFound } } diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 9973392..8b1610d 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -43,7 +43,7 @@ var ( // projectImage is the name of the image which will be build and loaded // with the code source changes to be tested. - projectImage = "example.com/cluster-api-provider-metal-stack:v0.0.1" + projectImage = "capms-controller:latest" ) // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index e5d99ea..8496c44 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -31,16 +31,16 @@ import ( ) // namespace where the project is deployed in -const namespace = "cluster-api-provider-metal-stack-system" +const namespace = "capms-system" // serviceAccountName created for the project -const serviceAccountName = "cluster-api-provider-metal-stack-controller-manager" +const serviceAccountName = "capms-controller-manager" // metricsServiceName is the name of the metrics service of the project -const metricsServiceName = "cluster-api-provider-metal-stack-controller-manager-metrics-service" +const metricsServiceName = "capms-controller-manager-metrics-service" // metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data -const metricsRoleBindingName = "cluster-api-provider-metal-stack-metrics-binding" +const metricsRoleBindingName = "capms-metrics-binding" var _ = Describe("Manager", Ordered, func() { var controllerPodName string @@ -68,7 +68,11 @@ var _ = Describe("Manager", Ordered, func() { // and deleting the namespace. AfterAll(func() { By("cleaning up the curl pod for metrics") - cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace) + cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", "kube-system") + _, _ = utils.Run(cmd) + + By("cleaning up metrics-server cluster role binding") + cmd = exec.Command("kubectl", "delete", "clusterrolebinding", metricsRoleBindingName) _, _ = utils.Run(cmd) By("undeploying the controller-manager") @@ -93,27 +97,27 @@ var _ = Describe("Manager", Ordered, func() { cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) controllerLogs, err := utils.Run(cmd) if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, fmt.Sprintf("Controller logs:\n %s", controllerLogs)) + _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) } else { - _, _ = fmt.Fprintf(GinkgoWriter, fmt.Sprintf("Failed to get Controller logs: %s", err)) + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err) } By("Fetching Kubernetes events") cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp") eventsOutput, err := utils.Run(cmd) if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, fmt.Sprintf("Kubernetes events:\n%s", eventsOutput)) + _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput) } else { - _, _ = fmt.Fprintf(GinkgoWriter, fmt.Sprintf("Failed to get Kubernetes events: %s", err)) + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err) } By("Fetching curl-metrics logs") - cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) + cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", "kube-system") metricsOutput, err := utils.Run(cmd) if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, fmt.Sprintf("Metrics logs:\n %s", metricsOutput)) + _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput) } else { - _, _ = fmt.Fprintf(GinkgoWriter, fmt.Sprintf("Failed to get curl-metrics logs: %s", err)) + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err) } By("Fetching controller manager pod description") @@ -166,7 +170,7 @@ var _ = Describe("Manager", Ordered, func() { It("should ensure the metrics endpoint is serving metrics", func() { By("creating a ClusterRoleBinding for the service account to allow access to metrics") cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName, - "--clusterrole=cluster-api-provider-metal-stack-metrics-reader", + "--clusterrole=capms-metrics-reader", fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName), ) _, err := utils.Run(cmd) @@ -208,7 +212,7 @@ var _ = Describe("Manager", Ordered, func() { By("creating the curl-metrics pod to access the metrics endpoint") cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never", - "--namespace", namespace, + "--namespace", "kube-system", "--image=curlimages/curl:7.78.0", "--", "/bin/sh", "-c", fmt.Sprintf( "curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics", @@ -220,7 +224,7 @@ var _ = Describe("Manager", Ordered, func() { verifyCurlUp := func(g Gomega) { cmd := exec.Command("kubectl", "get", "pods", "curl-metrics", "-o", "jsonpath={.status.phase}", - "-n", namespace) + "-n", "kube-system") output, err := utils.Run(cmd) g.Expect(err).NotTo(HaveOccurred()) g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status") @@ -278,7 +282,7 @@ func serviceAccountToken() (string, error) { // Parse the JSON output to extract the token var token tokenRequest - err = json.Unmarshal([]byte(output), &token) + err = json.Unmarshal(output, &token) g.Expect(err).NotTo(HaveOccurred()) out = token.Status.Token @@ -291,7 +295,7 @@ func serviceAccountToken() (string, error) { // getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint. func getMetricsOutput() string { By("getting the curl-metrics logs") - cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) + cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", "kube-system") metricsOutput, err := utils.Run(cmd) Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod") Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK")) diff --git a/test/utils/utils.go b/test/utils/utils.go index c3d51ce..8319bc4 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -92,7 +92,7 @@ func IsPrometheusCRDsInstalled() bool { if err != nil { return false } - crdList := GetNonEmptyLines(string(output)) + crdList := GetNonEmptyLines(output) for _, crd := range prometheusCRDs { for _, line := range crdList { if strings.Contains(line, crd) { @@ -153,7 +153,7 @@ func IsCertManagerCRDsInstalled() bool { } // Check if any of the Cert Manager CRDs are present - crdList := GetNonEmptyLines(string(output)) + crdList := GetNonEmptyLines(output) for _, crd := range certManagerCRDs { for _, line := range crdList { if strings.Contains(line, crd) {