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
data/bin/console
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
#
|
6
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'bundler/setup'
|
22
|
+
require 'ovh/provisioner'
|
23
|
+
|
24
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
25
|
+
# with your gem easier. You can also use a different console, if you like.
|
26
|
+
|
27
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
28
|
+
# require "pry"
|
29
|
+
# Pry.start
|
30
|
+
|
31
|
+
require 'irb'
|
32
|
+
IRB.start
|
data/bin/ovh_provisioner
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
#
|
6
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
|
21
|
+
# Trap interrupts to quit cleanly. See
|
22
|
+
# https://twitter.com/mitchellh/status/283014103189053442
|
23
|
+
Signal.trap('INT') { exit 1 }
|
24
|
+
|
25
|
+
require 'bundler/setup'
|
26
|
+
require 'ovh/provisioner'
|
27
|
+
OVH::Provisioner.start
|
data/bin/setup
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#
|
3
|
+
# Copyright (c) 2015-2016 Sam4Mobile, 2017-2018 Make.org
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
set -euo pipefail
|
19
|
+
IFS=$'\n\t'
|
20
|
+
|
21
|
+
bundle install
|
22
|
+
|
23
|
+
# Do any other automated setup that you need to do here
|
@@ -0,0 +1,43 @@
|
|
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 'logger'
|
20
|
+
|
21
|
+
module OVH
|
22
|
+
# Main module
|
23
|
+
module Provisioner
|
24
|
+
class << self
|
25
|
+
attr_accessor :logger
|
26
|
+
|
27
|
+
Dir[File.join(File.dirname(__FILE__), '*', '*.rb')].each do |file|
|
28
|
+
require file
|
29
|
+
end
|
30
|
+
|
31
|
+
def start
|
32
|
+
OVH::Provisioner::Cli.start(ARGV)
|
33
|
+
rescue StandardError => error
|
34
|
+
puts error.message
|
35
|
+
puts error.backtrace
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Initialize the base logger
|
41
|
+
OVH::Provisioner.logger = Logger.new(STDOUT)
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,158 @@
|
|
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 'yaml'
|
21
|
+
|
22
|
+
# rubocop:disable Metrics/ClassLength
|
23
|
+
module OVH
|
24
|
+
module Provisioner
|
25
|
+
# Represent an API list, that is a collection of a given kind of object
|
26
|
+
class APIList
|
27
|
+
include Celluloid
|
28
|
+
attr_reader :list
|
29
|
+
|
30
|
+
# targets is a list of regex on object id
|
31
|
+
# to match nothing, do not set any target
|
32
|
+
# to match everything, add a '' target
|
33
|
+
def initialize(api_object, parent, *targets)
|
34
|
+
@parent = parent
|
35
|
+
@url = api_object.entrypoint(parent).to_s
|
36
|
+
@name = api_object.name.split('::').last
|
37
|
+
@filter = generate_filter(targets)
|
38
|
+
@futures = {}
|
39
|
+
@list = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def init_properties
|
43
|
+
write = !@completed
|
44
|
+
unless @completed
|
45
|
+
# In ATOM concurrency, this is enough to avoid a race condition.
|
46
|
+
@completed = true
|
47
|
+
spawner = Actor[Spawner::NAME]
|
48
|
+
get.keep_if { |s| @filter.call(s) }.each do |i|
|
49
|
+
@futures[i] = spawner.future.get(@name, parent: @parent, id: i)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
wait(write)
|
53
|
+
end
|
54
|
+
|
55
|
+
def format(*fields)
|
56
|
+
items = classify(list, *fields)
|
57
|
+
items = deep_sort(items)
|
58
|
+
yamlize(items)
|
59
|
+
end
|
60
|
+
|
61
|
+
def parallel_map(method, args = [], subject = nil)
|
62
|
+
futures =
|
63
|
+
if subject.nil?
|
64
|
+
list.map { |item| item.future.send(method, *args) }
|
65
|
+
else
|
66
|
+
list.map { |item| subject.future.send(method, item, *args) }
|
67
|
+
end
|
68
|
+
futures.map(&:value)
|
69
|
+
end
|
70
|
+
|
71
|
+
def puts_each(method, args = [], subject = nil, remove_duplicate = true)
|
72
|
+
results = parallel_map(method, args, subject)
|
73
|
+
results = results.map(&:lines).flatten.compact.uniq if remove_duplicate
|
74
|
+
results.each { |i| puts i unless i.nil? }
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def get(path = '')
|
80
|
+
OVH::Provisioner.client.get("#{@url}/#{path}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def wait(write = false)
|
84
|
+
@futures.each_pair do |key, future|
|
85
|
+
v = future.value
|
86
|
+
@list << v if write
|
87
|
+
@futures.delete(key)
|
88
|
+
end
|
89
|
+
@list.sort! if write
|
90
|
+
end
|
91
|
+
|
92
|
+
def generate_filter(targets)
|
93
|
+
lambda do |id|
|
94
|
+
targets.reduce(false) do |acc, target|
|
95
|
+
acc | (id.to_s =~ /#{target.to_s}/)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def classify(items, *fields)
|
101
|
+
items.each_with_object({}) do |item, hash|
|
102
|
+
item = organize(item, *fields)
|
103
|
+
insert!(hash, @url[1..-1].tr('/', '_') => item)
|
104
|
+
hash
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def organize(item, *fields)
|
109
|
+
head, *tail = *fields
|
110
|
+
return [item.to_s] if head.nil?
|
111
|
+
|
112
|
+
key = item.send(head.to_sym)
|
113
|
+
{ key => organize(item, *tail) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def insert!(hash, item)
|
117
|
+
if hash.is_a?(Array)
|
118
|
+
hash << item.first
|
119
|
+
else
|
120
|
+
key = item.keys.first
|
121
|
+
if hash[key].nil?
|
122
|
+
hash.merge!(item)
|
123
|
+
else
|
124
|
+
insert!(hash[key], item[key])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def deep_sort(hash)
|
130
|
+
return hash.sort if hash.is_a?(Array)
|
131
|
+
return hash unless hash.is_a?(Hash)
|
132
|
+
|
133
|
+
result = {}
|
134
|
+
hash.each { |k, v| result[k] = deep_sort(v) }
|
135
|
+
result.sort.to_h
|
136
|
+
end
|
137
|
+
|
138
|
+
def yamlize(items)
|
139
|
+
token = 'TO_REMOVE_AFTER_TOYAML'
|
140
|
+
yaml = prep_for_yaml(items, token).to_yaml
|
141
|
+
yaml.lines[1..-1].grep_v(/\- \|\-/).grep_v(/#{token}/).join('')
|
142
|
+
end
|
143
|
+
|
144
|
+
def prep_for_yaml(obj, token)
|
145
|
+
if obj.is_a?(Hash)
|
146
|
+
obj.map { |k, v| [k, prep_for_yaml(v, token)] }.to_h
|
147
|
+
elsif obj.is_a?(Array)
|
148
|
+
obj.map { |e| prep_for_yaml(e, token) }
|
149
|
+
elsif obj.is_a?(String)
|
150
|
+
"#{obj}\n#{token}"
|
151
|
+
else
|
152
|
+
obj
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -0,0 +1,125 @@
|
|
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
|
+
|
21
|
+
module OVH
|
22
|
+
module Provisioner
|
23
|
+
module APIObject
|
24
|
+
# Base API Object
|
25
|
+
class APIObject
|
26
|
+
include Celluloid
|
27
|
+
include Comparable
|
28
|
+
|
29
|
+
# Define @attributes to contain all attr_readers
|
30
|
+
def self.attr_reader(*vars)
|
31
|
+
@attributes ||= superclass.attributes.dup unless superclass == Object
|
32
|
+
@attributes ||= []
|
33
|
+
@attributes.concat vars
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :id
|
38
|
+
|
39
|
+
def self.attributes # rubocop:disable Style/TrivialAccessors
|
40
|
+
@attributes
|
41
|
+
end
|
42
|
+
|
43
|
+
def attributes
|
44
|
+
self.class.attributes
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(id, parent = nil)
|
48
|
+
@id = id
|
49
|
+
@url = "#{self.class.entrypoint(parent)}/#{normalize(id)}"
|
50
|
+
@futures = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def init_properties
|
54
|
+
unless @completed
|
55
|
+
# In ATOM concurrency, this is enough to avoid a race condition.
|
56
|
+
@completed = true
|
57
|
+
infos = private_methods(false).grep(/^set_/)
|
58
|
+
infos.each { |i| @futures[i] = future.send(i) }
|
59
|
+
end
|
60
|
+
@futures.each_pair do |key, future|
|
61
|
+
future.value
|
62
|
+
@futures.delete(key)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
"#{self.class.entrypoint}: #{id}\n" \
|
68
|
+
"#{attributes.map { |s| "- #{send(s)}" }.join("\n")}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.entrypoint(parent = nil)
|
72
|
+
classpath = underscore(classname).tr('_', '/')
|
73
|
+
parent.nil? ? "/#{classpath}" : "#{parent}/#{classpath}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.classname
|
77
|
+
name.split('::').last
|
78
|
+
end
|
79
|
+
|
80
|
+
# inspired from Rails' ActiveSupport
|
81
|
+
def self.underscore(string)
|
82
|
+
res = string.gsub(/::/, '/')
|
83
|
+
res.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
84
|
+
res.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
85
|
+
res.tr('-', '_').downcase
|
86
|
+
end
|
87
|
+
|
88
|
+
def <=>(other)
|
89
|
+
id <=> other.id
|
90
|
+
end
|
91
|
+
|
92
|
+
def config
|
93
|
+
OVH::Provisioner.config
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(path = '')
|
97
|
+
call(:get, ["#{@url}/#{path}"])
|
98
|
+
end
|
99
|
+
|
100
|
+
def post(path, body = nil)
|
101
|
+
call(:post, ["#{@url}/#{path}", body])
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete(path = '')
|
105
|
+
call(:delete, ["#{@url}/#{path}"])
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def call(method, args)
|
111
|
+
OVH::Provisioner.client.send(method, *args)
|
112
|
+
rescue OVH::RESTError => error
|
113
|
+
error.to_s.split(':')[-1].strip
|
114
|
+
end
|
115
|
+
|
116
|
+
def normalize(id)
|
117
|
+
id = id.gsub('/', '%2F') if id.is_a? String
|
118
|
+
id
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
Dir[File.dirname(__FILE__) + '/*.rb'].each { |file| require file }
|
@@ -0,0 +1,225 @@
|
|
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 'hashdiff'
|
20
|
+
|
21
|
+
module OVH
|
22
|
+
module Provisioner
|
23
|
+
module APIObject
|
24
|
+
# Represent a dedicated server
|
25
|
+
class DedicatedServer < APIObject # rubocop:disable Metrics/ClassLength
|
26
|
+
# install_status const
|
27
|
+
IDLE = 'Server is not being installed or reinstalled at the moment'
|
28
|
+
NO_OS = 'none_64'
|
29
|
+
|
30
|
+
attr_reader :server_id, :reverse, :ip, :dc, :location, :os, :boot
|
31
|
+
attr_reader :state
|
32
|
+
attr_reader :flavor, :flavor_tag
|
33
|
+
attr_reader :vrack, :vrack_id
|
34
|
+
attr_reader :install_status
|
35
|
+
|
36
|
+
def install
|
37
|
+
raise 'Please provide a valid template' if config['template'].nil?
|
38
|
+
|
39
|
+
ok, reason = can_install
|
40
|
+
if ok
|
41
|
+
result = post('install/start', install_body)
|
42
|
+
if result.is_a?(String) # Meaning an error message
|
43
|
+
reason = result
|
44
|
+
ok = false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
"#{id}: #{ok ? 'ok' : 'failed'}\n #{reason}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"#{reverse}[#{id}/#{server_id}]" \
|
52
|
+
"\n #{location} - #{flavor} - #{vrack}:#{ip} " \
|
53
|
+
"- #{boot}:#{os} - #{state}" \
|
54
|
+
"\n #{install_status}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def newname
|
58
|
+
name_scheme = Provisioner.config['name_scheme']
|
59
|
+
return reverse if name_scheme.nil?
|
60
|
+
|
61
|
+
subst = attributes.map { |a| { a => send(a) } }.reduce(&:merge)
|
62
|
+
name_scheme % subst
|
63
|
+
end
|
64
|
+
|
65
|
+
def rename(domain, name = newname)
|
66
|
+
full_name = "#{name}.#{domain.id}"
|
67
|
+
return "#{reverse}: no change" if reverse == full_name
|
68
|
+
|
69
|
+
# check if name is already used
|
70
|
+
records = domain.filter_records('subdomain' => newname)
|
71
|
+
return "#{reverse}: name already used" unless records.list.empty?
|
72
|
+
|
73
|
+
# remove old name (only if it belongs to the same domain)
|
74
|
+
remove_old_names(domain)
|
75
|
+
|
76
|
+
domain.add_record(name, 'A', ip)
|
77
|
+
end
|
78
|
+
|
79
|
+
def define_reverse(domain, name = newname)
|
80
|
+
full_name = "#{name}.#{domain.id}"
|
81
|
+
return "#{reverse}: no change" if reverse == full_name
|
82
|
+
|
83
|
+
add_reverse(full_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
def ipmi
|
87
|
+
request_ipmi
|
88
|
+
|
89
|
+
(1..10).each do |_|
|
90
|
+
result = get('features/ipmi/access?type=kvmipJnlp')
|
91
|
+
unless result.is_a?(String)
|
92
|
+
launch_ipmi(result['value'])
|
93
|
+
break
|
94
|
+
end
|
95
|
+
sleep(1)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def can_install
|
102
|
+
if install_status != IDLE
|
103
|
+
[false, "Installation ongoing: #{install_status}"]
|
104
|
+
elsif !config['force'] && os != NO_OS
|
105
|
+
[false,
|
106
|
+
"An OS is already installed (#{os}) but option force is false"]
|
107
|
+
else [true, "Installation of #{config['template']} launched"]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def install_body
|
112
|
+
details = { 'customHostname' => reverse.chomp('.') }
|
113
|
+
%w[customHostname useDistribKernel sshKeyName].each do |opt|
|
114
|
+
value = config[APIObject.underscore(opt)]
|
115
|
+
details.merge!(opt => value) unless value.nil?
|
116
|
+
end
|
117
|
+
{
|
118
|
+
'templateName' => config['template'],
|
119
|
+
'details' => details
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_general
|
124
|
+
general = get
|
125
|
+
@reverse = get_reverse(general)
|
126
|
+
@server_id = general['serverId']
|
127
|
+
@ip = general['ip']
|
128
|
+
@dc = general['datacenter']
|
129
|
+
@location = dc.gsub(/[0-9]*$/, '')
|
130
|
+
@os = general['os']
|
131
|
+
@boot = get_boot(general)
|
132
|
+
@state = general['state']
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_reverse(general)
|
136
|
+
(general['reverse'] || id).chomp('.')
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_boot(general)
|
140
|
+
get("boot/#{general['bootId']}")['bootType']
|
141
|
+
end
|
142
|
+
|
143
|
+
def set_flavor
|
144
|
+
hardware = get('specifications/hardware')
|
145
|
+
flavors = get_flavors_from_config(hardware)
|
146
|
+
raise "Too many flavor possible: #{flavors}" if flavors.size > 1
|
147
|
+
|
148
|
+
@flavor, @flavor_tag =
|
149
|
+
if flavors.first.nil?
|
150
|
+
%w[undefined undef]
|
151
|
+
else
|
152
|
+
[flavors.first, config['flavors'][flavors.first]['tag']]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_flavors_from_config(hardware)
|
157
|
+
(config['flavors'].dup || {}).map do |flavor, desc|
|
158
|
+
diff = HashDiff.diff(hardware, desc['hardware'])
|
159
|
+
signs = diff.map(&:first).uniq - ['-']
|
160
|
+
signs.empty? ? flavor : nil
|
161
|
+
end.compact
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_vrack
|
165
|
+
vracks = get('vrack')
|
166
|
+
raise "Too many vracks for #{server}: #{vracks}" if vracks.size > 1
|
167
|
+
|
168
|
+
if vracks.first.nil?
|
169
|
+
@vrack_id = '-1'
|
170
|
+
@vrack = 'none'
|
171
|
+
else
|
172
|
+
@vrack_id = vracks.first
|
173
|
+
@vrack = Actor[Spawner::NAME].get('Vrack', id: @vrack_id).name
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def set_install_status
|
178
|
+
status = get('install/status')
|
179
|
+
if status.is_a?(String)
|
180
|
+
status = {
|
181
|
+
'progress' => ['status' => 'doing', 'comment' => status]
|
182
|
+
}
|
183
|
+
end
|
184
|
+
@install_status = extract_status(status)
|
185
|
+
end
|
186
|
+
|
187
|
+
def extract_status(status)
|
188
|
+
cur = status['progress'].select { |step| step['status'] == 'doing' }
|
189
|
+
cur << { 'comment' => 'Preparing installation' } if cur.empty?
|
190
|
+
cur.first['comment']
|
191
|
+
end
|
192
|
+
|
193
|
+
def remove_old_names(domain)
|
194
|
+
old_name = reverse.match(/^(.*)\.#{domain.id}$/).to_a.last
|
195
|
+
return if old_name.nil?
|
196
|
+
|
197
|
+
records = domain.filter_records('subdomain' => old_name)
|
198
|
+
records.parallel_map(:rm_record, [], domain)
|
199
|
+
end
|
200
|
+
|
201
|
+
def add_reverse(full_name)
|
202
|
+
ip_obj = Actor[Spawner::NAME].get('IP', id: "#{ip}/32")
|
203
|
+
result = ip_obj.add_reverse(full_name)
|
204
|
+
cant = "Cannot check if #{full_name}. resolves to #{ip}"
|
205
|
+
if result == cant
|
206
|
+
result = "#{reverse}: DNS propagation is not finished, try later_"
|
207
|
+
end
|
208
|
+
result
|
209
|
+
end
|
210
|
+
|
211
|
+
def request_ipmi
|
212
|
+
body = { 'ttl' => '15', 'type' => 'kvmipJnlp' }
|
213
|
+
result = post('features/ipmi/access', body)
|
214
|
+
puts result if result.is_a?(String)
|
215
|
+
end
|
216
|
+
|
217
|
+
def launch_ipmi(jnlp)
|
218
|
+
filename = "/tmp/#{id}.jnlp"
|
219
|
+
File.open(filename, 'w') { |file| file.write(jnlp) }
|
220
|
+
system("javaws #{filename}")
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|