GithubHelp home page GithubHelp logo

envato / stack_master Goto Github PK

View Code? Open in Web Editor NEW
286.0 73.0 47.0 1.73 MB

The missing CloudFormation tool

License: MIT License

Ruby 81.06% HTML 0.10% Gherkin 18.84%
aws-cloudformation cloudformation aws

stack_master's Introduction

StackMaster

License MIT Gem Version Build Status

StackMaster is a CLI tool to manage CloudFormation stacks, with the following features:

  • Synchronous visibility into stack updates. See exactly what is changing and what will happen before agreeing to apply a change.
  • Dynamic parameter resolvers.
  • Template compiler support for YAML and SparkleFormation.

Stack updates can cause a lot of damage if applied blindly. StackMaster helps with this by providing the operator with as much information about the proposed change as possible before asking for confirmation to continue. That information includes:

  • Template body and parameter diffs.
  • Change sets are displayed for review.
  • Once the diffs & change set have been reviewed, the change can be applied and stack events monitored.
  • Stack events will be displayed until an end state is reached.

Stack parameters can be dynamically resolved at runtime using one of the built in parameter resolvers. Parameters can be sourced from other stacks outputs, or by querying various AWS APIs to get resource ARNs, etc.

Installation

System-wide

gem install stack_master

# if you want linting capabilities:
pip install cfn-lint

Bundler

  • pip install cfn-lint if you need lint functionality
  • Add gem 'stack_master' to your Gemfile.
  • Run bundle install
  • Run bundle exec stack_master init to generate a directory structure and stack_master.yml file

Configuration

Stacks are defined inside a stack_master.yml YAML file. When running stack_master, it is assumed that this file will exist in the current working directory, or that the file is passed in with --config /path/to/stack_master.yml. Here's an example configuration file:

region_aliases:
  production: us-east-1
  staging: ap-southeast-2
stack_defaults:
  tags:
    application: my-awesome-app
  role_arn: service_role_arn
region_defaults:
  us-east-1:
    tags:
      environment: production
    notification_arns:
      - test_arn
  ap-southeast-2:
    tags:
      environment: staging
stacks:
  production:
    myapp-vpc:
      template: myapp_vpc.rb
      tags:
        purpose: front-end
    myapp-db:
      template: myapp_db.rb
      stack_policy_file: db_stack_policy.json
      tags:
        purpose: back-end
    myapp-web:
      template: myapp_web.rb
      tags:
        purpose: front-end
  staging:
    myapp-vpc:
      template: myapp_vpc.rb
      allowed_accounts: '123456789'
      tags:
        purpose: front-end
    myapp-db:
      template: myapp_db.rb
      allowed_accounts:
        - '1234567890'
        - '9876543210'
      tags:
        purpose: back-end
    myapp-web:
      template: myapp_web.rb
      tags:
        purpose: front-end
  eu-central-1:
    myapp-vpc:
      template: myapp_vpc.rb
      tags:
        purpose: vpc

S3

StackMaster can optionally use S3 to store the templates before creating a stack. This requires you to configure an S3 bucket in stack_master.yml:

stack_defaults:
  s3:
    bucket: my_bucket_name
    prefix: cfn_templates/my-awesome-app
    region: us-west-2

Additional files can be configured to be uploaded to S3 alongside the templates:

stacks:
  production:
    myapp-vpc:
      template: myapp_vpc.rb
      files:
        - userdata.sh

Directories

  • templates - CloudFormation, SparkleFormation or CfnDsl templates.
  • parameters - Parameters as YAML files.
  • secrets - encrypted secret files.
  • policies - Stack policy JSON files.

Templates

StackMaster supports CloudFormation templates in plain JSON or YAML. Any .yml or .yaml file will be processed as YAML, while any .json file will be processed as JSON. Additionally, YAML files can be pre-processed using ERB and compile-time parameters.

Ruby DSLs

By default, any template ending with .rb will be processed as a SparkleFormation template. However, if you want to use CfnDsl templates you can add the following lines to your stack_master.yml.

template_compilers:
  rb: cfndsl

Parameters

By default, parameters are loaded from multiple YAML files, merged from the following lookup paths from bottom to top:

  • parameters/[stack_name].yaml
  • parameters/[stack_name].yml
  • parameters/[region]/[stack_name].yaml
  • parameters/[region]/[stack_name].yml
  • parameters/[region_alias]/[stack_name].yaml
  • parameters/[region_alias]/[stack_name].yml

A simple parameter file could look like this:

key_name: myapp-us-east-1

Alternatively, a parameter_files array can be defined to explicitly list parameter files that will be loaded. If parameter_files are defined, the automatic search locations will not be used.

parameters_dir: parameters # the default
stacks:
  us-east-1:
    my-app:
      parameter_files:
        - my-app.yml # parameters/my-app.yml

Parameters can also be defined inline with stack definitions:

stacks:
  us-east-1:
    my-app:
      parameters:
        VpcId:
          stack_output: my-vpc/VpcId

Compile Time Parameters

Compile time parameters can be defined in a stack's parameters file, using the key compile_time_parameters. Keys in parameter files are automatically converted to camel case.

As an example:

# parameters/some_stack.yml
vpc_cidr: 10.0.0.0/16
compile_time_parameters:
  subnet_cidrs:
    - 10.0.0.0/28
    - 10.0.2.0/28

SparkleFormation

Compile time parameters can be used for SparkleFormation templates. It conforms and allows you to use the Compile Time Parameters feature.

CloudFormation YAML ERB

Compile time parameters can be used to pre-process YAML CloudFormation templates. An example template:

# templates/some_stack_template.yml.erb
Parameters:
  VpcCidr:
    Type: String
Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCidr
  # Given the two subnet_cidrs parameters, this creates two resources:
  # SubnetPrivate0 with a CidrBlock of 10.0.0.0/28, and
  # SubnetPrivate1 with a CidrBlock of 10.0.2.0/28
  <% params["SubnetCidrs"].each_with_index do |cidr, index| %>
  SubnetPrivate<%= index %>:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: ap-southeast-2
      CidrBlock: <%= cidr %>
  <% end %>

Parameter Resolvers

Parameter values can be sourced dynamically using parameter resolvers.

One benefit of using parameter resolvers instead of hard coding values like VPC IDs and resource ARNs is that the same configuration works cross region/account, even though the resolved values will be different.

Cross-account parameter resolving

One way to resolve parameter values from different accounts to the one StackMaster runs in, is to assume a role in another account with the relevant IAM permissions to execute successfully.

This is supported in StackMaster by specifying the role and account properties for the parameter resolver in the stack's parameters file.

All parameter resolvers are supported.

vpc_peering_id:
  role: cross-account-parameter-resolver
  account: 1234567890
  stack_output: vpc-peering-stack-in-other-account/peering_name

an_array_param:
  role: cross-account-parameter-resolver
  account: 1234567890
  stack_outputs:
    - stack-in-account1/output
    - stack-in-account1/another_output

another_array_param:
  - role: cross-account-parameter-resolver
    account: 1234567890
    stack_output: stack-in-account1/output
  - role: cross-account-parameter-resolver
    account: 0987654321
    stack_output: stack-in-account2/output

