config_o_mat 0.2.1 → 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: 35eaeccb6abdf69afb14c8cbe951c4579734f27af4844673b1761db70dfd1bf1
4
- data.tar.gz: 392a61b3ee55ba8d9351c2b7d69d39de7d591bbde87f3374fac77fc425e628b8
3
+ metadata.gz: bd094e1ed00e6019a324570d47475448e924c6c31abe2c9fb7c31cd37064ddfe
4
+ data.tar.gz: e9712d360ae7da7adf5c15142bf3a2365465832071998790ebd8e5a089429f9a
5
5
  SHA512:
6
- metadata.gz: 31f9277ada3b1a7fc09efe007a3b530fb3f100c480c09ab0af83a9879ab1fa3ea0b0db3252300fe8dbe9bb9877acf2244727a94554a6384bf52641a5a95c685b
7
- data.tar.gz: 4346bae4957800e67e238c0231ddf1b424e1c5afe60a86e2d29e2134f480d56979d320514e24b412557f7f31b239fd7342ffdce459c387ba2b3ce56ce3e3e054
6
+ metadata.gz: 1a63b431f2fe289c0defaadc88a7105910d0198bf8dc3862da8effb908953f2b480687cc7750fc4f282cc410dcc6f3dc4e8d54659523e909c07f5e8e8059a1e3
7
+ data.tar.gz: 8763d1eff5f21f0cb2cfc603595fb8b4e198dad30ccbb8f2727ee930df1438d8b5a5c5075de674e269b99a25313f6648be160b72ed6caa3d921d595af7c8fb83
data/.gitignore CHANGED
@@ -7,3 +7,6 @@ coverage/
7
7
  # YARD doc db and output
8
8
  .yardoc/
9
9
  doc/
10
+
11
+ # Gem builds
12
+ pkg/
data/Gemfile CHANGED
@@ -16,11 +16,4 @@
16
16
 
17
17
  source 'https://rubygems.org'
18
18
 
19
- gem 'aws-sdk-appconfig', '~> 1.18', require: false
20
- gem 'logsformyfamily', '~> 0.2', require: false
21
- gem 'lifecycle_vm', '~> 0.1.1', require: false
22
- gem 'ruby-dbus', '~> 0.16.0', require: false
23
- gem 'sd_notify', '~> 0.1', require: false
24
-
25
- gem 'rspec', '~> 3.10', group: :test, require: false
26
- gem 'simplecov', '~> 0.21', group: :test, require: false
19
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,36 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ config_o_mat (0.2.1)
5
+ aws-sdk-appconfig (~> 1.18)
6
+ aws-sdk-secretsmanager (~> 1.57)
7
+ lifecycle_vm (~> 0.1.1)
8
+ logsformyfamily (~> 0.2)
9
+ ruby-dbus (~> 0.16.0)
10
+ sd_notify (~> 0.1.1)
11
+
1
12
  GEM
2
13
  remote: https://rubygems.org/
3
14
  specs:
4
15
  aws-eventstream (1.2.0)
5
- aws-partitions (1.525.0)
6
- aws-sdk-appconfig (1.19.0)
7
- aws-sdk-core (~> 3, >= 3.122.0)
16
+ aws-partitions (1.554.0)
17
+ aws-sdk-appconfig (1.24.0)
18
+ aws-sdk-core (~> 3, >= 3.126.0)
8
19
  aws-sigv4 (~> 1.1)
9
- aws-sdk-core (3.122.0)
20
+ aws-sdk-core (3.126.1)
10
21
  aws-eventstream (~> 1, >= 1.0.2)
11
22
  aws-partitions (~> 1, >= 1.525.0)
12
23
  aws-sigv4 (~> 1.1)
13
24
  jmespath (~> 1.0)
25
+ aws-sdk-secretsmanager (1.57.0)
26
+ aws-sdk-core (~> 3, >= 3.126.0)
27
+ aws-sigv4 (~> 1.1)
14
28
  aws-sigv4 (1.4.0)
15
29
  aws-eventstream (~> 1, >= 1.0.2)
