sfn-parameters 0.2.6 → 0.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: 6402872602c46b70bbb1ce840268256a6a8c7a1e097093478603e5926a907c48
4
- data.tar.gz: e49e01025280eedd43e763a4f0069c4df68b468ebfb75a540a672917aa6befba
3
+ metadata.gz: ae546fd5056dfb22342cebe0403c7347cdd5e280666f178fbd7fbe6d32b10636
4
+ data.tar.gz: 35e0c98b0fdb9594d2f36233f0bb7c63a7fd8a46124365a600cdbc8e7f689683
5
5
  SHA512:
6
- metadata.gz: 885f6c47c742135a80f27f81d761def19675ecf12411b58f490fb627f2ee4378be903ef04c95ddb9e59ae2accc3283bc915ce98de0636c063d100526093d4675
7
- data.tar.gz: 3b341192e654d45aeac09a8221096ece01b76db70e02e7ae342f0ebc5e4adaa9780341c835f713cfc6ac4023b68908487e1cd3a42283d5c52a0f75eee1f95178
6
+ metadata.gz: 719f2ea18bf3cba78c0a2134683385bde3d415ed2afec9e886ee40ce823e9e23bb7e0fe499b027c79e36dd38b0e36cd21d48e85c56b421fa65d7f984616d6685
7
+ data.tar.gz: bfaa545962f8e8701b417924eb8ad1b596aac7572d8adea47a85b364657018fdf74937eedf3c5b7fae23d018da74b6dd30346e9b44e29a0d524fed094fa28421
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ # v0.3.0
2
+ * Add support for custom Resolvers (#20)
3
+
1
4
  # v0.2.6
2
5
  * Support all valid format types in show subcommand (#14)
3
6
  * Apply parameters for all commands (#17)
data/README.md CHANGED
@@ -244,6 +244,80 @@ $ sfn parameters show my-test-stack
244
244
 
245
245
  _NOTE: Full paths can also be used when defining parameters file._
246
246
 
247
+ ## Extending functionality (Resolvers)
248
+
249
+ Parameters can be fetched from remote locations using Resolvers. Resolvers
250
+ allow parameter values to be dynamically loaded via customized implementations.
251
+
252
+ ### Resolver usage
253
+
254
+ Parameter values will be loaded via a resolver when the value of the
255
+ parameter is a hash which includes a `resolver` key. The `resolver` key
256
+ identifies the name of the resolver which should be loaded. For example:
257
+
258
+ ~~~json
259
+ {
260
+ "parameters": {
261
+ "stack_creator": {
262
+ "resolver": "my_resolver",
263
+ "custom_key": "custom_value"
264
+ }
265
+ }
266
+ }
267
+ ~~~
268
+
269
+ This will create an instance of the `MyResolver` class and will then
270
+ call the `MyResolver#resolve` with the value `{"custom_key" => "custom_value"}`.
271
+
272
+ If the value to resolve is not a complex value, the configuration can
273
+ be reduced to a single key/value pair where the key is the name of the
274
+ resolver, and the value is the value to be resolved. This would look like:
275
+
276
+ ~~~json
277
+ {
278
+ "parameters": {
279
+ "stack_creator": {
280
+ "my_resolver": "custom_value"
281
+ }
282
+ }
283
+ }
284
+ ~~~
285
+
286
+ This will create an instance of the `MyResolver` class and will then
287
+ call the `MyResolver#resolve` with the value `"custom_value"`.
288
+
289
+ ### Resolver implementation
290
+
291
+ New resolvers can be created by subclassing the `SfnParameters::Resolver`
292
+ class and implementing a `#resolve` method. An optional `#setup` method
293
+ is available for setting up the instance. This is called during instance
294
+ creation and has the entire sfn configuration available via the `#config`
295
+ method.
296
+
297
+ ~~~ruby
298
+ require "sfn-parameters"
299
+
300
+ class MyResolver < SfnParameters::Resolver
301
+ def setup
302
+ # any custom setup
303
+ end
304
+
305
+ def resolve(value)
306
+ if value["custom_key"] == "custom_value"
307
+ "spox"
308
+ else
309
+ "unknown"
310
+ end
311
+ end
312
+ end
313
+ ~~~
314
+
315
+ ### Resolvers
316
+
317
+ List of known resolvers for sfn-parameters:
318
+
319
+ *
320
+
247
321
  # Info
248
322
 
249
323
  * Repository: https://github.com/sparkleformation/sfn-parameters
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ require "bundler/setup"
2
+ require "rake/testtask"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec) do |task|
6
+ task.rspec_opts = "--pattern test/rspecs/**{,/*/**}/*_rspec.rb"
7
+ end
8
+
9
+ namespace :rufo do
10
+ desc "Validate Ruby file formatting"
11
+ task :validate => [] do
12
+ base_path = File.dirname(__FILE__)
13
+ [
14
+ File.join(base_path, "lib"),
15
+ File.join(base_path, "test"),
16
+ ].each do |path|
17
+ if !system("rufo -c #{path}")
18
+ $stderr.puts "Files in #{path} directory require formatting!"
19
+ $stderr.puts " - Run `rake rufo:fmt`"
20
+ exit -1
21
+ end
22
+ end
23
+ end
24
+
25
+ desc "Format Ruby files in this project"
26
+ task :fmt => [] do
27
+ base_path = File.dirname(__FILE__)
28
+ [
29
+ File.join(base_path, "lib"),
30
+ File.join(base_path, "test"),
31
+ ].each do |path|
32
+ $stdout.puts "Formatting files in #{path} directory..."
33
+ system("rufo #{path}")
34
+ if $?.exitstatus != 0 && $?.exitstatus != 3
35
+ $stderr.puts "ERROR: Formatting files in #{path} failed!"
36
+ exit -1
37
+ end
38
+ end
39
+ $stdout.puts " -> File formatting complete!"
40
+ end
41
+ end
42
+
43
+ desc "Run all tests"
44
+ task :default => [] do
45
+ Rake::Task["rufo:validate"].invoke
46
+ Rake::Task[:spec].invoke
47
+ end
@@ -1,12 +1,13 @@
1
- require 'sfn'
1
+ require "sfn"
2
2
 
3
3
  module SfnParameters
4
- autoload :Safe, 'sfn-parameters/safe'
5
- autoload :Utils, 'sfn-parameters/utils'
4
+ autoload :Resolver, "sfn-parameters/resolver"
5
+ autoload :Safe, "sfn-parameters/safe"
6
+ autoload :Utils, "sfn-parameters/utils"
6
7
  end
7
8
 
8
- require 'sfn-parameters/version'
9
- require 'sfn-parameters/infrastructure'
10
- require 'sfn-parameters/stacks'
11
- require 'sfn-parameters/config'
12
- require 'sfn-parameters/command'
9
+ require "sfn-parameters/version"
10
+ require "sfn-parameters/infrastructure"
11
+ require "sfn-parameters/stacks"
12
+ require "sfn-parameters/config"
13
+ require "sfn-parameters/command"
@@ -1,4 +1,4 @@
1
- require 'sfn-parameters'
1
+ require "sfn-parameters"
2
2
 
3
3
  module Sfn
4
4
  class Config
@@ -4,6 +4,7 @@ module Sfn
4
4
  class Callback
5
5
  # Auto load stack parameters for infrastructure pattern
6
6
  class ParametersInfrastructure < Callback
7
+ include Bogo::Memoization
7
8
  include Sfn::Utils::JSON
8
9
  include SfnParameters::Utils
9
10
 
@@ -36,7 +37,8 @@ module Sfn
36
37
  root_path = config.fetch(:sfn_parameters, :directory, "infrastructure")
37
38
  isolation_name = config.fetch(
38
39
  :sfn_parameters, :destination,
39
- ENV.fetch("SFN_PARAMETERS_DESTINATION", "default"))
40
+ ENV.fetch("SFN_PARAMETERS_DESTINATION", "default")
41
+ )
40
42
  paths = Dir.glob(File.join(root_path, "#{isolation_name}{#{VALID_EXTENSIONS.join(",")}}")).map(&:to_s)
41
43
  if paths.size > 1
42
44
  raise ArgumentError.new "Multiple parameter file matches encountered! (#{paths.join(", ")})"
@@ -58,18 +60,18 @@ module Sfn
58
60
  end
59
61
  hash.fetch(:parameters, {}).each do |key, value|
60
62
  key = [*path, key].compact.map(&:to_s).join("__")
61
- if current_value = config[:parameters][key]
63
+ if current_value = config.get(:parameters, key)
62
64
  ui.debug "Not setting template parameter `#{key}`. Already set within config. (`#{current_value}`)"
63
65
  else
64
- config[:parameters][key] = value
66
+ config.set(:parameters, key, resolve(value))
65
67
  end
66
68
  end
67
69
  hash.fetch(:compile_parameters, {}).each do |key, value|
68
70
  key = [*path, key].compact.map(&:to_s).join("__")
69
- if current_value = config[:compile_parameters][key]
71
+ if current_value = config.get(:compile_parameters, key)
70
72
  ui.debug "Not setting compile time parameter `#{key}`. Already set within config. (`#{current_value}`)"
71
73
  else
72
- config[:compile_parameters][key] = value
74
+ config.set(:compile_parameters, key, resolve(value))
73
75
  end
74
76
  end
75
77
  hash.fetch(:stacks, {}).each do |key, value|
@@ -77,13 +79,64 @@ module Sfn
77
79
  end
78
80
  hash.fetch(:mappings, {}).each do |key, value|
79
81
  value = [*path, Bogo::Utility.camel(value)].compact.map(&:to_s).join("__")
80
- config[:apply_mapping][key] = value
82
+ config.set(:apply_mapping, key, value)
81
83
  end
82
84
  hash.fetch(:apply_stacks, []).each do |s_name|
83
85
  config[:apply_stack] << s_name
84
86
  end
87
+ config[:apply_stack].uniq!
85
88
  true
86
89
  end
90
+
91
+ # Load value via resolver if defined
92
+ #
93
+ # @param value [Object]
94
+ # @return [Object]
95
+ def resolve(value)
96
+ resolver, value = extract_resolver_information(value)
97
+ if resolver
98
+ resolver.resolve(value)
99
+ else
100
+ value
101
+ end
102
+ end
103
+
104
+ # Extract resolver name and data from value object
105
+ #
106
+ # @param value [Object]
107
+ # @return [Resolver, Object]
108
+ def extract_resolver_information(value)
109
+ if value.is_a?(Hash)
110
+ if value.size == 1
111
+ begin
112
+ r_name, val = value.to_a.flatten
113
+ resolver = load_resolver(Bogo::Utility.camel(r_name))
114
+ return resolver, val
115
+ rescue NameError
116
+ return nil, value
117
+ end
118
+ elsif value.to_smash.key?(:resolver)
119
+ val = value.to_smash
120
+ r_name = val.delete(:resolver)
121
+ resolver = load_resolver(Bogo::Utility.camel(r_name))
122
+ return resolver, val
123
+ else
124
+ return nil, value
125
+ end
126
+ end
127
+ return nil, value
128
+ end
129
+
130
+ # Load given resolver
131
+ #
132
+ # @param resolver_name [String]
133
+ # @return [Resolver]
134
+ def load_resolver(resolver_name)
135
+ memoize(resolver_name) do
136
+ klass = SfnParameters::Resolver.detect_resolver(resolver_name)
137
+ klass.new(config)
138
+ end
139
+ end
87
140
  end
88
141
  end
89
142
  end
@@ -0,0 +1,68 @@
1
+ require "sfn-parameters"
2
+
3
+ module SfnParameters
4
+ # Parameter resolver
5
+ class Resolver
6
+ @@resolvers = {}
7
+
8
+ # :nodoc:
9
+ # Used only for testing
10
+ def self.reset!
11
+ @@resolvers.clear
12
+ end
13
+
14
+ # :nodoc:
15
+ def self.inherited(klass)
16
+ if klass.name.nil?
17
+ raise ArgumentError.new("Unnamed classes are not supported")
18
+ end
19
+ klass_key = Bogo::Utility.snake(klass.name).gsub("::", "_")
20
+ @@resolvers[klass_key] = klass
21
+ end
22
+
23
+ # @return [Array<Resolver>]
24
+ def self.resolvers
25
+ @@resolvers.values
26
+ end
27
+
28
+ # Find resolver that matches given name
29
+ #
30
+ # @param name [String] resolver identifier name
31
+ # @return [Resolver] resolver class
32
+ # @raises [NameError]
33
+ def self.detect_resolver(name)
34
+ name = Bogo::Utility.snake(name).gsub("::", "_")
35
+ @@resolvers.each do |klass_name, klass|
36
+ return klass if klass_name.end_with?(name)
37
+ end
38
+ raise NameError.new("Unknown resolver requested `#{name}` - #{resolvers}")
39
+ end
40
+
41
+ # @return [Hash] sfn config
42
+ attr_reader :config
43
+
44
+ # Resolver initialization. It is provided
45
+ # the configuration from sfn to allow for
46
+ # any required customizations
47
+ #
48
+ # @param config [Hash] sfn config
49
+ # @return [self]
50
+ def initialize(config)
51
+ @config = config
52
+ setup
53
+ end
54
+
55
+ # Run any required setup for the resolver
56
+ def setup; end
57
+
58
+ # Resolve the given value. Value will be
59
+ # a Hash type but should be validated
60
+ # locally
61
+ #
62
+ # @param value [Hash]
63
+ # @return [Object]
64
+ def resolve(value)
65
+ raise NotImplementedError
66
+ end
67
+ end
68
+ end
@@ -1,10 +1,9 @@
1
- require 'sfn-parameters'
1
+ require "sfn-parameters"
2
2
 
3
3
  module SfnParameters
4
4
  # Safe storage
5
5
  class Safe
6
-
7
- autoload :Ssl, 'sfn-parameters/safe/ssl'
6
+ autoload :Ssl, "sfn-parameters/safe/ssl"
8
7
 
9
8
  # @return [Hash] safe configuration
10
9
  attr_reader :arguments
@@ -13,7 +12,7 @@ module SfnParameters
13
12
  #
14
13
  # @param args [Hash]
15
14
  # @return [self]
16
- def initialize(args={})
15
+ def initialize(args = {})
17
16
  @arguments = args.to_smash
18
17
  end
19
18
 
@@ -40,17 +39,15 @@ module SfnParameters
40
39
  # @param args [Hash] arguments for safe instance
41
40
  # @option args [String] :type type of safe
42
41
  # @return [Safe]
43
- def build(args={})
42
+ def build(args = {})
44
43
  args = args.to_smash
45
- type = Bogo::Utility.camel(args.fetch(:type, 'ssl'))
46
- if(const_defined?(type))
44
+ type = Bogo::Utility.camel(args.fetch(:type, "ssl"))
45
+ if const_defined?(type)
47
46
  const_get(type).new(args)
48
47
  else
49
48
  raise ArgumentError.new "Unknown safe type provided `#{type}`."
50
49
  end
51
50
  end
52
-
53
51
  end
54
-
55
52
  end
56
53
  end
@@ -1,4 +1,4 @@
1
- require 'sfn-parameters'
1
+ require "sfn-parameters"
2
2
 
3
3
  module SfnParameters
4
4
  # Safe storage
@@ -8,11 +8,11 @@ module SfnParameters
8
8
  class Ssl < Safe
9
9
 
10
10
  # Default cipher
11
- DEFAULT_CIPHER='AES-256-CBC'
11
+ DEFAULT_CIPHER = "AES-256-CBC"
12
12
  # Maximum computation iteration length
13
- CRYPT_ITER=10000
13
+ CRYPT_ITER = 10000
14
14
  # Default length of generated key
15
- CRYPT_KEY_LENGTH=32
15
+ CRYPT_KEY_LENGTH = 32
16
16
 
17
17
  # Create OpenSSL backed safe
18
18
  #
@@ -25,11 +25,11 @@ module SfnParameters
25
25
  # @return [self]
26
26
  def initialize(*_)
27
27
  super
28
- unless(arguments[:salt])
28
+ unless arguments[:salt]
29
29
  arguments[:salt] = OpenSSL::Random.random_bytes(16)
30
30
  end
31
- unless(arguments[:key])
32
- raise ArgumentError.new 'Required `:key` argument unset for `Safe::Ssl`!'
31
+ unless arguments[:key]
32
+ raise ArgumentError.new "Required `:key` argument unset for `Safe::Ssl`!"
33
33
  end
34
34
  end
35
35
 
@@ -47,7 +47,7 @@ module SfnParameters
47
47
  :cipher => arguments.fetch(:cipher, DEFAULT_CIPHER),
48
48
  :content => Base64.urlsafe_encode64(result),
49
49
  :salt => Base64.urlsafe_encode64(arguments[:salt]),
50
- :sfn_parameters_lock => Bogo::Utility.snake(self.class.name.split('::').last)
50
+ :sfn_parameters_lock => Bogo::Utility.snake(self.class.name.split("::").last),
51
51
  )
