name: CI Pipeline on: push: branches: - main tags: - 'v*' pull_request: branches: - main env: REGISTRY: ${{ vars.REGISTRY_URL }} IMAGE_NAME: enterprise-openldap jobs: # Stage 1: Lint Dockerfile lint: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Lint Dockerfile with hadolint run: | docker run --rm -i hadolint/hadolint < Dockerfile || { echo "::warning::Dockerfile linting found issues (non-blocking)" } # Stage 2: Build image build: runs-on: ubuntu-latest needs: lint outputs: image_tag: ${{ steps.version.outputs.VERSION }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Determine version tag id: version run: | if [[ "$GITHUB_REF" == refs/tags/v* ]]; then VERSION="${GITHUB_REF#refs/tags/v}" else VERSION="$(echo "$GITHUB_SHA" | cut -c1-7)" fi echo "VERSION=$VERSION" >> $GITHUB_OUTPUT echo "Building version: $VERSION" - name: Build Docker image run: | docker build -t ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} . docker tag ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} ${{ env.IMAGE_NAME }}:test - name: Save image for subsequent jobs run: | mkdir -p /tmp/images docker save ${{ env.IMAGE_NAME }}:test -o /tmp/images/image.tar - name: Upload image artifact uses: actions/upload-artifact@v3 with: name: docker-image path: /tmp/images/image.tar retention-days: 1 # Stage 3: Integration tests test: runs-on: ubuntu-latest needs: build steps: - name: Checkout repository uses: actions/checkout@v4 - name: Download image artifact uses: actions/download-artifact@v3 with: name: docker-image path: /tmp/images - name: Load Docker image run: | docker load -i /tmp/images/image.tar - name: Run integration tests run: | chmod +x tests/test-container.sh ./tests/test-container.sh env: IMAGE_NAME: ${{ env.IMAGE_NAME }}:test CONTAINER_NAME: openldap-ci-test # Stage 4: Security scan security-scan: runs-on: ubuntu-latest needs: build steps: - name: Download image artifact uses: actions/download-artifact@v3 with: name: docker-image path: /tmp/images - name: Load Docker image run: | docker load -i /tmp/images/image.tar - name: Scan image with Trivy run: | docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:latest image \ --severity HIGH,CRITICAL \ --exit-code 0 \ --no-progress \ ${{ env.IMAGE_NAME }}:test - name: Scan for critical vulnerabilities (blocking) run: | docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:latest image \ --severity CRITICAL \ --exit-code 1 \ --no-progress \ --ignore-unfixed \ ${{ env.IMAGE_NAME }}:test || { echo "::error::Critical vulnerabilities found!" exit 1 } # Stage 5: Auto-tag (only on main branch, not on tags or PRs) autotag: runs-on: ubuntu-latest needs: [test, security-scan] if: github.ref == 'refs/heads/main' && github.event_name == 'push' outputs: new_tag: ${{ steps.autotag.outputs.new_tag }} version: ${{ steps.autotag.outputs.version }} steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # Need full history for autotag - name: Configure git run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - name: Run autotag id: autotag run: | # Check if any tags exist CURRENT_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -z "$CURRENT_TAG" ]; then echo "No existing tags found, starting at v0.1.0" NEW_TAG="v0.1.0" else echo "Current tag: $CURRENT_TAG" # Download autotag AUTOTAG_VERSION="1.3.9" curl -sL "https://github.com/autotag-dev/autotag/releases/download/v${AUTOTAG_VERSION}/autotag_linux_amd64" -o /tmp/autotag chmod +x /tmp/autotag # Calculate next version based on commits # autotag looks for #major, #minor in commit messages, defaults to patch NEW_TAG=$(/tmp/autotag -n -b main 2>&1 || echo "") # Check if autotag returned an error or empty result if [ -z "$NEW_TAG" ] || echo "$NEW_TAG" | grep -qi "error"; then echo "Autotag failed or returned error, using fallback" # Fallback: increment patch version CURRENT_VERSION="${CURRENT_TAG#v}" MAJOR=$(echo $CURRENT_VERSION | cut -d. -f1) MINOR=$(echo $CURRENT_VERSION | cut -d. -f2) PATCH=$(echo $CURRENT_VERSION | cut -d. -f3) NEW_PATCH=$((PATCH + 1)) NEW_TAG="v${MAJOR}.${MINOR}.${NEW_PATCH}" fi fi echo "New tag will be: $NEW_TAG" echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT echo "version=${NEW_TAG#v}" >> $GITHUB_OUTPUT - name: Create and push tag run: | NEW_TAG="${{ steps.autotag.outputs.new_tag }}" # Check if tag already exists if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then echo "Tag $NEW_TAG already exists, skipping" exit 0 fi echo "Creating tag: $NEW_TAG" git tag -a "$NEW_TAG" -m "Release $NEW_TAG (auto-generated)" git push origin "$NEW_TAG" echo "Successfully pushed tag: $NEW_TAG" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Stage 6: Push to registry # Runs after autotag (on main) or after tests (on tag push) push: runs-on: ubuntu-latest needs: [test, security-scan, autotag] if: | always() && needs.test.result == 'success' && needs.security-scan.result == 'success' && github.event_name == 'push' && (needs.autotag.result == 'success' || startsWith(github.ref, 'refs/tags/v')) outputs: version: ${{ steps.version.outputs.VERSION }} full_image: ${{ steps.version.outputs.FULL_IMAGE }} steps: - name: Download image artifact uses: actions/download-artifact@v3 with: name: docker-image path: /tmp/images - name: Load Docker image run: | docker load -i /tmp/images/image.tar - name: Determine version and tags id: version run: | # Get version from autotag output or from git ref if [[ "$GITHUB_REF" == refs/tags/v* ]]; then VERSION="${GITHUB_REF#refs/tags/v}" else # Use version from autotag job VERSION="${{ needs.autotag.outputs.version }}" fi if [ -z "$VERSION" ]; then echo "::error::No version determined" exit 1 fi # For releases, tag with version, major.minor, and latest MAJOR=$(echo $VERSION | cut -d. -f1) MINOR=$(echo $VERSION | cut -d. -f2) TAGS="${VERSION},${MAJOR}.${MINOR},latest" echo "VERSION=$VERSION" >> $GITHUB_OUTPUT echo "TAGS=$TAGS" >> $GITHUB_OUTPUT echo "FULL_IMAGE=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}" >> $GITHUB_OUTPUT echo "Pushing version: $VERSION" - name: Log in to Docker Registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin - name: Tag and push images run: | IFS=',' read -ra TAGS <<< "${{ steps.version.outputs.TAGS }}" for TAG in "${TAGS[@]}"; do docker tag ${{ env.IMAGE_NAME }}:test ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$TAG docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$TAG echo "Pushed: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$TAG" done - name: Logout from registry if: always() run: docker logout ${{ env.REGISTRY }} || true # Stage 7: Trigger CD pipeline for terraform-docker-openldap update-cd: runs-on: ubuntu-latest needs: push if: needs.push.result == 'success' steps: - name: Trigger terraform-docker-openldap pipeline run: | echo "==============================================" echo " Triggering CD pipeline" echo "==============================================" echo "New version: ${{ needs.push.outputs.version }}" echo "Full image: ${{ needs.push.outputs.full_image }}" echo "" # Trigger the Gitea Actions workflow via repository dispatch curl -X POST \ -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ -H "Content-Type: application/json" \ "${{ vars.GITEA_URL }}/api/v1/repos/wbyc/terraform-docker-openldap/actions/workflows/pipeline.yaml/dispatches" \ -d '{ "ref": "main", "inputs": { "image_tag": "${{ needs.push.outputs.version }}" } }' || { echo "::warning::Failed to trigger CD pipeline" exit 1 } echo "==============================================" echo " CD pipeline triggered successfully" echo "=============================================="