cuffsert 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac6f917346598ca9866df6ae9806f0db45045430
4
- data.tar.gz: bc2fdead5386f5f2615818c9725c73620a56cddd
3
+ metadata.gz: d02e68aa26e398ea76222c0f564477dce26d44c9
4
+ data.tar.gz: 0984fe4d90622252665ec5a718d50cd6c0d76399
5
5
  SHA512:
6
- metadata.gz: cacc8cdef1aa751747562735e9aa8b7c302df010487a79a379b21af716f143017fc1e027442f6275fa189f1e7d94c20bfe6ec878573c85d45fdd6e502d110389
7
- data.tar.gz: 79d814fad2ceeed682b769979b5e580da02cdcdc9111306e3d53a95b5b328ed69d032979633a687e34e91b575bca58bd23504ba769cee59055970f96d69cadc8
6
+ metadata.gz: 61871cff6aadd97c39413dcdc28f7acce5641ab6a7cbc191aa336b6b7307c05ba2f8cbce4843e0ddf1f23086e362149c9e317fa6b760be64d1f4a15d28bba3ed
7
+ data.tar.gz: 775eebfd1732099b53d309686d0be459529e5f11c507e19d4ffe85e5ecf14edaa7cbcf13aa89db4c3421c6b2b4852deffa44b892d251e4881776ca93b6130fe1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cuffsert (0.9.1)
4
+ cuffsert (0.10.0)
5
5
  aws-sdk
6
6
  colorize
7
7
  ruby-termios
data/README.md CHANGED
@@ -2,9 +2,27 @@
2
2
 
3
3
  The primary goal of cuffsert is to provide a quick "up-arrow-enter" loading of a CloudFormation stack with good feedback, removing the need to click through three pesky screens each time. It figures out whether the stack needs to be created or rolled-back and whether it needs to be deleted first.
4
4
 
5
- Cuffsert allows encoding the metadata and commandline arguments needed to load a template in a versionable file which takes CloudFormation the last mile to really become an infrastructure-as-code platform.
5
+ ## Getting started
6
6
 
7
- ## Usage
7
+ Update a stack from a provided template without changing any parameters on the stack:
8
+
9
+ ```bash
10
+ cuffsert -n my-stack ./my-template.json
11
+ ```
12
+
13
+ If `./my-template.json` has no parameters the above command would create the stack if it did not already exist (so make sure you spell the stack name correctly :).
14
+
15
+ If you also want to provide a value for a stack parameter (whether on creation or update), you can use `-p key=value` to pass parameters. For all other parameters, cuffsert will tell CloudFormation to use the existing value.
16
+
17
+ Cuffsert can not (yet) be executed without a template in order to only change parameters.
18
+
19
+ ## Parameters under version control
20
+
21
+ Cuffsert also allows encoding the parameters (and some commandline arguments) needed to load a template in a YAML file which you can put under versiin control. This takes CloudFormation the last mile to really become an infrastructure-as-code platform.
22
+
23
+ ## Usage with file
24
+
25
+ cuffsert supports two basic use cases
8
26
 
9
27
  Given the file cuffsert.yml:
