diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..26adb1d --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,17 @@ +changelog: + categories: + - title: 🎉 New features + labels: + - Feature + - title: ⭐ Enhancements + labels: + - Enhancement + - title: 🐞 Bug fixes + labels: + - Bug + - title: 📝 Documentation + labels: + - Documentation + - title: Other changes + labels: + - "*" diff --git a/.github/workflows/hawkbit.yml b/.github/workflows/hawkbit.yml index 633fe45..87c3be9 100644 --- a/.github/workflows/hawkbit.yml +++ b/.github/workflows/hawkbit.yml @@ -1,86 +1,146 @@ name: Docker Image on: + # When a release is published release: types: [published] + + # Push excluding tags and Markdown-only changes push: branches: - main tags-ignore: - - "*.*" + - '*.*' + paths-ignore: + - '**/*.md' + + # Validate pull requests without publishing + pull_request: + branches: + - main paths-ignore: - - "**/*.md" + - '**/*.md' + + # Manual trigger + workflow_dispatch: permissions: contents: read security-events: write concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.ref }} cancel-in-progress: true +env: + IMAGE_NAME: ${{ vars.DOCKERHUB_NAMESPACE || github.repository_owner }}/hawkbit-update-server + PLATFORM: linux/amd64,linux/arm64 + jobs: image_hawkbit: + name: Build images runs-on: ubuntu-latest - env: - PLATFORM: linux/amd64,linux/arm64 - IMAGE_NAME: openremote/hawkbit-update-server steps: - - name: Set tags - id: set-tags - run: | - if [ -z "$RELEASE_TAG" ]; then - echo "DOCKER_TAG_ARGS=-t ${IMAGE_NAME}:develop" >> "$GITHUB_ENV" - echo "dockerImage=${IMAGE_NAME}:develop" >> "$GITHUB_OUTPUT" - else - echo "DOCKER_TAG_ARGS=-t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:$RELEASE_TAG" >> "$GITHUB_ENV" - echo "dockerImage=${IMAGE_NAME}:$RELEASE_TAG" >> "$GITHUB_OUTPUT" - fi - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 with: platforms: all - name: Set up Buildx - id: buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3 - with: - version: latest - install: true + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Available platforms - run: echo "${{ steps.buildx.outputs.platforms }}" - - - name: Login to DockerHub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3 + - name: Log in to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets._TEMP_DOCKERHUB_USER }} password: ${{ secrets._TEMP_DOCKERHUB_PASSWORD }} - - name: Build and push images + - name: Compute image metadata + id: meta + shell: bash run: | - docker build --build-arg GIT_COMMIT=${{ github.sha }} --push --platform $PLATFORM $DOCKER_TAG_ARGS . + VERSION="" + IS_VERSIONED="false" + PRIMARY_TAG="" + PUSH_TAGS="" + + if [[ "${{ github.event_name }}" == "release" ]]; then + VERSION="${{ github.event.release.tag_name }}" + IS_VERSIONED="true" + elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + if [[ "${{ github.ref_type }}" == "tag" ]]; then + VERSION="${{ github.ref_name }}" + IS_VERSIONED="true" + elif [[ "${{ github.ref }}" != "refs/heads/main" ]]; then + echo "workflow_dispatch must be run on main or on a tag" + exit 1 + fi + fi + + if [[ "$IS_VERSIONED" == "true" ]]; then + PRIMARY_TAG="${IMAGE_NAME}:${VERSION}" + PUSH_TAGS="${IMAGE_NAME}:${VERSION} + ${IMAGE_NAME}:latest" + elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + PRIMARY_TAG="${IMAGE_NAME}:develop" + PUSH_TAGS="${IMAGE_NAME}:develop" + else + PRIMARY_TAG="${IMAGE_NAME}:pr-${{ github.event.pull_request.number || github.run_number }}" + PUSH_TAGS="${PRIMARY_TAG}" + fi - - name: Scan hawkbit docker image - uses: anchore/scan-action@3c9a191a0fbab285ca6b8530b5de5a642cba332f # v7.2.2 + { + echo "version=$VERSION" + echo "is_versioned=$IS_VERSIONED" + echo "primary_tag=$PRIMARY_TAG" + echo "push_tags<> "$GITHUB_OUTPUT" + + - name: Build image locally for scanning + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + platforms: linux/amd64 + load: true + push: false + tags: ${{ steps.meta.outputs.primary_tag }} + build-args: | + GIT_COMMIT=${{ github.sha }} + cache-from: type=gha,scope=hawkbit-amd64 + cache-to: type=gha,mode=max,scope=hawkbit-amd64 + + - name: Scan hawkbit Docker image + uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 id: anchore-scan with: - image: ${{ steps.set-tags.outputs.dockerImage }} + image: ${{ steps.meta.outputs.primary_tag }} fail-build: false severity-cutoff: critical - name: Upload Anchore scan SARIF report - if: ${{ !cancelled() }} - uses: github/codeql-action/upload-sarif@c8e3174949dcd2ceb71718aeaa53fee4dc9052f2 # v4.31.7 + if: ${{ !cancelled() && github.event_name != 'pull_request' && (github.event_name != 'workflow_dispatch' || github.ref == 'refs/heads/main') }} + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: sarif_file: ${{ steps.anchore-scan.outputs.sarif }} + category: grype-hawkbit + + - name: Build and push multi-arch images + if: github.event_name != 'pull_request' + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + platforms: ${{ env.PLATFORM }} + push: true + tags: ${{ steps.meta.outputs.push_tags }} + build-args: | + GIT_COMMIT=${{ github.sha }} + cache-from: type=gha,scope=hawkbit-multiarch + cache-to: type=gha,mode=max,scope=hawkbit-multiarch - - name: Inspect Anchore scan SARIF report - if: ${{ !cancelled() }} - run: cat "${{ steps.anchore-scan.outputs.sarif }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5449bd9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Release + +on: + workflow_dispatch: + inputs: + VERSION: + description: 'Release version/tag to create (example: 1.0.0.0)' + type: string + required: true + +permissions: + actions: write + contents: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Validate version + shell: bash + run: | + if [[ -z "${VERSION}" ]]; then + echo "VERSION is required" + exit 1 + fi + + if git ls-remote --tags origin "refs/tags/${VERSION}" | grep -q "refs/tags/${VERSION}$"; then + echo "Tag ${VERSION} already exists on origin" + exit 1 + fi + env: + VERSION: ${{ github.event.inputs.VERSION }} + + - name: Create and push tag + shell: bash + run: | + git tag "${VERSION}" + git push origin "${VERSION}" + env: + VERSION: ${{ github.event.inputs.VERSION }} + + - name: Create GitHub release + shell: bash + run: | + gh release create "${VERSION}" \ + --generate-notes \ + --title "${VERSION}" + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ github.event.inputs.VERSION }} + + - name: Trigger Docker workflow + shell: bash + run: | + gh workflow run hawkbit.yml --ref "${VERSION}" + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ github.event.inputs.VERSION }}