Merge pull request #4723 from backstage/rugvip/frossa
scripts,workflows: add fossa wrapper script for config generation and retries
This commit is contained in:
@@ -6,39 +6,19 @@ on:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
analyze:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
# We use this to modify the generated .fossa.yml
|
||||
- name: Install yq
|
||||
run: sudo snap install yq
|
||||
|
||||
- name: Install Fossa
|
||||
run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash"
|
||||
|
||||
- name: Configure Fossa
|
||||
# The --option flag for fossa init does not work yet, see https://github.com/fossas/fossa-cli/issues/614
|
||||
run: |
|
||||
fossa init
|
||||
yq eval -i '.analyze.modules[].options.strategy = "yarn-list"' .fossa.yml
|
||||
|
||||
# This deletes entries for template and example packages found within packages and plugins
|
||||
# Seems like yq has a bug that causes only a subset of all matches to be deleted each run
|
||||
yq eval -i 'del(.analyze.modules[] | select(.path == "*/*/**"))' .fossa.yml
|
||||
yq eval -i 'del(.analyze.modules[] | select(.path == "*/*/**"))' .fossa.yml
|
||||
yq eval -i 'del(.analyze.modules[] | select(.path == "*/*/**"))' .fossa.yml
|
||||
yq eval -i 'del(.analyze.modules[] | select(.path == "*/*/**"))' .fossa.yml
|
||||
yq eval -i 'del(.analyze.modules[] | select(.path == "*/*/**"))' .fossa.yml
|
||||
|
||||
- name: Show config
|
||||
run: cat .fossa.yml
|
||||
|
||||
- name: Fossa Analyze
|
||||
- name: Fossa Configure & Analyze
|
||||
env:
|
||||
# FOSSA Push-Only API Token
|
||||
GITHUB_REF: $GITHUB_REF
|
||||
FOSSA_API_KEY: 9ee7e8893660832a7387dcc32377fb61
|
||||
run: fossa analyze --branch "$GITHUB_REF"
|
||||
run: node scripts/run-fossa.js
|
||||
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This script generates an appropriate fossa config, and wraps the running
|
||||
// of `fossa analyze` in a retry loop as it frequently fails with a 502 error
|
||||
|
||||
const { resolve: resolvePath, join: joinPath, basename } = require('path');
|
||||
const { promises: fs } = require('fs');
|
||||
const { execFile: execFileCb } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
|
||||
const execFile = promisify(execFileCb);
|
||||
|
||||
const FOSSA_YAML_HEAD = `
|
||||
version: 2
|
||||
cli:
|
||||
server: https://app.fossa.com
|
||||
fetcher: custom
|
||||
project: backstage
|
||||
analyze:
|
||||
modules:`;
|
||||
|
||||
const IGNORED_DIRS = ['node_modules', 'dist', 'bin', '.git'];
|
||||
|
||||
// Finds all directories containing package.json files that we're interested in analyzing
|
||||
async function findPackageJsonDirs(dir, depth = 0) {
|
||||
if (depth > 2) {
|
||||
return []; // Skipping packages that are deeper than 2 dirs in
|
||||
}
|
||||
const files = await fs.readdir(dir);
|
||||
const paths = await Promise.all(
|
||||
files
|
||||
.filter(file => !IGNORED_DIRS.includes(file))
|
||||
.map(async file => {
|
||||
const path = joinPath(dir, file);
|
||||
|
||||
if ((await fs.stat(path)).isDirectory()) {
|
||||
return findPackageJsonDirs(path, depth + 1);
|
||||
} else if (file === 'package.json') {
|
||||
return dir;
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
);
|
||||
return paths.flat();
|
||||
}
|
||||
|
||||
// A replacement for `fossa init`, as that generates a bad config for this repo
|
||||
async function generateConfig(paths) {
|
||||
let content = FOSSA_YAML_HEAD;
|
||||
|
||||
for (const path of paths) {
|
||||
content += `
|
||||
- name: ${basename(path)}
|
||||
type: npm
|
||||
path: ${path}
|
||||
target: ${path}
|
||||
options:
|
||||
strategy: yarn-list
|
||||
`;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
// Runs `fossa analyze`, with 502 errors being retried up to 3 times
|
||||
async function runAnalyze(githubRef) {
|
||||
for (let attempt = 1; attempt <= 3; attempt++) {
|
||||
console.error(`Running fossa analyze, attempt ${attempt}`);
|
||||
try {
|
||||
const { stdout, stderr } = await execFile(
|
||||
'fossa',
|
||||
['analyze', '--branch', githubRef],
|
||||
{ shell: true },
|
||||
);
|
||||
console.error(stderr);
|
||||
console.log(stdout);
|
||||
|
||||
return; // Analyze was successful, we're done
|
||||
} catch (error) {
|
||||
if (!error.code) {
|
||||
throw error;
|
||||
}
|
||||
if (error.stderr) {
|
||||
process.stderr.write(error.stderr);
|
||||
}
|
||||
if (error.stdout) {
|
||||
process.stdout.write(error.stdout);
|
||||
}
|
||||
if (error.stderr && error.stderr.includes('502 Bad Gateway')) {
|
||||
console.error('Encountered 502 during fossa analysis upload, retrying');
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Fossa analyze failed with code ${error.code}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Maximum number of retries reached, skipping fossa analysis');
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const githubRef = process.env.GITHUB_REF;
|
||||
if (!githubRef) {
|
||||
throw new Error('GITHUB_REF is not set');
|
||||
}
|
||||
// This is picked up by the fossa CLI and should be set
|
||||
if (!process.env.FOSSA_API_KEY) {
|
||||
throw new Error('FOSSA_API_KEY is not set');
|
||||
}
|
||||
|
||||
process.cwd(resolvePath(__dirname, '..'));
|
||||
|
||||
const packageJsonPaths = await findPackageJsonDirs('.');
|
||||
|
||||
const configContents = await generateConfig(packageJsonPaths);
|
||||
|
||||
await fs.writeFile('.fossa.yml', configContents, 'utf8');
|
||||
|
||||
console.error(`Generated fossa config:\n${configContents}`);
|
||||
|
||||
await runAnalyze(githubRef);
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user