Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/edge-cluster-trigger.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Trigger a SovereignSOC ADO edge-cluster lifecycle run.
# Smoke test for the GH→ADO trigger path. Once stable, this can be folded
# into component-tests.yaml as a pre-step that resets the target cluster
# before component tests run against it.
#
# Setup contract (k8sstormcenter/actions repo + SovereignSOC docs):
# secrets.ADO_PAT — PAT with Build:Read & Execute
# vars.ADO_ORG — AustrianDataLab
# vars.ADO_PROJECT — SovereignSOC
# vars.ADO_PIPELINE_EDGE — pipeline definition ID for edge-cluster.yml
name: edge-cluster-trigger

on:
workflow_dispatch:
inputs:
cluster:
description: 'Edge cluster slot'
required: true
default: 'edge4'
type: choice
options:
- edge4
- k3s-1
- k3s-2
- rocky-1
action:
description: 'Lifecycle action'
required: true
default: 'plan'
type: choice
options:
- plan
- create
- delete
- reset
os:
description: 'Base OS'
required: true
default: 'ubuntu'
type: choice
options:
- ubuntu
- rocky
wait:
description: 'Wait for ADO run to complete'
required: true
default: 'true'
type: boolean
Comment on lines +13 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider adding concurrency control keyed on cluster input.

The workflow allows parallel manual triggers. If multiple users trigger lifecycle operations (especially reset, create, or delete) on the same cluster simultaneously, they could interfere with each other.

♻️ Proposed addition of concurrency group

Add this block after the on: section and before jobs::

       wait:
         description: 'Wait for ADO run to complete'
         required: true
         default: 'true'
         type: boolean
 
+concurrency:
+  group: edge-cluster-${{ inputs.cluster }}
+  cancel-in-progress: false
+
 jobs:
   trigger:

This allows parallel operations on different clusters while serializing operations on the same cluster. Set cancel-in-progress: true if you prefer newer runs to cancel pending ones.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
workflow_dispatch:
inputs:
cluster:
description: 'Edge cluster slot'
required: true
default: 'edge4'
type: choice
options:
- edge4
- k3s-1
- k3s-2
- rocky-1
action:
description: 'Lifecycle action'
required: true
default: 'plan'
type: choice
options:
- plan
- create
- delete
- reset
os:
description: 'Base OS'
required: true
default: 'ubuntu'
type: choice
options:
- ubuntu
- rocky
wait:
description: 'Wait for ADO run to complete'
required: true
default: 'true'
type: boolean
on:
workflow_dispatch:
inputs:
cluster:
description: 'Edge cluster slot'
required: true
default: 'edge4'
type: choice
options:
- edge4
- k3s-1
- k3s-2
- rocky-1
action:
description: 'Lifecycle action'
required: true
default: 'plan'
type: choice
options:
- plan
- create
- delete
- reset
os:
description: 'Base OS'
required: true
default: 'ubuntu'
type: choice
options:
- ubuntu
- rocky
wait:
description: 'Wait for ADO run to complete'
required: true
default: 'true'
type: boolean
concurrency:
group: edge-cluster-${{ inputs.cluster }}
cancel-in-progress: false
jobs:
trigger:
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 13-48: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/edge-cluster-trigger.yml around lines 13 - 48, Add a
concurrency group to the workflow to prevent simultaneous lifecycle actions on
the same cluster by using the cluster input as the key; specifically insert a
concurrency block after the on: section (before jobs:) that sets group to
something like "edge-cluster-${{ github.workflow }}-${{
github.event.inputs.cluster }}" (or similar using inputs.cluster) and optionally
set cancel-in-progress: true to let newer runs cancel older ones; this will
serialize runs for the same cluster while still allowing parallel runs for
different clusters and references the existing inputs.cluster and the workflow's
trigger.


jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Guard — config sanity
env:
ADO_PAT: ${{ secrets.ADO_PAT }}
ADO_ORG: ${{ vars.ADO_ORG }}
ADO_PROJECT: ${{ vars.ADO_PROJECT }}
ADO_PIPELINE_EDGE: ${{ vars.ADO_PIPELINE_EDGE }}
run: |
missing=()
[[ -n "$ADO_PAT" ]] || missing+=("secrets.ADO_PAT")
[[ -n "$ADO_ORG" ]] || missing+=("vars.ADO_ORG")
[[ -n "$ADO_PROJECT" ]] || missing+=("vars.ADO_PROJECT")
[[ -n "$ADO_PIPELINE_EDGE" ]] || missing+=("vars.ADO_PIPELINE_EDGE")
if [[ ${#missing[@]} -gt 0 ]]; then
echo "::error::Missing required config: ${missing[*]}"
echo "Set them per docs at k8sstormcenter/actions/trigger-ado-pipeline/README.md"
exit 1
fi
echo "✔ All required config present"

- id: ado
uses: k8sstormcenter/actions/trigger-ado-pipeline@main
with:
org: ${{ vars.ADO_ORG }}
project: ${{ vars.ADO_PROJECT }}
pipeline_id: ${{ vars.ADO_PIPELINE_EDGE }}
pat: ${{ secrets.ADO_PAT }}
cluster: ${{ inputs.cluster }}
action: ${{ inputs.action }}
os: ${{ inputs.os }}
wait: ${{ inputs.wait }}
timeout: '1800'
Comment on lines +73 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the latest commit hash for the action to pin to

gh api repos/k8sstormcenter/actions/commits/main \
  --jq '.sha' \
  | head -c 7

Repository: k8sstormcenter/node-agent

Length of output: 76


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Show the exact workflow lines around the ADO trigger step
echo "---- Workflow snippet ----"
nl -ba .github/workflows/edge-cluster-trigger.yml | sed -n '60,95p'

# 2) Get full SHA for k8sstormcenter/actions@main
echo "---- GitHub: full SHA for repos/k8sstormcenter/actions@main ----"
FULL_SHA="$(gh api repos/k8sstormcenter/actions/commits/main --jq '.sha')"
echo "$FULL_SHA"

# 3) Verify that trigger-ado-pipeline path exists in that repo at that ref
# (GitHub Actions expects an action.yml/metadata inside the directory for the path portion.)
echo "---- GitHub: check contents/trigger-ado-pipeline exists at ref ----"
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline?ref=$FULL_SHA" --jq '.[].path' || \
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline?ref=$FULL_SHA" --jq '.path'

Repository: k8sstormcenter/node-agent

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- Workflow snippet (lines 65-95) ----"
awk 'NR>=65 && NR<=95 {printf "%d:%s\n", NR, $0}' .github/workflows/edge-cluster-trigger.yml

echo "---- GitHub: full SHA for repos/k8sstormcenter/actions@main ----"
FULL_SHA="$(gh api repos/k8sstormcenter/actions/commits/main --jq '.sha')"
SHORT_SHA="$(echo "$FULL_SHA" | head -c 7)"
echo "FULL_SHA=$FULL_SHA"
echo "SHORT_SHA=$SHORT_SHA"

echo "---- GitHub: check trigger-ado-pipeline directory exists at that ref ----"
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline?ref=$FULL_SHA" --jq '{path, type, name}' || true

echo "---- GitHub: check action entry files exist at that ref ----"
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline/action.yml?ref=$FULL_SHA" --jq '.path' || true
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline/action.yaml?ref=$FULL_SHA" --jq '.path' || true
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline/metadata.json?ref=$FULL_SHA" --jq '.path' || true

Repository: k8sstormcenter/node-agent

Length of output: 2318


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FULL_SHA="$(gh api repos/k8sstormcenter/actions/commits/main --jq '.sha')"
echo "FULL_SHA=$FULL_SHA"

echo "---- Find any paths containing 'trigger-ado' in that ref ----"
gh api "repos/k8sstormcenter/actions/git/trees/$FULL_SHA?recursive=1" --jq '
  .tree[]
  | select(.path | test("trigger-ado"; "i"))
  | {path, type}
' | head -n 50

echo "---- Find exact 'trigger-ado-pipeline' matches ----"
gh api "repos/k8sstormcenter/actions/git/trees/$FULL_SHA?recursive=1" --jq '
  .tree[]
  | select(.path == "trigger-ado-pipeline" or .path | test("trigger-ado-pipeline"; "i"))
  | {path, type}
' | head -n 50

Repository: k8sstormcenter/node-agent

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- contents: trigger-ado-pipeline at ref=main ----"
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline?ref=main" --jq '
  if type=="array" then map({name, path, type}) else {path, type} end
' || true

echo "---- contents: trigger-ado-pipeline at ref=SHORT_SHA=27790fc (if needed) ----"
gh api "repos/k8sstormcenter/actions/contents/trigger-ado-pipeline?ref=27790fc" --jq '
  if type=="array" then map({name, path, type}) else {path, type} end
' || true

Repository: k8sstormcenter/node-agent

Length of output: 508


Pin trigger-ado-pipeline to a commit SHA (remove @main).

uses: k8sstormcenter/actions/trigger-ado-pipeline@main lets the workflow automatically pick up whatever main points to next. Pin it to 27790fc0392ded46e74bf2d7620c0b790cde2681 (e.g., update the uses: line around line 74). [Optional: keep timeout: '1800' as-is since it’s reasonable for a smoke test.]

🧰 Tools
🪛 zizmor (1.25.2)

[error] 74-74: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/edge-cluster-trigger.yml around lines 73 - 84, Update the
GitHub Action step that defines the "ado" job to pin the external action instead
of using the floating ref: replace the uses string
"k8sstormcenter/actions/trigger-ado-pipeline@main" (in the step with id: ado)
with the commit SHA
"k8sstormcenter/actions/trigger-ado-pipeline@27790fc0392ded46e74bf2d7620c0b790cde2681";
keep the other inputs (org, project, pipeline_id, pat, cluster, action, os,
wait, timeout) unchanged.


- name: Summary
if: always()
run: |
echo "### ADO run" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "- cluster: \`${{ inputs.cluster }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "- action: \`${{ inputs.action }}\`" >> "$GITHUB_STEP_SUMMARY"
echo "- run id: ${{ steps.ado.outputs.run_id }}" >> "$GITHUB_STEP_SUMMARY"
echo "- url: ${{ steps.ado.outputs.run_url }}" >> "$GITHUB_STEP_SUMMARY"
echo "- result: ${{ steps.ado.outputs.result }}" >> "$GITHUB_STEP_SUMMARY"
Comment on lines +86 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Mitigate potential code injection via step outputs.

Lines 93-95 expand step outputs directly in the shell script. While the outputs come from a trusted internal action, if they contain shell metacharacters (backticks, $(), etc.), they could execute unintended code. Lines 91-92 are safe because workflow inputs are constrained by the choice type.

🛡️ Proposed fix using environment variables
       - name: Summary
         if: always()
+        env:
+          RUN_ID: ${{ steps.ado.outputs.run_id }}
+          RUN_URL: ${{ steps.ado.outputs.run_url }}
+          RESULT: ${{ steps.ado.outputs.result }}
         run: |
           echo "### ADO run" >> "$GITHUB_STEP_SUMMARY"
           echo "" >> "$GITHUB_STEP_SUMMARY"
           echo "- cluster: \`${{ inputs.cluster }}\`" >> "$GITHUB_STEP_SUMMARY"
           echo "- action:  \`${{ inputs.action }}\`"  >> "$GITHUB_STEP_SUMMARY"
-          echo "- run id:  ${{ steps.ado.outputs.run_id }}" >> "$GITHUB_STEP_SUMMARY"
-          echo "- url:     ${{ steps.ado.outputs.run_url }}" >> "$GITHUB_STEP_SUMMARY"
-          echo "- result:  ${{ steps.ado.outputs.result }}" >> "$GITHUB_STEP_SUMMARY"
+          echo "- run id:  $RUN_ID" >> "$GITHUB_STEP_SUMMARY"
+          echo "- url:     $RUN_URL" >> "$GITHUB_STEP_SUMMARY"
+          echo "- result:  $RESULT" >> "$GITHUB_STEP_SUMMARY"
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 91-91: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 92-92: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 93-93: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 94-94: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[info] 95-95: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/edge-cluster-trigger.yml around lines 86 - 95, The Summary
step currently injects step outputs directly into a shell script which can allow
shell metacharacters to be interpreted; instead, capture the ADO outputs into
environment variables (e.g., ADO_RUN_ID, ADO_RUN_URL, ADO_RESULT from
steps.ado.outputs.run_id/run_url/result) and then append them to
GITHUB_STEP_SUMMARY using safe, quoted expansion or printf (e.g., use printf
'%s\n' "$ADO_RUN_ID") so that backticks/$() are not executed; modify the
"Summary" run block to export/use those env vars and ensure all variable
expansions are quoted when writing to GITHUB_STEP_SUMMARY.