cuffsert 0.9.1 → 0.10.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
  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