Skip to content

Commit b003c10

Browse files
AIML-505: create contrast-orchestrator shared workflow (#1)
* AIML-505: create contrast-orchestrator shared workflow - added jobs and module detection to get app_id and app_name - use `CONTRAST_AUTHORIZATION_KEY` - cleanup - use docker cli not forked action - add support to use a `contrast_application_id` override if auto-detection does not find app id - add support to skip contrast-triage AI feature and how long it can run - updates - use contrast code terminology
1 parent 9c4f339 commit b003c10

File tree

4 files changed

+449
-3
lines changed

4 files changed

+449
-3
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* @dougj-contrast
1+
* @Contrast-Security-OSS/aiml-developers @dougj-contrast
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,396 @@
1+
name: Contrast Code Orchestrator
12

3+
on:
4+
workflow_call:
5+
inputs:
6+
# --- Contrast Platform (required) ---
7+
contrast_host:
8+
required: true
9+
type: string
10+
description: Contrast Platform base URL
11+
contrast_org_id:
12+
required: true
13+
type: string
14+
description: Contrast organization UUID
15+
contrast_app_install_id:
16+
required: true
17+
type: string
18+
description: Contrast GitHub App installation ID
19+
20+
# --- Contrast Platform (optional override) ---
21+
contrast_application_id:
22+
required: false
23+
type: string
24+
default: ''
25+
description: Manual override for the Contrast application UUID (skips auto-detection)
26+
run_triage:
27+
required: false
28+
type: boolean
29+
description: Whether to run contrast-triage feature in SmartScan
30+
triage_duration:
31+
required: false
32+
type: string
33+
description: Duration to run the contrast-triage tool (e.g. '1m', '5m').
34+
35+
# --- Repo Context (required) ---
36+
base_branch:
37+
required: true
38+
type: string
39+
description: Default branch of the repository
40+
repo_url:
41+
required: true
42+
type: string
43+
description: GitHub repository URL
44+
repo_name:
45+
required: true
46+
type: string
47+
description: Repository full name (owner/repo)
48+
49+
# --- EA Product Disable Flags ---
50+
disable_smartscan:
51+
type: string
52+
default: 'false'
53+
description: Set to true to skip SmartScan for this repo
54+
disable_smartsca:
55+
type: string
56+
default: 'false'
57+
description: Set to true to skip SmartSCA for this repo
58+
disable_smartfix:
59+
type: string
60+
default: 'false'
61+
description: Set to true to skip SmartFix for this repo
62+
63+
# --- SmartFix Config ---
64+
build_command:
65+
description: 'Build command to run before SmartFix'
66+
type: string
67+
default: 'echo No build command specified.'
68+
formatting_command:
69+
description: 'Format command to run before SmartFix'
70+
type: string
71+
default: ''
72+
max_open_prs:
73+
description: 'Maximum number of open PRs SmartFix will open'
74+
type: string
75+
default: '5'
76+
enable_full_telemetry:
77+
description: 'Enable SmartFix full telemetry for debugging'
78+
type: string
79+
default: 'true'
80+
debug_mode:
81+
description: 'Enable SmartFix debug mode for verbose logging'
82+
type: string
83+
default: 'false'
84+
85+
# --- LLM Config ---
86+
use_contrast_llm:
87+
description: "Indicates to use Contrast internal LLM"
88+
type: string
89+
default: 'true'
90+
agent_model:
91+
description: 'The provider LLM model to use'
92+
type: string
93+
default: ''
94+
azure_api_base:
95+
description: 'The Azure API base URL'
96+
type: string
97+
default: ''
98+
azure_api_version:
99+
description: 'The Azure API version'
100+
type: string
101+
default: ''
102+
use_aws:
103+
description: 'Whether to use AWS credentials configuration'
104+
type: string
105+
default: 'false'
106+
aws_region:
107+
description: 'The AWS region for Bedrock LLM'
108+
type: string
109+
default: ''
110+
111+
# --- Runner ---
112+
github_runner:
113+
description: 'The GitHub runner to use (example: ubuntu-latest)'
114+
type: string
115+
default: 'ubuntu-latest'
116+
117+
secrets:
118+
CONTRAST_API_KEY:
119+
description: The contrast API key
120+
required: true
121+
CONTRAST_AUTHORIZATION_KEY:
122+
description: The contrast authorization key
123+
required: true
124+
GH_TOKEN:
125+
description: The GitHub token
126+
required: true
127+
ANTHROPIC_API_KEY:
128+
description: The Anthropic API key
129+
required: false
130+
GEMINI_API_KEY:
131+
description: The Google Gemini API key
132+
required: false
133+
AZURE_API_KEY:
134+
description: The Azure API key
135+
required: false
136+
AWS_BEARER_TOKEN_BEDROCK:
137+
description: The AWS Bedrock API key
138+
required: false
139+
AWS_ACCESS_KEY_ID:
140+
description: The AWS access key id
141+
required: false
142+
AWS_SECRET_ACCESS_KEY:
143+
description: The AWS secret access key
144+
required: false
145+
AWS_SESSION_TOKEN:
146+
description: The AWS session token
147+
required: false
148+
AWS_ROLE_TO_ASSUME:
149+
description: The AWS role to assume
150+
required: false
151+
152+
permissions:
153+
contents: write
154+
pull-requests: write
155+
id-token: write
156+
157+
jobs:
158+
159+
# ─────────────────────────────────────────────────────────────────────
160+
# JOB 1: Detect Contrast Application Data
161+
# Runs contrast/module-identifier:v1 to resolve the Contrast app ID/Name
162+
# for this repository. The container writes CONTRAST_APP_ID / CONTRAST_APP_Name
163+
# to $GITHUB_ENV. The validate step forwards it to $GITHUB_OUTPUT so
164+
# downstream jobs can consume it.
165+
# ─────────────────────────────────────────────────────────────────────
166+
detect-app-data:
167+
name: Detect Contrast Application Data
168+
runs-on: ${{ inputs.github_runner }}
169+
outputs:
170+
contrast_app_id: ${{ steps.set-app-id-output.outputs.contrast_app_id }}
171+
contrast_app_name: ${{ steps.set-app-name-output.outputs.contrast_app_name }}
172+
steps:
173+
- name: Checkout Repository
174+
uses: actions/checkout@v4
175+
176+
#- name: Configure AWS Credentials
177+
#if: ${{ inputs.use_aws == 'true' && inputs.aws_region != '' }}
178+
#uses: aws-actions/configure-aws-credentials@v5
179+
#with:
180+
#aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
181+
#aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
182+
#aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
183+
#role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
184+
#aws-region: ${{ inputs.aws_region }}
185+
186+
- name: Print workspace path
187+
run: |
188+
echo "GitHub workspace path: ${{ github.workspace }}"
189+
echo "GITHUB_WORKSPACE env var: $GITHUB_WORKSPACE"
190+
191+
- name: Run Contrast Module Identifier
192+
uses: docker://contrast/module-identifier
193+
with:
194+
args: "$GITHUB_WORKSPACE" --single --debug
195+
env:
196+
CONTRAST_HOST_NAME: ${{ inputs.contrast_host }}
197+
CONTRAST_ORG_ID: ${{ inputs.contrast_org_id }}
198+
CONTRAST_API_KEY: ${{ secrets.CONTRAST_API_KEY }}
199+
CONTRAST_AUTH_TOKEN: ${{ secrets.CONTRAST_AUTHORIZATION_KEY }}
200+
201+
- name: Validate Application ID and Set Output
202+
id: set-app-id-output
203+
shell: bash
204+
run: |
205+
if [[ -n "$CONTRAST_APP_ID" && "$CONTRAST_APP_ID" != "null" ]]; then
206+
echo "Successfully detected contrast application ID: $CONTRAST_APP_ID"
207+
elif [[ -n "${{ inputs.contrast_application_id }}" ]]; then
208+
echo "Auto-detection did not find an contrast application ID. Using contrast_application_id input override."
209+
CONTRAST_APP_ID="${{ inputs.contrast_application_id }}"
210+
echo "CONTRAST_APP_ID=$CONTRAST_APP_ID" >> $GITHUB_ENV
211+
else
212+
echo "Contrast application ID could not be automatically detected for repository '${{ inputs.repo_name }}'."
213+
echo ""
214+
echo "Set CONTRAST_APP_ID manually:"
215+
echo " 1. Browse your applications: ${{ inputs.contrast_host }}/Contrast/static/ng/index.html#/${{ inputs.contrast_org_id }}/applications"
216+
echo " 2. Select your application"
217+
echo " 3. The ID is the UUID after applications/ in the URL"
218+
echo " e.g. applications/057254e6-f065-40fa-8197-e1d7ace10e67"
219+
fi
220+
221+
if [[ -n "$CONTRAST_APP_ID" && "$CONTRAST_APP_ID" != "null" ]]; then
222+
echo "contrast_app_id=$CONTRAST_APP_ID" >> $GITHUB_OUTPUT
223+
fi
224+
225+
- name: Validate Application Name and Set Output
226+
id: set-app-name-output
227+
shell: bash
228+
run: |
229+
if [[ -z "$CONTRAST_APP_NAME" || "$CONTRAST_APP_NAME" == "null" ]]; then
230+
echo "Contrast application name could not be automatically detected for repository '${{ inputs.repo_name }}'."
231+
else
232+
echo "Successfully detected application name: $CONTRAST_APP_NAME"
233+
echo "contrast_app_name=$CONTRAST_APP_NAME" >> $GITHUB_OUTPUT
234+
fi
235+
236+
# ─────────────────────────────────────────────────────────────────────
237+
# JOB 2: Run SmartScan
238+
# SAST scanner — runs on every trigger event.
239+
# Skipped if disable_smartscan is 'true'.
240+
# Uses contrast/smartscan:v1 public DockerHub image — no credentials required.
241+
# ─────────────────────────────────────────────────────────────────────
242+
run-contrast-code:
243+
name: Run Contrast Code
244+
needs: detect-app-data
245+
if: ${{ inputs.disable_smartscan != 'true' }}
246+
runs-on: ${{ inputs.github_runner }}
247+
steps:
248+
- name: Checkout Repository
249+
uses: actions/checkout@v4
250+
251+
- name: Configure AWS Credentials
252+
if: ${{ inputs.use_aws == 'true' && inputs.aws_region != '' }}
253+
uses: aws-actions/configure-aws-credentials@v5
254+
with:
255+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
256+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
257+
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
258+
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
259+
aws-region: ${{ inputs.aws_region }}
260+
261+
- name: Get Repository Languages
262+
id: get_repo_langs
263+
uses: octokit/request-action@v2.4.0
264+
with:
265+
route: GET /repos/${{ inputs.repo_name }}/languages
266+
env:
267+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
268+
269+
- name: Set Repo Language Info
270+
run: |
271+
echo '${{ steps.get_repo_langs.outputs.data }}' \
272+
| jq -r 'to_entries[] | [.[]] | join(",")' \
273+
| tr '\n' '|' \
274+
| xargs -I '{}' echo "REPO_LANG_INFO={}" >> $GITHUB_ENV
275+
276+
- name: Set Branch Name
277+
run: |
278+
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
279+
echo "BRANCH_NAME=${{ github.head_ref }}" >> $GITHUB_ENV
280+
else
281+
echo "BRANCH_NAME=${{ github.ref_name }}" >> $GITHUB_ENV
282+
fi
283+
284+
- name: Run Contrast Code Scanner
285+
uses: docker://contrast/sast-smartai
286+
env:
287+
LOGLEVEL: 'DEBUG'
288+
REPO_NAME: ${{ github.event.repository.name }}
289+
REPO_LANG_INFO: ${{ env.REPO_LANG_INFO }}
290+
REPO_HTTP_URL: ${{ inputs.repo_url }}/${{ inputs.repo_name }}
291+
BRANCH_NAME: ${{ env.BRANCH_NAME }}
292+
DEFAULT_BRANCH: ${{ inputs.base_branch }}
293+
APPLICATION_NAME: ${{ needs.detect-app-name.outputs.contrast_app_name }}
294+
COMMIT_HASH: ${{ github.sha }}
295+
COMMITTER: ${{ github.triggering_actor }}
296+
TAG: ${{ github.ref }}
297+
RUN_ID: ${{ github.run_id }}
298+
WORKFLOW_ID: ${{ github.workflow_sha }}
299+
CONTRAST_BASE_URL: ${{ inputs.contrast_host }}
300+
CONTRAST_ORG_ID: ${{ inputs.contrast_org_id }}
301+
CONTRAST_API_KEY: ${{ secrets.CONTRAST_API_KEY }}
302+
CONTRAST_AUTH_TOKEN: ${{ secrets.CONTRAST_AUTHORIZATION_KEY }}
303+
RUN_TRIAGE: ${{ inputs.run_triage || True }}
304+
TRIAGE_DURATION: ${{ inputs.triage_duration || '' }}
305+
APPLICATION_DIR: "$GITHUB_WORKSPACE"
306+
307+
# ─────────────────────────────────────────────────────────────────────
308+
# JOB 3: Run SmartSCA
309+
# SCA dependency scanning. Disabled by default for EA.
310+
# Will be enabled once contrast/smart-sca:v1 is delivered by the SCA team.
311+
# ─────────────────────────────────────────────────────────────────────
312+
run-sca:
313+
name: Run SmartSCA
314+
needs: detect-app-data
315+
if: ${{ inputs.disable_smartsca != 'true' }}
316+
runs-on: ${{ inputs.github_runner }}
317+
steps:
318+
- name: Checkout Repository
319+
uses: actions/checkout@v4
320+
321+
- name: Run SmartSCA
322+
uses: docker://dougjeremias489/contrast-sca-cli
323+
env:
324+
CONTRAST_HOST: ${{ inputs.contrast_host }}
325+
CONTRAST_ORG_ID: ${{ inputs.contrast_org_id }}
326+
CONTRAST_APP_INSTALLATION_ID: ${{ inputs.contrast_app_install_id }}
327+
CONTRAST_API_KEY: ${{ secrets.CONTRAST_API_KEY }}
328+
CONTRAST_AUTHORIZATION: ${{ secrets.CONTRAST_AUTHORIZATION_KEY }}
329+
GITHUB_SERVER_URL: ${{ inputs.repo_url }}
330+
GITHUB_REPOSITORY: ${{ inputs.repo_name }}
331+
332+
# ─────────────────────────────────────────────────────────────────────
333+
# JOB 4: Run SmartFix
334+
# AI-powered fix PR generation. Event-gated — only runs on:
335+
# • pull_request (closed) — handles merge notification or PR close
336+
# • workflow_dispatch — manual trigger to generate fixes
337+
# • schedule — scheduled fix generation
338+
# Does NOT run on PR open/synchronize to avoid redundant runs.
339+
# ─────────────────────────────────────────────────────────────────────
340+
run-smartfix:
341+
name: Run SmartFix
342+
needs: detect-app-data
343+
if: |
344+
inputs.disable_smartfix != 'true' &&
345+
(
346+
github.event_name == 'workflow_dispatch' ||
347+
github.event_name == 'schedule' ||
348+
(github.event_name == 'pull_request' && github.event.action == 'closed')
349+
)
350+
runs-on: ${{ inputs.github_runner }}
351+
steps:
352+
- name: Determine SmartFix Job Type
353+
id: job_type
354+
run: |
355+
if [[ "${{ github.event.pull_request.merged }}" == "true" ]]; then
356+
echo "label=Notify Contrast on PR Merge" >> $GITHUB_OUTPUT
357+
elif [[ "${{ github.event.pull_request.merged }}" == "false" ]]; then
358+
echo "label=Handle PR Close" >> $GITHUB_OUTPUT
359+
else
360+
echo "label=Run Contrast AI SmartFix - Generate Fixes" >> $GITHUB_OUTPUT
361+
fi
362+
363+
- name: Configure AWS Credentials
364+
if: ${{ inputs.use_aws == 'true' && inputs.aws_region != '' }}
365+
uses: aws-actions/configure-aws-credentials@v5
366+
with:
367+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
368+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
369+
aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }}
370+
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
371+
aws-region: ${{ inputs.aws_region }}
372+
373+
- name: ${{ steps.job_type.outputs.label }}
374+
uses: Contrast-Security-OSS/contrast-ai-smartfix-action@v1
375+
with:
376+
contrast_host: ${{ inputs.contrast_host }}
377+
contrast_org_id: ${{ inputs.contrast_org_id }}
378+
contrast_app_id: ${{ needs.detect-app-id.outputs.contrast_app_id || contrast_application_id }}
379+
base_branch: ${{ inputs.base_branch }}
380+
build_command: ${{ inputs.build_command }}
381+
formatting_command: ${{ inputs.formatting_command }}
382+
use_contrast_llm: ${{ inputs.use_contrast_llm }}
383+
max_open_prs: ${{ inputs.max_open_prs }}
384+
agent_model: ${{ inputs.agent_model }}
385+
azure_api_base: ${{ inputs.azure_api_base }}
386+
azure_api_version: ${{ inputs.azure_api_version }}
387+
enable_full_telemetry: ${{ inputs.enable_full_telemetry }}
388+
debug_mode: ${{ inputs.debug_mode }}
389+
aws_region: ${{ inputs.aws_region }}
390+
contrast_api_key: ${{ secrets.CONTRAST_API_KEY }}
391+
contrast_authorization_key: ${{ secrets.CONTRAST_AUTHORIZATION_KEY }}
392+
github_token: ${{ secrets.GH_TOKEN }}
393+
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
394+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
395+
azure_api_key: ${{ secrets.AZURE_API_KEY }}
396+
aws_bearer_token_bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}

0 commit comments

Comments
 (0)