Skip to content

Commit

Permalink
feat: add allow not run workflow option
Browse files Browse the repository at this point in the history
  • Loading branch information
eladhaim committed Jan 21, 2024
1 parent 1b5230d commit 7ff0595
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 49 deletions.
7 changes: 7 additions & 0 deletions src/commands/set-shas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ parameters:
description: |
By default, only workflows with CircleCI status of "success" will be detected for the main branch.
Enable this option to also detect workflows with CircleCI status of "on_hold" in case your workflow requires manual approvals.
allow-not-run-workflow:
type: boolean
default: false
description: |
By default, only workflows with CircleCI status of "success" will be detected for the main branch.
Enable this option to also detect workflows with CircleCI status of "not_run" in case your workflow requires manual approvals.
skip-branch-filter:
type: boolean
default: false
Expand All @@ -39,6 +45,7 @@ steps:
PARAM_ERROR_ON_NO_SUCCESSFUL_WORKFLOW: <<parameters.error-on-no-successful-workflow>>
PARAM_WORKFLOW_NAME: <<parameters.workflow-name>>
PARAM_ALLOW_ON_HOLD: <<parameters.allow-on-hold-workflow>>
PARMA_ALLOW_NOT_RUN: <<parameters.allow-not-run-workflow>>
PARAM_SKIP_BRANCH_FILTER: <<parameters.skip-branch-filter>>
PARAM_SCRIPT: <<include(scripts/find-successful-workflow.js)>>
name: Derives SHAs for base and head for use in `nx affected` commands
Expand Down
140 changes: 92 additions & 48 deletions src/scripts/find-successful-workflow.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const https = require('https');
const { execSync } = require("child_process");
const https = require("https");

const buildUrl = process.argv[2];
const branchName = process.argv[3];
const mainBranchName = process.env.MAIN_BRANCH_NAME || process.argv[4];
const errorOnNoSuccessfulWorkflow = process.argv[5] === '1';
const allowOnHoldWorkflow = process.argv[6] === '1';
const skipBranchFilter = process.argv[7] === '1';
const errorOnNoSuccessfulWorkflow = process.argv[5] === "1";
const allowOnHoldWorkflow = process.argv[6] === "1";
const skipBranchFilter = process.argv[7] === "1";
const workflowName = process.argv[8];
const allowNotRunWorkflow = process.argv[9] == "1";
const circleToken = process.env.CIRCLE_API_TOKEN;

const [, host, project] = buildUrl.match(/https?:\/\/([^\/]+)\/(.*)\/\d+/);

