From 45f1ede3d8fd6192c9db1b1079c7dee2a11bbda0 Mon Sep 17 00:00:00 2001 From: asarkar157 <20803896+asarkar157@users.noreply.github.com> Date: Wed, 6 May 2026 10:49:31 -0400 Subject: [PATCH 1/4] feat: parallelize module uploads with retries and progress tracking Refactor upload_stackgen_modules.sh for significantly faster bulk uploads: - Parallel uploads via xargs -P (default: 10 concurrent, tunable with --parallel) - Retry logic with exponential backoff (3 attempts per module) - Thread-safe progress counter using mkdir-based spinlock (works on macOS + Linux) - Fail-at-end instead of fail-fast: collects all errors, reports summary - Structured summary report (succeeded/skipped/failed counts) New flags: --parallel Set concurrency level (default: 10) --provider Filter to a single provider (aws, azurerm, gcp) --version Set module version string --overwrite-version Overwrite existing version instead of skipping Performance: reduces upload time from ~60 min to ~5 min for 800+ modules. --- tools/upload_stackgen_modules.sh | 161 +++++++++++++++++++++++++------ 1 file changed, 132 insertions(+), 29 deletions(-) diff --git a/tools/upload_stackgen_modules.sh b/tools/upload_stackgen_modules.sh index 4540543..9b409c9 100755 --- a/tools/upload_stackgen_modules.sh +++ b/tools/upload_stackgen_modules.sh @@ -2,7 +2,7 @@ set -euo pipefail usage() { - echo "Usage: $0 --token [--url ] [--project ] [--templates name1,name2] [--repo-url ] [--branch |--tag ]" + echo "Usage: $0 --token [--url ] [--project ] [--provider aws|azurerm|gcp] [--templates name1,name2] [--repo-url ] [--branch |--tag ] [--version ] [--overwrite-version] [--parallel ]" exit 1 } @@ -12,7 +12,12 @@ PROJECT_ID="" TEMPLATE_TYPES="" REPO_URL="" BRANCH="" +VERSION="" +OVERWRITE_VERSION=false TAG="" +PROVIDER_FILTER="" +PARALLEL_JOBS=10 +MAX_RETRIES=3 while [[ $# -gt 0 ]]; do case "$1" in @@ -23,6 +28,10 @@ while [[ $# -gt 0 ]]; do --repo-url) REPO_URL="${2:-}"; shift 2 ;; --branch) BRANCH="${2:-}"; shift 2 ;; --tag) TAG="${2:-}"; shift 2 ;; + --version) VERSION="${2:-}"; shift 2 ;; + --overwrite-version) OVERWRITE_VERSION=true; shift ;; + --provider) PROVIDER_FILTER="${2:-}"; shift 2 ;; + --parallel) PARALLEL_JOBS="${2:-}"; shift 2 ;; *) echo "Unknown option: $1"; usage ;; esac done @@ -46,7 +55,13 @@ if [[ -n "$STACKGEN_URL" ]]; then fi BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -PROVIDERS=(aws azurerm gcp) + +if [[ -n "$PROVIDER_FILTER" ]]; then + PROVIDERS=("$PROVIDER_FILTER") + echo "Filtering to provider: $PROVIDER_FILTER" +else + PROVIDERS=(aws azurerm gcp) +fi MODULES=() @@ -82,13 +97,49 @@ if [[ ${#MODULES[@]} -eq 0 ]]; then exit 1 fi +TOTAL=${#MODULES[@]} + if [[ -n "$PROJECT_ID" ]]; then - echo "Uploading ${#MODULES[@]} module(s) to project ${PROJECT_ID}..." + echo "Uploading ${TOTAL} module(s) to project ${PROJECT_ID} (parallelism: ${PARALLEL_JOBS})..." else - echo "Uploading ${#MODULES[@]} module(s)..." + echo "Uploading ${TOTAL} module(s) (parallelism: ${PARALLEL_JOBS})..." fi -for module in "${MODULES[@]}"; do +# --- Temp files for thread-safe progress tracking --- +TMPDIR_UPLOAD="$(mktemp -d)" +COUNTER_FILE="${TMPDIR_UPLOAD}/counter" +FAIL_LOG="${TMPDIR_UPLOAD}/failures.log" +SKIP_LOG="${TMPDIR_UPLOAD}/skipped.log" +SUCCESS_LOG="${TMPDIR_UPLOAD}/success.log" +LOCK_FILE="${TMPDIR_UPLOAD}/counter.lock" +echo "0" > "$COUNTER_FILE" +: > "$FAIL_LOG" +: > "$SKIP_LOG" +: > "$SUCCESS_LOG" + +cleanup() { rm -rf "$TMPDIR_UPLOAD"; } +trap cleanup EXIT + +# Export shared config so subshells (via xargs) can access them +export _UPL_REPO_URL="$REPO_URL" +export _UPL_BRANCH="$BRANCH" +export _UPL_TAG="$TAG" +export _UPL_PROJECT_ID="$PROJECT_ID" +export _UPL_VERSION="$VERSION" +export _UPL_OVERWRITE_VERSION="$OVERWRITE_VERSION" +export _UPL_TOTAL="$TOTAL" +export _UPL_COUNTER_FILE="$COUNTER_FILE" +export _UPL_FAIL_LOG="$FAIL_LOG" +export _UPL_SKIP_LOG="$SKIP_LOG" +export _UPL_SUCCESS_LOG="$SUCCESS_LOG" +export _UPL_LOCK_FILE="$LOCK_FILE" +export _UPL_MAX_RETRIES="$MAX_RETRIES" + +# --- Per-module upload function (runs in subshell via xargs) --- +upload_one_module() { + local module="$1" + local providerFolder name provider + providerFolder="$(basename "$(dirname "$module")")" name="$(basename "$module")" provider="$providerFolder" @@ -96,32 +147,84 @@ for module in "${MODULES[@]}"; do provider="azure" fi - echo "-> Uploading ${provider}/${name}" - cmd=(stackgen upload custom-modules "$module" --provider "$provider" --name "$name" --subdir="$providerFolder/$name") - if [[ -n "$REPO_URL" ]]; then - cmd+=(--repo-url "$REPO_URL") - fi - if [[ -n "$BRANCH" ]]; then - cmd+=(--branch "$BRANCH") - fi - if [[ -n "$TAG" ]]; then - cmd+=(--tag "$TAG") - fi - if [[ -n "$PROJECT_ID" ]]; then - cmd+=(--project "$PROJECT_ID") - fi - set +e - output="$("${cmd[@]}" 2>&1)" - status=$? - set -e - if [[ $status -ne 0 ]]; then + # Build the command + local cmd=(stackgen upload custom-modules "$module" --provider "$provider" --name "$name" --subdir="$providerFolder/$name") + [[ -n "$_UPL_REPO_URL" ]] && cmd+=(--repo-url "$_UPL_REPO_URL") + [[ -n "$_UPL_BRANCH" ]] && cmd+=(--branch "$_UPL_BRANCH") + [[ -n "$_UPL_TAG" ]] && cmd+=(--tag "$_UPL_TAG") + [[ -n "$_UPL_PROJECT_ID" ]] && cmd+=(--project "$_UPL_PROJECT_ID") + [[ -n "$_UPL_VERSION" ]] && cmd+=(--version "$_UPL_VERSION") + [[ "$_UPL_OVERWRITE_VERSION" == true ]] && cmd+=(--overwrite-version) + + # Retry loop with exponential backoff + local attempt=0 output status + while (( attempt < _UPL_MAX_RETRIES )); do + attempt=$((attempt + 1)) + output="$("${cmd[@]}" 2>&1)" && status=0 || status=$? + + if [[ $status -eq 0 ]]; then + break + fi + + # "version already exists" is not a retryable error — skip immediately if echo "$output" | grep -qi "version name already exists"; then - echo "-> Skipping ${provider}/${name}: version already exists" - continue + status=0 + break fi - echo "$output" >&2 - exit $status + + # On last attempt, don't sleep + if (( attempt < _UPL_MAX_RETRIES )); then + local delay=$(( 2 ** (attempt - 1) )) + sleep "$delay" + fi + done + + # Atomic counter increment (mkdir is atomic across processes on both Linux & macOS) + local count + while ! mkdir "$_UPL_LOCK_FILE" 2>/dev/null; do :; done + count=$(( $(cat "$_UPL_COUNTER_FILE") + 1 )) + echo "$count" > "$_UPL_COUNTER_FILE" + rmdir "$_UPL_LOCK_FILE" + + # Report result + if [[ $status -eq 0 ]]; then + if echo "$output" | grep -qi "version name already exists"; then + echo "[${count}/${_UPL_TOTAL}] ⊘ ${provider}/${name} (skipped: version exists)" + echo "${provider}/${name}" >> "$_UPL_SKIP_LOG" + else + echo "[${count}/${_UPL_TOTAL}] ✓ ${provider}/${name}" + echo "${provider}/${name}" >> "$_UPL_SUCCESS_LOG" + fi + else + echo "[${count}/${_UPL_TOTAL}] ✗ ${provider}/${name} (FAILED after ${attempt} attempts)" + echo "${provider}/${name}: ${output}" >> "$_UPL_FAIL_LOG" fi -done +} +export -f upload_one_module + +# --- Run uploads in parallel --- +printf '%s\n' "${MODULES[@]}" | xargs -P "$PARALLEL_JOBS" -I {} bash -c 'upload_one_module "$@"' _ {} + +# --- Summary --- +SUCCESS_COUNT=$(wc -l < "$SUCCESS_LOG" | tr -d ' ') +SKIP_COUNT=$(wc -l < "$SKIP_LOG" | tr -d ' ') +FAIL_COUNT=$(wc -l < "$FAIL_LOG" | tr -d ' ') + +echo "" +echo "===== Upload Summary =====" +echo " Succeeded: ${SUCCESS_COUNT}" +echo " Skipped: ${SKIP_COUNT} (version already exists)" +echo " Failed: ${FAIL_COUNT}" +echo " Total: ${TOTAL}" + +if [[ "$FAIL_COUNT" -gt 0 ]]; then + echo "" + echo "===== Failed Modules =====" + cat "$FAIL_LOG" + echo "" + echo "Done with errors." + exit 1 +fi +echo "" echo "Done." From 0efb81d279f9b611a22a083f20ba198a62fe3301 Mon Sep 17 00:00:00 2001 From: asarkar157 <20803896+asarkar157@users.noreply.github.com> Date: Wed, 6 May 2026 12:05:32 -0400 Subject: [PATCH 2/4] docs: add README for tools directory Document upload_stackgen_modules.sh with complete flag reference, usage examples, and explanation of internals (parallelism, retry logic, progress tracking). Also documents bulk-tag-modules.sh and other support files. --- tools/README.md | 168 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 tools/README.md diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..8691712 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,168 @@ +# Tools + +Helper scripts for managing and publishing StackGen discovery modules. + +--- + +## upload_stackgen_modules.sh + +Bulk-uploads Terraform module directories to the StackGen custom module registry using the `stackgen` CLI. Supports parallel execution for fast uploads of large module libraries. + +### Prerequisites + +- **`stackgen` CLI** installed and available on `$PATH` +- A valid **StackGen API token** (passed via `--token`) +- Modules organized under `aws/`, `azurerm/`, and/or `gcp/` provider directories + +### Usage + +```bash +./tools/upload_stackgen_modules.sh --token [OPTIONS] +``` + +### Flags + +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--token ` | **Yes** | — | StackGen API token for authentication | +| `--url ` | No | CLI default | StackGen instance URL (e.g., `https://seti.cloud.stackgen.com`) | +| `--project ` | No | — | Project ID for auth context (does **not** scope module visibility — modules are always org-wide) | +| `--provider ` | No | All providers | Filter to a single provider: `aws`, `azurerm`, or `gcp` | +| `--templates ` | No | All modules | Comma-separated list of module names to upload (e.g., `aws_s3_bucket,aws_iam_role`) | +| `--repo-url ` | No | — | Git repository URL for StackGen to reference (e.g., `https://github.com/org/repo`) | +| `--branch ` | No | — | Git branch to reference. Mutually exclusive with `--tag`. Requires `--repo-url` | +| `--tag ` | No | — | Git tag to reference. Mutually exclusive with `--branch`. Requires `--repo-url` | +| `--version ` | No | `1.0` (CLI default) | Module version string | +| `--overwrite-version` | No | `false` | Overwrite an existing version instead of skipping | +| `--parallel ` | No | `10` | Number of concurrent uploads | + +### Examples + +#### Upload all modules (all providers) + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" +``` + +#### Upload only AWS modules + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" \ + --provider aws +``` + +#### Upload specific modules by name + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" \ + --templates "aws_s3_bucket,aws_iam_role,aws_lambda_function" +``` + +#### Upload from a specific branch with repo reference + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" \ + --repo-url "https://github.com/stackgenhq/discovery-modules" \ + --branch "main" +``` + +#### Overwrite existing module versions + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" \ + --overwrite-version +``` + +#### Bump to a new version + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" \ + --version "2.0" \ + --provider aws +``` + +#### Increase parallelism for faster uploads + +```bash +./tools/upload_stackgen_modules.sh \ + --token "$STACKGEN_TOKEN" \ + --url "https://seti.cloud.stackgen.com" \ + --parallel 20 +``` + +### How it works + +1. **Module discovery** — Scans `aws/`, `azurerm/`, and `gcp/` directories (or a single provider if `--provider` is set) for subdirectories. Each subdirectory is treated as one module. If `--templates` is provided, only the named modules are uploaded. + +2. **Parallel dispatch** — Modules are uploaded concurrently using `xargs -P`. The default concurrency is 10, tunable with `--parallel`. + +3. **Retry with backoff** — Each module upload is retried up to 3 times with exponential backoff (1s → 2s → 4s) for transient API failures. "Version already exists" errors are detected immediately and skipped without retrying. + +4. **Provider mapping** — The provider name is derived from the parent directory (`aws` → `aws`, `gcp` → `gcp`). The `azurerm` directory is mapped to `azure` since the StackGen CLI uses `azure` as the provider name. + +5. **Progress tracking** — A thread-safe atomic counter (using `mkdir`-based spinlocking) provides real-time `[N/Total]` progress output, even across parallel subshells. Works on both macOS (BSD) and Linux (GNU). + +6. **Summary report** — After all uploads complete, a structured summary shows succeeded, skipped (version exists), and failed counts. Failed module names and error messages are listed. The script exits non-zero only if there were failures. + +### Output example + +``` +Uploading 817 module(s) (parallelism: 10)... +[1/817] ✓ aws/aws_s3_bucket +[2/817] ✓ aws/aws_iam_role +[3/817] ⊘ aws/aws_lambda_function (skipped: version exists) +[4/817] ✗ aws/aws_bad_module (FAILED after 3 attempts) +... + +===== Upload Summary ===== + Succeeded: 815 + Skipped: 1 (version already exists) + Failed: 1 + Total: 817 + +===== Failed Modules ===== +aws/aws_bad_module: Error: invalid module configuration +``` + +--- + +## bulk-tag-modules.sh + +Creates `v1.0.0` Git tags for all module subdirectories. These tags are required by the `module-backfill.yml` GitHub Actions workflow to discover and upload modules. + +### Usage + +```bash +./tools/bulk-tag-modules.sh # Dry run — shows what would be tagged +./tools/bulk-tag-modules.sh --apply # Creates tags locally +./tools/bulk-tag-modules.sh --apply --push # Creates tags and pushes to remote +``` + +### Tag format + +``` +-v1.0.0 +``` + +Examples: `aws_s3_bucket-v1.0.0`, `azurerm_resource_group-v1.0.0`, `google_compute_instance-v1.0.0` + +--- + +## Other files + +| File | Description | +|------|-------------| +| `stackgen_yaml_schema.json` | JSON Schema for `.stackgen/stackgen.yaml` files. Use for editor validation and CI linting. | +| `dummy.yaml` | Sample fixture input for development and testing. | From cfcd153299e6eb36b3a7f98ac23b0efe1f13d1cb Mon Sep 17 00:00:00 2001 From: asarkar157 <20803896+asarkar157@users.noreply.github.com> Date: Wed, 6 May 2026 12:10:09 -0400 Subject: [PATCH 3/4] docs: update root README with new upload script flags and behavior Sync the Options table and Behavior section with the current script: - Add --provider, --version, --overwrite-version, --parallel flags - Document parallel uploads, retry logic, and fail-at-end behavior - Add Default column to the flags table - Reference tools/README.md for detailed docs - Add bulk-tag-modules.sh to Tools section --- README.md | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 1a23f15..20ab8f4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The [`tools/`](tools/) directory contains scripts and helpers to **publish these ## Uploading to StackGen -Uploads use the **StackGen CLI**. The script **`tools/upload_stackgen_modules.sh`** is a thin wrapper: for each module it runs **`stackgen upload custom-modules`** (with `--provider`, `--name`, and optional `--repo-url` / `--branch` / `--tag` / `--project`). +Uploads use the **StackGen CLI**. The script **`tools/upload_stackgen_modules.sh`** bulk-uploads modules by running **`stackgen upload custom-modules`** in parallel (with `--provider`, `--name`, and optional `--repo-url` / `--branch` / `--tag` / `--project`). See [`tools/README.md`](tools/README.md) for full documentation, flag reference, and usage examples. Modules scanned are the immediate subdirectories of **`aws/`**, **`azurerm/`**, and **`gcp/`**, with optional `--templates` filtering. @@ -51,36 +51,40 @@ Modules scanned are the immediate subdirectories of **`aws/`**, **`azurerm/`**, ### Options -| Flag | Required | Description | -|------|----------|-------------| -| `--token` | Yes | StackGen authentication token | -| `--url` | No | StackGen base URL (otherwise use your CLI default / env) | -| `--project` | No | Project ID to upload modules into | -| `--templates` | No | Comma-separated module folder names (e.g. `aws_ec2,aws_s3`) to upload only those | -| `--repo-url` | No | Repository URL for source tracking in StackGen | -| `--branch` | No* | Git branch name (use only one of `--branch` or `--tag`) | -| `--tag` | No* | Git tag name | +| Flag | Required | Default | Description | +|------|----------|---------|-------------| +| `--token` | Yes | — | StackGen authentication token | +| `--url` | No | CLI default | StackGen base URL (e.g., `https://seti.cloud.stackgen.com`) | +| `--project` | No | — | Project ID for auth context (modules are always org-wide) | +| `--provider` | No | All | Filter to a single provider: `aws`, `azurerm`, or `gcp` | +| `--templates` | No | All modules | Comma-separated module folder names (e.g. `aws_ec2,aws_s3`) | +| `--repo-url` | No | — | Repository URL for source tracking in StackGen | +| `--branch` | No | — | Git branch name (mutually exclusive with `--tag`; requires `--repo-url`) | +| `--tag` | No | — | Git tag name (mutually exclusive with `--branch`; requires `--repo-url`) | +| `--version` | No | `1.0` | Module version string | +| `--overwrite-version` | No | `false` | Overwrite an existing version instead of skipping | +| `--parallel` | No | `10` | Number of concurrent uploads | ### Behavior - **Minimal input**: Only `--token` is strictly required by the script. -- **Batch upload**: Without `--templates`, all modules in those provider trees are uploaded (one CLI invocation per module). +- **Parallel uploads**: Modules are uploaded concurrently using `xargs -P` (default: 10 workers, tunable with `--parallel`). +- **Retry with backoff**: Each upload is retried up to 3 times with exponential backoff (1s → 2s → 4s) for transient failures. - **Provider mapping**: `azurerm` modules are uploaded with StackGen provider **`azure`**. -- **Skip existing**: If the CLI reports that the version name already exists, that module is skipped and the script continues. -- **Errors**: Other failures stop the run with a non-zero exit code. +- **Skip existing**: If the CLI reports that the version name already exists, that module is skipped (use `--overwrite-version` to force). +- **Fail-at-end**: Failures are collected and reported in a summary after all uploads complete, rather than stopping on the first error. +- **Cross-platform**: Works on both macOS (BSD) and Linux (GNU). ## Tools -Utilities for **publishing** and **maintaining** discovery modules (upload flow above). +Utilities for **publishing** and **maintaining** discovery modules. See [`tools/README.md`](tools/README.md) for full documentation. -### `upload_stackgen_modules.sh` - -For each module under **`aws/`**, **`azurerm/`**, and **`gcp/`**, this script invokes **`stackgen upload custom-modules`**. Usage, flags, and behavior are documented under [Uploading to StackGen](#uploading-to-stackgen). - -### Other files - -- **`stackgen_yaml_schema.json`** — JSON Schema for `.stackgen/stackgen.yaml` (validation and editor support). -- **`dummy.yaml`** — Sample / fixture input for development. +| Script | Description | +|--------|-------------| +| **`upload_stackgen_modules.sh`** | Bulk-uploads modules to StackGen with parallel execution, retries, and progress tracking. See [Uploading to StackGen](#uploading-to-stackgen). | +| **`bulk-tag-modules.sh`** | Creates `v1.0.0` Git tags for all module subdirectories (dry-run by default). Required by the `module-backfill.yml` workflow. | +| **`stackgen_yaml_schema.json`** | JSON Schema for `.stackgen/stackgen.yaml` (validation and editor support). | +| **`dummy.yaml`** | Sample / fixture input for development. | ## Versioning and provider compatibility From 12b6319721de179906973f01476e8544b3009de8 Mon Sep 17 00:00:00 2001 From: asarkar157 <20803896+asarkar157@users.noreply.github.com> Date: Tue, 19 May 2026 10:19:14 -0700 Subject: [PATCH 4/4] add missing bulk-tag-modules.sh script --- tools/bulk-tag-modules.sh | 132 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100755 tools/bulk-tag-modules.sh diff --git a/tools/bulk-tag-modules.sh b/tools/bulk-tag-modules.sh new file mode 100755 index 0000000..c727fa7 --- /dev/null +++ b/tools/bulk-tag-modules.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# bulk-tag-modules.sh +# +# Creates v1.0.0 tags for all module subdirectories in aws/, azurerm/, and gcp/. +# These tags are required for the module-backfill.yml workflow to discover and upload modules. +# +# Usage: +# ./tools/bulk-tag-modules.sh # Dry run (default) — shows what would be tagged +# ./tools/bulk-tag-modules.sh --apply # Creates tags locally +# ./tools/bulk-tag-modules.sh --apply --push # Creates tags and pushes to remote +# +# Tag format: -v1.0.0 +# Examples: +# aws_s3_bucket-v1.0.0 +# azurerm_resource_group-v1.0.0 +# google_compute_instance-v1.0.0 + +set -euo pipefail + +# Parse flags +APPLY=false +PUSH=false +for arg in "$@"; do + case "$arg" in + --apply) APPLY=true ;; + --push) PUSH=true ;; + --help|-h) + echo "Usage: $0 [--apply] [--push]" + echo "" + echo " --apply Create tags locally (without this flag, dry run only)" + echo " --push Push tags to remote (implies --apply)" + exit 0 + ;; + esac +done + +if [ "$PUSH" = true ]; then + APPLY=true +fi + +# Ensure we're in the repo root +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "")" +if [ -z "$REPO_ROOT" ]; then + echo "❌ Not in a git repository" + exit 1 +fi +cd "$REPO_ROOT" + +VERSION="v1.0.0" +CREATED=0 +SKIPPED=0 +TOTAL=0 +TAGS_TO_PUSH=() + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "📦 BULK MODULE TAGGER" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +if [ "$APPLY" = false ]; then + echo "🔍 DRY RUN MODE — no tags will be created" + echo " Run with --apply to create tags" + echo "" +fi + +for PROVIDER_DIR in aws azurerm gcp; do + if [ ! -d "$PROVIDER_DIR" ]; then + echo "⚠️ Directory not found: $PROVIDER_DIR — skipping" + continue + fi + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📂 Processing: $PROVIDER_DIR/" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + DIR_COUNT=0 + for MODULE_DIR in "$PROVIDER_DIR"/*/; do + # Strip trailing slash to get directory name + MODULE_DIR="${MODULE_DIR%/}" + MODULE_NAME="$(basename "$MODULE_DIR")" + TAG_NAME="${MODULE_NAME}-${VERSION}" + + TOTAL=$((TOTAL + 1)) + DIR_COUNT=$((DIR_COUNT + 1)) + + # Check if tag already exists + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo " ⏭ $TAG_NAME (already exists)" + SKIPPED=$((SKIPPED + 1)) + continue + fi + + if [ "$APPLY" = true ]; then + git tag "$TAG_NAME" + echo " ✅ $TAG_NAME" + TAGS_TO_PUSH+=("$TAG_NAME") + else + echo " 🔍 $TAG_NAME (would create)" + fi + CREATED=$((CREATED + 1)) + done + + echo " → $DIR_COUNT modules in $PROVIDER_DIR/" + echo "" +done + +# Summary +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "📊 SUMMARY" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo " Total modules: $TOTAL" +echo " Tags to create: $CREATED" +echo " Already tagged: $SKIPPED" +if [ "$APPLY" = false ]; then + echo " Mode: DRY RUN" + echo "" + echo "👉 Run with --apply to create tags locally" + echo "👉 Run with --apply --push to create and push tags" +else + echo " Mode: APPLIED" +fi +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +# Push if requested +if [ "$PUSH" = true ] && [ ${#TAGS_TO_PUSH[@]} -gt 0 ]; then + echo "" + echo "📤 Pushing ${#TAGS_TO_PUSH[@]} tags to origin..." + git push origin "${TAGS_TO_PUSH[@]}" + echo "✅ All tags pushed" +elif [ "$PUSH" = true ] && [ ${#TAGS_TO_PUSH[@]} -eq 0 ]; then + echo "" + echo "ℹ️ No new tags to push" +fi