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

Users/nibansal/releasesv1 update #7

Merged
merged 6 commits into from
Nov 7, 2019
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
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Trigger Azure pipeline
Use this action to trigger pipelines in Azure DevOps.
# GitHub Action to trigger a run in Azure pipelines

GitHub Actions makes it easy to build, test, and deploy your code right from GitHub.

However, if you would like to use your GH Action workflows just for CI and for CD, continue to use your favorite [Azure Pipelines](https://azure.microsoft.com/en-in/services/devops/pipelines/) with all the best-in-class features needed to enable compliant, safe deployments to their prod Environments, it is quite possible with this azure/pipelines action.

With this action, you could trigger an Azure pipeline run right from inside an Action workflow.

The definition of this Github Action is in [action.yml](https://github.com/Azure/pipelines/blob/master/action.yml).

## Sample workflow

Use this action to trigger a specific pipeline (YAML or Classic Release Pipeline) in Azure DevOps account.
Action takes Project URl, pipeline name and a [Personal Access Token (PAT)](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops) for your DevOps account.

```yaml
- uses: Azure/pipelines@v1
Expand Down
4 changes: 2 additions & 2 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ function main() {
return __awaiter(this, void 0, void 0, function* () {
try {
const pipelineRunner = new pipeline_runner_1.PipelineRunner(task_parameters_1.TaskParameters.getTaskParams());
core.info("Starting pipeline runner");
core.debug("Starting pipeline runner");
yield pipelineRunner.start();
core.info("pipeline runner completed");
core.debug("pipeline runner completed");
}
catch (error) {
const errorMessage = JSON.stringify(error);
Expand Down
68 changes: 26 additions & 42 deletions lib/pipeline.runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const pipeline_error_1 = require("./pipeline.error");
const ReleaseInterfaces = __importStar(require("azure-devops-node-api/interfaces/ReleaseInterfaces"));
const BuildInterfaces = __importStar(require("azure-devops-node-api/interfaces/BuildInterfaces"));
const pipeline_helper_1 = require("./util/pipeline.helper");
const logger_1 = require("./util/logger");
const url_parser_1 = require("./util/url.parser");
class PipelineRunner {
constructor(taskParameters) {
Expand All @@ -38,16 +39,17 @@ class PipelineRunner {
var taskParams = task_parameters_1.TaskParameters.getTaskParams();
let authHandler = azdev.getPersonalAccessTokenHandler(taskParams.azureDevopsToken);
let collectionUrl = url_parser_1.UrlParser.GetCollectionUrlBase(this.taskParameters.azureDevopsProjectUrl);
core.info("Creating connection with Azure DevOps service : " + collectionUrl);
core.info(`Creating connection with Azure DevOps service : "${collectionUrl}"`);
let webApi = new azdev.WebApi(collectionUrl, authHandler);
core.info("Connection created");
let pipelineName = this.taskParameters.azurePipelineName;
try {
core.info("Triggering Yaml pipeline : " + this.taskParameters.azurePipelineName);
core.debug(`Triggering Yaml pipeline : "${pipelineName}"`);
yield this.RunYamlPipeline(webApi);
}
catch (error) {
if (error instanceof pipeline_error_1.PipelineNotFoundError) {
core.info("Triggering Designer pipeline : " + this.taskParameters.azurePipelineName);
core.debug(`Triggering Designer pipeline : "${pipelineName}"`);
yield this.RunDesignerPipeline(webApi);
}
else {
Expand All @@ -63,40 +65,31 @@ class PipelineRunner {
}
RunYamlPipeline(webApi) {
return __awaiter(this, void 0, void 0, function* () {
let buildApi = yield webApi.getBuildApi();
let projectName = url_parser_1.UrlParser.GetProjectName(this.taskParameters.azureDevopsProjectUrl);
let pipelineName = this.taskParameters.azurePipelineName;
let buildApi = yield webApi.getBuildApi();
// Get matching build definitions for the given project and pipeline name
const buildDefinitions = yield buildApi.getDefinitions(projectName, pipelineName);
// If definition not found then Throw Error
if (buildDefinitions == null || buildDefinitions.length == 0) {
let errorMessage = `YAML Pipeline named "${pipelineName}" in project ${projectName} not found`;
throw new pipeline_error_1.PipelineNotFoundError(errorMessage);
}
// If more than 1 definition is returned, Throw Error
if (buildDefinitions.length > 1) {
let errorMessage = `YAML Pipeline named "${pipelineName}" in project ${projectName} not found`;
throw Error(errorMessage);
}
pipeline_helper_1.PipelineHelper.EnsureValidPipeline(projectName, pipelineName, buildDefinitions);
// Extract Id from build definition
let buildDefinitionReference = buildDefinitions[0];
let buildDefinitionId = buildDefinitionReference.id;
// Get build definition for the matching definition Id
let buildDefinition = yield buildApi.getDefinition(projectName, buildDefinitionId);
core.info("Pipeline object : " + pipeline_helper_1.PipelineHelper.getPrintObject(buildDefinition));
logger_1.Logger.LogPipelineObject(buildDefinition);
// Fetch repository details from build definition
let repositoryId = buildDefinition.repository.id.trim();
let repositoryType = buildDefinition.repository.type.trim();
let sourceBranch = null;
let sourceVersion = null;
// If definition is linked to existing github repo, pass github source branch and source version to build
if (pipeline_helper_1.PipelineHelper.equals(repositoryId, this.repository) && pipeline_helper_1.PipelineHelper.equals(repositoryType, this.githubRepo)) {
core.info("pipeline is linked to same Github repo");
core.debug("pipeline is linked to same Github repo");
sourceBranch = this.branch,
sourceVersion = this.commitId;
}
else {
core.info("pipeline is not linked to same Github repo");
core.debug("pipeline is not linked to same Github repo");
}
let build = {
definition: {
Expand All @@ -109,54 +102,45 @@ class PipelineRunner {
sourceVersion: sourceVersion,
reason: BuildInterfaces.BuildReason.Triggered
};
core.info("Input - \n" + pipeline_helper_1.PipelineHelper.getPrintObject(build));
logger_1.Logger.LogPipelineTriggerInput(build);
// Queue build
let buildQueueResult = yield buildApi.queueBuild(build, build.project.id, true);
if (buildQueueResult != null) {
core.info("Output - \n" + pipeline_helper_1.PipelineHelper.getPrintObject(buildQueueResult));
logger_1.Logger.LogPipelineTriggerOutput(buildQueueResult);
// If build result contains validation errors set result to FAILED
if (buildQueueResult.validationResults != null && buildQueueResult.validationResults.length > 0) {
let errorAndWarningMessage = pipeline_helper_1.PipelineHelper.getErrorAndWarningMessageFromBuildResult(buildQueueResult.validationResults);
core.setFailed("Errors: " + errorAndWarningMessage.errorMessage + " Warnings: " + errorAndWarningMessage.warningMessage);
}
else {
core.info(`\Pipeline "${pipelineName}" started - Id: ${buildQueueResult.id}`);
if (buildQueueResult._links != null && buildQueueResult._links.web != null) {
core.setOutput('pipeline-url', buildQueueResult._links.web.href);
logger_1.Logger.LogPipelineTriggered(pipelineName, projectName);
if (buildQueueResult._links != null) {
logger_1.Logger.LogOutputUrl(buildQueueResult._links.web.href);
}
}
}
});
}
RunDesignerPipeline(webApi) {
return __awaiter(this, void 0, void 0, function* () {
let releaseApi = yield webApi.getReleaseApi();
let projectName = url_parser_1.UrlParser.GetProjectName(this.taskParameters.azureDevopsProjectUrl);
let pipelineName = this.taskParameters.azurePipelineName;
let releaseApi = yield webApi.getReleaseApi();
// Get release definitions for the given project name and pipeline name
const releaseDefinitions = yield releaseApi.getReleaseDefinitions(projectName, pipelineName, ReleaseInterfaces.ReleaseDefinitionExpands.Artifacts);
// If definition not found then Throw Error
if (releaseDefinitions == null || releaseDefinitions.length == 0) {
let errorMessage = `Designer Pipeline named "${pipelineName}" in project ${projectName} not found`;
throw new pipeline_error_1.PipelineNotFoundError(errorMessage);
}
if (releaseDefinitions.length > 1) {
// If more than 1 definition found, throw ERROR
let errorMessage = `More than 1 Designer Pipeline named "${pipelineName}" in project ${projectName} found`;
throw Error(errorMessage);
}
pipeline_helper_1.PipelineHelper.EnsureValidPipeline(projectName, pipelineName, releaseDefinitions);
let releaseDefinition = releaseDefinitions[0];
core.info("Pipeline object : " + pipeline_helper_1.PipelineHelper.getPrintObject(releaseDefinition));
logger_1.Logger.LogPipelineObject(releaseDefinition);
// Filter Github artifacts from release definition
let gitHubArtifacts = releaseDefinition.artifacts.filter(pipeline_helper_1.PipelineHelper.isGitHubArtifact);
let artifacts = new Array();
if (gitHubArtifacts == null || gitHubArtifacts.length == 0) {
core.info("Pipeline is not linked to any GitHub artifact");
core.debug("Pipeline is not linked to any GitHub artifact");
// If no GitHub artifacts found it means pipeline is not linked to any GitHub artifact
}
else {
// If pipeline has any matching Github artifact
core.info("Pipeline is linked to GitHub artifact. Looking for now matching repository");
core.debug("Pipeline is linked to GitHub artifact. Looking for now matching repository");
gitHubArtifacts.forEach(gitHubArtifact => {
if (gitHubArtifact.definitionReference != null && pipeline_helper_1.PipelineHelper.equals(gitHubArtifact.definitionReference.definition.name, this.repository)) {
// Add version information for matching GitHub artifact
Expand All @@ -170,7 +154,7 @@ class PipelineRunner {
sourceVersion: this.commitId
}
};
core.info("pipeline is linked to same Github repo");
core.debug("pipeline is linked to same Github repo");
artifacts.push(artifactMetadata);
}
});
Expand All @@ -180,15 +164,15 @@ class PipelineRunner {
reason: ReleaseInterfaces.ReleaseReason.ContinuousIntegration,
artifacts: artifacts
};
core.info("Input - \n" + pipeline_helper_1.PipelineHelper.getPrintObject(releaseStartMetadata));
logger_1.Logger.LogPipelineTriggerInput(releaseStartMetadata);
// create release
let release = yield releaseApi.createRelease(releaseStartMetadata, projectName);
if (release != null) {
core.info("Output - \n" + pipeline_helper_1.PipelineHelper.getPrintObject(release));
if (release != null && release._links != null && release._links.web != null) {
core.setOutput('pipeline-url', release._links.web.href);
logger_1.Logger.LogPipelineTriggered(pipelineName, projectName);
logger_1.Logger.LogPipelineTriggerOutput(release);
if (release != null && release._links != null) {
logger_1.Logger.LogOutputUrl(release._links.web.href);
}
core.info("Release is created");
}
});
}
Expand Down
37 changes: 37 additions & 0 deletions lib/util/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use strict";
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
class Logger {
static LogOutputUrl(url) {
if (url) {
core.setOutput('pipeline-url', url);
core.info(`More details on triggered pipeline can be found here : "${url}"`);
}
}
static LogInfo(message) {
core.info(message);
}
static LogPipelineTriggered(pipelineName, projectName) {
core.info(`\Pipeline '${pipelineName}' is triggered in project '${projectName}'`);
}
static LogPipelineObject(object) {
core.debug("Pipeline object : " + this.getPrintObject(object));
}
static LogPipelineTriggerInput(input) {
core.debug("Input: " + this.getPrintObject(input));
}
static LogPipelineTriggerOutput(output) {
core.debug("Output: " + this.getPrintObject(output));
}
static getPrintObject(object) {
return JSON.stringify(object, null, 4);
}
}
exports.Logger = Logger;
16 changes: 13 additions & 3 deletions lib/util/pipeline.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
const BuildInterfaces = __importStar(require("azure-devops-node-api/interfaces/BuildInterfaces"));
const pipeline_error_1 = require("./../pipeline.error");
class PipelineHelper {
static EnsureValidPipeline(projectName, pipelineName, pipelines) {
// If definition not found then Throw Error
if (pipelines == null || pipelines.length == 0) {
let errorMessage = `Pipeline named "${pipelineName}" not found in project "${projectName}"`;
throw new pipeline_error_1.PipelineNotFoundError(errorMessage);
}
if (pipelines.length > 1) {
// If more than 1 definition found, throw ERROR
let errorMessage = `More than 1 Pipeline named "${pipelineName}" found in project "${projectName}"`;
throw Error(errorMessage);
}
}
static equals(str1, str2) {
if (str1 === str2) {
return true;
Expand All @@ -21,9 +34,6 @@ class PipelineHelper {
}
return str1.trim().toUpperCase() === str2.trim().toUpperCase();
}
static getPrintObject(object) {
return JSON.stringify(object, null, 4);
}
static processEnv(envVarName) {
const variable = process.env[envVarName];
if (!variable) {
Expand Down
54 changes: 51 additions & 3 deletions lib/util/url.parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,59 @@
Object.defineProperty(exports, "__esModule", { value: true });
class UrlParser {
static GetProjectName(projectUrl) {
var projectNamePart = projectUrl.substr(projectUrl.lastIndexOf("/") + 1);
return decodeURI(projectNamePart);
if (this.IsNullOrEmpty(projectUrl)) {
throw new Error(this.NullOrEmptyProjectUrl);
}
try {
projectUrl = projectUrl.trim();
this.EnsureProjectName(projectUrl);
var index = projectUrl.lastIndexOf("/");
var projectNamePart = projectUrl.substr(index + 1);
var projectName = decodeURI(projectNamePart);
if (projectName) {
return projectName;
}
else {
throw Error();
}
}
catch (error) {
var errorMessage = this.GetUrlParseExceptionMessage(projectUrl);
throw new Error(errorMessage);
}
}
static GetCollectionUrlBase(projectUrl) {
return projectUrl.substr(0, projectUrl.lastIndexOf("/"));
if (this.IsNullOrEmpty(projectUrl)) {
throw new Error(this.NullOrEmptyProjectUrl);
}
try {
projectUrl = projectUrl.trim();
var collectionUrl = projectUrl.substr(0, projectUrl.lastIndexOf("/"));
if (collectionUrl) {
return collectionUrl;
}
else {
throw Error();
}
}
catch (error) {
var errorMessage = this.GetUrlParseExceptionMessage(projectUrl);
throw new Error(errorMessage);
}
}
static EnsureProjectName(projectUrl) {
var index = projectUrl.lastIndexOf("/");
if (index == (projectUrl.length - 1)) {
throw Error();
}
}
static GetUrlParseExceptionMessage(projectUrl) {
let errorMessage = `Failed to parse project url: "${projectUrl}". Specify the valid project url (eg, https://dev.azure.com/organization/project-name or https://server.example.com:8080/tfs/DefaultCollection/project-name)) and try again.`;
return errorMessage;
}
static IsNullOrEmpty(value) {
return (!value);
}
}
exports.UrlParser = UrlParser;
UrlParser.NullOrEmptyProjectUrl = "Project url is null or empty. Specify the valid project url and try again";
Loading