kumo_config 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.buildkite/pipeline.yml +18 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1156 -0
- data/Gemfile +4 -0
- data/README.md +121 -0
- data/Rakefile +2 -0
- data/VERSION +1 -0
- data/kumo_config.gemspec +25 -0
- data/lib/kumo_config/environment_config.rb +125 -0
- data/lib/kumo_config/file_loader.rb +38 -0
- data/lib/kumo_config.rb +1 -0
- data/script/build.sh +12 -0
- data/script/integration_test.sh +13 -0
- data/script/unit_test.sh +13 -0
- data/spec/integration/.gitkeep +0 -0
- data/spec/integration/config_spec.rb +67 -0
- data/spec/integration/fixtures/.gitkeep +0 -0
- data/spec/integration/fixtures/common.yml +2 -0
- data/spec/integration/fixtures/common_secrets.yml +11 -0
- data/spec/integration/fixtures/development.yml +1 -0
- data/spec/integration/fixtures/development_secrets.yml +5 -0
- data/spec/integration/fixtures/production.yml +2 -0
- data/spec/integration/fixtures/production_secrets.yml +11 -0
- data/spec/lib/environment_config_spec.rb +202 -0
- data/spec/lib/file_loader_spec.rb +64 -0
- data/spec/spec_helper.rb +28 -0
- metadata +153 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# KumoConfig [![Build status](https://badge.buildkite.com/1d42699430881063b95a0082c8f23f0acc6d5bad5909716720.svg)](https://buildkite.com/redbubble/kumo-config-gem) [![Code Climate](https://codeclimate.com/github/redbubble/kumo_keisei_config/badges/gpa.svg)](https://codeclimate.com/github/redbubble/kumo_config_gem)
|
2
|
+
|
3
|
+
A utility for resolving environment configuration.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
This gem is automatically installed in the kumo container, so any `apply-env` or `deploy` scripts have access to it.
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'kumo_config'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install kumo_config
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Basic Usage
|
26
|
+
|
27
|
+
The basic usage will give you an EnvironmentConfig based on the paths you specify.
|
28
|
+
|
29
|
+
`config_path`: Where environments' configuration files live, e.g. `production.yml`, `staging.yml`, `production_secrets.yml`, `staging_secrets.yml`
|
30
|
+
`template_path`: Where the CloudFormation template lives - **TODO: THIS BELONGS IN KUMO_KEISEI, NOT HERE**
|
31
|
+
`params_template_file_path`: The location of the template for submitting paramters to CloudFormation. **AGAIN, THIS DOES NOT BELONG HERE**
|
32
|
+
`injected_config`: A Hash containing any configuration you want to inject at runtime rather than loading from a file. For example, you might want to pull some settings from a VPC.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
EnvironmentConfig.new(
|
36
|
+
config_path: File.join('/app', 'env', 'config'),
|
37
|
+
template_path: File.join('/app', 'env', 'cloudformation', 'redbubble.json'),
|
38
|
+
params_template_file_path: File.join('/app', 'env', 'cloudformation', 'redbubble.yml.erb')
|
39
|
+
injected_config: { key: 'value' }
|
40
|
+
)
|
41
|
+
```
|
42
|
+
|
43
|
+
### Configuration Hierarchy
|
44
|
+
|
45
|
+
Configuration will be loaded from the following sources:
|
46
|
+
|
47
|
+
1. `common.yml` and `common_secrets.yml` if they exist.
|
48
|
+
2. `{environment}.yml` and `{environment}_secrets.yml` or `development.yml` and `development_secrets.yml` if environment specific config does not exist.
|
49
|
+
|
50
|
+
### Injecting Configuration
|
51
|
+
|
52
|
+
You can also inject configuration at run time by adding it to the object provided to the `apply!` call:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
stack_config = {
|
56
|
+
config_path: File.join('/app', 'env', 'config'),
|
57
|
+
template_path: File.join('/app', 'env', 'cloudformation', 'myapp.json'),
|
58
|
+
injected_config: {
|
59
|
+
'Seed' => random_seed,
|
60
|
+
}
|
61
|
+
}
|
62
|
+
stack.apply!(stack_config)
|
63
|
+
```
|
64
|
+
|
65
|
+
### Getting the configuration and secrets without an `apply!`
|
66
|
+
|
67
|
+
If you need to inspect the configuration without applying a stack, call `config`:
|
68
|
+
```ruby
|
69
|
+
stack_config = {
|
70
|
+
config_path: File.join('/app', 'env', 'config'),
|
71
|
+
template_path: File.join('/app', 'env', 'cloudformation', 'myapp.json'),
|
72
|
+
injected_config: {
|
73
|
+
'Seed' => random_seed,
|
74
|
+
}
|
75
|
+
}
|
76
|
+
marshalled_config = stack.config(stack_config)
|
77
|
+
marshalled_secrets = stack.plain_text_secrets(stack_config)
|
78
|
+
|
79
|
+
if marshalled_config['DB_HOST'].start_with? '192.' then
|
80
|
+
passwd = marshalled_secrets['DB_PASS']
|
81
|
+
...
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
## Dependencies
|
86
|
+
|
87
|
+
#### Ruby Versions
|
88
|
+
|
89
|
+
This gem is tested with Ruby (MRI) versions 1.9.3 and 2.2.3.
|
90
|
+
|
91
|
+
## Release
|
92
|
+
|
93
|
+
1. Upgrade version in VERSION
|
94
|
+
2. Run ./script/release-gem
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
1. Fork it ( https://github.com/[my-github-username]/kumo_config/fork )
|
99
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
100
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
101
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
102
|
+
5. Create a new Pull Request
|
103
|
+
|
104
|
+
### Automated AWS Integration Tests - TODO: REVISE
|
105
|
+
|
106
|
+
You can test the Cloudformation responsibilities of this gem by extending the integration tests at `spec/integration`.
|
107
|
+
|
108
|
+
To run these tests you need a properly configured AWS environment (with `AWS_DEFAULT_REGION`, `AWS_ACCESS_KEY` and `AWS_SECRET_ACCESS_KEY` set) and then run `./script/integration_test.sh`.
|
109
|
+
|
110
|
+
If you run this within a Buildkite job then you will have a stack named "kumokeisei-test-$buildnumber" created and torn down for each integration test context. If you run this outside of a Buildkite job then the stack will be named "kumokeisei-test-$username".
|
111
|
+
|
112
|
+
### Manual testing with Kumo Tools container
|
113
|
+
|
114
|
+
Changes to the gem can be manually tested end to end in a project that uses the gem (i.e. http-wala).
|
115
|
+
|
116
|
+
1. First start the dev-tools container: `kumo tools debug non-production`
|
117
|
+
1. gem install specific_install
|
118
|
+
1. Re-install the gem: `gem specific_install https://github.com/redbubble/kumo_keisei_gem.git -b <your_branch>`
|
119
|
+
1. Fire up a console: `irb`
|
120
|
+
1. Require the gem: `require "kumo_keisei"`
|
121
|
+
1. Interact with the gem's classes. `KumoKeisei::Stack.new(...).apply!`
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/kumo_config.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
version = File.read(File.expand_path('../VERSION', __FILE__)).strip
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kumo_config"
|
8
|
+
spec.version = version
|
9
|
+
spec.authors = ["Redbubble"]
|
10
|
+
spec.email = ["delivery-engineering@redbubble.com"]
|
11
|
+
spec.summary = "A utility for reading Kumo configuration"
|
12
|
+
spec.homepage = "http://redbubble.com"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_runtime_dependency 'kumo_ki'
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
24
|
+
spec.add_development_dependency "pry", "~> 0.10"
|
25
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'kumo_ki'
|
2
|
+
require 'logger'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require_relative 'file_loader'
|
6
|
+
|
7
|
+
module KumoConfig
|
8
|
+
# Environment Configuration for a cloud formation stack
|
9
|
+
class EnvironmentConfig
|
10
|
+
class ConfigurationError < StandardError; end
|
11
|
+
|
12
|
+
LOGGER = Logger.new(STDOUT)
|
13
|
+
|
14
|
+
attr_reader :app_name, :env_name
|
15
|
+
|
16
|
+
def initialize(options, logger = LOGGER)
|
17
|
+
@env_name = options[:env_name]
|
18
|
+
@params_template_file_path = options[:params_template_file_path]
|
19
|
+
@injected_config = options[:injected_config] || {}
|
20
|
+
@log = logger
|
21
|
+
|
22
|
+
if options[:config_path]
|
23
|
+
@config_file_loader = KumoConfig::FileLoader.new(config_dir_path: options[:config_path])
|
24
|
+
elsif options[:config_dir_path]
|
25
|
+
@log.warn "[DEPRECATION] `:config_dir_path` is deprecated, please pass in `:config_path` instead"
|
26
|
+
@config_file_loader = KumoConfig::FileLoader.new(config_dir_path: options[:config_dir_path])
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def production?
|
32
|
+
env_name == 'production'
|
33
|
+
end
|
34
|
+
|
35
|
+
def development?
|
36
|
+
!%w(production staging).include? env_name
|
37
|
+
end
|
38
|
+
|
39
|
+
def plain_text_secrets
|
40
|
+
@plain_text_secrets ||= decrypt_secrets(encrypted_secrets)
|
41
|
+
end
|
42
|
+
|
43
|
+
def config
|
44
|
+
# a hash of all settings that apply to this environment
|
45
|
+
@config ||= common_config.merge(env_config).merge(@injected_config)
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_binding
|
49
|
+
binding
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def kms
|
55
|
+
@kms ||= KumoKi::KMS.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def params_template_erb
|
59
|
+
return nil unless @params_template_file_path && File.exist?(@params_template_file_path)
|
60
|
+
template_file_loader = KumoConfig::FileLoader.new(config_dir_path: File.dirname(@params_template_file_path))
|
61
|
+
template_file_loader.load_erb(File.basename(@params_template_file_path))
|
62
|
+
end
|
63
|
+
|
64
|
+
def decrypt_secrets(secrets)
|
65
|
+
Hash[
|
66
|
+
secrets.map do |name, cipher_text|
|
67
|
+
@log.debug "Decrypting '#{name}'"
|
68
|
+
decrypt_cipher name, cipher_text
|
69
|
+
end
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
def decrypt_cipher(name, cipher_text)
|
74
|
+
if cipher_text.start_with? '[ENC,'
|
75
|
+
begin
|
76
|
+
[name, kms.decrypt(cipher_text[5, cipher_text.size]).to_s]
|
77
|
+
rescue
|
78
|
+
@log.error "Error decrypting secret '#{name}'"
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
else
|
82
|
+
[name, cipher_text]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def env_config_file_name
|
87
|
+
"#{env_name}.yml"
|
88
|
+
end
|
89
|
+
|
90
|
+
def env_secrets_file_name
|
91
|
+
"#{env_name}_secrets.yml"
|
92
|
+
end
|
93
|
+
|
94
|
+
def encrypted_secrets
|
95
|
+
encrypted_common_secrets.merge(encrypted_env_secrets)
|
96
|
+
end
|
97
|
+
|
98
|
+
def encrypted_common_secrets
|
99
|
+
@config_file_loader.load_hash('common_secrets.yml')
|
100
|
+
end
|
101
|
+
|
102
|
+
def encrypted_env_secrets
|
103
|
+
secrets = @config_file_loader.load_hash(env_secrets_file_name)
|
104
|
+
|
105
|
+
if !secrets.empty?
|
106
|
+
secrets
|
107
|
+
else
|
108
|
+
@config_file_loader.load_hash('development_secrets.yml')
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def common_config
|
113
|
+
@config_file_loader.load_hash('common.yml')
|
114
|
+
end
|
115
|
+
|
116
|
+
def env_config
|
117
|
+
config = @config_file_loader.load_hash(env_config_file_name)
|
118
|
+
if !config.empty?
|
119
|
+
config
|
120
|
+
else
|
121
|
+
@config_file_loader.load_hash('development.yml')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module KumoConfig
|
4
|
+
class FileLoader
|
5
|
+
def initialize(options)
|
6
|
+
@config_dir_path = options[:config_dir_path]
|
7
|
+
end
|
8
|
+
|
9
|
+
def load_hash(file_name, optional = true)
|
10
|
+
# reads a file presuming it's a yml in form of key: value, returning it as a hash
|
11
|
+
path = file_path(file_name)
|
12
|
+
|
13
|
+
begin
|
14
|
+
YAML::load(File.read(path))
|
15
|
+
rescue Errno::ENOENT => ex
|
16
|
+
# file not found, return empty dictionary if that is ok
|
17
|
+
return {} if optional
|
18
|
+
raise ex
|
19
|
+
rescue StandardError => ex
|
20
|
+
# this is an error we weren't expecting
|
21
|
+
raise ex
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_erb(file_name)
|
26
|
+
# loads a file, constructs an ERB object from it and returns the ERB object
|
27
|
+
# DOES NOT RENDER A RESULT!!
|
28
|
+
path = file_path(file_name)
|
29
|
+
ERB.new(File.read(path))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def file_path(file_name)
|
35
|
+
File.join(@config_dir_path, file_name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/kumo_config.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "kumo_config/environment_config"
|
data/script/build.sh
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
bundle install
|
6
|
+
if [[ -z "$KUMO_CONFIG_VERSION" && -n "$BUILDKITE_BUILD_NUMBER" ]]; then
|
7
|
+
export KUMO_CONFIG_VERSION="$BUILDKITE_BUILD_NUMBER"
|
8
|
+
fi
|
9
|
+
|
10
|
+
echo "--- :wind_chime: Building gem :wind_chime:"
|
11
|
+
|
12
|
+
gem build kumo_config.gemspec
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
echo "--- :clock1: :clock2: running specs :clock3: :clock4:"
|
6
|
+
bundle install && bundle exec rspec --pattern "spec/integration/*_spec.rb"
|
7
|
+
|
8
|
+
function inline_image {
|
9
|
+
printf '\033]1338;url='"$1"';alt='"$2"'\a\n'
|
10
|
+
}
|
11
|
+
|
12
|
+
echo "+++ Done! :thumbsup: :shipit:"
|
13
|
+
inline_image "https://giftoppr.desktopprassets.com/uploads/f828c372186b5fa80a1c553adbcd4bc4d331396b/tumblr_m2cg550aq21ql201ao1_500.gif" "Yuss"
|
data/script/unit_test.sh
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
echo "--- :clock1: :clock2: running specs :clock3: :clock4:"
|
6
|
+
bundle install && bundle exec rspec --exclude-pattern "spec/integration/*_spec.rb"
|
7
|
+
|
8
|
+
function inline_image {
|
9
|
+
printf '\033]1338;url='"$1"';alt='"$2"'\a\n'
|
10
|
+
}
|
11
|
+
|
12
|
+
echo "+++ Done! :thumbsup: :shipit:"
|
13
|
+
inline_image "https://giftoppr.desktopprassets.com/uploads/f828c372186b5fa80a1c553adbcd4bc4d331396b/tumblr_m2cg550aq21ql201ao1_500.gif" "Yuss"
|
File without changes
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'KumoConfig' do
|
4
|
+
let(:config_path) { File.join(File.dirname(__FILE__), 'fixtures') }
|
5
|
+
let(:environment_name) { 'production' }
|
6
|
+
|
7
|
+
subject { KumoConfig::EnvironmentConfig.new(
|
8
|
+
env_name: environment_name,
|
9
|
+
config_path: config_path
|
10
|
+
) }
|
11
|
+
|
12
|
+
it 'resolves the configuration for the specified environment' do
|
13
|
+
expect(subject.config['production_key']).to eq('production_value')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'uses commmon configuration if nothing is specified in an environment or injected config' do
|
17
|
+
expect(subject.config['common_key']).to eq('common_value')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'gives the environment config precedence over common config' do
|
21
|
+
expect(subject.config['overridden_key']).to eq('production override value')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'resolves secret configuration into a Hash' do
|
25
|
+
expect(subject.plain_text_secrets['production_secret_key']).to eq('production_secret_value')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'uses commmon secret configuration if nothing is specified in the environment' do
|
29
|
+
expect(subject.plain_text_secrets['common_secret_key']).to eq('common_secret_value')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'gives the environment secret precedence over common secrets' do
|
33
|
+
expect(subject.plain_text_secrets['overridden_secret']).to eq('production_override_secret')
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when there is injected configuration' do
|
37
|
+
subject { KumoConfig::EnvironmentConfig.new(
|
38
|
+
env_name: environment_name,
|
39
|
+
config_path: config_path,
|
40
|
+
injected_config: {
|
41
|
+
'injected_key' => 'injected value',
|
42
|
+
'overridden_key' => 'injected override value'
|
43
|
+
}
|
44
|
+
) }
|
45
|
+
|
46
|
+
it 'resolves injected configuration items' do
|
47
|
+
expect(subject.config['injected_key']).to eq('injected value')
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'gives injected config precedence over config from files' do
|
51
|
+
expect(subject.config['overridden_key']).to eq('injected override value')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
context 'when an environment is specified for which we do not have configuration files' do
|
57
|
+
let(:environment_name) { 'nonsense' }
|
58
|
+
|
59
|
+
it 'uses development environment as a default' do
|
60
|
+
expect(subject.config['development_key']).to eq('development value')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'uses development secrets' do
|
64
|
+
expect(subject.plain_text_secrets['development_secret']).to eq('development_secret_value')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
common_secret_key: >
|
2
|
+
[ENC,AQECAHiRAIBw6aG5PR5XWhLpFs0CtWZCoVenu5eErfT6U2j+PgAAAHEwbwYJ
|
3
|
+
KoZIhvcNAQcGoGIwYAIBADBbBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEE
|
4
|
+
DFIEC1gYWtlJQlCFnwIBEIAuKctvUp9pni1Ij1M5tbyBkAms4mcKedYIRzUO
|
5
|
+
FASJonP8HpTe11oL0gbQQFP5vA==
|
6
|
+
|
7
|
+
overridden_secret: >
|
8
|
+
[ENC,AQECAHiRAIBw6aG5PR5XWhLpFs0CtWZCoVenu5eErfT6U2j+PgAAAG4wbAYJ
|
9
|
+
KoZIhvcNAQcGoF8wXQIBADBYBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEE
|
10
|
+
DPBwsLbzuz9H4WL7twIBEIAre1We74YNLNu3xKLLvF3J5UJD8eH1+7IHn0II
|
11
|
+
Bne6CbUPITvPc2N1Hq9KYA==
|
@@ -0,0 +1 @@
|
|
1
|
+
development_key: development value
|
@@ -0,0 +1,11 @@
|
|
1
|
+
production_secret_key: >
|
2
|
+
[ENC,AQECAHiRAIBw6aG5PR5XWhLpFs0CtWZCoVenu5eErfT6U2j+PgAAAHUwcwYJ
|
3
|
+
KoZIhvcNAQcGoGYwZAIBADBfBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEE
|
4
|
+
DOYHUsqhXlV2Chl9AQIBEIAyPvjZe1pWhGOZ6RGP8srNB1oMC/4JwX+nYdjC
|
5
|
+
+i1U5PusiCrRERJdk3B0Cm4yIz3GexY=
|
6
|
+
|
7
|
+
overridden_secret: >
|
8
|
+
[ENC,AQECAHiRAIBw6aG5PR5XWhLpFs0CtWZCoVenu5eErfT6U2j+PgAAAHgwdgYJ
|
9
|
+
KoZIhvcNAQcGoGkwZwIBADBiBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEE
|
10
|
+
DEgzP81EmQgMXTuNFwIBEIA1lkR9cGUtdAOQjT3S19RKKL3w2KgIHWnrZh05
|
11
|
+
tM25nWeufrLe4wldF73/4zSXfEZqlxycfZ0=
|