diff --git a/.github/workflows/auto-pull-request-create.yml b/.github/workflows/auto-pull-request-create.yml index 8bd37f8..c4c84bd 100644 --- a/.github/workflows/auto-pull-request-create.yml +++ b/.github/workflows/auto-pull-request-create.yml @@ -1,10 +1,11 @@ -name: (Auto) Pull Request Create +name: (Automatic) Pull Request Create on: push: branches-ignore: - master - main + - release/** - dependabot/** permissions: diff --git a/.github/workflows/auto-release-create.yml b/.github/workflows/auto-release-create.yml new file mode 100644 index 0000000..0922cd8 --- /dev/null +++ b/.github/workflows/auto-release-create.yml @@ -0,0 +1,32 @@ +name: (Automatic) Release Create + +on: + push: + branches: + - master + - main + workflow_dispatch: + inputs: + release_branch: + description: Release branch to publish from (e.g. release/v1.3.0) + required: false + default: '' + type: string + release_version: + description: Explicit release version override (e.g. v1.3.0) + required: false + default: '' + type: string + +permissions: + contents: write + pull-requests: read + +jobs: + call: + uses: devops-infra/.github/.github/workflows/reusable-auto-release-create.yml@v1 + with: + profile: dockerized + release-branch: ${{ inputs.release_branch }} + release-version: ${{ inputs.release_version }} + secrets: inherit diff --git a/.github/workflows/manual-release-branch-prepare.yml b/.github/workflows/manual-release-branch-prepare.yml new file mode 100644 index 0000000..81e7b12 --- /dev/null +++ b/.github/workflows/manual-release-branch-prepare.yml @@ -0,0 +1,39 @@ +name: (Manual) Release Branch Prepare + +on: + workflow_dispatch: + inputs: + type: + description: Bump type + required: false + default: patch + type: choice + options: + - patch + - minor + - major + - set + version: + description: Explicit version when type="set" (e.g., v1.2.3) + required: false + default: '' + build_only: + description: Build and push artifacts without version bump + required: false + default: false + type: boolean + +permissions: + contents: write + packages: write + pull-requests: write + +jobs: + call: + uses: devops-infra/.github/.github/workflows/reusable-manual-release-branch-prepare.yml@v1 + with: + bump-type: ${{ inputs.type }} + explicit-version: ${{ inputs.version }} + build-and-push-only: ${{ inputs.build_only }} + profile: dockerized + secrets: inherit diff --git a/.gitignore b/.gitignore index 274f060..22f7e67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Intellij -/.idea/ +.idea/ *.iml # Custom @@ -10,3 +10,10 @@ .envrc .env .tmp + +# Python +build/ +dist/ +*.egg-info/ +*.pyc +__py diff --git a/Taskfile.cicd.yml b/Taskfile.cicd.yml index f32386f..a997bcd 100644 --- a/Taskfile.cicd.yml +++ b/Taskfile.cicd.yml @@ -1,12 +1,9 @@ version: '3' - silent: true - vars: PR_TEMPLATE: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/PULL_REQUEST_TEMPLATE.md CONFIGS_BASE_URL: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/templates/dockerized/configs TASKFILES_BASE_URL: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/templates/dockerized/taskfiles - tasks: pre-commit: desc: Run all pre-commit hooks @@ -29,34 +26,12 @@ tasks: lint:actionlint: desc: Lint GitHub Actions workflows with actionlint cmds: - - | - echo "▶️ Running actionlint..." - set +e - docker run --rm -i -v "$PWD:/work" -w /work rhysd/actionlint:latest -color - rc=$? - set -e - if [ "$rc" -eq 0 ]; then - echo "✅ actionlint passed" - else - echo "❌ actionlint failed" - exit $rc - fi + - task: scripts:lint:actionlint lint:hadolint: desc: Lint Dockerfile with hadolint cmds: - - | - echo "▶️ Running hadolint..." - set +e - docker run --rm -i -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian < Dockerfile - rc=$? - set -e - if [ "$rc" -eq 0 ]; then - echo "✅ hadolint passed" - else - echo "❌ hadolint failed" - exit $rc - fi + - task: scripts:lint:hadolint lint:shellcheck: desc: Lint shell scripts with shellcheck @@ -66,36 +41,17 @@ tasks: lint:yamllint: desc: Lint YAML files with yamllint cmds: - - | - echo "▶️ Running yamllint..." - set +e - docker run --rm -i -v "$PWD:/work" -w /work cytopia/yamllint -c .yamllint.yml . - rc=$? - set -e - if [ "$rc" -eq 0 ]; then - echo "✅ yamllint passed" - else - echo "❌ yamllint failed" - exit $rc - fi + - task: scripts:lint:yamllint dependency:update: - desc: Update repository dependencies not covered by dependabot + desc: 'No-op: no dedicated dependency updater configured for this profile' cmds: - - task: scripts:packages:update + - task: scripts:dependency:update version:set: desc: Validate version cmds: - - | - if [ -z "{{.VERSION}}" ]; then - echo "❌ ERROR: VERSION is empty" - exit 1 - fi - if ! echo "{{.VERSION}}" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "❌ ERROR: VERSION '{{.VERSION}}' is not a valid semantic version (expected vX.Y.Z or X.Y.Z)" - exit 1 - fi + - task: scripts:version:set version:update:patch: desc: Increment patch version (e.g., 1.2.3 -> 1.2.4) @@ -115,135 +71,24 @@ tasks: version:resolve-next: desc: Resolve next version from bump type and profile cmds: - - | - set -eu - bump_type="${BUMP_TYPE:-patch}" - input_version="${INPUT_VERSION:-}" - - normalize_version() { - candidate="${1#v}" - if ! printf "%s" "${candidate}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then - return 1 - fi - printf "v%s" "${candidate}" - } - - current="$(task version:get 2>/dev/null || true)" - - case "$bump_type" in - set) - [ -n "$input_version" ] || { echo "Missing version for type=set"; exit 1; } - next="$(normalize_version "$input_version")" || { - echo "Invalid explicit version: $input_version. Expected vX.Y.Z or X.Y.Z" - exit 1 - } - ;; - patch|minor|major) - [ -n "$current" ] || { echo "Current version not found or invalid. Expected vX.Y.Z"; exit 1; } - current="$(normalize_version "$current")" || { echo "Current version not found or invalid. Expected vX.Y.Z"; exit 1; } - no_v="${current#v}" - major="$(printf "%s" "$no_v" | awk -F. '{print $1}')" - minor="$(printf "%s" "$no_v" | awk -F. '{print $2}')" - patch="$(printf "%s" "$no_v" | awk -F. '{print $3}')" - case "$bump_type" in - patch) next="v${major}.${minor}.$((patch + 1))" ;; - minor) next="v${major}.$((minor + 1)).0" ;; - major) next="v$((major + 1)).0.0" ;; - esac - ;; - *) - echo "Unknown type: $bump_type" - exit 1 - ;; - esac - - printf "%s" "$next" + - task: scripts:version:resolve-next version:tag-release: desc: Create set of git tags cmds: - - | - set -eu - if (set -o | grep -q pipefail) 2>/dev/null; then set -o pipefail; fi - - REMOTE='origin' - FULL='{{.VERSION_FULL}}' - MINOR='{{.VERSION_MINOR}}' - MAJOR='{{.VERSION_MAJOR}}' - - # Validate vX.Y.Z - if ! printf "%s" "$FULL" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "❌ ERROR: VERSION '$FULL' must match vX.Y.Z" >&2 - exit 1 - fi - - tag_sha() { git rev-parse "refs/tags/$1" 2>/dev/null || true; } - remote_tag_sha() { git ls-remote --tags "$REMOTE" "refs/tags/$1" 2>/dev/null | awk '{print $1}' || true; } - - echo "ℹ️ INFO: Tags - Full: $FULL | Minor: $MINOR | Major: $MAJOR" - - # Full tag: must NOT exist on remote; fail fast if it does - full_remote_sha="$(remote_tag_sha "$FULL")" - if [ -n "$full_remote_sha" ]; then - echo "❌ ERROR: Full tag '$FULL' already exists on remote; aborting" >&2 - exit 1 - fi - - # Create full tag locally (if missing) and push - if git rev-parse --quiet --verify "refs/tags/$FULL" >/dev/null 2>&1; then - echo "ℹ️ INFO: Full tag '$FULL' exists locally but not on remote; pushing" - else - echo "ℹ️ INFO: Creating full tag '$FULL'" - git tag --annotate "$FULL" --message "$FULL" - fi - git push "$REMOTE" "refs/tags/$FULL" - echo "✅ OK: Pushed full tag '$FULL'" - - # Minor tag: create or update - git tag --force --annotate "$MINOR" --message "$FULL" - minor_local_sha="$(tag_sha "$MINOR")" - minor_remote_sha="$(remote_tag_sha "$MINOR")" - if [ -z "$minor_remote_sha" ]; then - git push "$REMOTE" "refs/tags/$MINOR" - echo "✅ OK: Created and pushed minor tag '$MINOR' -> $minor_local_sha" - else - if [ "$minor_local_sha" != "$minor_remote_sha" ]; then - echo "⚠️ WARN: Updating remote minor tag '$MINOR' to $minor_local_sha (was $minor_remote_sha)" - git push --force "$REMOTE" "refs/tags/$MINOR" - else - echo "ℹ️ INFO: Minor tag '$MINOR' already up-to-date" - fi - fi - - # Major tag: create or update - git tag --force --annotate "$MAJOR" --message "$FULL" - major_local_sha="$(tag_sha "$MAJOR")" - major_remote_sha="$(remote_tag_sha "$MAJOR")" - if [ -z "$major_remote_sha" ]; then - git push "$REMOTE" "refs/tags/$MAJOR" - echo "✅ OK: Created and pushed major tag '$MAJOR' -> $major_local_sha" - else - if [ "$major_local_sha" != "$major_remote_sha" ]; then - echo "⚠️ WARN: Updating remote major tag '$MAJOR' to $major_local_sha (was $major_remote_sha)" - git push --force "$REMOTE" "refs/tags/$MAJOR" - else - echo "ℹ️ INFO: Major tag '$MAJOR' already up-to-date" - fi - fi + - task: scripts:version:tag-release git:get-pr-template: desc: Get pull request template cmds: - - mkdir -p .tmp - - curl -LsS {{.PR_TEMPLATE}} -o .tmp/PULL_REQUEST_TEMPLATE.md + - task: scripts:git:get-pr-template git:set-config: desc: Set git user config cmds: - - git config user.name "github-actions[bot]" - - git config user.email "github-actions[bot]@users.noreply.github.com" + - task: scripts:git:set-config version:get: desc: Get current version cmds: - - echo "{{.VERSION}}" + - task: scripts:version:get diff --git a/Taskfile.docker.yml b/Taskfile.docker.yml index 1407f3b..f0f0baa 100644 --- a/Taskfile.docker.yml +++ b/Taskfile.docker.yml @@ -43,7 +43,7 @@ tasks: docker:cmds: desc: Show full docker build command cmds: - - echo -e '{{.DOCKER_BUILD_START}} {{.DOCKER_BUILD_FINISH}}' | {{.SED}} 's/--/ \\\n --/g' + - echo -e '{{.DOCKER_BUILD_START}} {{.DOCKER_BUILD_FINISH}}' | {{.SED}} 's/--/ \\\n+ --/g' docker:build: desc: Build Docker image @@ -80,7 +80,6 @@ tasks: rc=$? set -e - # Validate that docker inspect returned a non-empty array with an Id has_local=0 if [ "$rc" -eq 0 ] && [ -n "$image_inspect_out" ]; then if echo "$image_inspect_out" | jq -e 'type=="array" and (length > 0) and \ diff --git a/Taskfile.scripts.yml b/Taskfile.scripts.yml index 7aded7f..6dd8909 100644 --- a/Taskfile.scripts.yml +++ b/Taskfile.scripts.yml @@ -45,11 +45,23 @@ tasks: lint:shellcheck: desc: Lint shell scripts with shellcheck + shell: bash cmds: - | echo "▶️ Running shellcheck..." + mapfile -t files < <(git ls-files '*.sh') + existing_files=() + for f in "${files[@]}"; do + if [ -f "$f" ]; then + existing_files+=("$f") + fi + done + if [ "${#existing_files[@]}" -eq 0 ]; then + echo "ℹ️ No shell scripts found, skipping shellcheck" + exit 0 + fi set +e - docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style entrypoint.sh + docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style -- "${existing_files[@]}" rc=$? set -e if [ "$rc" -eq 0 ]; then @@ -75,11 +87,19 @@ tasks: exit $rc fi + dependency:update: + desc: 'No-op: no dedicated dependency updater configured for this profile' + cmds: + - | + echo "INFO: No dedicated dependency updater configured for this repository profile." + echo "INFO: Dependabot handles GitHub Actions and package metadata updates." + echo "INFO: Keep this task as a safe no-op until a repo-specific dependency updater is defined." + git:get-pr-template: desc: Get pull request template cmds: - mkdir -p .tmp - - curl -LsS https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/PULL_REQUEST_TEMPLATE.md -o .tmp/PULL_REQUEST_TEMPLATE.md + - curl -LsS https://raw.githubusercontent.com/devops-infra/.github/master/PULL_REQUEST_TEMPLATE.md -o .tmp/PULL_REQUEST_TEMPLATE.md git:set-config: desc: Set git user config @@ -231,10 +251,6 @@ tasks: echo "❌ ERROR: VERSION is empty" exit 1 fi - if ! echo "{{.VERSION}}" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "❌ ERROR: VERSION '{{.VERSION}}' is not a valid semantic version (expected vX.Y.Z or X.Y.Z)" - exit 1 - fi version:update:patch: desc: Increment patch version (e.g., 1.2.3 -> 1.2.4) @@ -251,6 +267,53 @@ tasks: cmds: - task version:set VERSION=v{{.NEXT_MAJOR}}.0.0 + version:resolve-next: + desc: Resolve next version from bump type and profile + cmds: + - | + set -eu + bump_type="${BUMP_TYPE:-patch}" + input_version="${INPUT_VERSION:-}" + + normalize_version() { + candidate="${1#v}" + if ! printf "%s" "${candidate}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + return 1 + fi + printf "v%s" "${candidate}" + } + + current="$(task version:get 2>/dev/null || true)" + + case "$bump_type" in + set) + [ -n "$input_version" ] || { echo "Missing version for type=set"; exit 1; } + next="$(normalize_version "$input_version")" || { + echo "Invalid explicit version: $input_version. Expected vX.Y.Z or X.Y.Z" + exit 1 + } + ;; + patch|minor|major) + [ -n "$current" ] || { echo "Current version not found or invalid. Expected vX.Y.Z"; exit 1; } + current="$(normalize_version "$current")" || { echo "Current version not found or invalid. Expected vX.Y.Z"; exit 1; } + no_v="${current#v}" + major="$(printf "%s" "$no_v" | awk -F. '{print $1}')" + minor="$(printf "%s" "$no_v" | awk -F. '{print $2}')" + patch="$(printf "%s" "$no_v" | awk -F. '{print $3}')" + case "$bump_type" in + patch) next="v${major}.${minor}.$((patch + 1))" ;; + minor) next="v${major}.$((minor + 1)).0" ;; + major) next="v$((major + 1)).0.0" ;; + esac + ;; + *) + echo "Unknown type: $bump_type" + exit 1 + ;; + esac + + printf "%s" "$next" + version:tag-release: desc: Create set of git tags cmds: diff --git a/alpine-packages.txt b/alpine-packages.txt index e037727..421e2d0 100644 --- a/alpine-packages.txt +++ b/alpine-packages.txt @@ -1,6 +1,4 @@ bash~=5.3 -docker~=29.1 -make~=4.4 -ncurses~=6.5 -python3~=3.12 -py3-pip~=25.1 +curl +python3 +py3-pip