stack_master 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +208 -0
  7. data/Rakefile +11 -0
  8. data/apply_demo.gif +0 -0
  9. data/bin/stack_master +16 -0
  10. data/example/simple/Gemfile +3 -0
  11. data/example/simple/parameters/myapp_vpc.yml +1 -0
  12. data/example/simple/parameters/myapp_web.yml +2 -0
  13. data/example/simple/stack_master.yml +13 -0
  14. data/example/simple/templates/myapp_vpc.rb +39 -0
  15. data/example/simple/templates/myapp_web.rb +16 -0
  16. data/features/apply.feature +241 -0
  17. data/features/delete.feature +43 -0
  18. data/features/diff.feature +191 -0
  19. data/features/events.feature +38 -0
  20. data/features/init.feature +6 -0
  21. data/features/outputs.feature +49 -0
  22. data/features/region_aliases.feature +66 -0
  23. data/features/resources.feature +45 -0
  24. data/features/stack_defaults.feature +88 -0
  25. data/features/status.feature +124 -0
  26. data/features/step_definitions/stack_steps.rb +50 -0
  27. data/features/support/env.rb +14 -0
  28. data/lib/stack_master.rb +81 -0
  29. data/lib/stack_master/aws_driver/cloud_formation.rb +56 -0
  30. data/lib/stack_master/cli.rb +164 -0
  31. data/lib/stack_master/command.rb +13 -0
  32. data/lib/stack_master/commands/apply.rb +104 -0
  33. data/lib/stack_master/commands/delete.rb +53 -0
  34. data/lib/stack_master/commands/diff.rb +31 -0
  35. data/lib/stack_master/commands/events.rb +39 -0
  36. data/lib/stack_master/commands/init.rb +109 -0
  37. data/lib/stack_master/commands/list_stacks.rb +16 -0
  38. data/lib/stack_master/commands/outputs.rb +27 -0
  39. data/lib/stack_master/commands/resources.rb +33 -0
  40. data/lib/stack_master/commands/status.rb +47 -0
  41. data/lib/stack_master/commands/validate.rb +17 -0
  42. data/lib/stack_master/config.rb +86 -0
  43. data/lib/stack_master/ctrl_c.rb +4 -0
  44. data/lib/stack_master/parameter_loader.rb +17 -0
  45. data/lib/stack_master/parameter_resolver.rb +45 -0
  46. data/lib/stack_master/parameter_resolvers/secret.rb +42 -0
  47. data/lib/stack_master/parameter_resolvers/security_group.rb +20 -0
  48. data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +29 -0
  49. data/lib/stack_master/parameter_resolvers/stack_output.rb +53 -0
  50. data/lib/stack_master/prompter.rb +14 -0
  51. data/lib/stack_master/security_group_finder.rb +29 -0
  52. data/lib/stack_master/sns_topic_finder.rb +27 -0
  53. data/lib/stack_master/stack.rb +96 -0
  54. data/lib/stack_master/stack_definition.rb +49 -0
  55. data/lib/stack_master/stack_differ.rb +80 -0
  56. data/lib/stack_master/stack_events/fetcher.rb +45 -0
  57. data/lib/stack_master/stack_events/presenter.rb +27 -0
  58. data/lib/stack_master/stack_events/streamer.rb +55 -0
  59. data/lib/stack_master/stack_states.rb +34 -0
  60. data/lib/stack_master/template_compiler.rb +21 -0
  61. data/lib/stack_master/test_driver/cloud_formation.rb +139 -0
  62. data/lib/stack_master/testing.rb +7 -0
  63. data/lib/stack_master/utils.rb +31 -0
  64. data/lib/stack_master/validator.rb +25 -0
  65. data/lib/stack_master/version.rb +3 -0
  66. data/logo.png +0 -0
  67. data/script/buildkite/bundle.sh +5 -0
  68. data/script/buildkite/clean.sh +3 -0
  69. data/script/buildkite_rspec.sh +27 -0
  70. data/spec/fixtures/parameters/myapp_vpc.yml +1 -0
  71. data/spec/fixtures/stack_master.yml +35 -0
  72. data/spec/fixtures/templates/myapp_vpc.json +1 -0
  73. data/spec/spec_helper.rb +99 -0
  74. data/spec/stack_master/commands/apply_spec.rb +92 -0
  75. data/spec/stack_master/commands/delete_spec.rb +40 -0
  76. data/spec/stack_master/commands/init_spec.rb +17 -0
  77. data/spec/stack_master/commands/status_spec.rb +38 -0
  78. data/spec/stack_master/commands/validate_spec.rb +26 -0
  79. data/spec/stack_master/config_spec.rb +81 -0
  80. data/spec/stack_master/parameter_loader_spec.rb +81 -0
  81. data/spec/stack_master/parameter_resolver_spec.rb +58 -0
  82. data/spec/stack_master/parameter_resolvers/secret_spec.rb +66 -0
  83. data/spec/stack_master/parameter_resolvers/security_group_spec.rb +17 -0
  84. data/spec/stack_master/parameter_resolvers/sns_topic_name_spec.rb +43 -0
  85. data/spec/stack_master/parameter_resolvers/stack_output_spec.rb +77 -0
  86. data/spec/stack_master/security_group_finder_spec.rb +49 -0
  87. data/spec/stack_master/sns_topic_finder_spec.rb +25 -0
  88. data/spec/stack_master/stack_definition_spec.rb +37 -0
  89. data/spec/stack_master/stack_differ_spec.rb +34 -0
  90. data/spec/stack_master/stack_events/fetcher_spec.rb +65 -0
  91. data/spec/stack_master/stack_events/presenter_spec.rb +18 -0
  92. data/spec/stack_master/stack_events/streamer_spec.rb +33 -0
  93. data/spec/stack_master/stack_spec.rb +157 -0
  94. data/spec/stack_master/template_compiler_spec.rb +48 -0
  95. data/spec/stack_master/test_driver/cloud_formation_spec.rb +24 -0
  96. data/spec/stack_master/utils_spec.rb +30 -0
  97. data/spec/stack_master/validator_spec.rb +38 -0
  98. data/stack_master.gemspec +38 -0
  99. data/stacktemplates/parameter_region.yml +3 -0
  100. data/stacktemplates/parameter_stack_name.yml +3 -0
  101. data/stacktemplates/stack.json.erb +20 -0
  102. data/stacktemplates/stack_master.yml.erb +6 -0
  103. metadata +427 -0