my_secret:
  role: cross-account-parameter-resolver
  account: 1234567890
  parameter_store: ssm_parameter_name

An example of use case where cross-account parameter resolving is particularly useful is when setting up VPC peering where you need the VPC ID of the peer. Without the ability to assume a role in another account, the only option was to hard code the peer's VPC ID.

Stack Output

The stack output parameter resolver looks up outputs from other stacks in the same or different region. The expected format is [(region|region-alias):]stack-name/(OutputName|output_name).

vpc_id:
  # Output from a stack in the same region
  stack_output: my-vpc-stack/VpcId

bucket_name:
  # Output from a stack in a different region
  stack_output: us-east-1:init-bucket/bucket_name

zone_name:
  # Output from a stack in a different region using its alias
  stack_output: global:hosted-zone/ZoneName

This is the most used parameter resolver because it enables stacks to be split up into their separated concerns (VPC, web, database etc) with outputs feeding into parameters of dependent stacks.

Secret

Note: The GPG parameter resolver has been extracted into a dedicated gem. Please install and follow the instructions for the stack_master-gpg_parameter_resolver gem.

Parameter Store

An alternative to the secrets store, uses the AWS SSM Parameter store to protect secrets. Expects a parameter of either String or SecureString type to be present in the same region as the stack. You can store the parameter using a command like this

aws ssm put-parameter --region <region> --name <parameter name> --value <secret> --type (String|SecureString)

When doing so make sure you don't accidentally store the secret in your .bash_history and you will likely want to set the parameter to NoEcho in your template.

db_password:
  parameter_store: ssm_parameter_name

1Password Lookup

An alternative to the secrets store is accessing 1password secrets using the 1password cli (op). You declare a 1password lookup with the following parameters in your parameters file:

# parameters/database.yml
database_password:
  one_password:
    title: production database
    vault: Shared
    type: password

1password stores the name of the secret in the title. You can pass the vault you expect the secret to be in. Currently we support two types of secrets, passwords and secureNotes. All values must be declared, there are no defaults.

For more information on 1password cli please see here

EJSON Store

ejson is a tool to manage asymmetrically encrypted values in JSON format. This allows you to keep secrets securely in git/Github and gives anyone the ability the capability to add new secrets without requiring access to the private key. ejson_wrapper encrypts the underlying EJSON private key with KMS and stores it in the ejson file as _private_key_enc. Each time an ejson secret is required, the underlying EJSON private key is first decrypted before passing it onto ejson to decrypt the file.

First, generate an ejson file with ejson_wrapper, specifying the KMS key ID to be used:

gem install ejson_wrapper
ejson_wrapper generate --region us-east-1 --kms-key-id [key_id] --file secrets/production.ejson

Then, add the ejson_file argument to your stack in stack_master.yml:

stacks:
  us-east-1:
    my_app:
      template: my_app.json
      ejson_file: production.ejson

finally refer to the secret key in the parameter file, i.e. parameters/my_app.yml:

my_param:
  ejson: "my_secret"

Additional configuration options:

  • ejson_file_region The AWS region to attempt to decrypt private key with
  • ejson_file_kms Default: true. Set to false to use ejson without KMS.

Security Group

Looks up a security group by name and returns the ARN.

ssh_sg:
  security_group: SSHSecurityGroup

Security Groups

An array of security group names can also be provided.

ssh_sg:
  security_groups:
    - SSHSecurityGroup
    - WebAccessSecurityGroup

SNS Topic

Looks up an SNS topic by name and returns the ARN.

notification_topic:
  sns_topic_name: PagerDuty

Latest AMI by Tag

Looks up the latest AMI ID by a given set of tags.

web_ami:
  latest_ami_by_tags: role=web,application=myapp

Note that the corresponding array resolver is named latest_amis_by_tags.

Latest AMI by attribute

Looks up the latest AMI ID by a given set of attributes. By default it will only return AMIs from the account the stack is created in, but you can specify the account ID or certain keywords mentioned in the aws documentation

This selects the latest wily hvm AMI from Ubuntu (using the account id):

bastion_ami:
  latest_ami:
    owners: 099720109477
    filters:
      name: ubuntu/images/hvm/ubuntu-wily-15.10-amd64-server-*

A set of possible attributes is available in the AWS documentation.

Any value can be an array of possible matches.

Latest Container from Repository

Looks up the a Container Image from an ECR repository. By default this will return the latest container in a repository. If tag is specified we return the sha digest of the image with this tag. This avoids the issue where CloudFormation won't update a Task Definition if we use a tag such as latest, because it only updates resources if a parameter has changed. This allows us to tag an image and deploy the latest version of that image via CloudFormation and avoids versioning our tags and having to store the metadata about the latest tag version somewhere.

Returns the docker repository URI, i.e. aws_account_id.dkr.ecr.region.amazonaws.com/container@sha256:digest

container_image_id:
  latest_container:
    repository_name: nginx # Required. The name of the repository
    registry_id: "012345678910" # The AWS Account ID the repository is located in. Defaults to the current account's default registry. Must be in quotes.
    region: us-east-1 # Defaults to the region the stack is located in
    tag: production # By default we'll find the latest image pushed to the repository. If tag is specified we return the sha digest of the image with this tag

Environment Variable

Lookup an environment variable:

db_username:
  env: DB_USERNAME

ACM Certificates

Find an ACM certificate by domain name:

cert:
  acm_certificate: www.example.com

Custom parameter resolvers

New parameter resolvers can be created in a separate gem.

To create a resolver named my_resolver:

  • Create a new gem using your favorite tool
  • The gem structure must contain the following path:
lib/stack_master/parameter_resolvers/my_resolver.rb
  • That file needs to contain a class named StackMaster::ParameterResolvers::MyResolver that implements a resolve method and an initializer taking 2 parameters :
module StackMaster
  module ParameterResolvers
    class MyResolver < Resolver
      array_resolver # Also create a MyResolvers resolver to handle arrays

      def initialize(config, stack_definition)
        @config = config
        @stack_definition = stack_definition
      end

      def resolve(value)
        value
      end
    end
  end
end
  • Note that the filename and classname are both derived from the resolver name passed in the parameter file. In our case, the parameters YAML would look like:
vpc_id:
  my_resolver: dummy_value

Resolver Arrays

Most resolvers support taking an array of values that will each be resolved. Unless stated otherwise in the documentation, the array version of the resolver will be named with the pluralized name of the original resolver.

When creating a new resolver, one can automatically create the array resolver by adding a array_resolver statement in the class definition, with an optional class name if different from the default one.

module StackMaster
  module ParameterResolvers
    class MyResolver < Resolver
      array_resolver class_name: 'MyCustomArrayResolver'
      ...
    end
  end
end

In that example, using the array resolver would look like:

my_parameter:
  my_custom_array_resolver:
    - value1
    - value2

Array parameter values can include nested parameter resolvers.

For example, given the following parameter definition:

my_parameter:
  - stack_output: my-stack/output # value resolves to 'value1'
  - value2

