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

Add Support for AWS Service Connections #149

Open
wants to merge 6 commits into
base: master
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
53 changes: 53 additions & 0 deletions buildAndReleaseTask/awsServiceEndpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2016-2023, Pulumi Corporation. All rights reserved.

import * as tl from "azure-pipelines-task-lib/task";
zbuchheit marked this conversation as resolved.
Show resolved Hide resolved

export interface IAWSServiceEndpoint {
accessKeyId: string;
secretAccessKey: string;
sessionToken: string;
roleArn: string;
region: string;
}

export function getAWSServiceEndpoint(
connectedServiceName: string
): IAWSServiceEndpoint | undefined {
const endpointAuthorization = tl.getEndpointAuthorization(
connectedServiceName,
true
);
if (!endpointAuthorization) {
return undefined;
}

const endpoint = {
accessKeyId: tl.getEndpointAuthorizationParameter(
connectedServiceName,
"username",
false
),
secretAccessKey: tl.getEndpointAuthorizationParameter(
connectedServiceName,
"password",
false
),
sessionToken: tl.getEndpointAuthorizationParameter(
connectedServiceName,
"sessionToken",
true
),
roleArn: tl.getEndpointAuthorizationParameter(
connectedServiceName,
"assumeRoleArn",
true
),
region: tl.getEndpointAuthorizationParameter(
connectedServiceName,
"region",
true
),
} as IAWSServiceEndpoint;

return endpoint;
}
1 change: 1 addition & 0 deletions buildAndReleaseTask/models.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare module "models" {
// and assert the value at the place of
// its use.
azureSubscription?: string;
awsServiceConnection?: string;
command?: string;
loginArgs?: string;
args?: string;
Expand Down
34 changes: 34 additions & 0 deletions buildAndReleaseTask/pulumi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { join as pathJoin } from "path";
import * as tl from "azure-pipelines-task-lib/task";
import * as tr from "azure-pipelines-task-lib/toolrunner";

import { getAWSServiceEndpoint } from "./awsServiceEndpoint";
import { getServiceEndpoint } from "./serviceEndpoint";
import { INSTALLED_PULUMI_VERSION, PULUMI_ACCESS_TOKEN } from "./vars";

Expand Down Expand Up @@ -243,6 +244,36 @@ function tryGetAzureEnvVarsFromServiceEndpoint(): IEnvMap {
AZURE_TENANT_ID: serviceEndpoint.tenantId,
};
}
/**
* If the `serviceEndpoint` param is not `undefined`, then
* this function returns an env var map with the `AWS_*`
* env vars.
*/
function tryGetAWSEnvVarsFromServiceEndpoint(): IEnvMap {
const connectedServiceName = tl.getInput("awsServiceConnection", false);
if (!connectedServiceName) {
return {};
}
tl.debug(tl.loc("Debug_AWSServiceEndpointName", connectedServiceName));

const awsServiceEndpoint = getAWSServiceEndpoint(connectedServiceName);
if (awsServiceEndpoint) {
tl.debug(
`Service endpoint retrieved with access key ID ${awsServiceEndpoint.accessKeyId}`
);
}
if (!awsServiceEndpoint) {
return {};
}

return {
AWS_ACCESS_KEY_ID: awsServiceEndpoint.accessKeyId,
AWS_SECRET_ACCESS_KEY: awsServiceEndpoint.secretAccessKey,
AWS_SESSION_TOKEN: awsServiceEndpoint.sessionToken,
AWS_ROLE_ARN: awsServiceEndpoint.roleArn,
AWS_REGION: awsServiceEndpoint.region,
};
}