@@ -0,0 +1,124 @@
1
+ Feature: Status command
2
+
3
+ Background:
4
+ Given a file named "stack_master.yml" with:
5
+ """
6
+ stacks:
7
+ us_east_1:
8
+ stack1:
9
+ template: stack1.json
10
+ stack2:
11
+ template: stack2.json
12
+ stack3:
13
+ template: stack3.json
14
+ """
15
+ And a directory named "parameters"
16
+ And a file named "parameters/stack1.yml" with:
17
+ """
18
+ KeyName: my-key
19
+ """
20
+ And a directory named "templates"
21
+ And a file named "templates/stack1.json" with:
22
+ """
23
+ {
24
+ "AWSTemplateFormatVersion": "2010-09-09",
25
+ "Description": "Test template",
26
+ "Parameters": {
27
+ "InstanceTypeParameter" : { "Type" : "String" }
28
+ },
29
+ "Mappings": {},
30
+ "Resources": {
31
+ "MyAwesomeQueue" : {
32
+ "Type" : "AWS::SQS::Queue",
33
+ "Properties" : {
34
+ "VisibilityTimeout" : "1"
35
+ }
36
+ }
37
+ },
38
+ "Outputs": {}
39
+ }
40
+ """
41
+ And a file named "templates/stack2.json" with:
42
+ """
43
+ {
44
+ "AWSTemplateFormatVersion": "2010-09-09",
45
+ "Description": "Test template",
46
+ "Parameters": {},
47
+ "Mappings": {},
48
+ "Resources": {
49
+ "MoarQueue" : {
50
+ "Type" : "AWS::SQS::Queue",
51
+ "Properties" : {
52
+ "VisibilityTimeout" : "1"
53
+ }
54
+ }
55
+ },
56
+ "Outputs": {}
57
+ }
58
+ """
59
+ And a file named "templates/stack3.json" with:
60
+ """
61
+ {
62
+ }
63
+ """
64
+ And I set the environment variables to:
65
+ | variable | value |
66
+ | STUB_AWS | true |
67
+
68
+ Scenario: Run status command and get a list of stack statuii
69
+ Given I set the environment variables to:
70
+ | variable | value |
71
+ | ANSWER | y |
72
+ And I stub the following stacks:
73
+ | stack_id | stack_name | parameters | region | stack_status |
74
+ | 1 | stack1 | KeyName=my-key | us-east-1 | CREATE_COMPLETE |
75
+ | 2 | stack2 | | us-east-1 | UPDATE_COMPLETE |
76
+ And I stub a template for the stack "stack1":
77
+ """
78
+ {
79
+ "AWSTemplateFormatVersion": "2010-09-09",
80
+ "Description": "Test template",
81
+ "Parameters": {
82
+ "InstanceTypeParameter" : { "Type" : "String" }
83
+ },
84
+ "Mappings": {},
85
+ "Resources": {
86
+ "MyAwesomeQueue" : {
87
+ "Type" : "AWS::SQS::Queue",
88
+ "Properties" : {
89
+ "VisibilityTimeout" : "7"
90
+ }
91
+ }
92
+ },
93
+ "Outputs": {}
94
+ }
95
+ """
96
+ And I stub a template for the stack "stack2":
97
+ """
98
+ {
99
+ "AWSTemplateFormatVersion": "2010-09-09",
100
+ "Description": "Test template",
101
+ "Parameters": {},
102
+ "Mappings": {},
103
+ "Resources": {
104
+ "MoarQueue" : {
105
+ "Type" : "AWS::SQS::Queue",
106
+ "Properties" : {
107
+ "VisibilityTimeout" : "1"
108
+ }
109
+ }
110
+ },
111
+ "Outputs": {}
112
+ }
113
+ """
114
+
115
+ When I run `stack_master status --trace` interactively
116
+ And the output should contain all of these lines:
117
+ | REGION \| STACK_NAME \| STACK_STATUS \| DIFFERENT |
118
+ | ----------\|------------\|-----------------\|---------- |
119
+ | us-east-1 \| stack1 \| CREATE_COMPLETE \| Yes |
120
+ | us-east-1 \| stack2 \| UPDATE_COMPLETE \| No |
121
+ | us-east-1 \| stack3 \| \| Yes |
122
+
123
+ Then the exit status should be 0
124
+
@@ -0,0 +1,50 @@
1
+ Given(/^I stub the following stack events:$/) do |table|
2
+ table.hashes.each do |row|
3
+ row.symbolize_keys!
4
+ StackMaster.cloud_formation_driver.add_stack_event(row)
5
+ end
6
+ end
7
+
8
+ Given(/^I stub the following stack resources:$/) do |table|
9
+ table.hashes.each do |row|
10
+ row.symbolize_keys!
11
+ StackMaster.cloud_formation_driver.add_stack_resource(row)
12
+ end
13
+ end
14
+
15
+ def extract_hash_from_kv_string(string)
16
+ string.to_s.split(',').inject({}) do |hash, kv|
17
+ key, value = kv.split('=')
18
+ hash[key] = value
19
+ hash
20
+ end
21
+ end
22
+
23
+
24
+ Given(/^I stub the following stacks:$/) do |table|
25
+ table.hashes.each do |row|
26
+ row.symbolize_keys!
27
+ row[:parameters] = StackMaster::Utils.hash_to_aws_parameters(extract_hash_from_kv_string(row[:parameters]))
28
+ outputs = extract_hash_from_kv_string(row[:outputs]).inject([]) do |array, (k, v)|
29
+ array << OpenStruct.new(output_key: k, output_value: v)
30
+ array
31
+ end
32
+ row[:outputs] = outputs
33
+ StackMaster.cloud_formation_driver.add_stack(row)
34
+ end
35
+ end
36
+
37
+ Given(/^I stub a template for the stack "([^"]*)":$/) do |stack_name, template_body|
38
+ StackMaster.cloud_formation_driver.set_template(stack_name, template_body)
39
+ end
40
+
41
+ Then(/^the stack "([^"]*)" should have a policy with the following:$/) do |stack_name, policy|
42
+ stack_policy_body = StackMaster.cloud_formation_driver.get_stack_policy(stack_name: stack_name).stack_policy_body
43
+ expect(stack_policy_body).to eq policy
44
+ end
45
+
46
+ Then(/^the stack "([^"]*)" should contain this notification ARN "([^"]*)"$/) do |stack_name, notification_arn|
47
+ stack = StackMaster.cloud_formation_driver.describe_stacks(stack_name: stack_name).stacks.first
48
+ expect(stack).to be
49
+ expect(stack.notification_arns).to include notification_arn
50
+ end
@@ -0,0 +1,14 @@
1
+ require 'aruba/cucumber'
2
+ require 'stack_master'
3
+ require 'stack_master/testing'
4
+ require 'aruba/in_process'
5
+ require 'pry'
6
+
7
+ Aruba.configure do |config|
8
+ config.command_launcher = :in_process
9
+ config.main_class = StackMaster::CLI
10
+ end
11
+
12
+ Before do
13
+ StackMaster.cloud_formation_driver.reset
14
+ end
@@ -0,0 +1,81 @@
1
+ require "commander"
2
+ require "yaml"
3
+ require "virtus"
4
+ require "aws-sdk"
5
+ require "diffy"
6
+ require "colorize"
7
+ require "table_print"
8
+ require 'active_support/core_ext/string'
9
+ require "erb"
10
+ require 'sparkle_formation'
11
+ require 'dotgpg'
12
+
13
+ require "stack_master/ctrl_c"
14
+ require "stack_master/command"
15
+ require "stack_master/version"
16
+ require "stack_master/stack"
17
+ require "stack_master/prompter"
18
+ require "stack_master/aws_driver/cloud_formation"
19
+ require "stack_master/test_driver/cloud_formation"
20
+ require "stack_master/stack_events/fetcher"
21
+ require "stack_master/stack_events/presenter"
22
+ require "stack_master/stack_events/streamer"
23
+ require "stack_master/stack_states"
24
+ require "stack_master/sns_topic_finder"
25
+ require "stack_master/security_group_finder"
26
+ require "stack_master/parameter_loader"
27
+ require "stack_master/parameter_resolver"
28
+ require "stack_master/parameter_resolvers/stack_output"
29
+ require "stack_master/parameter_resolvers/secret"
30
+ require "stack_master/parameter_resolvers/sns_topic_name"
31
+ require "stack_master/parameter_resolvers/security_group"
32
+ require "stack_master/utils"
33
+ require "stack_master/config"
34
+ require "stack_master/stack_definition"
35
+ require "stack_master/template_compiler"
36
+ require "stack_master/commands/apply"
37
+ require "stack_master/commands/events"
38
+ require "stack_master/commands/outputs"
39
+ require "stack_master/commands/init"
40
+ require "stack_master/commands/diff"
41
+ require "stack_master/commands/list_stacks"
42
+ require "stack_master/commands/validate"
43
+ require "stack_master/commands/resources"
44
+ require "stack_master/commands/delete"
45
+ require "stack_master/commands/status"
46
+ require "stack_master/stack_differ"
47
+ require "stack_master/validator"
48
+ require "stack_master/cli"
49
+
50
+ module StackMaster
51
+ extend self
52
+
53
+ def base_dir
54
+ File.expand_path(File.join(File.dirname(__FILE__), ".."))
55
+ end
56
+
57
+ def cloud_formation_driver
58
+ @cloud_formation_driver ||= AwsDriver::CloudFormation.new
59
+ end
60
+
61
+ def cloud_formation_driver=(value)
62
+ @cloud_formation_driver = value
63
+ end
64
+
65
+ def stdout
66
+ @stdout || $stdout
67
+ end
68
+
69
+ def stdout=(io)
70
+ @stdout = io
71
+ end
72
+
73
+ def stderr
74
+ @stderr || $stderr
75
+ end
76
+
77
+ def stderr=(io)
78
+ @stderr = io
79
+ end
80
+ end
81
+
@@ -0,0 +1,56 @@
1
+ module StackMaster
2
+ module AwsDriver
3
+ class CloudFormation
4
+ def set_region(region)
5
+ @region = region
6
+ @cf = nil
7
+ end
8
+
9
+ def delete_stack(options)
10
+ cf.delete_stack(options)
11
+ end
12
+
13
+ def describe_stacks(options)
14
+ cf.describe_stacks(options)
15
+ end
16
+
17
+ def cancel_update_stack(options)
18
+ cf.cancel_update_stack(options)
19
+ end
20
+
21
+ def describe_stack_resources(options)
22
+ cf.describe_stack_resources(options)
23
+ end
24
+
25
+ def get_template(options)
26
+ cf.get_template(options)
27
+ end
28
+
29
+ def get_stack_policy(options)
30
+ cf.get_stack_policy(options)
31
+ end
32
+
33
+ def describe_stack_events(options)
34
+ cf.describe_stack_events(options)
35
+ end
36
+
37
+ def update_stack(options)
38
+ cf.update_stack(options)
39
+ end
40
+
41
+ def create_stack(options)
42
+ cf.create_stack(options)
43
+ end
44
+
45
+ def validate_template(options)
46
+ cf.validate_template(options)
47
+ end
48
+
49
+ private
50
+
51
+ def cf
52
+ @cf ||= Aws::CloudFormation::Client.new(region: @region)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,164 @@
1
+ require 'commander'
2
+
3
+ module StackMaster
4
+ class CLI
5
+ include Commander::Methods
6
+
7
+ def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
8
+ @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
9
+ Commander::Runner.instance_variable_set('@singleton', Commander::Runner.new(argv))
10
+ StackMaster.stdout = @stdout
11
+ StackMaster.stderr = @stderr
12
+ TablePrint::Config.io = StackMaster.stdout
13
+ end
14
+
15
+ def execute!
16
+ program :name, 'StackMaster'
17
+ program :version, '0.0.1'
18
+ program :description, 'AWS Stack Management'
19
+
20
+ global_option '-c', '--config FILE', 'Config file to use'
21
+
22
+ command :apply do |c|
23
+ c.syntax = 'stack_master apply [region_or_alias] [stack_name]'
24
+ c.summary = 'Creates or updates a stack'
25
+ c.description = "Creates or updates a stack. Shows a diff of the proposed stack's template and parameters. Tails stack events until CloudFormation has completed."
26
+ c.example 'update a stack named myapp-vpc in us-east-1', 'stack_master apply us-east-1 myapp-vpc'
27
+ c.action do |args, options|
28
+ execute_stack_command(StackMaster::Commands::Apply, args, options)
29
+ end
30
+ end
31
+
32
+ command :outputs do |c|
33
+ c.syntax = 'stack_master outputs [region_or_alias] [stack_name]'
34
+ c.summary = 'Displays outputs for a stack'
35
+ c.description = "Displays outputs for a stack"
36
+ c.action do |args, options|
37
+ execute_stack_command(StackMaster::Commands::Outputs, args, options)
38
+ end
39
+ end
40
+
41
+ command :init do |c|
42
+ c.syntax = 'stack_master init [region_or_alias] [stack_name]'
43
+ c.summary = 'Initialises the expected directory structure and stack_master.yml file'
44
+ c.description = 'Initialises the expected directory structure and stack_master.yml file'
45
+ c.option('--overwrite', 'Overwrite existing files')
46
+ c.action do |args, options|
47
+ unless args.size == 2
48
+ say "Invalid arguments. stack_master init [region] [stack_name]"
49
+ else
50
+ StackMaster::Commands::Init.perform(options.overwrite, *args)
51
+ end
52
+ end
53
+ end
54
+
55
+ command :diff do |c|
56
+ c.syntax = 'stack_master diff [region_or_alias] [stack_name]'
57
+ c.summary = "Shows a diff of the proposed stack's template and parameters"
58
+ c.description = "Shows a diff of the proposed stack's template and parameters"
59
+ c.example 'diff a stack named myapp-vpc in us-east-1', 'stack_master diff us-east-1 myapp-vpc'
60
+ c.action do |args, options|
61
+ execute_stack_command(StackMaster::Commands::Diff, args, options)
62
+ end
63
+ end
64
+
65
+ command :events do |c|
66
+ c.syntax = 'stack_master events [region_or_alias] [stack_name]'
67
+ c.summary = "Shows events for a stack"
68
+ c.description = "Shows events for a stack"
69
+ c.example 'show events for myapp-vpc in us-east-1', 'stack_master events us-east-1 myapp-vpc'
70
+ c.option '--number Integer', Integer, 'Number of recent events to show'
71
+ c.option '--all', 'Show all events'
72
+ c.option '--tail', 'Tail events'
73
+ c.action do |args, options|
74
+ execute_stack_command(StackMaster::Commands::Events, args, options)
75
+ end
76
+ end
77
+
78
+ command :resources do |c|
79
+ c.syntax = 'stack_master resources [region] [stack_name]'
80
+ c.summary = "Shows stack resources"
81
+ c.description = "Shows stack resources"
82
+ c.action do |args, options|
83
+ execute_stack_command(StackMaster::Commands::Resources, args, options)
84
+ end
85
+ end
86
+
87
+ command :list do |c|
88
+ c.syntax = 'stack_master list'
89
+ c.summary = 'List stack definitions'
90
+ c.description = 'List stack definitions'
91
+ c.action do |args, options|
92
+ say "Invalid arguments." if args.size > 0
93
+ config = load_config(options.config)
94
+ StackMaster::Commands::ListStacks.perform(config)
95
+ end
96
+ end
97
+
98
+ command :validate do |c|
99
+ c.syntax = 'stack_master validate [region_or_alias] [stack_name]'
100
+ c.summary = 'Validate a template'
101
+ c.description = 'Validate a template'
102
+ c.example 'validate a stack named myapp-vpc in us-east-1', 'stack_master validate us-east-1 myapp-vpc'
103
+ c.action do |args, options|
104
+ execute_stack_command(StackMaster::Commands::Validate, args, options)
105
+ end
106
+ end
107
+
108
+ command :status do |c|
109
+ c.syntax = 'stack_master status'
110
+ c.summary = 'Check the current status stacks.'
111
+ c.description = 'Checks the status of all stacks defined in the stack_master.yml file. Warning this operation can be somewhat slow.'
112
+ c.example 'description', 'Check the status of all stack definitions'
113
+ c.action do |args, options|
114
+ say "Invalid arguments. stack_master status" and return unless args.size == 0
115
+ config = load_config(options.config)
116
+ StackMaster::Commands::Status.perform(config)
117
+ end
118
+ end
119
+
120
+ command :delete do |c|
121
+ c.syntax = 'stack_master delete [region] [stack_name]'
122
+ c.summary = 'Delete an existing stack'
123
+ c.description = 'Deletes a stack. The stack does not necessarily have to appear in the stack_master.yml file.'
124
+ c.example 'description', 'Delete a stack'
125
+ c.action do |args, options|
126
+ unless args.size == 2
127
+ say "Invalid arguments. stack_master delete [region] [stack_name]"
128
+ return
129
+ end
130
+ StackMaster.cloud_formation_driver.set_region(args[0])
131
+ StackMaster::Commands::Delete.perform(*args)
132
+ end
133
+ end
134
+
135
+ run!
136
+ end
137
+
138
+ def load_config(file)
139
+ stack_file = file || 'stack_master.yml'
140
+ StackMaster::Config.load!(stack_file)
141
+ rescue Errno::ENOENT => e
142
+ say "Failed to load config file #{stack_file}"
143
+ exit 1
144
+ end
145
+
146
+ def execute_stack_command(command, args, options)
147
+ unless args.size == 2
148
+ say "Invalid arguments. stack_master #{command.name.split('::').last.downcase} [region] [stack_name]"
149
+ return
150
+ end
151
+ config = load_config(options.config)
152
+ aliased_region, stack_name = args
153
+ region = Utils.underscore_to_hyphen(config.unalias_region(aliased_region))
154
+ stack_name = Utils.underscore_to_hyphen(stack_name)
155
+ StackMaster.cloud_formation_driver.set_region(region)
156
+ stack_definition ||= config.find_stack(region, stack_name)
157
+ if stack_definition.nil?
158
+ say "Could not find stack definition #{stack_name} in region #{region}"
159
+ return
160
+ end
161
+ command.perform(config, stack_definition, options)
162
+ end
163
+ end
164
+ end