Skip to content

Commit

Permalink
AWS ECS with Container Instances and Automation API (pulumi#845)
Browse files Browse the repository at this point in the history
Co-authored-by: Justin Van Patten <[email protected]>
  • Loading branch information
MitchellGerdisch and justinvp committed Jan 7, 2021
1 parent 1990689 commit 248d41e
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 0 deletions.
104 changes: 104 additions & 0 deletions aws-py-ecs-instances-autoapi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port
82 changes: 82 additions & 0 deletions aws-py-ecs-instances-autoapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# AWS ECS with Container Instances and Delete Orchestration

This example demonstrates three use-cases:

- AWS ECS using Container Instances (Python): A Python Pulumi program that stands up a custom AWS ECS cluster that uses instances instead of fargate for the infrastructure.
- Automation API Orchestration: Destroying this stack without any sort of orchestration will fail due to this issue in the underlying provider: https://github.com/hashicorp/terraform-provider-aws/issues/4852. So, Automation API to the rescue. By orchestrating sizing of the autoscaling group to 0 before the destroy, the destroy is able to complete as expected.
- Automation API cross-language support: Although the automation logic is written in TypeScript, the ECS cluster stack is written in Python.

## Project Structure

### `/py-ecs-instance`:

This is a Pulumi project/stack python program that deploys the following:

- ECS Cluster using "container instances" instead of Fargate.
- An nginx "hello world" test container and related load balancer and networking.
One can change to this directory and run `pulumi up` and deploy the stack just as would be done with any Pulumi project ...

But wait, there's more ...

### `/automation`

This directory contains the automation api code (`index.ts`) that handles deploying and, more importantly, orchestrating the deletion of the stack to avoid a dependency constraint.

## How to Use

To run this example you'll need a few pre-reqs:

1. A Pulumi CLI installation ([v2.15.6](https://www.pulumi.com/docs/get-started/install/versions/) or later)
2. Python 3.6+
3. The AWS CLI, with appropriate credentials.

To run our automation program we just `cd` to the `automation` directory and use `yarn` to run the automation api code.

```shell
$ yarn install
$ yarn start
yarn run v1.19.1
$ ./node_modules/ts-node/dist/bin.js index.ts
successfully initialized stack
setting up config
config set
refreshing stack...
Refreshing (dev)
...
refresh complete
updating stack...
Updating (dev)
...

update summary:
{
"same": 0,
"update": 16
}
website url: http:https://load-balancer-xxxxxxxxx.us-east-1.elb.amazonaws.com
```

To destroy the stack, we run the automation program with an additional `destroy` argument:

```shell
$ yarn start destroy
yarn run v1.19.1
$ ./node_modules/ts-node/dist/bin.js index.ts destroy
successfully initialized stack
setting up config
config set
refreshing stack...
Refreshing (dev)
destroying stack ...
Destroying (dev)
...
@ Destroying ...
...
Resources:
- 16 deleted

The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained.
If you want to remove the stack completely, run 'pulumi stack rm dev'.

stack destroy complete
```
56 changes: 56 additions & 0 deletions aws-py-ecs-instances-autoapi/automation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { LocalProgramArgs, LocalWorkspace } from "@pulumi/pulumi/x/automation";
import * as upath from "upath";

const process = require("process");

const args = process.argv.slice(2);
let destroy = false;
if (args.length > 0 && args[0]) {
destroy = args[0] === "destroy";
}

const run = async () => {


// Create our stack using a local program
// in the ../fargate directory
const args: LocalProgramArgs = {
stackName: "dev",
workDir: upath.joinSafe(__dirname, "..", "py-ecs-instance"),
};
const asgSize = "1"

// create (or select if one already exists) a stack that uses our local program
const stack = await LocalWorkspace.createOrSelectStack(args);

console.info("successfully initialized stack");
console.info("setting up config");
await stack.setConfig("aws:region", { value: "us-east-1" });
await stack.setConfig("cfg:autoscalingGroupSize", { value: asgSize });
console.info("config set");
console.info("refreshing stack...");
await stack.refresh({ onOutput: console.info });
console.info("refresh complete");

if (destroy) {
// The autoscaling group is sized down to 0 so that there are no instances running.
// This allows the cluster to be deleted. Otherwise, the cluster delete will fail due to the existence of instances.
console.info("resizing autoscaling group size to 0 before destroying the stack ...")
await stack.setConfig("cfg:autoscalingGroupSize", { value: "0" });
await stack.up({ onOutput: console.info });
await stack.setConfig("cfg:autoscalingGroupSize", { value: asgSize });

console.info("destroying stack...");
await stack.destroy({onOutput: console.info});

console.info("stack destroy complete");
process.exit(0);
}

console.info("updating stack...");
const upRes = await stack.up({ onOutput: console.info });
console.log(`update summary: \n${JSON.stringify(upRes.summary.resourceChanges, null, 4)}`);
console.log(`website url: ${upRes.outputs.app_url.value}`);
};

run().catch(err => console.log(err));
18 changes: 18 additions & 0 deletions aws-py-ecs-instances-autoapi/automation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "automation",
"version": "1.0.0",
"main": "index.ts",
"repository": "github.com/pulumi/automation-api-examples",
"author": "EvanBoyle",
"license": "MIT",
"dependencies": {
"@pulumi/aws": "^3.6.1",
"@pulumi/pulumi": "^2.12.0",
"@types/node": "^14.14.20",
"ts-node": "^9.0.0",
"upath": "^2.0.0"
},
"scripts": {
"start": "./node_modules/ts-node/dist/bin.js index.ts"
}
}
6 changes: 6 additions & 0 deletions aws-py-ecs-instances-autoapi/py-ecs-instance/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: aws-py-ecs-instances-autoapi
description: A container running in AWS ECS Container Instance, using Python infrastructure as code
runtime:
name: python
options:
virtualenv: venv
Loading

0 comments on commit 248d41e

Please sign in to comment.