prima-twig 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c85bc796545f4a0f38fa6be4380dec88e6c2fb771e8fe39d5044c57e9c0a6ee
4
- data.tar.gz: 4eef557dba95dc99c88f3f07951bb6219aa2fe63eb928c12e9c6a743954400e6
3
+ metadata.gz: 94183c3b380822569fa755c6cd914da1eb15d36378d93a8faf827cf8b2a8a119
4
+ data.tar.gz: 865c21d79254b4b4ad2e8034d665f74301585fd50f4fd20b019e0c6bde977183
5
5
  SHA512:
6
- metadata.gz: 4d2472dfc0c866aab6563b9cca251fd606256dc05c55bd450abbd65e5db86371327626bbb8001b06a3e15f50355f6d9a26fb1760513bc3a04698c043b9cbf572
7
- data.tar.gz: 4e86ab32ba1df95732055f29da69469fdd2c71d197233c5db726e18780910141206e9eb6ab5d94d2db621b3be8199d0bf22bc69bc8fdc24c26204eef6a0f1c19
6
+ metadata.gz: 7604f33c35162fde446fb5e91e92451745d7eab126d09f417c36c835254949f2690c77c3f4ebbd8248c885c6c19b7786498da0cfbfe44adcb798b5641e53613f
7
+ data.tar.gz: 24f093ea1a89deef5debd86afdf69b5dbb43f5f1a6aef9bc68f8a8a0b9e2e938baee20ba897116d8a2d66e5e8d7b8d60a9e1f6af267973003e7357c34d138884
@@ -326,7 +326,7 @@ class Release
326
326
  tags.push tag_keep_data
327
327
  end
328
328
 
329
- update_cluster_stack(cluster_stack_name, tags)
329
+ update_cluster_stack(cluster_stack_name, tags, get_stack_parameters(cluster_stack_name))
330
330
 
331
331
  output "Finito!".green
332
332
  end
@@ -581,9 +581,9 @@ class Release
581
581
  resp.load_balancers[0].dns_name
582
582
  end
583
583
 
584
- def update_cluster_stack(stack_name, tags = [])
584
+ def update_cluster_stack(stack_name, tags = [], parameters = [])
585
585
  stack_body = get_stack_template(stack_name)
586
- update_stack(stack_name, stack_body, [], tags)
586
+ update_stack(stack_name, stack_body, parameters, tags)
587
587
  end
588
588
 
