Skip to content

Commit

Permalink
add 'iidy demo' for running packaged demo scripts
Browse files Browse the repository at this point in the history
- updated the hello-world example to follow this format
- this required exporting some types from index.ts. Further
  refactoring of that file is coming.
- documentation for this feature is coming
  • Loading branch information
tavisrudd committed Aug 8, 2017
1 parent f3b77fd commit 1391b88
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 56 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ $(TESTS_STATEFILE) : $(BUILD_ARTIFACTS) $(EXAMPLE_FILES)
cp Makefile.test dist/docker/Makefile
cp -a examples dist/docker/
docker build -t iidy-test dist/docker
docker run --rm -v ~/.aws/:/root/.aws/ iidy-test make test
docker run --rm -it -v ~/.aws/:/root/.aws/ iidy-test make test
touch $(TESTS_STATEFILE)

# ifeq ($(shell uname),Darwin)
Expand Down
16 changes: 0 additions & 16 deletions examples/hello-world/.wrap-bash

This file was deleted.

32 changes: 5 additions & 27 deletions examples/hello-world/Makefile
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
.PHONY : test
.PHONY : demo

.DEFAULT_GOAL := test
.DEFAULT_GOAL := demo

test:
@mkdir -p demo-tmp/
@iidy render stack-args.yaml > demo-tmp/stack-args.yaml
@grep StackName demo-tmp/stack-args.yaml | cut -f 2 -d':' > demo-tmp/stack-name
@cp cfn-template.yaml demo-tmp/
test: demo

@./.wrap-bash iidy help | grep --color=always -E 'create-stack .*$$|$$'
@sleep 2
@./.wrap-bash cat demo-tmp/stack-args.yaml

@./.wrap-bash iidy create-stack demo-tmp/stack-args.yaml

@./.wrap-bash 'iidy list-stacks --region us-west-2 --profile sandbox | grep $$(cat demo-tmp/stack-name)'

@./.wrap-bash grep owner demo-tmp/stack-args.yaml
@./.wrap-bash sed -i.bak s/your-name/Tavis/ demo-tmp/stack-args.yaml
@./.wrap-bash grep owner demo-tmp/stack-args.yaml

@./.wrap-bash iidy help | grep --color=always -E 'update-stack .*$$|$$'
@./.wrap-bash iidy update-stack demo-tmp/stack-args.yaml

@./.wrap-bash iidy help | grep --color=always -E 'delete-stack .*$$|$$'
@./.wrap-bash iidy delete-stack --region us-west-2 --profile sandbox --yes $$(cat demo-tmp/stack-name)

@rm -r demo-tmp
@echo "\033[91m Thanks for watching! \033[0m"
demo:
iidy demo demo-script.yaml
2 changes: 1 addition & 1 deletion examples/hello-world/cfn-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ Resources:
HelloWorld:
Type: AWS::SNS::Topic
Properties:
DisplayName: !Sub "Hello ${Name}}"
DisplayName: !Sub "Hello ${Name}"
110 changes: 110 additions & 0 deletions examples/hello-world/demo-script.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env iidy demo
$imports:
nameSuffix: random:dashed-name
StackName: "literal:iidy-demo-{{ nameSuffix }}"

files:
cfn-template.yaml: |-
Parameters:
Name:
Type: String
Resources:
HelloWorld:
Type: AWS::SNS::Topic
Properties:
DisplayName: !Sub "Hello ${Name}"
.broken-cfn-template.yaml: |-
Parameters:
Name:
Type: String
Resources:
HelloWorld:
Type: AWS::SNS::Topic
Properties:
DisplayName: !Sub "Hello ${Name}"
Foo: 1234
stack-args.yaml: |-
StackName: "{{ StackName }}" # auto generated name
Template: ./cfn-template.yaml
Profile: sandbox
Region: us-west-2
Parameters:
Name: world
Tags:
owner: your-name
project: iidy-demo
environment: development
lifetime: short
demo:
- setenv:
AWS_REGION: us-west-2
AWS_PROFILE: sandbox
"BASH_FUNC_highlight%%": |
() {
GREP_COLOR="1;32" grep --color -E "$1|$";
}
- banner: In this *live* demo we will create, update, and then delete a simple stack.
- sleep: 3
- ls .
- banner: "We'll use these 2 files throughout the demo."
- sleep: 3
- banner: "'cfn-template.yaml' is a very simple CloudFormation Template. It creates an SNS Topic."
- cat cfn-template.yaml
- sleep: 3