/**
* Returns all variables available to this task.
Expand Down Expand Up @@ -282,7 +313,9 @@ export async function runPulumi(taskConfig: TaskConfig) {
const toolPath = tl.which("pulumi");
const agentEnvVars = tryGetEnvVars();
const azureServiceEndpointEnvVars = tryGetAzureEnvVarsFromServiceEndpoint();
const awsServiceEndpointEnvVars = tryGetAWSEnvVarsFromServiceEndpoint();
const loginEnvVars = {
...awsServiceEndpointEnvVars,
...azureServiceEndpointEnvVars,
...agentEnvVars,
...processEnv,
Expand Down Expand Up @@ -329,6 +362,7 @@ export async function runPulumi(taskConfig: TaskConfig) {
}

const envVars: IEnvMap = {
...awsServiceEndpointEnvVars,
...azureServiceEndpointEnvVars,
...agentEnvVars,
...processEnv,
Expand Down
9 changes: 9 additions & 0 deletions buildAndReleaseTask/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
"required": "false",
"helpMarkDown": "Select the Azure Resource Manager subscription for the deployment. If you do not provide a service connection, ensure that you have configured your cloud provider by following the setup instructions for your respective [cloud provider](https://www.pulumi.com/docs/intro/cloud-providers/)."
},
{
"name": "awsServiceConnection",
"type": "connectedService:AWS",
"label": "AWS Service Connection",
"defaultValue": "",
"required": "false",
"helpMarkDown": "Select the AWS Service Connection for the deployment. If you do not provide a service connection, ensure that you have configured your cloud provider by following the setup instructions for your respective [cloud provider](https://www.pulumi.com/docs/intro/cloud-providers/)."
},
{
"name": "command",
"type": "pickList",
Expand Down Expand Up @@ -147,6 +155,7 @@
"Debug_Login": "Logging in to Pulumi CLI.",
"Debug_PrintingVersion": "Printing version.",
"Debug_ServiceEndpointName": "Using service endpoint '%s'.",
"Debug_AWSServiceEndpointName": "Using AWS service endpoint '%s'.",
"Debug_TempDirectoryNotSet": "agent.tempdirectory not set. Using $HOME/temp.",
"Debug_LatestPulumiVersion": "Latest pulumi version is '%s'.",
"Debug_CachingPulumiToHome": "Caching pulumi CLI to path '%s'.",
Expand Down
1 change: 1 addition & 0 deletions buildAndReleaseTask/taskConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { TaskConfig } from "models";
export function getTaskConfig(): TaskConfig {
return {
azureSubscription: tl.getInput("azureSubscription"),
awsServiceConnection: tl.getInput("awsServiceConnection"),
command: tl.getInput("command"),
loginArgs: tl.getInput("loginArgs"),
args: tl.getInput("args"),
Expand Down
8 changes: 8 additions & 0 deletions buildAndReleaseTask/tests/envvars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ tmr.registerMock("./serviceEndpoint", {
},
});

tmr.registerMock("./awsServiceEndpoint", {
getAWSServiceEndpoint: (_: string) => {
// Returning undefined to test that our task extension isn't requiring
// an AWS Endpoint.
return undefined;
},
});

tmr.registerMock("./version", {
getLatestPulumiVersion: (): Promise<string> => {
return Promise.resolve(latestPulumiVersion);
Expand Down
8 changes: 8 additions & 0 deletions buildAndReleaseTask/tests/envvars_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ tmr.registerMock("./serviceEndpoint", {
},
});

tmr.registerMock("./awsServiceEndpoint", {
getAWSServiceEndpoint: (_: string) => {
// Returning undefined to test that our task extension isn't requiring
// an AWS Endpoint.
return undefined;
},
});

tmr.registerMock("./version", {
getLatestPulumiVersion: (): Promise<string> => {
return Promise.resolve(latestPulumiVersion);
Expand Down
14 changes: 14 additions & 0 deletions buildAndReleaseTask/tests/success.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as ma from "azure-pipelines-task-lib/mock-answer";
import * as tmrm from "azure-pipelines-task-lib/mock-run";
import * as path from "path";

import { IAWSServiceEndpoint } from "../awsServiceEndpoint";
import { IServiceEndpoint } from "../serviceEndpoint";

const taskPath = path.join(__dirname, "..", "index.js");
Expand All @@ -24,6 +25,7 @@ process.env["HOME"] = "/fake/home";
tmr.setVariableName("PULUMI_ACCESS_TOKEN", "fake-access-token", true);
// Set the mock inputs for the task.
tmr.setInput("azureSubscription", "fake-subscription-id");
tmr.setInput("awsServiceConnection", "fake-aws-service-endpoint");
tmr.setInput("command", "preview");
tmr.setInput("cwd", "dir/");
tmr.setInput("stack", "myOrg/project/dev");
Expand All @@ -40,6 +42,18 @@ tmr.registerMock("./serviceEndpoint", {
},
});

tmr.registerMock("./awsServiceEndpoint", {
getAWSServiceEndpoint: (_: string): IAWSServiceEndpoint => {
return {
accessKeyId: "fake-access-key-id",
secretAccessKey: "fake-secret-access-key",
sessionToken: "fake-session-token",
roleArn: "fake-role-arn",
region: "fake-region",
}
},
});

tmr.registerMock("./version", {
getLatestPulumiVersion: (): Promise<string> => {
return Promise.resolve(latestPulumiVersion);
Expand Down
1 change: 1 addition & 0 deletions integrationtest.azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:

- task: Test Pulumi@1
inputs:
awsServiceConnection: "$(Example.AWS.ServiceConnectName)"
command: "preview"
cwd: "./examples/aws"
stack: "$(Example.AWS.StackName)"
Expand Down
8 changes: 4 additions & 4 deletions overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Read on to learn how you can get started quickly.
- You must have an active Azure DevOps account and an organization. Create a new account at https://dev.azure.com.
- Install this extension to your organization. To do this, you must be an admin of the organization.
- Once installed, you may have to have your admin make the extension available to you or your project.
- You'll need an Azure subscription if you plan on creating resources in Azure. Create a [service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops) for the Azure Subscription in your DevOps project.
- **Note**: At this time, only a `Service Principal Authentication` based service connection can be used with this extension.
- The name of this service connection is what you will use in the Pulumi task for the input `azureSubscription` if you are using the YAML configuration.
- You'll need an Azure subscription if you plan on creating resources in Azure. Create a [service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops) for the Azure Subscription or AWS Authentication in your DevOps project.
- **Note**: For Azure Service Connections, only `Service Principal Authentication` work with this extension.
- The name of the relevant service connection is what you will use in the Pulumi task for the input `azureSubscription` or `awsServiceConnection` if you are using the YAML configuration.

## Quickstart

Expand Down Expand Up @@ -192,7 +192,7 @@ Try to uninstall the extension, and then re-install it. Most of the times this r

Pulumi supports several [cloud providers](https://www.pulumi.com/docs/intro/cloud-providers/), including [Kubernetes](https://www.pulumi.com/docs/intro/cloud-providers/kubernetes/). You can deploy to any cloud provider that Pulumi supports using this task extension, by simply setting the required environment variables as part of each cloud provider's setup, as your Pipeline's build variable.

For example, in order to deploy to [AWS](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/#environment-variables), simply set the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` env vars either as pipeline variables or in a variable group that is linked to your pipeline.
For example, in order to deploy to [AWS](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/#environment-variables), you may use the AWS Service Connection or by simply set the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` env vars either as pipeline variables or in a variable group that is linked to your pipeline.

## Troubleshooting

Expand Down