589
589
  def choose_branch_to_deploy(project_name, select_master = false)
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require_relative '../lib/prima_twig'
5
+ require 'launchy'
6
+ require 'yaml'
7
+
8
+ class TwigPackerBuilder
9
+ include Command
10
+
11
+ def initialize
12
+ output 'Checking for new gem releases...'
13
+ unless `gem outdated`.lines.grep(/^prima-twig \(.*\)/).empty?
14
+ exec "gem update prima-twig && twig update-ami #{ARGV.join ' '}"
15
+ end
16
+ @templates_path = "#{Dir.pwd}/amis"
17
+ end
18
+
19
+ def execute!(args)
20
+ @template_name = args[0]
21
+ @env = args[1]
22
+ @country = args[2]
23
+
24
+ File.open("#{Dir.pwd}/ami_state.yml", 'r') do |f|
25
+ @config = YAML.load(f.read)
26
+ end
27
+
28
+ @country_conf = @config['countries'].detect { |country| country['name'] == @country }
29
+ stop_if(@country_conf.nil?, "Cannot find country #{@country} inside ami_state.yml, exiting...".red)
30
+
31
+ @ami_conf = @country_conf['amis'].detect { |ami| ami['template'] == @template_name and ami['env'] == @env }
32
+ stop_if(@ami_conf.nil?, "Cannot find an ami in #{@country} generated from #{@template_name} and env #{@env} inside ami_state.yml, exiting...".red)
33
+
34
+ output "Starting update process with #{@template_name} in env #{@env} in country #{@country}".light_green
35
+ output 'running packer (this could take some time)'.light_green
36
+ new_ami_id = execute_packer
37
+ stop_if(new_ami_id.to_s.empty?, 'Failed to generate AMI!'.red)
38
+
39
+ output "new ami id: #{new_ami_id}".light_green
40
+ output "ami id that can be replaced: #{@ami_conf['id']}".light_green
41
+ output "you can run \"aws-vault exec YOUR_CONTRY_PROFILE -- twig-update-ami #{@ami_conf['id']} #{new_ami_id}\" to update all stacks".light_green
42
+ output 'Done'.light_green
43
+ end
44
+
45
+ private
46
+
47
+ def execute_packer
48
+ execute_command "AWS_MAX_ATTEMPTS=90 AWS_POLL_DELAY_SECONDS=60 packer build -var env=#{@env} -var teams_mapping=#{@ami_conf['teams_mapping']} -var country=#{@country} -var aws_account=#{@country_conf['aws_account']} -machine-readable #{@templates_path}/#{@template_name} | tee build.log"
49
+
50
+ `grep 'artifact,0,id' build.log | cut -d, -f6 | cut -d: -f2`.sub(/\n/, '')
51
+ end
52
+
53
+ def help_content
54
+ <<-HELP
55
+
56
+ twig-packer-builder
57
+ ===========
58
+
59
+ Creates AMIs using umami's templates
60
+
61
+ Synopsis
62
+ --------
63
+
64
+ twig-packer-builder
65
+
66
+ Description
67
+ -----------
68
+
69
+ from umami main folder run
70
+ `aws-vault exec AWS_PROFILE_WITH_ACCESS_TO_COMMON_ACCOUNT -- twig-update-ami ${AMI_TEMPLATE} ${ENV} ${COUNTRY}`
71
+
72
+ Subcommand for Twig: <http://rondevera.github.io/twig/>
73
+ HELP
74
+ end
75
+
76
+ args = ARGV.dup
77
+
78
+ if args.include?('--help')
79
+ puts help_content
80
+ exit
81
+ end
82
+
83
+ TwigPackerBuilder.new.execute!(args)
84
+ end
@@ -1,166 +1,105 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'rubygems'
4
- require_relative '../lib/prima_twig.rb'
5
- require_relative '../lib/prima_aws_client.rb'
4
+ require_relative '../lib/prima_twig'
5
+ require_relative '../lib/prima_aws_client'
6
6
  require 'launchy'
7
- require 'json'
8
- require 'aws-sdk-s3'
7
+ require 'yaml'
9
8
 
10
9
  class TwigUpdateAmi
11
10
  include Command
12
11
  include PrimaAwsClient
12
+
13
13
  def initialize
14
- output 'Controllo se ci sono aggiornamenti da fare...'
15
- exec "gem update prima-twig && twig update-ami #{ARGV.join ' '}" unless `gem outdated`.lines.grep(/^prima-twig \(.*\)/).empty?
16
- @s3 = Aws::S3::Client.new
17
- @s3_bucket = 'prima-deploy'
18
- @templates_base_url = "https://s3-eu-west-1.amazonaws.com"
14
+ output 'Checking for new gem releases...'
15
+ unless `gem outdated`.lines.grep(/^prima-twig \(.*\)/).empty?
16
+ exec "gem update prima-twig && twig update-ami #{ARGV.join ' '}"
17
+ end
18
+ @state_path = "#{Dir.pwd}/ami_state.yml"
19
19
  end
20
20
 
21
21
  def execute!(args)
22
- update_amis(args[0], args[1], args[2], args[3], args[4])
23
- end
24
-
25
- private
22
+ old_ami = args[0]
23
+ new_ami = args[1]
26
24
 
