|
1 | | -name: Update Keyfactor Integrations Catalog Entry |
2 | | -on: |
3 | | - workflow_call: |
4 | | - secrets: |
5 | | - token: |
6 | | - description: 'Secret token from caller workflow to access SDK repo' |
7 | | - required: true |
8 | | - |
9 | | -jobs: |
10 | | - update-catalog-entry: |
11 | | - runs-on: ubuntu-latest |
12 | | - |
13 | | - steps: |
14 | | - - name: Checkout project repo |
15 | | - uses: keyfactor/checkout@v4 |
16 | | - |
17 | | - - name: Checkout catalog repo |
18 | | - uses: keyfactor/checkout@v4 |
19 | | - with: |
20 | | - token: ${{ secrets.token }} |
21 | | - path: './catalog-temp/' |
22 | | - repository: 'Keyfactor/integrations-catalog' # Change back to integrations-catalog after testing |
23 | | - |
24 | | - - uses: Keyfactor/jinja2-action@v1.2.0-multiple-data-files |
25 | | - with: |
26 | | - template: ./catalog-temp/_integration.md.tpl |
27 | | - output_file: ${{ format('./catalog-temp/_integrations/{0}.md', github.event.repository.name) }} |
28 | | - data_file: integration-manifest.json |
29 | | - variables: | |
30 | | - repository= ${{ format('https://github.com/{0}', github.repository) }} |
31 | | - env: |
32 | | - GITHUB_TOKEN: ${{ secrets.token }} |
33 | | - |
34 | | - - uses: Keyfactor/add-and-commit@v9.1.4 |
35 | | - with: |
36 | | - author_name: 'Keyfactor' |
37 | | - author_email: 'keyfactor@keyfactor.github.io' |
38 | | - branch: 'main' |
39 | | - message: ${{ format('Added the manifest for {0}', github.event.repository.name) }} |
40 | | - add: ${{ format('_integrations/{0}.md --force', github.event.repository.name) }} |
41 | | - cwd: './catalog-temp/' |
| 1 | +# ============================================================================ |
| 2 | +# PROPOSED replacement for: Keyfactor/actions/.github/workflows/update-catalog.yml |
| 3 | +# |
| 4 | +# Changes from current version: |
| 5 | +# 1. Adds validation gate (repo name filter, manifest field checks, visibility) |
| 6 | +# 2. Replaces direct-to-main commit with PR-based flow |
| 7 | +# 3. Handles private repos as "Coming Soon" entries (link_github=false) |
| 8 | +# 4. Labels PRs by type: catalog-add, catalog-update, coming-soon |
| 9 | +# ============================================================================ |
| 10 | + |
| 11 | +name: Update Keyfactor Integrations Catalog Entry |
| 12 | +on: |
| 13 | + workflow_call: |
| 14 | + secrets: |
| 15 | + token: |
| 16 | + description: 'Secret token from caller workflow to access catalog repo' |
| 17 | + required: true |
| 18 | + |
| 19 | +jobs: |
| 20 | + update-catalog-entry: |
| 21 | + runs-on: ubuntu-latest |
| 22 | + |
| 23 | + steps: |
| 24 | + - name: Checkout project repo |
| 25 | + uses: keyfactor/checkout@v4 |
| 26 | + |
| 27 | + - name: Checkout catalog repo |
| 28 | + uses: keyfactor/checkout@v4 |
| 29 | + with: |
| 30 | + token: ${{ secrets.token }} |
| 31 | + path: './catalog-temp/' |
| 32 | + repository: 'Keyfactor/integrations-catalog' |
| 33 | + |
| 34 | + # ---------------------------------------------------------------- |
| 35 | + # STEP 1: Validation Gate |
| 36 | + # ---------------------------------------------------------------- |
| 37 | + - name: Validate entry |
| 38 | + id: validate |
| 39 | + env: |
| 40 | + GH_TOKEN: ${{ secrets.token }} |
| 41 | + REPO_NAME: ${{ github.event.repository.name }} |
| 42 | + REPO_FULL: ${{ github.repository }} |
| 43 | + run: | |
| 44 | + echo "### Validating: $REPO_NAME" >> "$GITHUB_STEP_SUMMARY" |
| 45 | +
|
| 46 | + # --- Failsafe: reject -dev, -test, -staging, -poc repo names --- |
| 47 | + # NOTE: This is a failsafe only. Developers are responsible for setting |
| 48 | + # update_catalog=false in their manifest for non-production repos. |
| 49 | + if echo "$REPO_NAME" | grep -qE '-(dev|test|staging|poc)$'; then |
| 50 | + echo "::error::Repository name '$REPO_NAME' matches a non-production pattern (-dev, -test, -staging, -poc). Set update_catalog=false in your integration-manifest.json for non-production repos." |
| 51 | + echo "rejected=true" >> "$GITHUB_OUTPUT" |
| 52 | + echo "reject_reason=Repo name matches non-production pattern. Developers: set \`update_catalog=false\` in your manifest." >> "$GITHUB_OUTPUT" |
| 53 | + exit 1 |
| 54 | + fi |
| 55 | +
|
| 56 | + # --- Validate required manifest fields --- |
| 57 | + if [ ! -f "integration-manifest.json" ]; then |
| 58 | + echo "::error::integration-manifest.json not found in repository root." |
| 59 | + exit 1 |
| 60 | + fi |
| 61 | +
|
| 62 | + NAME=$(jq -r '.name // empty' integration-manifest.json) |
| 63 | + TYPE=$(jq -r '.integration_type // empty' integration-manifest.json) |
| 64 | + DESC=$(jq -r '.description // empty' integration-manifest.json) |
| 65 | + LINK_GITHUB=$(jq -r '.link_github // "true"' integration-manifest.json | tr '[:upper:]' '[:lower:]') |
| 66 | +
|
| 67 | + ERRORS="" |
| 68 | + if [ -z "$NAME" ]; then |
| 69 | + ERRORS="$ERRORS\n- Missing required field: \`name\`" |
| 70 | + fi |
| 71 | + if [ -z "$TYPE" ]; then |
| 72 | + ERRORS="$ERRORS\n- Missing required field: \`integration_type\`" |
| 73 | + fi |
| 74 | + if [ -z "$DESC" ]; then |
| 75 | + ERRORS="$ERRORS\n- Missing required field: \`description\`" |
| 76 | + fi |
| 77 | +
|
| 78 | + # Validate integration_type against allowed values |
| 79 | + ALLOWED_TYPES="orchestrator windows-orchestrator iot-orchestrator ca-gateway anyca-plugin caplugin dns-plugin pam approval-handler orchestrator-registration metadata registration-handler alert-handler api-client terraform" |
| 80 | + if [ -n "$TYPE" ]; then |
| 81 | + if ! echo "$ALLOWED_TYPES" | grep -qw "$TYPE"; then |
| 82 | + ERRORS="$ERRORS\n- Invalid \`integration_type\`: \`$TYPE\`. Allowed: $ALLOWED_TYPES" |
| 83 | + fi |
| 84 | + fi |
| 85 | +
|
| 86 | + if [ -n "$ERRORS" ]; then |
| 87 | + printf "::error::Manifest validation failed:%b\n" "$ERRORS" |
| 88 | + exit 1 |
| 89 | + fi |
| 90 | +
|
| 91 | + # --- Check repo visibility --- |
| 92 | + IS_PRIVATE=$(gh api "repos/$REPO_FULL" --jq '.private') |
| 93 | +
|
| 94 | + if [ "$IS_PRIVATE" = "true" ] && [ "$LINK_GITHUB" = "true" ]; then |
| 95 | + echo "::error::Repository '$REPO_FULL' is private but link_github=true. This would create a broken link on the public catalog. Set link_github=false in your manifest to create a 'Coming Soon' entry, or make the repo public first." |
| 96 | + exit 1 |
| 97 | + fi |
| 98 | +
|
| 99 | + # Determine entry type for labeling |
| 100 | + if [ "$IS_PRIVATE" = "true" ] && [ "$LINK_GITHUB" = "false" ]; then |
| 101 | + echo "entry_type=coming-soon" >> "$GITHUB_OUTPUT" |
| 102 | + echo "Entry type: Coming Soon (private repo)" >> "$GITHUB_STEP_SUMMARY" |
| 103 | + elif [ -f "./catalog-temp/_integrations/$REPO_NAME.md" ]; then |
| 104 | + echo "entry_type=catalog-update" >> "$GITHUB_OUTPUT" |
| 105 | + echo "Entry type: Update (existing entry)" >> "$GITHUB_STEP_SUMMARY" |
| 106 | + else |
| 107 | + echo "entry_type=catalog-add" >> "$GITHUB_OUTPUT" |
| 108 | + echo "Entry type: Add (new entry)" >> "$GITHUB_STEP_SUMMARY" |
| 109 | + fi |
| 110 | +
|
| 111 | + echo "link_github=$LINK_GITHUB" >> "$GITHUB_OUTPUT" |
| 112 | + echo "Validation passed" >> "$GITHUB_STEP_SUMMARY" |
| 113 | +
|
| 114 | + # ---------------------------------------------------------------- |
| 115 | + # STEP 2: Render Template |
| 116 | + # ---------------------------------------------------------------- |
| 117 | + - uses: Keyfactor/jinja2-action@v1.2.0-multiple-data-files |
| 118 | + with: |
| 119 | + template: ./catalog-temp/_integration.md.tpl |
| 120 | + output_file: ${{ format('./catalog-temp/_integrations/{0}.md', github.event.repository.name) }} |
| 121 | + data_file: integration-manifest.json |
| 122 | + variables: | |
| 123 | + repository= ${{ format('https://github.com/{0}', github.repository) }} |
| 124 | + env: |
| 125 | + GITHUB_TOKEN: ${{ secrets.token }} |
| 126 | + |
| 127 | + # ---------------------------------------------------------------- |
| 128 | + # STEP 3: Create PR (instead of direct commit) |
| 129 | + # ---------------------------------------------------------------- |
| 130 | + - name: Create or update PR |
| 131 | + env: |
| 132 | + GH_TOKEN: ${{ secrets.token }} |
| 133 | + REPO_NAME: ${{ github.event.repository.name }} |
| 134 | + ENTRY_TYPE: ${{ steps.validate.outputs.entry_type }} |
| 135 | + working-directory: './catalog-temp/' |
| 136 | + run: | |
| 137 | + BRANCH="catalog-update/$REPO_NAME" |
| 138 | +
|
| 139 | + git config user.name "Keyfactor" |
| 140 | + git config user.email "keyfactor@keyfactor.github.io" |
| 141 | +
|
| 142 | + # Check if branch already exists on remote |
| 143 | + if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then |
| 144 | + git fetch origin "$BRANCH" |
| 145 | + git checkout "$BRANCH" |
| 146 | + git reset --hard origin/main |
| 147 | + else |
| 148 | + git checkout -b "$BRANCH" |
| 149 | + fi |
| 150 | +
|
| 151 | + # Stage the rendered file |
| 152 | + git add "_integrations/$REPO_NAME.md" --force |
| 153 | +
|
| 154 | + # Check if there are actual changes |
| 155 | + if git diff --cached --quiet; then |
| 156 | + echo "No changes detected — catalog entry is already up to date." |
| 157 | + echo "### No changes" >> "$GITHUB_STEP_SUMMARY" |
| 158 | + echo "Catalog entry for \`$REPO_NAME\` is already current." >> "$GITHUB_STEP_SUMMARY" |
| 159 | + exit 0 |
| 160 | + fi |
| 161 | +
|
| 162 | + # Determine commit message |
| 163 | + case "$ENTRY_TYPE" in |
| 164 | + catalog-add) |
| 165 | + COMMIT_MSG="Add catalog entry for $REPO_NAME" |
| 166 | + PR_TITLE="Add integration: $REPO_NAME" |
| 167 | + ;; |
| 168 | + catalog-update) |
| 169 | + COMMIT_MSG="Update catalog entry for $REPO_NAME" |
| 170 | + PR_TITLE="Update integration: $REPO_NAME" |
| 171 | + ;; |
| 172 | + coming-soon) |
| 173 | + COMMIT_MSG="Add Coming Soon catalog entry for $REPO_NAME" |
| 174 | + PR_TITLE="Coming Soon: $REPO_NAME" |
| 175 | + ;; |
| 176 | + esac |
| 177 | +
|
| 178 | + git commit -m "$COMMIT_MSG" |
| 179 | + git push origin "$BRANCH" --force |
| 180 | +
|
| 181 | + # Ensure the three labels this workflow uses exist (idempotent — gh |
| 182 | + # label create exits 1 if the label is already present, which we |
| 183 | + # swallow). Without this, gh pr create --label hard-fails when the |
| 184 | + # label is missing in the catalog repo. |
| 185 | + gh label create catalog-add --color "2ECC71" --description "New integration entry added to the catalog" 2>/dev/null || true |
| 186 | + gh label create catalog-update --color "1A73E8" --description "Existing integration entry updated" 2>/dev/null || true |
| 187 | + gh label create coming-soon --color "F39C12" --description "Private repo published as a Coming Soon entry" 2>/dev/null || true |
| 188 | +
|
| 189 | + # Build PR body |
| 190 | + PR_BODY=$(cat <<EOF |
| 191 | + ## Catalog Entry: \`$REPO_NAME\` |
| 192 | +
|
| 193 | + **Type:** $ENTRY_TYPE |
| 194 | + **Source:** [${REPO_NAME}](https://github.com/${{ github.repository }}) |
| 195 | + **Triggered by:** Push to \`${{ github.ref_name }}\` by @${{ github.actor }} |
| 196 | +
|
| 197 | + ### Entry Preview |
| 198 | + \`\`\`yaml |
| 199 | + $(cat "_integrations/$REPO_NAME.md") |
| 200 | + \`\`\` |
| 201 | +
|
| 202 | + --- |
| 203 | + *This PR was automatically created by the catalog update workflow.* |
| 204 | + *Review the entry content above and merge when ready.* |
| 205 | + EOF |
| 206 | + ) |
| 207 | +
|
| 208 | + # Check if a PR already exists for this branch |
| 209 | + EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || true) |
| 210 | +
|
| 211 | + if [ -n "$EXISTING_PR" ] && [ "$EXISTING_PR" != "null" ]; then |
| 212 | + echo "PR #$EXISTING_PR already exists for branch $BRANCH — updated with force push." |
| 213 | + echo "### Updated existing PR #$EXISTING_PR" >> "$GITHUB_STEP_SUMMARY" |
| 214 | + else |
| 215 | + gh pr create \ |
| 216 | + --title "$PR_TITLE" \ |
| 217 | + --body "$PR_BODY" \ |
| 218 | + --label "$ENTRY_TYPE" \ |
| 219 | + --base main \ |
| 220 | + --head "$BRANCH" |
| 221 | +
|
| 222 | + echo "### Created PR" >> "$GITHUB_STEP_SUMMARY" |
| 223 | + echo "PR created for \`$REPO_NAME\` with label \`$ENTRY_TYPE\`" >> "$GITHUB_STEP_SUMMARY" |
| 224 | + fi |
0 commit comments