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.
- checksums.yaml +7 -0
- data/.gitignore +35 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +62 -0
- data/README.md +146 -0
- data/bin/ck +11 -0
- data/cluster/consul.yml +64 -0
- data/config/README.md +134 -0
- data/config/int0/int0.axle.yml +48 -0
- data/config/int0/int0.infra.yml +31 -0
- data/config/stg0/stg0.axle.yml +48 -0
- data/config/stg0/stg0.infra.yml +31 -0
- data/config_kit.gemspec +35 -0
- data/lib/config_kit.rb +20 -0
- data/lib/config_kit/cli/command.rb +77 -0
- data/lib/config_kit/cli/commands/bootstrap.rb +65 -0
- data/lib/config_kit/cli/commands/deploy.rb +48 -0
- data/lib/config_kit/cli/commands/describe.rb +41 -0
- data/lib/config_kit/cli/commands/get.rb +37 -0
- data/lib/config_kit/cli/commands/init.rb +42 -0
- data/lib/config_kit/cli/commands/rollback.rb +40 -0
- data/lib/config_kit/client.rb +203 -0
- data/lib/config_kit/config_data.rb +92 -0
- data/lib/config_kit/configuration.rb +16 -0
- data/lib/config_kit/data/loader.rb +91 -0
- data/lib/config_kit/data/loaders/file_loader.rb +68 -0
- data/lib/config_kit/data/loaders/git_loader.rb +39 -0
- data/lib/config_kit/deploy_data.rb +78 -0
- data/lib/config_kit/error.rb +3 -0
- data/lib/config_kit/ext/hash.rb +6 -0
- data/lib/config_kit/ext/slashed_hash.rb +45 -0
- data/lib/config_kit/idc_data.rb +28 -0
- data/lib/config_kit/manager.rb +104 -0
- data/lib/config_kit/tool.rb +168 -0
- data/lib/config_kit/version.rb +3 -0
- data/scripts/create_config_kit.rb +56 -0
- data/scripts/profile_to_consul.sh +9 -0
- metadata +139 -0
@@ -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,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
|