27
- def update_amis(ami_template, ami_id, ami_name, ami_description, env)
28
- output "updating instance definition #{ami_template}".light_green
29
- Dir.chdir 'ami'
30
- update_instance_name(ami_id, ami_name, ami_description, ami_template)
31
- output 'running packer update (this could take some time)'.light_green
32
- new_ami_id = update_packer(ami_template, env)
33
- # new_ami_id = 'ami-026890988d91ee8c6'
34
- Dir.chdir '..'
35
- stop_if(new_ami_id.to_s.empty?, 'Failed to generate AMI!'.red)
36
- output "new ami id: #{new_ami_id}"
37
-
38
- output 'searching for ami to update...'
39
- ami_mappings = JSON.parse(@s3.get_object(bucket: @s3_bucket, key: "ami/ami-mappings.json")["body"].read())
40
- old_amis = update_ami_mappings(ami_mappings, ami_template, env, new_ami_id)
41
- stop_if(old_amis.empty?, "No ami to update! No #{ami_template} in env #{env}, exiting".yellow)
42
-
43
- output "retrieving stacks that uses old ami ids: #{old_amis}"
44
- exports = list_exports()
45
- stacks = get_stacks_from_exports(exports, old_amis)
46
- stop_if(stacks.empty?, "No stack to update found! This means that ami-mapping file is not in sync, please check manually")
25
+ output "retrieving stacks that uses old ami #{old_ami}".light_green
26
+ stacks = get_stacks_from_exports(list_exports, old_ami)
27
+ stop_if(stacks.empty?, 'No stack to update! Please check manually'.red)
47
28
 
48
29
  stacks.each do |stack|
49
- output "processing stack #{stack}"
30
+ output "processing stack #{stack}".light_green
50
31
  if stack.include?('qa')
51
- output "skipping stack #{stack} because is a qa"
32
+ output "skipping stack #{stack} because is a qa".yellow
52
33
  next
53
34
  else
54
- stack_tags = tags_to_hashes(get_stack_tags(stack))
55
- stack_tags['TemplateVersion'] = stack_tags['TemplateVersion'].to_i + 1
56
-
57
- if stack.include?('batch')
58
- stack_parameters = update_stack_parameters(get_stack_parameters(stack),
59
- [
60
- { parameter_key: 'AMIID', parameter_value: new_ami_id },
61
- { parameter_key: 'TemplateVersion', parameter_value: stack_tags['TemplateVersion'].to_s }
62
- ]
63
- )
64
- if stack.include?('offsite-backups')
65
- stack_template = File.read("./cloudformation/stacks/batch/compute-environment-offsite-backups.yml")
66
- else
67
- stack_template = File.read("./cloudformation/stacks/batch/compute-environment.yml")
68
- end
69
- else
70
- stack_parameters = update_stack_parameters(get_stack_parameters(stack),
71
- [
72
- { parameter_key: 'AMIID', parameter_value: new_ami_id },
73
- { parameter_key: 'DesiredCapacity', parameter_value: get_desired_capacity(stack).to_s },
74
- { parameter_key: 'TemplateVersion', parameter_value: stack_tags['TemplateVersion'].to_s }
75
- ]
76
- )
77
- stack_template = File.read("./cloudformation/stacks/asg/#{stack.to_s.split("/")[1]}.yml")
35
+ stack_parameters = get_stack_parameters(stack)
36
+ stack_parameters = update_stack_parameters(stack_parameters, 'AMIID', new_ami)
37
+ unless stack.include?('batch')
38
+ stack_parameters = update_stack_parameters(stack_parameters, 'DesiredCapacity', get_desired_capacity(stack).to_s)
78
39
  end
79
- update_stack(stack, stack_template, stack_parameters, hashes_to_tags(stack_tags))
40
+
41
+ update_stack_reuse_template(stack, stack_parameters)
80
42
  end
81
43
  end
82
44
 
83
45
  stacks.each do |stack|
