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 ability to skip pinning actions #6

Merged
merged 5 commits into from
Nov 10, 2020
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
73 changes: 61 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ jobs:
name: nexmo/github-actions/submodule-auto-pr@master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: nexmo/github-actions/submodule-auto-pr
uses: nexmo/github-actions/submodule-auto-pr@master
- uses: actions/checkout@master
- name: nexmo/github-actions/submodule-auto-pr
uses: nexmo/github-actions/submodule-auto-pr@master
```

In to this:
Expand Down Expand Up @@ -68,14 +68,63 @@ GH_ADMIN_TOKEN=<your-token-here> pin-github-action /path/to/.github/workflows/yo
Run it as many times as you like! Each time you run the tool the exact sha will
be updated to the latest available sha for your pinned ref.

## Leaving Actions unpinned

To leave an action unpinned, pass the `--allow` option when running `pin-github-action`.

Running `pin-github-action /path/to/.github/workflows/your-name.yml --allow "actions/*"` will turn this:

```yaml
jobs:
build:
name: nexmo/github-actions/submodule-auto-pr@master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: nexmo/github-actions/submodule-auto-pr@master
```

Into this (notice how `actions/checkout@master` is ignored):

```yaml
jobs:
build:
name: nexmo/github-actions/submodule-auto-pr@master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: nexmo/github-actions/submodule-auto-pr
uses: nexmo/github-actions/submodule-auto-pr@73549280c1c566830040d9a01fe9050dae6a3036 # pin@master
```

You can pass multiple actions to allow as a comma separated list e.g. `actions/checkout,mheap/*`

A quick overview of the available globbing patterns (taken from [multimatch](https://github.com/sindresorhus/multimatch), which we use to match globs):

- `*` matches any number of characters, but not `/`
- `?` matches a single character, but not `/`
- `**` matches any number of characters, including `/`, as long as it's the only thing in a path part
- `{}` allows for a comma-separated list of "or" expressions
- `!` at the beginning of a pattern will negate the match

Examples:

- Exact match: `actions/checkout`
- Partial match: `actions/*`
- Negated match: `!actions/*` (will only pin `actions/*` actions)

## How it works

* Load the workflow file provided
* Tokenise it in to an AST
* Extract all `uses` steps, skipping any `docker://` or `./local-path` actions
* Loop through all `uses` steps to determine the target ref
* If there's a comment in the step, remove `pin@` and use that as the target
* Otherwise, fall back to the ref in the action as the default
* Look up the current sha for each repo on GitHub and update the action to use the specific hash
* If needed, add a comment with the target pinned version
* Write the workflow file with the new pinned version and original target version as a comment
- Load the workflow file provided
- Tokenise it in to an AST
- Extract all `uses` steps, skipping any `docker://` or `./local-path` actions
- Loop through all `uses` steps to determine the target ref
- If there's a comment in the step, remove `pin@` and use that as the target
- Otherwise, fall back to the ref in the action as the default
- Look up the current sha for each repo on GitHub and update the action to use the specific hash
- If needed, add a comment with the target pinned version
- Write the workflow file with the new pinned version and original target version as a comment

```

```
12 changes: 10 additions & 2 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ const packageDetails = require(path.join(__dirname, "package.json"));
(async () => {
try {
// Allow for command line arguments
program.version(packageDetails.version).parse(process.argv);
program
.version(packageDetails.version)
.option(
"-a, --allow <actions>",
"comma separated list of actions to allow e.g. mheap/debug-action. May be a glob e.g. mheap/*"
)
.parse(process.argv);

const filename = program.args[0];
let allowed = program.opts().allow;
allowed = (allowed || "").split(",").filter(r => r);

const input = fs.readFileSync(filename).toString();

const output = await run(input);
const output = await run(input, allowed);

fs.writeFileSync(filename, output.workflow);

Expand Down
16 changes: 16 additions & 0 deletions checkAllowedRepos.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const matcher = require("matcher");

module.exports = function(input, ignored) {
// Nothing ignored, so it can't match
if (ignored.length === 0) {
return false;
}

// Exact match
if (ignored.includes(input)) {
return true;
}

// Glob match
return matcher([input], ignored).length > 0;
};
30 changes: 30 additions & 0 deletions checkIgnoredRepos.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const checkAllowedRepos = require("./checkAllowedRepos");

test("empty allow list", () => {
const actual = checkAllowedRepos("mheap/demo", []);
expect(actual).toBe(false);
});

test("no match", () => {
expect(checkAllowedRepos("mheap/demo", ["other/repo"])).toBe(false);
});

test("exact match", () => {
expect(checkAllowedRepos("mheap/demo", ["mheap/demo"])).toBe(true);
});

test("partial match", () => {
expect(checkAllowedRepos("mheap/demo", ["mheap/*"])).toBe(true);
});

test("no partial match", () => {
expect(checkAllowedRepos("other/demo", ["mheap/*"])).toBe(false);
});

test("multiple ignores", () => {
expect(checkAllowedRepos("mheap/demo", ["other", "mheap/*"])).toBe(true);
});

test("negative ignores", () => {
expect(checkAllowedRepos("other/demo", ["!mheap/*"])).toBe(true);
});
11 changes: 10 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ const YAML = require("yaml");
const extractActions = require("./extractActions");
const replaceActions = require("./replaceActions");
const findRefOnGithub = require("./findRefOnGithub");
const checkAllowedRepos = require("./checkAllowedRepos");

module.exports = async function(input, allowed) {
allowed = allowed || [];

module.exports = async function(input) {
// Parse the workflow file
let workflow = YAML.parseDocument(input);

// Extract list of actions
let actions = extractActions(workflow);

for (let i in actions) {
// Should this action be updated?
const action = `${actions[i].owner}/${actions[i].repo}`;
if (checkAllowedRepos(action, allowed)) {
continue;
}

// Look up those actions on Github
const newVersion = await findRefOnGithub(actions[i]);
actions[i].newVersion = newVersion;
Expand Down
17 changes: 16 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dependencies": {
"@octokit/rest": "^16.35.2",
"commander": "^4.0.1",
"matcher": "^3.0.0",
"yaml": "^1.7.2"
},
"bin": {
Expand Down