16
- diff-lcs (1.4.4)
30
+ diff-lcs (1.5.0)
17
31
  docile (1.4.0)
18
- jmespath (1.4.0)
32
+ jmespath (1.6.0)
19
33
  lifecycle_vm (0.1.2)
20
34
  logsformyfamily (0.2.3)
21
- rspec (3.10.0)
22
- rspec-core (~> 3.10.0)
23
- rspec-expectations (~> 3.10.0)
24
- rspec-mocks (~> 3.10.0)
25
- rspec-core (3.10.1)
26
- rspec-support (~> 3.10.0)
27
- rspec-expectations (3.10.1)
35
+ rspec (3.11.0)
36
+ rspec-core (~> 3.11.0)
37
+ rspec-expectations (~> 3.11.0)
38
+ rspec-mocks (~> 3.11.0)
39
+ rspec-core (3.11.0)
40
+ rspec-support (~> 3.11.0)
41
+ rspec-expectations (3.11.0)
28
42
  diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.10.0)
30
- rspec-mocks (3.10.2)
43
+ rspec-support (~> 3.11.0)
44
+ rspec-mocks (3.11.0)
31
45
  diff-lcs (>= 1.2.0, < 2.0)
32
- rspec-support (~> 3.10.0)
33
- rspec-support (3.10.3)
46
+ rspec-support (~> 3.11.0)
47
+ rspec-support (3.11.0)
34
48
  ruby-dbus (0.16.0)
35
49
  sd_notify (0.1.1)
36
50
  simplecov (0.21.2)
@@ -38,19 +52,15 @@ GEM
38
52
  simplecov-html (~> 0.11)
39
53
  simplecov_json_formatter (~> 0.1)
40
54
  simplecov-html (0.12.3)
41
- simplecov_json_formatter (0.1.3)
55
+ simplecov_json_formatter (0.1.4)
42
56
 
43
57
  PLATFORMS
44
58
  ruby
45
59
 
46
60
  DEPENDENCIES
47
- aws-sdk-appconfig (~> 1.18)
48
- lifecycle_vm (~> 0.1.1)
49
- logsformyfamily (~> 0.2)
61
+ config_o_mat!
50
62
  rspec (~> 3.10)
51
- ruby-dbus (~> 0.16.0)
52
- sd_notify (~> 0.1)
53
- simplecov (~> 0.21)
63
+ simplecov (~> 0.21.2)
54
64
 
55
65
  BUNDLED WITH
56
66
  2.1.4
data/config_o_mat.gemspec CHANGED
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.executables = ["config_o_mat-configurator", "config_o_mat-meta_configurator"]
40
40
 
41
41
  spec.add_dependency('aws-sdk-appconfig', '~> 1.18')
42
+ spec.add_dependency('aws-sdk-secretsmanager', '~> 1.57')
42
43
  spec.add_dependency('logsformyfamily', '~> 0.2')
43
44
  spec.add_dependency('lifecycle_vm', '~> 0.1.1')
44
45
  spec.add_dependency('ruby-dbus', '~> 0.16.0')
@@ -25,7 +25,8 @@ module ConfigOMat
25
25
  :client_id, :compiled_templates, :applied_profiles, :applying_profile,
26
26
  :generated_templates, :services_to_reload, :profiles_to_apply,
27
27
  :last_refresh_time, :next_state, :retry_count, :retries_left, :retry_wait,
28
- :region, :appconfig_client, :systemd_interface
28
+ :region, :appconfig_client, :secretsmanager_client, :systemd_interface,
29
+ :secrets_loader_memory
29
30
 
30
31
  def initialize(
31
32
  argv: [],
@@ -55,7 +56,9 @@ module ConfigOMat
55
56
  retry_wait: 2,
56
57
  region: nil,
57
58
  appconfig_client: nil,
58
- systemd_interface: nil
59
+ secretsmanager_client: nil,
60
+ systemd_interface: nil,
61
+ secrets_loader_memory: nil
59
62
  )
60
63
  super()
61
64
 
@@ -86,7 +89,9 @@ module ConfigOMat
86
89
  @retry_wait = retry_wait
