Skip to content

Commit 2b4d6b4

Browse files
authored
Merge pull request #169 from OpenPathfinder/feat/add-checklists
2 parents 407a8bb + 9dd0767 commit 2b4d6b4

File tree

7 files changed

+196
-13
lines changed

7 files changed

+196
-13
lines changed

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,27 @@ There is an specific workflow that runs all the checks sequentially:
151151
node index.js workflow run run-all-checks
152152
```
153153

154+
### Checklist
155+
156+
The checklist are collections of checks. You can list the available list by running:
157+
158+
```bash
159+
node index.js checklist list
160+
```
161+
162+
Run a specific checklist:
163+
164+
```bash
165+
node index.js checklist run [--name <name>]
166+
```
167+
168+
169+
It is possible also to define a project scope:
170+
171+
```bash
172+
node index.js checklist run [--name <name>] [--project-scope <name1,name2,...>]
173+
```
174+
154175
## Database Management
155176

156177
### Migrations

index.js

+45-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ const { Command } = require('commander')
22
const { getConfig } = require('./src/config')
33
const { projectCategories, dbSettings } = getConfig()
44
const { logger } = require('./src/utils')
5-
const { runAddProjectCommand, runWorkflowCommand, listWorkflowCommand, listCheckCommand, runCheckCommand } = require('./src/cli')
5+
const { runAddProjectCommand, runWorkflowCommand, listWorkflowCommand, listCheckCommand, runCheckCommand, runChecklist, listChecklist } = require('./src/cli')
66
const knex = require('knex')(dbSettings)
77

88
const program = new Command()
99

10-
const project = program.command('project').description('Manage projects')
10+
const project = program
11+
.command('project')
12+
.description('Manage projects')
1113

1214
// Project related commands
1315
project
@@ -28,7 +30,9 @@ project
2830
})
2931

3032
// Workflows related commands
31-
const workflow = program.command('workflow').description('Manage workflows')
33+
const workflow = program
34+
.command('workflow')
35+
.description('Manage workflows')
3236

3337
workflow
3438
.command('run')
@@ -53,7 +57,9 @@ workflow
5357
})
5458

5559
// Checks related commands
56-
const check = program.command('check').description('Manage checks')
60+
const check = program
61+
.command('check')
62+
.description('Manage checks')
5763

5864
check
5965
.command('list')
@@ -84,4 +90,39 @@ check
8490
}
8591
})
8692

93+
// Checklists related commands
94+
const checklist = program
95+
.command('checklist')
96+
.description('Manage checklists')
97+
98+
checklist
99+
.command('list')
100+
.description('List all available checklists')
101+
.action(async (options) => {
102+
try {
103+
await listChecklist(knex, options)
104+
} catch (error) {
105+
logger.error(error)
106+
process.exit(1)
107+
} finally {
108+
await knex.destroy()
109+
}
110+
})
111+
112+
checklist
113+
.command('run')
114+
.description('Run a checklist')
115+
.option('--name <name>', 'Name of the checklist')
116+
.option('--project-scope <projects...>', 'Scope of the checklist')
117+
.action(async (options) => {
118+
try {
119+
await runChecklist(knex, options)
120+
} catch (error) {
121+
logger.error(error)
122+
process.exit(1)
123+
} finally {
124+
await knex.destroy()
125+
}
126+
})
127+
87128
program.parse(process.argv)

src/checks/complianceChecks/githubOrgMFA.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ const validators = require('../validators')
22
const { initializeStore } = require('../../store')
33
const debug = require('debug')('checks:githubOrgMFA')
44

5-
module.exports = async (knex) => {
5+
module.exports = async (knex, { projects } = {}) => {
66
const {
7-
getAllGithubOrganizations, getCheckByCodeName,
7+
getAllGithubOrganizationsByProjectsId, getCheckByCodeName,
88
getAllProjects, addAlert, addTask, upsertComplianceCheckResult,
99
deleteAlertsByComplianceCheckId, deleteTasksByComplianceCheckId
1010
} = initializeStore(knex)
1111
debug('Collecting relevant data...')
1212
const check = await getCheckByCodeName('githubOrgMFA')
13-
const organizations = await getAllGithubOrganizations()
14-
const projects = await getAllProjects()
13+
if (!projects || (Array.isArray(projects) && projects.length === 0)) {
14+
projects = await getAllProjects()
15+
}
16+
const organizations = await getAllGithubOrganizationsByProjectsId(projects.map(p => p.id))
1517

1618
debug('Extracting the validation results...')
1719
const analysis = validators.githubOrgMFA({ organizations, check, projects })

src/checks/complianceChecks/softwareDesignTraining.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ const validators = require('../validators')
22
const { initializeStore } = require('../../store')
33
const debug = require('debug')('checks:softwareDesignTraining')
44

5-
module.exports = async (knex) => {
5+
module.exports = async (knex, { projects } = {}) => {
66
const {
7-
getAllSSoftwareDesignTrainings, getCheckByCodeName,
7+
getAllSSoftwareDesignTrainingsByProjectIds, getCheckByCodeName,
88
getAllProjects, addAlert, addTask, upsertComplianceCheckResult,
99
deleteAlertsByComplianceCheckId, deleteTasksByComplianceCheckId
1010
} = initializeStore(knex)
1111
debug('Collecting relevant data...')
1212
const check = await getCheckByCodeName('softwareDesignTraining')
13-
const trainings = await getAllSSoftwareDesignTrainings()
14-
const projects = await getAllProjects()
13+
if (!projects || (Array.isArray(projects) && projects.length === 0)) {
14+
projects = await getAllProjects()
15+
}
16+
17+
const trainings = await getAllSSoftwareDesignTrainingsByProjectIds(projects.map(project => project.id))
1518

1619
debug('Extracting the validation results...')
1720
const analysis = validators.softwareDesignTraining({ trainings, check, projects })

src/cli/checklists.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const inquirer = require('inquirer').default
2+
const { initializeStore } = require('../store')
3+
const { logger } = require('../utils')
4+
const debug = require('debug')('cli:checklists')
5+
const { stringToArray } = require('@ulisesgascon/string-to-array')
6+
const checks = require('../checks')
7+
8+
async function listChecklist (knex, options = {}) {
9+
const { getAllChecklists } = initializeStore(knex)
10+
const checklists = await getAllChecklists(knex)
11+
checklists.forEach(checklist => {
12+
logger.info(`- [${checklist.author}][${checklist.code_name}] ${checklist.title}`)
13+
})
14+
logger.info('------------------------------------')
15+
logger.info(`Available checklists: ${checklists.length}.`)
16+
return checklists
17+
}
18+
19+
async function runChecklist (knex, options = {}) {
20+
const { getAllChecklists, getAllProjects, getAllChecksInChecklistById } = initializeStore(knex)
21+
const checklists = await getAllChecklists(knex)
22+
const projects = await getAllProjects(knex)
23+
const validCommandNames = checklists.map((item) => item.code_name)
24+
const validProjectNames = ['ALL'].concat(projects.map((item) => item.name))
25+
26+
if (Object.keys(options).length && !validCommandNames.includes(options.name)) {
27+
throw new Error('Invalid checklist name. Please enter a valid checklist name.')
28+
}
29+
30+
if (options.projectScope && options.projectScope.length > 0) {
31+
const invalidProjects = stringToArray(options.projectScope[0]).filter((project) => !validProjectNames.includes(project))
32+
if (invalidProjects.length > 0) {
33+
throw new Error(`Invalid project names: ${invalidProjects.join(', ')}`)
34+
}
35+
}
36+
37+
if (validProjectNames.length === 1) {
38+
throw new Error('No projects in database. Please add projects to run checklists')
39+
}
40+
41+
const answers = options.name
42+
? options
43+
: await inquirer.prompt([
44+
{
45+
type: 'list',
46+
name: 'name',
47+
message: 'What is the name (code_name) of the checklist?',
48+
choices: validCommandNames,
49+
when: () => !options.name
50+
},
51+
{
52+
type: 'checkbox',
53+
name: 'projectScope',
54+
choices: validProjectNames,
55+
message: 'Choose the projects to run the checklist against',
56+
when: () => !options.name
57+
}
58+
])
59+
60+
if (!answers.projectScope || answers.projectScope?.length === 0 || stringToArray(answers.projectScope[0]).includes('ALL')) {
61+
answers.projectScope = undefined
62+
logger.info('Running checklist against all projects')
63+
} else {
64+
const projectsSelected = stringToArray(answers.projectScope[0])
65+
answers.projectScope = projectsSelected.map(p => projects.find(project => project.name === p))
66+
logger.info('Running checklist against specific projects')
67+
}
68+
69+
debug('Running checklist with code_name:', answers.name)
70+
71+
const checklist = checklists.find(checklist => checklist.code_name === answers.name)
72+
const checksToRun = await getAllChecksInChecklistById(knex, checklist.id)
73+
74+
for (const check of checksToRun) {
75+
if (check.implementation_status !== 'completed') {
76+
logger.info(`Check (${check.code_name}) is not implemented yet. skipping...`)
77+
continue
78+
}
79+
logger.info(`Running check (${check.code_name})`)
80+
await checks[check.code_name](knex, { projects: answers.projectScope })
81+
}
82+
83+
logger.info(`Checklist (${answers.name}) ran successfully`)
84+
}
85+
86+
module.exports = {
87+
listChecklist,
88+
runChecklist
89+
}

src/cli/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
const project = require('./project')
22
const workflows = require('./workflows')
33
const checks = require('./checks')
4+
const checklists = require('./checklists')
45

56
module.exports = {
67
...project,
78
...workflows,
8-
...checks
9+
...checks,
10+
...checklists
911
}

src/store/index.js

+25
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ const upsertGithubRepository = (knex) => (repository, orgId) => upsertRecord({
9393
data: { ...repository, github_organization_id: orgId }
9494
})
9595

96+
const getAllChecksInChecklistById = (knex, checklistId) =>
97+
debug(`Fetching all checks in checklist by id (${checklistId})...`) ||
98+
knex('checklist_items')
99+
.join('compliance_checks', 'compliance_checks.id', 'checklist_items.compliance_check_id')
100+
.where('checklist_items.checklist_id', checklistId)
101+
.select('compliance_checks.*')
102+
103+
const getAllGithubOrganizationsByProjectsId = (knex, projectIds) => {
104+
debug(`Fetching all github organizations by projects id (${projectIds})...`)
105+
return knex('github_organizations')
106+
.whereIn('github_organizations.project_id', projectIds)
107+
.select('*')
108+
}
109+
110+
const getAllSSoftwareDesignTrainingsByProjectIds = (knex, projectIds) => {
111+
debug(`Fetching all software design trainings by project ids (${projectIds})...`)
112+
return knex('software_design_training')
113+
.whereIn('software_design_training.project_id', projectIds)
114+
.select('*')
115+
}
116+
96117
const initializeStore = (knex) => {
97118
debug('Initializing store...')
98119
const getAll = getAllFn(knex)
@@ -113,6 +134,10 @@ const initializeStore = (knex) => {
113134
upsertComplianceCheckResult: upsertComplianceCheckResult(knex),
114135
getAllSSoftwareDesignTrainings: () => getAll('software_design_training'),
115136
getAllGithubRepositories: () => getAll('github_repositories'),
137+
getAllChecklists: () => getAll('compliance_checklists'),
138+
getAllChecksInChecklistById,
139+
getAllGithubOrganizationsByProjectsId: (projectIds) => getAllGithubOrganizationsByProjectsId(knex, projectIds),
140+
getAllSSoftwareDesignTrainingsByProjectIds: (projectIds) => getAllSSoftwareDesignTrainingsByProjectIds(knex, projectIds),
116141
upsertOSSFScorecard: upsertOSSFScorecard(knex)
117142
}
118143
}

0 commit comments

Comments
 (0)