cps-property-generator 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b480ea73120071abacc074c6279e1da3b9f62d53
4
+ data.tar.gz: e2092c1a6218e51058d1ff049cca4e4bc8f19ddd
5
+ SHA512:
6
+ metadata.gz: f8ceb169740e2ebfb3c0d26379f690507954b166a674081b60080556ecc7994ae9ad4aa0f8856cc4748e81ed89c8730a27e185da864a7679532377d77a642e3b
7
+ data.tar.gz: b9d8808e6608a4dc7dd0f5625401fba02083d8141f3aca8e97e9472d0246c7ed494f559c5450e3d26dc6b318eaa258a7881fb53719c6e7315e65cdea41a7f682
@@ -0,0 +1,115 @@
1
+ ## **Getting Started**
2
+
3
+ #### Creating your properties project:
4
+ ##### Step 1: Creating your config.yml
5
+ 1. Create your top level directory for your property project.
6
+
7
+ `mkdir example-properties`
8
+
9
+ 2. Create a config directory and inside a config.yml file.
10
+
11
+ ```sh
12
+ cd example-properties/
13
+ mkdir config
14
+ cd config
15
+ touch config.yml
16
+ ```
17
+
18
+ The config.yml will define essential configurations that the generator needs to create the property json files.
19
+ In the config.yml file we need three keys set to explain our project to the generator.
20
+
21
+ 1. The `environments` key. This key will define a array of the environments that we are setting properties for. Environments must be unique and have a one to one mapping with amazon aws regions.
22
+ 2. The `accounts` key. This key will define a array of amazon accounts properties will be uploaded to and served in.
23
+ 3. The `environment_configs` key. This key will define three things.
24
+ * The one to one mapping or aws regions to environments.
25
+ * The account a environment lives in.
26
+ * Interpolations for a given environment. Interpolations will be explained in a separate section.
27
+
28
+ Here is a example config.yml
29
+ ```yaml
30
+ environments:
31
+ - 'my-test-env1'
32
+ - 'my-test-env2'
33
+
34
+ accounts:
35
+ - 123456789012
36
+ - 987654321098
37
+
38
+ environment_configs:
39
+ my-test-env1:
40
+ region: 'us-east-1'
41
+ account: 123456789012
42
+ interpolations:
43
+ region: 'us-east-1'
44
+ cloud: 'test-cloud-1'
45
+ domain: 'my1.com'
46
+ my-test-env2:
47
+ region: 'eu-central-1'
48
+ account: 987654321098
49
+ interpolations:
50
+ region: 'eu-central-1'
51
+ cloud: 'test-cloud-2'
52
+ domain: 'my2.com'
53
+ ```
54
+
55
+ ##### Step 2: Creating your globals
56
+ Globals are properties that get mixed in with service definitions. The hierarchy of the global definition sets the ruling for what that property can supersede.
57
+ The globals supersedence order is as follows. Any Environmental globals will overwrite account globals. The resultant merge of environmental and account globals overwrite definitions in the top level globals.yml. In short
58
+ Superscedence from least to greatest is globals.yml, account globals, environment globals. To define globals follow the steps below.
59
+ ###### Top level globals
60
+ 1. Create a globals folder
61
+ ```sh
62
+ cd example-properties/
63
+ mkdir globals
64
+ ```
65
+ 2. If you would like top level globals then create a `globals.yml` in your globals folder.
66
+ ```sh
67
+ cd globals/
68
+ touch globals.yml
69
+ ```
70
+ 3. Define your yaml values in the globals.yml
71
+ ###### Account globals
72
+ 1. Create a folder called the account id of your aws account.
73
+ 2. In the folder created above create an YAML file named after the account id.
74
+ 3. Define your account level globals in the YAML file you created above.
75
+ ###### Environment Globals
76
+ 1. Create a folder called `environments` inside your folder named after your account.
77
+ 2. Inside the `environments` folder create a yaml file named after the environment you would like to define globals for. Only environments defined in your config are supported. The environment must also be mapped in the config to the account the sharing the same name as the folder the environment global yaml file lives in.
78
+ 3. In the newly created environment's yaml file you may define your globals.
79
+
80
+ ##### Step 3: Creating your service definitions
81
+ Service definitions have the highest level of supersedence and will overwrite matching global definitions.
82
+ To create you service definitions you need to create the services folder and then the services yaml files.
83
+ ```sh
84
+ cd example-properties/
85
+ mkdir services
86
+ cd services
87
+ touch my-service-name.yml
88
+ ```
89
+ Service definitions consist of three parts `default`, `environments`, and `encrypted`. Encrypted definitions overwrite environment definitions which will overwrite default definitions. Here is a example of a service file. The name of your service file MUST be the same as your service.
90
+ ```yaml
91
+ default:
92
+ database.host: 'my.database.{domain}'
93
+ database.port: 3306
94
+
95
+ environments:
96
+ my-test-env-1:
97
+ thread.pool.size: 12
98
+ my-test-env-2:
99
+ thread.pool.size: 8
100
+
101
+ encrypted:
102
+ my-test-env-1:
103
+ my.encrypted.property:
104
+ $ssm:
105
+ region: us-east-1
106
+ encrypted: PRETEND_ENCRYPTED_PROPERTY_CIPHERTEXT
107
+ ```
108
+ ###### Adding interpolations
109
+ An interpolation is a value that will be dynamically substituted during generation with the correct value for the environment being generated. Interpolations are declared in the config for a given environment. Once declared an interpolation can be used in a property definition by referencing it in braces. If we were to reference the domain interpolation from the example config above we would use `{domain}`.
110
+
111
+ ##### Step 4: Generating Your Properties (Using the CLI)
112
+ The bin directory contains the generator.rb cli. An example of running the cli is below. The `project_path` argument specifies the path to the properties repo we are generating a uploading properties from. You must be able to create a session with s3 to upload.
113
+ ```sh
114
+ ./generator.rb generate --project_path "~/projects/project-properties" --upload true --upload_account "123456789012" --upload_region "us-east-1" --upload_bucket "propertiesbucket.my-cloud.com"
115
+ ```
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require 'thor'
4
+ require 'yaml'
5
+ require_relative '../lib/generator/generator.rb'
6
+ require_relative '../lib/linter/linter.rb'
7
+ class GeneratorCLI < ::Thor
8
+
9
+ desc 'generate', 'Generate properties'
10
+ option 'project_path', banner: 'PROJECT_PATH', type: :string, desc: 'Path to the property project to generate properties for'
11
+ option 'output', banner: 'OUTPUT', type: :string, desc: 'Output path for locally dumping generated outputs', :default => '/tmp/'
12
+ option 'upload', banner: 'UPLOAD', type: :boolean, desc: 'Whether to upload or not', :default => false
13
+ option 'upload_account', banner: 'UPLOAD_ACCOUNT', type: :string, desc: 'The account you are uploading properties to'
14
+ option 'upload_region', banner: 'UPLOAD_REGION', type: :string, desc: 'The region your property bucket is in'
15
+ option 'upload_bucket', banner: 'UPLOAD_BUCKET', type: :string, desc: 'The bucket you are uploading properties to.'
16
+
17
+ def generate
18
+
19
+ generator = PropertyGenerator::Generator.new(options)
20
+ out = generator.generate
21
+ if options['upload']
22
+ if options['upload_account'].nil? || options['upload_region'].nil? && options['upload_bucket'].nil?
23
+ puts 'Please specify upload configs'
24
+ else
25
+ generator.upload(out, options)
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ desc 'lint', 'Lint YAML files for properties repo'
32
+ option 'project_path', banner: 'PROJECT_PATH', type: :string, desc: 'Path to the property project to generate properties for'
33
+ def lint
34
+ linter = PropertyGenerator::Linter.new(options['project_path'])
35
+ linter.run_tests
36
+ linter.display_report
37
+ if linter.fail_check
38
+ return 1
39
+ else
40
+ return 0
41
+ end
42
+ end
43
+ end
44
+
45
+ GeneratorCLI.start(ARGV)
@@ -0,0 +1,32 @@
1
+ module PropertyGenerator
2
+ require 'yaml'
3
+ class Config
4
+
5
+ attr_accessor :configs
6
+
7
+ def initialize(project_path)
8
+ @project_path = project_path
9
+ end
10
+
11
+ def configs
12
+ @configs ||= read_configs
13
+ end
14
+
15
+ def environments
16
+ configs['environments']
17
+ end
18
+
19
+ def accounts
20
+ configs['accounts']
21
+ end
22
+
23
+ def environment_configs
24
+ configs['environment_configs']
25
+ end
26
+
27
+ def read_configs
28
+ file = "#{@project_path}/config/config.yml"
29
+ YAML.load_file(file)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'config'
2
+ require_relative 'globals'
3
+ require_relative 'service'
4
+ require_relative 'helpers'
5
+
6
+ module PropertyGenerator
7
+ class Generator
8
+ require 'fileutils'
9
+ require 'securerandom'
10
+ include PropertyGenerator
11
+ # purpose: initialize globals and configs
12
+ # serve as a broker between tasks
13
+ def initialize(options)
14
+ project_path = File.expand_path(options['project_path'])
15
+ @configs = PropertyGenerator::Config.new(project_path)
16
+ @globals = PropertyGenerator::Globals.new(project_path, @configs)
17
+ @globals = @globals.globals
18
+ @output_path = "#{File.expand_path(options['output'])}/properties/#{SecureRandom.hex}"
19
+ puts "Properties will be output here #{@output_path}"
20
+ @service_list = PropertyGenerator.read_services(project_path)
21
+
22
+ end
23
+
24
+ def generate
25
+ output = []
26
+ @service_list.each do | service, path|
27
+ service_instance = PropertyGenerator::Service.new(YAML.load_file(path), @configs, @globals)
28
+ service_instance.service
29
+ service_instance.interpolate
30
+
31
+ out = PropertyGenerator.writer(service, service_instance.service, @configs, @output_path)
32
+ (output << out).flatten!
33
+ end
34
+ output
35
+ end
36
+
37
+ def upload(out, config)
38
+ upload_account = config['upload_account']
39
+ upload_region = config['upload_region']
40
+ upload_bucket = config['upload_bucket']
41
+ out.each do |file|
42
+ next unless file.include?("#{upload_account}") && file.include?("#{upload_region}")
43
+ file_region = file.split("/")[-2]
44
+ PropertyGenerator.sync(upload_region, upload_account, upload_bucket, file, file_region)
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+
@@ -0,0 +1,85 @@
1
+ module PropertyGenerator
2
+ class Globals
3
+ require 'yaml'
4
+ attr_accessor :globals
5
+
6
+ def initialize(project_path, config)
7
+ @project_path = project_path
8
+ @environments = config.environments
9
+ @environment_configs = config.environment_configs
10
+ @accounts = config.accounts
11
+ end
12
+
13
+ def globals
14
+ @globals ||= condense_globals
15
+ end
16
+
17
+
18
+ def get_main_global
19
+ top_level = {}
20
+ if File.exists?("#{@project_path}/globals/globals.yml")
21
+ top_level = YAML.load_file("#{@project_path}/globals/globals.yml")
22
+ end
23
+ top_level
24
+ end
25
+
26
+ def get_account_globals
27
+ data = {}
28
+ @accounts.each do |account|
29
+ next unless Dir.exists?("#{@project_path}/globals/accounts/#{account}")
30
+ account_default_file = "#{@project_path}/globals/accounts/#{account}/#{account}.yml"
31
+ data[account] = YAML.load_file(account_default_file) if File.exists?(account_default_file)
32
+ end
33
+ data
34
+ end
35
+
36
+ def get_environment_globals
37
+ data = {}
38
+ @accounts.each do |account|
39
+ next unless Dir.exists?("#{@project_path}/globals/accounts/#{account}/environments")
40
+ data[account] = {}
41
+ @environments.each do |env|
42
+ next unless File.exists?("#{@project_path}/globals/accounts/#{account}/environments/#{env}.yml")
43
+ data[account][env] = YAML.load_file("#{@project_path}/globals/accounts/#{account}/environments/#{env}.yml")
44
+ end
45
+ end
46
+ data
47
+ end
48
+
49
+
50
+ #merge environment globals with account globals.
51
+ def condense_globals
52
+ condensed = {}
53
+ # get account and the environmental hash's for said account
54
+ environment_globals = get_environment_globals
55
+ account_globals = get_account_globals
56
+ main_global = get_main_global
57
+ # nothing to do here if everything is empty
58
+ return condensed if environment_globals.empty? && account_globals.empty? && main_global.empty?
59
+
60
+ environment_globals.each do |account, env_global |
61
+ # get the env and the values
62
+ env_global.each do |env, hash|
63
+ account_globals[account] ||= {}
64
+ # set the environment globals to be the account global merged with the env globals
65
+ env_global[env] = account_globals[account].merge(hash) unless hash.empty?
66
+ condensed[env] = env_global[env]
67
+ end
68
+ end
69
+
70
+ unless main_global.empty?
71
+ # All environments need the main global definitions
72
+ @environments.each do |env|
73
+ # If a key/value pair for a environment has not been defined set one so we can merge
74
+ condensed[env] ||= {}
75
+ # We need to merge into the globals so any env configs overwrite main global configs.
76
+ # Dup so we dont modify the original object
77
+ main_global_dup = main_global.dup
78
+ condensed[env] = main_global_dup.merge(condensed[env])
79
+ end
80
+ end
81
+ condensed
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,89 @@
1
+ module PropertyGenerator
2
+ class Service
3
+ attr_accessor :service
4
+ def initialize(service_data, config, globals)
5
+ @service_data = service_data
6
+ @environments = config.environments
7
+ @globals = globals
8
+ @environment_configs = config.environment_configs
9
+ set_service
10
+ end
11
+
12
+ def set_service
13
+ service_data = merge_env_default(@service_data, @environments)
14
+ @service = merge_service_with_globals(@globals, service_data, @environments)
15
+ end
16
+
17
+ def service
18
+ @service
19
+ end
20
+
21
+ def interpolate
22
+ environments = @environments
23
+ #read in config
24
+ #interate through environment and substitute config for values for that environment
25
+ environments.each do | env|
26
+ #get the map of config for a env
27
+ interpolations = @environment_configs[env]['interpolations']
28
+
29
+ #interate through the properties for an environment and gsub the config
30
+ @service[env].each do | property_key, property_value|
31
+ property_value_dup = property_value.dup
32
+ interpolations.each do |matcher_key, matcher_value|
33
+ if property_value.class == String && property_value_dup.include?("{#{matcher_key}}")
34
+ @service[env][property_key] = property_value_dup.gsub!("{#{matcher_key}}", matcher_value)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ service
40
+ end
41
+
42
+ def merge_env_default(data, environments)
43
+ #creates a hash of the enviornments merged with the defaults
44
+ # {service => {env1 => {properties},
45
+ # env2 => {properties}
46
+ # }
47
+ # }
48
+ output = {}
49
+ default = data['default']
50
+
51
+ environments.each do |env|
52
+ default_clone = default.dup
53
+ #if nil, use set to environments as nothing to merge env with
54
+ data['environments'] ||= {}
55
+ data['environments'][env] ||= {}
56
+ environment_data = data['environments'][env].dup
57
+ if data['encrypted']
58
+ encrypted = data['encrypted'][env].dup unless data['encrypted'][env].nil?
59
+ environment_data = data['environments'][env].merge(encrypted) unless encrypted.nil?
60
+ end
61
+ if default_clone.nil?
62
+ merged = environment_data
63
+ else
64
+ merged = default_clone.merge(environment_data)
65
+ end
66
+ output[env] = merged
67
+ end
68
+ output
69
+ end
70
+
71
+ def merge_service_with_globals(globals_data, service_data, environments)
72
+ #service will now overwrite globals, merging will be done for each environment
73
+ output = {}
74
+ envs = environments
75
+ envs.each do |env|
76
+ globals_clone = globals_data.dup
77
+ if globals_clone[env].nil? || globals_clone[env] == false
78
+ merged = service_data[env]
79
+ else
80
+ merged = globals_clone[env].merge(service_data[env])
81
+ end
82
+ output[env] = merged
83
+ end
84
+ output
85
+ end
86
+
87
+
88
+ end
89
+ end
@@ -0,0 +1,81 @@
1
+ module PropertyGenerator
2
+ require 'json'
3
+ require 'fileutils'
4
+ require 'aws-sdk-s3'
5
+ class << self
6
+
7
+ def test_runner(object, test_list)
8
+ results = {}
9
+ test_list.each do |test|
10
+ results[test] = object.send(test)
11
+ end
12
+ results
13
+ end
14
+
15
+ def get_list_of_files(path, ignore_list)
16
+ #Returns a list of files in given path
17
+ #Ignores files in a given ignore list
18
+ Dir.glob(path + "/**/*").select{ |e| File.file? e unless ignore_list.include?(e.split('/')[(e.split('/')).length - 1])}
19
+ end
20
+
21
+ def valid_paths(path)
22
+ valid_paths = []
23
+ list_of_file_paths = get_list_of_files(path, [])
24
+ list_of_file_paths.each do |file_path|
25
+ begin
26
+ YAML.load_file(file_path)
27
+ valid_paths << file_path
28
+ rescue
29
+ next
30
+ end
31
+ end
32
+ valid_paths
33
+ end
34
+
35
+ def invalid_paths(path, ignore_list)
36
+ invalid_paths = []
37
+ list_of_file_paths = get_list_of_files(path, ignore_list)
38
+ list_of_file_paths.each do |file_path|
39
+ begin
40
+ YAML.load_file(file_path)
41
+ rescue
42
+ invalid_paths << file_path
43
+ end
44
+ end
45
+ invalid_paths
46
+ end
47
+
48
+ def read_services(path)
49
+ Dir.glob("#{path}/services/*.{yaml,yml}").each_with_object({}) do |file, acc|
50
+ name = File.basename(file)[/(?<service>.*)\.ya?ml$/, :service]
51
+ path = File.absolute_path(file)
52
+ acc[name] = path
53
+ end
54
+ end
55
+
56
+ def writer(service_name, finalized, configs, output_path)
57
+ output = []
58
+ envs = configs.environments
59
+ environmental_configs = configs.environment_configs
60
+ envs.each do | env|
61
+ account = environmental_configs[env]["account"]
62
+ region = environmental_configs[env]["region"]
63
+ json = JSON.pretty_generate({"properties" => finalized[env]})
64
+ FileUtils.mkdir_p("#{output_path}/#{account}/#{region}/") unless Dir.exist?("#{output_path}/#{account}/#{region}/")
65
+ File.write("#{output_path}/#{account}/#{region}/#{service_name}.json", json)
66
+ output << "#{output_path}/#{account}/#{region}/#{service_name}.json"
67
+ end
68
+ output
69
+ end
70
+
71
+ def sync(region, account, bucket, file, file_region)
72
+ s3 = Aws::S3::Resource.new(region: region)
73
+ filename = file.split("/").last
74
+ puts "Destination: #{account}/#{file_region}/#{filename}"
75
+ puts "Uploading: #{file}"
76
+ obj = s3.bucket(bucket).object("#{account}/#{file_region}/#{filename}")
77
+ obj.upload_file(file)
78
+ end
79
+
80
+ end
81
+ end