ovh-provisioner 0.1.0
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 +9 -0
- data/.gitlab-ci.yml +23 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/CHANGELOG +7 -0
- data/CONTRIBUTING.md +62 -0
- data/Gemfile +6 -0
- data/LICENSE +202 -0
- data/README.md +107 -0
- data/Rakefile +24 -0
- data/bin/console +32 -0
- data/bin/ovh_provisioner +27 -0
- data/bin/setup +23 -0
- data/lib/ovh/provisioner.rb +43 -0
- data/lib/ovh/provisioner/api_list.rb +158 -0
- data/lib/ovh/provisioner/api_object/api_object.rb +125 -0
- data/lib/ovh/provisioner/api_object/dedicated_server.rb +225 -0
- data/lib/ovh/provisioner/api_object/domain_zone.rb +115 -0
- data/lib/ovh/provisioner/api_object/ip.rb +83 -0
- data/lib/ovh/provisioner/api_object/record.rb +48 -0
- data/lib/ovh/provisioner/api_object/vrack.rb +92 -0
- data/lib/ovh/provisioner/cli.rb +173 -0
- data/lib/ovh/provisioner/cli_domain.rb +138 -0
- data/lib/ovh/provisioner/cli_ip.rb +64 -0
- data/lib/ovh/provisioner/cli_vrack.rb +71 -0
- data/lib/ovh/provisioner/init.rb +77 -0
- data/lib/ovh/provisioner/self_cli.rb +81 -0
- data/lib/ovh/provisioner/spawner.rb +63 -0
- data/lib/ovh/provisioner/version.rb +24 -0
- data/ovh-provisioner.gemspec +53 -0
- data/spec/config.yml +53 -0
- data/spec/helpers/highline_helper.rb +36 -0
- data/spec/ovh/provisioner/cli_domain_spec.rb +140 -0
- data/spec/ovh/provisioner/cli_ip_spec.rb +90 -0
- data/spec/ovh/provisioner/cli_spec.rb +186 -0
- data/spec/ovh/provisioner/cli_vrack_spec.rb +83 -0
- data/spec/ovh/provisioner/stubs/domain_stubs.rb +204 -0
- data/spec/ovh/provisioner/stubs/ip_stubs.rb +152 -0
- data/spec/ovh/provisioner/stubs/server_stubs.rb +146 -0
- data/spec/ovh/provisioner/stubs/vrack_stubs.rb +87 -0
- data/spec/ovh/provisioner_spec.rb +25 -0
- data/spec/spec_helper.rb +47 -0
- metadata +350 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'thor'
|
20
|
+
require 'pp'
|
21
|
+
require 'ruby-progressbar'
|
22
|
+
require 'highline/import'
|
23
|
+
|
24
|
+
module OVH
|
25
|
+
module Provisioner
|
26
|
+
# Command line for domain (actually domain/zone)
|
27
|
+
class CliDomain < Thor
|
28
|
+
# Exit 1 on failure
|
29
|
+
def self.exit_on_failure?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
# options
|
34
|
+
SUBDOMAIN = [
|
35
|
+
:subdomain,
|
36
|
+
default: '',
|
37
|
+
desc: 'Record subdomain',
|
38
|
+
aliases: ['-d']
|
39
|
+
].freeze
|
40
|
+
|
41
|
+
TYPE = [
|
42
|
+
:type,
|
43
|
+
default: '',
|
44
|
+
desc: 'Record type',
|
45
|
+
aliases: ['-y']
|
46
|
+
].freeze
|
47
|
+
|
48
|
+
TARGET = [
|
49
|
+
:target,
|
50
|
+
default: '',
|
51
|
+
desc: 'Record target',
|
52
|
+
aliases: ['-t']
|
53
|
+
].freeze
|
54
|
+
|
55
|
+
TTL = [
|
56
|
+
:ttl,
|
57
|
+
default: '0',
|
58
|
+
desc: 'Record TTL',
|
59
|
+
aliases: ['-l']
|
60
|
+
].freeze
|
61
|
+
|
62
|
+
desc 'list', 'Print the list of your domains'
|
63
|
+
def list(*targets)
|
64
|
+
spawner = Provisioner.init(options)
|
65
|
+
puts spawner.get('DomainZone', *Cli.all(targets)).format
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'show domain', 'Show records in a domain'
|
69
|
+
def show(domain)
|
70
|
+
spawner = Provisioner.init(options)
|
71
|
+
zones = spawner.get('DomainZone', domain).list
|
72
|
+
zones.each { |z| puts z.details } if check_zone_input(domain, zones)
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'add domain', 'Add a record in a domain'
|
76
|
+
[SUBDOMAIN, TYPE, TARGET, TTL].map { |o| option(*o) }
|
77
|
+
def add(domain)
|
78
|
+
spawner = Provisioner.init(options)
|
79
|
+
zones = spawner.get('DomainZone', domain).list
|
80
|
+
return unless check_zone_input(domain, zones, false)
|
81
|
+
|
82
|
+
zone = zones.first
|
83
|
+
add_record(zone, options)
|
84
|
+
end
|
85
|
+
|
86
|
+
desc 'rm domain', 'Remove records in domain'
|
87
|
+
[SUBDOMAIN, TYPE, TARGET, TTL].map { |o| option(*o) }
|
88
|
+
def rm(domain)
|
89
|
+
spawner = Provisioner.init(options)
|
90
|
+
zones = spawner.get('DomainZone', domain).list
|
91
|
+
return unless check_zone_input(domain, zones, false)
|
92
|
+
|
93
|
+
zone = zones.first
|
94
|
+
matches = zone.filter_records(Provisioner.config)
|
95
|
+
rm_records(zone, matches)
|
96
|
+
end
|
97
|
+
|
98
|
+
no_commands do # rubocop:disable Metrics/BlockLength
|
99
|
+
def check_zone_input(search, zones, allow_many = true)
|
100
|
+
ok = true
|
101
|
+
if zones.empty?
|
102
|
+
puts "No registered services of your account match #{search}"
|
103
|
+
ok = false
|
104
|
+
end
|
105
|
+
if !allow_many && zones.size > 1
|
106
|
+
puts "Need one zone, got many: #{zones.map(&:id)}"
|
107
|
+
ok = false
|
108
|
+
end
|
109
|
+
ok
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_record(zone, options)
|
113
|
+
sub = options['subdomain']
|
114
|
+
type = options['type'].upcase
|
115
|
+
target = options['target']
|
116
|
+
ttl = options['ttl']
|
117
|
+
Cli.ask_validation(
|
118
|
+
"You are going to add a record to #{zone.id}:",
|
119
|
+
" #{APIObject::Record.print(zone, sub, ttl, type, target)}"
|
120
|
+
)
|
121
|
+
puts zone.add_record(sub, type, target, ttl)
|
122
|
+
end
|
123
|
+
|
124
|
+
def rm_records(zone, matches)
|
125
|
+
if matches.list.empty?
|
126
|
+
puts 'Nothing to do…'
|
127
|
+
else
|
128
|
+
Cli.ask_validation(
|
129
|
+
"You are going to remove theses zones from #{zone.id}:",
|
130
|
+
zone.details(matches).lines[1..-1].join('')
|
131
|
+
)
|
132
|
+
puts zone.rm_records(matches)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'thor'
|
20
|
+
require 'pp'
|
21
|
+
require 'ruby-progressbar'
|
22
|
+
require 'highline/import'
|
23
|
+
|
24
|
+
module OVH
|
25
|
+
module Provisioner
|
26
|
+
# The command line runner
|
27
|
+
class CliIP < Thor
|
28
|
+
# Exit 1 on failure
|
29
|
+
def self.exit_on_failure?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'list', 'print the list of your OVH IPs'
|
34
|
+
def list(*targets)
|
35
|
+
spawner = Provisioner.init(options)
|
36
|
+
puts spawner.get('IP', *Cli.all(targets)).format('routed_to', 'kind')
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'set_reverse ip reverse', 'Set the reverse of the IP'
|
40
|
+
def set_reverse(ip, reverse)
|
41
|
+
spawner = Provisioner.init(options)
|
42
|
+
ips = spawner.get('IP', ip).list
|
43
|
+
return unless Cli.check_service_input(ip, ips, false)
|
44
|
+
|
45
|
+
ip = ips.first
|
46
|
+
ask = "You are going to set the reverse of #{ip.id} to #{reverse}"
|
47
|
+
Cli.ask_validation(ask)
|
48
|
+
puts ip.add_reverse(reverse)
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'rm_reverse ip', 'Remove the reverse of the IP'
|
52
|
+
def rm_reverse(ip)
|
53
|
+
spawner = Provisioner.init(options)
|
54
|
+
ips = spawner.get('IP', ip).list
|
55
|
+
return unless Cli.check_service_input(ip, ips, false)
|
56
|
+
|
57
|
+
ip = ips.first
|
58
|
+
ask = "You are going to remove the reverse of #{ip.id}"
|
59
|
+
Cli.ask_validation(ask)
|
60
|
+
puts ip.rm_reverse
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'thor'
|
20
|
+
require 'pp'
|
21
|
+
require 'ruby-progressbar'
|
22
|
+
require 'highline/import'
|
23
|
+
|
24
|
+
module OVH
|
25
|
+
module Provisioner
|
26
|
+
# The command line runner
|
27
|
+
class CliVrack < Thor
|
28
|
+
# Exit 1 on failure
|
29
|
+
def self.exit_on_failure?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'list', 'print the list of your dedicated servers'
|
34
|
+
def list(*targets)
|
35
|
+
spawner = Provisioner.init(options)
|
36
|
+
puts spawner.get('Vrack', *Cli.all(targets)).format
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'add vrack_id targets…', 'Add one/multiple servers in a vrack'
|
40
|
+
def add(vrack_id, *targets)
|
41
|
+
msg = 'You are going to add those servers to vrack'
|
42
|
+
execute_on_vrack(vrack_id, targets, :add, msg)
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'rm vrack_id targets_', 'Remove one/multiple servers from a vrack'
|
46
|
+
def rm(vrack_id, *targets)
|
47
|
+
msg = 'You are going to remove those servers from vrack'
|
48
|
+
execute_on_vrack(vrack_id, targets, :remove, msg)
|
49
|
+
end
|
50
|
+
|
51
|
+
no_commands do
|
52
|
+
def init_vrack(vrack_id, targets)
|
53
|
+
spawner = Provisioner.init(options)
|
54
|
+
servers = spawner.get('DedicatedServer', *Cli.all(targets))
|
55
|
+
vracks = spawner.get('Vrack', vrack_id)
|
56
|
+
[servers, vracks]
|
57
|
+
end
|
58
|
+
|
59
|
+
def execute_on_vrack(vrack_id, targets, method, msg)
|
60
|
+
servers, vracks = init_vrack(vrack_id, targets)
|
61
|
+
return unless vracks.list.size == 1
|
62
|
+
|
63
|
+
vrack = vracks.list.first
|
64
|
+
msg = "#{msg} #{vrack.id}(#{vrack.name}):"
|
65
|
+
Cli.ask_validation(msg, servers.format('vrack'))
|
66
|
+
servers.puts_each(method, [], vrack)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'yaml'
|
20
|
+
require 'ovh/rest'
|
21
|
+
|
22
|
+
module OVH
|
23
|
+
# Load configuration and initialize client
|
24
|
+
module Provisioner
|
25
|
+
class << self
|
26
|
+
attr_reader :config
|
27
|
+
attr_reader :client
|
28
|
+
attr_reader :spawner
|
29
|
+
|
30
|
+
def init(options)
|
31
|
+
@config = load_config(options)
|
32
|
+
@client = create_client
|
33
|
+
@spawner = create_spawner
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_config(options)
|
37
|
+
config_file = options['config_file']
|
38
|
+
begin
|
39
|
+
config = YAML.load_file config_file if File.exist? config_file
|
40
|
+
rescue StandardError => e
|
41
|
+
puts "#{e}\nCould not load configuration file: #{config_file}"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
check_missing((config || {}).merge(options))
|
45
|
+
end
|
46
|
+
|
47
|
+
def check_missing(config)
|
48
|
+
missing = %w[
|
49
|
+
app_key app_secret consumer_key api_url
|
50
|
+
].map { |key| config[key].nil? ? key : nil }.compact
|
51
|
+
|
52
|
+
return config if missing.empty?
|
53
|
+
|
54
|
+
puts "Please provide valid #{missing.join(', ')}"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_client
|
59
|
+
return @client unless @client.nil?
|
60
|
+
|
61
|
+
OVH::REST.new(
|
62
|
+
config['app_key'],
|
63
|
+
config['app_secret'],
|
64
|
+
config['consumer_key'],
|
65
|
+
config['api_url']
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_spawner
|
70
|
+
return @spawner unless @spawner.nil?
|
71
|
+
|
72
|
+
spawner = Spawner.new
|
73
|
+
Celluloid::Actor[Spawner::NAME] = spawner
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'thor'
|
20
|
+
|
21
|
+
module OVH
|
22
|
+
module Provisioner
|
23
|
+
# The command line runner
|
24
|
+
class Cli < Thor
|
25
|
+
# Exit 1 on failure
|
26
|
+
def self.exit_on_failure?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.ask_validation(question, what = nil)
|
31
|
+
say question
|
32
|
+
say what unless what.nil?
|
33
|
+
exit unless HighLine.agree('Do you want to proceed?')
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.check_service_input(search, services, allow_many = true)
|
37
|
+
ok = true
|
38
|
+
if services.empty?
|
39
|
+
puts "No registered services of your account match #{search}"
|
40
|
+
ok = false
|
41
|
+
end
|
42
|
+
if !allow_many && services.size > 1
|
43
|
+
puts "Need one service, got many: #{services.map(&:id)}"
|
44
|
+
ok = false
|
45
|
+
end
|
46
|
+
ok
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.all(targets)
|
50
|
+
targets.empty? ? [''] : targets
|
51
|
+
end
|
52
|
+
|
53
|
+
class_option(
|
54
|
+
:config_file,
|
55
|
+
desc: 'Configuration file to use',
|
56
|
+
default: File.join(Dir.home, '.config', 'ovh-provisioner.yml'),
|
57
|
+
aliases: ['-c']
|
58
|
+
)
|
59
|
+
class_option(
|
60
|
+
:app_key,
|
61
|
+
desc: 'Define/Override the Application Key',
|
62
|
+
aliases: ['-a']
|
63
|
+
)
|
64
|
+
class_option(
|
65
|
+
:app_secret,
|
66
|
+
desc: 'Define/Override the Application Secret',
|
67
|
+
aliases: ['-s']
|
68
|
+
)
|
69
|
+
class_option(
|
70
|
+
:consumer_key,
|
71
|
+
desc: 'Define/Override the Consumer Key',
|
72
|
+
aliases: ['-k']
|
73
|
+
)
|
74
|
+
class_option(
|
75
|
+
:api_url,
|
76
|
+
desc: 'Override the API url',
|
77
|
+
aliases: ['-u']
|
78
|
+
)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'celluloid/current'
|
20
|
+
require 'ovh/provisioner/api_object/api_object'
|
21
|
+
|
22
|
+
module OVH
|
23
|
+
module Provisioner
|
24
|
+
# Is responsible for spawning other cells
|
25
|
+
class Spawner
|
26
|
+
include Celluloid
|
27
|
+
|
28
|
+
NAME = 'Spawner'
|
29
|
+
|
30
|
+
# Return and create on demand a given api_object or api_list
|
31
|
+
#
|
32
|
+
# - class_name is an api_object class name
|
33
|
+
# - args is an array of arguments used to object creation
|
34
|
+
# - parent is the requester (like a vrack asking for its tasks)
|
35
|
+
# - id is the id of the api_object (nil for api_list)
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
# - get('Vrack', id: 'pn-123'): Vrack.new('pn-123')
|
39
|
+
# - get('Task', parent: 'vrack/pn-12', id: '98'):
|
40
|
+
# Task('98', 'vrack/pn-12')
|
41
|
+
# - get('DedicatedServer', '03'): APIList.new(DedicatedServer, '03')
|
42
|
+
def get(class_name, *args, parent: nil, id: nil)
|
43
|
+
cell_name = "#{parent}:#{class_name}@#{id}##{args}"
|
44
|
+
cell = Actor[cell_name.to_sym] ||= create(class_name, parent, id, args)
|
45
|
+
cell.init_properties
|
46
|
+
cell
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def create(class_name, parent, id, args)
|
52
|
+
exclusive do
|
53
|
+
cell_class = APIObject.const_get(class_name.to_sym)
|
54
|
+
if id.nil?
|
55
|
+
APIList.new(cell_class, parent, *args)
|
56
|
+
else
|
57
|
+
cell_class.new(id, parent, *args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|