- banner: |-
'stack-args.yaml' specifies the arguments for CloudFormation operations, including 'Template: cfn-template.yaml'.
- sleep: 1
- cat stack-args.yaml
- sleep: 4

- banner: "Let's review the help docs for 'iidy create-stack' and then use it."
- sleep: 1
- iidy help | highlight 'create-stack .*'
- sleep: 2
- iidy create-stack stack-args.yaml
- sleep: 1

- banner: "We can see it with `iidy list-stacks` or `iidy describe-stack {{ StackName }}`."
- "iidy list-stacks | grep {{ StackName }}"
- sleep: 1


- banner: "Let's change the 'owner' tag and then update the stack."
- sleep: 2

- highlight 'owner.*' < stack-args.yaml
- "sed -i.bak s/your-name/${USER:-demouser}/ stack-args.yaml"
- highlight 'owner.*' < stack-args.yaml
- sleep: 2

- banner: "Now, we update-stack."
- sleep: 1
- iidy update-stack stack-args.yaml

- banner: "Let's break the template on purpose to see what an error looks like."
- sleep: 3
- mv cfn-template.yaml cfn-template.yaml.bak
- mv .broken-cfn-template.yaml cfn-template.yaml
- iidy update-stack stack-args.yaml || true

- banner: "We can now see the full stack history via 'describe-stack'."
- sleep: 2
- iidy describe-stack {{ StackName }}
- sleep: 2

#- mv cfn-template.yaml.bak cfn-template.yaml

- sleep: 2
- banner: "We've created and updated the stack. Time to delete it."
- sleep: 1
- "iidy delete-stack --region us-west-2 --profile sandbox --yes {{ StackName }}"

- banner: "That's all folks. Thanks for watching!"
29 changes: 24 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ interface Commands {
createCreationChangesetMain: Handler
executeChangesetMain: Handler

renderMain: Handler
renderMain: Handler,
demoMain: Handler

};