The parameter value will resolve to:

my_parameter: 'value1,value2'

ERB Template Files in SparkleFormation templates

An extension to SparkleFormation is the user_data_file! method, which evaluates templates in templates/user_data/[file_name]. Most of the usual SparkleFormation methods are available in user data templates. Example:

# templates/user_data/app.erb
REGION=<%= region! %>
ROLE=<%= role %>

And used like this in SparkleFormation templates:

# templates/app.rb
  user_data user_data_file!('app.erb', role: :worker)

You can also use the joined_file! method which evaluates templates in templates/config/[file_name]. It is similar to user_data_file! but doesn't do base64 encoding. Example:

# templates/config/someconfig.conf.erb
my_variable=<%= ref!(:foo) %>
my_other_variable=<%= account_id! %>
# templates/ecs_task.rb
container_definitions array!(
  -> {
    command array!(
      "-e",
      joined_file!('someconfig.conf.erb')
    )
    ...
  }
)

Compiler Options & Alternate Template Directories

StackMaster allows you to separate your stack definitions and parameters from your templates by way of a template_dir key in your stack_master.yml. You can also pass compiler-specific options to the template compiler to further customize SparkleFormation or CfnDsl's behavior. Combining the 2 lets you move your SFN templates away from your stack definitions. For example, if your project is laid out as:

project-root
|-- envs
  |-- env-1
    |-- stack_master.yml
  |-- env-2
    |-- stack_master.yml
|-- sparkle
  |-- templates
    |-- my-stack.rb

Your env-1/stack_master.yml files can reference common templates by setting:

template_dir: ../../sparkle/templates
stack_defaults:
  compiler_options:
    sparkle_path: ../../sparkle

stacks:
  us-east-1:
    my-stack:
      template: my-stack.rb

Loading SparklePacks

SparklePacks can be pre-loaded using compiler options. This requires the name of a rubygem to require followed by the name of the SparklePack, which is usually the same name as the Gem.

stacks:
  us-east-1:
    my-stack:
      template: my-stack-with-dynamic.rb
      compiler_options:
        sparkle_packs:
          - vpc-sparkle-pack

The template can then simply load a dynamic from the sparkle pack like so:

SparkleFormation.new(:my_stack_with_dynamic) do
   dynamic!(:sparkle_pack_dynamic)
end

Note though that if a dynamic with the same name exists in your templates/dynamics/ directory it will get loaded since it has higher precedence.

Templates can be also loaded from sparkle packs by defining sparkle_pack_template. The name corresponds to the registered symbol rather than specific name. That means for a sparkle pack containing:

SparkleFormation.new(:template_name) do
  ...
end

we can use stack defined as follows:

stacks:
  us-east-1:
    my-stack:
      template: template_name
      compiler: sparkle_formation
      compiler_options:
        sparkle_packs:
          - some-sparkle-pack
        sparkle_pack_template: true

Allowed accounts

The AWS account the command is executing in can be restricted to a specific list of allowed accounts. This is useful in reducing the possibility of applying non-production changes in a production account. Each stack definition can specify the allowed_accounts property with an array of AWS account IDs or aliases the stack is allowed to work with.

This is an opt-in feature which is enabled by specifying at least one account to allow.

Unlike other stack defaults, the allowed_accounts property values specified in the stack definition override values specified in the stack defaults (i.e., other stack property values are merged together with those specified in the stack defaults). This allows specifying allowed accounts in the stack defaults (inherited by all stacks) and override them for specific stacks. See below example config for an example.

stack_defaults:
  allowed_accounts: '555555555'
stacks:
  us-east-1:
    myapp-vpc: # only allow account 555555555 (inherited from the stack defaults)
      template: myapp_vpc.rb
      tags:
        purpose: front-end
    myapp-db:
      template: myapp_db.rb
      allowed_accounts: # only allow these accounts (overrides the stack defaults)
        - '1234567890'
        - my-account-alias
      tags:
        purpose: back-end
    myapp-web:
      template: myapp_web.rb
      allowed_accounts: [] # allow all accounts (overrides the stack defaults)
      tags:
        purpose: front-end
    myapp-redis:
      template: myapp_redis.rb
      allowed_accounts: '888888888' # only allow this account (overrides the stack defaults)
      tags:
        purpose: back-end

In the cases where you want to bypass the account check, there is the StackMaster flag --skip-account-check that can be used.

Commands

stack_master help # Display up to date docs on the commands available
stack_master init # Initialises a directory structure and stack_master.yml file
stack_master list # Lists stack definitions
stack_master apply [region-or-alias] [stack-name] # Create or update a stack
stack_master apply [region-or-alias] [stack-name] [region-or-alias] [stack-name] # Create or update multiple stacks
stack_master apply [region-or-alias] # Create or update stacks in the given region
stack_master apply # Create or update all stacks
stack_master --changed apply # Create or update all stacks that have changed
stack_master --yes apply [region-or-alias] [stack-name] # Create or update a stack non-interactively (forcing yes)
stack_master diff [region-or-alias] [stack-name] # Display a stack template and parameter diff
stack_master drift [region-or-alias] [stack-name] # Detects and displays stack drift using the CloudFormation Drift API
stack_master delete [region-or-alias] [stack-name] # Delete a stack
stack_master events [region-or-alias] [stack-name] # Display events for a stack
stack_master outputs [region-or-alias] [stack-name] # Display outputs for a stack
stack_master resources [region-or-alias] [stack-name] # Display outputs for a stack
stack_master status # Displays the status of each stack
stack_master tidy # Find missing or extra templates or parameter files
stack_master compile # Print the compiled version of a given stack
stack_master validate # Validate a template
stack_master lint # Check the stack definition locally using cfn-lint
stack_master nag # Check the stack template with cfn_nag

Applying updates - stack_master apply

The apply command does the following:

  • Compiles the proposed stack template and resolves parameters.
  • Fetches the current state of the stack from CloudFormation.
  • Displays a diff of the current stack and the proposed stack.
  • Creates a change set and displays the actions that CloudFormation will take to perform the update (if the stack already exists).
  • Asks if the update should continue.
  • If yes, the API calls are made to update or create the stack.
  • Stack events are displayed until CloudFormation has finished applying the changes.

Demo:

Apply Demo

Drift Detection - stack_master drift

stack_master drift us-east-1 mystack uses the CloudFormation APIs to trigger drift detection and display resources that have changed outside of the CloudFormation stack. This can happen if a resource has been updated via the console or CLI directly rather than via a stack update.

Diff - stack_master diff

stack_master diff us-east-1 mystack displays whether the computed parameters or template differ to what was last applied in CloudFormation. This can happen if the template or computed parameters have changed in code and the change hasn't been applied to this stack.

Maintainers

License

StackMaster uses the MIT license. See LICENSE.txt for details.

