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

feat: Wait for pipeline #65

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
11 changes: 11 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ inputs:
target-tag:
required: false
description: 'The tag of the target CircleCI project. Will default to the current tag name if set. This or branch should be overwritten if "target-slug" is set. Can be overwritten with "TARGET_TAG" environment variable.'
wait-for-pipeline:
required: false
description: 'Wait for pipeline to finish'
wait-for-pipeline-timeout:
required: false
default: "300"
description: 'Wait for pipeline timeout'
wait-for-pipeline-interval:
required: false
default: "10"
description: 'Wait for pipeline to finish'
outputs:
id:
description: The unique ID of the pipeline.
Expand Down
178 changes: 156 additions & 22 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16242,19 +16242,15 @@ function wrappy (fn, cb) {

/***/ }),

/***/ 1373:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
/***/ 9164:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {

"use strict";

var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CircleCIPipelineTrigger = void 0;
exports.CircleCIBase = void 0;
const core_1 = __nccwpck_require__(2186);
const axios_1 = __importDefault(__nccwpck_require__(6545));
class CircleCIPipelineTrigger {
class CircleCIBase {
constructor(context, host = process.env.CCI_HOST || "circleci.com") {
this.context = context;
this.host = host;
Expand All @@ -16265,15 +16261,10 @@ class CircleCIPipelineTrigger {
this.vcs = vcs;
this.owner = owner;
this.repo = repo;
this.url = `https://${this.host}/api/v2/project/${this.vcs}/${this.owner}/${this.repo}/pipeline`;
this.metaData = (0, core_1.getInput)("GHA_Meta");
this.base_url = `https://${this.host}/api/v2`;
this.project_url = `${this.base_url}/project/${this.vcs}/${this.owner}/${this.repo}`;
this.tag = this.getTag();
this.branch = this.getBranch();
this.parameters = {
GHA_Actor: context.actor,
GHA_Action: context.action,
GHA_Event: context.eventName,
};
}
parseSlug(slug) {
const [vcs, owner, repo] = slug.split("/");
Expand Down Expand Up @@ -16307,6 +16298,36 @@ class CircleCIPipelineTrigger {
}
return branch;
}
}
exports.CircleCIBase = CircleCIBase;


/***/ }),

/***/ 1373:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CircleCIPipelineTrigger = void 0;
const core_1 = __nccwpck_require__(2186);
const axios_1 = __importDefault(__nccwpck_require__(6545));
const CircleCIBase_1 = __nccwpck_require__(9164);
class CircleCIPipelineTrigger extends CircleCIBase_1.CircleCIBase {
constructor(context, host = process.env.CCI_HOST || "circleci.com") {
super(context, host);
this.pipeline_url = `${this.project_url}/pipeline`;
this.metaData = (0, core_1.getInput)("GHA_Meta");
this.parameters = {
GHA_Actor: context.actor,
GHA_Action: context.action,
GHA_Event: context.eventName,
};
}
triggerPipeline() {
const body = {
parameters: this.parameters,
Expand All @@ -16319,13 +16340,13 @@ class CircleCIPipelineTrigger {
}
body[this.tag ? "tag" : "branch"] = this.tag || this.branch;
(0, core_1.info)(`Triggering CircleCI Pipeline for ${this.owner}/${this.repo}`);
(0, core_1.info)(` Triggering URL: ${this.url}`);
(0, core_1.info)(` Triggering URL: ${this.pipeline_url}`);
const trigger = this.tag ? `tag: ${this.tag}` : `branch: ${this.branch}`;
(0, core_1.info)(` Triggering ${trigger}`);
(0, core_1.info)(` Parameters:\n${JSON.stringify(this.parameters)}`);
(0, core_1.endGroup)();
axios_1.default
.post(this.url, body, {
return axios_1.default
.post(this.pipeline_url, body, {
headers: {
"content-type": "application/json",
"x-attribution-login": this.context.actor,
Expand All @@ -16341,18 +16362,123 @@ class CircleCIPipelineTrigger {
(0, core_1.setOutput)("number", response.data.number);
(0, core_1.setOutput)("state", response.data.state);
(0, core_1.endGroup)();
return String(response.data.id);
})
.catch((error) => {
(0, core_1.startGroup)("Failed to trigger CircleCI Pipeline");
(0, core_1.error)(error);
(0, core_1.setFailed)(error.message);
(0, core_1.setFailed)(error);
(0, core_1.endGroup)();
return "";
});
}
}
exports.CircleCIPipelineTrigger = CircleCIPipelineTrigger;


/***/ }),

/***/ 7860:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

"use strict";

var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CircleCIPipelineWait = void 0;
const core_1 = __nccwpck_require__(2186);
const axios_1 = __importDefault(__nccwpck_require__(6545));
const CircleCIBase_1 = __nccwpck_require__(9164);
class CircleCIPipelineWait extends CircleCIBase_1.CircleCIBase {
constructor(context, host = process.env.CCI_HOST || "circleci.com") {
super(context, host);
this.wait_for_pipeline = undefined;
this.base_url = `https://${this.host}/api/v2/project/${this.vcs}/${this.owner}/${this.repo}`;
if (JSON.parse((0, core_1.getInput)("wait-for-pipeline").toLowerCase())) {
this.wait_for_pipeline = {
timeout: Number((0, core_1.getInput)("wait-for-pipeline-timeout")),
interval: Number((0, core_1.getInput)("wait-for-pipeline-interval")),
};
}
}
checkWorkflow(workflow_url, params = undefined) {
return axios_1.default
.get(workflow_url, {
headers: {
"content-type": "application/json",
"x-attribution-login": this.context.actor,
"x-attribution-actor-id": this.context.actor,
"Circle-Token": `${process.env.CCI_TOKEN}`,
},
params: params,
})
.then((response) => {
let finished = true;
// Check status of all workflows
for (const workflow of response.data.items) {
switch (workflow.status) {
// Do not do anything special for failing, wait for failed state
case "failing":
case "running":
case "on_hold":
finished = false;
break;
case "failed":
case "not_run":
case "error":
case "unauthorized":
case "canceled":
// Accumulate any failed workflow states
(0, core_1.setFailed)(`Failed workflow: ${workflow.name} as ${workflow.status}`);
break;
case "success":
break;
default:
(0, core_1.setFailed)(`Unrecognized workflow state: ${workflow.status}\nDetails: ${workflow}`);
}
}
// Accumulate the next page results
if (response.data.next_page_token) {
params = {
"next_page_token": response.data.next_page_token,
};
}
if (params) {
// Finished only when all workflow pages are finished
return finished && this.checkWorkflow(workflow_url, params);
}
return finished;
})
.catch((error) => {
(0, core_1.setFailed)(error);
// Return as finished
return true;
});
}
waitForPipeline(pipeline_id) {
// If not requested to wait for pipeline just return
if (!this.wait_for_pipeline)
return;
const workflow_url = `https://${this.host}/api/v2/pipeline/${pipeline_id}/workflow`;
(0, core_1.startGroup)("Waiting for pipeline to finish");
const waitInterval = setInterval(async () => {
const finished = await this.checkWorkflow(workflow_url);
if (finished) {
clearInterval(waitInterval);
clearTimeout(waitTimeout);
}
}, this.wait_for_pipeline.interval * 1000);
const waitTimeout = setTimeout(() => {
clearInterval(waitInterval);
(0, core_1.setFailed)(`Pipeline did not finish in ${this.wait_for_pipeline?.timeout} (s)`);
}, this.wait_for_pipeline.timeout * 1000);
(0, core_1.endGroup)();
}
}
exports.CircleCIPipelineWait = CircleCIPipelineWait;


/***/ }),

/***/ 2877:
Expand Down Expand Up @@ -16554,8 +16680,16 @@ var exports = __webpack_exports__;
Object.defineProperty(exports, "__esModule", ({ value: true }));
const CircleCIPipelineTrigger_1 = __nccwpck_require__(1373);
const github_1 = __nccwpck_require__(5438);
const trigger = new CircleCIPipelineTrigger_1.CircleCIPipelineTrigger(github_1.context);
trigger.triggerPipeline();
const CircleCIPipelineWait_1 = __nccwpck_require__(7860);
async function main() {
const trigger = new CircleCIPipelineTrigger_1.CircleCIPipelineTrigger(github_1.context);
const pipelineID = await trigger.triggerPipeline();
if (pipelineID) {
const waiter = new CircleCIPipelineWait_1.CircleCIPipelineWait(github_1.context);
waiter.waitForPipeline(pipelineID);
}
}
main();

})();

