forked from pulumi/deploy-demos
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a Time-To-Live Stacks example (pulumi#14)
- Loading branch information
Showing
7 changed files
with
2,906 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
config: | ||
aws:region: us-west-2 | ||
ttlstacks:githubToken: | ||
secure: AAABAHtQA1KxZ3dqKUIh11TNyfas+vJxfASDzlpbuwRpP6UCnHONDEvd/P2V8kJu5nAVjRhYyPBOmC06tyW+tL9DcL4lgXjS | ||
ttlstacks:pulumiAccessToken: | ||
secure: AAABALPgH6Vxjw9qEL55mHNqu2QzAhVsGSQZo5bYGtMGBg18Ukth+oq1CQl1ZChTaPdJiGfdHbo8DyAG/SVhYNahUgfdCxKXCZUzsg== |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
name: ttlstacks | ||
description: Clean up resources that are past their TTL | ||
runtime: nodejs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# TTL Stacks | ||
|
||
> Automatically destroy stacks that are older than their TTL. | ||
Deploy automation with Pulumi that uses Pulumi Deploy to automatically trigger stack destroy on any stacks tagged with `pulumi:ttl` stack tag that are past the configured TTL value. | ||
|
||
This stack deploys a cron job to AWS that runs every 30 minutes to identify stacks that need to be destroyed. | ||
|
||
## Setup | ||
|
||
1. Install prerequisites: | ||
|
||
```bash | ||
npm install | ||
``` | ||
|
||
1. Create a new Pulumi stack, which is an isolated deployment target for this example: | ||
|
||
```bash | ||
pulumi stack init | ||
``` | ||
|
||
1. Set required configuration | ||
|
||
Using the pulumi deployment API requires a [pulumi access token](https://www.pulumi.com/docs/intro/pulumi-service/accounts/#access-tokens). | ||
If using with private GitHub repos, an optional GitHub access token can also be provided. | ||
|
||
```bash | ||
pulumi config set aws:region us-west-2 | ||
pulumi config set --secret pulumiAccessToken xxxxxxxxxxxxxxxxx # your Pulumi access token | ||
pulumi config set --secret githubToken xxxxxxxxxxxxxxxxx # (optional) your GitHub access token | ||
``` | ||
|
||
1. Execute the Pulumi program: | ||
|
||
```bash | ||
pulumi up | ||
``` | ||
|
||
1. Tag a stack. | ||
|
||
You can now add the `pulumi:ttl` tag to any stack. This can be done either on the stack page at https://app.pulumi.com/<org>/<project>/<stack>, or via `pulumi stack tag set pulumi:ttl 24`. | ||
The value set for the tag should be the number of hours the stack should live after it was created. | ||
|
||
1. When you are done, cleanup: | ||
|
||
```bash | ||
pulumi destroy | ||
``` | ||
|
||
(or just tag the stack with `pulumi:ttl` set to 0 to cause the stack to destroy itself! :-)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import * as pulumi from "@pulumi/pulumi"; | ||
import * as aws from "@pulumi/aws"; | ||
import fetch from "node-fetch"; | ||
|
||
const config = new pulumi.Config(); | ||
const pat = config.requireSecret("pulumiAccessToken"); | ||
const githubtoken = config.getSecret("githubToken"); | ||
|
||
async function postDeployment(operation: string, stack: string, repoURL: string, branch: string, preRunCommands?: string[]) { | ||
const res = await fetch(`https://api.pulumi.com/api/preview/${stack}/deployments`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
"Authorization": `token ${pat.get()}`, | ||
}, | ||
body: JSON.stringify({ | ||
sourceContext: { | ||
git: { | ||
repoURL, | ||
branch, | ||
gitAuth: githubtoken ? { accessToken: `${githubtoken.get()}` } : undefined, | ||
}, | ||
}, | ||
operationContext: { | ||
operation, | ||
environmentVariables: { | ||
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, | ||
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, | ||
AWS_SESSION_TOKEN: process.env.AWS_SESSION_TOKEN, | ||
}, | ||
preRunCommands, | ||
}, | ||
}), | ||
}); | ||
console.debug(`status=${res.status}`); | ||
return res.json(); | ||
} | ||
|
||
async function getPulumiAPI(path: string) { | ||
const res = await fetch(`https://api.pulumi.com/api${path}`, { | ||
method: "GET", | ||
headers: { | ||
"Content-Type": "application/json", | ||
"Authorization": `token ${pat.get()}`, | ||
}, | ||
}); | ||
return res.json(); | ||
} | ||
|
||
const subscription = aws.cloudwatch.onSchedule("get-tags", "rate(30 minutes)", async (ev, ctx) => { | ||
const stacksToDestroy: Record<string, { ttl: string; runtime: string }> = {}; | ||
const resp = await getPulumiAPI(`/user/stacks?maxResults=2000&tagName=pulumi:ttl`); | ||
for (const stackid of resp.stacks) { | ||
const stack = await getPulumiAPI(`/stacks/${stackid.orgName}/${stackid.projectName}/${stackid.stackName}`); | ||
const fqsn = `${stack.orgName}/${stack.projectName}/${stack.stackName}`; | ||
for (const tag in stack.tags) { | ||
if (tag == "pulumi:ttl") { | ||
let tagValue = +(stack.tags[tag]); | ||
if (isNaN(tagValue)) { | ||
tagValue = 24; | ||
} | ||
const ttlSeconds = tagValue * 60 * 60; | ||
const timeSinceUpdateSeconds = Math.floor(+new Date() / 1000) - (stackid.lastUpdate ?? 0); | ||
const timeLeft = ttlSeconds - timeSinceUpdateSeconds; | ||
console.log(`stack '${fqsn}' with TTL tag '${stack.tags[tag]}': ${timeLeft} seconds left`); | ||
if (timeLeft <= 0) { | ||
stacksToDestroy[fqsn] = { | ||
ttl: stack.tags[tag], | ||
runtime: stack.tags["pulumi:runtime"], | ||
}; | ||
console.log(`registering stack ${fqsn} for deletion`) | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
for (const fqsn in stacksToDestroy) { | ||
console.log(`destroying stack ${fqsn}...`) | ||
const stackDetails = stacksToDestroy[fqsn]; | ||
await postDeployment("destroy", fqsn, "https://github.com/lukehoban/blank", "refs/heads/main", [ | ||
// Try to remove the stack if it has no resources. | ||
// TODO: Ideally this would be a post-run command or a `--rm` flag on the destroy operation. | ||
`pulumi stack rm -y -s ${fqsn} || true`, | ||
`echo "name: ${fqsn.split("/")[1]}" > Pulumi.yaml`, | ||
`echo "runtime: ${stackDetails.runtime}" >> Pulumi.yaml`, | ||
`pulumi config refresh -s ${fqsn}`, | ||
]); | ||
console.log(`destroyed stack ${fqsn}.`) | ||
} | ||
}); | ||
|
||
export const functionArn = subscription.func.name; |
Oops, something went wrong.