secret_config 0.1.0 → 0.2.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: 6ac6f9526e20dd9f6b39762ff6363ec3090fa51db2dfa0726351742d7abe5f3e
4
- data.tar.gz: 78e69dd6f2bed35f5dde021a0ef247d392fee388ae5d3b3c3f41ca551928ec0c
3
+ metadata.gz: ce78920783d826865d118ba93de56be3e428d766efd502070999f7a5b1ebb069
4
+ data.tar.gz: 69bf56d09a5e20e3966531a9e75591c44376c6e5d0413d429a79dbfad11d4823
5
5
  SHA512:
6
- metadata.gz: 3f8fd6c22492f28bf71b1938792fe1cc392bfd073a0e541645dfd7869fe41f12f54e53575eabb9252440f34a137cce1b2bad89667dba84f80fec3183ef17de8f
7
- data.tar.gz: f2ea35ffbb3acecacff2eca9ca16c73f3e6b1de15a78b3c19e8f2b59b6425fccc01c8c06779167ae3470d7fb5688bd7cbb5ec4e652f31841518bb824662417c9
6
+ metadata.gz: dee07a19a4728594c49f9c207d2006dde24ce2d87e25654c274efa11de4a9e0c274b9c66c6e31b4d8043c756fa007df8a2e5db84a189bf36b5ebeef2c596138c
7
+ data.tar.gz: 3a1eb219305927565c9920943bbde78b34bb4a1ad2cc7c703cb53bef9a1c7a35a3b9d82736d7969af4c08562de85dc05bc4224f76d6c2f38474ea0d479297ec2
data/lib/secret_config.rb CHANGED
@@ -24,8 +24,8 @@ module SecretConfig
24
24
  end
25
25
 
26
26
  def self.root
27
- @root ||= ENV["SECRETCONFIG_ROOT"] ||
28
- raise(UndefinedRootError, "Either set env var 'SECRETCONFIG_ROOT' or call SecretConfig.root=")
27
+ @root ||= ENV["SECRET_CONFIG_ROOT"] ||
28
+ raise(UndefinedRootError, "Either set env var 'SECRET_CONFIG_ROOT' or call SecretConfig.root=")
29
29
  end
30
30
 
31
31
  def self.root=(root)
@@ -33,8 +33,15 @@ module SecretConfig
33
33
  @registry = nil if @registry
34
34
  end
35
35
 
36
- def self.provider #(provider, **args)
37
- @provider ||= (ENV["SECRETCONFIG_PROVIDER"] || :file).to_sym
36
+ # When provider is not supplied, returns the current provider instance
37
+ # When provider is supplied, sets the new provider and stores any arguments
38
+ def self.provider(provider = nil, **args)
39
+ if provider.nil?
40
+ return @provider ||= create_provider((ENV["SECRET_CONFIG_PROVIDER"] || :file).to_sym)
41
+ end
42
+
43
+ @provider = create_provider(provider, args)
44
+ @registry = nil if @registry
38
45
  end
39
46
 
40
47
  def self.provider=(provider)
@@ -45,4 +52,37 @@ module SecretConfig
45
52
  def self.registry
46
53
  @registry ||= SecretConfig::Registry.new(root: root, provider: provider)
47
54
  end
55
+
56
+ private
57
+
58
+ def self.create_provider(provider, args = nil)
59
+ klass = constantize_symbol(provider)
60
+ if args && args.size > 0
61
+ klass.new(**args)
62
+ else
63
+ klass.new
64
+ end
65
+ end
66
+
67
+ def implementation
68
+ @implementation ||= constantize_symbol(provider).new
69
+ end
70
+
71
+ def self.constantize_symbol(symbol, namespace = 'SecretConfig::Providers')
72
+ klass = "#{namespace}::#{camelize(symbol.to_s)}"
73
+ begin
74
+ Object.const_get(klass)
75
+ rescue NameError
76
+ raise(ArgumentError, "Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}. Looking for: #{klass}")
77
+ end
78
+ end
79
+
80
+ # Borrow from Rails, when not running Rails
81
+ def self.camelize(term)
82
+ string = term.to_s
83
+ string = string.sub(/^[a-z\d]*/, &:capitalize)
84
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
85
+ string.gsub!('/'.freeze, '::'.freeze)
86
+ string
87
+ end
48
88
  end
@@ -7,7 +7,7 @@ module SecretConfig
7
7
  class File
8
8
  attr_reader :file_name
9
9
 
10
- def initialize(file_name: ENV['SECRETCONFIG_FILE_NAME'] || "config/application.yml")
10
+ def initialize(file_name: "config/application.yml")
11
11
  @file_name = file_name
