Better dockerRunArgs handling, ensure slug is lowercase

Some `dockerRunArg` values would fail, due to improper handling of quotes. This is sidestepped by parsing the arguments into an array using `shlex`, then passing the array items as separate N arguments to `run-on-arch.sh`.

It also seems that the container names must be lower-case, so we ensure the `slug()` function returns a lower-case string. Some GitHub repositories will have upper-case letters.
This commit is contained in:
Elijah Shaw-Rutschman
2020-09-07 15:57:04 -05:00
parent b17be6e1d1
commit 5606c1fd5d
19 changed files with 730 additions and 54 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
on: [push]
name: Advanced Example
on: [push, pull_request]
jobs:
build_job:
+2 -2
View File
@@ -1,5 +1,5 @@
on: [push]
name: Basic Example
on: [push, pull_request]
jobs:
armv7_job:
+3 -3
View File
@@ -1,5 +1,5 @@
name: swift-build
on: [push,pull_request]
name: Build with Swift on armv7
on: [push, pull_request]
jobs:
testactions_job:
@@ -8,7 +8,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build with Swift on armv7
- name: Build
uses: ./
with:
arch: armv7
+53 -29
View File
@@ -1,4 +1,4 @@
name: test
name: Unit Tests
on: [push, pull_request]
jobs:
@@ -38,41 +38,53 @@ jobs:
arch: ${{ matrix.arch }}
distro: ${{ matrix.distro }}
env: |
var_1: value_1
var_2: value_2
env_arch: ${{ matrix.arch }}
env_distro: ${{ matrix.distro }}
# Test multiple argument formats
dockerRunArgs: |
-v "${PWD}/volume_1:/volume_1"
-v "${PWD}/volume_2:/volume_2"
--volume=${PWD}/volume_2:/volume_2
"-v${PWD}/volume_3:/volume_3"
-v "${PWD}/volume_4:/volume_4" -v "${PWD}/volume_5:/volume_5"
# Sourced on host, after container build, before container run
setup: |
distro_info=$(cat /etc/*-release | tr '[:upper:]' '[:lower:]' | tr '"' ' ' | tr '\n' ' ')
echo ::set-output name=host_arch::$(uname -m)
echo ::set-output name=host_distro_info::$distro_info
echo ::set-output name=host_var_1::$var_1
echo ::set-output name=host_var_2::$var_2
echo ::set-output name=host_shell_options::$-
echo ::set-output name=host_arch::"$(uname -m)"
echo ::set-output name=host_distro_info::"$distro_info"
echo ::set-output name=host_env_arch::"$env_arch"
echo ::set-output name=host_env_distro::"$env_distro"
echo ::set-output name=host_shell_options::"$-"
# Setup volumes
mkdir "${PWD}/volume_1"
touch "${PWD}/volume_1/file_1"
mkdir "${PWD}/volume_2"
touch "${PWD}/volume_2/file_2"
mkdir "${PWD}/volume_3"
touch "${PWD}/volume_3/file_3"
mkdir "${PWD}/volume_4"
touch "${PWD}/volume_4/file_4"
mkdir "${PWD}/volume_5"
touch "${PWD}/volume_5/file_5"
# Run on container
run: |
distro_info=$(cat /etc/*-release | tr '[:upper:]' '[:lower:]' | sed 's/"//g' | tr '\n' ';')
echo ::set-output name=arch::$(uname -m)
echo ::set-output name=distro_info::$distro_info
echo ::set-output name=shebang::$(head -n 1 "$0")
echo ::set-output name=var_1::$var_1
echo ::set-output name=var_2::$var_2
echo ::set-output name=volume_1_ls::$(ls /volume_1 || true)
echo ::set-output name=volume_2_ls::$(ls /volume_2 || true)
echo ::set-output name=shell_options::$-
echo ::set-output name=arch::"$(uname -m)"
echo ::set-output name=distro_info::"$distro_info"
echo ::set-output name=shebang::"$(head -n 1 "$0")"
echo ::set-output name=env_arch::"$env_arch"
echo ::set-output name=env_distro::"$env_distro"
echo ::set-output name=volume_1_ls::"$(ls /volume_1 || true)"
echo ::set-output name=volume_2_ls::"$(ls /volume_2 || true)"
echo ::set-output name=volume_3_ls::"$(ls /volume_3 || true)"
echo ::set-output name=volume_4_ls::"$(ls /volume_4 || true)"
echo ::set-output name=volume_5_ls::"$(ls /volume_5 || true)"
echo ::set-output name=shell_options::"$-"
- name: Assert setup script runs on host (check arch info)
run: |
@@ -90,14 +102,14 @@ jobs:
- name: Assert setup script receives environment variables
run: |
var_1="${{ steps.build.outputs.host_var_1 }}"
var_2="${{ steps.build.outputs.host_var_2 }}"
arch="${{ steps.build.outputs.host_env_arch }}"
distro="${{ steps.build.outputs.host_env_distro }}"
echo "Assert var_1: '$var_1' == 'value_1'"
test "$var_1" == "value_1"
echo "Assert env_arch: '$arch' == '${{ matrix.arch }}'"
test "$arch" == "${{ matrix.arch }}"
echo "Assert var_2: '$var_2' == 'value_2'"
test "$var_1" == "value_1"
echo "Assert env_distro: '$distro' == '${{ matrix.distro }}'"
test "$distro" == "${{ matrix.distro }}"
- name: Assert job would fail on setup script error
run: |
@@ -137,14 +149,14 @@ jobs:
- name: Assert container receives environment variables
run: |
var_1="${{ steps.build.outputs.var_1 }}"
var_2="${{ steps.build.outputs.var_2 }}"
arch="${{ steps.build.outputs.env_arch }}"
distro="${{ steps.build.outputs.env_distro }}"
echo "Assert var_1: '$var_1' == 'value_1'"
test "$var_1" == "value_1"
echo "Assert env_arch: '$arch' == '${{ matrix.arch }}'"
test "$arch" == "${{ matrix.arch }}"
echo "Assert var_2: '$var_2' == 'value_2'"
test "$var_1" == "value_1"
echo "Assert env_distro: '$distro' == '${{ matrix.distro }}'"
test "$distro" == "${{ matrix.distro }}"
- name: Assert job would fail on run script error
run: |
@@ -159,6 +171,9 @@ jobs:
run: |
volume_1_ls="${{ steps.build.outputs.volume_1_ls }}"
volume_2_ls="${{ steps.build.outputs.volume_2_ls }}"
volume_3_ls="${{ steps.build.outputs.volume_3_ls }}"
volume_4_ls="${{ steps.build.outputs.volume_4_ls }}"
volume_5_ls="${{ steps.build.outputs.volume_5_ls }}"
echo "Assert volume_1_ls: '$volume_1_ls' == 'file_1'"
test "$volume_1_ls" == "file_1"
@@ -166,6 +181,15 @@ jobs:
echo "Assert volume_2_ls: '$volume_2_ls' == 'file_2'"
test "$volume_2_ls" == "file_2"
echo "Assert volume_3_ls: '$volume_3_ls' == 'file_3'"
test "$volume_3_ls" == "file_3"
echo "Assert volume_4_ls: '$volume_4_ls' == 'file_4'"
test "$volume_4_ls" == "file_4"
echo "Assert volume_5_ls: '$volume_5_ls' == 'file_5'"
test "$volume_5_ls" == "file_5"
- name: Assert container determines correct default shell for distro
run: |
shebang="${{ steps.build.outputs.shebang }}"
+4 -4
View File
@@ -27,7 +27,7 @@ The action also accepts some optional input parameters:
A basic example that sets an output variable for use in subsequent steps:
```yaml
on: [push]
on: [push, pull_request]
jobs:
armv7_job:
@@ -36,7 +36,7 @@ jobs:
name: Build on ubuntu-18.04 armv7
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.1
- uses: uraimo/run-on-arch-action@v2.0.2
name: Run commands
id: runcmd
with:
@@ -63,7 +63,7 @@ jobs:
This shows how to use a matrix to produce platform-specific artifacts, and includes example values for the optional input parameters `setup`, `shell`, `env`, and `dockerRunArgs`.
```yaml
on: [push]
on: [push, pull_request]
jobs:
build_job:
@@ -84,7 +84,7 @@ jobs:
steps:
- uses: actions/checkout@v2.1.0
- uses: uraimo/run-on-arch-action@v2.0.1
- uses: uraimo/run-on-arch-action@v2.0.2
name: Build artifact
id: build
with:
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "standard",
"rules": {
"camelcase": "off",
"no-multiple-empty-lines": "off"
}
}
+26
View File
@@ -0,0 +1,26 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test
env:
CI: true
Generated Vendored
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Ryan Govostes
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+42
View File
@@ -0,0 +1,42 @@
# node-shlex
![Build Status](https://github.com/rgov/node-shlex/workflows/Node.js%20CI/badge.svg)
`node-shlex` is a Node.js module for quoting and parsing shell commands.
The API was inspired by the [`shlex`][pyshlex] module from the Python Standard
Library. However, the Python implementation is fairly complex, and supports a
confusing matrix of modes that is not replicated here. `node-shlex` always
operates in what the Python module calls "POSIX mode."
[pyshlex]: https://docs.python.org/3/library/shlex.html
As of version 2.0.0, Bash's [ANSI C strings][ansi-c] (`$'x'`) and
[locale-specific translation strings][locale] (`$"x"`) are supported. This
diverges from the Python `shlex` behavior but makes parsing more accurate.
[ansi-c]: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
[locale]: https://www.gnu.org/software/bash/manual/html_node/Locale-Translation.html
Note that `node-shlex` does not attempt to split on or otherwise parse
operators (such as `2>/dev/null`), and it does not perform variable
interpolation.
## Usage
### `shlex.quote()`
```node
var quote = require("shlex").quote
quote("abc") // returns: abc
quote("abc def") // returns: 'abc def'
quote("can't") // returns: 'can'"'"'t'
```
### `shlex.split()`
```node
var split = require("shlex").split
split('ls -al /') // returns: [ 'ls', '-al', '/' ]
split('rm -f "/Volumes/Macintosh HD"') // returns [ 'rm', '-f', '/Volumes/Macintosh HD' ]
```
+66
View File
@@ -0,0 +1,66 @@
{
"_from": "shlex@2.0.2",
"_id": "shlex@2.0.2",
"_inBundle": false,
"_integrity": "sha512-i4p9nNXgBTILspHwZlBCNsZzwuVWW8SFx5dyIONrjL0R+AbMOPbg7ndqgGfjYivkYRTtZMKqIT8HT+QyOhPQWA==",
"_location": "/shlex",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "shlex@2.0.2",
"name": "shlex",
"escapedName": "shlex",
"rawSpec": "2.0.2",
"saveSpec": null,
"fetchSpec": "2.0.2"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/shlex/-/shlex-2.0.2.tgz",
"_shasum": "debf51a145f1df9e7cb7cce04340ccb45120092d",
"_spec": "shlex@2.0.2",
"_where": "/Users/elijahrutschman/Development/run-on-arch-action",
"author": {
"name": "Ryan Govostes"
},
"bugs": {
"url": "https://github.com/rgov/node-shlex/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "Node.js port of Python's shlex shell-like lexer",
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^5.15.3",
"eslint-config-standard": "^12.0.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"mocha": "^5.2.0"
},
"homepage": "https://github.com/rgov/node-shlex#readme",
"keywords": [
"shell",
"command-line",
"cli",
"lexer"
],
"license": "MIT",
"main": "shlex.js",
"name": "shlex",
"repository": {
"type": "git",
"url": "git+https://github.com/rgov/node-shlex.git"
},
"scripts": {
"posttest": "eslint .",
"test": "mocha"
},
"types": "shlex.d.ts",
"version": "2.0.2"
}
+2
View File
@@ -0,0 +1,2 @@
export function split(s: string): string[];
export function quote(s: string): string;
+284
View File
@@ -0,0 +1,284 @@
'use strict'
/*
Port of a subset of the features of CPython's shlex module, which provides a
shell-like lexer. Original code by Eric S. Raymond and other contributors.
*/
class Shlexer {
constructor (string) {
this.i = 0
this.string = string
/**
* Characters that will be considered whitespace and skipped. Whitespace
* bounds tokens. By default, includes space, tab, linefeed and carriage
* return.
*/
this.whitespace = ' \t\r\n'
/**
* Characters that will be considered string quotes. The token accumulates
* until the same quote is encountered again (thus, different quote types
* protect each other as in the shell.) By default, includes ASCII single
* and double quotes.
*/
this.quotes = `'"`
/**
* Characters that will be considered as escape. Just `\` by default.
*/
this.escapes = '\\'
/**
* The subset of quote types that allow escaped characters. Just `"` by default.
*/
this.escapedQuotes = '"'
/**
* Whether to support ANSI C-style $'' quotes
* https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
*/
this.ansiCQuotes = true
/**
* Whether to support localized $"" quotes
* https://www.gnu.org/software/bash/manual/html_node/Locale-Translation.html
*
* The behavior is as if the current locale is set to C or POSIX, i.e., the
* contents are not translated.
*/
this.localeQuotes = true
this.debug = false
}
readChar () {
return this.string.charAt(this.i++)
}
processEscapes (string, quote, isAnsiCQuote) {
if (!isAnsiCQuote && !this.escapedQuotes.includes(quote)) {
// This quote type doesn't support escape sequences
return string
}
// We need to form a regex that matches any of the escape characters,
// without interpreting any of the characters as a regex special character.
let anyEscape = '[' + this.escapes.replace(/(.)/g, '\\$1') + ']'
// In regular quoted strings, we can only escape an escape character, and
// the quote character itself.
if (!isAnsiCQuote && this.escapedQuotes.includes(quote)) {
let re = new RegExp(
anyEscape + '(' + anyEscape + '|\\' + quote + ')', 'g')
return string.replace(re, '$1')
}
// ANSI C quoted strings support a wide variety of escape sequences
if (isAnsiCQuote) {
let patterns = {
// Literal characters
'([\\\\\'"?])': (x) => x,
// Non-printable ASCII characters
'a': () => '\x07',
'b': () => '\x08',
'e|E': () => '\x1b',
'f': () => '\x0c',
'n': () => '\x0a',
'r': () => '\x0d',
't': () => '\x09',
'v': () => '\x0b',
// Octal bytes
'([0-7]{1,3})': (x) => String.fromCharCode(parseInt(x, 8)),
// Hexadecimal bytes
'x([0-9a-fA-F]{1,2})': (x) => String.fromCharCode(parseInt(x, 16)),
// Unicode code units
'u([0-9a-fA-F]{1,4})': (x) => String.fromCharCode(parseInt(x, 16)),
'U([0-9a-fA-F]{1,8})': (x) => String.fromCharCode(parseInt(x, 16)),
// Control characters
// https://en.wikipedia.org/wiki/Control_character#How_control_characters_map_to_keyboards
'c(.)': (x) => {
if (x === '?') {
return '\x7f'
} else if (x === '@') {
return '\x00'
} else {
return String.fromCharCode(x.charCodeAt(0) & 31)
}
}
}
// Construct an uber-RegEx that catches all of the above pattern
let re = new RegExp(
anyEscape + '(' + Object.keys(patterns).join('|') + ')', 'g')
// For each match, figure out which subpattern matched, and apply the
// corresponding function
return string.replace(re, function (m, p1) {
for (let matched in patterns) {
let mm = new RegExp('^' + matched + '$').exec(p1)
if (mm === null) {
continue
}
return patterns[matched].apply(null, mm.slice(1))
}
})
}
// Should not get here
return undefined
}
* [Symbol.iterator] () {
let inQuote = false
let inDollarQuote = false
let escaped = false
let lastDollar = -2 // position of last dollar sign we saw
let token
if (this.debug) {
console.log('full input:', '>' + this.string + '<')
}
while (true) {
const pos = this.i
const char = this.readChar()
if (this.debug) {
console.log(
'position:', pos,
'input:', '>' + char + '<',
'accumulated:', token,
'inQuote:', inQuote,
'inDollarQuote:', inDollarQuote,
'lastDollar:', lastDollar,
'escaped:', escaped
)
}
// Ran out of characters, we're done
if (char === '') {
if (inQuote) { throw new Error('Got EOF while in a quoted string') }
if (escaped) { throw new Error('Got EOF while in an escape sequence') }
if (token !== undefined) { yield token }
return
}
// We were in an escape sequence, complete it
if (escaped) {
if (char === '\n') {
// An escaped newline just means to continue the command on the next
// line. We just need to ignore it.
} else if (inQuote) {
// If we are in a quote, just accumulate the whole escape sequence,
// as we will interpret escape sequences later.
token = (token || '') + escaped + char
} else {
// Just use the literal character
token = (token || '') + char
}
escaped = false
continue
}
if (this.escapes.includes(char)) {
if (!inQuote || inDollarQuote !== false || this.escapedQuotes.includes(inQuote)) {
// We encountered an escape character, which is going to affect how
// we treat the next character.
escaped = char
continue
} else {
// This string type doesn't use escape characters. Ignore for now.
}
}
// We were in a string
if (inQuote !== false) {
// String is finished. Don't grab the quote character.
if (char === inQuote) {
token = this.processEscapes(token, inQuote, inDollarQuote === '\'')
inQuote = false
inDollarQuote = false
continue
}
// String isn't finished yet, accumulate the character
token = (token || '') + char
continue
}
// This is the start of a new string, don't accumulate the quotation mark
if (this.quotes.includes(char)) {
inQuote = char
if (lastDollar === pos - 1) {
if (char === '\'' && !this.ansiCQuotes) {
// Feature not enabled
} else if (char === '"' && !this.localeQuotes) {
// Feature not enabled
} else {
inDollarQuote = char
}
}
token = (token || '') // fixes blank string
if (inDollarQuote !== false) {
// Drop the opening $ we captured before
token = token.slice(0, -1)
}
continue
}
// This is a dollar sign, record that we saw it in case it's the start of
// an ANSI C or localized string
if (inQuote === false && char === '$') {
lastDollar = pos
}
// This is whitespace, so yield the token if we have one
if (this.whitespace.includes(char)) {
if (token !== undefined) { yield token }
token = undefined
continue
}
// Otherwise, accumulate the character
token = (token || '') + char
}
}
}
/**
* Splits a given string using shell-like syntax.
*
* @param {String} s String to split.
* @returns {String[]}
*/
exports.split = function (s) {
return Array.from(new Shlexer(s))
}
/**
* Escapes a potentially shell-unsafe string using quotes.
*
* @param {String} s String to quote
* @returns {String}
*/
exports.quote = function (s) {
if (s === '') { return '\'\'' }
var unsafeRe = /[^\w@%\-+=:,./]/
if (!unsafeRe.test(s)) { return s }
return '\'' + s.replace(/'/g, '\'"\'"\'') + '\''
}
+8
View File
@@ -0,0 +1,8 @@
import * as shlex from '../'
shlex.quote('test text')
shlex.split('test text "multi word thing"')
// Should error
shlex.quote()
shlex.split()
+39
View File
@@ -0,0 +1,39 @@
/* eslint-env mocha */
'use strict'
const { assert } = require('chai')
const shlex = require('../shlex')
describe('shlex.quote()', function () {
const safeUnquoted = 'abcdefghijklmnopqrstuvwxyz' +
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'0123456789' +
'@%_-+=:,./'
const unicodeSample = '\xe9\xe0\xdf' // e + acute accent, a + grave, sharp s
const unsafe = '"`$\\!' + unicodeSample
it('should escape the empty string', function () {
assert.equal(shlex.quote(''), '\'\'')
})
it('should not escape safe strings', function () {
assert.equal(shlex.quote(safeUnquoted), safeUnquoted)
})
it('should escape strings containing spaces', function () {
assert.equal(shlex.quote('test file name'), "'test file name'")
})
it('should escape unsafe characters', function () {
for (var char of unsafe) {
const input = 'test' + char + 'file'
const expected = '\'' + input + '\''
assert.equal(shlex.quote(input), expected)
}
})
it('should escape single quotes', function () {
assert.equal(shlex.quote('test\'file'), '\'test\'"\'"\'file\'')
})
})
+146
View File
@@ -0,0 +1,146 @@
/* eslint-env mocha */
'use strict'
const { assert } = require('chai')
const shlex = require('../shlex')
describe('shlex.split()', function () {
// The original test data set was from shellwords, by Hartmut Goebel
const posixTestcases = [
['x', 'x'],
['foo bar', 'foo', 'bar'],
[' foo bar', 'foo', 'bar'],
[' foo bar ', 'foo', 'bar'],
['foo bar bla fasel', 'foo', 'bar', 'bla', 'fasel'],
['x y z xxxx', 'x', 'y', 'z', 'xxxx'],
['\\x bar', 'x', 'bar'],
['\\ x bar', ' x', 'bar'],
['\\ bar', ' bar'],
['foo \\x bar', 'foo', 'x', 'bar'],
['foo \\ x bar', 'foo', ' x', 'bar'],
['foo \\ bar', 'foo', ' bar'],
['foo "bar" bla', 'foo', 'bar', 'bla'],
['"foo" "bar" "bla"', 'foo', 'bar', 'bla'],
['"foo" bar "bla"', 'foo', 'bar', 'bla'],
['"foo" bar bla', 'foo', 'bar', 'bla'],
["foo 'bar' bla", 'foo', 'bar', 'bla'],
["'foo' 'bar' 'bla'", 'foo', 'bar', 'bla'],
["'foo' bar 'bla'", 'foo', 'bar', 'bla'],
["'foo' bar bla", 'foo', 'bar', 'bla'],
['blurb foo"bar"bar"fasel" baz', 'blurb', 'foobarbarfasel', 'baz'],
["blurb foo'bar'bar'fasel' baz", 'blurb', 'foobarbarfasel', 'baz'],
['""', ''],
["''", ''],
['foo "" bar', 'foo', '', 'bar'],
["foo '' bar", 'foo', '', 'bar'],
['foo "" "" "" bar', 'foo', '', '', '', 'bar'],
["foo '' '' '' bar", 'foo', '', '', '', 'bar'],
['\\"', '"'],
['"\\""', '"'],
['"foo\\ bar"', 'foo\\ bar'],
['"foo\\\\ bar"', 'foo\\ bar'],
['"foo\\\\ bar\\""', 'foo\\ bar"'],
['"foo\\\\" bar\\"', 'foo\\', 'bar"'],
['"foo\\\\ bar\\" dfadf"', 'foo\\ bar" dfadf'],
['"foo\\\\\\ bar\\" dfadf"', 'foo\\\\ bar" dfadf'],
['"foo\\\\\\x bar\\" dfadf"', 'foo\\\\x bar" dfadf'],
['"foo\\x bar\\" dfadf"', 'foo\\x bar" dfadf'],
["\\'", "'"],
["'foo\\ bar'", 'foo\\ bar'],
["'foo\\\\ bar'", 'foo\\\\ bar'],
["\"foo\\\\\\x bar\\\" df'a\\ 'df\"", "foo\\\\x bar\" df'a\\ 'df"],
['\\"foo', '"foo'],
['\\"foo\\x', '"foox'],
['"foo\\x"', 'foo\\x'],
['"foo\\ "', 'foo\\ '],
['foo\\ xx', 'foo xx'],
['foo\\ x\\x', 'foo xx'],
['foo\\ x\\x\\"', 'foo xx"'],
['"foo\\ x\\x"', 'foo\\ x\\x'],
['"foo\\ x\\x\\\\"', 'foo\\ x\\x\\'],
['"foo\\ x\\x\\\\""foobar"', 'foo\\ x\\x\\foobar'],
["\"foo\\ x\\x\\\\\"\\'\"foobar\"", "foo\\ x\\x\\'foobar"],
["\"foo\\ x\\x\\\\\"\\'\"fo'obar\"", "foo\\ x\\x\\'fo'obar"],
["\"foo\\ x\\x\\\\\"\\'\"fo'obar\" 'don'\\''t'", "foo\\ x\\x\\'fo'obar", "don't"],
["\"foo\\ x\\x\\\\\"\\'\"fo'obar\" 'don'\\''t' \\\\", "foo\\ x\\x\\'fo'obar", "don't", '\\'],
["'foo\\ bar'", 'foo\\ bar'],
["'foo\\\\ bar'", 'foo\\\\ bar'],
['foo\\ bar', 'foo bar'],
// ["foo#bar\nbaz", "foo", "baz"], // FIXME: Comments are not implemented
[':-) ;-)', ':-)', ';-)'],
['\u00e1\u00e9\u00ed\u00f3\u00fa', '\u00e1\u00e9\u00ed\u00f3\u00fa'],
['hello \\\n world', 'hello', 'world']
]
const ansiCTestcases = [
['$\'x\'', 'x'], // non-escaped character
['$\'\\a\'', '\x07'], // alert (bell)
['$\'\\b\'', '\x08'], // backspace
['$\'\\e\'', '\x1b'], // escape character
['$\'\\E\'', '\x1b'], // escape character
['$\'\\f\'', '\x0c'], // form feed / new page
['$\'\\n\'', '\x0a'], // newline
['$\'\\r\'', '\x0d'], // carriage return
['$\'\\t\'', '\x09'], // horizontal tab
['$\'\\v\'', '\x0b'], // vertical tab
['$\'\\\\\'', '\\'], // backslash
['$\'\\\'\'', '\''], // single quote
['$\'\\"\'', '"'], // double quote
['$\'\\?\'', '?'], // question mark
['$\'\\79\'', '\x07\x39'], // octal + non-octal
['$\'\\07\'', '\x07'], // octal, zero prefix
['$\'\\xfx\'', '\x0f\x78'], // hex (one digit) + non-hex
['$\'\\xffx\'', '\xff\x78'], // hex (two digits) + non-hex
['$\'\\xxx\'', '\\xxx'], // invalid hex
['$\'\\u2603\'', '☃'], // unicode character
['$\'\\U2603\'', '☃'], // unicode character
['$\'\\ca\'', '\x01'], // control-a character
['$\'\\cA\'', '\x01'], // control-A character, same as above
['$\'\\c@\'', '\x00'], // control-@ character: null
['$\'\\c?\'', '\x7f'], // control-? character: del
['$\'\\\\x30\'', '\\x30'],
['x$\'y\'z', 'xyz'],
['"x"$\'y\'"z"', 'xyz'],
['$\'x\'"y"$\'z\'', 'xyz'],
['x"$\'y\'"z', 'x$\'y\'z']
]
const localeTestcases = [
['$"x"', 'x'], // non-escaped character
['$"\\""', '"'], // escaped quotation mark
['$"\\\\"', '\\'], // escaped escape character
['$"\\x33"', '\\x33'], // other escape sequences do not work
['x$"y"z', 'xyz'],
['"x"$"y""z"', 'xyz'],
['$"x""y"$"z"', 'xyz'],
['x"$"y""z', 'x$yz']
]
it('should split according to POSIX rules', function () {
posixTestcases.forEach(function (test) {
const input = test[0]
const expected = test.slice(1)
assert.deepEqual(shlex.split(input), expected)
})
})
it('should split ANSI C strings', function () {
ansiCTestcases.forEach(function (test) {
const input = test[0]
const expected = test.slice(1)
assert.deepEqual(shlex.split(input), expected)
})
})
it('should split localized strings', function () {
localeTestcases.forEach(function (test) {
const input = test[0]
const expected = test.slice(1)
assert.deepEqual(shlex.split(input), expected)
})
})
})
+6 -1
View File
@@ -1,6 +1,6 @@
{
"name": "run-on-arch",
"version": "1.0.0",
"version": "2.0.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -14,6 +14,11 @@
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.1.tgz",
"integrity": "sha512-nvFkxwiicvpzNiCBF4wFBDfnBvi7xp/as7LE1hBxBxKG2L29+gkIPBiLKMVORL+Hg3JNf07AKRfl0V5djoypjQ=="
},
"shlex": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/shlex/-/shlex-2.0.2.tgz",
"integrity": "sha512-i4p9nNXgBTILspHwZlBCNsZzwuVWW8SFx5dyIONrjL0R+AbMOPbg7ndqgGfjYivkYRTtZMKqIT8HT+QyOhPQWA=="
},
"yaml": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
+2 -1
View File
@@ -1,12 +1,13 @@
{
"name": "run-on-arch",
"version": "2.0.1",
"version": "2.0.2",
"description": "Multi architecture support for GitHub Actions",
"author": "Umberto Raimondi, Elijah Shaw-Rutschman",
"license": "BSD-3-Clause",
"dependencies": {
"@actions/core": "^1.1.1",
"@actions/exec": "^1.0.1",
"shlex": "^2.0.2",
"yaml": "^1.10.0"
}
}
+6 -6
View File
@@ -2,10 +2,11 @@ const core = require('@actions/core')
const fs = require('fs');
const path = require('path')
const YAML = require('yaml');
const shlex = require('shlex');
const { exec } = require('@actions/exec')
function slug(str) {
return str.replace(/[^a-zA-Z0-9]/g, '-');
return str.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
}
async function main() {
@@ -48,9 +49,8 @@ async function main() {
commands,
);
// Sanitize dockerRunArgs: replace escaped and unescaped newlines with a space
let dockerRunArgs = (core.getInput('dockerRunArgs') || '')
.replace(/\\?[\r\n]+/g, ' ');
// Parse dockerRunArgs into an array with shlex
const dockerRunArgs = shlex.split(core.getInput('dockerRunArgs') || '');
const githubToken = core.getInput('githubToken') || '';
@@ -76,7 +76,7 @@ async function main() {
throw new Error(`run-on-arch: env ${key} value must be flat.`);
}
env[key] = value;
dockerRunArgs += ` -e ${key} `;
dockerRunArgs.push(`-e${key}`);
});
}
@@ -86,7 +86,7 @@ async function main() {
console.log('Configuring Docker for multi-architecture support')
await exec(
path.join(__dirname, 'run-on-arch.sh'),
[ dockerFile, dockerRunArgs, containerName, shell],
[ dockerFile, containerName, ...dockerRunArgs ],
{ env },
);
}
+11 -7
View File
@@ -4,12 +4,12 @@ set -euo pipefail
# Args
DOCKERFILE=$1
DOCKER_RUN_ARGS=$2
CONTAINER_NAME=$3
CONTAINER_NAME=$2
# Remainder of args get passed to docker
declare -a DOCKER_RUN_ARGS=${@:3:${#@}}
# Defaults
ACTION_DIR="$(cd "$(dirname "$0")"/.. >/dev/null 2>&1 ; pwd -P)"
GITHUB_TOKEN=${GITHUB_TOKEN:-}
PACKAGE_REGISTRY="docker.pkg.github.com/${GITHUB_REPOSITORY}/${CONTAINER_NAME}"
DEBIAN_FRONTEND=noninteractive
@@ -44,7 +44,7 @@ build_container () {
# If the GITHUB_TOKEN env var has a value, the container images will be
# cached between builds.
if [[ -z "$GITHUB_TOKEN" ]]
if [[ -z "${GITHUB_TOKEN:-}" ]]
then
docker build . --file "$DOCKERFILE" --tag "${CONTAINER_NAME}:latest"
else
@@ -61,6 +61,7 @@ build_container () {
echo "$GITHUB_TOKEN" | docker login docker.pkg.github.com \
-u "$GITHUB_ACTOR" \
--password-stdin
set -x
set "$BASH_FLAGS"
docker pull "$PACKAGE_REGISTRY:latest" || true
@@ -78,8 +79,11 @@ run_container () {
# Run user-provided setup script, in same shell
source "${ACTION_DIR}/src/run-on-arch-setup.sh"
# Interpolate DOCKER_RUN_ARGS, to support evaluation of $VAR references
DOCKER_RUN_ARGS=$(eval echo "$DOCKER_RUN_ARGS")
# Interpolate DOCKER_RUN_ARGS, to support evaluation of $VAR references
for i in "${!DOCKER_RUN_ARGS[@]}"
do
DOCKER_RUN_ARGS[$i]=$(eval echo "${DOCKER_RUN_ARGS[$i]}")
done
chmod +x "${ACTION_DIR}/src/run-on-arch-commands.sh"
@@ -123,7 +127,7 @@ run_container () {
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
-v "${ACTION_DIR}:${ACTION_DIR}" \
--tty \
$DOCKER_RUN_ARGS \
${DOCKER_RUN_ARGS[@]} \
"${CONTAINER_NAME}:latest" \
"${ACTION_DIR}/src/run-on-arch-commands.sh"
}