Skip to content

Commit fb23bac

Browse files
esolitosCopilot
andcommitted
feat: add reusable Mend code scan workflow
refactor(workflows): replace code scan with unified mend scan workflow Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ece1505 commit fb23bac

7 files changed

Lines changed: 740 additions & 0 deletions

File tree

.github/workflows/mend.yml

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
---
2+
name: Mend Scan (reusable)
3+
4+
on:
5+
workflow_call:
6+
inputs:
7+
scan-type:
8+
description: |
9+
Type of scan to perform.
10+
Options:
11+
- 'sast' (or 'code') for Static Application Security Testing (scanning source code)
12+
- 'sca' (or 'dependencies') for Software Composition Analysis (scanning dependencies)
13+
- 'image' for Container Image Scanning. Note: This REQUIRES the 'image-list' input to be set.
14+
required: true
15+
type: string
16+
17+
mend-app-name:
18+
description: "Mend Application Name"
19+
required: true
20+
type: string
21+
22+
mend-project-name:
23+
description: "Mend Project Name"
24+
required: false
25+
type: string
26+
27+
tags:
28+
description: "Assign tags to scan and project (comma-separated key:value pairs)"
29+
required: false
30+
type: string
31+
32+
sast-source-path:
33+
description: "Path to the source code to be scanned"
34+
required: false
35+
type: string
36+
default: "."
37+
38+
sast-engines:
39+
description: |
40+
**Recommended:** Comma-separated list of SAST engine IDs (e.g., '101,108' for Java and JavaScript). Omit for auto-recognition.
41+
Useful ones: Ruby(5), C/C++(12), Go(18), Java(101), Python(104), JavaScript(108)
42+
[See documentation for complete list](https://docs.mend.io/platform/latest/configure-the-mend-cli-for-sast#ConfiguretheMendCLIforSAST-Mend-CLI-SAST-supported-languages-and-engine-IDsMendCLISAST-supportedlanguagesandengineIDs)
43+
required: false
44+
type: string
45+
46+
image-list:
47+
description: |
48+
List of container images to scan (one per line).
49+
Required when scan-type is 'image'.
50+
Example:
51+
myregistry.io/app:latest
52+
myregistry.io/api:v1.0
53+
required: false
54+
type: string
55+
56+
secrets:
57+
MEND_EMAIL:
58+
description: "Mend user email for authentication"
59+
required: true
60+
MEND_USER_KEY:
61+
description: "Mend user API key for authentication"
62+
required: true
63+
64+
defaults:
65+
run:
66+
# Specify to ensure "pipefail and errexit" are set.
67+
# Ref: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#defaultsrunshell
68+
shell: bash
69+
70+
env:
71+
MEND_APP: ${{ inputs.mend-app-name }}
72+
MEND_PROJ: ${{ inputs.mend-project-name }} # optional
73+
MEND_EMAIL: ${{ secrets.MEND_EMAIL }}
74+
MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }}
75+
MEND_URL: https://saas-eu.mend.io
76+
77+
jobs:
78+
prepare-image-matrix:
79+
if: ${{ inputs.scan-type == 'image' }}
80+
81+
runs-on: ubuntu-latest
82+
outputs:
83+
images: ${{ steps.parse.outputs.images }}
84+
85+
steps:
86+
- name: Parse Image List
87+
id: parse
88+
run: |
89+
images='${{ inputs.image-list }}'
90+
91+
if [ -z "$images" ]; then
92+
echo "❌ Error: image-list input is required for image scanning"
93+
exit 1
94+
fi
95+
96+
# Convert newline-separated list to JSON array
97+
# Remove comments (lines starting with #), empty lines, and whitespace
98+
json_array=$(echo "$images" | sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' | jq -R -s -c 'split("\n") | map(select(length > 0))')
99+
100+
echo "images=$json_array" >> $GITHUB_OUTPUT
101+
echo "📋 Images to scan: $json_array"
102+
103+
104+
scan-image:
105+
if: ${{ inputs.scan-type == 'image' }}
106+
107+
runs-on: ubuntu-latest
108+
109+
env:
110+
MEND_IMAGE_SCAN_TIMEOUT_TOTAL: "3600"
111+
MEND_IMAGE_SCAN_RETRIES: "3"
112+
113+
needs: [prepare-image-matrix]
114+
115+
strategy:
116+
fail-fast: false
117+
matrix:
118+
image: ${{ fromJson(needs.prepare-image-matrix.outputs.images) }}
119+
120+
steps:
121+
- name: Install Mend CLI
122+
run: |
123+
if command -v mend &> /dev/null; then
124+
echo "✅ Mend CLI already installed"
125+
mend --version
126+
exit 0
127+
fi
128+
129+
ARCH=$(uname -m)
130+
case $ARCH in
131+
x86_64) ARCH="amd64" ;;
132+
aarch64) ARCH="arm64" ;;
133+
esac
134+
135+
CLI_URL="https://downloads.mend.io/cli/linux_${ARCH}/mend"
136+
curl -fsSL "${CLI_URL}" -o /tmp/mend
137+
sudo mv /tmp/mend /usr/local/bin/mend
138+
sudo chmod +x /usr/local/bin/mend
139+
echo "✅ Mend CLI installed"
140+
mend --version
141+
142+
- name: Define Mend Project Name
143+
if: ${{ env.MEND_PROJ == '' }}
144+
run: |
145+
# Replace any forward slashes and colons in the image name with double underscores
146+
MEND_PROJ=$(echo "${{ matrix.image }}" | sed 's/[\/:]/__/g')
147+
echo "Normalized Mend Project Name: ${MEND_PROJ}"
148+
echo "MEND_PROJ=${MEND_PROJ}" >> $GITHUB_ENV
149+
150+
- name: Run Mend Image Scan
151+
id: scan
152+
run: |
153+
echo "🔍 Scanning image: ${{ matrix.image }}"
154+
155+
mend_args=(
156+
"--non-interactive"
157+
"--scope" "${MEND_APP}//${MEND_PROJ}"
158+
)
159+
160+
if [ -n "${{ inputs.tags }}" ]; then
161+
mend_args+=("--tags" "${{ inputs.tags }}")
162+
fi
163+
164+
echo "Using Mend arguments:"
165+
for arg in "${mend_args[@]}"; do
166+
echo " - $arg"
167+
done
168+
169+
set +e
170+
mend image "${mend_args[@]}" "${{ matrix.image }}"
171+
exit_code=$?
172+
echo "mend-exit=$exit_code" >> $GITHUB_OUTPUT
173+
174+
- name: Process Scan Results
175+
env:
176+
SCAN_EXIT_CODE: ${{ steps.scan.outputs.mend-exit }}
177+
run: |
178+
# Generate GitHub Actions Job Summary
179+
echo "## 🔍 Mend Image Scan Results" >> $GITHUB_STEP_SUMMARY
180+
echo "" >> $GITHUB_STEP_SUMMARY
181+
echo "**Image:** ${{ matrix.image }}" >> $GITHUB_STEP_SUMMARY
182+
echo "**Application:** ${MEND_APP}" >> $GITHUB_STEP_SUMMARY
183+
echo "**Project:** ${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
184+
echo "" >> $GITHUB_STEP_SUMMARY
185+
186+
case $SCAN_EXIT_CODE in
187+
0)
188+
echo "### ✅ Success" >> $GITHUB_STEP_SUMMARY
189+
echo "Image scan completed successfully with no policy violations." >> $GITHUB_STEP_SUMMARY
190+
;;
191+
1)
192+
echo "### ❌ Invalid Configuration" >> $GITHUB_STEP_SUMMARY
193+
echo "An invalid configuration parameter was passed. Check for typos in the parameters." >> $GITHUB_STEP_SUMMARY
194+
;;
195+
2)
196+
echo "### ❌ Connection Error" >> $GITHUB_STEP_SUMMARY
197+
echo "Unable to access the update or license details from the Mend server URL. Check internet connection." >> $GITHUB_STEP_SUMMARY
198+
;;
199+
9)
200+
echo "### ⚠️ Policy Violation" >> $GITHUB_STEP_SUMMARY
201+
echo "Image scan results contain vulnerabilities that contravene the defined policy." >> $GITHUB_STEP_SUMMARY
202+
echo "" >> $GITHUB_STEP_SUMMARY
203+
echo "Please review the findings in the Mend platform." >> $GITHUB_STEP_SUMMARY
204+
;;
205+
*)
206+
echo "### ❌ Error" >> $GITHUB_STEP_SUMMARY
207+
echo "Image scan failed with exit code: ${SCAN_EXIT_CODE}" >> $GITHUB_STEP_SUMMARY
208+
;;
209+
esac
210+
211+
exit $SCAN_EXIT_CODE

