diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..ff62dea --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,19 @@ +name: Checks + +on: + pull_request: + push: + branches: + - main + +jobs: + pre_commit_checks: + name: Pre-Commit checks + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@main + with: + submodules: true + + - uses: pre-commit/action@main diff --git a/.github/workflows/django-docker-ci.yml b/.github/workflows/django-docker-ci.yml deleted file mode 100644 index 831910d..0000000 --- a/.github/workflows/django-docker-ci.yml +++ /dev/null @@ -1,197 +0,0 @@ -name: Django Docker CI - -on: - workflow_call: - inputs: - push_docker_image: - type: boolean - description: "Push docker image to registry" - default: false - run_graphql_check: - type: boolean - description: "Run GraphQL schema validation step" - default: true - compose_file: - type: string - description: "Docker Compose file to use for running containers" - default: "docker-compose.yml" - service_name: - type: string - description: "Docker Compose service name for the Django application" - default: "web" - test_target: - type: string - description: "Test script path to run inside the container" - required: true - chart_directory: - type: string - description: "Path to the Helm chart directory" - required: true - outputs: - docker_image_name: - description: "Only docker image name" - value: ${{ jobs.test.outputs.docker_image_name }} - docker_image_tag: - description: "Only docker image tag" - value: ${{ jobs.test.outputs.docker_image_tag }} - docker_image: - description: "docker image with tag" - value: ${{ jobs.test.outputs.docker_image }} - -env: - COMPOSE_FILE: ${{ inputs.compose_file }} - -jobs: - pre_commit_checks: - name: Pre-Commit checks - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@main - with: - submodules: true - - - uses: actions/setup-python@v5 - with: - python-version-file: '.python-version' - - - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - - - name: Setup uv python environment - run: uv venv - - - name: uv lock check - run: uv lock --locked --offline - - - name: uv sync - run: uv sync --all-groups --all-extras - - - uses: pre-commit/action@main - - test: - name: Test - runs-on: ubuntu-latest - needs: pre_commit_checks - - outputs: - docker_image_name: ${{ steps.prep.outputs.tagged_image_name }} - docker_image_tag: ${{ steps.prep.outputs.tag }} - docker_image: ${{ steps.prep.outputs.tagged_image }} - - steps: - - uses: actions/checkout@main - with: - submodules: true - - - name: Login to GitHub Container Registry - if: ${{ inputs.push_docker_image }} - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: ๐Ÿณ Prepare Docker - id: prep - env: - IMAGE_NAME: ghcr.io/${{ github.repository }} - run: | - BRANCH_NAME=$(echo "$GITHUB_REF_NAME" \ - | tr '[:upper:]' '[:lower:]' \ - | sed 's|[:_/]|-|g' \ - | cut -c1-100 \ - | sed 's/-*$//') - - if [[ "$BRANCH_NAME" == *"/"* ]]; then - IMAGE_NAME="$IMAGE_NAME-dev" - TAG="$(echo "$BRANCH_NAME" | sed 's|/|-|g').c$(echo $GITHUB_SHA | head -c7)" - else - TAG="$BRANCH_NAME.c$(echo $GITHUB_SHA | head -c7)" - fi - - IMAGE_NAME=$(echo "$IMAGE_NAME" | tr '[:upper:]' '[:lower:]') - - echo "tagged_image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "tagged_image=${IMAGE_NAME}:${TAG}" >> $GITHUB_OUTPUT - - - uses: docker/setup-buildx-action@v3 - - - name: ๐Ÿณ Build image - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile - load: true - push: false - provenance: false - tags: ${{ steps.prep.outputs.tagged_image }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Prep django containers - env: - DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} - run: | - touch .env - docker compose run --rm ${{ inputs.service_name }} \ - bash -c 'wait-for-it db:5432 && ./manage.py check' - - - name: ๐Ÿ•ฎ Validate django migrations - env: - DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} - run: | - docker compose run --rm ${{ inputs.service_name }} bash -c './manage.py makemigrations --check --dry-run' || { - echo "There are some changes to be reflected in the migration. Make sure to run makemigrations"; - } - - - name: ๐Ÿ•ฎ Validate latest graphql schema - if: ${{ inputs.run_graphql_check }} - env: - DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} - run: | - docker compose run --rm ${{ inputs.service_name }} ./manage.py graphql_schema --out /ci-share/schema-latest.graphql && - cmp --silent schema.graphql ./ci-share/schema-latest.graphql || { - echo 'The schema.graphql is not up to date with the latest changes. Please update and push latest'; - diff schema.graphql ./ci-share/schema-latest.graphql; - exit 1; - } - - - name: ๐Ÿคž Run tests - env: - DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} - run: | - docker compose run --rm ${{ inputs.service_name }} ${{ inputs.test_target }} - - - name: ๐Ÿณ Docker push - if: ${{ inputs.push_docker_image }} - run: docker push ${{ steps.prep.outputs.tagged_image }} - - validate_helm: - name: Validate Helm - runs-on: ubuntu-latest - - defaults: - run: - working-directory: ${{ inputs.chart_directory }} - - steps: - - uses: actions/checkout@main - with: - submodules: true - - - name: Install Helm - uses: azure/setup-helm@v4 - - - name: ๐Ÿณ Helm dependency - run: | - yq --indent 0 '.dependencies | map(select(.repository | test("^oci:") | not)) | map(["helm", "repo", "add", .name, .repository] | join(" ")) | .[]' Chart.lock | sh -- - helm dependency build . - - - name: Helm lint - run: helm lint . --values linter_values.yaml - - - name: Helm template (snapshot) - run: ./update-snapshots.sh --check-diff-only diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml new file mode 100644 index 0000000..52c6062 --- /dev/null +++ b/.github/workflows/git.yml @@ -0,0 +1,11 @@ +name: Lint commits + +on: [pull_request] + +jobs: + lint: + name: Commit Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - uses: toggle-corp/commit-lint@main diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml deleted file mode 100644 index 9f69a81..0000000 --- a/.github/workflows/helm-publish.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Helm Publish - -on: - workflow_call: - inputs: - push_docker_image: - type: boolean - description: "Push docker image to registry" - default: true - run_graphql_check: - type: boolean - description: "Run GraphQL schema validation step" - default: true - compose_file: - type: string - description: "Docker Compose file to use for running containers" - default: "docker-compose.yml" - service_name: - type: string - description: "Docker Compose service name for the Django application" - default: "web" - test_target: - type: string - description: "Test script path to run inside the container" - required: true - chart_directory: - type: string - description: "Path to the Helm chart directory" - required: true - outputs: - helm_repo_url: - description: "Helm repo URL" - value: ${{ jobs.build.outputs.helm_repo_url }} - helm_chart: - description: "Helm Chart" - value: ${{ jobs.build.outputs.helm_chart }} - helm_target_revision: - description: "Helm target revision" - value: ${{ jobs.build.outputs.helm_target_revision }} - docker_image: - description: "Docker image" - value: ${{ jobs.ci.outputs.docker_image }} - -permissions: - contents: read - packages: write - -jobs: - ci: - name: CI - uses: ./.github/workflows/django-docker-ci.yml - with: - push_docker_image: ${{ inputs.push_docker_image }} - run_graphql_check: ${{ inputs.run_graphql_check }} - compose_file: ${{ inputs.compose_file }} - service_name: ${{ inputs.service_name }} - chart_directory: ${{ inputs.chart_directory }} - test_target: ${{inputs.test_target}} - secrets: inherit - - build: - name: Publish Helm - needs: ci - runs-on: ubuntu-latest - defaults: - run: - working-directory: ${{ inputs.chart_directory }} - - outputs: - helm_repo_url: ${{ steps.push.outputs.helm_repo_url }} - helm_chart: ${{ steps.push.outputs.helm_chart }} - helm_target_revision: ${{ steps.push.outputs.helm_target_revision }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - submodules: true - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Install Helm - uses: azure/setup-helm@v4 - - - name: ๐Ÿณ Helm dependency - run: | - yq --indent 0 '.dependencies | map(select(.repository | test("^oci:") | not)) | map(["helm", "repo", "add", .name, .repository] | join(" ")) | .[]' Chart.lock | sh -- - helm dependency build . - - - name: Tag docker image in Helm Chart values.yaml - env: - IMAGE_NAME: ${{ needs.ci.outputs.docker_image_name }} - IMAGE_TAG: ${{ needs.ci.outputs.docker_image_tag }} - run: | - sed -i "s|SET-BY-CICD-IMAGE|$IMAGE_NAME|" values.yaml - sed -i "s/SET-BY-CICD-TAG/$IMAGE_TAG/" values.yaml - - - name: Package Helm Chart - env: - IMAGE_TAG: ${{ needs.ci.outputs.docker_image_tag }} - run: | - if [[ "$GITHUB_REF" == refs/tags/* ]]; then - TAG="-$(echo $GITHUB_SHA | head -c7)" - sed -i "s/-SET-BY-CICD/$TAG/g" Chart.yaml - else - if [[ "$GITHUB_REF_NAME" == *"/"* ]]; then - sed -i 's/^\(name:.*\)-helm$/\1-dev-helm/' Chart.yaml - fi - sed -i "s/SET-BY-CICD/$IMAGE_TAG/g" Chart.yaml - fi - mkdir -p .helm-charts - helm package . -d .helm-charts - - - name: Push Helm Chart - id: push - env: - IMAGE: ${{ needs.ci.outputs.docker_image }} - OCI_REPO: oci://ghcr.io/${{ github.repository_owner }} - run: | - OCI_REPO=$(echo $OCI_REPO | tr '[:upper:]' '[:lower:]') - PACKAGE_FILE=$(ls .helm-charts/*.tgz | head -n 1) - - echo "# Helm Chart" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```yaml' >> $GITHUB_STEP_SUMMARY - helm push "$PACKAGE_FILE" $OCI_REPO 2>> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - HELM_CHART=$(helm show chart "$PACKAGE_FILE" | grep '^name:' | awk '{print $2}') - HELM_TARGET_REVISION=$(helm show chart "$PACKAGE_FILE" | grep '^version:' | awk '{print $2}') - - echo "> [!Important]" >> $GITHUB_STEP_SUMMARY - echo "> Helm Repo URL: **$OCI_REPO**" >> $GITHUB_STEP_SUMMARY - echo "> Helm Chart: **$HELM_CHART**" >> $GITHUB_STEP_SUMMARY - echo "> Helm Target Revision: **$HELM_TARGET_REVISION**" >> $GITHUB_STEP_SUMMARY - echo "> Docker image: **$IMAGE**" >> $GITHUB_STEP_SUMMARY - - echo "::notice::Helm Repo URL: $OCI_REPO" - echo "::notice::Helm Chart: $HELM_CHART" - echo "::notice::Helm Target Revision: $HELM_TARGET_REVISION" - - echo "helm_repo_url=$OCI_REPO" >> $GITHUB_OUTPUT - echo "helm_chart=$HELM_CHART" >> $GITHUB_OUTPUT - echo "helm_target_revision=$HELM_TARGET_REVISION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..2e9da88 --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,402 @@ +# NOTE: Shared workflow for other repo +name: Build, Test & Release + +on: + workflow_call: + inputs: + # Job: Pre-commit + pre_commit__apt_packages: + type: string + description: "APT packages required for pre-commit checks" + required: false + default: "" + # Job: Docker + docker__push: + type: boolean + description: "Push docker image to ghcr registry" + default: false + docker__wait_cmd: + type: string + description: "Command used to wait to prep containers before starting checks/tests" + required: true + docker__migration_dummy_test: + type: string + description: "Test for running dummy test to run django migrations" + required: true + docker__run_graphql_check: + type: boolean + description: "Run GraphQL schema validation step" + default: false + docker__run_openapi_check: + type: boolean + description: "Run OpenAPI schema validation step" + default: false + docker__run_openapi_check_schema_filepath: + type: string + description: "Path to the tracked openAPI schema file" + default: "" + docker__compose_file: + type: string + description: "Docker Compose file to use for running containers" + default: "docker-compose.yml" + docker__compose_service_name: + type: string + description: "Docker Compose service name for the Django application" + default: "web" + docker__compose_test_cmd: + type: string + description: "Test command run inside the container" + required: true + docker__compose_ci_share_host_dir: + type: string + description: "Host directory used for ci-share in compose file" + default: "./ci-share" + # -- Extra steps + docker__post_test__extra_step_01: + type: string + description: "Extra test step - 01: Name" + default: "(Post test) Extra test step - 01" + docker__post_test__extra_step_01_command: + type: string + description: "(Post test) Extra test step - 01 - Command" + default: "" + # Job: Helm + helm__push: + type: boolean + description: "Push helm chart to ghcr registry" + default: false + helm__chart_directory: + type: string + description: "Path to the Helm chart directory" + required: true + # TODO: Job: Release + outputs: + # Docker + docker_image_name: + description: "Only docker image name" + value: ${{ jobs.docker_build.outputs.docker_image_name }} + docker_image_tag: + description: "Only docker image tag" + value: ${{ jobs.docker_build.outputs.docker_image_tag }} + docker_image: + description: "docker image with tag" + value: ${{ jobs.docker_build.outputs.docker_image }} + # Helm + helm_repo_url: + description: "Helm repo URL" + value: ${{ jobs.helm_publish.outputs.helm_repo_url }} + helm_chart: + description: "Helm Chart" + value: ${{ jobs.helm_publish.outputs.helm_chart }} + helm_target_revision: + description: "Helm target revision" + value: ${{ jobs.helm_publish.outputs.helm_target_revision }} + +env: + # NOTE: Remove this after all actions are upgraded to node24 + ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true + COMPOSE_CI_SHARE_DIR: ${{ inputs.docker__compose_ci_share_host_dir }} + +jobs: + pre_commit_checks: + name: Pre-Commit checks + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@main + with: + submodules: true + + - uses: actions/setup-python@v6 + with: + python-version-file: '.python-version' + + - uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 + if: ${{ inputs.pre_commit__apt_packages != '' }} + with: + packages: ${{ inputs.pre_commit__apt_packages }} + + - name: Setup uv python environment + run: uv venv + + - name: uv lock check + run: uv lock --locked --offline + + - name: uv sync + run: uv sync --all-groups --all-extras + + - uses: pre-commit/action@main + + docker_build: + name: Test + runs-on: ubuntu-latest + needs: pre_commit_checks + env: + COMPOSE_FILE: ${{ inputs.docker__compose_file }} + SERVICE_NAME: ${{ inputs.docker__compose_service_name }} + + outputs: + docker_image_name: ${{ steps.prep.outputs.tagged_image_name }} + docker_image_tag: ${{ steps.prep.outputs.tag }} + docker_image: ${{ steps.prep.outputs.tagged_image }} + + steps: + - uses: actions/checkout@main + with: + submodules: true + + - name: Login to GitHub Container Registry + if: ${{ inputs.docker__push }} + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿณ Prepare Docker + id: prep + env: + IMAGE_NAME: ghcr.io/${{ github.repository }} + run: | + BRANCH_NAME="${GITHUB_REF_NAME,,}" # lowercase + BRANCH_NAME="${BRANCH_NAME:0:100}" # truncate + + SHORT_SHA="${GITHUB_SHA:0:7}" + + if [[ "$BRANCH_NAME" == *"/"* ]]; then + IMAGE_NAME="${IMAGE_NAME}-dev" + BRANCH_NAME="${BRANCH_NAME//\//-}" # Replace / with _ + TAG="${BRANCH_NAME//\//-}.c${SHORT_SHA}" + else + TAG="${BRANCH_NAME}.c${SHORT_SHA}" + fi + + IMAGE_NAME="${IMAGE_NAME,,}" + + { + echo "tagged_image_name=${IMAGE_NAME}" + echo "tag=${TAG}" + echo "tagged_image=${IMAGE_NAME}:${TAG}" + } >> "$GITHUB_OUTPUT" + + - uses: docker/setup-buildx-action@v4 + + - name: ๐Ÿณ Build image + uses: docker/build-push-action@v7 + with: + context: . + file: Dockerfile + load: true + push: false + provenance: false + tags: ${{ steps.prep.outputs.tagged_image }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Prep django containers + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + run: | + touch .env + docker compose run --rm "$SERVICE_NAME" \ + ${{ inputs.docker__wait_cmd }} + + - name: ๐Ÿ•ฎ Validate django migrations + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + run: | + docker compose run --rm "$SERVICE_NAME" \ + bash -c './manage.py makemigrations --check --dry-run' || { + echo "There are some changes to be reflected in the migration. Make sure to run makemigrations"; + } + + - name: Run django migrations (Using dummy test) + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + TMP_TEST_PATTERN: ${{ inputs.docker__migration_dummy_test }} + run: | + docker compose run --rm "$SERVICE_NAME" \ + ./manage.py test --keepdb -v 2 "$TMP_TEST_PATTERN" + + - name: ๐Ÿ•ฎ Validate latest graphql schema + if: ${{ inputs.docker__run_graphql_check }} + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + run: | + docker compose run --rm "$SERVICE_NAME" \ + ./manage.py graphql_schema --out /ci-share/schema-latest.graphql + + # Compare with the committed schema + if ! cmp --silent schema.graphql ./ci-share/schema-latest.graphql; then + echo 'The schema.graphql is not up to date with the latest changes. Please update and push latest' + diff schema.graphql ./ci-share/schema-latest.graphql + exit 1 + fi + + # NOTE: Schema generation requires a valid database. Therefore, this step must run after "Run Django migrations." + - name: ๐Ÿ•ฎ Validate latest openapi schema + if: ${{ inputs.docker__run_openapi_check }} + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + OPENAPI_FILEPATH: ${{ inputs.docker__run_openapi_check_schema_filepath }} + run: | + docker compose run --rm "$SERVICE_NAME" \ + ./manage.py spectacular --file /ci-share/openapi-schema-latest.yaml + + # Compare with the committed schema + if ! cmp --silent "$OPENAPI_FILEPATH" ./ci-share/openapi-schema-latest.yaml; then + echo "The '$OPENAPI_FILEPATH' is not up to date with the latest changes. Please update and push latest" + diff "$OPENAPI_FILEPATH" ./ci-share/openapi-schema-latest.yaml + exit 1 + fi + + - name: ๐Ÿคž Run tests + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + run: | + docker compose run --rm "$SERVICE_NAME" \ + ${{ inputs.docker__compose_test_cmd }} + + - name: ${{ inputs.docker__post_test__extra_step_01 }} + if: ${{ inputs.docker__post_test__extra_step_01_command != '' }} + env: + DOCKER_IMAGE_BACKEND: ${{ steps.prep.outputs.tagged_image }} + run: ${{ inputs.docker__post_test__extra_step_01_command }} + + - name: ๐Ÿณ Docker push + if: ${{ inputs.docker__push }} + run: docker push ${{ steps.prep.outputs.tagged_image }} + + helm_validate: + name: Validate Helm + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ${{ inputs.helm__chart_directory }} + + steps: + - uses: actions/checkout@main + with: + submodules: true + + - name: Install Helm + # TODO: Upgrade to v5 + uses: azure/setup-helm@v4 + + - name: ๐Ÿณ Helm dependency + run: | + yq --indent 0 '.dependencies | map(select(.repository | test("^oci:") | not)) | map(["helm", "repo", "add", .name, .repository] | join(" ")) | .[]' Chart.lock | sh -- + helm dependency build . + + - name: Helm lint + run: helm lint . + + - name: Helm template (snapshot) + run: ./update-snapshots.sh --check-diff-only + + helm_publish: + name: Publish Helm + if: ${{ inputs.helm__push }} + needs: + - docker_build + - helm_validate + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.helm__chart_directory }} + + outputs: + helm_repo_url: ${{ steps.push.outputs.helm_repo_url }} + helm_chart: ${{ steps.push.outputs.helm_chart }} + helm_target_revision: ${{ steps.push.outputs.helm_target_revision }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: true + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Helm + # TODO: Upgrade to v5 + uses: azure/setup-helm@v4 + + - name: ๐Ÿณ Helm dependency + run: | + yq --indent 0 '.dependencies | map(select(.repository | test("^oci:") | not)) | map(["helm", "repo", "add", .name, .repository] | join(" ")) | .[]' Chart.lock | sh -- + helm dependency build . + + - name: Tag docker image in Helm Chart values.yaml + env: + IMAGE_NAME: ${{ needs.docker_build.outputs.docker_image_name }} + IMAGE_TAG: ${{ needs.docker_build.outputs.docker_image_tag }} + run: | + sed -i "s|SET-BY-CICD-IMAGE|$IMAGE_NAME|" values.yaml + sed -i "s/SET-BY-CICD-TAG/$IMAGE_TAG/" values.yaml + + - name: Package Helm Chart + env: + IMAGE_TAG: ${{ needs.docker_build.outputs.docker_image_tag }} + run: | + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + TAG="-$(echo "$GITHUB_SHA" | head -c7)" + sed -i "s/-SET-BY-CICD/$TAG/g" Chart.yaml + else + if [[ "$GITHUB_REF_NAME" == *"/"* ]]; then + sed -i 's/^\(name:.*\)-helm$/\1-dev-helm/' Chart.yaml + fi + sed -i "s/SET-BY-CICD/$IMAGE_TAG/g" Chart.yaml + fi + mkdir -p .helm-charts + helm package . -d .helm-charts + + - name: Push Helm Chart + id: push + env: + IMAGE: ${{ needs.docker_build.outputs.docker_image }} + OCI_REPO: oci://ghcr.io/${{ github.repository_owner }} + run: | + # Ensure OCI_REPO is lowercase + OCI_REPO="${OCI_REPO,,}" + + # Safely get the first .tgz file + PACKAGE_FILE=$(find .helm-charts -maxdepth 1 -name '*.tgz' | head -n 1) + + HELM_CHART=$(helm show chart "$PACKAGE_FILE" | grep '^name:' | awk '{print $2}') + HELM_TARGET_REVISION=$(helm show chart "$PACKAGE_FILE" | grep '^version:' | awk '{print $2}') + + { + echo "# Helm Chart" + echo "" + echo '```yaml' + helm push "$PACKAGE_FILE" "$OCI_REPO" 2>&1 + echo '```' + + echo "> [!IMPORTANT]" + echo "> Helm Repo URL: **$OCI_REPO**" + echo "> Helm Chart: **$HELM_CHART**" + echo "> Helm Target Revision: **$HELM_TARGET_REVISION**" + echo "> Docker image: **$IMAGE**" + } >> "$GITHUB_STEP_SUMMARY" + + # Annotations + echo "::notice::Helm Repo URL: $OCI_REPO" + echo "::notice::Helm Chart: $HELM_CHART" + echo "::notice::Helm Target Revision: $HELM_TARGET_REVISION" + + { + echo "helm_repo_url=$OCI_REPO" + echo "helm_chart=$HELM_CHART" + echo "helm_target_revision=$HELM_TARGET_REVISION" + } >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3700d2a..d0e34c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,11 +5,9 @@ on: tags: - "v*.*.*" - permissions: contents: write - jobs: generate-release: name: Generate release @@ -19,9 +17,11 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + submodules: true - name: Set the release version id: release + shell: bash run: | RELEASE_VERSION=${GITHUB_REF:11} @@ -30,8 +30,11 @@ jobs: IS_PRERELEASE=true fi - echo "release_version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT - echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT + { + echo "release_version=${RELEASE_VERSION}" + echo "is_prerelease=${IS_PRERELEASE}" + } >> "$GITHUB_OUTPUT" + echo "::notice::Release version: ${RELEASE_VERSION}" echo "::notice::Is prerelease: ${IS_PRERELEASE}" @@ -39,8 +42,11 @@ jobs: uses: orhun/git-cliff-action@main id: git-cliff with: - config: cliff.toml + config: fugit/configs/cliff.toml args: -vv --latest --no-exec --github-repo ${{ github.repository }} --strip all + env: + GIT_CLIFF__REMOTE__GITHUB__OWNER: toggle-corp + GIT_CLIFF__REMOTE__GITHUB__REPO: toggle-django-action - name: Create Github Release uses: softprops/action-gh-release@v2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..49d5a2c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "fugit"] + path = fugit + url = git@github.com:toggle-corp/fugit.git + branch = v0.1.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4e6f75d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +repos: + - repo: https://github.com/rhysd/actionlint + rev: v1.7.11 + hooks: + - id: actionlint + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + - id: check-yaml + - id: detect-private-key + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/crate-ci/typos + rev: v1.31.1 + hooks: + - id: typos + args: ["--exclude=CHANGELOG.md", "--force-exclude"] + + - repo: https://github.com/gitleaks/gitleaks + rev: v8.24.2 + hooks: + - id: gitleaks diff --git a/TODO.md b/TODO.md index 692a94d..1c98905 100644 --- a/TODO.md +++ b/TODO.md @@ -11,3 +11,5 @@ - compose_file: "docker-compose.yml:.github/docker-compose.yaml" - Needs to have this directory for ci-share - used by graphql_schema +- Check https://github.com/helm/chart-releaser-action +- add example project here to test ./.github/workflows/pipeline.yml diff --git a/cliff.toml b/cliff.toml deleted file mode 100644 index c757aa7..0000000 --- a/cliff.toml +++ /dev/null @@ -1,148 +0,0 @@ -# git-cliff ~ configuration file -# https://git-cliff.org/docs/configuration - -[remote.github] -owner = "toggle-corp" -repo = "toggle-django-action" - -[changelog] -# A Tera template to be rendered as the changelog's header. -# See https://keats.github.io/tera/docs/#introduction -header = """ -# Changelog\n -""" -# A Tera template to be rendered for each release in the changelog. -# See https://keats.github.io/tera/docs/#introduction -body = """ -{%- macro remote_url() -%} - https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} -{%- endmacro -%} - -{%- macro print_contributor(username) -%} - [@{{ username }}](https://github.com/{{ username }}) -{%- endmacro -%} - -{% macro print_commit(commit) -%} - - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ - {% if commit.breaking %}[**breaking**] {% endif %}\ - {{ commit.message | upper_first }} - \ - ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\ -{% endmacro -%} - -{% if version %}\ - {% if previous.version %}\ - ## [{{ version | trim_start_matches(pat="v") }}]\ - ({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} - {% else %}\ - ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} - {% endif %}\ -{% else %}\ - ## [unreleased] -{% endif %}\ - -### Changes: -{% for group, commits in commits | group_by(attribute="group") %} - #### {{ group | striptags | trim | upper_first }} - {% for commit in commits - | filter(attribute="scope") - | sort(attribute="scope") %} - {{ self::print_commit(commit=commit) }} - {%- endfor %} - {% for commit in commits %} - {%- if not commit.scope -%} - {{ self::print_commit(commit=commit) }} - {% endif -%} - {% endfor -%} -{% endfor -%} - -{%- set pr_commits = commits | filter(attribute="remote.pr_number") | sort(attribute="remote.pr_number") -%} -{% if pr_commits | length > 0 %} -### ๐Ÿป Pull Requests ({{ pr_commits | length }}) -{%- for commit in pr_commits %} -- (#{{ commit.remote.pr_number }}) \ - [{{ commit.remote.pr_title | upper_first }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) -{%- endfor %} -{% endif %} - -{%- if github -%} -{%- set new_contributors = github.contributors | filter(attribute="is_first_time", value=true) -%} -{% if new_contributors | length > 0 %} -### :tada: New Contributors ({{ new_contributors | length }}) -{% endif %}\ -{% for contributor in new_contributors %} - - {{ self::print_contributor(username=contributor.username) }} made their first contribution - {%- if contributor.pr_number %} in \ - [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ - {%- endif %} -{%- endfor -%} -{%- endif %} - - -""" -# A Tera template to be rendered as the changelog's footer. -# See https://keats.github.io/tera/docs/#introduction -footer = """ - -""" -# Remove leading and trailing whitespaces from the changelog's body. -trim = true -# An array of regex based postprocessors to modify the changelog. -postprocessors = [ - # Replace the placeholder `` with a URL. - { pattern = '', replace = "https://github.com/toggle-corp/toggle-toggle-django-action" }, # replace repository URL -] - -[git] -# Parse commits according to the conventional commits specification. -# See https://www.conventionalcommits.org -conventional_commits = true -# Exclude commits that do not match the conventional commits specification. -filter_unconventional = false -# Split commits on newlines, treating each line as an individual commit. -split_commits = false -# An array of regex based parsers to modify commit messages prior to further processing. -commit_preprocessors = [ - # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. - { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, - # Check spelling of the commit message using https://github.com/crate-ci/typos. - # If the spelling is incorrect, it will be fixed automatically. - { pattern = '.*', replace_command = 'typos --write-changes -' }, -] -# An array of regex based parsers for extracting data from the commit message. -# Assigns commits to groups. -# Optionally sets the commit's scope and can decide to exclude commits from further processing. -commit_parsers = [ - { message = "^feat", group = "๐Ÿš€ Features" }, - { message = "^fix", group = "๐Ÿ› Bug Fixes" }, - { message = "^doc", group = "๐Ÿ“š Documentation" }, - { message = "^perf", group = "โšก Performance" }, - { message = "^refactor\\(clippy\\)", skip = true }, - { message = "^refactor", group = "๐Ÿšœ Refactor" }, - { message = "^style", group = "๐ŸŽจ Styling" }, - { message = "^test", group = "๐Ÿงช Testing" }, - { message = "^chore\\(release\\): prepare for", skip = true }, - { message = "^chore\\(deps.*\\)", skip = true }, - { message = "^chore\\(pr\\)", skip = true }, - { message = "^chore\\(pull\\)", skip = true }, - { message = "^chore\\(npm\\).*yarn\\.lock", skip = true }, - { message = "^chore|^ci", group = "โš™๏ธ Miscellaneous Tasks" }, - { body = ".*security", group = "๐Ÿ›ก๏ธ Security" }, - { message = "^revert", group = "โ—€๏ธ Revert" }, -] -# Prevent commits that are breaking from being excluded by commit parsers. -protect_breaking_commits = false -# Exclude commits that are not matched by any commit parser. -filter_commits = false -# Regex to select git tags that represent releases. -tag_pattern = "v[0-9].*" -# Regex to select git tags that do not represent proper releases. -# Takes precedence over `tag_pattern`. -# Changes belonging to these releases will be included in the next release. -skip_tags = "beta|alpha" -# Regex to exclude git tags after applying the tag_pattern. -ignore_tags = "rc|v2.1.0|v2.1.1" -# Order releases topologically instead of chronologically. -topo_order = false -# Order of commits in each group/release within the changelog. -# Allowed values: newest, oldest -sort_commits = "newest" diff --git a/fugit b/fugit new file mode 160000 index 0000000..1460a27 --- /dev/null +++ b/fugit @@ -0,0 +1 @@ +Subproject commit 1460a27ff4048425fb3efb34dfc35ea1293fe8b7 diff --git a/release.sh b/release.sh index 854a9a9..ae26a87 100755 --- a/release.sh +++ b/release.sh @@ -1,65 +1,20 @@ -#!/usr/bin/env bash -# Original https://github.com/orhun/git-cliff/blob/main/release.sh -set -e +#!/bin/bash -if ! command -v typos &>/dev/null; then - echo "typos is not installed. Run 'cargo install typos-cli' to install it, otherwise the typos won't be fixed" -fi +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export SCRIPT_DIR -if ! command -v semver &>/dev/null; then - echo "semver is required to validate the tag." -fi - -version_tag="$1" - -if [ -z "$version_tag" ]; then - echo "Please provide a tag." - echo "Usage: ./release.sh v[X.Y.Z]" - exit -fi - -if semver valid "$version_tag" > /dev/null; then - echo "Valid SemVer: $version_tag" -else - echo "Invalid SemVer: \"$version_tag\"" >&2 - echo "Eg: 0.1.1 0.1.1-dev0" - exit 1 -fi - -# Define your cleanup or final function -exit_message() { - echo "-----------------" - echo "If you aren't happy with these changes. try again with" - echo "git reset --soft HEAD~1" - echo "git tag -d $version_tag" +function release_custom_hook { + # shellcheck disable=SC2154 + echo "No custom hook for: \"${version_tag#v}\"" } -trap exit_message EXIT - - -BASE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd "$BASE_DIR" - -echo "Preparing $version_tag..." -# update the changelog -git-cliff --config cliff.toml --tag "$version_tag" > CHANGELOG.md -git add CHANGELOG.md -git commit -m "chore(release): prepare for $version_tag" -git show +export -f release_custom_hook +export START_COMMIT=8e193ed343a53166dd0a54511ea3cd0ea07fec96 +export RELEASE_CUSTOM_HOOK=release_custom_hook +export REPO_NAME=toggle-corp/toggle-django-action +export DEFAULT_BRANCH=develop -# generate a changelog for the tag message -export GIT_CLIFF_TEMPLATE="\ - {% for group, commits in commits | group_by(attribute=\"group\") %} - {{ group | upper_first }}\ - {% for commit in commits %} - - {% if commit.breaking %}(breaking) {% endif %}{{ commit.message | upper_first }} ({{ commit.id | truncate(length=7, end=\"\") }})\ - {% endfor %} - {% endfor %}" -changelog=$(git-cliff --config detailed.toml --unreleased --strip all) +export GIT_CLIFF__REMOTE__GITHUB__OWNER=toggle-corp +export GIT_CLIFF__REMOTE__GITHUB__REPO=toggle-django-action -# create a signed tag -# https://keyserver.ubuntu.com/pks/lookup?search=0x4A92FA17B6619297&op=vindex -git tag "$version_tag" -m "Release $version_tag" -m "$changelog" -git tag -v "$version_tag" -echo "Done!" -echo "Now push the commit (git push) and the tag ( git push origin tag $version_tag)." +"$SCRIPT_DIR/fugit/scripts/release.sh"