87
90
  @region = region
88
91
  @appconfig_client = appconfig_client
92
+ @secretsmanager_client = secretsmanager_client
89
93
  @systemd_interface = systemd_interface
94
+ @secrets_loader_memory = secrets_loader_memory
90
95
  end
91
96
  end
92
97
  end
@@ -17,18 +17,20 @@
17
17
  require 'lifecycle_vm/op_base'
18
18
 
19
19
  require 'aws-sdk-appconfig'
20
+ require 'aws-sdk-secretsmanager'
20
21
 
21
22
  module ConfigOMat
22
23
  module Op
23
- class ConnectToAppconfig < LifecycleVM::OpBase
24
+ class ConnectToAws < LifecycleVM::OpBase
24
25
  reads :region
25
- writes :appconfig_client
26
+ writes :appconfig_client, :secretsmanager_client
26
27
 
27
28
  def call
28
29
  client_opts = { logger: logger }
29
30
  client_opts[:region] = region if region
30
31
 
31
32
  self.appconfig_client = Aws::AppConfig::Client.new(client_opts)
33
+ self.secretsmanager_client = Aws::SecretsManager::Client.new(client_opts)
32
34
  end
33
35
  end
34
36
  end
@@ -16,11 +16,13 @@
16
16
 
17
17
  require 'lifecycle_vm/op_base'
18
18
 
19
+ require 'config_o_mat/secrets_loader'
20
+
19
21
  module ConfigOMat
20
22
  module Op
21
23
  class RefreshAllProfiles < LifecycleVM::OpBase
22
- reads :profile_defs, :applied_profiles, :client_id, :appconfig_client
23
- writes :profiles_to_apply, :last_refresh_time
24
+ reads :profile_defs, :applied_profiles, :client_id, :appconfig_client, :secrets_loader_memory, :secretsmanager_client
25
+ writes :profiles_to_apply, :last_refresh_time, :secrets_loader_memory
24
26
 
25
27
  def call
26
28
  self.profiles_to_apply = []
@@ -53,9 +55,29 @@ module ConfigOMat
53
55
  name: profile_name, previous_version: current_version, new_version: loaded_version
54
56
  )
55
57
 
56
- profiles_to_apply << LoadedProfile.new(
58
+ profile = LoadedAppconfigProfile.new(
57
59
  profile_name, loaded_version, response.content.read, response.content_type
58
60
  )
61
+
62
+ loaded_secrets = nil
63
+
64
+ if !profile.secret_defs.empty?
65
+ self.secrets_loader_memory ||= ConfigOMat::SecretsLoader::Memory.new(secretsmanager_client: secretsmanager_client)
66
+ secrets_loader_memory.update_secret_defs_to_load(profile.secret_defs.values)
67
+
68
+ vm = ConfigOMat::SecretsLoader::VM.new(secrets_loader_memory).call
69
+
70
+ if vm.errors?
71
+ error :"#{profile_name}_secrets", vm.errors
72
+ next
73
+ end
74
+
75
+ loaded_secrets = secrets_loader_memory.loaded_secrets.each_with_object({}) do |(key, value), hash|
76
+ hash[value.name] = value
77
+ end
78
+ end
79
+
80
+ profiles_to_apply << LoadedProfile.new(profile, loaded_secrets)
59
81
  end
60
82
  end
61
83
  end
@@ -19,8 +19,8 @@ require 'lifecycle_vm/op_base'
19
19
  module ConfigOMat
20
20
  module Op
21
21
  class RefreshProfile < LifecycleVM::OpBase
22
- reads :profile_defs, :client_id, :applying_profile, :appconfig_client
23
- writes :applying_profile
22
+ reads :profile_defs, :client_id, :applying_profile, :appconfig_client, :secretsmanager_client, :secrets_loader_memory
23
+ writes :applying_profile, :secrets_loader_memory
24
24
 
25
25
  def call
26
26
  profile_name = applying_profile.name
@@ -56,9 +56,29 @@ module ConfigOMat
56
56
  name: profile_name, previous_version: profile_version, new_version: loaded_version
57
57
  )
58
58
 
