secret_config 0.1.0 → 0.2.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: 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