Skip to content

Commit

Permalink
feat: switch to tagless workflow success check (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
meeroslav authored Jul 19, 2021
1 parent 592a04e commit 2eec050
Show file tree
Hide file tree
Showing 10 changed files with 406 additions and 48 deletions.
Binary file added .github/assets/nx-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/assets/nx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*

# Unplanned lock files
yarn.lock
pnpm-lock.yml

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Dependency directories
node_modules/
jspm_packages/

# Compiled files
dist
tmp

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Yarn Integrity file
.yarn-integrity

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# IDE
.idea
.vscode

.DS_Store
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(The MIT License)

Copyright (c) 2021-present Narwhal Technologies Inc.

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.
74 changes: 60 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
# nx-set-shas
<p style="text-align: center;"><img src=".github/assets/nx.png"
width="100%" alt="Nx - Smart, Extensible Build Framework"></p>

<h1 align="center">Set SHAs Action</h2>

> ✨ A Github Action which sets the base and head SHAs required for `nx affected` commands in CI
- [Example Usage](#example-usage)
- [Configuration Options](#configuration-options)
- [Background](#background)
- [License](#license)

> This documentation is for version 2.x.x. You can find documentation for version 1.x.x [here](https://github.com/nrwl/nx-set-shas/blob/c8f5a54f6ee7f2127f3df063f36a0242faee4cb7/README.md).
## Example Usage

Expand All @@ -25,7 +37,7 @@ jobs:
# OPTION 1) Environment variables
# ===========================================================================
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v1
uses: nrwl/nx-set-shas@v2

- run: |
echo "BASE: ${{ env.NX_BASE }}"
Expand All @@ -36,7 +48,9 @@ jobs:
# ===========================================================================
- name: Derive appropriate SHAs for base and head for `nx affected` commands
id: setSHAs
uses: nrwl/nx-set-shas@v1
uses: nrwl/nx-set-shas@v2
with:
set-environment-variables-for-job: 'false'

- run: |
echo "BASE: ${{ steps.setSHAs.outputs.base }}"
Expand All @@ -58,22 +72,54 @@ jobs:
# Default: main
main-branch-name: ''

# The glob(7) pattern to be provided to `git describe --match` in order to match against
# the latest relevant tag on the specified "main" branch.
#
# The default pattern aligns with the default behavior of the complementary `nrwl/nx-tag-successful-ci-run` action.
#
# Default: nx_successful_ci_run*
tag-match-pattern: ''

# Applies the derived SHAs for base and head as NX_BASE and NX_HEAD environment variables within the current Job.
#
# Default: true
set-environment-variables-for-job: ''

# By default, if no matching tags are found on the main branch to determine the SHA, we will log a warning and use HEAD~1. Enable this option to error and exit instead.
# By default, if no successful workflow run is found on the main branch to determine the SHA, we will log a warning and use HEAD~1. Enable this option to error and exit instead.
#
# Default: false
error-on-no-matching-tags: ''
error-on-no-successful-workflow: ''

# The ID of the github action workflow to check for successful run or the name of the file name containing the workflow.
# E.g. 'ci.yml'. If not provided, current workflow id will be used
#
workflow-id: ''
```
<!-- end configuration-options -->
<!-- end configuration-options -->
## Background
When we run `affected` command on [Nx](https://nx.dev/), we can specify 2 git history positions - base and head, and it calculates [which projects in your repository changed
between those 2 commits](https://nx.dev/latest/angular/tutorial/11-test-affected-projects#step-11-test-affected-projects
). We can then run a set of tasks (like building or linting) only on those **affected** projects.

This makes it easy to set-up a CI system that scales well with the continous growth of your repository, as you add more and more projects.


### Problem

Figuring out what these two git commits are might not be as simple as it seems.

On a CI system that runs on submitted PRs, we determine what commits to include in the **affected** calculation by comparing our `HEAD-commit-of-PR-branch` to the commit in main branch (`master` or `main` usually) from which the PR branch originated. This will ensure the entirety of our PR is always being tested.

But what if we want to set up a continuous deployment system
that, as changes get pushed to `master`, it builds and deploys
only the affected projects?

What are the `FROM` and `TO` commits in that case?

Conceptually, what we want is to use the absolute latest commit on the `master` branch as the HEAD, and the previous _successful_ commit on `master` as the BASE. Note, we want the previous _successful_ one because it is still possible for commits on the `master` branch to fail for a variety of reasons.

The commits therefore can't just be `HEAD` and `HEAD~1`. If a few deployments fail one after another, that means that we're accumulating a list of affected projects that are not getting deployed. Anytime we retry the deployment, we want to include **every commit since the last time we deployed successfully**. That way we ensure we don't accidentally skip deploying a project that has changed.

This action enables you to find:
* Commit SHA from which PR originated (in the case of `pull_request`)
* Commit SHA of the last successful CI run

## License

[MIT](http://opensource.org/licenses/MIT)

Copyright (c) 2021-present Narwhal Technologies Inc.
25 changes: 12 additions & 13 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
name: 'Nx set SHAs'
description: 'Derives appropriate SHAs for base and head for use in `nx affected` commands, optionally setting them as environment variables for the current job'
name: "Nx set SHAs"
description: "Derives appropriate SHAs for base and head for use in `nx affected` commands, optionally setting them as environment variables for the current job"

inputs:
main-branch-name:
description: 'The name of the main branch in your repo, used as the target of PRs. E.g. main, master etc'
default: 'main'
tag-match-pattern:
description: 'The glob(7) pattern to be provided to `git describe --match` in order to match against the latest relevant tag on the specified main branch'
default: 'nx_successful_ci_run*'
description: "The name of the main branch in your repo, used as the target of PRs. E.g. main, master etc"
default: "main"
set-environment-variables-for-job:
description: 'Applies the derived SHAs for base and head as NX_BASE and NX_HEAD environment variables within the current Job'
default: 'true'
error-on-no-matching-tags:
description: 'By default, if no matching tags are found on the main branch to determine the SHA, we will log a warning and use HEAD~1. Enable this option to error and exit instead.'
description: "Applies the derived SHAs for base and head as NX_BASE and NX_HEAD environment variables within the current Job"
default: "true"
error-on-no-successful-workflow:
description: "By default, if no successful workflow is found on the main branch to determine the SHA, we will log a warning and use HEAD~1. Enable this option to error and exit instead."
workflow-id:
description: "The ID of the workflow to track or name of the file name. E.g. ci.yml. Defaults to current workflow"

outputs:
base:
Expand All @@ -23,12 +22,12 @@ outputs:
value: ${{ steps.setSHAs.outputs.head }}

runs:
using: 'composite'
using: "composite"
steps:
- name: Set base and head SHAs used for nx affected
id: setSHAs
shell: bash
run: ${{ github.action_path }}/run.sh '${{ github.event_name }}' '${{ inputs.main-branch-name }}' '${{ inputs.tag-match-pattern }}' '${{ inputs.error-on-no-matching-tags }}'
run: ${{ github.action_path }}/run.sh ${{ github.token }} ${{ github.event_name }} ${{ inputs.main-branch-name }} ${{ inputs.error-on-no-successful-workflow }} ${{ inputs.workflow-id }}

- name: Log base and head SHAs used for nx affected
shell: bash
Expand Down
69 changes: 69 additions & 0 deletions find-successful-workflow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
const { Octokit } = require("@octokit/action");
const core = require("@actions/core");
const { execSync } = require('child_process');

const token = process.argv[2];
const branch = process.argv[3];
const workflowId = process.argv[4];
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
const run_id = process.env.GITHUB_RUN_ID;
process.env.GITHUB_TOKEN = token;

(async () => {
try {
const octokit = new Octokit();
let workflow_id = workflowId;
if (!workflow_id) {
// retrieve workflow-id
workflow_id = await octokit.request(`GET /repos/${owner}/${repo}/actions/runs/${run_id}`, {
owner,
repo,
branch,
run_id
}).then(({ data: { workflow_id } }) => workflow_id);
}
// fetch all workflow runs on a given repo/branch/workflow with push and success
const shas = await octokit.request(`GET /repos/${owner}/${repo}/actions/workflows/${workflow_id}/runs`, {
owner,
repo,
branch,
workflow_id,
event: 'push',
status: 'success'
}).then(({ data: { workflow_runs } }) => workflow_runs.map(run => run.head_sha));

const sha = await findExistingCommit(shas);
console.log(sha);
} catch (e) {
core.setFailed(e.message);
process.exit(1);
}
})();

/**
* Get first existing commit
* @param {string[]} commit_shas
* @returns {string?}
*/
async function findExistingCommit(shas) {
for (const commitSha of shas) {
if (await commitExists(commitSha)) {
return commitSha;
}
}
return undefined;
}

/**
* Check if given commit is valid
* @param {string} commitSha
* @returns {boolean}
*/
async function commitExists(commitSha) {
try {
execSync(`git cat-file -e ${commitSha} 2> /dev/null`);
return true;
} catch {
return false;
}
}
Loading

0 comments on commit 2eec050

Please sign in to comment.