knife-scaleway 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 +20 -0
- data/.rubocop.yml +11 -0
- data/Gemfile +4 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +201 -0
- data/README.md +480 -0
- data/Rakefile +23 -0
- data/knife-scaleway.gemspec +36 -0
- data/lib/chef/knife/scaleway.rb +110 -0
- data/lib/chef/knife/scaleway_account_info.rb +45 -0
- data/lib/chef/knife/scaleway_base.rb +89 -0
- data/lib/chef/knife/scaleway_droplet_create.rb +338 -0
- data/lib/chef/knife/scaleway_droplet_destroy.rb +69 -0
- data/lib/chef/knife/scaleway_droplet_list.rb +54 -0
- data/lib/chef/knife/scaleway_droplet_power.rb +69 -0
- data/lib/chef/knife/scaleway_droplet_powercycle.rb +48 -0
- data/lib/chef/knife/scaleway_droplet_reboot.rb +48 -0
- data/lib/chef/knife/scaleway_droplet_rebuild.rb +61 -0
- data/lib/chef/knife/scaleway_droplet_rename.rb +58 -0
- data/lib/chef/knife/scaleway_droplet_resize.rb +58 -0
- data/lib/chef/knife/scaleway_droplet_snapshot.rb +58 -0
- data/lib/chef/knife/scaleway_image_destroy.rb +42 -0
- data/lib/chef/knife/scaleway_image_list.rb +58 -0
- data/lib/chef/knife/scaleway_ip_list.rb +46 -0
- data/lib/chef/knife/scaleway_region_list.rb +43 -0
- data/lib/chef/knife/scaleway_size_list.rb +41 -0
- data/lib/chef/knife/scaleway_volume_list.rb +48 -0
- data/lib/knife-scaleway.rb +8 -0
- data/lib/knife-scaleway/version.rb +5 -0
- metadata +269 -0
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
|
+
|
11
|
+
require 'rubocop/rake_task'
|
12
|
+
desc 'Run RuboCop on the lib directory'
|
13
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
14
|
+
task.patterns = ['lib/**/*.rb']
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Display LOC stats'
|
18
|
+
task :loc do
|
19
|
+
puts "\n## LOC Stats"
|
20
|
+
sh 'countloc -r lib/chef/knife'
|
21
|
+
end
|
22
|
+
|
23
|
+
task default: :spec
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'knife-scaleway/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'knife-scaleway'
|
8
|
+
gem.version = Knife::Scaleway::VERSION
|
9
|
+
gem.authors = ['Lukas Diener']
|
10
|
+
gem.email = ['lukas.diener@hotmail.com']
|
11
|
+
gem.description = "A plugin for chef's knife to manage instances of Scaleway servers"
|
12
|
+
gem.summary = "A plugin for chef's knife to manage instances of Scaleway servers"
|
13
|
+
gem.homepage = 'https://github.com/LukasSkywalker/knife-scaleway'
|
14
|
+
gem.license = 'Apache 2.0'
|
15
|
+
|
16
|
+
gem.add_dependency 'chef', '>= 10.18'
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec', '~> 3.1'
|
19
|
+
gem.add_development_dependency 'rubocop', '~> 0.27'
|
20
|
+
gem.add_development_dependency 'rake'
|
21
|
+
gem.add_development_dependency 'knife-solo'
|
22
|
+
gem.add_development_dependency 'knife-zero'
|
23
|
+
gem.add_development_dependency 'webmock', '~> 1.20'
|
24
|
+
gem.add_development_dependency 'vcr', '~> 2.9'
|
25
|
+
gem.add_development_dependency 'guard', '~> 2.8'
|
26
|
+
gem.add_development_dependency 'guard-rspec', '~> 4.3'
|
27
|
+
gem.add_development_dependency 'coveralls'
|
28
|
+
gem.add_development_dependency 'countloc'
|
29
|
+
gem.add_development_dependency 'simplecov'
|
30
|
+
gem.add_development_dependency 'simplecov-console'
|
31
|
+
|
32
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
33
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
34
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
35
|
+
gem.require_paths = ['lib']
|
36
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Scaleway
|
5
|
+
class Client
|
6
|
+
attr_accessor :access_key, :token
|
7
|
+
|
8
|
+
def initialize(access_key, token)
|
9
|
+
@host1 = 'https://api.scaleway.com'
|
10
|
+
@host2 = 'https://account.scaleway.com'
|
11
|
+
@access_key = access_key
|
12
|
+
@token = token
|
13
|
+
@instance = self
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.instance
|
17
|
+
#raise StandardError, 'Create client before accessing methods' if @instance.nil?
|
18
|
+
#@instance
|
19
|
+
Scaleway::Client.new(Chef::Config[:knife][:scaleway_access_key], Chef::Config[:knife][:scaleway_token])
|
20
|
+
end
|
21
|
+
|
22
|
+
def request(path, method, payload = nil)
|
23
|
+
host = @host1 if path.index('/servers') || path.index('/images') || path.index('/volumes') || path.index('/ips')
|
24
|
+
host = @host2 if path.index('/organizations')
|
25
|
+
raise StandardError, "Add /#{path.split('/')[1]} to host map" if host.nil?
|
26
|
+
|
27
|
+
headers = {:'X-Auth-Token' => @token, :'Content-Type' => 'application/json'}
|
28
|
+
url = host + path
|
29
|
+
|
30
|
+
options = { url: url, method: method, verify_ssl: false, headers: headers }
|
31
|
+
if method == :post
|
32
|
+
options.merge!(payload: payload)
|
33
|
+
puts payload if ENV['DEBUG']
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
puts "### Req #{method.upcase} #{host + path}" if ENV['DEBUG']
|
38
|
+
JSON.parse(RestClient::Request.execute(options).body, object_class: OpenStruct, array_class: Array)
|
39
|
+
rescue => e
|
40
|
+
data = JSON.parse(e.response, object_class: OpenStruct)
|
41
|
+
puts "#{data.type}: #{data.message}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get(path)
|
46
|
+
request(path, :get)
|
47
|
+
end
|
48
|
+
|
49
|
+
def post(path, data)
|
50
|
+
request(path, :post, data)
|
51
|
+
end
|
52
|
+
|
53
|
+
def put(path, data)
|
54
|
+
request(path, :put, data)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class Organization
|
59
|
+
def self.all
|
60
|
+
Client.instance.get('/organizations')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Ip
|
65
|
+
def self.all
|
66
|
+
Client.instance.get('/ips').ips
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Image
|
71
|
+
def self.all
|
72
|
+
Client.instance.get('/images').images
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.find(query)
|
76
|
+
response = Client.instance.get('/images')
|
77
|
+
response.images.select do |image|
|
78
|
+
image.name.downcase.index(query.downcase)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Server
|
84
|
+
def self.all
|
85
|
+
Client.instance.get('/servers').servers
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.find(id)
|
89
|
+
Client.instance.get("/servers/#{id}").server
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.create(name, image, commercial_type)
|
93
|
+
Client.instance.post('/servers', { name: name, organization: Client.instance.access_key, image: image, commercial_type: commercial_type}.to_json).server
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.actions(id)
|
97
|
+
Client.instance.get("/servers/#{id}/action")
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.action(id, act)
|
101
|
+
Client.instance.post("/servers/#{id}/action", { action: act }.to_json)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Volume
|
106
|
+
def self.all
|
107
|
+
Client.instance.get('/volumes').volumes
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
require 'chef/knife/scaleway_base'
|
14
|
+
|
15
|
+
class Chef
|
16
|
+
class Knife
|
17
|
+
class ScalewayAccountInfo < Knife
|
18
|
+
include Knife::ScalewayBase
|
19
|
+
|
20
|
+
banner 'knife scaleway account info (options)'
|
21
|
+
|
22
|
+
def run
|
23
|
+
$stdout.sync = true
|
24
|
+
|
25
|
+
validate!
|
26
|
+
|
27
|
+
account_info = [
|
28
|
+
ui.color('UUID', :bold),
|
29
|
+
ui.color('Email', :bold),
|
30
|
+
ui.color('Droplet Limit', :bold),
|
31
|
+
ui.color('Email Verified', :bold)
|
32
|
+
]
|
33
|
+
|
34
|
+
account = client.account.info
|
35
|
+
|
36
|
+
account_info << account.uuid.to_s
|
37
|
+
account_info << account.email.to_s
|
38
|
+
account_info << account.server_limit.to_s
|
39
|
+
account_info << account.email_verified.to_s
|
40
|
+
|
41
|
+
puts ui.list(account_info, :uneven_columns_across, 4)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# lots of awesome stoff stolen from opscode/knife-azure ;-)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
class Chef
|
16
|
+
class Knife
|
17
|
+
module ScalewayBase
|
18
|
+
def self.load_deps
|
19
|
+
require_relative 'scaleway'
|
20
|
+
require 'json'
|
21
|
+
require 'chef/mixin/shell_out'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.included(includer)
|
25
|
+
includer.class_eval do
|
26
|
+
category 'scaleway'
|
27
|
+
|
28
|
+
# Lazy load our dependencies. Later calls to `Knife#deps` override
|
29
|
+
# previous ones, so if the including class calls it, it needs to also
|
30
|
+
# call our #load_deps, i.e:
|
31
|
+
#
|
32
|
+
# Include Chef::Knife::ScalewayBase
|
33
|
+
#
|
34
|
+
# deps do
|
35
|
+
# require 'foo'
|
36
|
+
# require 'bar'
|
37
|
+
# Chef::Knife::ScalewayBase.load_deps
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
deps { Chef::Knife::ScalewayBase.load_deps }
|
41
|
+
|
42
|
+
option :scaleway_access_token,
|
43
|
+
short: '-A ACCESS_TOKEN',
|
44
|
+
long: '--scaleway_access_token ACCESS_TOKEN',
|
45
|
+
description: 'Your Scaleway ACCESS_TOKEN',
|
46
|
+
proc: proc { |access_token| Chef::Config[:knife][:scaleway_access_token] = access_token }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def client
|
51
|
+
Scaleway::Client.new(Chef::Config[:knife][:scaleway_access_key], Chef::Config[:knife][:scaleway_token])
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate!(keys = [:scaleway_access_key, :scaleway_token])
|
55
|
+
errors = []
|
56
|
+
|
57
|
+
keys.each do |k|
|
58
|
+
if locate_config_value(k).nil?
|
59
|
+
errors << "You did not provide a valid '#{k}' value. " \
|
60
|
+
"Please set knife[:#{k}] in your knife.rb or pass as an option."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
exit 1 if errors.each { |e| ui.error(e) }.any?
|
65
|
+
end
|
66
|
+
|
67
|
+
def locate_config_value(key)
|
68
|
+
key = key.to_sym
|
69
|
+
config[key] || Chef::Config[:knife][key]
|
70
|
+
end
|
71
|
+
|
72
|
+
def wait_for_status(result, status: 'in-progress', sleep: 3)
|
73
|
+
print "Waiting for state #{status}"
|
74
|
+
result = Scaleway::Server.find(locate_config_value(:id))
|
75
|
+
while result.state != status
|
76
|
+
sleep sleep
|
77
|
+
print('.')
|
78
|
+
|
79
|
+
#if status == 'starting' || status == 'stopping'
|
80
|
+
#break if client.servers.find(id: locate_config_value(:id)).status != 'in-progress'
|
81
|
+
#else
|
82
|
+
break if Scaleway::Server.find(locate_config_value(:id)).state == status
|
83
|
+
#end
|
84
|
+
end
|
85
|
+
ui.info 'OK'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
2
|
+
# you may not use this file except in compliance with the License.
|
3
|
+
# You may obtain a copy of the License at
|
4
|
+
#
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
6
|
+
#
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10
|
+
# See the License for the specific language governing permissions and
|
11
|
+
# limitations under the License.
|
12
|
+
#
|
13
|
+
require 'chef/knife/scaleway_base'
|
14
|
+
|
15
|
+
class Chef
|
16
|
+
class Knife
|
17
|
+
class ScalewayServerCreate < Knife
|
18
|
+
include Knife::ScalewayBase
|
19
|
+
|
20
|
+
deps do
|
21
|
+
require 'socket'
|
22
|
+
require 'chef/knife/bootstrap'
|
23
|
+
Chef::Knife::Bootstrap.load_deps
|
24
|
+
Chef::Knife::ScalewayBase.load_deps
|
25
|
+
# Knife loads subcommands automatically, so we can just check if the
|
26
|
+
# class exists.
|
27
|
+
Chef::Knife::SoloBootstrap.load_deps if defined? Chef::Knife::SoloBootstrap
|
28
|
+
if defined? Chef::Knife::ZeroBootstrap
|
29
|
+
Chef::Knife::ZeroBootstrap.load_deps
|
30
|
+
self.options = Chef::Knife::ZeroBootstrap.options.merge(options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
banner 'knife scaleway server create (options)'
|
35
|
+
|
36
|
+
option :server_name,
|
37
|
+
short: '-N NAME',
|
38
|
+
long: '--server-name NAME',
|
39
|
+
description: 'The server name',
|
40
|
+
proc: proc { |server_name| Chef::Config[:knife][:server_name] = server_name }
|
41
|
+
|
42
|
+
option :image,
|
43
|
+
short: '-I IMAGE',
|
44
|
+
long: '--image IMAGE',
|
45
|
+
description: 'Your Scaleway Image',
|
46
|
+
proc: proc { |image| Chef::Config[:knife][:image] = image }
|
47
|
+
|
48
|
+
option :size,
|
49
|
+
short: '-S SIZE',
|
50
|
+
long: '--size SIZE',
|
51
|
+
description: 'Your Scaleway Size',
|
52
|
+
proc: proc { |size| Chef::Config[:knife][:size] = size }
|
53
|
+
|
54
|
+
option :location,
|
55
|
+
short: '-L REGION',
|
56
|
+
long: '--location REGION',
|
57
|
+
description: 'Scaleway Location (Region)',
|
58
|
+
proc: proc { |location| Chef::Config[:knife][:location] = location }
|
59
|
+
|
60
|
+
option :ssh_key_ids,
|
61
|
+
short: '-K KEYID',
|
62
|
+
long: '--ssh-keys KEY_ID',
|
63
|
+
description: 'Comma spearated list of your SSH key ids',
|
64
|
+
proc: ->(o) { o.split(/[\s,]+/) }
|
65
|
+
|
66
|
+
option :identity_file,
|
67
|
+
short: '-i IDENTITY_FILE',
|
68
|
+
long: '--identity-file IDENTITY_FILE',
|
69
|
+
description: 'The SSH identity file used for authentication',
|
70
|
+
proc: proc { |identity| Chef::Config[:knife][:identity_file] = identity }
|
71
|
+
|
72
|
+
option :bootstrap,
|
73
|
+
short: '-B',
|
74
|
+
long: '--bootstrap',
|
75
|
+
description: 'Do a chef-client bootstrap on the created server (for use with chef-server)'
|
76
|
+
|
77
|
+
option :solo,
|
78
|
+
long: '--[no-]solo',
|
79
|
+
description: 'Do a chef-solo bootstrap on the server using knife-solo',
|
80
|
+
proc: proc { |s| Chef::Config[:knife][:solo] = s }
|
81
|
+
|
82
|
+
option :zero,
|
83
|
+
long: '--[no-]zero',
|
84
|
+
description: 'Do a chef-zero bootstrap on the server using knife-zero',
|
85
|
+
proc: proc { |z| Chef::Config[:knife][:zero] = z }
|
86
|
+
|
87
|
+
option :ssh_user,
|
88
|
+
short: '-x USERNAME',
|
89
|
+
long: '--ssh-user USERNAME',
|
90
|
+
description: 'The ssh username; default is "root"',
|
91
|
+
default: 'root'
|
92
|
+
|
93
|
+
option :distro,
|
94
|
+
short: '-d DISTRO',
|
95
|
+
long: '--distro DISTRO',
|
96
|
+
description: 'Chef-Bootstrap a distro using a template; default is "chef-full"',
|
97
|
+
proc: proc { |d| Chef::Config[:knife][:distro] = d },
|
98
|
+
default: 'chef-full'
|
99
|
+
|
100
|
+
option :run_list,
|
101
|
+
short: '-r RUN_LIST',
|
102
|
+
long: '--run-list RUN_LIST',
|
103
|
+
description: 'Comma separated list of roles/recipes to apply',
|
104
|
+
proc: ->(o) { o.split(/[\s,]+/) },
|
105
|
+
default: []
|
106
|
+
|
107
|
+
option :template_file,
|
108
|
+
long: '--template-file TEMPLATE',
|
109
|
+
description: 'Full path to location of template to use',
|
110
|
+
proc: proc { |t| Chef::Config[:knife][:template_file] = t },
|
111
|
+
default: false
|
112
|
+
|
113
|
+
option :host_key_verify,
|
114
|
+
long: '--[no-]host-key-verify',
|
115
|
+
description: 'Verify host key, enabled by default',
|
116
|
+
default: true
|
117
|
+
|
118
|
+
option :prerelease,
|
119
|
+
long: '--prerelease',
|
120
|
+
description: 'Install the pre-release chef gems'
|
121
|
+
|
122
|
+
option :bootstrap_version,
|
123
|
+
long: '--bootstrap-version VERSION',
|
124
|
+
description: 'The version of Chef to install',
|
125
|
+
proc: proc { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
126
|
+
|
127
|
+
option :environment,
|
128
|
+
short: '-E ENVIRONMENT',
|
129
|
+
long: '--environment ENVIRONMENT',
|
130
|
+
description: 'The name of the chef environment to use',
|
131
|
+
proc: proc { |e| Chef::Config[:knife][:environment] = e },
|
132
|
+
default: '_default'
|
133
|
+
|
134
|
+
option :json_attributes,
|
135
|
+
short: '-j JSON',
|
136
|
+
long: '--json-attributes JSON',
|
137
|
+
description: 'A JSON string to be added to the first run of chef-client',
|
138
|
+
proc: ->(o) { JSON.parse(o) }
|
139
|
+
|
140
|
+
option :private_networking,
|
141
|
+
long: '--private_networking',
|
142
|
+
description: 'Enables private networking if the selected region supports it',
|
143
|
+
default: false
|
144
|
+
|
145
|
+
option :secret_file,
|
146
|
+
long: '--secret-file SECRET_FILE',
|
147
|
+
description: 'A file containing the secret key to use to encrypt data bag item values',
|
148
|
+
proc: proc { |sf| Chef::Config[:knife][:secret_file] = sf }
|
149
|
+
|
150
|
+
option :ssh_port,
|
151
|
+
short: '-p PORT',
|
152
|
+
long: '--ssh-port PORT',
|
153
|
+
description: 'The ssh port',
|
154
|
+
default: '22',
|
155
|
+
proc: proc { |port| Chef::Config[:knife][:ssh_port] = port }
|
156
|
+
|
157
|
+
option :backups,
|
158
|
+
short: '-b',
|
159
|
+
long: '--backups-enabled',
|
160
|
+
description: 'Enables backups for the created server',
|
161
|
+
default: false
|
162
|
+
|
163
|
+
option :ipv6,
|
164
|
+
short: '-6',
|
165
|
+
long: '--ipv6-enabled',
|
166
|
+
description: 'Enables ipv6 for the created server',
|
167
|
+
default: false
|
168
|
+
|
169
|
+
def run
|
170
|
+
$stdout.sync = true
|
171
|
+
|
172
|
+
validate!
|
173
|
+
|
174
|
+
unless locate_config_value(:server_name)
|
175
|
+
ui.error('Server Name cannot be empty: -N <servername>')
|
176
|
+
exit 1
|
177
|
+
end
|
178
|
+
|
179
|
+
unless locate_config_value(:image)
|
180
|
+
ui.error('Image cannot be empty: -I <image>')
|
181
|
+
exit 1
|
182
|
+
end
|
183
|
+
=begin
|
184
|
+
unless locate_config_value(:size)
|
185
|
+
ui.error('Size cannot be empty: -S <size>')
|
186
|
+
exit 1
|
187
|
+
end
|
188
|
+
|
189
|
+
unless locate_config_value(:location)
|
190
|
+
ui.error('Location cannot be empty: -L <region>')
|
191
|
+
exit 1
|
192
|
+
end
|
193
|
+
|
194
|
+
unless locate_config_value(:ssh_key_ids)
|
195
|
+
ui.error('One or more Scaleway SSH key ids missing: -K <KEY1>, <KEY2> ...')
|
196
|
+
exit 1
|
197
|
+
end
|
198
|
+
=end
|
199
|
+
if solo_bootstrap? && !defined?(Chef::Knife::SoloBootstrap)
|
200
|
+
ui.error [
|
201
|
+
'Knife plugin knife-solo was not found.',
|
202
|
+
'Please add the knife-solo gem to your Gemfile or',
|
203
|
+
'install it manually with `gem install knife-solo`.'
|
204
|
+
].join(' ')
|
205
|
+
exit 1
|
206
|
+
end
|
207
|
+
|
208
|
+
if zero_bootstrap? && !defined?(Chef::Knife::ZeroBootstrap)
|
209
|
+
ui.error [
|
210
|
+
'Knife plugin knife-zero was not found.',
|
211
|
+
'Please add the knife-zero gem to your Gemfile or',
|
212
|
+
'install it manually with `gem install knife-zero`.'
|
213
|
+
].join(' ')
|
214
|
+
exit 1
|
215
|
+
end
|
216
|
+
=begin
|
217
|
+
server = DropletKit::Droplet.new(name: locate_config_value(:server_name),
|
218
|
+
size: locate_config_value(:size),
|
219
|
+
image: locate_config_value(:image),
|
220
|
+
region: locate_config_value(:location),
|
221
|
+
ssh_keys: locate_config_value(:ssh_key_ids),
|
222
|
+
private_networking: locate_config_value(:private_networking),
|
223
|
+
backups: locate_config_value(:backups),
|
224
|
+
ipv6: locate_config_value(:ipv6)
|
225
|
+
)
|
226
|
+
=end
|
227
|
+
server = Scaleway::Server.create(locate_config_value(:server_name), locate_config_value(:image), 'VC1S')
|
228
|
+
|
229
|
+
#server = client.servers.create(server)
|
230
|
+
|
231
|
+
if Scaleway::Server.find(server.id).state != 'stopped'
|
232
|
+
ui.error("Droplet could not be started #{server.inspect}")
|
233
|
+
exit 1
|
234
|
+
end
|
235
|
+
|
236
|
+
puts "Droplet creation for #{locate_config_value(:server_name)} started. Droplet-ID is #{server.id}"
|
237
|
+
|
238
|
+
unless !config.key?(:json_attributes) || config[:json_attributes].empty?
|
239
|
+
puts ui.color("JSON Attributes: #{config[:json_attributes]}", :magenta)
|
240
|
+
end
|
241
|
+
|
242
|
+
puts "Starting server #{server.id}"
|
243
|
+
|
244
|
+
Scaleway::Server.action(server.id, 'poweron')
|
245
|
+
|
246
|
+
print ui.color('Waiting for IPv4-Address', :magenta)
|
247
|
+
print('.') until ip_address = ip_address_available(server.id) do
|
248
|
+
puts 'done'
|
249
|
+
end
|
250
|
+
|
251
|
+
puts ui.color("IPv4 address is: #{ip_address.address}", :green)
|
252
|
+
|
253
|
+
print ui.color('Waiting for sshd:', :magenta)
|
254
|
+
print('.') until tcp_test_ssh(ip_address.address) do
|
255
|
+
sleep 2
|
256
|
+
puts 'done'
|
257
|
+
end
|
258
|
+
|
259
|
+
if locate_config_value(:bootstrap) || solo_bootstrap? || zero_bootstrap?
|
260
|
+
bootstrap_for_node(ip_address.address).run
|
261
|
+
else
|
262
|
+
puts ip_address.address
|
263
|
+
exit 0
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def ip_address_available(server_id)
|
268
|
+
server = Scaleway::Server.find(server_id)
|
269
|
+
if server.public_ip
|
270
|
+
yield
|
271
|
+
server.public_ip
|
272
|
+
else
|
273
|
+
sleep @initial_sleep_delay ||= 10
|
274
|
+
false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def tcp_test_ssh(hostname)
|
279
|
+
port = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
|
280
|
+
tcp_socket = TCPSocket.new(hostname, port)
|
281
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
282
|
+
if readable
|
283
|
+
Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
|
284
|
+
yield
|
285
|
+
true
|
286
|
+
else
|
287
|
+
false
|
288
|
+
end
|
289
|
+
rescue Errno::ETIMEDOUT
|
290
|
+
false
|
291
|
+
rescue Errno::EPERM
|
292
|
+
false
|
293
|
+
rescue Errno::ECONNREFUSED
|
294
|
+
sleep 2
|
295
|
+
false
|
296
|
+
rescue Errno::EHOSTUNREACH
|
297
|
+
sleep 2
|
298
|
+
false
|
299
|
+
ensure
|
300
|
+
tcp_socket && tcp_socket.close
|
301
|
+
end
|
302
|
+
|
303
|
+
def bootstrap_for_node(ip_address)
|
304
|
+
bootstrap = bootstrap_class.new
|
305
|
+
bootstrap.name_args = [ip_address]
|
306
|
+
bootstrap.config.merge! config
|
307
|
+
bootstrap.config[:chef_node_name] = locate_config_value(:server_name)
|
308
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
309
|
+
bootstrap.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
|
310
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
311
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
312
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
313
|
+
bootstrap.config[:environment] = locate_config_value(:environment)
|
314
|
+
bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
|
315
|
+
bootstrap.config[:secret_file] = locate_config_value(:secret_file) || {}
|
316
|
+
bootstrap
|
317
|
+
end
|
318
|
+
|
319
|
+
def bootstrap_class
|
320
|
+
if solo_bootstrap?
|
321
|
+
Chef::Knife::SoloBootstrap
|
322
|
+
elsif zero_bootstrap?
|
323
|
+
Chef::Knife::ZeroBootstrap
|
324
|
+
else
|
325
|
+
Chef::Knife::Bootstrap
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def solo_bootstrap?
|
330
|
+
config[:solo] || (config[:solo].nil? && Chef::Config[:knife][:solo])
|
331
|
+
end
|
332
|
+
|
333
|
+
def zero_bootstrap?
|
334
|
+
config[:zero] || (config[:zero].nil? && Chef::Config[:knife][:zero])
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|