Contributing

  1. Fork it ( http://github.com/envato/stack_master/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

stack_master's People

Contributors

andrewjhumphrey avatar arecker avatar berniedurfee-ge avatar denmat avatar dependabot-preview[bot] avatar dependabot[bot] avatar dissonanz avatar ejoubaud avatar flyinbutrs avatar grosser avatar gstamp avatar gstib avatar gusgollings avatar jacobbednarz avatar johnf avatar lethalpaga avatar liamdawson avatar mipearson avatar orien avatar petervandoros avatar phazel avatar rbayerl avatar redterror avatar scottyp-env avatar simpson-ross avatar simpsora avatar skatsuta avatar stevehodgkiss avatar thekindofme avatar viraptor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stack_master's Issues

Support SparkleFormation compile-time parameters

http://www.sparkleformation.io/docs/sparkle_formation/compile-time-parameters.html

Use case: some resource types expect a particular repetitive data structure, e.g. an array of JSON objects. There is no suitable existing variable type which supports such a thing.

Enabling compile-time parameters would expose parameters to ruby methods in the DSL, allowing the caller to create the required data structure.

This could be implemented by supporting an additional type of parameters file, whose contents are applied to compile-time parameters rather than the standard parameters.

Ability to pass user data as a parameter.

Requesting the ability to pass user data as parameter to the stack instead of having to write in inline in the template.

Something like

DateInit: files/user_data.txt
"DataInit": {
  "Type": "String",
  "Description": "Initialisation script"
}
...

"UserData": { "Fn::Base64": { "Ref": "DataInit" } }

...

uninitialized constant StackMaster::VERSION with 0.7.0 gem

I'm getting the following exception when I use the stack_master command running 0.7.0

/opt/rubies/2.2.4/lib/ruby/gems/2.2.0/gems/stack_master-0.7.0/lib/stack_master/cli.rb:22:in `execute!': uninitialized constant StackMaster::VERSION (NameError)
    from /opt/rubies/2.2.4/lib/ruby/gems/2.2.0/gems/stack_master-0.7.0/bin/stack_master:13:in `<top (required)>'
    from /opt/rubies/2.2.4/lib/ruby/gems/2.2.0/bin/stack_master:23:in `load'
    from /opt/rubies/2.2.4/lib/ruby/gems/2.2.0/bin/stack_master:23:in `<main>'

I suspect this is an issue with the latest change to use autoload, but I can't replicate it on the master branch or v0.7.0 tag, only with the installed Gem.

Add/update stack outputs without changing stack resource raises exception

Stack diff:
--- /var/folders/t1/cl0szsx16n34jz8265mbkl5h0000gn/T/diffy20160202-18953-w6q3cx 2016-02-02 13:20:57.000000000 +1100
+++ /var/folders/t1/cl0szsx16n34jz8265mbkl5h0000gn/T/diffy20160202-18953-758jgx 2016-02-02 13:20:57.000000000 +1100
@@ -68,14 +68,14 @@
           ]
         }
       }
     }
+  },
+  "Outputs": {
+    "S3EncryptionKmsKey": {
+      "Description": "S3 encryption KMS key",
+      "Value": {
+        "Ref": "S3EncryptionKey"
+      }
+    }
   }
 }
\ No newline at end of file
Parameters diff: No changes
Continue and apply the stack (y/n)?
/opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/aws-sdk-core-2.2.8/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call': No updates are to be performed. (Aws::CloudFormation::Errors::ValidationError)
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/aws-sdk-core-2.2.8/lib/aws-sdk-core/plugins/param_converter.rb:20:in `call'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/aws-sdk-core-2.2.8/lib/seahorse/client/plugins/response_target.rb:21:in `call'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/aws-sdk-core-2.2.8/lib/seahorse/client/request.rb:70:in `send_request'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/aws-sdk-core-2.2.8/lib/seahorse/client/base.rb:207:in `block (2 levels) in define_operation_methods'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/aws_driver/cloud_formation.rb:38:in `update_stack'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/commands/apply.rb:81:in `update_stack'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/commands/apply.rb:64:in `create_or_update_stack'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/commands/apply.rb:23:in `perform'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/command.rb:9:in `perform'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/cli.rb:168:in `execute_stack_command'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/cli.rb:28:in `block (2 levels) in execute!'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.5/lib/commander/command.rb:178:in `call'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.5/lib/commander/command.rb:153:in `run'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.5/lib/commander/runner.rb:428:in `run_active_command'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.5/lib/commander/runner.rb:68:in `run!'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.5/lib/commander/delegates.rb:15:in `run!'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/lib/stack_master/cli.rb:142:in `execute!'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.0.4/bin/stack_master:13:in `<top (required)>'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
        from /opt/boxen/homebrew/var/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'

Per project parameter resolvers

StackMaster doesn't contain many parameter resolvers. It would be useful if there were a way to quickly build them inside of a project before deciding whether to submit a PR to StackMaster with the resolver.

Proposal:

Add a new directory named parameters_resolvers. All ruby files in that directory would be loaded. A resolver could be defined like this:

StackMaster.resolver(:rds_cluster) do # defines a class and hooks things up under the hood
  def call(value)
    value.reverse
  end
end

Feature Suggestion - Adding support for stack include files in Stack_Master.yml

We have large amounts of Stacks and a lot of them have common templates and names across our infrastructure so was wondering if I looked at a way to dynamically add include blocks to the yml file so when Stack_Master read the file it would dynamically inject the referenced files.

My current thought is something like this inside a region block where these stacks are placeholders for where to injected the nested includes..

This would work like we have a parameters directory we can also now have a stacks directory and we have the same override system as parameters. As currently we have to use multiple stack_master.yml files to have this behaviour and it be great to be able to have our stack_master really clean and allow us to break up our stacks into nice reusable components and reducing duplications.

--- 
stacks:  
  us-east-1:
    networking:
      includes:
        - virtual_private_cloud.yml
        - private_subnets.yml
        - public_subnets.yml
        - security_groups.yml
    software-repository-s3-bucket:
      template: s3/bucket.json
      stack_policy_file: s3/bucket.json
    software-backups-s3-bucket:
      template: s3/bucket.json
      stack_policy_file: s3/bucket.json    
    services:
      includes:  
        - emr_cluster.yml
        - ec2_instances.yml
  us-west-2:
    networking:
      includes:
        - virtual_private_cloud.yml
        - private_subnets.yml
        - public_subnets.yml
        - security_groups.yml
    database-backups-s3-bucket:
      template: s3/bucket.json
      stack_policy_file: s3/bucket.json    
    databases:
      includes:  
        - mysql_databases.yml
        - postgres_databases.yml		

Any suggestion about how best to build this feature as happy to do the coding to get this done and submit a PR. Just wondered what people thought of the best way to get this included..

Create a change set when creating a stack

Currently StackMaster issues CloudFormation::Client#create_stack when creating a stack, while it updates a stack using CloudFormation::Client#create_change_set and #execute_change_set.
However, we can also use CreateChangeSet API when creating a stack.
I think it's better to use the API to create a stack because we can confirm all resources to be added by change set details.
In addition, it's required to use CreateChangeSet API to create a stack from a SAM template (So this is a blocker of #176).