52
52
  end
53
53
 
@@ -60,6 +60,11 @@ module SfnParameters
60
60
  # @return [String]
61
61
  def unlock(value)
62
62
  value = value.to_smash
63
+ [:content, :iv, :salt].each do |key|
64
+ unless value[key]
65
+ raise ArgumentError.new("Missing required information `#{key}`")
66
+ end
67
+ end
63
68
  o_cipher = arguments[:cipher]
64
69
  arguments[:cipher] = value[:cipher] if value[:cipher]
65
70
  cipher = build(
@@ -78,7 +83,7 @@ module SfnParameters
78
83
  # @param iv [String] initialization vector
79
84
  # @param salt [String] random value
80
85
  # @return [OpenSSL::Cipher]
81
- def build(salt=nil, iv=nil)
86
+ def build(salt = nil, iv = nil)
82
87
  cipher = OpenSSL::Cipher.new(arguments[:cipher] || DEFAULT_CIPHER)
83
88
  iv ? cipher.decrypt : cipher.encrypt
84
89
  key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(
@@ -91,7 +96,6 @@ module SfnParameters
91
96
  cipher.key = key
92
97
  cipher
93
98
  end
94
-
95
99
  end
96
100
  end
97
101
  end
@@ -14,7 +14,7 @@ module Sfn
14
14
  paths = Dir.glob(File.join(root_path, "#{stack_name}{#{VALID_EXTENSIONS.join(",")}}")).map(&:to_s)
15
15
  if paths.size > 1
16
16
  raise ArgumentError.new "Multiple parameter file matches encountered! (#{paths.join(", ")})"
17
- elsif (paths.empty?)
17
+ elsif paths.empty?
18
18
  Smash.new
19
19
  else
20
20
  unlock_content(Bogo::Config.new(paths.first).data)
@@ -1,4 +1,4 @@
1
- require 'sfn-parameters'
1
+ require "sfn-parameters"
2
2
 
3
3
  module SfnParameters
4
4
  # Common helper methods
@@ -23,7 +23,7 @@ module SfnParameters
23
23
  # @return [Hash] unlocked content
24
24
  def unlock_content(content)
25
25
  content = content.to_smash
26
- if(content[:sfn_parameters_lock])
26
+ if content[:sfn_parameters_lock]
27
27
  safe = SfnParameters::Safe.build(
28
28
  config.fetch(:sfn_parameters, :safe, Smash.new)
29
29
  )
@@ -32,6 +32,5 @@ module SfnParameters
32
32
  content
33
33
  end
34
34
  end
35
-
36
35
  end
37
36
  end
@@ -1,3 +1,3 @@
1
1
  module SfnParameters
2
- VERSION = Gem::Version.new("0.2.6")
2
+ VERSION = Gem::Version.new("0.3.0")
3
3
  end
@@ -1,15 +1,18 @@
1
- $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
- require 'sfn-parameters/version'
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + "/lib/"
2
+ require "sfn-parameters/version"
3
3
  Gem::Specification.new do |s|
4
- s.name = 'sfn-parameters'
4
+ s.name = "sfn-parameters"
5
5
  s.version = SfnParameters::VERSION.version
6
- s.summary = 'SparkleFormation Parameters Callback'
7
- s.author = 'Chris Roberts'
8
- s.email = 'code@chrisroberts.org'
9
- s.homepage = 'http://github.com/sparkleformation/sfn-parameters'
10
- s.description = 'SparkleFormation Parameters Callback'
11
- s.license = 'Apache-2.0'
12
- s.require_path = 'lib'
13
- s.add_dependency 'sfn', '>= 3.0', '< 4.0'
14
- s.files = Dir['{lib,bin,docs}/**/*'] + %w(sfn-parameters.gemspec README.md CHANGELOG.md LICENSE)
6
+ s.summary = "SparkleFormation Parameters Callback"
7
+ s.author = "Chris Roberts"
8
+ s.email = "code@chrisroberts.org"
9
+ s.homepage = "http://github.com/sparkleformation/sfn-parameters"
10
+ s.description = "SparkleFormation Parameters Callback"
11
+ s.license = "Apache-2.0"
12
+ s.require_path = "lib"
13
+ s.add_runtime_dependency "sfn", ">= 3.0", "< 4.0"
14
+ s.add_development_dependency "rake", "~> 10"
15
+ s.add_development_dependency "rspec", "~> 3.5"
16
+ s.add_development_dependency "rufo", "~> 0.3.0"
17
+ s.files = Dir["{lib,bin,docs}/**/*"] + %w(sfn-parameters.gemspec README.md CHANGELOG.md LICENSE Rakefile)
15
18
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sfn-parameters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Roberts
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-22 00:00:00.000000000 Z
11
+ date: 2018-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sfn
@@ -30,6 +30,48 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '4.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: rake
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '10'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '10'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.5'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.5'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rufo
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.3.0
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.3.0
33
75
  description: SparkleFormation Parameters Callback
34
76
  email: code@chrisroberts.org
35
77
  executables: []
@@ -39,10 +81,12 @@ files:
39
81
  - CHANGELOG.md
40
82
  - LICENSE
41
83
  - README.md
84
+ - Rakefile
42
85
  - lib/sfn-parameters.rb
43
86
  - lib/sfn-parameters/command.rb
44
87
  - lib/sfn-parameters/config.rb
45
88
  - lib/sfn-parameters/infrastructure.rb
89
+ - lib/sfn-parameters/resolver.rb
46
90
  - lib/sfn-parameters/safe.rb
47
91
  - lib/sfn-parameters/safe/ssl.rb
48
92
  - lib/sfn-parameters/stacks.rb