Skip to content

Commit

Permalink
Merge pull request #21 from keesschollaart81/dev
Browse files Browse the repository at this point in the history
V5.0 - Rewrite to Node
  • Loading branch information
keesschollaart81 authored Dec 28, 2018
2 parents 419e22d + 277a25d commit 4e6f448
Show file tree
Hide file tree
Showing 35 changed files with 583 additions and 47 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,5 @@ paket-files/
*.sln.iml

# Custom
*.vsix
*.vsix
dist/
61 changes: 30 additions & 31 deletions Marketplace.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,33 @@
# ARM Outputs
<h1 align="center">
<img src="https://raw.githubusercontent.com/keesschollaart81/vsts-arm-outputs/dev/images/banner.png" width=500 alt="ARM Outputs Banner"/>
</h1>

This extension enables you to use the ARM Deployment outputs in your VSTS environment.
This extension enables you to use the ARM Deployment outputs in your Azure Pipelines.

This step will use the last successful deployment within the selected resource group. If this deployent has outputs, all of them are copied to VSTS variables by the ARM Output key.
This step will use the last successful deployment within the selected resource group. If this deployent has outputs, all of them are copied to Pipelines variables by the ARM Output key:

This outputs can then be used by default VSTS ways: ```$(same-key-as-in-arm-template)```
[![screenshot-1](images/screenshot.png "Screenshot-1")](images/screenshot.png)

This outputs can then be used by default Azure Devops Pipelines ways: ```$(same-key-as-in-arm-template)```

Usually this task is ran directly after the 'Azure Resource Group Deployment' task.

[![screenshot-1](images/screenshots-vsts-arm-outputs-1.png "Screenshot-1")](images/screenshots-vsts-arm-outputs-1.png)
[![screenshot-2](images/screenshot2.png "Screenshot-1")](images/screenshot2.png)

## Release notes

### Version 4.0 - 03-09-2018

- Support for complex outputs
- Now based on AzurePowershell task handler
- Improved performance
- Less dependencies
- Easier to port to Linux agents Powershell Core on VSTS becomes a thing

### Version 3.0 - 01-02-2018

- Filter on deployment name

### Version 2.0 - 18-11-2017

- LastDeploymentBehaviour added
### Version 5.0 - 25-12-2018

### Version 1.0 - 13-04-2017
- Rewrite to Node to enable Linux based agents
- Updated naming (VSTS > Azure DevOps)

- Initial version
Previous release info can be found on [GitHub Releases](https://github.com/keesschollaart81/vsts-arm-outputs/releases)

## Parameter usage

### Secrets

If your output is of type ```SecureString``` the output value cannot be read and these outputs are therefore ignored.

You can off course output your secrets as string but then these values might be exposed in logfiles (and visible via the Azure Portal as well)

### Prefix

Using the 'prefix' parameter, it is possible to prefix the variables used within VSTS. A prefix can be used to distinct variables coming out of ARM from regular VSTS variables. A prefix can also be to prevent collisions between ARM Output names and VSTS Variable names.
Using the 'prefix' parameter, it is possible to prefix the variables used within Pipelines. A prefix can be used to distinct variables coming out of ARM from regular Pipelines variables. A prefix can also be to prevent collisions between ARM Output names and Pipelines Variable names.

### Output Names

Expand All @@ -54,9 +39,23 @@ Using the 'When last deployment is failed' parameter, you can choose the behavio

### Filter deployment name

Optional string to filter deployments by. This can be useful if you have concurrent deployments to the same resource group. Deployment names in VSTS are the name of the json file plus date and time, so a file CreateKeyVault.json could have a deployment name of CreateKeyVault-20180025-151538-0688. In this case, if you want to filter to deployments of this file enter CreateKeyVault as the filter
Optional string to filter deployments by. This can be useful if you have concurrent deployments to the same resource group. Deployment names in Pipelines are the name of the json file plus date and time, so a file CreateKeyVault.json could have a deployment name of CreateKeyVault-20180025-151538-0688. In this case, if you want to filter to deployments of this file enter CreateKeyVault as the filter

## Good to know

### Secrets

If your output is of type ```SecureString``` the output value cannot be read and these outputs are therefore ignored.

You can off course output your secrets as string but then these values might be exposed in logfiles (and visible via the Azure Portal as well)

### Telemetry

From version 5.x onwards this task sends some data to my Application Insights. You can opt-out by adding a variable in your pipelines with the name 'arm-outputs-notelemetry'

The thinks I track to improve/monitor this task are: the type of host/os, the version and duration of this task and the message/callstack of exceptions when they occur. I will never send things like name/value of your tenant, subscription, resource-group or your ARM Outputs. Please don't just take my word but check [the code](https://github.com/keesschollaart81/vsts-arm-outputs/) and see the actual [deployment pipelines history](https://caseonline.visualstudio.com/ARM%20Outputs/_release?definitionId=1) for any (recent) version.

## Complex outputs
### Complex outputs

If your output is not a single value but a complex type, like ```second``` in this example:

Expand Down
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
# ARM Outputs
<h1 align="center">
<img src="https://raw.githubusercontent.com/keesschollaart81/vsts-arm-outputs/dev/images/banner.png" width=500 alt="ARM Outputs Banner"/>
</h1>

[Click here to view this extension in the VSTS Marketplace](https://marketplace.visualstudio.com/items?itemName=keesschollaart.arm-outputs)
<div align="center">

This extension enables you to use the ARM Deployment outputs in your VSTS environment.
[Click here to view this extension in the Azure DevOps Marketplace](https://marketplace.visualstudio.com/items?itemName=keesschollaart.arm-outputs)

This step will use the last successful deployment within the selected resource group. If this deployent has outputs, all of them are copied to VSTS variables by the ARM Output key.
This extension enables you to use the ARM Deployment outputs in your Azure Pipelines.

This outputs can then be used by default VSTS ways: ```$(same-key-as-in-arm-template)```
[![Build Status](https://caseonline.visualstudio.com/ARM%20Outputs/_apis/build/status/ARM%20Outputs-CI?branchName=dev)](https://caseonline.visualstudio.com/ARM%20Outputs/_build/latest?definitionId=19?branchName=dev) [![Dev Deploy](https://caseonline.vsrm.visualstudio.com/_apis/public/Release/badge/0b79a2e6-b205-45d0-a677-ad0688669d24/1/1)](https://caseonline.visualstudio.com/ARM%20Outputs/_releaseDefinition?definitionId=1) [![Production](https://caseonline.vsrm.visualstudio.com/_apis/public/Release/badge/0b79a2e6-b205-45d0-a677-ad0688669d24/1/3)](https://caseonline.visualstudio.com/ARM%20Outputs/_releaseDefinition?definitionId=1)

</div>

This step will use the last successful deployment within the selected resource group. If this deployent has outputs, all of them are copied to Pipelines variables by the ARM Output key:

[![screenshot-1](images/screenshot.png "Screenshot-1")](images/screenshot.png)

This outputs can then be used by default Azure DevOps Pipelines ways: ```$(same-key-as-in-arm-template)```

Usually this task is ran directly after the 'Azure Resource Group Deployment' task.

[![screenshot-1](images/screenshots-vsts-arm-outputs-1.png "Screenshot-1")](images/screenshots-vsts-arm-outputs-1.png)
[![screenshot-2](images/screenshot2.png "Screenshot-1")](images/screenshot2.png)

## How to use

Checkout the docs in the [Marketplace page](Marketplace.md)

## Help & Contact

Find me at http://case.schollaart.net/. Experiencing problems, or do you have an idea? Please let me know via [Twitter](https://twitter.com/keesschollaart) or by [mail](mailto:[email protected]). Or even better, raise an issue on [GitHub](https://github.com/keesschollaart81/vsts-arm-outputs/issues).
Find me at http://case.schollaart.net/. Experiencing problems, or do you have an idea? Please let me know via [Twitter](https://twitter.com/keesschollaart) or by [mail](mailto:[email protected]). Or even better, raise an issue on [GitHub](https://github.com/keesschollaart81/vsts-arm-outputs/issues).

## MIT License
Copyright (c) 2018 Kees Schollaart

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.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions arm-outputs/arm-outputsV2/ArmOutputParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FailBehaviour } from "./FailBehaviour";
import * as msrestAzure from 'ms-rest-azure';

export class ArmOutputParams {
public tokenCredentials: msrestAzure.ApplicationTokenCredentials;
public subscriptionId: string;
public resourceGroupName: string;
public prefix: string;
public outputNames: string[];
public whenLastDeploymentIsFailed: FailBehaviour;
public deploymentNameFilter: string;
}
4 changes: 4 additions & 0 deletions arm-outputs/arm-outputsV2/ArmOutputResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class ArmOutputResult {
public key: string;
public value: string;
}
4 changes: 4 additions & 0 deletions arm-outputs/arm-outputsV2/FailBehaviour.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum FailBehaviour {
fail,
latestSuccesful
}
77 changes: 77 additions & 0 deletions arm-outputs/arm-outputsV2/arm-outputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ArmOutputParams } from "./ArmOutputParams";
import * as r from "azure-arm-resource"
import * as mm from "micromatch"
import { FailBehaviour } from "./FailBehaviour";
import { ArmOutputResult } from "./ArmOutputResult";

export class ArmOutputs {

private resourceManagementClient: r.ResourceManagementClient.default;

constructor(private config: ArmOutputParams) {
this.resourceManagementClient = new r.ResourceManagementClient.ResourceManagementClient(this.config.tokenCredentials, this.config.subscriptionId);
}

public run = async (): Promise<ArmOutputResult[]> => {
var deployments = await this.resourceManagementClient.deployments.listByResourceGroup(this.config.resourceGroupName);
if (this.config.deploymentNameFilter) {
deployments = deployments.filter(x => mm.isMatch(x.name, this.config.deploymentNameFilter, { nocase: true }));
}
deployments.sort((a, b) => +b.properties.timestamp - +a.properties.timestamp); // descending

if (deployments.length > 0 && this.config.whenLastDeploymentIsFailed == FailBehaviour.latestSuccesful) {
if (deployments[0].properties.provisioningState != "Succeeded") {
console.debug(`Deployment '${deployments[0].name}' of Resource Group '${this.config.resourceGroupName}' did not succeed ('${deployments[0].properties.provisioningState}'), ingoring this deployment and finding latest succesful deployment`)
}
deployments = deployments.filter(x => x.properties.provisioningState == "Succeeded");
}

if (deployments.length == 0) {
throw new Error(`Deployment could not be found for Resource Group '${this.config.resourceGroupName}'.`)
}

if (deployments[0].properties.provisioningState != "Succeeded" && this.config.whenLastDeploymentIsFailed == FailBehaviour.fail) {
throw new Error(`Deployment '${deployments[0].name}' of Resource Group '${this.config.resourceGroupName}' did not succeed (status '${deployments[0].properties.provisioningState}')`);
}

var outputs = deployments[0].properties.outputs;
if (!outputs) {
throw new Error(`No output parameters could be found for the deployment '${deployments[0].name}' of Resource Group '${this.config.resourceGroupName}'."`)
}

var results: ArmOutputResult[] = [];
for (var output in outputs) {

if (this.config.outputNames.length > 0 && !this.config.outputNames.some(x => x.trim() == output)) {
console.info(`Variable '${output}' is not one of the ${this.config.outputNames.length} given key's to set, ignoring...`);
continue;
}

if (outputs[output]["type"] == "SecureString") {
console.info(`Variable '${output}' is of type SecureString, ignoring...`);
continue;
}

if (outputs[output]["type"] == "String") {
results.push({ key: `${this.config.prefix}${output}`, value: `${outputs[output]["value"]}` });
}

if (outputs[output]["type"] == "Object") {
var flatten = this.flatten(outputs[output]["value"]);
for (var propery in flatten) {
results.push({ key: `${this.config.prefix}${output}_${propery}`, value: `${flatten[propery]}` });
}
}
}
return results;
}

private flatten = (o, prefix = "", out = {}) => {
for (var name in o) {
if (o.hasOwnProperty(name)) {
typeof o[name] === "object" ? this.flatten(o[name], prefix + name + '_', out) : out[prefix + name] = o[name];
}
}
return out;
}
}
Binary file added arm-outputs/arm-outputsV2/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
91 changes: 91 additions & 0 deletions arm-outputs/arm-outputsV2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as tl from 'azure-pipelines-task-lib/task';
import * as msrestAzure from 'ms-rest-azure';
import { ArmOutputs } from './arm-outputs';
import { ArmOutputParams } from './ArmOutputParams';
import { FailBehaviour } from './FailBehaviour';
import { _checkPath } from 'azure-pipelines-task-lib/internal';
import appInsights from "./logger"

export class AzureDevOpsArmOutputsTaskHost {

public run = async (): Promise<void> => {
let start = Date.now();
let success = true;

try {
let connectedServiceNameARM: string = tl.getInput("ConnectedServiceNameARM");
var endpointAuth = tl.getEndpointAuthorization(connectedServiceNameARM, true);
var credentials = this.getCredentials(endpointAuth);
var subscriptionId = tl.getEndpointDataParameter(connectedServiceNameARM, "SubscriptionId", true);

var resourceGroupName = tl.getInput("resourceGroupName", true);
var prefix = tl.getInput("prefix", false);
var outputNames = tl.getDelimitedInput("outputNames", ",", false);
var whenLastDeploymentIsFailedString = tl.getInput("whenLastDeploymentIsFailed", true);
var whenLastDeploymentIsFailed = FailBehaviour[whenLastDeploymentIsFailedString];
var deploymentNameFilter = tl.getInput("deploymentNameFilter", false);

if (!prefix || prefix == "null") prefix = "";

const debugModeString: string = tl.getVariable('System.Debug');
const debugMode: boolean = debugModeString ? debugModeString.toLowerCase() != 'false' : false;
if (debugMode) {
tl.warning("You are running in debug mode (variable System.Debug is set to true), the values of your ARM Outputs will be printed to the log. If your deployment outputs any secret values, they will be shown, be careful (especially with public projects)!");
}

var params = <ArmOutputParams>{
tokenCredentials: credentials,
subscriptionId: subscriptionId,
resourceGroupName: resourceGroupName,
prefix: prefix,
outputNames: outputNames,
whenLastDeploymentIsFailed: whenLastDeploymentIsFailed,
deploymentNameFilter: deploymentNameFilter,
};

var armOutputs = new ArmOutputs(params);
var outputs = await armOutputs.run();
outputs.forEach(output => {
console.info(`Updating Azure Pipelines variable '${output.key}'`);
tl.setVariable(output.key, output.value, false);
});
}
catch (err) {
console.error("Unhandled exception during ARM Outputs Task", err);
try {
if (!tl.getVariable("arm-outputs-notelemetry")) {
appInsights.trackException({ exception: err });
}
success = false;
}
catch{ } // dont let AI cause exceptions
throw err;
}
finally {
try {
let duration = Date.now() - start;
if (!tl.getVariable("arm-outputs-notelemetry")) {
appInsights.trackRequest({ duration: duration, name: "ARM Outputs", url: "/", resultCode: success ? 200 : 500, success: success })
appInsights.flush();
}
}
catch{ }// dont let AI cause exceptions
}
}

private getCredentials = (endpointAuthorization: tl.EndpointAuthorization): msrestAzure.ApplicationTokenCredentials => {
var servicePrincipalId: string = endpointAuthorization.parameters["serviceprincipalid"];
var servicePrincipalKey: string = endpointAuthorization.parameters["serviceprincipalkey"];
var tenantId: string = endpointAuthorization.parameters["tenantid"];
var credentials = new msrestAzure.ApplicationTokenCredentials(servicePrincipalId, tenantId, servicePrincipalKey);

return credentials;
}
}
var azureDevOpsArmOutputsTaskHost = new AzureDevOpsArmOutputsTaskHost();

azureDevOpsArmOutputsTaskHost.run().then((result) =>
tl.setResult(tl.TaskResult.Succeeded, "")
).catch((error) =>
tl.setResult(tl.TaskResult.Failed, error)
);
19 changes: 19 additions & 0 deletions arm-outputs/arm-outputsV2/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as appInsights from 'applicationinsights';
import * as tl from 'azure-pipelines-task-lib/task';

if (!tl.getVariable("arm-outputs-notelemetry")) {
appInsights.setup("d90eab13-5c01-4e27-bbf9-376cf28bf5cf")
.setAutoDependencyCorrelation(false)
.setAutoCollectRequests(false)
.setAutoCollectPerformance(false)
.setAutoCollectExceptions(true)
.setAutoCollectDependencies(false)
.setAutoCollectConsole(false)
.setUseDiskRetryCaching(false);

appInsights.defaultClient.context.tags[appInsights.defaultClient.context.keys.applicationVersion] = "5.0.0";
appInsights.start();
}
let appInsightsClient = appInsights.defaultClient;

export default appInsightsClient
27 changes: 27 additions & 0 deletions arm-outputs/arm-outputsV2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"scripts": {
"build": "tsc -p .",
"postbuild": "webpack --config webpack.config",
"clean": "rimraf ./dist && rimraf ./*.vsix"
},
"devDependencies": {
"@types/node": "^10.12.10",
"@types/micromatch": "3.1.0",
"@types/applicationinsights": "0.20.0",
"rimraf": "^2.6.2",
"typescript": "3.0.3",
"webpack": "^4.26.1",
"webpack-cli": "^3.1.2",
"copy-webpack-plugin": "4.6.0"
},
"dependencies": {
"applicationinsights": "^1.0.8",
"azure-arm-resource": "7.2.1",
"azure-pipelines-task-lib": "^2.7.5",
"micromatch": "3.1.10",
"ms-rest-azure": "2.5.9"
},
"name": "caseonline.azuredevops.arm-outputs",
"private": true,
"version": "1.0.0"
}
Loading

0 comments on commit 4e6f448

Please sign in to comment.