config_kit 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
1
+ require 'yaml'
2
+ module ConfigKit::Data
3
+ class FileLoader < Loader
4
+ def self.loader; "file"; end
5
+
6
+ attr_reader :files
7
+ def initialize(uri_kls, env, app, branch)
8
+ @uri_kls, @env, @app, @branch = uri_kls, env, app, branch
9
+ @path = File.expand_path('.',File.join(retrieve_path(@uri_kls.path),env))
10
+ super()
11
+ end
12
+
13
+ def run(&block)
14
+ return run_all unless block
15
+ run_batch(&block)
16
+ end
17
+
18
+ def run_all
19
+ files_data = {}
20
+ @files.each do |f|
21
+ files_data.merge!(load_one(f))
22
+ end
23
+ files_data
24
+ end
25
+
26
+ def run_batch(&block)
27
+ while !finish?
28
+ next_batch
29
+ files_data = {}
30
+ @current_files.each do |f|
31
+ files_data.merge!(load_one(f))
32
+ end
33
+ block.call(files_data)
34
+ end
35
+ end
36
+
37
+ def retrieve_files
38
+ files = if @app == 'all'
39
+ Dir["#{@path}/**/*.yml"].select { |f| match_for?(f, @env) }
40
+ else
41
+ Dir["#{@path}/**/*.yml"].select {|f| match_for?(f, @app) && match_for?(f, @env)}
42
+ end
43
+ raise ConfigKit::Data::Loader::LoaderFailure.new('No data file found.') if files.empty?
44
+ files
45
+ end
46
+ private
47
+
48
+ def retrieve_path(path)
49
+ split_path = path[1..-1].split('/')
50
+ return path[1..-1] if split_path[0] == '.'
51
+ path
52
+ end
53
+
54
+ def env_app_for(f)
55
+ File.basename(f).split('.')[0..1]
56
+ end
57
+
58
+ def load_one(f)
59
+ env, app = env_app_for(f)
60
+ raise ConfigKit::Data::Loader::LoaderFailure.new("Wrong data file env(#{env}) for loaded #{@env}") unless env == @env
61
+ {app => YAML.load_file(f)}
62
+ end
63
+
64
+ def match_for?(f,info)
65
+ File.basename(f).split('.').find {|a| a == info}
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,39 @@
1
+ require 'git'
2
+ module ConfigKit::Data
3
+ class GitLoader < Loader
4
+ class GitLoaderError < ConfigKit::Error; end
5
+ def self.loader; "git"; end
6
+ attr_reader :clone_path
7
+ def initialize(uri_kls, env, app, version)
8
+ @uri_kls, @env, @app, @version = uri_kls, env, app, version
9
+ @clone_path = Dir.mktmpdir
10
+ @file_path = "file://#{@clone_path}"
11
+ @file_kls = URI.parse(@file_path)
12
+ clone
13
+ end
14
+
15
+ def run(&block)
16
+ ConfigKit.logger.debug "Git is loading env(#{@env}) #{@app} from #{@uri_kls.to_s}"
17
+ begin
18
+ FileLoader.new(@file_kls, @env, @app, @version).run(&block)
19
+ ensure
20
+ FileUtils.rm_rf @clone_path
21
+ end
22
+ end
23
+
24
+ private
25
+ def clone
26
+ ConfigKit.logger.debug "Git is cloning env(#{@env}) #{@app} from #{@uri_kls.to_s}"
27
+ begin
28
+ g = Git.clone(@uri_kls.to_s, @clone_path)
29
+ tags = g.tags.map { |t| t.name }
30
+ raise GitLoaderError.new "Version(#{@version}) not found" unless tags.include?(@version)
31
+ g.checkout(@version)
32
+ rescue GitLoaderError => e
33
+ raise GitLoaderError.new e.message
34
+ rescue Exception => e
35
+ raise GitLoaderError.new "Unknown error to load (#{@version}) in #{@uri_kls.to_s}"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,78 @@
1
+ require 'config_kit/ext/hash'
2
+ module ConfigKit
3
+ class DeployData < SlashedHash
4
+ class DeployDataOptsError < ConfigKit::Error; end
5
+ def initialize(name, version, opts)
6
+ @name = name
7
+ @version = version[0] == 'v' ? version : "v#{version}"
8
+ @api_version = opts.fetch(:api_version, '1.0')
9
+ @kind = opts.fetch(:kind, 'config_kit')
10
+ @extra = opts.fetch(:extra, :no_default)
11
+ @cs = opts.fetch(:cs, "no_cs") if @extra != :default_only
12
+
13
+ ConfigKit.logger.debug "Deploy in #{@extra} options"
14
+ check_extra(@extra)
15
+ @binded_data = binding_data
16
+ super(@binded_data)
17
+ end
18
+
19
+ def binding_deploy
20
+ {'deploy' => data}
21
+ end
22
+
23
+ def binding_kind
24
+ { @kind => binding_deploy}
25
+ end
26
+
27
+ def binding_default
28
+ {
29
+ @kind => {
30
+ 'deploy' => deploy_default_data
31
+ }
32
+ }
33
+ end
34
+
35
+ def binding_data
36
+ if @extra == :no_default
37
+ binding_kind
38
+ elsif @extra == :set_default
39
+ binding_default.deep_merge(binding_kind)
40
+ elsif @extra == :default_only
41
+ binding_default
42
+ end
43
+ end
44
+
45
+ def data
46
+ {
47
+ @name => {
48
+ @version =>
49
+ {
50
+ "ts" => ts,
51
+ "cs" => @cs
52
+ }
53
+ }
54
+ }
55
+ end
56
+
57
+ def deploy_default_data
58
+ {
59
+ @name => {
60
+ 'default' => @version
61
+ }
62
+ }
63
+ end
64
+
65
+ private
66
+ def extra_options
67
+ [:no_default, :set_default, :default_only]
68
+ end
69
+
70
+ def check_extra(extra)
71
+ raise DeployDataOptsError.new "extra options(#{extra_options.join('|')}) error: #{extra}." unless extra_options.include?(extra)
72
+ end
73
+
74
+ def ts
75
+ (Time.now.to_f * 1000).to_i
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module ConfigKit
2
+ class Error < StandardError; end
3
+ end
@@ -0,0 +1,6 @@
1
+ class ::Hash
2
+ def deep_merge(second)
3
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
4
+ self.merge(second.to_h, &merger)
5
+ end
6
+ end
@@ -0,0 +1,45 @@
1
+ module ConfigKit
2
+ class SlashedHash < ::Hash
3
+ class SlashedHashTypeError < Exception; end
4
+ def initialize(hash, keep_nesting=false)
5
+ self.merge!(hash) if keep_nesting
6
+
7
+ self.merge!(dot_flattened(hash))
8
+ SlashedHash.symbolize(self)
9
+ end
10
+
11
+ def inspect
12
+ "#<#{self.class.name}:#{object_id} #{super}>"
13
+ end
14
+
15
+ def to_hash
16
+ {}.replace(self)
17
+ end
18
+
19
+ def self.symbolize(hash)
20
+ hash.keys.each do |key|
21
+ hash[key.to_sym] = hash.delete(key)
22
+ end
23
+ end
24
+
25
+ protected
26
+ # turns {'a' => {'b' => 'c'}} into {'a.b' => 'c'}
27
+ def dot_flattened(nested_hash, names=[], result={})
28
+ nested_hash.each do |key, val|
29
+ next if val == nil
30
+ if val.respond_to?(:has_key?)
31
+ dot_flattened(val, names + [key], result)
32
+ elsif val.is_a?(Array)
33
+ result[(names + [key]).join('/')] = (val.map do |e|
34
+ raise SlashedHashTypeError.new "Not Support #{e.class.name} in array" unless e.respond_to?(:to_s)
35
+ e.to_s
36
+ end).join(',')
37
+ else
38
+ result[(names + [key]).join('/')] = val
39
+ end
40
+ end
41
+ result
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ require 'config_kit/ext/slashed_hash'
2
+
3
+ module ConfigKit
4
+ class IDCData < SlashedHash
5
+ def initialize(name, env, opts)
6
+ @name, @env = name, env
7
+ @api_version = opts.fetch(:api_version, '1.0')
8
+ @kind = opts.fetch(:kind, 'config_kit')
9
+ @bind_data = binding_data
10
+ super(@bind_data)
11
+ end
12
+
13
+ def data
14
+ {
15
+ 'name' => @name,
16
+ 'env' => @env
17
+ }
18
+ end
19
+
20
+ def binding_idc
21
+ { 'idc' => data}
22
+ end
23
+
24
+ def binding_data
25
+ { @kind => binding_idc }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,104 @@
1
+ require 'optparse'
2
+ require 'config_kit/tool'
3
+ module ConfigKit
4
+ class Manager
5
+ class IDCEnvMissing < ConfigKit::Error; end
6
+ def self.bootstrap(app, from, uri_kls, version, opts={})
7
+ opts['uri_kls'] = uri_kls
8
+ opts['version'] = version
9
+ opts['from'] = from
10
+ new(app, opts).bootstrap
11
+ end
12
+
13
+ def self.describe(app, version, opts={})
14
+ opts['version'] = version
15
+ new(app, opts).describe
16
+ end
17
+
18
+ def self.rollback(app, version, opts={})
19
+ opts['version'] = version
20
+ new(app, opts).rollback
21
+ end
22
+
23
+ def self.deploy(app, version, opts={})
24
+ opts['version'] = version
25
+ new(app, opts).deploy
26
+ end
27
+
28
+ def self.init(name, env='int0', opts={})
29
+ app = 'idc'
30
+ opts['name'] = name
31
+ opts['env'] = env
32
+ opts['skip_env_check'] = true
33
+ new(app,opts).init
34
+ end
35
+
36
+ def self.get(app,opts={})
37
+ new(app, opts).get
38
+ end
39
+
40
+ def initialize(app, opts)
41
+ @app = app
42
+ @opts = opts
43
+ @tool = ConfigKit::Tool.new
44
+ unless opts['skip_env_check'] == true
45
+ env = @tool.get_idc_env
46
+ raise ConfigKit::Manager::IDCEnvMissing.new 'IDC environment missing, pls init it first!' if env.nil?
47
+ @opts['env'] = env
48
+ end
49
+
50
+ end
51
+
52
+ def init
53
+ name = @opts.delete('name')
54
+ env = @opts.delete('env')
55
+ @tool.init_txn
56
+ @tool.idc_init_txn(name, env, @opts)
57
+ @tool.perform_txn
58
+ end
59
+
60
+ def describe
61
+ version = @opts['version']
62
+ @tool.describe(@app, version)
63
+ end
64
+
65
+ def bootstrap
66
+ create
67
+ {app: @app, version: @opts['version']}
68
+ end
69
+
70
+ def create(extra=:no_default)
71
+ ConfigKit::Data::Loader.load(@app, @opts['from'], @opts['uri_kls'], @opts['env'], @opts['version']) do |data|
72
+ @tool.init_txn
73
+ data.each_pair do |k,v|
74
+ @tool.bootstrap_txn(v, k)
75
+ version, cs = get_deploy_info(@tool.config_data)
76
+ @tool.deploy_txn(k, version, extra,cs)
77
+ end
78
+ @tool.perform_txn
79
+ end
80
+ end
81
+
82
+ def get_deploy_info(config_data)
83
+ return [] unless config_data.kind_of?(ConfigKit::ConfigData)
84
+ [config_data.data_version, config_data.data_cs]
85
+ end
86
+
87
+ def get
88
+ data = @tool.get(@app)[@app]
89
+ data || {}
90
+ end
91
+
92
+ def deploy
93
+ change_default
94
+ end
95
+
96
+ def rollback
97
+ change_default
98
+ end
99
+
100
+ def change_default
101
+ @tool.deploy_txn(@app, @opts['version'], :default_only)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,168 @@
1
+ require 'config_kit/config_data'
2
+ require 'config_kit/deploy_data'
3
+ require 'config_kit/idc_data'
4
+ require 'config_kit/ext/hash'
5
+ module ConfigKit
6
+ class Tool
7
+ class ConfigMetadataError < ConfigKit::Error; end
8
+ class MissingDefaultVersionError < ConfigKit::Error;end
9
+ attr_reader :kind, :namespace, :api_version, :config_data, :deploy_data, :idc_data
10
+ def initialize(opts={}, api_version=ConfigKit.config.api_version, kind=ConfigKit.config.kind)
11
+ @kind = kind
12
+ @api_version = api_version
13
+ @url = opts.fetch(:url, ConfigKit.config.url)
14
+ @acl_token = opts.fetch(:acl_token, ConfigKit.config.acl_token)
15
+ @client = ConfigKit::Client.new(@url, @acl_token)
16
+ end
17
+
18
+ def bootstrap_config(data, name)
19
+ ConfigKit::ConfigData.new(data, name, api_version, kind)
20
+ end
21
+
22
+ def bootstrap_txn(data,name)
23
+ @config_data = bootstrap_config(data, name)
24
+ @client.create_txn(@config_data)
25
+ end
26
+
27
+ def deploy_config(name, version,extra,cs)
28
+ ConfigKit::DeployData.new(name, version, extra: extra, cs: cs)
29
+ end
30
+
31
+ def deploy_txn(app,version,extra,cs=nil)
32
+ if extra != :no_default && check_version_for?(app,version)
33
+ ConfigKit.logger.debug "Missing version(#{version}) for app(#{app}) to set default"
34
+ raise MissingDefaultVersionError.new "Missing default version for app(#{app}), pls set default version first.\n"
35
+ end
36
+ ConfigKit.logger.debug "compose deploy data for app(#{app}:#{version})"
37
+ @deploy_data = deploy_config(app,version,extra, cs)
38
+ @client.create_txn(@deploy_data)
39
+ @client.perform_txn
40
+ @deploy_data
41
+ end
42
+
43
+ def check_version_for?(app, version)
44
+ path = path_for(app, version)
45
+ @client.read(path).nil?
46
+ end
47
+
48
+ def idc_config(name, env, opts)
49
+ ConfigKit::IDCData.new(name, env, opts)
50
+ end
51
+
52
+ def idc_init_txn(name, env, opts={})
53
+ @idc_data = idc_config(name, env, opts)
54
+ @client.create_txn(@idc_data)
55
+ end
56
+
57
+ def get_idc
58
+ data = @client.read(idc_path)
59
+ return {'idc' => 'N/A'} if data.nil?
60
+ data['config_kit']['idc']
61
+ end
62
+
63
+ def get_idc_env
64
+ data = @client.read(idc_path)
65
+ return nil if data.nil?
66
+ data['config_kit']['idc']['env']
67
+ end
68
+
69
+ def describe(app, version)
70
+ @content = {}
71
+ data = @client.read(idc_path)
72
+ return @content if data.nil?
73
+ @content['idc'] = data['config_kit']['idc']
74
+ data = @client.read(deploy_path)
75
+ return @content if data.nil?
76
+ @content['deploy'] = data['config_kit']['deploy']
77
+ unless app == 'idc'
78
+ if version.nil?
79
+ data = @client.read(path_for(app))
80
+ return @content if data.nil?
81
+ @content[app] = data['config_kit'][app]
82
+ else
83
+ data = @client.read(path_for(app))
84
+ return @content if data.nil?
85
+ @content[app] = data['config_kit'][app][version]
86
+ end
87
+ end
88
+ @content
89
+ end
90
+
91
+ def delete_txn(data, name, version=nil)
92
+ config = bootstrap_config(data)
93
+ end
94
+
95
+ def get(app)
96
+ return get_all if app == 'all'
97
+ get_one(app)
98
+ end
99
+
100
+ def get_all()
101
+ config_data = {}
102
+ all_apps.each do |app|
103
+ config_data = config_data.deep_merge(get_one(app))
104
+ end
105
+ config_data
106
+ end
107
+
108
+ def get_one(app)
109
+ context = {}
110
+ app_data = get_app(app)
111
+ context[app] = app_data
112
+ context[app]['idc'] = get_idc
113
+ unless app == 'infra'
114
+ infra_data = get_app('infra')['service']
115
+ context[app]['service'] = infra_data
116
+ end
117
+ context
118
+ end
119
+
120
+ def get_app(app)
121
+ version = version_for(app)
122
+ if version.nil?
123
+ ConfigKit.logger.debug "Missing default version for app(#{app})"
124
+ raise MissingDefaultVersionError.new "Missing default version for app(#{app}), pls set default version first.\n"
125
+ end
126
+ app_path = path_for(app, version)
127
+ pp app_path
128
+ pp @client.read(app_path)
129
+ app_version = @client.read(app_path)[kind]
130
+ app_data = {}
131
+ app_data[app] = app_version[app][version]
132
+ end
133
+
134
+ def all_apps
135
+ apps = @client.read(deploy_path)[kind]['deploy'].keys
136
+ apps
137
+ end
138
+
139
+ def version_for(app)
140
+ @client.read(deploy_app_path(app),false)
141
+ end
142
+
143
+ def init_txn
144
+ @client.init_txn
145
+ end
146
+
147
+ def perform_txn
148
+ @client.perform_txn
149
+ end
150
+
151
+ def deploy_app_path(app)
152
+ "#{deploy_path}/#{app}/default"
153
+ end
154
+
155
+ def deploy_path
156
+ "/#{kind}/deploy"
157
+ end
158
+
159
+ def path_for(app, version=nil)
160
+ return "/#{kind}/#{app}" if version.nil?
161
+ "/#{kind}/#{app}/#{version}"
162
+ end
163
+
164
+ def idc_path
165
+ "/#{kind}/idc"
166
+ end
167
+ end
168
+ end