12
12
  raise(ConfigurationError, "Cannot find config file: #{file_name}") unless ::File.exist?(file_name)
13
13
  end
@@ -6,7 +6,7 @@ module SecretConfig
6
6
  class Ssm
7
7
  attr_reader :client, :key_id
8
8
 
9
- def initialize(key_id: ENV["SECRETCONFIG_KEY_ID"])
9
+ def initialize(key_id: nil)
10
10
  @key_id = key_id
11
11
  logger = SemanticLogger['Aws::SSM'] if defined?(SemanticLogger)
12
12
  @client = Aws::SSM::Client.new(logger: logger)
@@ -2,16 +2,11 @@ require 'base64'
2
2
 
3
3
  module SecretConfig
4
4
  # Centralized configuration with values stored in AWS System Manager Parameter Store
5
- #
6
- # Values are fetched from the central store on startup. Only those values starting with the specified
7
- # root are loaded, supply multiple paths using the env var SECRETCONFIG_PATHS.
8
- #
9
- # Existing event mechanisms can be used to force a reload of the cached copy.
10
5
  class Registry
11
6
  attr_reader :provider
12
7
  attr_accessor :root
13
8
 
14
- def initialize(root:, provider: :ssm)
9
+ def initialize(root:, provider:)
15
10
  # TODO: Validate root starts with /, etc
16
11
  @root = root
17
12
  @provider = provider
@@ -21,7 +16,7 @@ module SecretConfig
21
16
  # Returns [Hash] a copy of the in memory configuration data.
22
17
  def configuration
23
18
  h = {}
24
- registry.each_pair { |key, value| h[key] = value }
19
+ registry.each_pair { |k, v| decompose(k, v, h) }
25
20
  h
26
21
  end
27
22
 
@@ -54,7 +49,7 @@ module SecretConfig
54
49
 
55
50
  def refresh!
56
51
  h = {}
57
- implementation.each(root) { |k, v| h[k] = v }
52
+ provider.each(root) { |k, v| h[k] = v }
58
53
  @registry = h
59
54
  end
60
55
 
@@ -66,8 +61,19 @@ module SecretConfig
66
61
  key.start_with?('/') ? key : "#{root}/#{key}"
67
62
  end
68
63
 
69
- def implementation
70
- @implementation ||= constantize_symbol(provider).new
64
+ def decompose(key, value, h = {})
65
+ path, name = File.split(key)
66
+ last = path.split('/').reduce(h) do |target, path|
67
+ if path == ''
68
+ target
69
+ elsif target.key?(path)
70
+ target[path]
71
+ else
72
+ target[path] = {}
73
+ end
74
+ end
75
+ last[name] = value
76
+ h
71
77
  end
72
78
 
73
79
  def convert_encoding(encoding, value)
@@ -87,26 +93,12 @@ module SecretConfig
87
93
  value.to_f
88
94
  when :string
89
95
  value
96
+ when :boolean
97
+ %w[true 1 t].include?(value.to_s.downcase)
98
+ when :symbol
99
+ value.to_sym unless value.nil? || value.to_s.strip == ''
90
100
  end
91
101
  end
92
102
 
93
- def constantize_symbol(symbol, namespace = 'SecretConfig::Providers')
94
- klass = "#{namespace}::#{camelize(symbol.to_s)}"
95
- begin
96
- Object.const_get(klass)
97
- rescue NameError
98
- raise(ArgumentError, "Could not convert symbol: #{symbol.inspect} to a class in: #{namespace}. Looking for: #{klass}")
99
- end
100
- end
101
-
102
- # Borrow from Rails, when not running Rails
103
- def camelize(term)
104
- string = term.to_s
105
- string = string.sub(/^[a-z\d]*/, &:capitalize)
106
- string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}" }
107
- string.gsub!('/'.freeze, '::'.freeze)
108
- string
109
- end
110
-
111
103
  end
112
104
  end
@@ -1,3 +1,3 @@
1
1
  module SecretConfig
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -2,10 +2,10 @@
2
2
  # These are for development and test only.
3
3
 
4
4
  #
5
- # Development - Local - Root: '/development/connect'
5
+ # Development - Local - Root: '/development/my_application'
6
6
  #
7
7
  development:
8
- connect:
8
+ my_application:
9
9
  symmetric_encryption:
10
10
  key: QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=
11
11
  iv: QUJDREVGMTIzNDU2Nzg5MA==
@@ -26,7 +26,7 @@ development:
26
26
  secret_key_base: somereallylongstring
27
27
 
28
28
  test:
29
- connect:
29
+ my_application:
30
30
  symmetric_encryption:
31
31
  key: QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=
32
32
  iv: QUJDREVGMTIzNDU2Nzg5MA==
