diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..0fb0880 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,193 @@ +name: Build artifacts + +on: + push: + branches: + - main + + pull_request: + branches: + - main + +concurrency: build-${{ github.ref }} + +env: + HELM_VERSION: v3.11.3 + KIND_VERSION: v0.19.0 + REGISTRY: ghcr.io + CHART_DIRECTORY: chart + +defaults: + run: + shell: bash + +jobs: + test: + name: Run tests + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + + - name: Check that license header boilerplate is correct + uses: sap/cs-actions/check-go-license-boilerplate@main + with: + boilerplate-path: hack/boilerplate.go.txt + + - name: Check that license headers are correct + uses: sap/cs-actions/check-go-license-headers@main + with: + boilerplate-path: hack/boilerplate.go.txt + + - name: Check that generated artifacts are up-to-date + run: | + make generate + echo "Running 'git status' ..." + if [ -z "$(git status --porcelain)" ]; then + echo "Generated artifacts are up-to-date." + else + >&2 echo "Generated artifacts are not up-to-date; probably 'make generate' was not run before committing." + exit 1 + fi + + - name: Check that manifests are up-to-date + run: | + make manifests + echo "Running 'git status' ..." + if [ -z "$(git status --porcelain)" ]; then + echo "Manifests are up-to-date." + else + >&2 echo "Manifests are not up-to-date; probably 'make manifests' was not run before committing." + exit 1 + fi + + - name: Run tests + run: | + make envtest + KUBEBUILDER_ASSETS=$(pwd)/bin/k8s/current E2E_ENABLED=${{ github.event_name == 'push' }} go test -count 1 ./... + + build-docker: + name: Build Docker image + runs-on: ubuntu-22.04 + needs: test + permissions: + contents: read + outputs: + image-archive: image.tar + image-repository: ${{ steps.prepare-repository-name.outputs.repository }} + image-tag: ${{ steps.extract-metadata.outputs.version }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Prepare repository name + id: prepare-repository-name + run: | + repository=$REGISTRY/${{ github.repository }} + echo "repository=${repository,,}" >> $GITHUB_OUTPUT + + - name: Prepare custom labels for Docker + id: extract-custom-labels + run: | + echo "labels<> $GITHUB_OUTPUT + for c in pkg/operator/data/charts/*/Chart.yaml; do + name=$(yq .name $c) + version=$(yq .version $c) + app_version=$(yq .appVersion $c) + echo "com.sap.cs.image.content.charts.$name.version=$version" >> $GITHUB_OUTPUT + if [ ! -z "$app_version" ]; then + echo "com.sap.cs.image.content.charts.$name.app-version=$app_version" >> $GITHUB_OUTPUT + fi + done + echo "EOF" >> $GITHUB_OUTPUT + + - name: Extract metadata (tags, labels) for Docker + id: extract-metadata + uses: docker/metadata-action@v4 + with: + images: ${{ steps.prepare-repository-name.outputs.repository }} + labels: ${{ steps.extract-custom-labels.outputs.labels }} + + - name: Build Docker image + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64,linux/arm64 + context: . + cache-from: | + type=gha,scope=sha-${{ github.sha }} + type=gha,scope=${{ github.ref_name }} + type=gha,scope=${{ github.base_ref || 'main' }} + type=gha,scope=main + cache-to: | + type=gha,scope=sha-${{ github.sha }},mode=max + type=gha,scope=${{ github.ref_name }},mode=max + outputs: | + type=oci,dest=${{ runner.temp }}/image.tar + tags: ${{ steps.extract-metadata.outputs.tags }} + labels: ${{ steps.extract-metadata.outputs.labels }} + + - name: Upload Docker image archive + uses: actions/upload-artifact@v3 + with: + name: image.tar + path: ${{ runner.temp }}/image.tar + + test-helm: + name: Run Helm chart tests + runs-on: ubuntu-22.04 + needs: build-docker + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: azure/setup-helm@v3 + with: + version: ${{ env.HELM_VERSION }} + + - name: Lint Helm chart + run: | + helm lint $CHART_DIRECTORY + + - name: Create Kind cluster + uses: helm/kind-action@v1 + with: + version: ${{ env.KIND_VERSION }} + cluster_name: kind + + - name: Show Kubernetes version + run: | + kubectl version + + - name: Download Docker image archive + uses: actions/download-artifact@v3 + with: + name: ${{ needs.build-docker.outputs.image-archive }} + path: ${{ runner.temp }} + + - name: Load Docker image archive into Kind cluster + run: | + kind load image-archive ${{ runner.temp }}/${{ needs.build-docker.outputs.image-archive }} + + - name: Install Helm chart and deploy sample component + run: | + release_name=$(yq .name $CHART_DIRECTORY/Chart.yaml) + kubectl create ns cop-system + helm -n cop-system upgrade -i $release_name --wait --timeout 5m \ + --set image.repository=${{ needs.build-docker.outputs.image-repository }} \ + --set image.tag=${{ needs.build-docker.outputs.image-tag }} \ + $CHART_DIRECTORY + kubectl create ns component-system + kubectl -n component-system apply -f examples/sample.yaml + kubectl -n component-system wait -f examples/sample.yaml --for condition=Ready --timeout 120s + diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..b7d7772 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,140 @@ +name: Publish artifacts + +on: + release: + types: [published] + +concurrency: release-${{ github.event.release.tag_name }} + +env: + REGCTL_VERSION: v0.4.8 + REGISTRY: ghcr.io + CHART_DIRECTORY: chart + +defaults: + run: + shell: bash + +jobs: + validate: + name: Run validations + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Validate chart version/appVersion + run: | + chart_version=$(yq .version $CHART_DIRECTORY/Chart.yaml) + app_version=$(yq .appVersion $CHART_DIRECTORY/Chart.yaml) + if [ "v$chart_version" != "${{ github.event.release.tag_name }}" ]; then + >&2 echo "Version in $CHART_DIRECTORY/Chart.yaml ($chart_version) does not match release version (${{ github.event.release.tag_name }})." + exit 1 + fi + if [ "$app_version" != "${{ github.event.release.tag_name }}" ]; then + >&2 echo "AppVersion in $CHART_DIRECTORY/Chart.yaml ($app_version) does not match release version (${{ github.event.release.tag_name }})." + exit 1 + fi + + publish-docker: + name: Publish Docker image + runs-on: ubuntu-22.04 + needs: validate + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Prepare repository name + id: prepare-repository-name + run: | + repository=$REGISTRY/${{ github.repository }} + echo "repository=${repository,,}" >> $GITHUB_OUTPUT + + - name: Prepare custom labels for Docker + id: extract-custom-labels + run: | + echo "labels<> $GITHUB_OUTPUT + for c in pkg/operator/data/charts/*/Chart.yaml; do + name=$(yq .name $c) + version=$(yq .version $c) + app_version=$(yq .appVersion $c) + echo "com.sap.cs.image.content.charts.$name.version=$version" >> $GITHUB_OUTPUT + if [ ! -z "$app_version" ]; then + echo "com.sap.cs.image.content.charts.$name.app-version=$app_version" >> $GITHUB_OUTPUT + fi + done + echo "EOF" >> $GITHUB_OUTPUT + + - name: Extract metadata (tags, labels) for Docker + id: extract-metadata + uses: docker/metadata-action@v4 + with: + images: ${{ steps.prepare-repository-name.outputs.repository }} + labels: ${{ steps.extract-custom-labels.outputs.labels }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64,linux/arm64 + context: . + cache-from: | + type=gha,scope=sha-${{ github.sha }} + type=gha,scope=${{ github.ref_name }} + type=gha,scope=${{ github.base_ref || 'main' }} + type=gha,scope=main + cache-to: | + type=gha,scope=sha-${{ github.sha }},mode=max + type=gha,scope=${{ github.ref_name }},mode=max + push: true + tags: ${{ steps.extract-metadata.outputs.tags }} + labels: ${{ steps.extract-metadata.outputs.labels }} + + publish-crds: + name: Publish CRD image + runs-on: ubuntu-22.04 + needs: validate + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup regctl + uses: regclient/actions/regctl-installer@main + with: + release: ${{ env.REGCTL_VERSION }} + install-dir: ${{ runner.temp }}/bin + + - name: Log in to the registry + # regctl-login action is currently broken ... + # uses: regclient/actions/regctl-login@main + # with: + # registry: ${{ env.REGISTRY }} + # username: ${{ github.actor }} + # password: ${{ github.token }} + run: | + regctl registry login $REGISTRY --user ${{ github.actor }} --pass-stdin <<< ${{ github.token }} + + - name: Build and push artifact + run: | + cd crds + repository=$REGISTRY/${{ github.repository }}/crds + tar cvz * | regctl artifact put -m application/gzip ${repository,,}:${{ github.event.release.tag_name }} + diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..e7ed0c7 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,202 @@ +name: Trigger release + +on: + workflow_dispatch: + inputs: + version-bump: + description: 'Whether to bump major, minor or patch version' + required: false + default: patch + type: choice + options: + - major + - minor + - patch + desired-version: + description: 'Version to be released; if specified, version-bump will be ignored' + required: false + default: '' + + schedule: + - cron: '10 11 * * 1' + - cron: '10 12 * * 1' + +concurrency: trigger-release + +env: + TAG_PREFIX: v + INITIAL_TAG: v0.1.0 + SEMVER_VERSION: 3.4.0 + HELM_DOCS_VERSION: v1.11.0 + CHART_DIRECTORY: chart + +defaults: + run: + shell: bash + +jobs: + release: + name: Trigger release + runs-on: ubuntu-22.04 + permissions: + contents: write + + steps: + - name: Validate ref + run: | + if [ "${{ github.ref }}" != refs/heads/main ]; then + >&2 echo "Invalid ref: ${{ github.ref }} (expected: refs/heads/main)" + exit 1 + fi + + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.WORKFLOW_USER_GH_TOKEN }} + fetch-depth: 0 + + - name: Setup semver + uses: sap/cs-actions/setup-semver@main + with: + version: ${{ env.SEMVER_VERSION }} + install-directory: ${{ runner.temp }}/bin + + - name: Setup helm-docs + uses: sap/cs-actions/setup-helm-docs@main + with: + version: ${{ env.HELM_DOCS_VERSION }} + install-directory: ${{ runner.temp }}/bin + + - name: Determine current release + id: get_current_release + uses: sap/cs-actions/get-highest-tag@main + with: + prefix: ${{ env.TAG_PREFIX }} + + - name: Determine target release + id: get_target_release + run: | + create_release=true + + if ${{ github.event_name == 'schedule' }}; then + commits_count=$(git rev-list --count --no-merges ${{ steps.get_current_release.outputs.tag }}..HEAD --before=1.hour) + if [ $commits_count -eq 0 ]; then + create_release=false + echo "There are no commits since latest release, nothing to do." + fi + version_bump=patch + else + version_bump=${{ inputs.version-bump }} + fi + + echo "Create release: $create_release" + echo "create_release=$create_release" >> $GITHUB_OUTPUT + + if [ "$create_release" != true ]; then + exit 0 + fi + + desired_version=${{ inputs.desired-version }} + current_version=${{ steps.get_current_release.outputs.version }} + + if [ -z "$desired_version" ]; then + case $version_bump in + major|minor|patch) + # ok + ;; + *) + >&2 echo "Invalid input: version-bump ($version_bump)" + exit 1 + esac + if [ -z "$current_version" ]; then + version=${INITIAL_TAG/#$TAG_PREFIX/} + tag=$INITIAL_TAG + else + version=$(semver bump $version_bump $current_version) + tag=$TAG_PREFIX$version + fi + else + if [[ $desired_version =~ ^$TAG_PREFIX([0-9].*)$ ]]; then + version=${BASH_REMATCH[1]} + tag=$desired_version + else + >&2 echo "Invalid input: desired-version ($desired_version) should start with $TAG_PREFIX." + exit 1 + fi + if [ "$(semver validate $version)" != valid ]; then + >&2 echo "Invalid input: desired-version ($version) is not a valid semantic version." + exit 1 + fi + if [ "$(semver compare $version $current_version)" -le 0 ]; then + >&2 echo "Invalid input: desired-version ($version) should be higher than current version ($current_version)." + exit 1 + fi + fi + + echo "Target version: $version" + echo "Target tag: $tag" + echo "version=$version" >> $GITHUB_OUTPUT + echo "tag=$tag" >> $GITHUB_OUTPUT + + # TODO: if this job fails after the following step (resp. after the following step has any pushed commits), + # a re-run of the job will fail in the following step, because it will try to make pushes to the triggering sha + # which is no longer the head; this should be caught/improved. + - name: Update chart version/appVersion and chart docs + if: steps.get_target_release.outputs.create_release == 'true' + run: | + current_chart_version=$(yq .version $CHART_DIRECTORY/Chart.yaml) + current_app_version=$(yq .appVersion $CHART_DIRECTORY/Chart.yaml) + target_chart_version=${{ steps.get_target_release.outputs.version }} + target_app_version=${{ steps.get_target_release.outputs.tag }} + + echo "Updating $CHART_DIRECTORY/Chart.yaml from $current_chart_version to $target_chart_version ..." + perl -pi -e "s#^version:.*#version: $target_chart_version#g" $CHART_DIRECTORY/Chart.yaml + perl -pi -e "s#^appVersion:.*#appVersion: $target_app_version#g" $CHART_DIRECTORY/Chart.yaml + + if [ -z "$(git status --porcelain)" ]; then + echo "Nothing has changed; skipping commit ..." + else + git config user.name "${{ vars.WORKFLOW_USER_NAME }}" + git config user.email "${{ vars.WORKFLOW_USER_EMAIL }}" + git add -A + git commit -m "Bump chart/app version from $current_chart_version/$current_app_version to $target_chart_version/$target_app_version" + fi + + helm-docs -c $CHART_DIRECTORY -s file + + if [ -z "$(git status --porcelain)" ]; then + echo "Nothing has changed; skipping commit ..." + else + git config user.name "${{ vars.WORKFLOW_USER_NAME }}" + git config user.email "${{ vars.WORKFLOW_USER_EMAIL }}" + git add -A + git commit -m "Update chart docs" + fi + + git push + + - name: Determine target commit + id: get_target_commit + if: steps.get_target_release.outputs.create_release == 'true' + run: | + sha=$(git rev-parse HEAD) + echo "Target commit: $sha" + echo "sha=$sha" >> $GITHUB_OUTPUT + + - name: Wait for check suites to complete + if: steps.get_target_release.outputs.create_release == 'true' + uses: sap-contributions/await-check-suites@master + with: + ref: ${{ steps.get_target_commit.outputs.sha }} + intervalSeconds: 10 + timeoutSeconds: 1800 + failStepIfUnsuccessful: true + appSlugFilter: github-actions + + - name: Create Release + if: steps.get_target_release.outputs.create_release == 'true' + env: + GH_TOKEN: ${{ secrets.WORKFLOW_USER_GH_TOKEN }} + run: | + gh release create ${{ steps.get_target_release.outputs.tag }} \ + --target "${{ steps.get_target_commit.outputs.sha }}"