I'm willing to implement this feature, but have one concern.
CloudFormation::Client#create_stack can pass a stack policy with stack_policy_body or stack_policy_url parameters, while CloudFormation::Client#create_change_set cannot.
So I have two ideas:

  1. add another command or additional option in apply command to create a stack using a change set
  2. always create a stack using a change set and then update the stack when a stack policy exists in stack_master.yml

I'd like to hear your ideas and opinions to add (or against) this feature. Thank you.

Installation on windows

Hi,

I really want to give stack_master a go however I am having trouble installing the gem on windows. I am running gem 2.6.7 and it pulls down all the dependencies however when it gets to "building native extensions" it errors. I have looked at the logs but I am unable to work out what is wrong, I am not that familiar with ruby. Can you help at all? I have tried it on 3 systems, windows 7 and windows 10 with the same result.

ERROR: Error installing stack_master:
ERROR: Failed to build gem native extension.

current directory: N:/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12/ext/gpgme

N:/Ruby23/bin/ruby.exe -r ./siteconf20161013-1180-gq47yd.rb extconf.rb


IMPORTANT! gpgme gem uses locally built versions of required C libraries,
namely libgpg-error, libassuan, and gpgme.

If this is a concern for you and you want to use the system library
instead, abort this installation process and reinstall gpgme gem as
follows:

gem install gpgme -- --use-system-libraries

Extracting libgpg-error-1.21.tar.bz2 into tmp/i686-w64-mingw32/ports/libgpg-error/1.21... OK
Running 'configure' for libgpg-error 1.21... OK
Running 'compile' for libgpg-error 1.21... ERROR, review 'N:/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12/ext/gpgme/tmp/

i686-w64-mingw32/ports/libgpg-error/1.21/compile.log' to see what happened. Last lines are:

libtool: compile: gcc -DHAVE_CONFIG_H -I. -I.. -DLOCALEDIR="N:/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12/ports/i686
-w64-mingw32/libgpg-error/1.21/share/locale" -fPIC -Wall -Wpointer-arith -Wno-psabi -MT libgpg_error_la-estream.lo -MD
-MP -MF .deps/libgpg_error_la-estream.Tpo -c estream.c -o libgpg_error_la-estream.o
estream.c:1:0: warning: -fPIC ignored for target (all code is position independent) [enabled by default]
estream.c: In function 'es_fill':
estream.c:1714:24: error: 'EWOULDBLOCK' undeclared (first use in this function)
estream.c:1714:24: note: each undeclared identifier is reported only once for each function it appears in
estream.c: In function 'es_flush':
estream.c:1784:28: error: 'EWOULDBLOCK' undeclared (first use in this function)
estream.c: In function 'es_read_nbf':
estream.c:2044:24: error: 'EWOULDBLOCK' undeclared (first use in this function)
estream.c: In function 'es_seek':
estream.c:2316:20: error: 'EWOULDBLOCK' undeclared (first use in this function)
estream.c: In function 'es_write_nbf':
estream.c:2374:24: error: 'EWOULDBLOCK' undeclared (first use in this function)
make[3]: *** [libgpg_error_la-estream.lo] Error 1
make[3]: Leaving directory /n/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12/ext/gpgme/tmp/i686-w64-mingw32/ports/libgpg- error/1.21/libgpg-error-1.21/src' make[2]: *** [all] Error 2 make[2]: Leaving directory/n/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12/ext/gpgme/tmp/i686-w64-mingw32/ports/libgpg-
error/1.21/libgpg-error-1.21/src'
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory `/n/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12/ext/gpgme/tmp/i686-w64-mingw32/ports/libgpg-
error/1.21/libgpg-error-1.21'

make: *** [all] Error 2

*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers. Check the mkmf.log file for more details. You may
need configuration options.

Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=N:/Ruby23/bin/$(RUBY_BASE_NAME)
--clean
--use-system-libraries
N:/Ruby23/lib/ruby/gems/2.3.0/gems/mini_portile2-2.1.0/lib/mini_portile2/mini_portile.rb:366:in block in execute': Fail ed to complete compile task (RuntimeError) from N:/Ruby23/lib/ruby/gems/2.3.0/gems/mini_portile2-2.1.0/lib/mini_portile2/mini_portile.rb:337:inchdir'
from N:/Ruby23/lib/ruby/gems/2.3.0/gems/mini_portile2-2.1.0/lib/mini_portile2/mini_portile.rb:337:in execute' from N:/Ruby23/lib/ruby/gems/2.3.0/gems/mini_portile2-2.1.0/lib/mini_portile2/mini_portile.rb:111:incompile'
from N:/Ruby23/lib/ruby/gems/2.3.0/gems/mini_portile2-2.1.0/lib/mini_portile2/mini_portile.rb:150:in cook' from extconf.rb:76:inblock in

'
from extconf.rb:65:in tap' from extconf.rb:65:in'

extconf failed, exit code 1

Gem files will remain installed in N:/Ruby23/lib/ruby/gems/2.3.0/gems/gpgme-2.0.12 for inspection.
Results logged to N:/Ruby23/lib/ruby/gems/2.3.0/extensions/x86-mingw32/2.3.0/gpgme-2.0.12/gem_make.out

region-aliases and parameters used

Hi,

Just stumbled over one use-case which I think may be worth a thought.

assuming having the following stack_master.yml

region_aliases:
  testing: us-east-1
  production: us-east-1
  staging: ap-southeast-2
stacks:
  us-east-1:
    my-stack-name:
      template: my-cf-stack.json
  ap-southeast-2:
    my-stack-name:
      template: my-cf-stack.json

So with that, I thought to put parameters under parameters/testing, parameters/production and parameters/staging with the name my_cf_stack.yml.

And I was also under the assumption if I now do a stack_master apply testing my-stack-name it will pull the parameters only from testing ... but obviously due to the same region used for testing and production it will always just take 1 of the files, regardless if I use the alias for either production or testing.

Maybe there is another way to achieve that but I am not sure on that and want to point that out here.

Config level tags are ignored

The documentation promises that you can specify stack-level and config-level tags. Running stack_master init us-west-2 test-stack even generates a stack-level tag for you.

stacks:
  us-west-2:
    test-stack:
      template: test-stack.json
      tags:
        environment: production

However, running stack_master diff against the template, the environment:production tag is nowhere to be seen.

arecker@localhost:testproject$ be stack_master diff
Executing diff on test-stack in us-west-2
Stack diff: 
--- /var/folders/sc/b7xz6j0d57z5_d7j72mzyw800000gp/T/diffy20170411-39386-b6rlc6 2017-04-11 15:39:47.000000000 -0500
+++ /var/folders/sc/b7xz6j0d57z5_d7j72mzyw800000gp/T/diffy20170411-39386-e3a2o7 2017-04-11 15:39:47.000000000 -0500
@@ -0,0 +1,16 @@
+{
+  "AWSTemplateFormatVersion": "2010-09-09",
+  "Description": "Cloudformation stack for test-stack",
+  "Parameters": {
+    "InstanceType": {
+      "Description": "EC2 instance type",
+      "Type": "String"
+    }
+  },
+  "Mappings": {
+  },
+  "Resources": {
+  },
+  "Outputs": {
+  }
+}
\ No newline at end of file
Parameters diff: 
+---
+InstanceType: 
No stack found

How are these tags intended to work exactly? I would have expected any taggable resource in the stack to receive this. Maybe this feature isn't complete?

Unfriendly error when template file is missing

I had a typo in the filename of my template file in stack_master.yml, which meant that it referred to a nonexistent file (left off the .rb extension):

    elb-test:
      template: elb_test

The error message was quite confusing, however:

$ stack_master apply us-east-1 elb-test
Executing apply on elb-test in us-east-1
StackMaster::TemplateCompiler::TemplateCompilationFailed Failed to compile /Users/.../templates/elb_test.
 Caused by: KeyError key not found: :""

Note the trailing . on the template filename (missing from my definition), and the resulting KeyError with a weird value (:"").

Ideally it would raise a more descriptive error here.

$ stack_master --version
StackMaster 0.7.1

Would be nice if sm could search for it's stack_master.yml ala rake and cap and...

MacBook-Pro:ap-southeast-2 andrewhumphrey$ sm apply ap-southeast-2 tsuru-vpc 
Failed to load config file stack_master.yml
MacBook-Pro:ap-southeast-2 andrewhumphrey$ pwd
/Users/andrewhumphrey/src/tsuru-infrastructure/parameters/ap-southeast-2
MacBook-Pro:ap-southeast-2 andrewhumphrey$ cd ../..
sm apply ap-southeast-2 tsuru-vpc 
Stack diff: 
+{
+  "Description": "Cloudformation template for the tsuru VPC",
+  "Parameters": {
+    "KeyName": {
<snip>

Better handling of throttling errors?

Our CI environment calls the validate command with no arguments, which tries to validate every entry in stack_master.yml. We sometimes encounter Aws::CloudFormation::Errors::Throttling Rate exceeded.

It would be helpful if there was a retry mechanism, so that this didn't fail builds. There's not a great way for the user to do this on their side, other than parsing the output for strings that look like errors and re-calling the entire validate command.

Add compile command

It would be helpful to see the compiled template. You could use the apply command and say no at the prompt, but if a stack has unmet dependencies (e.g. an unresolved stack output), it will show an error message and exit.

Adding a command which compiled the template and output it for examination would be helpful.

Support Serverless Application Model (SAM) templates

CloudFormation supports Serverless Application Model (SAM) templates, but when we use a SAM template StackMaster returns the following error:

$ stack_master apply
...
...
No stack found
Create stack (y/n)?
Uploading files to S3:
...
Aws::CloudFormation::Errors::ValidationError CreateStack cannot be used with templates containing Transforms.

This is because CreateStack API does not support SAM templates.
Instead, we need to use a change set to create a SAM-based stack.
So it'd be great if StackMaster supports SAM templates.

Feature request: stack_master.yml is a pest with multiple PRs in play

Lots of folks adding to a stack_master.yml makes for much merge hassle.

Maybe this could be "solved" by making convering stack_master.yml to a stack_master.d directory and merging all the files that are found in there.

I guess regularly merging origin/master back into your branch would do the same thing but for long lived PRs that's a bit of a pest just to pick up new stacks others have created.

Invalid ChangeSetName

It looks like the code that generates change set names has got a bug:

$ stack_master apply us-east-1 awscope
Executing apply on awscope in us-east-1
Stack diff:
...
Parameters diff: No changes
Aws::CloudFormation::Errors::ValidationError 1 validation error detected: Value 'StackMaster2016-04- 1-1014-1459466063' at 'changeSetName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*
exit status 1

Would love to be able to get stack_master to prompt for parameter values at run time

I'd like to be able to commit example stacks that folks can use without modification, but some of the parameters refer to other stacks. The simplest way I can think of is to be able to market a parameter as needing to be requested from the user at run time. If it's overriden in a more specific parameter file then don't prompt.

Something like:

# parameters/foo.yml
foo_id:
  request_if_required: true

Would prompt when the template is compiled or applied, but if the file below existed it would use the value from it instead of prompting:

# paramters/us-east-1/foo.yml
foo_id: x-12312312321

Using an old version of SparkleFormation

StackMaster is pinning an old version of sparkle_formation. This makes it impossible to integrate StackMaster with something like sfn-lambda.

With a fresh project and Gemfile

# frozen_string_literal: true
source 'https://rubygems.org'

gem 'stack_master'
group :sfn do
  gem 'sfn-lambda'
end

I get the following after bundle install.

Bundler could not find compatible versions for gem "sparkle_formation":
  In Gemfile:
    sfn-lambda was resolved to 0.1.0, which depends on
      sparkle_formation (>= 2.1.0)

    stack_master was resolved to 0.0.1, which depends on
      sparkle_formation (~> 1.1)

Could we bump the version number to make this possible?

Numeric parameters not handled well

When you pass a numeric parameter to Stack Master it blows up with something like this:

src/stack_master/lib/stack_master/parameter_resolver.rb:22:in `rescue in block in resolve': Unable to resolve parameter "AutoScalingGroupMinSize" value causing error: 2 (StackMaster::ParameterResolver::InvalidParameter)

