ovh-provisioner 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|