59
- self.applying_profile = LoadedProfile.new(
59
+ profile = LoadedAppconfigProfile.new(
60
60
  profile_name, loaded_version, response.content.read, response.content_type
61
61
  )
62
+
63
+ loaded_secrets = nil
64
+
65
+ if !profile.secret_defs.empty?
66
+ self.secrets_loader_memory ||= ConfigOMat::SecretsLoader::Memory.new(secretsmanager_client: secretsmanager_client)
67
+ secrets_loader_memory.update_secret_defs_to_load(profile.secret_defs.values)
68
+
69
+ vm = ConfigOMat::SecretsLoader::VM.new(secrets_loader_memory).call
70
+
71
+ if vm.errors?
72
+ error :"#{profile_name}_secrets", vm.errors
73
+ return
74
+ end
75
+
76
+ loaded_secrets = secrets_loader_memory.loaded_secrets.each_with_object({}) do |(key, value), hash|
77
+ hash[value.name] = value
78
+ end
79
+ end
80
+
81
+ self.applying_profile = LoadedProfile.new(profile, loaded_secrets)
62
82
  end
63
83
  end
64
84
  end
@@ -41,9 +41,9 @@ module ConfigOMat
41
41
 
42
42
  on :reading_meta_config, do: Op::LoadMetaConfig, then: :compiling_templates
43
43
 
44
- on :compiling_templates, do: Op::CompileTemplates, then: :connecting_to_appconfig
44
+ on :compiling_templates, do: Op::CompileTemplates, then: :connecting_to_aws
45
45
 
46
- on :connecting_to_appconfig, do: Op::ConnectToAppconfig, then: :refreshing_profiles
46
+ on :connecting_to_aws, do: Op::ConnectToAws, then: :refreshing_profiles
47
47
 
48
48
  on :refreshing_profiles,
49
49
  do: Op::RefreshAllProfiles,
@@ -116,7 +116,7 @@ module ConfigOMat
116
116
  }
117
117
  }
118
118
 
119
- on :refreshing_profile, do: Op::RefreshProfile, then: :applying_profile
119
+ on :refreshing_profile, do: Op::RefreshProfile, then: :generating_templates
120
120
  end
121
121
  end