10
28
  ```yaml
@@ -61,7 +79,7 @@ Values from deeper levels merged onto values from higher levels to produce a con
61
79
 
62
80
  cuffsert [--stack-name=name] [--tag=k:v ...] [--parameter=k:v ...]
63
81
  [--metadata=directory | yml-file] [--metadata-path=path/to]
64
- cloudformation-file | cloudformation-directory
82
+ cloudformation-file | cloudformation-template
65
83
 
66
84
  All values set in the metadata file can be overridden on commandline.
67
85
 
data/bin/cuffup CHANGED
@@ -1,46 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'optparse'
4
- require 'yaml'
3
+ require 'cuffup'
5
4
 
6
- module CuffUp
7
- def self.parameters(io)
8
- template = YAML.load(io)
9
- (template['Parameters'] || [])
10
- .map do |key, data|
11
- {
12
- 'Name' => key,
13
- 'Value' => data['Default'],
14
- }
15
- end
16
- end
17
-
18
- def self.dump(args, input, output)
19
- result = {
20
- 'Format' => 'v1',
21
- }
22
- result['Parameters'] = input if input.size > 0
23
- result['Suffix'] = args[:selector].join('-') if args.include?(:selector)
24
- YAML.dump(result, output)
25
- end
26
-
27
- def self.run(args, template)
28
- self.dump(args, self.parameters(open(template)), STDOUT)
29
- end
30
- end
31
-
32
- args = {}
33
- parser = OptionParser.new do |opts|
34
- opts.on('--selector selector', '-s selector', 'Set as sufflx in the generated output') do |selector|
35
- args[:selector] = selector.split(/[-,\/]/)
36
- end
37
- end
38
-
39
- template = parser.parse(ARGV)
40
-
41
- unless template
5
+ args = CuffUp.parse_cli_args(ARGV)
6
+ unless args[:template]
42
7
  STDERR.puts("Usage: #{__FILE__} <template>")
43
8
  exit(1)
44
9
  end
45
10
 
46
- CuffUp.run(args, template[0])
11
+ input = open(args[:template][0])
12
+ output = open(args[:output], 'w')
13
+
14
+ CuffUp.run(args, input, output)
data/cuffsert.gemspec CHANGED
@@ -1,6 +1,8 @@
1
+ require File.expand_path('../lib/cuffsert/version', __FILE__)
2
+
1
3
  Gem::Specification.new do |spec|
2
4
  spec.name = 'cuffsert'
3
- spec.version = '0.9.1'
5
+ spec.version = CuffSert::VERSION
4
6
  spec.summary = 'Cuffsert provides a quick up-arrow-enter loading of a CloudFormation stack with good feedback'
5
7
  spec.description = 'Cuffsert allows encoding the metadata and commandline arguments needed to load a template in a versionable file which takes CloudFormation the last mile to really become an infrastructure-as-code platform.'
6
8
  spec.authors = ['Anders Qvist']
data/lib/cuffbase.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'yaml'
2
+
3
+ module CuffBase
4
+ def self.empty_from_template(io)
5
+ self.template_parameters(io) {|_| nil }
6
+ end
7
+
8
+ def self.defaults_from_template(io)
9
+ self.template_parameters(io) {|data| data['Default'] }
10
+ end
11
+
12
+ private_class_method
13
+
14
+ def self.template_parameters(io, &block)
15
+ template = YAML.load(io)
16
+ parameters = {}
17
+ (template['Parameters'] || []).each do |key, data|
18
+ parameters[key] = block.call(data)
19
+ end
20
+ parameters
21
+ end
22
+ end
@@ -19,7 +19,11 @@ module CuffSert
19
19
 
20
20
  unless meta.parameters.empty?
21
21
  cfargs[:parameters] = meta.parameters.map do |k, v|
22
- {:parameter_key => k, :parameter_value => v.to_s}
22
+ if v.nil?
23
+ {:parameter_key => k, :use_previous_value => true}
24
+ else
25
+ {:parameter_key => k, :parameter_value => v.to_s}
26
+ end
23
27
  end
24
28
  end
25
29
 
@@ -41,6 +45,9 @@ module CuffSert
41
45
  end
42
46
 
43
47
  def self.as_create_stack_args(meta)
48
+ no_value = meta.parameters.select {|_, v| v.nil? }.keys
49
+ raise "Supply value for #{no_value.join(', ')}" unless no_value.empty?
50
+
44
51
  cfargs = self.as_cloudformation_args(meta)
45
52
  cfargs[:timeout_in_minutes] = TIMEOUT
46
53
  cfargs[:on_failure] = 'DELETE'
@@ -1,4 +1,5 @@
1
1
  require 'optparse'
2
+ require 'cuffsert/version'
2
3
 
3
4
  module CuffSert
4
5
  STACKNAME_RE = /^[A-Za-z0-9_-]+$/
@@ -15,7 +16,7 @@ module CuffSert
15
16
  }
16
17
  }
17
18
  parser = OptionParser.new do |opts|
18
- opts.banner = 'Upsert a CloudFormation template, reading creation options and metadata from a yaml file. Currently, parameter values, stack name and stack tags are read from metadata file.'
19
+ opts.banner = "Upsert a CloudFormation template, reading creation options and metadata from a yaml file. Currently, parameter values, stack name and stack tags are read from metadata file. Version #{CuffSert::VERSION}."
19
20
  opts.separator('')
20
21
  opts.separator('Usage: cuffsert --selector production/us stack.json')
21
22
  opts.on('--metadata path', '-m path', 'Yaml file to read stack metadata from') do |path|
@@ -79,7 +80,7 @@ module CuffSert
79
80
  raise 'You cannot do --yes and --dry-run at the same time' if args[:op_mode]
80
81
  args[:op_mode] = :dangerous_ok
81
82
  end
82
-
83
+
83
84
  opts.on('--dry-run', 'Describe what would be done') do
84
85
  raise 'You cannot do --yes and --dry-run at the same time' if args[:op_mode]
85
86
  args[:op_mode] = :dry_run
@@ -89,8 +90,25 @@ module CuffSert
89
90
  abort(opts.to_s)
90
91
  end
91
92
  end
92
-
93
+
93
94
  args[:stack_path] = parser.parse(argv)
94
95
  args
95
96
  end
97
+
98
+ def self.validate_cli_args(cli_args)
99
+ errors = []
100
+ if cli_args[:stack_path].nil? || cli_args[:stack_path].size != 1
101
+ errors << 'Requires exactly one template'
102
+ end
103
+
104
+ if cli_args[:metadata].nil? && cli_args[:overrides][:stackname].nil?
105
+ errors << 'Without --metadata, you must supply --name to identify stack to update'
106
+ end
107
+
108
+ if cli_args[:selector] && cli_args[:metadata].nil?
109
+ errors << 'You cannot use --selector without --metadata'
110
+ end
111
+
112
+ raise errors.join(', ') unless errors.empty?
113
+ end
96
114
  end
data/lib/cuffsert/main.rb CHANGED
@@ -9,31 +9,7 @@ require 'cuffsert/rxcfclient'
9
9
  require 'rx'
10
10
  require 'uri'
11
11
 
12
- # TODO:
13
- # - Stop using file: that we anyway need to special-case in cfarguments
14
- # - default value for meta.metadata when stack_path is local file
15
- # - selector and metadata are mandatory and need guards accordingly
16
- # - validate_and_urlify belongs in metadata.rb
17
- # - execute should use helpers and not know details of statuses
18
- # - update 'abort' should delete cheangeset and emit the result
19
-
20
12
  module CuffSert
21
- def self.validate_and_urlify(stack_path)
22
- if stack_path =~ /^[A-Za-z0-9]+:/
23
- stack_uri = URI.parse(stack_path)
24
- else
25
- normalized = File.expand_path(stack_path)
26
- unless File.exist?(normalized)
27
- raise "Local file #{normalized} does not exist"
28
- end
29
- stack_uri = URI.join('file:///', normalized)
30
- end
31
- unless ['s3', 'file'].include?(stack_uri.scheme)
32
- raise "Uri #{stack_uri.scheme} is not supported"
33
- end
34
- stack_uri
35
- end
36
-
37
13
  def self.create_stack(client, meta, confirm_create)
38
14
  cfargs = CuffSert.as_create_stack_args(meta)
39
15
  Rx::Observable.concat(
@@ -112,15 +88,11 @@ module CuffSert
112
88
  ProgressbarRenderer.new(STDOUT, STDERR, cli_args)
113
89
  end
114
90
  end
115
-
91
+
116
92
  def self.run(argv)
117
93
  cli_args = CuffSert.parse_cli_args(argv)
94
+ CuffSert.validate_cli_args(cli_args)
118
95
  meta = CuffSert.build_meta(cli_args)
119
- if cli_args[:stack_path].nil? || cli_args[:stack_path].size != 1
120
- raise 'Requires exactly one stack path'
121
- end
122
- stack_path = cli_args[:stack_path][0]
123
- meta.stack_uri = CuffSert.validate_and_urlify(stack_path)
124
96
  events = CuffSert.execute(meta, CuffSert.method(:confirmation),
125
97
  force_replace: cli_args[:force_replace])
126
98
  renderer = CuffSert.make_renderer(cli_args)
@@ -1,3 +1,4 @@
1
+ require 'cuffbase'
1
2
  require 'yaml'
2
3
 
3
4
  module CuffSert
@@ -29,6 +30,22 @@ module CuffSert
29
30
  end
30
31
  end
31
32
 
33
+ def self.validate_and_urlify(stack_path)
34
+ if stack_path =~ /^[A-Za-z0-9]+:/
35
+ stack_uri = URI.parse(stack_path)
36
+ else
37
+ normalized = File.expand_path(stack_path)
38
+ unless File.exist?(normalized)
39
+ raise "Local file #{normalized} does not exist"
40
+ end
41
+ stack_uri = URI.join('file:///', normalized)
42
+ end
43
+ unless ['s3', 'file'].include?(stack_uri.scheme)
44
+ raise "Uri #{stack_uri.scheme} is not supported"
45
+ end
46
+ stack_uri
47
+ end
48
+
32
49
  def self.load_config(io)
33
50
  config = YAML.load(io)
34
51
  raise 'config does not seem to be a YAML hash?' unless config.is_a?(Hash)
@@ -56,9 +73,8 @@ module CuffSert
56
73
  end
57
74
 
58
75
  def self.build_meta(cli_args)
59
- io = open(cli_args[:metadata])
60
- config = CuffSert.load_config(io)
61
76
  default = self.meta_defaults(cli_args)
77
+ config = self.metadata_if_present(cli_args)
62
78
  meta = CuffSert.meta_for_path(config, cli_args[:selector], default)
63
79
  CuffSert.cli_overrides(meta, cli_args)
64
80
  end
@@ -66,14 +82,27 @@ module CuffSert
66
82
  private_class_method
67
83
 
68
84
  def self.meta_defaults(cli_args)
85
+ nil_params = CuffBase.empty_from_template(open(cli_args[:stack_path][0]))
69
86
  default = StackConfig.new
70
- default.suffix = File.basename(cli_args[:metadata], '.yml')
87
+ default.update_from({:parameters => nil_params})
88
+ default.suffix = File.basename(cli_args[:metadata], '.yml') if cli_args[:metadata]
71
89
  default
72
90
  end
73
91
 
92
+ def self.metadata_if_present(cli_args)
93
+ if cli_args[:metadata]
94
+ io = open(cli_args[:metadata])
95
+ CuffSert.load_config(io)
96
+ else
97
+ {}
98
+ end
99
+ end
100
+
74
101
  def self.cli_overrides(meta, cli_args)
75
102
  meta.update_from(cli_args[:overrides])
76
103
  meta.op_mode = cli_args[:op_mode] || meta.op_mode
104
+ stack_path = cli_args[:stack_path][0]
105
+ meta.stack_uri = CuffSert.validate_and_urlify(stack_path)
77
106
  meta
78
107
  end
79
108
 
@@ -0,0 +1,3 @@
1
+ module CuffSert
2
+ VERSION = '0.10.0'
3
+ end
data/lib/cuffup.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'cuffbase'
2
+ require 'optparse'
3
+
4
+ module CuffUp
5
+ def self.parse_cli_args(argv)
6
+ args = {
7
+ :output => '/dev/stdout'
8
+ }
9
+ parser = OptionParser.new do |opts|
10
+ opts.on('--output metadata', '-o metadata', 'File to write metadata file to; decaults to stdout') do |f|
11
+ args[:output] = f
12
+ end
13
+
14
+ opts.on('--selector selector', '-s selector', 'Set as sufflx in the generated output') do |selector|
15
+ args[:selector] = selector.split(/[-,\/]/)
16
+ end
17
+ end
18
+
19
+ args[:template] = parser.parse(argv)
20
+ args
21
+ end
22
+
23
+ def self.parameters(io)
24
+ CuffBase.defaults_from_template(io)
25
+ .map {|k, v| {'Name' => k, 'Value' => v} }
26
+ end
27
+
28
+ def self.dump(args, input, output)
29
+ result = {
30
+ 'Format' => 'v1',
31
+ }
32
+ result['Parameters'] = input if input.size > 0
33
+ result['Suffix'] = args[:selector].join('-') if args.include?(:selector)
34
+ YAML.dump(result, output)
35
+ end
36
+
37
+ def self.run(args, input, output)
38
+ self.dump(args, self.parameters(input), output)
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuffsert
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anders Qvist
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-28 00:00:00.000000000 Z
11
+ date: 2017-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -158,6 +158,7 @@ files:
158
158
  - bin/cuffsert
159
159
  - bin/cuffup
160
160
  - cuffsert.gemspec
161
+ - lib/cuffbase.rb
161
162
  - lib/cuffsert/cfarguments.rb
162
163
  - lib/cuffsert/cfstates.rb
163
164
  - lib/cuffsert/cli_args.rb
@@ -167,6 +168,8 @@ files:
167
168
  - lib/cuffsert/metadata.rb
168
169
  - lib/cuffsert/presenters.rb
169
170
  - lib/cuffsert/rxcfclient.rb
171
+ - lib/cuffsert/version.rb
172
+ - lib/cuffup.rb
170
173
  homepage: https://github.com/bittrance/cuffsert
171
174
  licenses:
172
175
  - MIT