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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +21 -3
- data/bin/cuffup +7 -39
- data/cuffsert.gemspec +3 -1
- data/lib/cuffbase.rb +22 -0
- data/lib/cuffsert/cfarguments.rb +8 -1
- data/lib/cuffsert/cli_args.rb +21 -3
- data/lib/cuffsert/main.rb +2 -30
- data/lib/cuffsert/metadata.rb +32 -3
- data/lib/cuffsert/version.rb +3 -0
- data/lib/cuffup.rb +40 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d02e68aa26e398ea76222c0f564477dce26d44c9
|
4
|
+
data.tar.gz: 0984fe4d90622252665ec5a718d50cd6c0d76399
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61871cff6aadd97c39413dcdc28f7acce5641ab6a7cbc191aa336b6b7307c05ba2f8cbce4843e0ddf1f23086e362149c9e317fa6b760be64d1f4a15d28bba3ed
|
7
|
+
data.tar.gz: 775eebfd1732099b53d309686d0be459529e5f11c507e19d4ffe85e5ecf14edaa7cbcf13aa89db4c3421c6b2b4852deffa44b892d251e4881776ca93b6130fe1
|
data/Gemfile.lock
CHANGED
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
|
-
|
5
|
+
## Getting started
|
6
6
|
|
7
|
-
|
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-
|
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 '
|
4
|
-
require 'yaml'
|
3
|
+
require 'cuffup'
|
5
4
|
|
6
|
-
|
7
|
-
|
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
|
-
|
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 =
|
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
|
data/lib/cuffsert/cfarguments.rb
CHANGED
@@ -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
|
-
|
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'
|
data/lib/cuffsert/cli_args.rb
CHANGED
@@ -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 =
|
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)
|
data/lib/cuffsert/metadata.rb
CHANGED
@@ -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.
|
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
|
|
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.
|
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-
|
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
|