A work around is to just convert those parameters to strings but it would be nice not to have to worry about this.

accessing parameters in ruby

Hi Guys,

Thanks for the Gem!

Is there a way I can access a parameter in ruby rather than use a parsed parameter in cfndsl.

For example I want to do something like

Description parameters['environment'] + 'ec2_cloudformation_stack'

Rather than an FnJoin with a Cloudformation parameter

Thanks for your help,

thebeefcake.

Missing required parameter params[:role_arn]

I've just started looking at stack_master and I am stuck on this error message:

bundle exec stack_master status                                                                 │}
Fetching stack information: |=========================================================================================|│
error: missing required parameter params[:role_arn]. Use --trace to view backtrace

I'm running this from the basic bundle exec stack_master init <region> <stack name> command so I haven't made any further changes. Also, my ~/.aws/credentials file has a bunch of different credentials set-up with a [default] one, which is the one it should be using.

Any ideas?

Stack Master incompatible with ruby 2.0

On my local Development Machine (MacOS 10.11) and the current AWS Linux (2016.09.1) Ruby 2.0 is the default Version, but Stack Master fails with some strange errors:

 stack_master --trace list
 /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/config.rb:13:in `attr_accessor': nil is not a symbol (TypeError)
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/config.rb:13:in `<class:Config>'
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/config.rb:5:in `<module:StackMaster>'
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/config.rb:4:in `<top (required)>'
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/cli.rb:177:in `load_config'
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/cli.rb:118:in `block (2 levels) in execute!'
	from /usr/local/share/ruby/gems/2.0/gems/commander-4.4.3/lib/commander/command.rb:178:in `call'
	from /usr/local/share/ruby/gems/2.0/gems/commander-4.4.3/lib/commander/command.rb:178:in `call'
	from /usr/local/share/ruby/gems/2.0/gems/commander-4.4.3/lib/commander/command.rb:153:in `run'
	from /usr/local/share/ruby/gems/2.0/gems/commander-4.4.3/lib/commander/runner.rb:446:in `run_active_command'
	from /usr/local/share/ruby/gems/2.0/gems/commander-4.4.3/lib/commander/runner.rb:68:in `run!'
	from /usr/local/share/ruby/gems/2.0/gems/commander-4.4.3/lib/commander/delegates.rb:15:in `run!'
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/lib/stack_master/cli.rb:172:in `execute!'
	from /usr/local/share/ruby/gems/2.0/gems/stack_master-0.12.0/bin/stack_master:13:in `<top (required)>'
	from /usr/local/bin/stack_master:23:in `load'
	from /usr/local/bin/stack_master:23:in `<main>'