84
- if stack.include?('qa')
85
- next
46
+ next if stack.include?('qa')
47
+
48
+ wait_for_stack_ready(stack, %w[CREATE_FAILED ROLLBACK_IN_PROGRESS ROLLBACK_FAILED DELETE_IN_PROGRESS DELETE_FAILED DELETE_COMPLETE UPDATE_ROLLBACK_FAILED UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_ROLLBACK_COMPLETE ROLLBACK_COMPLETE])
49
+ end
50
+
51
+ output 'Saving new ami in ami_state.yml'.light_green
52
+ File.open(@state_path, 'r') do |f|
53
+ @config = YAML.load(f.read)
54
+ end
55
+
56
+ @config['countries'].each do |country|
57
+ country['amis'].each do |ami|
58
+ ami['id'] = new_ami if ami['id'] == old_ami
86
59
  end
87
- wait_for_stack_ready(stack, ['CREATE_FAILED', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_FAILED', 'DELETE_IN_PROGRESS', 'DELETE_FAILED', 'DELETE_COMPLETE', 'UPDATE_ROLLBACK_FAILED', 'UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS', 'UPDATE_ROLLBACK_COMPLETE', 'ROLLBACK_COMPLETE'])
88
60
  end
89
61
 
90
- output 'writing new ami mapping'
91
- File.open("ami/ami-mappings.json", 'w+') do |f|
92
- mapping_file = JSON.pretty_generate(ami_mappings)
93
- f.write(mapping_file)
94
- @s3.put_object(bucket: @s3_bucket, key: "ami/ami-mappings.json", body: mapping_file)
62
+ File.open(@state_path, 'w+') do |f|
63
+ yaml = YAML.dump(@config)
64
+ f.write(yaml)
95
65
  end
96
66
 
97
- output 'Update finished! ( ͡° ͜ʖ ͡°)'
67
+ output 'Update finished! ( ͡° ͜ʖ ͡°)'.light_green
98
68
  end
99
69
 
100
- def get_stacks_from_exports(exports, old_amis)
70
+ private
71
+
72
+ def get_stacks_from_exports(exports, old_ami)
101
73
  stacks = []
102
- old_amis.each do |old_ami|
103
- exports.each do |export|
104
- if export.value.eql?(old_ami)
105
- stacks.insert(0,export.exporting_stack_id)
106
- end
107
- end
74
+ exports.each do |export|
75
+ stacks.insert(0, export.exporting_stack_id) if export.value.eql?(old_ami)
108
76
  end
109
77
  stacks
110
78
  end
111
79
 
112
- def update_ami_mappings(mappings, ami_template, env, new_ami_id)
113
- old_values = []
114
- mappings.each do |item|
115
- if item['ami_template'].eql?(ami_template) and item['env'].eql?(env)
116
- old_values.insert(0,item['ami_id'])
117
- item['ami_id'] = new_ami_id
118
- end
119
- end
120
- old_values.uniq
121
- end
122
-
123
- def update_stack_parameters(stack_parameters, new_parameters)
124
- new_parameters.each do |new_param|
125
- stack_parameters.reject{ |k| k["parameter_key"] == new_param["parameter_key"] }
126
- stack_parameters.push(new_param)
80
+ def update_stack_parameters(stack_parameters, key, value)
81
+ stack_parameters.each do |param|
82
+ param.parameter_value = value if param.parameter_key == key
127
83
  end
128
84
  stack_parameters
129
85
  end
130
86
 
131
- def update_instance_name(ami_id, ami_name, ami_description, ecs_json_path)
132
- ecs_json = JSON.parse File.read(ecs_json_path)
133
-
134
- ecs_json['builders'][0]['source_ami'] = ami_id
135
- ecs_json['builders'][0]['ami_name'] = ami_name
136
- ecs_json['builders'][0]['ami_description'] = ami_description
137
-
138
- File.open ecs_json_path, 'w' do |f|
139
- f.write(JSON.pretty_generate(ecs_json))
140
- end
141
- end
142
-
143
87
  def get_desired_capacity(stack_name)
144
88
  stack_outputs = get_stack_outputs(stack_name)
145
89
  stack_outputs.each do |out|
146
- if out.export_name.include?('EC2Fleet') or out.export_name.include?('AutoScalingGroup')
90
+ if out.export_name.include?('EC2Fleet') || out.export_name.include?('AutoScalingGroup') || out.export_name.include?('NodeGroup')
147
91
  return get_autoscaling_capacity(out.output_value)
148
92
  end
149
93
  end
150
94
  end
151
95
 
152
- def update_packer(json_filename, env)
153
- execute_command "AWS_MAX_ATTEMPTS=90 AWS_POLL_DELAY_SECONDS=60 packer build -var datadog_apikey=`biscuit get -f ../configs/secrets/common.yml common_production_apikey_datadog` -var github_token=`biscuit get -f ../configs/secrets/common.yml common_private_repo_github_token` -var drone_key=\"`biscuit get -f ../configs/secrets/common.yml drone_license_key`\" -var env=#{env} -machine-readable ./#{json_filename} | tee build.log"
154
- `grep 'artifact,0,id' build.log | cut -d, -f6 | cut -d: -f2`.sub(/\n/, '')
155
- end
156
-
157
96
  def help_content
158
97
  <<-HELP
159
98
 
160
99
  twig-update-ami
161
100
  ===========
162
101
 
163
- Updates ami version and applies it to stacks on cloudformation
102
+ Updates AutoScalingGroups/BatchComputeEnvs with a new AMI
164
103
 
165
104
  Synopsis
166
105
  --------
@@ -170,8 +109,8 @@ class TwigUpdateAmi
170
109
  Description
171
110
  -----------
172
111
 
173
- from artemide main folder run
174
- `twig-update-ami ${AMI_TEMPLATE} ${AMI_ID} ${AMI_NAME} ${AMI_DESCRIPTION} ${ENV}`
112
+ from tsunami main folder run
113
+ `aws-vault exec AWS_PROFILE_WITH_ACCESS_TO_COUNTRY_ACCOUNT -- twig-update-ami ${OLD_AMI} ${NEW_AMI}`
175
114
 
176
115
  Subcommand for Twig: <http://rondevera.github.io/twig/>
177
116
  Author: Eugenio Laghi <https://github.com/eugeniolaghi>
@@ -179,13 +118,6 @@ class TwigUpdateAmi
179
118
  HELP
180
119
  end
181
120
 
182
- class ::Hash
183
- def deep_merge(second)
184
- merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
185
- self.merge(second.to_h, &merger)
186
- end
187
- end
188
-
189
121
  args = ARGV.dup
190
122
 
191
123
  if args.include?('--help')
@@ -175,6 +175,32 @@ module PrimaAwsClient
175
175
  end
176
176
  end
177
177
 
178
+ def update_stack_reuse_template(stack_name, parameters = [], tags = [], role = nil)
179
+ cf_args = {
180
+ stack_name: stack_name,
181
+ use_previous_template: true,
182
+ parameters: parameters,
183
+ tags: tags,
184
+ capabilities: ['CAPABILITY_IAM']
185
+ }
186
+
187
+ if role != nil then
188
+ cf_args.merge!(role_arn: role)
189
+ end
190
+
191
+ begin
192
+ cf_client.update_stack(cf_args)
193
+ rescue Aws::CloudFormation::Errors::Throttling => e
194
+ output 'Throttling, retrying in 15 seconds'.red
195
+ sleep 15
196
+ update_stack(stack_name, template_body, parameters = [], tags = [])
197
+ rescue Aws::CloudFormation::Errors::ValidationError => e
198
+ raise e
199
+ else
200
+ output "L'update dello stack #{stack_name} è stato avviato".green
201
+ end
202
+ end
203
+
178
204
  def stack_exists?(stack_name)
179
205
  begin
180
206
  cf_client.describe_stacks(stack_name: stack_name)
@@ -474,21 +500,4 @@ module PrimaAwsClient
474
500
  resp = ec2_client.describe_spot_fleet_requests(spot_fleet_request_ids: [fleet_arn])
475
501
  resp.spot_fleet_request_configs[0].spot_fleet_request_config.target_capacity
476
502
  end
477
-
478
- def hashes_to_tags(hashes)
479
- tags = []
480
- hkeys = hashes.keys
481
- hkeys.each do |hkey|
482
- tags.insert(0, { key: hkey, value: hashes[hkey].to_s })
483
- end
484
- tags
485
- end
486
-
487
- def tags_to_hashes(tags)
488
- hash = Hash.new
489
- tags.each do |tags_obj|
490
- hash[tags_obj.key] = tags_obj.value
491
- end
492
- hash
493
- end
494
503
  end
@@ -8,8 +8,8 @@ require 'aws-sdk-core'
8
8
  require 'rubyflare'
9
9
 
10
10
  class Prima
11
- CONFIG_KEYS=['github', 'cloudflare_email', 'cloudflare_apikey', 'aws_username', 'aws_password', 'prima_apikey']
12
- attr_reader :gh, :twig, :config, :rugged, :aws
11
+ CONFIG_KEYS=['github', 'cloudflare_email', 'cloudflare_apikey']
12
+ attr_reader :gh, :twig, :config, :rugged
13
13
 
14
14
  def initialize
15
15
  @twig = Twig.new(:read_options => true, :max_days_old => 30)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prima-twig
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matteo Giachino
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2020-07-21 00:00:00.000000000 Z
17
+ date: 2020-11-16 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: aws-sdk-autoscaling
@@ -73,21 +73,21 @@ dependencies:
73
73
  - !ruby/object:Gem::Version
74
74
  version: '1'
75
75
  - !ruby/object:Gem::Dependency
76
- name: aws-sdk-ec2
76
+ name: aws-sdk-core
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: '1'
81
+ version: '3'
82
82
  type: :runtime
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '1'
88
+ version: '3'
89
89
  - !ruby/object:Gem::Dependency
90
- name: aws-sdk-ecs
90
+ name: aws-sdk-ec2
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
@@ -101,7 +101,7 @@ dependencies:
101
101
  - !ruby/object:Gem::Version
102
102
  version: '1'
103
103
  - !ruby/object:Gem::Dependency
104
- name: aws-sdk-elasticloadbalancingv2
104
+ name: aws-sdk-ecs
105
105
  requirement: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
@@ -115,7 +115,7 @@ dependencies:
115
115
  - !ruby/object:Gem::Version
116
116
  version: '1'
117
117
  - !ruby/object:Gem::Dependency
118
- name: aws-sdk-s3
118
+ name: aws-sdk-elasticloadbalancingv2
119
119
  requirement: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
@@ -129,19 +129,19 @@ dependencies:
129
129
  - !ruby/object:Gem::Version
130
130
  version: '1'
131
131
  - !ruby/object:Gem::Dependency
132
- name: aws-sdk-core
132
+ name: aws-sdk-s3
133
133
  requirement: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: '3'
137
+ version: '1'
138
138
  type: :runtime
139
139
  prerelease: false
140
140
  version_requirements: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: '3'
144
+ version: '1'
145
145
  - !ruby/object:Gem::Dependency
146
146
  name: colorize
147
147
  requirement: !ruby/object:Gem::Requirement
@@ -286,11 +286,13 @@ description: Our tools to manage git and github
286
286
  email: matteo.giachino@prima.it
287
287
  executables:
288
288
  - twig-feature
289
+ - twig-packer-builder
289
290
  - twig-update-ami
290
291
  extensions: []
291
292
  extra_rdoc_files: []
292
293
  files:
293
294
  - bin/twig-feature
295
+ - bin/twig-packer-builder
294
296
  - bin/twig-update-ami
295
297
  - lib/command.rb
296
298
  - lib/prima_aws_client.rb