Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI support for checklist #169

Merged
merged 4 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,27 @@ There is an specific workflow that runs all the checks sequentially:
node index.js workflow run run-all-checks
```

### Checklist

The checklist are collections of checks. You can list the available list by running:

```bash
node index.js checklist list
```

Run a specific checklist:

```bash
node index.js checklist run [--name <name>]
```


It is possible also to define a project scope:

```bash
node index.js checklist run [--name <name>] [--project-scope <name1,name2,...>]
```

## Database Management

### Migrations
Expand Down
49 changes: 45 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ const { Command } = require('commander')
const { getConfig } = require('./src/config')
const { projectCategories, dbSettings } = getConfig()
const { logger } = require('./src/utils')
const { runAddProjectCommand, runWorkflowCommand, listWorkflowCommand, listCheckCommand, runCheckCommand } = require('./src/cli')
const { runAddProjectCommand, runWorkflowCommand, listWorkflowCommand, listCheckCommand, runCheckCommand, runChecklist, listChecklist } = require('./src/cli')
const knex = require('knex')(dbSettings)

const program = new Command()

const project = program.command('project').description('Manage projects')
const project = program
.command('project')
.description('Manage projects')

// Project related commands
project
Expand All @@ -28,7 +30,9 @@ project
})

// Workflows related commands
const workflow = program.command('workflow').description('Manage workflows')
const workflow = program
.command('workflow')
.description('Manage workflows')

workflow
.command('run')
Expand All @@ -53,7 +57,9 @@ workflow
})

// Checks related commands
const check = program.command('check').description('Manage checks')
const check = program
.command('check')
.description('Manage checks')

check
.command('list')
Expand Down Expand Up @@ -84,4 +90,39 @@ check
}
})

// Checklists related commands
const checklist = program
.command('checklist')
.description('Manage checklists')

checklist
.command('list')
.description('List all available checklists')
.action(async (options) => {
try {
await listChecklist(knex, options)
} catch (error) {
logger.error(error)
process.exit(1)
} finally {
await knex.destroy()
}
})

checklist
.command('run')
.description('Run a checklist')
.option('--name <name>', 'Name of the checklist')
.option('--project-scope <projects...>', 'Scope of the checklist')
.action(async (options) => {
try {
await runChecklist(knex, options)
} catch (error) {
logger.error(error)
process.exit(1)
} finally {
await knex.destroy()
}
})

program.parse(process.argv)
10 changes: 6 additions & 4 deletions src/checks/complianceChecks/githubOrgMFA.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ const validators = require('../validators')
const { initializeStore } = require('../../store')
const debug = require('debug')('checks:githubOrgMFA')

module.exports = async (knex) => {
module.exports = async (knex, { projects } = {}) => {
const {
getAllGithubOrganizations, getCheckByCodeName,
getAllGithubOrganizationsByProjectsId, getCheckByCodeName,
getAllProjects, addAlert, addTask, upsertComplianceCheckResult,
deleteAlertsByComplianceCheckId, deleteTasksByComplianceCheckId
} = initializeStore(knex)
debug('Collecting relevant data...')
const check = await getCheckByCodeName('githubOrgMFA')
const organizations = await getAllGithubOrganizations()
const projects = await getAllProjects()
if (!projects || (Array.isArray(projects) && projects.length === 0)) {
projects = await getAllProjects()
}
const organizations = await getAllGithubOrganizationsByProjectsId(projects.map(p => p.id))

debug('Extracting the validation results...')
const analysis = validators.githubOrgMFA({ organizations, check, projects })
Expand Down
11 changes: 7 additions & 4 deletions src/checks/complianceChecks/softwareDesignTraining.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ const validators = require('../validators')
const { initializeStore } = require('../../store')
const debug = require('debug')('checks:softwareDesignTraining')

module.exports = async (knex) => {
module.exports = async (knex, { projects } = {}) => {
const {
getAllSSoftwareDesignTrainings, getCheckByCodeName,
getAllSSoftwareDesignTrainingsByProjectIds, getCheckByCodeName,
getAllProjects, addAlert, addTask, upsertComplianceCheckResult,
deleteAlertsByComplianceCheckId, deleteTasksByComplianceCheckId
} = initializeStore(knex)
debug('Collecting relevant data...')
const check = await getCheckByCodeName('softwareDesignTraining')
const trainings = await getAllSSoftwareDesignTrainings()
const projects = await getAllProjects()
if (!projects || (Array.isArray(projects) && projects.length === 0)) {
projects = await getAllProjects()
}

const trainings = await getAllSSoftwareDesignTrainingsByProjectIds(projects.map(project => project.id))

debug('Extracting the validation results...')
const analysis = validators.softwareDesignTraining({ trainings, check, projects })
Expand Down
89 changes: 89 additions & 0 deletions src/cli/checklists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const inquirer = require('inquirer').default
const { initializeStore } = require('../store')
const { logger } = require('../utils')
const debug = require('debug')('cli:checklists')
const { stringToArray } = require('@ulisesgascon/string-to-array')
const checks = require('../checks')

async function listChecklist (knex, options = {}) {
const { getAllChecklists } = initializeStore(knex)
const checklists = await getAllChecklists(knex)
checklists.forEach(checklist => {
logger.info(`- [${checklist.author}][${checklist.code_name}] ${checklist.title}`)
})
logger.info('------------------------------------')
logger.info(`Available checklists: ${checklists.length}.`)
return checklists
}

async function runChecklist (knex, options = {}) {
const { getAllChecklists, getAllProjects, getAllChecksInChecklistById } = initializeStore(knex)
const checklists = await getAllChecklists(knex)
const projects = await getAllProjects(knex)
const validCommandNames = checklists.map((item) => item.code_name)
const validProjectNames = ['ALL'].concat(projects.map((item) => item.name))

if (Object.keys(options).length && !validCommandNames.includes(options.name)) {
throw new Error('Invalid checklist name. Please enter a valid checklist name.')
}

if (options.projectScope && options.projectScope.length > 0) {
const invalidProjects = stringToArray(options.projectScope[0]).filter((project) => !validProjectNames.includes(project))
if (invalidProjects.length > 0) {
throw new Error(`Invalid project names: ${invalidProjects.join(', ')}`)
}
}

if (validProjectNames.length === 1) {
throw new Error('No projects in database. Please add projects to run checklists')
}

const answers = options.name
? options
: await inquirer.prompt([
{
type: 'list',
name: 'name',
message: 'What is the name (code_name) of the checklist?',
choices: validCommandNames,
when: () => !options.name
},
{
type: 'checkbox',
name: 'projectScope',
choices: validProjectNames,
message: 'Choose the projects to run the checklist against',
when: () => !options.name
}
])

if (!answers.projectScope || answers.projectScope?.length === 0 || stringToArray(answers.projectScope[0]).includes('ALL')) {
answers.projectScope = undefined
logger.info('Running checklist against all projects')
} else {
const projectsSelected = stringToArray(answers.projectScope[0])
answers.projectScope = projectsSelected.map(p => projects.find(project => project.name === p))
logger.info('Running checklist against specific projects')
}

debug('Running checklist with code_name:', answers.name)

const checklist = checklists.find(checklist => checklist.code_name === answers.name)
const checksToRun = await getAllChecksInChecklistById(knex, checklist.id)

for (const check of checksToRun) {
if (check.implementation_status !== 'completed') {
logger.info(`Check (${check.code_name}) is not implemented yet. skipping...`)
continue
}
logger.info(`Running check (${check.code_name})`)
await checks[check.code_name](knex, { projects: answers.projectScope })
}

logger.info(`Checklist (${answers.name}) ran successfully`)
}

module.exports = {
listChecklist,
runChecklist
}
4 changes: 3 additions & 1 deletion src/cli/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const project = require('./project')
const workflows = require('./workflows')
const checks = require('./checks')
const checklists = require('./checklists')

module.exports = {
...project,
...workflows,
...checks
...checks,
...checklists
}
25 changes: 25 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,27 @@ const upsertGithubRepository = (knex) => (repository, orgId) => upsertRecord({
data: { ...repository, github_organization_id: orgId }
})

const getAllChecksInChecklistById = (knex, checklistId) =>
debug(`Fetching all checks in checklist by id (${checklistId})...`) ||
knex('checklist_items')
.join('compliance_checks', 'compliance_checks.id', 'checklist_items.compliance_check_id')
.where('checklist_items.checklist_id', checklistId)
.select('compliance_checks.*')

const getAllGithubOrganizationsByProjectsId = (knex, projectIds) => {
debug(`Fetching all github organizations by projects id (${projectIds})...`)
return knex('github_organizations')
.whereIn('github_organizations.project_id', projectIds)
.select('*')
}

const getAllSSoftwareDesignTrainingsByProjectIds = (knex, projectIds) => {
debug(`Fetching all software design trainings by project ids (${projectIds})...`)
return knex('software_design_training')
.whereIn('software_design_training.project_id', projectIds)
.select('*')
}

const initializeStore = (knex) => {
debug('Initializing store...')
const getAll = getAllFn(knex)
Expand All @@ -113,6 +134,10 @@ const initializeStore = (knex) => {
upsertComplianceCheckResult: upsertComplianceCheckResult(knex),
getAllSSoftwareDesignTrainings: () => getAll('software_design_training'),
getAllGithubRepositories: () => getAll('github_repositories'),
getAllChecklists: () => getAll('compliance_checklists'),
getAllChecksInChecklistById,
getAllGithubOrganizationsByProjectsId: (projectIds) => getAllGithubOrganizationsByProjectsId(knex, projectIds),
getAllSSoftwareDesignTrainingsByProjectIds: (projectIds) => getAllSSoftwareDesignTrainingsByProjectIds(knex, projectIds),
upsertOSSFScorecard: upsertOSSFScorecard(knex)
}
}
Expand Down
Loading