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

feat(aws-ec2): add support for CloudFormation::Init #792

Closed
wants to merge 4 commits into from

Conversation

rix0rrr
Copy link
Contributor

@rix0rrr rix0rrr commented Sep 27, 2018

Start of an API to manage UserData in a more principled way, hide the details of resource signaling and support CloudFormation Init.

Soliciting opinions from @moofish32 and @taichi to see if this solves their use cases--I'm not 100% sure I have all the details right.

Proposed to fix #623 and #777.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.

Make a new construct to manager User Data, which should make it easier
to apply the features correctly.

Fixes #623 and 777.
@eladb
Copy link
Contributor

eladb commented Sep 27, 2018

@rix0rrr I merged and aligned this from master and converted indentation to 2 spaces. Enjoy!

@eladb
Copy link
Contributor

eladb commented Sep 27, 2018

@mindstorms6 I believe you had some nifty ideas around this, can you take a look?

Copy link
Contributor

@eladb eladb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README?

this.userData = new ec2.UserData(this, 'UserData', {
os: machineImage.os,
defaultSignalResource: this.autoScalingGroup
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation

@@ -290,9 +297,11 @@ export class AutoScalingGroup extends cdk.Construct implements cdk.ITaggable, el
/**
* Add command to the startup script of fleet instances.
* The command must be in the scripting language supported by the fleet's OS (i.e. Linux/Windows).
*
* @deprecated Use userdata.addCommands() instead.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with this as sugar? Looks pretty useful...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. I want to guide people towards the object that has convenience methods for common operations, so they don't go templating some complicated strings if there are better solutions available.

const resource = new cdk.Resource(this, 'Resource', {
// I know this thing says it lives in the AWS::CloudFormation namespace,
// but it really doesn't make sense there.
type: 'AWS::CloudFormation::Init',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is weird. I believe AWS::CloudFormation::Init is a metadata key, not a resource type:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, you're right! Completely misread that.

/**
* Whether to stop executing as soon as an error is encountered
*
* @default false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense that this would be true by default?
Also, we should document what this means on each platform?

Copy link
Contributor Author

@rix0rrr rix0rrr Sep 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we probably do want that, but then we have to do signaling very differently (which I think is still a good thing!)

Signaling is supposed to be used this way:

some_command
cfn-signal -e $? ...

You must always run cfn-signal, but then use it to send the error code of the previous command (typically but not necessarily this will be cfn-init). Of course, this breaks down and becomes complicated if you run multiple commands.

An alternative that I like better is to install a bash error trap:

set -euo pipefail
signal_failure() {
  cfn-signal --success false 
}
trap signal_failure ERR

command1
command2
command3
cfn-signal --success true

And this will do the right thing. But it is a lot more ceremony...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not trap exit and just send the last status. If they choose not to exit on error, strict (I think). The signal behavior would be the same. This would simplify the need for two signal commands.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I thought the same later, just hadn't figured out if I can use trap abd $! together in that way

if (options.verbose !== false) { bashFlags.push('x'); }
if (options.strict) { bashFlags.push('eu'); }

const bashCommand = '#!/bin/bash' + (bashFlags.length > 0) ? ` -${bashFlags.join('')}` : '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure it's possible to pass in flags like this instead of set -euo pipefail...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes:

huijbers ~/W/P/ecs-cdk(ecs-demo)> bash -euo pipefail
bash-3.2$ echo $HELLO
bash: HELLO: unbound variable
huijbers ~/W/P/ecs-cdk(ecs-demo)> 

public createUserData(scripts: string[], options: UserDataOptions): string {
const bashFlags = [];
if (options.verbose !== false) { bashFlags.push('x'); }
if (options.strict) { bashFlags.push('eu'); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I'd expect strict to also imply pipefail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I was on the fence for that one, but I can totally add it.

const parts = ['cfn-signal',
`--region ${new cdk.AwsRegion()}`,
`--stack ${new cdk.AwsStackId()}`,
`--resource ${resource.logicalId}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the EC2 instance that you want to signal

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or AutoScalingGroup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this in quite a few userdata sections

            # Ensure our PATH is set correctly (on Amazon Linux, cfn-signal is in /opt/aws/bin)
            . ~/.bash_profile

This has served us well, but perhaps we want to offer a path override for the location of cfn-signal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like we should hardcode the path in the script (w/ cdk override)

Copy link
Contributor

@moofish32 moofish32 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of questions. This does look it would cover my use case. So far all of mine are ASG based not ec2.

const parts = ['cfn-signal',
`--region ${new cdk.AwsRegion()}`,
`--stack ${new cdk.AwsStackId()}`,
`--resource ${resource.logicalId}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this in quite a few userdata sections

            # Ensure our PATH is set correctly (on Amazon Linux, cfn-signal is in /opt/aws/bin)
            . ~/.bash_profile

This has served us well, but perhaps we want to offer a path override for the location of cfn-signal?

/**
* Whether to stop executing as soon as an error is encountered
*
* @default false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not trap exit and just send the last status. If they choose not to exit on error, strict (I think). The signal behavior would be the same. This would simplify the need for two signal commands.

@eladb
Copy link
Contributor

eladb commented Dec 19, 2018

Closing this PR for now. The branch is still in the repo and we can reopen as needed.

@eladb eladb closed this Dec 19, 2018
@rix0rrr rix0rrr deleted the huijbers/init-support branch April 23, 2019 07:11
@DavidChristiansen
Copy link
Contributor

Can we consider bringing this back into the roadmap?

@NGL321 NGL321 added the contribution/core This is a PR that came from AWS. label Sep 27, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Auto Scaling Group - Turn UserData into rich object
5 participants