Skip to content

Commit ac8a198

Browse files
feat: scan github actions for commit hash reference (RD-2964) (#76)
* feat(RD-2964): add static analysis action for GitHub workflows Introduces a new GitHub Action that performs static analysis on workflow YAML files, ensuring that all action calls use commit hashes. The action scans for YAML files in the workflows directory and provides warnings if any issues are found, along with a link to a Jira ticket for resolution. * feat: add S3 Cache Action for GitHub workflows Introduces a new GitHub Action that caches files and folders to an S3 bucket, allowing users to specify the cache path, unique key, and AWS credentials. This action enhances workflow efficiency by enabling caching capabilities directly within GitHub Actions. * fix(RD-2964): add logging for script execution in action.yml Enhances the static analysis GitHub Action by adding logging statements to indicate the script being run and the current working directory. This improves visibility during execution and aids in debugging. * fix(RD-2964): update working directory in static analysis action Modifies the working directory for the static analysis GitHub Action to use the GitHub workspace combined with the specified input path, ensuring correct execution context during script runs. * fix(RD-2964): simplify working directory in static analysis action Removes the input path requirement for the working directory in the static analysis GitHub Action, ensuring it defaults to the GitHub workspace for improved execution context. * fix: remove S3 Cache Action from GitHub workflows * fix(RD-2964): change exit code to zero when warnings are found in static analysis
1 parent aa4af9b commit ac8a198

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: "Static Analysis"
2+
description: "Runs static analysis on the code"
3+
4+
runs:
5+
using: "composite"
6+
steps:
7+
- name: Checkout Code
8+
uses: actions/checkout@v4
9+
- name: Scan workflow yml files
10+
run: |
11+
echo "Running the following script: ${{ github.action_path }}/scan_github_actions.js"
12+
echo "With the current working directory: $(pwd)"
13+
node ${{ github.action_path }}/scan_github_actions.js
14+
shell: bash
15+
working-directory: ${{ github.workspace }}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
class StaticAnalysis {
2+
constructor(dataProvider, process) {
3+
this.dataProvider = dataProvider;
4+
this.process = process;
5+
}
6+
7+
isCommitHash(ref) {
8+
return /^[0-9a-f]{40}$/.test(ref);
9+
}
10+
11+
findYamlFiles(dir) {
12+
let results = [];
13+
const list = this.dataProvider.readDirectory(dir);
14+
list.forEach(file => {
15+
const fullPath = this.dataProvider.path.join(dir, file);
16+
const stat = this.dataProvider.getFileStats(fullPath);
17+
if (stat && stat.isDirectory()) {
18+
results = results.concat(this.findYamlFiles(fullPath));
19+
} else if (file.endsWith('.yml') || file.endsWith('.yaml')) {
20+
results.push(fullPath);
21+
}
22+
});
23+
return results;
24+
}
25+
26+
scanFile(filePath) {
27+
const content = this.dataProvider.readFile(filePath);
28+
const regex = /uses:\s*[\w-]+\/[\w-]+@([\w-.]+)/g;
29+
let match;
30+
let warnings = [];
31+
while ((match = regex.exec(content)) !== null) {
32+
const ref = match[1];
33+
if (!this.isCommitHash(ref)) {
34+
warnings.push(`Warning: In file ${filePath}, '${match[0]}' does not use a commit hash.`);
35+
}
36+
}
37+
return warnings;
38+
}
39+
40+
run() {
41+
const workflowDir = this.dataProvider.path.join(this.process.cwd(), '.github', 'workflows');
42+
if (!this.dataProvider.fileExists(workflowDir)) {
43+
console.error(`Error: Directory ${workflowDir} does not exist.`);
44+
this.process.exit(1);
45+
}
46+
47+
const yamlFiles = this.findYamlFiles(workflowDir);
48+
let allWarnings = [];
49+
50+
yamlFiles.forEach(filePath => {
51+
const warnings = this.scanFile(filePath);
52+
allWarnings = allWarnings.concat(warnings);
53+
});
54+
55+
if (allWarnings.length > 0) {
56+
allWarnings.forEach(warning => console.warn(warning));
57+
console.log('To fix these issues, refer to the solution in the following Jira ticket: https://virdocs.atlassian.net/browse/RD-2964');
58+
this.process.exit(0); // TODO: Exit with non-zero code if warnings are found
59+
} else {
60+
console.log('No issues found. All action calls use commit hashes.');
61+
this.process.exit(0);
62+
}
63+
}
64+
}
65+
66+
class DataProvider {
67+
constructor(fs, path) {
68+
this.fs = fs;
69+
this.path = path;
70+
}
71+
72+
readDirectory(dir) {
73+
return this.fs.readdirSync(dir);
74+
}
75+
76+
readFile(filePath) {
77+
return this.fs.readFileSync(filePath, 'utf8');
78+
}
79+
80+
fileExists(filePath) {
81+
return this.fs.existsSync(filePath);
82+
}
83+
84+
getFileStats(filePath) {
85+
return this.fs.statSync(filePath);
86+
}
87+
}
88+
89+
// Dependency injection
90+
const fs = require('fs');
91+
const path = require('path');
92+
const process = require('process');
93+
94+
const dataProvider = new DataProvider(fs, path);
95+
const staticAnalysis = new StaticAnalysis(dataProvider, process);
96+
staticAnalysis.run();

0 commit comments

Comments
 (0)