@@ -8,22 +8,22 @@ module Providers
8
8
  end
9
9
 
10
10
  let :root do
11
- "/development/connect"
11
+ "/development/my_application"
12
12
  end
13
13
 
14
14
  let :expected do
15
15
  {
16
- "/development/connect/mongo/database" => "secret_config_development",
17
- "/development/connect/mongo/primary" => "127.0.0.1:27017",
18
- "/development/connect/mongo/secondary" => "127.0.0.1:27018",
19
- "/development/connect/mysql/database" => "secret_config_development",
20
- "/development/connect/mysql/password" => "secret_configrules",
21
- "/development/connect/mysql/username" => "secret_config",
22
- "/development/connect/mysql/host" => "127.0.0.1",
23
- "/development/connect/secrets/secret_key_base" => "somereallylongstring",
24
- "/development/connect/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
25
- "/development/connect/symmetric_encryption/version" => 2,
26
- "/development/connect/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
16
+ "/development/my_application/mongo/database" => "secret_config_development",
17
+ "/development/my_application/mongo/primary" => "127.0.0.1:27017",
18
+ "/development/my_application/mongo/secondary" => "127.0.0.1:27018",
19
+ "/development/my_application/mysql/database" => "secret_config_development",
20
+ "/development/my_application/mysql/password" => "secret_configrules",
21
+ "/development/my_application/mysql/username" => "secret_config",
22
+ "/development/my_application/mysql/host" => "127.0.0.1",
23
+ "/development/my_application/secrets/secret_key_base" => "somereallylongstring",
24
+ "/development/my_application/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
25
+ "/development/my_application/symmetric_encryption/version" => 2,
26
+ "/development/my_application/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
27
27
  }
28
28
  end
29
29
 
@@ -8,42 +8,50 @@ module Providers
8
8
  end
9
9
 
10
10
  let :root do
11
- "/development/connect"
11
+ "/development/my_application"
12
12
  end
13
13
 
14
14
  let :expected do
15
15
  {
16
- "/development/connect/mongo/database" => "secret_config_development",
17
- "/development/connect/mongo/primary" => "127.0.0.1:27017",
18
- "/development/connect/mongo/secondary" => "127.0.0.1:27018",
19
- "/development/connect/mysql/database" => "secret_config_development",
20
- "/development/connect/mysql/password" => "secret_configrules",
21
- "/development/connect/mysql/username" => "secret_config",
22
- "/development/connect/mysql/host" => "127.0.0.1",
23
- "/development/connect/secrets/secret_key_base" => "somereallylongstring",
24
- "/development/connect/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
25
- "/development/connect/symmetric_encryption/version" => "2",
26
- "/development/connect/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
16
+ "/development/my_application/mongo/database" => "secret_config_development",
17
+ "/development/my_application/mongo/primary" => "127.0.0.1:27017",
18
+ "/development/my_application/mongo/secondary" => "127.0.0.1:27018",
19
+ "/development/my_application/mysql/database" => "secret_config_development",
20
+ "/development/my_application/mysql/password" => "secret_configrules",
21
+ "/development/my_application/mysql/username" => "secret_config",
22
+ "/development/my_application/mysql/host" => "127.0.0.1",
23
+ "/development/my_application/secrets/secret_key_base" => "somereallylongstring",
24
+ "/development/my_application/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
25
+ "/development/my_application/symmetric_encryption/version" => "2",
26
+ "/development/my_application/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
27
27
  }
28
28
  end
29
29
 
30
+ before do
31
+ unless ENV['AWS_ACCESS_KEY_ID']
32
+ skip "Skipping AWS SSM Parameter Store tests because env var 'AWS_ACCESS_KEY_ID' is not defined."
33
+ end
34
+ end
35
+
30
36
  describe '#each' do
31
37
  it 'fetches all keys in path' do
32
- upload_settings if ENV['SECRETCONFIG_TEST_UPLOAD_SSM']
33
-
34
38
  ssm = SecretConfig::Providers::Ssm.new
35
39
  paths = {}
36
40
  ssm.each(root) { |key, value| paths[key] = value }
37
41
 
42
+ if paths.empty?
43
+ upload_settings(ssm) unless ssm.key?("/development/my_application/mongo/database")
44
+ ssm.each(root) { |key, value| paths[key] = value }
45
+ end
46
+
38
47
  expected.each_pair do |key, value|
39
48
  assert_equal paths[key], value, "Path: #{key}"
40
49
  end
41
50
  end
42
51
  end
43
52
 
44
- def upload_settings
53
+ def upload_settings(ssm)
45
54
  file_provider = SecretConfig::Providers::File.new(file_name: file_name)
