config_kit 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ module ConfigKit::Cli
2
+ class Get < Command
3
+ def self.command; "get"; end
4
+
5
+ def initialize(args)
6
+ @app = 'all'
7
+ @output = nil
8
+ super(args)
9
+ end
10
+
11
+ def run
12
+ begin
13
+ @output = ConfigKit::Manager.get(@app)
14
+ pp @output
15
+ rescue ConfigKit::Cli::Command::CommandFailure
16
+ raise
17
+ rescue => e
18
+ ConfigKit.logger.error "Unexpected error attempting to get config data #{@uri} in env #{@env} for #{@app.nil? ? 'all' : @app}"
19
+ ConfigKit.logger.debug "#{e}: #{e.backtrace.join("\n ")}"
20
+ raise ConfigKit::Cli::Command::CommandFailure.new(e.to_s)
21
+ end
22
+ end
23
+
24
+ private
25
+ def options
26
+ OptionParser.new %Q{Usage: #{$0} #{self.class.command} [OPTIONS] ["description"] }, 40 do |opts|
27
+ opts.separator ''
28
+ opts.separator 'Specific options:'
29
+
30
+ opts.on('-a APP', '--app', 'Specify an app of config to create(default: all to deploy all apps)') do |app|
31
+ @app = app
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,42 @@
1
+ module ConfigKit::Cli
2
+ class Init < Command
3
+ def self.command; "init"; end
4
+
5
+ def initialize(args)
6
+ @output
7
+ super(args)
8
+ end
9
+
10
+ def run
11
+ begin
12
+ raise ConfigKit::Cli::Command::CommandFailure.new 'Missing name options' if @name.nil?
13
+ raise ConfigKit::Cli::Command::CommandFailure.new 'Missing environment options' if @env.nil?
14
+ @output = ConfigKit::Manager.init(@name, @env)
15
+ pp @output.to_h.to_json
16
+ rescue ConfigKit::Cli::Command::CommandFailure
17
+ raise
18
+ rescue => e
19
+ ConfigKit.logger.error "Unexpected error attempting to get config data #{@uri} in env #{@env} for #{@app.nil? ? 'all' : @app}"
20
+ ConfigKit.logger.debug "#{e}: #{e.backtrace.join("\n ")}"
21
+ raise ConfigKit::Cli::Command::CommandFailure.new(e.to_s)
22
+ end
23
+ end
24
+
25
+ private
26
+ def options
27
+ OptionParser.new %Q{Usage: #{$0} #{self.class.command} [OPTIONS] ["description"] }, 40 do |opts|
28
+ opts.separator ''
29
+ opts.separator 'Specific options:'
30
+
31
+ opts.on('-n NAME', '--name', 'Specify a name of IDC to config') do |name|
32
+ @name = name
33
+ end
34
+
35
+ opts.on('-e ENV', '--env', 'Specify a environment of IDC to config') do |env|
36
+ @env = env
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,40 @@
1
+ module ConfigKit::Cli
2
+ class Rollback < Command
3
+ def self.command; "rollback"; end
4
+
5
+ def initialize(args)
6
+ @app = nil
7
+ super(args)
8
+ end
9
+
10
+ def run
11
+ begin
12
+ @output = ConfigKit::Manager.rollback(@app, @version)
13
+ pp @output.to_h.to_json
14
+ rescue ConfigKit::Cli::Command::CommandFailure
15
+ raise
16
+ rescue => e
17
+ ConfigKit.logger.error "Unexpected error attempting to get config data #{@uri} in env #{@env} for #{@app.nil? ? 'all' : @app}"
18
+ ConfigKit.logger.debug "#{e}: #{e.backtrace.join("\n ")}"
19
+ raise ConfigKit::Cli::Command::CommandFailure.new(e.to_s)
20
+ end
21
+ end
22
+
23
+ private
24
+ def options
25
+ OptionParser.new %Q{Usage: #{$0} #{self.class.command} [OPTIONS] ["description"] }, 40 do |opts|
26
+ opts.separator ''
27
+ opts.separator 'Specific options:'
28
+
29
+ opts.on('-v VERSION', '--version', 'Specify a existing version to rollout') do |version|
30
+ @version = version[0] == 'v' ? version : "v#{version}"
31
+ end
32
+
33
+ opts.on('-a APP', '--app', 'Specify an app of config to create(default: all to deploy all apps)') do |app|
34
+ @app = app
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,203 @@
1
+ require 'diplomat'
2
+ require 'config_kit/error'
3
+ module ConfigKit
4
+ class Client
5
+ class ConfigKitCreateError < ConfigKit::Error; end
6
+ class ConfigKitUpdateError < ConfigKit::Error; end
7
+ class ConfigKitTxnError < ConfigKit::Error; end
8
+ class ConfigKitReadError < ConfigKit::Error; end
9
+ attr_reader :url, :acl_token, :opts
10
+
11
+ class ConsulConnection
12
+ attr_reader :config, :connection
13
+
14
+ extend Forwardable
15
+ def_delegators :@connection, :put, :get, :delete, :txn
16
+ def initialize(url, acl_token)
17
+ setup_consul(url, acl_token)
18
+ end
19
+
20
+ def setup_consul(url, acl_token)
21
+ @config = Diplomat.configure do |config|
22
+ config.url = url
23
+ config.acl_token = acl_token unless acl_token.nil?
24
+ end
25
+ @connection = Diplomat
26
+ end
27
+
28
+ def put!(key, value, options = nil)
29
+ response = put(key, value, options = nil)
30
+ raise ConfigKitUpdateError, "Config Kit Update key:#{key} error" unless response
31
+ response
32
+ end
33
+
34
+ def create_txn(data)
35
+ prepare_txn_data(data,type='create')
36
+ self
37
+ end
38
+
39
+ def read_txn(data,recurse=false)
40
+ prepare_txn_data(data,type='read',recurse)
41
+ self
42
+ end
43
+
44
+ def update_txn(data)
45
+ prepare_txn_data(data,type='read')
46
+ self
47
+ end
48
+
49
+ def delete_txn(data,recurse=false)
50
+ prepare_txn_data(data,type='delete',recurse)
51
+ self
52
+ end
53
+
54
+ def init_txn
55
+ reset_txn_data
56
+ end
57
+
58
+ def perform_txn
59
+ begin
60
+ txn(@txn_data)
61
+ rescue => e
62
+ raise ConfigKitTxnError.new "perform txn error:#{e.message}"
63
+ ensure
64
+ reset_txn_data
65
+ end
66
+ end
67
+
68
+ def reset_txn_data
69
+ @txn_data = []
70
+ end
71
+
72
+ def prepare_txn_data(data, type='read', recurse=false)
73
+ init_txn if @txn_data.nil?
74
+ verb = determine_verb(type, recurse)
75
+ data.each_pair do |k,v|
76
+ kv = {
77
+ "Verb" => verb,
78
+ "Key" => k.to_s,
79
+ "Value" => v.to_s
80
+ }
81
+
82
+ @txn_data << {"KV" => kv}
83
+ end
84
+ @txn_data
85
+ end
86
+
87
+ def determine_verb(_type, recurse)
88
+ verb = nil
89
+ type = _type.to_s
90
+ case type
91
+ when 'read'
92
+ if recurse
93
+ verb = 'get-tree'
94
+ else
95
+ verb = 'get'
96
+ end
97
+ when 'update'
98
+ verb = 'set'
99
+ when 'create'
100
+ verb = 'set'
101
+ when 'delete'
102
+ if recurse
103
+ verb = 'delete-tree'
104
+ else
105
+ verb = 'delete'
106
+ end
107
+ else
108
+ verb = nil
109
+ end
110
+ verb
111
+ end
112
+ end
113
+
114
+ def initialize(url, acl_token)
115
+ @url = url
116
+ @acl_token = acl_token
117
+ @connection = ConsulConnection.new(@url, @acl_token)
118
+ end
119
+
120
+ def create(key,value)
121
+ response = @connection.put(key, value, cas: 0)
122
+ raise ConfigKitCreateError, "Config Kit create #{key} error" unless response
123
+ response
124
+ end
125
+
126
+ def update(key, value)
127
+ response = @connection.put(key, value)
128
+ raise ConfigKitUpdateError, "Config Kit update #{key} error" unless response
129
+ end
130
+
131
+ def atom_update(key,value)
132
+ begin
133
+ retries ||= 0
134
+ modify_idx = @connection.get(key, modify_index: true)
135
+ response = @connection.put!(key, value, cas: modify_idx)
136
+ rescue ConfigKitUpdateError => e
137
+ if (retries += 1) < 3
138
+ #
139
+ # TODO:
140
+ # 1. Need to log
141
+ # 2. Need to delay and retry refactor
142
+ #
143
+ sleep(0.5)
144
+ retry
145
+ end
146
+ end
147
+ end
148
+
149
+ #
150
+ # TODO: enhancement
151
+ #
152
+ def read(key, convert_to_hash=true, recurse=true)
153
+ begin
154
+ ConfigKit.logger.debug "getting key: #{key}"
155
+ response = @connection.get(key, convert_to_hash: convert_to_hash, recurse: recurse)
156
+ response
157
+ rescue Diplomat::KeyNotFound => e
158
+ return nil
159
+ rescue Faraday::ConnectionFailed => e
160
+ raise ConfigKitReadError, "config server #{@url} is not avaliable. #{e.message}"
161
+ rescue Diplomat::UnknownStatus => e
162
+ raise ConfigKitReadError, "Unknown error #{e.message}."
163
+ rescue Exception => e
164
+ ConfigKit.logger.debug e.backtrace.join("\n ")
165
+ return nil
166
+ end
167
+
168
+ end
169
+
170
+ #
171
+ # TODO: enhancement
172
+ #
173
+
174
+ def delete(key)
175
+ response = @connection.delete(url, recurse: true)
176
+ response
177
+ end
178
+
179
+ def create_txn(data)
180
+ @connection.create_txn(data)
181
+ end
182
+
183
+ def read_txn(data)
184
+ @connection.read_txn(data)
185
+ end
186
+
187
+ def update_txn(data)
188
+ @connection.update_txn(data)
189
+ end
190
+
191
+ def delete_txn(data)
192
+ @connection.delete_txn(data)
193
+ end
194
+
195
+ def init_txn
196
+ @connection.init_txn
197
+ end
198
+
199
+ def perform_txn
200
+ @connection.perform_txn
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,92 @@
1
+ require 'config_kit/ext/slashed_hash'
2
+
3
+ module ConfigKit
4
+ class ConfigData < SlashedHash
5
+ class MetadataError < ConfigKit::Error; end
6
+ class MetadataMissingRequireFieldError < ConfigKit::Error; end
7
+
8
+ class DataError < ConfigKit::Error; end
9
+ class DataMissingRequireFieldError < ConfigKit::Error; end
10
+
11
+ def initialize(config_data, name, api_version='v1.0', kind='config_kit')
12
+
13
+ @defined_api_version=api_version
14
+ @defined_kind = kind
15
+
16
+ @metadata_required_fields = ['api_version', 'kind']
17
+ @data_required_fields = ['version', 'namespace']
18
+ @config_data = config_data
19
+ @name = name
20
+ check_metadata
21
+ check_data
22
+ @binded_data = binding_data
23
+ super(@binded_data)
24
+ end
25
+
26
+ def check_data
27
+ raise DataError, 'Config Kit Support Hash Data Formate ONLY.' unless data.is_a?(Hash)
28
+ @data_required_fields.each do |f|
29
+ raise DataMissingRequireFieldError,"Config Kit Raw data missing require field #{f} " unless data.has_key?(f)
30
+ end
31
+ end
32
+
33
+ def check_metadata
34
+ raise MetadataError, 'Config Kit Support Hash Data Formate ONLY.' unless @config_data.is_a?(Hash)
35
+ @metadata_required_fields.each do |f|
36
+ raise MetadataMissingRequireFieldError,"Config Kit Meta data missing require field #{f} " unless @config_data.has_key?(f)
37
+ end
38
+ raise MetadataError, "Config Kit API Version metadata mis-match #{@config_data['api_version']}." unless support_api?
39
+ raise MetadataError, "Config Kit Kind metadate mis-match #{@config_data['kind']}." unless same_kind?
40
+ end
41
+
42
+ def binding_version
43
+ { data_version => data }
44
+ end
45
+
46
+ def binding_name
47
+ { @name => binding_version }
48
+ end
49
+
50
+ def binding_kind
51
+ { kind => binding_name }
52
+ end
53
+
54
+ #
55
+ # /v1/config_kit/axle/v1.0/database => mysql
56
+ #
57
+
58
+ def binding_data
59
+ binding_kind
60
+ end
61
+
62
+ def same_kind?
63
+ @defined_kind == kind
64
+ end
65
+
66
+ def support_api?
67
+ @defined_api_version == api_version
68
+ end
69
+
70
+ def data_version
71
+ _version = @config_data && @config_data[@name] && @config_data[@name]['version']
72
+ version = _version[0] == 'v' ? _version : "v#{_version}"
73
+ version
74
+ end
75
+
76
+ def data_cs
77
+ nil
78
+ end
79
+
80
+ def data
81
+ @config_data && @config_data[@name]
82
+ end
83
+
84
+ def api_version
85
+ @config_data['api_version']
86
+ end
87
+
88
+ def kind
89
+ @config_data['kind']
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,16 @@
1
+ require 'logger'
2
+ module ConfigKit
3
+ class Configuration
4
+ attr_accessor :url, :acl_token, :options, :api_version, :kind, :debug, :logger
5
+ def initialize(url, opts={})
6
+ @url = url
7
+ @opts = opts
8
+ @acl_token = opts[:acl_token]
9
+ @api_version = 'v1.0'
10
+ @kind = 'config_kit'
11
+ @logger = @opts.fetch(:logger, ::Logger.new(STDOUT))
12
+ @debug = @opts[:debug] || false
13
+ @logger.level = ::Logger::DEBUG if @debug
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,91 @@
1
+ require 'uri'
2
+ module ConfigKit
3
+ module Data
4
+ class Loader
5
+
6
+ class LoaderFailure < StandardError
7
+ attr_reader :options
8
+ def initialize message, opts=nil
9
+ super message
10
+ @options = opts
11
+ end
12
+ end
13
+
14
+ def self.info(message)
15
+ STDOUT.puts message
16
+ end
17
+
18
+ def self.err(message)
19
+ STDERR.puts message
20
+ end
21
+
22
+
23
+ @loaders = []
24
+
25
+ def self.inherited(subclass)
26
+ @loaders << subclass
27
+ end
28
+
29
+ ldrs = ldrs = File.expand_path(File.join(File.dirname(__FILE__), 'loaders', '*.rb'))
30
+ Dir[ldrs].each {|loader| require loader}
31
+
32
+ def self.load(app, from, uri_kls, env, version, &block)
33
+ @loader_names = @loaders.map{ |l| l.loader}
34
+
35
+ @uri_kls, @env, @app, @version = uri_kls, env, app, version
36
+
37
+ loader = from
38
+
39
+
40
+ if loader.nil?
41
+ ConfigKit.logger.error "from empty source with #{@uri_kls.to_s}"
42
+ elsif !@loader_names.include?(loader)
43
+ ConfigKit.logger.error "Unrecognize loader: #{loader} for #{@uri}"
44
+ else
45
+ loader_class = @loaders.find{ |c| c.loader == loader}
46
+ ConfigKit.logger.debug "#{loader_class.loader} is loading env(#{@env}) app(#{app}) data(#{@version}) from #{uri_kls.to_s}"
47
+ loader_class.new(@uri_kls, @env, @app, @version).run(&block)
48
+ ConfigKit.logger.debug "#{loader_class.loader} is loaded env(#{@env}) app(#{app}) data(#{@version}) from #{uri_kls.to_s}"
49
+ end
50
+ rescue => e
51
+ raise ConfigKit::Data::Loader::LoaderFailure.new e.message
52
+ end
53
+
54
+ attr_reader :cursor,:batch_size, :files
55
+ def initialize(batch_size=10)
56
+ @files = []
57
+ @batch_size = batch_size
58
+ @current_files = []
59
+ @cursor = 0
60
+ @files = retrieve_files
61
+ end
62
+
63
+ def file_count
64
+ @files.count
65
+ end
66
+
67
+ def next_batch
68
+ if finish?
69
+ @current_files = []
70
+ @cursor = file_count
71
+ return @cursor
72
+ end
73
+ _next_cursor = next_cursor
74
+ @current_files = @files[@cursor.._next_cursor - 1]
75
+ @cursor = _next_cursor
76
+ end
77
+
78
+ def next_cursor
79
+ @cursor + @batch_size > file_count ? file_count : (@cursor + @batch_size)
80
+ end
81
+
82
+ def finish?
83
+ @cursor >= file_count
84
+ end
85
+
86
+ def retrieve_files
87
+ raise NotImplementedError
88
+ end
89
+ end
90
+ end
91
+ end