mend-image/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Mend Image Scan Action
2+
3+
## Usage
4+
5+
```yaml
6+
- uses: vespa-engine/gh-actions/mend-image@main
7+
with:
8+
image-name: "docker.io/vespaengine/vespa:latest"
9+
mend-api-key: ${{ secrets.MEND_API_KEY }}
10+
mend-user: ${{ secrets.MEND_USER }}
11+
```

mend-image/action.yml

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
name: Image Scan with Mend CLI
2+
description: Run Mend container image scanning on your images
3+
4+
inputs:
5+
image-name:
6+
description: "Container image to scan (e.g., alpine:latest, myregistry.io/app:v1.0)"
7+
required: true
8+
9+
mend-app-name:
10+
description: "Mend Application Name"
11+
required: true
12+
13+
mend-project-name:
14+
description: "Mend Project Name"
15+
required: true
16+
17+
mend-api-key:
18+
description: "Mend API Key (pass from secrets)"
19+
required: true
20+
21+
mend-user:
22+
description: "Mend User Email (pass from secrets)"
23+
required: true
24+
25+
mend-url:
26+
description: "Mend Server URL"
27+
required: false
28+
default: "https://saas-eu.mend.io"
29+
30+
local-pull:
31+
description: "Pull from local Docker only (true/false)"
32+
required: false
33+
default: "false"
34+
35+
fail-on-policy-violation:
36+
description: "Fail build on policy violations (true/false)"
37+
required: false
38+
default: "true"
39+
40+
log-level:
41+
description: "Log level (INFO, DEBUG, WARNING, ERROR)"
42+
required: false
43+
default: "INFO"
44+
45+
runs:
46+
using: "composite"
47+
steps:
48+
- name: Install Mend CLI
49+
shell: bash
50+
env:
51+
ARCH: ${{ contains(fromJson('["X86", "X64"]'), runner.arch) && 'amd64' || 'arm64' }}
52+
run: |
53+
CLI_URL="https://downloads.mend.io/cli/linux_${ARCH}/mend"
54+
echo "📥 Downloading Mend CLI from ${CLI_URL}"
55+
curl -fsSL "${CLI_URL}" -o /tmp/mend
56+
sudo mv /tmp/mend /usr/local/bin/mend
57+
sudo chmod +x /usr/local/bin/mend
58+
mend version
59+
60+
- name: Run Mend Image Scan
61+
shell: bash
62+
id: scan
63+
env:
64+
# Authentication variables
65+
MEND_USER_KEY: ${{ inputs.mend-api-key }}
66+
MEND_EMAIL: ${{ inputs.mend-user }}
67+
MEND_URL: ${{ inputs.mend-url }}
68+
69+
# Scope
70+
MEND_APP: ${{ inputs.mend-app-name }}
71+
MEND_PROJ: ${{ inputs.mend-project-name }}
72+
73+
MEND_LOG_LEVEL: ${{ inputs.log-level }}
74+
run: |
75+
echo "🐳 Starting Mend Image Scan..."
76+
echo "Image: ${{ inputs.image-name }}"
77+
echo "Application: ${MEND_APP}"
78+
echo "Project: ${MEND_PROJ}"
79+
80+
mend_args=(
81+
"--non-interactive"
82+
"--scope" "${MEND_APP}//${MEND_PROJ}"
83+
)
84+
85+
# Add local-pull flag if enabled
86+
if [[ "${{ inputs.local-pull }}" == "true" ]]; then
87+
mend_args+=("--local-pull")
88+
fi
89+
90+
# Add fail-policy flag if enabled
91+
if [[ "${{ inputs.fail-on-policy-violation }}" == "true" ]]; then
92+
mend_args+=("--fail-policy")
93+
fi
94+
95+
set +e
96+
mend image "${{ inputs.image-name }}" "${mend_args[@]}"
97+
exit_code=$?
98+
99+
# Save exit code for next step
100+
echo "mend-exit=$exit_code" >> $GITHUB_OUTPUT
101+
echo "MEND_APP=${MEND_APP}" >> $GITHUB_ENV
102+
echo "MEND_PROJ=${MEND_PROJ}" >> $GITHUB_ENV
103+
104+
- name: Process Scan Results
105+
shell: bash
106+
env:
107+
SCAN_EXIT_CODE: ${{ steps.scan.outputs.mend-exit }}
108+
run: |
109+
# Generate GitHub Actions Job Summary
110+
echo "## 🐳 Mend Image Scan Results" >> $GITHUB_STEP_SUMMARY
111+
echo "" >> $GITHUB_STEP_SUMMARY
112+
echo "**Image:** ${{ inputs.image-name }}" >> $GITHUB_STEP_SUMMARY
113+
echo "**Application:** ${MEND_APP}" >> $GITHUB_STEP_SUMMARY
114+
echo "**Project:** ${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
115+
echo "" >> $GITHUB_STEP_SUMMARY
116+
117+
case $SCAN_EXIT_CODE in
118+
0)
119+
echo "### ✅ Success" >> $GITHUB_STEP_SUMMARY
120+
echo "Image scan completed successfully with no policy violations." >> $GITHUB_STEP_SUMMARY
121+
;;
122+
1)
123+
echo "### ❌ Invalid Configuration" >> $GITHUB_STEP_SUMMARY
124+
echo "An invalid configuration parameter was passed. Check for typos in the parameters." >> $GITHUB_STEP_SUMMARY
125+
;;
126+
2)
127+
echo "### ❌ Connection Error" >> $GITHUB_STEP_SUMMARY
128+
echo "Unable to access the update or license details from the Mend server URL. Check internet connection." >> $GITHUB_STEP_SUMMARY
129+
;;
130+
9)
131+
echo "### ⚠️ Policy Violation" >> $GITHUB_STEP_SUMMARY
132+
echo "Image scan results contain vulnerabilities that contravene the defined policy." >> $GITHUB_STEP_SUMMARY
133+
echo "" >> $GITHUB_STEP_SUMMARY
134+
echo "Please review the findings in the Mend platform." >> $GITHUB_STEP_SUMMARY
135+
;;
136+
*)
137+
echo "### ❌ Error" >> $GITHUB_STEP_SUMMARY
138+
echo "Image scan failed with exit code: ${SCAN_EXIT_CODE}" >> $GITHUB_STEP_SUMMARY
139+
echo "" >> $GITHUB_STEP_SUMMARY
140+
echo "Check the Mend CLI documentation for exit code details." >> $GITHUB_STEP_SUMMARY
141+
;;
142+
esac
143+
144+
exit $SCAN_EXIT_CODE

mend-sast/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Mend Code Scan Action
2+
3+
## Usage
4+
5+
```yaml
6+
- uses: vespa-engine/gh-actions/mend-sast-scan@main
7+
with:
8+
mend-app-name: "vespa-engine"
9+
mend-project-name: "MyProject" # Optional
10+
mend-api-key: ${{ secrets.MEND_API_KEY }}
11+
mend-email: ${{ secrets.MEND_EMAIL }}
12+
```

0 commit comments

Comments
 (0)