46
- ssm = SecretConfig::Providers::Ssm.new
47
55
  file_provider.each(root) { |key, value| ap key; ssm.set(key, value) }
48
56
  end
49
57
  end
@@ -7,38 +7,36 @@ class RegistryTest < Minitest::Test
7
7
  end
8
8
 
9
9
  let :root do
10
- "/development/connect"
10
+ "/development/my_application"
11
11
  end
12
12
 
13
- let :registry do
14
- ENV['SECRETCONFIG_FILE_NAME'] = file_name
13
+ let :provider do
14
+ SecretConfig::Providers::File.new(file_name: file_name)
15
+ end
15
16
 
16
- SecretConfig::Registry.new(root: root, provider: :file)
17
+ let :registry do
18
+ SecretConfig::Registry.new(root: root, provider: provider)
17
19
  end
18
20
 
19
21
  let :expected do
20
22
  {
21
- "/development/connect/mongo/database" => "secret_config_development",
22
- "/development/connect/mongo/primary" => "127.0.0.1:27017",
23
- "/development/connect/mongo/secondary" => "127.0.0.1:27018",
24
- "/development/connect/mysql/database" => "secret_config_development",
25
- "/development/connect/mysql/password" => "secret_configrules",
26
- "/development/connect/mysql/username" => "secret_config",
27
- "/development/connect/mysql/host" => "127.0.0.1",
28
- "/development/connect/secrets/secret_key_base" => "somereallylongstring",
29
- "/development/connect/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
30
- "/development/connect/symmetric_encryption/version" => 2,
31
- "/development/connect/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
23
+ "/development/my_application/mongo/database" => "secret_config_development",
24
+ "/development/my_application/mongo/primary" => "127.0.0.1:27017",
25
+ "/development/my_application/mongo/secondary" => "127.0.0.1:27018",
26
+ "/development/my_application/mysql/database" => "secret_config_development",
27
+ "/development/my_application/mysql/password" => "secret_configrules",
28
+ "/development/my_application/mysql/username" => "secret_config",
29
+ "/development/my_application/mysql/host" => "127.0.0.1",
30
+ "/development/my_application/secrets/secret_key_base" => "somereallylongstring",
31
+ "/development/my_application/symmetric_encryption/key" => "QUJDREVGMTIzNDU2Nzg5MEFCQ0RFRjEyMzQ1Njc4OTA=",
32
+ "/development/my_application/symmetric_encryption/version" => 2,
33
+ "/development/my_application/symmetric_encryption/iv" => "QUJDREVGMTIzNDU2Nzg5MA=="
32
34
  }
33
35
  end
34
36
 
35
37
  describe '#configuration' do
36
38
  it 'returns a copy of the config' do
37
- paths = registry.configuration
38
-
39
- expected.each_pair do |key, value|
40
- assert_equal value, paths[key], "Path: #{key}"
41
- end
39
+ assert_equal "127.0.0.1", registry.configuration.dig("development", "my_application", "mysql", "host")
42
40
  end
43
41
  end
44
42
 
@@ -108,5 +106,22 @@ class RegistryTest < Minitest::Test
108
106
  assert_equal "ABCDEF1234567890ABCDEF1234567890", registry.fetch("symmetric_encryption/key", encoding: :base64)
109
107
  end
110
108
  end
109
+
110
+ private
111
+
112
+ def decompose(key, value, h = {})
113
+ path, name = File.split(key)
114
+ last = path.split('/').reduce(h) do |target, path|
115
+ if path == ''
116
+ target
117
+ elsif target.key?(path)
118
+ target[path]
119
+ else
120
+ target[path] = {}
121
+ end
122
+ end
123
+ last[name] = value
124
+ h
125
+ end
111
126
  end
112
127
  end
@@ -7,18 +7,17 @@ class SecretConfigTest < Minitest::Test
7
7
  end
8
8
 
9
9
  let :root do
10
- "/development/connect"
10
+ "/development/my_application"
11
11
  end
12
12
 
13
13
  before do
14
- ENV['SECRETCONFIG_FILE_NAME'] = file_name
15
- SecretConfig.root = root
16
- SecretConfig.provider = :file
14
+ SecretConfig.root = root
15
+ SecretConfig.provider :file, file_name: file_name
17
16
  end
18
17
 
19
18
  describe '#configuration' do
20
19
  it 'returns a copy of the config' do
21
- assert_equal "127.0.0.1", SecretConfig.configuration["/development/connect/mysql/host"]
20
+ assert_equal "127.0.0.1", SecretConfig.configuration.dig("development", "my_application", "mysql", "host")
22
21
  end
23
22
  end
24
23
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secret_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-12 00:00:00.000000000 Z
11
+ date: 2019-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby