mirror of
https://github.com/tschoffelen/gha.git
synced 2024-10-05 21:18:05 +03:00
Initial commit
This commit is contained in:
commit
d832b04aea
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
43
index.js
Executable file
43
index.js
Executable file
@ -0,0 +1,43 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
const fs = require('fs')
|
||||
const program = require('commander')
|
||||
const hcl = require('hcl')
|
||||
const chalk = require('chalk')
|
||||
|
||||
const {
|
||||
cleanupHcl,
|
||||
buildDependencies,
|
||||
runAction,
|
||||
checkDocker
|
||||
} = require('./lib/utils')
|
||||
|
||||
program
|
||||
.version('1.0.0')
|
||||
.option('-f <workflowfile>', 'Set workflow file path, defaults to .github/main.workflow')
|
||||
.option('-e <event>', 'Set event, defaults to push')
|
||||
.parse(process.argv)
|
||||
|
||||
checkDocker()
|
||||
|
||||
const content = hcl.parse(fs.readFileSync(program.workflowfile || '.github/main.workflow', 'utf8'))
|
||||
const event = program.event || 'push'
|
||||
|
||||
const actions = cleanupHcl(content.action)
|
||||
const workflows = cleanupHcl(content.workflow)
|
||||
|
||||
for (const workflowTitle in workflows) {
|
||||
if (!workflows.hasOwnProperty(workflowTitle)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const workflow = workflows[workflowTitle]
|
||||
if ('on' in workflow && workflow.on && workflow.on === event) {
|
||||
console.log(chalk.bold(`Running ${workflowTitle}...\n`))
|
||||
workflow.resolves
|
||||
.forEach((action) => {
|
||||
buildDependencies(action, actions)
|
||||
.forEach((action) => runAction(action, actions, event))
|
||||
})
|
||||
}
|
||||
}
|
168
lib/utils.js
Normal file
168
lib/utils.js
Normal file
@ -0,0 +1,168 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const glob = require('glob')
|
||||
const chalk = require('chalk')
|
||||
const {exec} = require('shelljs')
|
||||
|
||||
const cleanupHcl = (hcl) => {
|
||||
const objects = {}
|
||||
hcl.map(object => {
|
||||
const title = Object.keys(object)[0]
|
||||
const values = {}
|
||||
object[title].map((value) => {
|
||||
const key = Object.keys(value)[0]
|
||||
let val = value[key]
|
||||
if (typeof val === 'object' && 'hint' in val && val.hint === 'block') {
|
||||
val = val.value
|
||||
const env = {}
|
||||
val.map(obj => {
|
||||
const key = Object.keys(obj)[0]
|
||||
env[key] = obj[key]
|
||||
})
|
||||
val = env
|
||||
}
|
||||
values[key] = val
|
||||
})
|
||||
objects[title] = values
|
||||
})
|
||||
|
||||
return objects
|
||||
}
|
||||
|
||||
const err = (message) => {
|
||||
console.log('\n' + chalk.red(message) + '\n')
|
||||
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const buildDependencies = (startAction, actions) => {
|
||||
let output = [startAction]
|
||||
|
||||
if (!(startAction in actions)) {
|
||||
err(`Action "${startAction}" referenced but not found`)
|
||||
}
|
||||
|
||||
const action = actions[startAction]
|
||||
if (action && 'needs' in action && action.needs) {
|
||||
if (typeof action.needs === 'string') {
|
||||
action.needs = [action.needs]
|
||||
} else if (typeof action.needs !== 'object') {
|
||||
err(`Action "${startAction}" has invalid value for key 'needs'`)
|
||||
}
|
||||
action.needs.forEach(item => {
|
||||
output = output.concat(buildDependencies(item, actions).reverse())
|
||||
})
|
||||
}
|
||||
|
||||
return output.reverse()
|
||||
}
|
||||
|
||||
const resolveRunner = (uses) => {
|
||||
// TODO: add support for a local Dockerfile path
|
||||
// https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#using-a-dockerfile-image-in-an-action
|
||||
|
||||
let [url, version] = uses.split('@', 2)
|
||||
version = version || 'master'
|
||||
|
||||
let [user, repo, subdir] = url.split('/', 3)
|
||||
subdir = subdir || ''
|
||||
|
||||
let baseName = `${user}-${repo}-${subdir.replace(/\//g, '-')}`.replace(/-+$/, '')
|
||||
let cacheFile = `/tmp/gha.${baseName}-${version}`
|
||||
let dockerFile = `${cacheFile}/*/${subdir}/Dockerfile`
|
||||
|
||||
if (!glob.sync(dockerFile).length) {
|
||||
exec(`curl -o ${cacheFile}.tgz --fail --silent --show-error --location https://api.github.com/repos/${user}/${repo}/tarball/${version}`)
|
||||
exec(`mkdir -p ${cacheFile}`)
|
||||
exec(`tar xf ${cacheFile}.tgz -C ${cacheFile}/`)
|
||||
exec(`rm ${cacheFile}.tgz`)
|
||||
}
|
||||
|
||||
if (!glob.sync(dockerFile).length) {
|
||||
err(`Could not find Dockerfile: ${dockerFile}`)
|
||||
}
|
||||
|
||||
dockerFile = glob.sync(dockerFile)[0]
|
||||
let baseDir = path.dirname(dockerFile)
|
||||
let imageName = path.basename(baseDir)
|
||||
|
||||
exec(`if [[ "$(docker images -q ${imageName} 2> /dev/null)" == "" ]]; then
|
||||
docker build ${baseDir} -f ${dockerFile} -t ${imageName};
|
||||
fi`)
|
||||
|
||||
return imageName
|
||||
}
|
||||
|
||||
const defaultEnv = (action, event) => {
|
||||
return {
|
||||
GITHUB_ACTOR: 'octocat',
|
||||
HOME: '/github/home',
|
||||
GITHUB_REPOSITORY: 'github/example',
|
||||
GITHUB_EVENT_NAME: event,
|
||||
GITHUB_EVENT_PATH: '/github/workflow/event.json',
|
||||
GITHUB_WORKSPACE: '/github/workspace',
|
||||
GITHUB_SHA: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
||||
GITHUB_TOKEN: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
|
||||
GITHUB_REF: 'refs/heads/master'
|
||||
}
|
||||
}
|
||||
|
||||
const runAction = (actionTitle, actions, event) => {
|
||||
console.log(chalk.bold(chalk.blue(`===> ${actionTitle}`)))
|
||||
|
||||
const action = actions[actionTitle]
|
||||
if (!('uses' in action) || !action.uses || typeof action.uses !== 'string') {
|
||||
err(`Invalid 'uses' key for this action`)
|
||||
}
|
||||
|
||||
const uses = action.uses
|
||||
const imageName = resolveRunner(uses)
|
||||
let args = []
|
||||
|
||||
if ('runs' in action && action.runs) {
|
||||
args.push(`--entrypoint "${action.runs.replace(/"/g, '\"')}"`)
|
||||
}
|
||||
|
||||
action.env = Object.assign(defaultEnv(action, event), 'env' in action && action.env ? action.env : {})
|
||||
for (const title in action.env) {
|
||||
if (!action.env.hasOwnProperty(title)) {
|
||||
continue
|
||||
}
|
||||
args.push(`--env ${title}="${action.env[title].replace(/"/g, '\"')}"`)
|
||||
}
|
||||
|
||||
let after = ''
|
||||
if ('args' in action && action.args) {
|
||||
if (typeof action.args === 'object') {
|
||||
action.args = action.args.join(' ')
|
||||
}
|
||||
after = action.args
|
||||
}
|
||||
|
||||
const cmd = `docker run --rm -t -v \`pwd\`:/github/workspace -w /github/workspace ${args.join(' ')} ${imageName} ${after}`
|
||||
const res = exec(cmd)
|
||||
|
||||
if (res.code === 0) {
|
||||
console.log(chalk.green('(success)'))
|
||||
} else if (res.code === 78) {
|
||||
console.log(chalk.magenta('(neutral, skipping other steps)'))
|
||||
process.exit(0)
|
||||
} else {
|
||||
err(`Command failed with error code ${res.code}`)
|
||||
}
|
||||
|
||||
console.log('\n')
|
||||
}
|
||||
|
||||
const checkDocker = () => {
|
||||
if (exec('docker -v', {async: false, silent: true}).code !== 0) {
|
||||
err('Could not find docker locally')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cleanupHcl,
|
||||
buildDependencies,
|
||||
runAction,
|
||||
checkDocker
|
||||
}
|
180
package-lock.json
generated
Normal file
180
package-lock.json
generated
Normal file
@ -0,0 +1,180 @@
|
||||
{
|
||||
"name": "gha",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
|
||||
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"hcl": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hcl/-/hcl-0.1.0.tgz",
|
||||
"integrity": "sha1-1dQ8H4nsl3oU8LiJgGEUlw9/U2g="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"interpret": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
|
||||
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||
},
|
||||
"rechoir": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
|
||||
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
|
||||
"requires": {
|
||||
"resolve": "^1.1.6"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz",
|
||||
"integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==",
|
||||
"requires": {
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
|
||||
"integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
|
||||
"requires": {
|
||||
"glob": "^7.0.0",
|
||||
"interpret": "^1.0.0",
|
||||
"rechoir": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
39
package.json
Normal file
39
package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "gha",
|
||||
"version": "1.0.0",
|
||||
"description": "Test Github Actions locally through Docker.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"preferGlobal": true,
|
||||
"bin": {
|
||||
"gha": "index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tschoffelen/gha.git"
|
||||
},
|
||||
"keywords": [
|
||||
"github-actions",
|
||||
"github",
|
||||
"actions",
|
||||
"docker",
|
||||
"hcl",
|
||||
"testing",
|
||||
"debugging"
|
||||
],
|
||||
"author": "Thomas Schoffelen <thomas@includable.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tschoffelen/gha/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tschoffelen/gha#readme",
|
||||
"dependencies": {
|
||||
"chalk": "^2.4.1",
|
||||
"commander": "^2.19.0",
|
||||
"glob": "^7.1.3",
|
||||
"hcl": "^0.1.0",
|
||||
"shelljs": "^0.8.3"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user