122
122
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm/cond_base'
18
+
19
+ module ConfigOMat
20
+ module Cond
21
+ class RequiresLoad < LifecycleVM::CondBase
22
+ reads :loading_secret
23
+
24
+ def call
25
+ !loading_secret.nil?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm/cond_base'
18
+
19
+ module ConfigOMat
20
+ module Cond
21
+ class SecretsToLoad < LifecycleVM::CondBase
22
+ reads :secret_defs_to_load
23
+
24
+ def call
25
+ !secret_defs_to_load.empty?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ Dir[File.join(__dir__, 'cond', '*')].sort.each { |file| require file }
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm'
18
+
19
+ module ConfigOMat
20
+ module SecretsLoader
21
+ class Memory < LifecycleVM::Memory
22
+ attr_reader :secretsmanager_client
23
+
24
+ attr_accessor :secrets_cache, :secret_defs_to_load, :loaded_secrets, :loading_secret
25
+
26
+ def initialize(
27
+ secretsmanager_client: nil,
28
+ secret_defs_to_load: [],
29
+ secrets_cache: {},
30
+ loading_secret: nil,
31
+ logger: nil
32
+ )
33
+ @secretsmanager_client = secretsmanager_client
34
+ @secret_defs_to_load = secret_defs_to_load
35
+ @secrets_cache = secrets_cache
36
+ @loading_secret = loading_secret
37
+ @loaded_secrets = {}
38
+ @logger = logger
39
+ end
40
+
41
+ def update_secret_defs_to_load(defs)
42
+ @loaded_secrets = {}
43
+ @secret_defs_to_load = defs
44
+ self
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm/op_base'
18
+
19
+ module ConfigOMat
20
+ module Op
21
+ class CheckCache < LifecycleVM::OpBase
22
+ reads :loading_secret, :secrets_cache, :loaded_secrets
23
+ writes :loading_secret, :loaded_secrets
24
+
25
+ def call
26
+ # If we're referencing a secret by its version stage, never cache it, since these can be
27
+ # updated out from under us.
28
+ return unless loading_secret.version_stage.nil? || loading_secret.version_stage.empty?
29
+
30
+ cached_secret = secrets_cache[loading_secret.secret_id]
31
+
32
+ if cached_secret && cached_secret.version_id == loading_secret.version_id
33
+ logger&.info(:cached_secret, name: loading_secret.name, version_id: loading_secret.version_id)
34
+ loaded_secrets[loading_secret] = cached_secret
35
+ self.loading_secret = nil
36
+ elsif cached_secret
37
+ logger&.info(
38
+ :invalid_cached_secret,
39
+ name: loading_secret.name,
40
+ expected_version_id: loading_secret.version_id,
41
+ cached_version_id: cached_secret.version_id
42
+ )
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm/op_base'
18
+
19
+ require 'config_o_mat/shared/types'
20
+
21
+ module ConfigOMat
22
+ module Op
23
+ class LoadSecret < LifecycleVM::OpBase
24
+ reads :loading_secret, :secrets_cache, :loaded_secrets, :secretsmanager_client
25
+ writes :loading_secret, :loaded_secrets, :secrets_cache
26
+
27
+ def call
28
+ opts = {
29
+ secret_id: loading_secret.secret_id
30
+ }
31
+
32
+ if loading_secret.version_id
33
+ opts[:version_id] = loading_secret.version_id
34
+ else
35
+ opts[:version_stage] = loading_secret.version_stage
36
+ end
37
+
38
+ response =
39
+ begin
40
+ secretsmanager_client.get_secret_value(opts)
41
+ rescue StandardError => e
42
+ error loading_secret.name, e
43
+ nil
44
+ end
45
+
46
+ return if response.nil? || errors?
47
+
48
+ loaded_secret = LoadedSecret.new(
49
+ loading_secret.name, loading_secret.secret_id, response.version_id,
50
+ response.secret_string, loading_secret.content_type
51
+ )
52
+
53
+ logger&.info(
54
+ :loaded_secret, name: loading_secret.name, arn: response.arn,
55
+ version_id: response.version_id
56
+ )
57
+
58
+ begin
59
+ loaded_secret.validate!
60
+ rescue StandardError => e
61
+ logger&.error(
62
+ :invalid_secret, name: loading_secret.name, arn: response.arn,
63
+ version_id: response.version_id, errors: e
64
+ )
65
+ error loaded_secret.name, e
66
+ end
67
+
68
+ return if errors?
69
+
70
+ secrets_cache[loading_secret.secret_id] = loaded_secret
71
+ loaded_secrets[loading_secret] = loaded_secret
72
+ self.loading_secret = nil
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm/op_base'
18
+
19
+ module ConfigOMat
20
+ module Op
21
+ class StageOneSecret < LifecycleVM::OpBase
22
+ reads :secret_defs_to_load
23
+ writes :loading_secret, :secret_defs_to_load
24
+
25
+ def call
26
+ self.loading_secret = secret_defs_to_load.pop
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ Dir[File.join(__dir__, 'op', '*')].sort.each { |file| require file }
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2021 Teak.io, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'lifecycle_vm'
18
+
19
+ require 'config_o_mat/secrets_loader/op'
20
+ require 'config_o_mat/secrets_loader/cond'
21
+ require 'config_o_mat/secrets_loader/memory'
22
+
23
+ module ConfigOMat
24
+ module SecretsLoader
25
+ class VM < LifecycleVM::VM
26
+ memory_class ConfigOMat::SecretsLoader::Memory
27
+
28
+ on :start, then: {
29
+ case: Cond::SecretsToLoad,
30
+ when: {
31
+ true => {
32
+ do: Op::StageOneSecret,
33
+ then: :check_cache
34
+ },
35
+ false => {
36
+ then: :exit
37
+ }
38
+ }
39
+ }
40
+
41
+ on :check_cache, do: Op::CheckCache, then: {
42
+ case: Cond::RequiresLoad,
43
+ when: {
44
+ true => :load_secret,
45
+ false => :start
46
+ }
47
+ }
48
+
49
+ on :load_secret, do: Op::LoadSecret, then: :start
50
+ end
51
+ end
52
+ end
@@ -187,8 +187,8 @@ module ConfigOMat
187
187
  end