let BASE_SHA;
(async () => {
if (branchName !== mainBranchName) {
BASE_SHA = execSync(`git merge-base origin/${mainBranchName} HEAD`, { encoding: 'utf-8' });
BASE_SHA = execSync(`git merge-base origin/${mainBranchName} HEAD`, {
encoding: "utf-8",
});
} else {
try {
BASE_SHA = await findSuccessfulCommit(skipBranchFilter ? undefined : mainBranchName, workflowName);
BASE_SHA = await findSuccessfulCommit(
skipBranchFilter ? undefined : mainBranchName,
workflowName
);
} catch (e) {
process.stderr.write(e.message);
if (errorOnNoSuccessfulWorkflow) {
Expand All @@ -31,7 +37,9 @@ This might be a temporary issue with their API or a misconfiguration of the CIRC
We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'.
NOTE: You can instead make this a hard error by settting 'error-on-no-successful-workflow' on the step in your workflow.\n\n`);
BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, { encoding: 'utf-8' });
BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, {
encoding: "utf-8",
});
}
}

Expand All @@ -51,7 +59,9 @@ WARNING: Unable to find a successful workflow run on 'origin/${mainBranchName}'.
We are therefore defaulting to use HEAD~1 on 'origin/${mainBranchName}'.
NOTE: You can instead make this a hard error by settting 'error-on-no-successful-workflow' on the step in your workflow.\n\n`);
BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, { encoding: 'utf-8' });
BASE_SHA = execSync(`git rev-parse origin/${mainBranchName}~1`, {
encoding: "utf-8",
});
}
} else {
process.stdout.write(`
Expand All @@ -69,15 +79,18 @@ async function findSuccessfulCommit(branch, workflowName) {
let foundSHA;

do {
const fullParams = params.concat(nextPage ? [`page-token=${nextPage}`] : []).join('&');
const { next_page_token, sha } = await getJson(`${url}${fullParams}`)
.then(async ({ next_page_token, items }) => {
const fullParams = params
.concat(nextPage ? [`page-token=${nextPage}`] : [])
.join("&");
const { next_page_token, sha } = await getJson(`${url}${fullParams}`).then(
async ({ next_page_token, items }) => {
const pipeline = await findSuccessfulPipeline(items, workflowName);
return {
next_page_token,
sha: pipeline ? pipeline.vcs.revision : void 0
sha: pipeline ? pipeline.vcs.revision : void 0,
};
});
}
);

foundSHA = sha;
nextPage = next_page_token;
Expand All @@ -88,9 +101,11 @@ async function findSuccessfulCommit(branch, workflowName) {

async function findSuccessfulPipeline(pipelines, workflowName) {
for (const pipeline of pipelines) {
if (!pipeline.errors.length
&& commitExists(pipeline.vcs.revision)
&& await isWorkflowSuccessful(pipeline.id, workflowName)) {
if (
!pipeline.errors.length &&
commitExists(pipeline.vcs.revision) &&
(await isWorkflowSuccessful(pipeline.id, workflowName))
) {
return pipeline;
}
}
Expand All @@ -99,7 +114,7 @@ async function findSuccessfulPipeline(pipelines, workflowName) {

function commitExists(commitSha) {
try {
execSync(`git cat-file -e ${commitSha}`, { stdio: ['pipe', 'pipe', null] });
execSync(`git cat-file -e ${commitSha}`, { stdio: ["pipe", "pipe", null] });
return true;
} catch {
return false;
Expand All @@ -108,11 +123,28 @@ function commitExists(commitSha) {

async function isWorkflowSuccessful(pipelineId, workflowName) {
if (!workflowName) {
return getJson(`https://${host}/api/v2/pipeline/${pipelineId}/workflow`)
.then(({ items }) => items.every(item => (item.status === 'success') || (allowOnHoldWorkflow && item.status === 'on_hold')));
return getJson(
`https://${host}/api/v2/pipeline/${pipelineId}/workflow`
).then(({ items }) =>
items.every(
(item) =>
item.status === "success" ||
(allowOnHoldWorkflow && item.status === "on_hold") ||
(allowNotRunWorkflow && item.status === "not_run")
)
);
} else {
return getJson(`https://${host}/api/v2/pipeline/${pipelineId}/workflow`)
.then(({ items }) => items.some(item => ((item.status === 'success') || (allowOnHoldWorkflow && item.status === 'on_hold')) && item.name === workflowName));
return getJson(
`https://${host}/api/v2/pipeline/${pipelineId}/workflow`
).then(({ items }) =>
items.some(
(item) =>
(item.status === "success" ||
(allowOnHoldWorkflow && item.status === "on_hold") ||
(allowNotRunWorkflow && item.status === "not_run")) &&
item.name === workflowName
)
);
}
}

Expand All @@ -122,34 +154,46 @@ async function getJson(url) {

if (circleToken) {
options.headers = {
'Circle-Token': circleToken
}
"Circle-Token": circleToken,
};
}

https.get(url, options, (res) => {
let data = [];

res.on('data', chunk => {
data.push(chunk);
});

res.on('end', () => {
const response = Buffer.concat(data).toString();
try {
const responseJSON = JSON.parse(response);
resolve(responseJSON);
} catch (e) {
if (response.includes('Project not found')) {
reject(new Error(`Error: Project not found.\nIf you are using a private repo, make sure the CIRCLE_API_TOKEN is set.\n\n${response}`));
} else {
reject(e)
https
.get(url, options, (res) => {
let data = [];

res.on("data", (chunk) => {
data.push(chunk);
});

res.on("end", () => {
const response = Buffer.concat(data).toString();
try {
const responseJSON = JSON.parse(response);
resolve(responseJSON);
} catch (e) {
if (response.includes("Project not found")) {
reject(
new Error(
`Error: Project not found.\nIf you are using a private repo, make sure the CIRCLE_API_TOKEN is set.\n\n${response}`
)
);
} else {
reject(e);
}
}
}
});
}).on('error', error => reject(
circleToken
? new Error(`Error: Pipeline fetching failed.\nCheck if you set the correct user CIRCLE_API_TOKEN.\n\n${error.toString()}`)
: new Error(`Error: Pipeline fetching failed.\nIf this is private repo you will need to set CIRCLE_API_TOKEN\n\n${error.toString()}`)
));
});
})
.on("error", (error) =>
reject(
circleToken
? new Error(
`Error: Pipeline fetching failed.\nCheck if you set the correct user CIRCLE_API_TOKEN.\n\n${error.toString()}`
)
: new Error(
`Error: Pipeline fetching failed.\nIf this is private repo you will need to set CIRCLE_API_TOKEN\n\n${error.toString()}`
)
)
);
});
}
2 changes: 1 addition & 1 deletion src/scripts/set-shas.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if [ -z "$CIRCLE_BRANCH" ]; then
else
TARGET_BRANCH=$CIRCLE_BRANCH
fi
RESPONSE=$(node index.js $CIRCLE_BUILD_URL $TARGET_BRANCH $PARAM_MAIN_BRANCH $PARAM_ERROR_ON_NO_SUCCESSFUL_WORKFLOW $PARAM_ALLOW_ON_HOLD $PARAM_SKIP_BRANCH_FILTER $PARAM_WORKFLOW_NAME)
RESPONSE=$(node index.js $CIRCLE_BUILD_URL $TARGET_BRANCH $PARAM_MAIN_BRANCH $PARAM_ERROR_ON_NO_SUCCESSFUL_WORKFLOW $PARAM_ALLOW_ON_HOLD $PARAM_SKIP_BRANCH_FILTER $PARAM_WORKFLOW_NAME $PARMA_ALLOW_NOT_RUN)
echo "$RESPONSE"
BASE_SHA=$(echo "$RESPONSE" | grep 'Commit:' | sed 's/.*Commit: //')
HEAD_SHA=$(git rev-parse HEAD)
Expand Down

0 comments on commit 7ff0595

Please sign in to comment.