sfn-parameters 0.2.6 → 0.3.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
  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