Expand Down
13 changes: 11 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { CircleCIPipelineTrigger } from "./lib/CircleCIPipelineTrigger";
import { context } from "@actions/github";
import { CircleCIPipelineWait } from "./lib/CircleCIPipelineWait";

const trigger = new CircleCIPipelineTrigger(context);
trigger.triggerPipeline();
async function main() {
const trigger = new CircleCIPipelineTrigger(context);
const pipelineID = await trigger.triggerPipeline();
if (pipelineID) {
const waiter = new CircleCIPipelineWait(context);
waiter.waitForPipeline(pipelineID);
}
}

main();
69 changes: 69 additions & 0 deletions src/lib/CircleCIBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
getInput,
info,
} from "@actions/core";
import { Context } from "@actions/github/lib/context";

export class CircleCIBase {
vcs: string;
owner: string;
repo: string;
tag?: string;
branch?: string;
host: string;
context: Context;
base_url: string;
project_url: string;

constructor(context: Context, host = process.env.CCI_HOST || "circleci.com") {
this.context = context;
this.host = host;
const slug = process.env.TARGET_SLUG ?? getInput("target-slug");
const { vcs, owner, repo } = slug
? this.parseSlug(slug)
: { ...context.repo, vcs: "gh" };
this.vcs = vcs;
this.owner = owner;
this.repo = repo;
this.base_url = `https://${this.host}/api/v2`;
this.project_url = `${this.base_url}/project/${this.vcs}/${this.owner}/${this.repo}`;
this.tag = this.getTag();
this.branch = this.getBranch();
}

parseSlug(slug: string) {
const [vcs, owner, repo] = slug.split("/");
if (!owner || !repo || !vcs) {
throw new Error(`Invalid target-slug: ${slug}`);
}
return { vcs, owner, repo };
}

getTag() {
let tag = process.env.TARGET_TAG ?? getInput("target-tag");
if (!tag) {
const tagRef = "refs/tags/";
if (this.context.ref.startsWith(tagRef)) {
tag = this.context.ref.substring(tagRef.length);
}
}
return tag;
}

getBranch() {
let branch = process.env.TARGET_BRANCH ?? getInput("target-branch");
if (!branch) {
if (this.context.ref.startsWith("refs/heads/")) {
branch = this.context.ref.substring(11);
} else if (this.context.ref.startsWith("refs/pull/")) {
info(`This is a PR. Using head PR branch`);
const pullRequestNumber = (
this.context.ref.match(/refs\/pull\/([0-9]*)\//) as RegExpMatchArray
)[1];
const newref = `pull/${pullRequestNumber}/head`;
branch = newref;
}
}
return branch;
}
}
Loading