Expand All @@ -59,8 +60,17 @@ interface Commands {
// estimateCost: index.estimateCost
// }

const lazyLoad = (fnname: keyof Commands): Handler =>
(args) => require('./index')[fnname](args);
type LazyLoadModules = './index' | './demo'

const lazyLoad = (fnname: keyof Commands, modName: LazyLoadModules='./index'): Handler =>
(args) => {
// note, the requires must be literal for `pkg` to find the modules to include
if (modName === './index') {
return require('./index')[fnname](args);
} else if (modName === './demo') {
return require('./demo')[fnname](args);
}
}

const lazy: Commands = {
createStackMain: lazyLoad('createStackMain'),
Expand All @@ -77,7 +87,9 @@ const lazy: Commands = {

renderMain: lazyLoad('renderMain'),

estimateCost: lazyLoad('estimateCost')
estimateCost: lazyLoad('estimateCost'),

demoMain: lazyLoad('demoMain', './demo'),

// TODO init-stack-args command to create a stack-args.yaml
// TODO example command pull down an examples dir
Expand Down Expand Up @@ -218,10 +230,17 @@ export async function main() {
.strict(),
wrapMainHandler(commands.renderMain))

.command(
'demo <demoscript>',
'run a demo script',
(yargs) => yargs
.demand(0, 0)
.strict(),
wrapMainHandler(commands.demoMain))

.option('region', {
type: 'string', default: null,
group: 'AWS Options',
//choices: AWSRegions,
description: 'AWS region'})
.option('profile', {
type: 'string', default: null,
Expand Down
94 changes: 94 additions & 0 deletions src/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as fs from 'fs';
import * as pathmod from 'path';
import * as child_process from 'child_process';

import * as _ from 'lodash';
import {Arguments} from 'yargs';
import * as tmp from 'tmp';
import * as cli from 'cli-color';


import timeout from './timeout';
import { transform } from './index';
import * as yaml from './yaml';

export async function demoMain(argv: Arguments): Promise<number> {
const demoFile = argv.demoscript;
const script0 = yaml.loadString(fs.readFileSync(demoFile), demoFile);
const script: any = await transform(script0, demoFile);
let tmpdir = tmp.dirSync();
try {
for (let fp in script.files) {
if (pathmod.isAbsolute(fp)) {
throw new Error(`Illegal path ${fp}. Must be relative.`);
}
const fullpath = pathmod.resolve(tmpdir.name, fp)
if (fp.indexOf(pathmod.sep) !== -1) {
fs.mkdirSync(pathmod.dirname(fullpath))
}
fs.writeFileSync(fullpath, script.files[fp]);
}
let bashEnv = _.merge({}, process.env);
const exec = (command: string, captureAs?: string) => {
let res = child_process.spawnSync(
command,
{shell: '/bin/bash',
cwd: tmpdir.name,
env: bashEnv,
stdio: [0,1,2] })
if (res.status !== 0) {
// TODO improve this
throw new Error(`command failed: ${command}. exitcode=${res.status}`)
}
console.log();

}
async function printComm(command: string) {
process.stdout.write(cli.red('Shell Prompt > '));
process.stdout.write('\x1b[37m')
for (let char of command) {
process.stdout.write(char);
await timeout(50);
}
process.stdout.write('\x1b[0m')
console.log();
}
// prompt:, capture:
for (let command of script.demo) {
if (typeof command === 'string') {
await printComm(command);
exec(command)
} else if (command.do) {
await printComm(command.show);
exec(command.do)
} else if (command.setenv) {
_.extend(bashEnv, command.setenv)
} else if (command.sleep) {
await timeout(command.sleep * 1000);
} else if (command.silent) {
// logger
exec(command.silent)
} else if (command.banner) {
console.log()
const tty: any = process.stdout;
const bannerFormat = cli.bgXterm(236);
console.log(bannerFormat(' '.repeat(tty.columns)));
for (const ln of command.banner.split('\n')) {
const pad = (tty.columns - ln.length);
console.log(bannerFormat(cli.bold(cli.yellow(' '.repeat(2) + ln + ' '.repeat(pad-2)))));
}
console.log(bannerFormat(' '.repeat(tty.columns)));
console.log()
} else {
console.log(command);
}
}

} catch (e) {
throw e;
} finally {
// TODO check result
child_process.execSync(`rm -r ${tmpdir.name}`, {cwd: tmpdir.name })
}
return 0;
}
12 changes: 6 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ export type ImportRecord = {
sha256Digest: SHA256Digest,
};

type $envValues = {[key: string]: any} // TODO might need more general value type
export type $envValues = {[key: string]: any} // TODO might need more general value type

type $param = {
export type $param = {
Name: string,
Default?: any,
Type?: any,
Expand All @@ -81,7 +81,7 @@ type $param = {
};

// TODO find a better name for this Interface
interface ExtendedCfnDoc extends CfnDoc {
export interface ExtendedCfnDoc extends CfnDoc {
$imports?: {[key: string]: any},
$params?: Array<$param>,
$location: string,
Expand Down Expand Up @@ -653,7 +653,7 @@ function visitNode(node: any, path: string, env: Env): any {
};


async function transform(root: ExtendedCfnDoc, rootDocLocation: ImportLocation): Promise<CfnDoc> {
export async function transform(root: ExtendedCfnDoc, rootDocLocation: ImportLocation): Promise<CfnDoc> {
const isCFNDoc = root.AWSTemplateFormatVersion || root.Resources;
const accumulatedImports: ImportRecord[] = [];
await loadImports(root, rootDocLocation, accumulatedImports);
Expand Down Expand Up @@ -744,6 +744,7 @@ import * as wrapAnsi from 'wrap-ansi';
import * as ora from 'ora';

import {AWSRegion} from './aws-regions';
import timeout from './timeout';

async function configureAWS(profile?: string, region?: AWSRegion) {
process.env.AWS_SDK_LOAD_CONFIG = 'true'; // see https://github.com/aws/aws-sdk-js/pull/1391
Expand Down Expand Up @@ -886,8 +887,6 @@ const objectToCFNParams =

type ExitCode = number;

const timeout = (ms:number) => new Promise(res => setTimeout(res, ms));

async function showStackEvents(StackName: string, limit=10) {
let evs = (await getAllStackEvents(StackName));
evs = _.sortBy(evs, (ev) => ev.Timestamp)
Expand Down Expand Up @@ -1040,6 +1039,7 @@ async function summarizeCompletedStackOperation(StackName: string): Promise<aws.
export type CfnOperation = 'CREATE_STACK' | 'UPDATE_STACK' | 'CREATE_CHANGESET' | 'EXECUTE_CHANGESET' | 'ESTIMATE_COST';

function runCommandSet(commands: string[]) {
// TODO: merge this with the demo script functionality
for (let command of commands) {
console.log('Running command:\n' + cli.blackBright(command))
const result = child_process.spawnSync(command, [], {shell: true});
Expand Down
2 changes: 2 additions & 0 deletions src/timeout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const timeout = (ms:number): Promise<void> => new Promise(res => setTimeout(res, ms));
export default timeout;

0 comments on commit 1391b88

Please sign in to comment.