diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6b90713..e11a679 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,14 @@ version: 2 updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "gomod" + directory: "/" schedule: interval: "daily" - versioning-strategy: "lockfile-only" - allowed_updates: - - match: - dependency_type: "all" - update_type: "semver:patch" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..2a8d19b --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,66 @@ +categories: + - title: '⚠️ Breaking changes' + labels: + - 'kind/major' + - 'kind/breaking-change' + - title: '🚀 Features' + labels: + - 'kind/enhancement' + - 'kind/feature' + - title: '🐛 Bug Fixes' + labels: + - 'kind/bug' + - title: '🧰 Maintenance' + labels: + - 'kind/chore' + - 'area/dependencies' + +exclude-labels: + - duplicate + - invalid + - later + - wontfix + - kind/question + - release/skip-changelog + +change-template: '- $TITLE (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +name-template: 'v$RESOLVED_VERSION' +template: | + $CHANGES + +autolabeler: + # Tag any PR with "!" in the subject as major update. In other words, breaking change + - label: 'kind/breaking-change' + title: '/.*!:.*/' + - label: 'area/dependencies' + title: 'chore(deps)' + - label: 'area/dependencies' + title: 'fix(deps)' + - label: 'area/dependencies' + title: 'build(deps)' + - label: 'kind/feature' + title: 'feat' + - label: 'kind/bug' + title: 'fix' + - label: 'kind/chore' + title: 'chore' + +version-resolver: + major: + labels: + - 'kind/major' + - 'kind/breaking-change' + minor: + labels: + - 'kind/minor' + - 'kind/feature' + - 'kind/enhancement' + patch: + labels: + - 'kind/patch' + - 'kind/fix' + - 'kind/bug' + - 'kind/chore' + - 'area/dependencies' + default: patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e58ff68 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: CI + +on: + workflow_call: + push: + pull_request: + +# Declare default permissions as read only. +permissions: read-all + +jobs: + unit_tests: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: "1.21" + - run: make test + + golangci: + name: Golangci-lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4.1.0 + with: + go-version: "1.21" + - name: golangci-lint + uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc # v3.7.0 + with: + version: v1.54.2 diff --git a/.github/workflows/container-build.yml b/.github/workflows/container-build.yml new file mode 100644 index 0000000..f874703 --- /dev/null +++ b/.github/workflows/container-build.yml @@ -0,0 +1,39 @@ +name: Build container image, sign it, and generate SBOMs + +on: + workflow_call: + outputs: + digest: + description: "Container image digest" + value: ${{jobs.build.outputs.digest}} + + push: + branches: + - "main" + - "feat-**" + +jobs: + build: + uses: ./.github/workflows/container-image.yml + permissions: + packages: write + with: + push-image: true + + sign: + needs: build + uses: ./.github/workflows/sign-image.yml + permissions: + packages: write + id-token: write + with: + image-digest: ${{ needs.build.outputs.digest }} + + sbom: + needs: build + uses: ./.github/workflows/sbom.yml + permissions: + packages: write + id-token: write + with: + image-digest: ${{ needs.build.outputs.digest }} diff --git a/.github/workflows/container-image.yml b/.github/workflows/container-image.yml new file mode 100644 index 0000000..178d506 --- /dev/null +++ b/.github/workflows/container-image.yml @@ -0,0 +1,72 @@ +name: Build container image + +on: + workflow_call: + inputs: + push-image: + type: boolean + required: true + outputs: + repository: + description: "Repository used to build the container image" + value: ${{ jobs.build.outputs.repository }} + tag: + description: "Tag used to build the container image" + value: ${{ jobs.build.outputs.tag }} + digest: + description: "Image digest" + value: ${{ jobs.build.outputs.digest }} + +jobs: + build: + name: Build container image + permissions: + packages: write + runs-on: ubuntu-latest + outputs: + repository: ${{ steps.setoutput.outputs.repository }} + tag: ${{ steps.setoutput.outputs.tag }} + artifact: ${{ steps.setoutput.outputs.artifact }} + digest: ${{ steps.setoutput.outputs.digest }} + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Set up QEMU + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + - name: Login to GitHub Container Registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Retrieve tag name (main branch) + if: ${{ startsWith(github.ref, 'refs/heads/main') }} + run: | + echo TAG_NAME=latest >> $GITHUB_ENV + - name: Retrieve tag name (feat branch) + if: ${{ startsWith(github.ref, 'refs/heads/feat') }} + run: | + echo "TAG_NAME=latest-$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV + - name: Retrieve tag name (tag) + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: | + echo TAG_NAME=$(echo $GITHUB_REF | sed -e "s|refs/tags/||") >> $GITHUB_ENV + - name: Build and push container image + if: ${{ inputs.push-image }} + id: build-image + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64, linux/arm64 + push: true + tags: | + ghcr.io/${{github.repository_owner}}/kwasm-operator:${{ env.TAG_NAME }} + - id: setoutput + name: Set output parameters + run: | + echo "repository=ghcr.io/${{github.repository_owner}}/kwasm-operator" >> $GITHUB_OUTPUT + echo "tag=${{ env.TAG_NAME }}" >> $GITHUB_OUTPUT + echo "digest=${{ steps.build-image.outputs.digest }}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml deleted file mode 100644 index ec9a1cf..0000000 --- a/.github/workflows/docker-build-push.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Docker Image CI - -on: - push: - branches: [ main ] - tags: [ '*' ] - pull_request: - branches: [ main ] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - buildx: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v1 - - - name: Log in to the Container registry - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@v2 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - file: Dockerfile - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Configure Git - run: | - git config user.name "$GITHUB_ACTOR" - git config user.email "$GITHUB_ACTOR@users.noreply.github.com" - - - name: Install Helm - uses: azure/setup-helm@v3 - with: - version: v3.10.0 - - - name: Run chart-releaser - if: github.ref == 'refs/heads/main' - uses: helm/chart-releaser-action@v1.4.1 - env: - CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/helm-chart-release.yml b/.github/workflows/helm-chart-release.yml new file mode 100644 index 0000000..f9cb9d3 --- /dev/null +++ b/.github/workflows/helm-chart-release.yml @@ -0,0 +1,42 @@ +# This action releases the kwasm-operator helm chart +# The action must run on each commit done against main, however +# a new release will be performed **only** when a change occurs inside +# of the `charts` directory. +name: Release helm chart + +on: + push: + branches: + - main + +jobs: + release: + runs-on: ubuntu-latest + + permissions: + id-token: write + packages: write + contents: write + + steps: + - name: Checkout + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3.5 + with: + version: v3.10.0 + + - name: Run chart-releaser + if: github.ref == 'refs/heads/main' + uses: helm/chart-releaser-action@a917fd15b20e8b64b94d9158ad54cd6345335584 # v1.6.0 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + CR_RELEASE_NAME_TEMPLATE: "{{ .Name }}-chart-{{ .Version }}" diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..694ac00 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,37 @@ +name: Release Drafter + +on: + workflow_dispatch: + push: + # branches to consider in the event; optional, defaults to all + branches: + - main + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize, edited] + # pull_request_target event is required for autolabeler to support PRs from forks + pull_request_target: + types: [opened, reopened, synchronize, edited] + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@09c613e259eb8d4e7c81c2cb00618eb5fc4575a7 # v5.25.0 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3aede1a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,125 @@ +name: kwasm-operator release +on: + push: + tags: + - "v*" + +# Declare default permissions as read only. +permissions: read-all + +jobs: + ci: + uses: ./.github/workflows/ci.yml + permissions: read-all + + build: + name: Build container image, sign it, and generate SBOMs + uses: ./.github/workflows/container-build.yml + permissions: + id-token: write + packages: write + + release: + name: Create release + + needs: + - ci + - build + + permissions: + contents: write + + runs-on: ubuntu-latest + + steps: + - name: Retrieve tag name + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: | + echo TAG_NAME=$(echo ${{ github.ref_name }}) >> $GITHUB_ENV + + - name: Get latest release tag + id: get_last_release_tag + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + script: | + let release = await github.rest.repos.getLatestRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + + if (release.status === 200 ) { + core.setOutput('old_release_tag', release.data.tag_name) + return + } + core.setFailed("Cannot find latest release") + + - name: Get release ID from the release created by release drafter + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + script: | + let releases = await github.rest.repos.listReleases({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + for (const release of releases.data) { + if (release.draft) { + core.info(release) + core.exportVariable('RELEASE_ID', release.id) + return + } + } + core.setFailed(`Draft release not found`) + + - name: Download SBOM artifact + uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + with: + name: sbom + + - name: Display structure of downloaded files + run: ls -R + + - name: Upload release assets + id: upload_release_assets + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + script: | + let fs = require('fs'); + let path = require('path'); + + let files = [ + 'kwasm-operator-sbom-amd64.spdx', + 'kwasm-operator-sbom-amd64.spdx.cert', + 'kwasm-operator-sbom-amd64.spdx.sig', + 'kwasm-operator-sbom-arm64.spdx', + 'kwasm-operator-sbom-arm64.spdx.cert', + 'kwasm-operator-sbom-arm64.spdx.sig'] + const {RELEASE_ID} = process.env + + for (const file of files) { + let file_data = fs.readFileSync(file); + + let response = await github.rest.repos.uploadReleaseAsset({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: `${RELEASE_ID}`, + name: path.basename(file), + data: file_data, + }); + } + + - name: Publish release + uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1 + with: + script: | + const {RELEASE_ID} = process.env + const {TAG_NAME} = process.env + github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: `${RELEASE_ID}`, + draft: false, + tag_name: `${TAG_NAME}`, + name: `${TAG_NAME}`, + prerelease: `${{ contains(github.event.workflow_run.head_branch, '-alpha') || contains(github.event.workflow_run.head_branch, '-beta') || contains(github.event.workflow_run.head_branch, '-rc') }}`, + make_latest: `${{ !(contains(github.event.workflow_run.head_branch, '-alpha') || contains(github.event.workflow_run.head_branch, '-beta') || contains(github.event.workflow_run.head_branch, '-rc')) }}` + }); diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 0000000..c0f0a35 --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,83 @@ +name: Generate SBOMs + +on: + workflow_call: + inputs: + image-digest: + type: string + required: true + +jobs: + sbom: + name: Generate SBOM, sign and attach them to OCI image + strategy: + matrix: + arch: [amd64, arm64] + + permissions: + packages: write + id-token: write + + runs-on: ubuntu-latest + steps: + - name: Install cosign + uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2 + + - name: Install the syft command + uses: anchore/sbom-action/download-syft@fd74a6fb98a204a1ad35bbfae0122c1a302ff88b # v0.15.0 + + - name: Install the crane command + uses: IAreKyleW00t/crane-installer@51d8c3cde789d00a49bcb1aaaf55c5f55fe60674 # v1.3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Find platform digest + shell: bash + run: | + set -e + DIGEST=$(crane digest \ + --platform "linux/${{ matrix.arch }}" \ + ghcr.io/${{ github.repository_owner }}/kwasm-operator@${{ inputs.image-digest }}) + echo "PLATFORM_DIGEST=${DIGEST}" >> "$GITHUB_ENV" + + - name: Create SBOM file + shell: bash + run: | + syft \ + -o spdx-json \ + --file kwasm-operator-sbom-${{ matrix.arch }}.spdx \ + ghcr.io/${{ github.repository_owner }}/kwasm-operator@${{ env.PLATFORM_DIGEST }} + + - name: Sign SBOM file + run: | + cosign sign-blob --yes \ + --output-certificate kwasm-operator-sbom-${{ matrix.arch }}.spdx.cert \ + --output-signature kwasm-operator-sbom-${{ matrix.arch }}.spdx.sig \ + kwasm-operator-sbom-${{ matrix.arch }}.spdx + + - name: Attach SBOM file in the container image + shell: bash + run: | + cosign attach \ + sbom --sbom kwasm-operator-sbom-${{ matrix.arch }}.spdx \ + ghcr.io/${{ github.repository_owner }}/kwasm-operator@${{ env.PLATFORM_DIGEST }} + + - name: Sign SBOM file pushed to OCI registry + shell: bash + run: | + set -e + SBOM_TAG="$(echo ${{ env.PLATFORM_DIGEST }} | sed -e 's/:/-/g').sbom" + + cosign sign --yes \ + ghcr.io/${{github.repository_owner}}/kwasm-operator:${SBOM_TAG} + + - name: Upload SBOMs as artifacts + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: sbom + path: kwasm-operator-sbom-* diff --git a/.github/workflows/sign-image.yml b/.github/workflows/sign-image.yml new file mode 100644 index 0000000..e010dcf --- /dev/null +++ b/.github/workflows/sign-image.yml @@ -0,0 +1,32 @@ +name: Sign image + +on: + workflow_call: + inputs: + image-digest: + type: string + required: true + +jobs: + sign: + name: Sign image + permissions: + packages: write + id-token: write + + runs-on: ubuntu-latest + steps: + - name: Install cosign + uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Sign container image + run: | + cosign sign --yes \ + ghcr.io/${{github.repository_owner}}/kwasm-operator@${{ inputs.image-digest }} diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e3ef22b --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,98 @@ +# This file contains all available configuration options +# with their default values. + +# options for analysis running +run: + tests: true + timeout: 10m + +issues: + exclude-rules: + - linters: + - funlen + # Disable 'funlen' linter for test functions. + # It's common for table-driven tests to be more than 60 characters long + source: "^func Test" + - path: internal/pkg/admission/policy-server-error.go + linters: + - deadcode + - unused + - path: internal/pkg/admissionregistration/cert.go + linters: + - gocritic + - path: internal/pkg/admission/validating-webhook.go + linters: + - dupl + - path: internal/pkg/admission/mutating-webhook.go + linters: + - dupl + - path: pkg/apis/policies/v1alpha2/clusteradmissionpolicy_types.go + text: "got 'TypeMeta' want 'typeMeta'" + - path: pkg/apis/policies/v1alpha2/policyserver_types.go + text: "got 'TypeMeta' want 'typeMeta'" + +linters: + enable-all: true + disable: + - exhaustivestruct + - exhaustruct + - gci + - gochecknoglobals + - gochecknoinits + - gocognit + - godot + - goerr113 + - golint + - gofumpt + - gomnd + - maligned + - nlreturn + - paralleltest + - scopelint + - testpackage + - wsl + - lll # long lines + # https://github.com/golangci/golangci-lint/issues/541 + - interfacer + - interfacebloat + # TODO: enable once we can set some exceptions + - funlen + - ifshort # deprecated + +linters-settings: + cyclop: + max-complexity: 13 + nestif: + min-complexity: 8 + depguard: + # Rules to apply. + # + # Variables: + # - File Variables + # you can still use and exclamation mark ! in front of a variable to say not to use it. + # Example !$test will match any file that is not a go test file. + # + # `$all` - matches all go files + # `$test` - matches all go test files + # + # - Package Variables + # + # `$gostd` - matches all of go's standard library (Pulled from `GOROOT`) + # + # Default: Only allow $gostd in all files. + rules: + # Name of a rule. + main: + # List of file globs that will match this list of settings to compare against. + # Default: $all + files: + - $all + - "!$test" + deny: + - pkg: "github.com/sirupsen/logrus" + desc: not allowed + - pkg: "github.com/pkg/errors" + desc: Should be replaced by standard lib errors package + godox: + keywords: + - "FIXME" diff --git a/Makefile b/Makefile index 37cd753..6b9af98 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role webhook paths="./..." output:artifacts + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -105,6 +105,22 @@ vet: ## Run go vet against code. test: manifests generate fmt vet envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out +GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint +GOLANGCI_LINT_VERSION ?= v1.54.2 +golangci-lint: + @[ -f $(GOLANGCI_LINT) ] || { \ + set -e ;\ + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\ + } + +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter & yamllint + $(GOLANGCI_LINT) run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + $(GOLANGCI_LINT) run --fix + ##@ Build .PHONY: build diff --git a/config/crd/bases/README.md b/config/crd/bases/README.md new file mode 100644 index 0000000..5369799 --- /dev/null +++ b/config/crd/bases/README.md @@ -0,0 +1,4 @@ +This directory is currently empty because this controller doesn't define any Custom Resource Definition. + +This directory is referenced by some of the Makefile target generated by Kubebuilder, hence we need to keep +it around. diff --git a/controllers/job_controller.go b/controllers/job_controller.go index fae6d57..b3af619 100644 --- a/controllers/job_controller.go +++ b/controllers/job_controller.go @@ -18,6 +18,7 @@ package controllers import ( "context" + "fmt" "github.com/rs/zerolog/log" batchv1 "k8s.io/api/batch/v1" @@ -59,7 +60,7 @@ func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R return ctrl.Result{}, nil } log.Err(err).Msg("Unable to get Job") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("failed to get Job: %w", err) } if job.Labels["kwasm.sh/job"] != "true" { @@ -74,9 +75,15 @@ func (r *JobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R case batchv1.JobFailed: log.Info().Msgf("Job %s is still failing...", job.Name) return ctrl.Result{}, nil + case batchv1.JobFailureTarget: + log.Info().Msgf("Job %s is about to fail", job.Name) + return ctrl.Result{}, nil case batchv1.JobComplete: log.Info().Msgf("Job %s is Completed. Happy WASMing", job.Name) return ctrl.Result{}, nil + case batchv1.JobSuspended: + log.Info().Msgf("Job %s is suspended", job.Name) + return ctrl.Result{}, nil } return ctrl.Result{}, nil @@ -94,7 +101,9 @@ func (r *JobReconciler) isJobFinished(job *batchv1.Job) (bool, batchv1.JobCondit // SetupWithManager sets up the controller with the Manager. func (r *JobReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&batchv1.Job{}). - Complete(r) + if err := ctrl.NewControllerManagedBy(mgr).For(&batchv1.Job{}).Complete(r); err != nil { + return fmt.Errorf("failed to setup manager for JobReconciler: %w", err) + } + + return nil } diff --git a/controllers/provisioner_controller.go b/controllers/provisioner_controller.go index 916f353..bb6e85d 100644 --- a/controllers/provisioner_controller.go +++ b/controllers/provisioner_controller.go @@ -18,6 +18,7 @@ package controllers import ( "context" + "fmt" "math" "os" @@ -44,7 +45,6 @@ type ProvisionerReconciler struct { const ( addKWasmNodeLabelAnnotation = "kwasm.sh/kwasm-node" nodeNameLabel = "kwasm.sh/kwasm-provisioned" - jobOwnerKey = ".metadata.controller" ) //+kubebuilder:rbac:groups=wasm.kwasm.sh,resources=provisioners,verbs=get;list;watch;create;update;patch;delete @@ -62,6 +62,8 @@ const ( // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile +// +//nolint:cyclop func (r *ProvisionerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.With().Str("node", req.Name).Logger() node := &corev1.Node{} @@ -73,7 +75,7 @@ func (r *ProvisionerReconciler) Reconcile(ctx context.Context, req ctrl.Request) // on deleted requests. return ctrl.Result{}, nil } - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("failed to get node: %w", err) } /* @@ -96,13 +98,16 @@ func (r *ProvisionerReconciler) Reconcile(ctx context.Context, req ctrl.Request) } node.Labels[nodeNameLabel] = node.Name log.Info().Msgf("Trying to Deploy on %s", node.Name) - dep := r.deployJob(node, req) - err := r.Create(ctx, dep) + + dep, err := r.deployJob(node, req) if err != nil { - log.Err(err).Msg("Failed to create new Job " + req.Namespace + " Job.Name " + req.Name) return ctrl.Result{}, err } + if err = r.Create(ctx, dep); err != nil { + log.Err(err).Msg("Failed to create new Job " + req.Namespace + " Job.Name " + req.Name) + return ctrl.Result{}, fmt.Errorf("failed to create new job: %w", err) + } } else if !r.AutoProvision { // If the label should not be set but is, remove it. delete(node.Labels, nodeNameLabel) @@ -131,14 +136,13 @@ func (r *ProvisionerReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{Requeue: true}, nil } log.Error().Err(err).Msg("unable to update Node") - return ctrl.Result{}, err + return ctrl.Result{}, fmt.Errorf("failed to update Node: %w", err) } return ctrl.Result{}, nil } -func (r *ProvisionerReconciler) deployJob(n *corev1.Node, req ctrl.Request) *batchv1.Job { - +func (r *ProvisionerReconciler) deployJob(node *corev1.Node, req ctrl.Request) (*batchv1.Job, error) { priv := true name := req.Name + "-provision-kwasm" nameMax := int(math.Min(float64(len(name)), 63)) @@ -186,15 +190,17 @@ func (r *ProvisionerReconciler) deployJob(n *corev1.Node, req ctrl.Request) *bat }, }, } - ctrl.SetControllerReference(n, dep, r.Scheme) + if err := ctrl.SetControllerReference(node, dep, r.Scheme); err != nil { + return nil, fmt.Errorf("failed to set controller reference: %w", err) + } - return dep + return dep, nil } // SetupWithManager sets up the controller with the Manager. func (r *ProvisionerReconciler) SetupWithManager(mgr ctrl.Manager) error { - - return ctrl.NewControllerManagedBy(mgr). - For(&corev1.Node{}). - Complete(r) + if err := ctrl.NewControllerManagedBy(mgr).For(&corev1.Node{}).Complete(r); err != nil { + return fmt.Errorf("failed to setup manager for ProvisionerReconciler: %w", err) + } + return nil } diff --git a/controllers/provisioner_controller_test.go b/controllers/provisioner_controller_test.go index 4ae01ad..4302879 100644 --- a/controllers/provisioner_controller_test.go +++ b/controllers/provisioner_controller_test.go @@ -15,15 +15,14 @@ import ( ) var namespaceName = "kwasm-provisioner" +var installerImage = "ghcr.io/kwasm/kwasm-node-installer:latest" var _ = Describe("ProvisionerController", func() { Context("Kwasm Provisioner controller test", func() { var ( - ctx context.Context - node *corev1.Node - //scheme *runtime.Scheme - err error - //request ctrl.Request + ctx context.Context + node *corev1.Node + err error result ctrl.Result job *batchv1.Job ) @@ -43,8 +42,9 @@ var _ = Describe("ProvisionerController", func() { } kwasmReconciler := &controllers.ProvisionerReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + InstallerImage: installerImage, } err = k8sClient.Create(ctx, node) Expect(err).NotTo(HaveOccurred()) @@ -77,7 +77,6 @@ var _ = Describe("ProvisionerController", func() { err = k8sClient.Get(ctx, types.NamespacedName{Name: nodeName + "-provision-kwasm", Namespace: namespaceName}, job) Expect(err).NotTo(HaveOccurred()) Expect(job).NotTo(BeNil()) - }) It("should not set autoProvision to true by default", func() { @@ -90,8 +89,9 @@ var _ = Describe("ProvisionerController", func() { nodeNameNamespaced := types.NamespacedName{Name: nodeName, Namespace: ""} kwasmReconciler := &controllers.ProvisionerReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), + Client: k8sClient, + Scheme: k8sClient.Scheme(), + InstallerImage: installerImage, } node = &corev1.Node{ @@ -111,16 +111,17 @@ var _ = Describe("ProvisionerController", func() { job = &batchv1.Job{} err = k8sClient.Get(ctx, types.NamespacedName{Name: nodeName + "-provision-kwasm", Namespace: namespaceName}, job) Expect(err).To(HaveOccurred()) - }) + It("should provision node if autoprovision is true", func() { nodeName := "autoprovision-node" nodeNameNamespaced := types.NamespacedName{Name: nodeName, Namespace: ""} kwasmReconciler := &controllers.ProvisionerReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - AutoProvision: true, + Client: k8sClient, + Scheme: k8sClient.Scheme(), + InstallerImage: installerImage, + AutoProvision: true, } node = &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ @@ -133,6 +134,8 @@ var _ = Describe("ProvisionerController", func() { _, err = kwasmReconciler.Reconcile(ctx, reconcile.Request{ NamespacedName: nodeNameNamespaced, }) + Expect(err).NotTo(HaveOccurred()) + // Check that the job was created. job = &batchv1.Job{} err = k8sClient.Get(ctx, types.NamespacedName{Name: nodeName + "-provision-kwasm", Namespace: namespaceName}, job) diff --git a/go.mod b/go.mod index d276bd8..9949c25 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kwasm/kwasm-operator -go 1.18 +go 1.21 require ( github.com/onsi/ginkgo v1.16.5 diff --git a/go.sum b/go.sum index 02de9c4..6a538d2 100644 --- a/go.sum +++ b/go.sum @@ -55,6 +55,7 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -142,6 +143,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= @@ -190,6 +192,7 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -276,6 +279,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=