188
188
  end
189
189
 
190
- class LoadedProfile < ConfigItem
191
- attr_reader :name, :version, :contents
190
+ class LoadedAppconfigProfile < ConfigItem
191
+ attr_reader :name, :version, :contents, :secret_defs
192
192
 
193
193
  PARSERS = {
194
194
  'text/plain' => proc { |str| str },
@@ -199,12 +199,14 @@ module ConfigOMat
199
199
  def initialize(name, version, contents, content_type)
200
200
  @name = name
201
201
  @version = version
202
+ @secret_defs = {}
202
203
 
203
204
  parser = PARSERS[content_type]
204
205
 
205
206
  if parser
206
207
  begin
207
208
  @contents = parser.call(contents)
209
+ parse_secrets if @contents.kind_of?(Hash)
208
210
  rescue StandardError => e
209
211
  error :contents, e
210
212
  end
@@ -233,6 +235,133 @@ module ConfigOMat
233
235
  return false if other.version != version || other.contents != contents || other.name != name
234
236
  true
235
237
  end
238
+
239
+ private
240
+
241
+ def parse_secrets
242
+ secret_entries = @contents[:"aws:secrets"]
243
+ return if secret_entries.nil?
244
+
245
+ error :contents_secrets, 'must be a dictionary' if !secret_entries.kind_of?(Hash)
246
+
247
+ secret_entries.each do |(secret_name, secret_conf)|
248
+ secret_def = Secret.new(secret_name, secret_conf)
249
+ secret_def.validate
250
+ error :"contents_secrets_#{secret_name}", secret_def.errors if secret_def.errors?
251
+
252
+ @secret_defs[secret_name] = secret_def
253
+ end
254
+ end
255
+ end
256
+
257
+ class Secret < ConfigItem
258
+ VALID_CONTENT_TYPES = LoadedAppconfigProfile::PARSERS.keys.freeze
259
+
260
+ attr_reader :name, :secret_id, :version_id, :version_stage, :content_type
261
+
262
+ def initialize(name, opts)
263
+ @name = name
264
+ @secret_id = opts[:secret_id]
265
+ @version_id = opts[:version_id]
266
+ @version_stage = opts[:version_stage]
267
+ @content_type = opts[:content_type]&.downcase
268
+
269
+ if (@version_id.nil? || @version_id.empty?) && (@version_stage.nil? || @version_stage.empty?)
270
+ @version_stage = 'AWSCURRENT'
271
+ end
272
+
273
+ @content_type ||= 'application/json'
274
+ end
275
+
276
+ def validate
277
+ error :secret_id, 'must be present' if @secret_id.nil? || @secret_id.empty?
278
+ error :content_type, "must be one of #{VALID_CONTENT_TYPES}" unless VALID_CONTENT_TYPES.include?(@content_type)
279
+ end
280
+
281
+ def hash
282
+ secret_id.hash ^ version_id.hash ^ version_stage.hash & content_type.hash
283
+ end
284
+
285
+ def eql?(other)
286
+ return false if !super(other)
287
+ if other.name != name ||
288
+ other.secret_id != secret_id ||
289
+ other.version_id != version_id ||
290
+ other.version_stage != version_stage ||
291
+ other.content_type != content_type
292
+ return false
293
+ end
294
+ true
295
+ end
296
+ end
297
+
298
+ class LoadedSecret < ConfigItem
299
+ attr_reader :name, :secret_id, :version_id, :contents
300
+
301
+ def initialize(name, secret_id, version_id, secret_string, content_type)
302
+ @name = name
303
+ @secret_id = secret_id
304
+ @version_id = version_id
305
+
306
+ begin
307
+ @contents = LoadedAppconfigProfile::PARSERS[content_type].call(secret_string)
308
+ rescue StandardError => e
309
+ error :contents, e
310
+ end
311
+ end
312
+
313
+ def validate
314
+ # Since name and version_id are coming from AWS and must be present, I'm not going to check
315
+ # them here.
316
+ end
317
+
318
+ def hash
319
+ @name.hash ^ @secret_id.hash ^ @version_id.hash ^ @contents.hash
320
+ end
321
+
322
+ def eql?(other)
323
+ return false if !super(other)
324
+ if other.name != name ||
325
+ other.secret_id != secret_id ||
326
+ other.version_id != version_id ||
327
+ other.contents != contents
328
+ return false
329
+ end
330
+ true
331
+ end
332
+ end
333
+
334
+
335
+ class LoadedProfile < ConfigItem
336
+ extend Forwardable
337
+
338
+ attr_reader :secrets, :loaded_appconfig_profile
339
+
340
+ def_delegators :@loaded_appconfig_profile, :name, :version, :contents
341
+
342
+ def initialize(loaded_appconfig_profile, secrets)
343
+ @loaded_appconfig_profile = loaded_appconfig_profile
344
+ @secrets = secrets || {}
345
+
346
+ @errors = @loaded_appconfig_profile.errors if @loaded_appconfig_profile.errors?
347
+ end
348
+
349
+ def validate
350
+ end
351
+
352
+ def hash
353
+ @loaded_appconfig_profile.hash ^ @secrets.hash
354
+ end
355
+
356
+ def to_h
357
+ contents
358
+ end
359
+
360
+ def eql?(other)
361
+ return false if !super(other)
362
+ return false if other.loaded_appconfig_profile != @loaded_appconfig_profile || other.secrets != @secrets
363
+ true
364
+ end
236
365
  end
237
366
 
238
367
  class GeneratedTemplate < ConfigItem
@@ -15,5 +15,5 @@
15
15
  # limitations under the License.
16
16
 
17
17
  module ConfigOMat
18
- VERSION = "0.2.1"
18
+ VERSION = "0.3.0"
19
19
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: config_o_mat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Scarborough
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-15 00:00:00.000000000 Z
11
+ date: 2022-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-appconfig
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.18'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-secretsmanager
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.57'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.57'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: logsformyfamily
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -144,7 +158,7 @@ files:
144
158
  - lib/config_o_mat/configurator/op/apply_all_profiles.rb
145
159
  - lib/config_o_mat/configurator/op/commit_staged_profile.rb
146
160
  - lib/config_o_mat/configurator/op/compile_templates.rb
147
- - lib/config_o_mat/configurator/op/connect_to_appconfig.rb
161
+ - lib/config_o_mat/configurator/op/connect_to_aws.rb
148
162
  - lib/config_o_mat/configurator/op/generate_all_templates.rb
149
163
  - lib/config_o_mat/configurator/op/next_tick.rb
150
164
  - lib/config_o_mat/configurator/op/notify_systemd_start.rb
@@ -170,6 +184,15 @@ files:
170
184
  - lib/config_o_mat/meta_configurator/op.rb
171
185
  - lib/config_o_mat/meta_configurator/op/generate_systemd_config.rb
172
186
  - lib/config_o_mat/meta_configurator/op/parse_meta_cli.rb
187
+ - lib/config_o_mat/secrets_loader.rb
188
+ - lib/config_o_mat/secrets_loader/cond.rb
189
+ - lib/config_o_mat/secrets_loader/cond/requires_load.rb
190
+ - lib/config_o_mat/secrets_loader/cond/secrets_to_load.rb
191
+ - lib/config_o_mat/secrets_loader/memory.rb
192
+ - lib/config_o_mat/secrets_loader/op.rb
193
+ - lib/config_o_mat/secrets_loader/op/check_cache.rb
194
+ - lib/config_o_mat/secrets_loader/op/load_secret.rb
195
+ - lib/config_o_mat/secrets_loader/op/stage_one_secret.rb
173
196
  - lib/config_o_mat/shared/cond.rb
174
197
  - lib/config_o_mat/shared/cond/early_exit.rb
175
198
  - lib/config_o_mat/shared/op.rb