Please express this Ruby Version requirement in your gemspec like: spec.required_ruby_version = '>= 2.1.0'

Feature request - Nested stacks creation

At the moment stack_master cannot create nested stacks as it's supplying the template body to the API.
Do you have any plans to support that?

The way I see it :

  • An S3 bucket URL and prefix would have to be added to the config file (global and per-region configuration)
  • All nested templates would have to be declared alongside the main template, maybe as a file parameter (that would allow one to upload any kind of file to that bucket, if used by a UserData script or such) and uploaded to that bucket
  • When creating the stack, pass in a template_url instead of a template_body parameter
  • I'm not sure how the stack events would be handled. Worst case would be that only the events from the toplevel stack would be displayed (so not events from the nested stacks). I suppose it's good enough for a first version
  • Stack deletion should work as-is as cfn takes care of the substack deletion
  • Diffs, Output and Resources listing would only show the ones from the toplevel template. AFAIK there is no easy way to get substacks other that iterating over the Cloudformation::Stack resources recursively. Again I assume that would be acceptable in a first version.
  • The too_big?method would need to know which method is used as the allowed size is different

Any thoughts on this? If not I'm happy to give a crack at this.

Support for optional parameters

Cloudformation allows parameters to be optional by using a conditional in the template:

https://cloudonaut.io/optional-parameter-in-cloudformation/

Though, it appears because Stack Master checks for empty parameters, it won't allow the use of conditions to create optional parameters.

Seems like without actually evaluating the conditions in a template, it would be difficult to evaluate when a parameters is being treated as optional.

Is there an easier way? Maybe a annotation in the stack_master.yml file to allow a stack to be submitted even when there appear to be empty parameters?

Automatic tag with template's filesystem path

In an organization with multiple SCM repositories and stacks defined all over the place, it can be hard to know where the source for a stack lives, looking at the stack itself.

Add a new tag to the stack which contains the fully-expanded path to the template file which created the stack.

For example:

Key = template_source
Value = /Users/rosssimpson/src/aws/templates/ntp.rb

If git would just support $Header$ this wouldn't be a problem! :)

Constants Parameter Resolver

We find ourselves putting account IDs in parameter files a few times and this opens a potential enhancement.

We could have a Constants parameter resolver that works like so:

permitted_account:
  constant: account_id/my_awesome_other_account

Where this looks up another YAML file in constants/account_id.yml:

my_awesome_other_account: 0123456789012

[Feature request] Support search tags that do not have value for AMI lookup

What is this about?

It is a good practice to use the latest_ami_by_tags feature to look up one AMI by its tag. For example:

web_ami:
  latest_ami_by_tags: role=web,application=myapp

However I learn that if you have a tag pair that has no value and the stack definition you omit the value part, the parser would error:

web_ami:
  latest_ami_by_tags: a_valueless_tag

It would be nice if the above format is accepted

Add dependency graph

Since stacks can declare dependencies on others (via the stack_output resolver), it would be nice to be able to visualize these dependencies.

Much like bundle viz, create a command which outputs a Graphviz (or other format) visualization of stack dependencies.

provide provider support like terraform

Alot of corporates are starting to use terraform because it has support for non-AWS providers. This allows corporate to provision both AWS and non-AWS resources from the same terraform script.
This is a killer feature for corporates because they other have non-AWS resources like their own DNS etc.
I would be easy to add the ability to define providers into stackmaster and allow folks to write third party gems that can be hooked into stack master. This would be a real killer feature for you and allow you to compete with terraform.

Stack exists but output does not.

Anyone know what I am doing wrong here....I keep getting the following error when I run the following command "stack_master apply --trace" I have two stacks...test and test2

/home/centos/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/stack_master-0.5.0/lib/stack_master/parameter_resolvers/stack_output.rb:24:in `resolve': Stack exists (test), but output does not: testVar (StackMaster::ParameterResolvers::StackOutput::StackOutputNotFound)

Why do I see testVar when I do: "stack_master outputs us-east-1 test --trace"?


*The below is in the template (test.yaml) I want to get it from....
"Outputs" : {
"testVar" : {
"Description" : "Test Variable",
"Value" : "TESTDATA"
}
}

In my parameters file for my second template I am referencing it as follows
testVar2:
stack_output: test/testVar

On my second template I am I referencing testVar2 as follows
Parameters section
"testVar2" : {
"Type" : "String",
"Description" : "Test variable that is read from the output of the test template."
}

Under tags section of EC2Instance
"Tags": [
{ "Key": "Name", "Value": {"Ref": "testVar2" } }
]

How to access parameters??

Hi,

I've been going through the templates and examples, and for the life of me I don't know how to reference parameters (parameters/.yml) from a yml or json CF template file (templates/)?

Bad exit code when apply fails

stack_master exits with a status of zero even when a stack is left in a bad state. As a result it can be difficult to notice if one stack fails as the diff for the next stack pushes the failure message off of the screen when running apply against all stacks.

stack_master should exit on failure with a non-zero status. This would allow for automated updates with Travis CI or some other tool via git hooks, and it would ensure users notice when a stack update or creation did not actually work.

Add support for multiple AWS accounts

When using multiple accounts, it would help to be able to specify an AWS credentials profile in the stack_master.yml file for a particular environment.

https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-multiple-profiles

I think this might mean that instead of a region being the only attribute for where to 'run' the stack, you'd need multiple attributes to describe the 'environment' in which to run the stack. At least the region and the credentials profile to use.

Example

environments:
  production-us:
    region: us-east-1
    profile: prod_profile
  production-eu:
    region: eu-west-1
    profile: prod_profile
  development-us:
    region: us-east-1
    profile: dev_profile
stacks:
  production-us:
    my_stack:
      template: my_prod_stack.cf.json
  production-eu:
    my_stack:
      template: my_prod_stack.cf.json
  development-us:
    my_stack:
      template: my_dev_stack.cf.json

sm init should append if stack_master.yml already exists

MacBook-Pro:tsuru-infrastructure andrewhumphrey$ sm init ap-southeast-2 tsuru-vpc
Writing stack_master.yml
Writing templates/tsuru-vpc.json
Writing parameters/tsuru_vpc.yml
Writing parameters/ap-southeast-2/tsuru_vpc.yml
MacBook-Pro:tsuru-infrastructure andrewhumphrey$ sm init ap-southeast-2 tsuru-databases
Aborting: stack_master.yml already exists. Use --overwrite to force overwriting file.```

Should just attempt to append to the bottom of the yml

Add support for pre/post-apply/delete actions

It would be great if stacks could be setup so certain actions (or hooks) could run before and after applying and deleting them.

In my head, indicating the actions would be done in the stack_master.yml itself. Something like:

stacks:
  us-east-1:
    my-s3-bucket:
      pre_apply: delete-if-exists.rb
      post_apply: print-outputs.rb
      pre_delete: delete-bucket-contents.rb
      post_delete: notify-admins.rb
  • On a very basic version, we would be talking about executable scripts in any language that would receive as parameters:
Action Parameters
pre_apply The compiled CloudFormation JSON template to be applied and parameter values.
post_apply The compiled CloudFormation JSON template that was applied, parameter values and output values.
pre_delete The compiled CloudFormation JSON template to be deleted, parameter values and output values.
post_delete Same as pre_delete.
  • If the script returns zero, then stack_master would continue execution. Any non-zero return code would interrupt the execution.

  • Actions would be located under an actions directory at the same level as parameters, templates, etc.

Way confusing error message

If you define a parameter this way:

  parameters.enable_dns_hostnames do
    description 'Enable DNS hostnames on the VPC?'
    type 'String'
  end

And then in the parameters file use a boolean type by mistake:

enable_dns_hostnames: true

instead of

enable_dns_hostnames: `true`

You get the not so awesome error message:

error: expected params[:parameters][0][:parameter_value] to be a string. Use --trace to view backtrace

And are left to your own devices to figure out which parameter has the broken type.

Would be nice if it at least gave a hint, even something like 'the parameter starting with the letter b has caused us problems' would be better than the current message ;-P

Specifying a config file moves the project root

StackMaster always expects the templates folder to be in the same directory as the stack master config.

To reproduce:

├── Gemfile
├── Gemfile.lock
├── configs
│   └── stack_master.yml
├── parameters
│   ├── mystack.yml
│   └── us-west-2
│       └── mystack.yml
└── templates
    └── mystack.json
arecker@localhost:testproject$ bundle exec stack_master diff -c configs/stack_master.yml 
Executing diff on mystack in us-west-2
StackMaster::TemplateCompiler::TemplateCompilationFailed Failed to compile /Users/arecker/Desktop/testproject/configs/templates/mystack.json.
 Caused by: Errno::ENOENT No such file or directory @ rb_sysopen - /Users/arecker/Desktop/testproject/configs/templates/mystack.json

New parameter resolvers

It would be useful to have parameter resolvers for the following:

  • EC2 KeyPair ID by name
  • SSL Certificate ID by name
  • IP address by EIP allocation ID

Add ability for 'UsePreviousValue' option when defining parameters.

I would like to be able to use the previous values of certain parameters when applying updates. Perhaps parameters which are not defined or are left blank could default to use the previous value if it is present in the stack already. Another option is to have some way to explicitly use previous values.

Issues on MRI 2.3

Whilst running on MRI 2.3, I've hit the following slew of errors.

$ bundle exec stack_master status

(eval):90: warning: constant ::TimeoutError is deprecated                                                                                                                           |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
(eval):90: warning: constant ::TimeoutError is deprecated=========                                                                                                                  |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
(eval):90: warning: constant ::TimeoutError is deprecated===========================                                                                                                |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
(eval):90: warning: constant ::TimeoutError is deprecated===============================================                                                                            |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
(eval):90: warning: constant ::TimeoutError is deprecated=================================================================                                                          |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
(eval):90: warning: constant ::TimeoutError is deprecated=====================================================================================                                      |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
(eval):90: warning: constant ::TimeoutError is deprecated=======================================================================================================                    |
(eval):90: warning: constant ::TimeoutError is deprecated
Thread.exclusive is deprecated, use Mutex
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:51:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/resources/aws.rb:19:in `load!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:556:in `block in compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bogo-0.1.32/lib/bogo/memoization.rb:60:in `memoize'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:545:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/sparkle_formation-1.2.0/lib/sparkle_formation/sparkle_formation.rb:105:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/template_compiler.rb:9:in `compile'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/stack.rb:70:in `generate'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:45:in `get_status'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:15:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `map'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/commands/status.rb:14:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `block in perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `tap'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/command.rb:9:in `perform'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:137:in `block (2 levels) in execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:178:in `call'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/command.rb:153:in `run'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:428:in `run_active_command'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/runner.rb:71:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/commander-4.3.8/lib/commander/delegates.rb:15:in `run!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/lib/stack_master/cli.rb:166:in `execute!'
/opt/boxen/rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/stack_master-0.1.0/bin/stack_master:13:in `<top (required)>'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `load'
/opt/boxen/rbenv/versions/2.3.0/bin/stack_master:23:in `<main>'
Fetching stack information: |=======================================================================================================================================================|
REGION         | STACK_NAME                  | STACK_STATUS    | DIFFERENT
---------------|-----------------------------|-----------------|----------
ap-southeast-2 | example-stack               |                 | Yes

Haven't dived into the cause (yet) but will post some more information once investigated.

Support for stack tags

Default tags can be set for all stacks or for regions, but it would be good to allow tags at the stack level. For example, if I have a stack that builds a particular tier or service, I want to add a tag: "service":"data-collector"

region_aliases:
  production: us-east-1
  staging: ap-southeast-2
stack_defaults:
  tags:
    application: my-awesome-app
region_defaults:
  us-east-1:
    secret_file: production.yml.gpg
    tags:
      environment: production
    notification_arns:
      - test_arn
  ap-southeast-2:
    secret_file: staging.yml.gpg
    tags:
      environment: staging
stacks:
  production:
    myapp-vpc:
      template: myapp_vpc.rb
***
      tags:
            service: data-collector
***

Delete prompt doesn't show stack name

When you are a deleting a CloudFormation stack, it's a little unsettling that the prompt doesn't confirm which stack you are deleting. The prompt simply says Really delete stack? (y/n)

Error in parameter yaml is very hard to track down

I had an error in a parameter YAML file:

ami:
  latest_ami:
    owners: <SNIP>
      filters:
        name: not-for-public-consumption-*

Note the extra spaces in front of the filters element.

The error that stack_master produces does not reference the file that cause it at all, making it somewhat of a pain to track down.

 sm apply us-west-2 not-for-public-consumption --trace
Executing apply on not-for-public-consumption  in us-west-2
/usr/local/var/rbenv/versions/2.2.4/lib/ruby/2.2.0/psych.rb:370:in `parse': (<unknown>): mapping values are not allowed in this context at line 4 column 13 (Psych::SyntaxError)
    from /usr/local/var/rbenv/versions/2.2.4/lib/ruby/2.2.0/psych.rb:370:in `parse_stream'
    from /usr/local/var/rbenv/versions/2.2.4/lib/ruby/2.2.0/psych.rb:318:in `parse'
    from /usr/local/var/rbenv/versions/2.2.4/lib/ruby/2.2.0/psych.rb:245:in `load'

Would be nice if there was something in the trace that told you the file that went boom, to avoid having to manually call YAML.load_file on each yaml file in turn to find the culprit.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.