vagrant-simplecloud 0.0.1
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 +3 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +23 -0
- data/README.md +143 -0
- data/Rakefile +22 -0
- data/box/metadata.json +3 -0
- data/box/simple_cloud.box +0 -0
- data/lib/vagrant-simplecloud.rb +20 -0
- data/lib/vagrant-simplecloud/actions.rb +165 -0
- data/lib/vagrant-simplecloud/actions/check_state.rb +19 -0
- data/lib/vagrant-simplecloud/actions/create.rb +84 -0
- data/lib/vagrant-simplecloud/actions/destroy.rb +32 -0
- data/lib/vagrant-simplecloud/actions/modify_provision_path.rb +38 -0
- data/lib/vagrant-simplecloud/actions/power_off.rb +33 -0
- data/lib/vagrant-simplecloud/actions/power_on.rb +36 -0
- data/lib/vagrant-simplecloud/actions/rebuild.rb +56 -0
- data/lib/vagrant-simplecloud/actions/reload.rb +33 -0
- data/lib/vagrant-simplecloud/actions/setup_key.rb +53 -0
- data/lib/vagrant-simplecloud/actions/setup_sudo.rb +48 -0
- data/lib/vagrant-simplecloud/actions/setup_user.rb +66 -0
- data/lib/vagrant-simplecloud/actions/shut_down.rb +34 -0
- data/lib/vagrant-simplecloud/actions/sync_folders.rb +89 -0
- data/lib/vagrant-simplecloud/commands/list.rb +89 -0
- data/lib/vagrant-simplecloud/commands/rebuild.rb +29 -0
- data/lib/vagrant-simplecloud/config.rb +64 -0
- data/lib/vagrant-simplecloud/errors.rb +37 -0
- data/lib/vagrant-simplecloud/helpers/client.rb +151 -0
- data/lib/vagrant-simplecloud/helpers/result.rb +40 -0
- data/lib/vagrant-simplecloud/plugin.rb +31 -0
- data/lib/vagrant-simplecloud/provider.rb +102 -0
- data/lib/vagrant-simplecloud/version.rb +5 -0
- data/locales/en.yml +90 -0
- data/test/Vagrantfile +41 -0
- data/test/cookbooks/test/recipes/default.rb +1 -0
- data/test/scripts/provision.sh +3 -0
- data/test/test.sh +14 -0
- data/test/test_id_rsa +27 -0
- data/test/test_id_rsa.pub +1 -0
- data/testkit.rb +40 -0
- data/vagrant-simplecloud.gemspec +22 -0
- metadata +146 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'vagrant-simplecloud/helpers/client'
|
3
|
+
|
4
|
+
module VagrantPlugins
|
5
|
+
module SimpleCloud
|
6
|
+
module Commands
|
7
|
+
class List < Vagrant.plugin('2', :command)
|
8
|
+
def self.synopsis
|
9
|
+
"list available images and regions from Simple Cloud"
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
@token = nil
|
14
|
+
|
15
|
+
@opts = OptionParser.new do |o|
|
16
|
+
o.banner = 'Usage: vagrant simplecloud-list [options] <images|regions|sizes> <token>'
|
17
|
+
|
18
|
+
o.on("-r", "--[no-]regions", "show the regions when listing images") do |r|
|
19
|
+
@regions = r
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
argv = parse_options(@opts)
|
24
|
+
@token = argv[1]
|
25
|
+
|
26
|
+
if @token.nil?
|
27
|
+
usage
|
28
|
+
return 1
|
29
|
+
end
|
30
|
+
|
31
|
+
case argv[0]
|
32
|
+
when "images"
|
33
|
+
result = query('/v2/images')
|
34
|
+
images = Array(result["images"])
|
35
|
+
if @regions
|
36
|
+
images_table = images.map do |image|
|
37
|
+
'%-50s %-20s %-20s %-50s' % ["#{image['distribution']} #{image['name']}", image['slug'], image['id'], image['regions'].join(', ')]
|
38
|
+
end
|
39
|
+
@env.ui.info I18n.t('vagrant_simple_cloud.info.images_with_regions', images: images_table.sort.join("\r\n"))
|
40
|
+
else
|
41
|
+
images_table = images.map do |image|
|
42
|
+
'%-50s %-30s %-30s' % ["#{image['distribution']} #{image['name']}", image['slug'], image['id']]
|
43
|
+
end
|
44
|
+
@env.ui.info I18n.t('vagrant_simple_cloud.info.images', images: images_table.sort.join("\r\n"))
|
45
|
+
end
|
46
|
+
when "regions"
|
47
|
+
result = query('/v2/regions')
|
48
|
+
regions = Array(result["regions"])
|
49
|
+
regions_table = regions.map { |region| '%-30s %-12s' % [region['name'], region['slug']] }
|
50
|
+
@env.ui.info I18n.t('vagrant_simple_cloud.info.regions', regions: regions_table.sort.join("\r\n"))
|
51
|
+
when "sizes"
|
52
|
+
result = query('/v2/sizes')
|
53
|
+
sizes = Array(result["sizes"])
|
54
|
+
sizes_table = sizes.map { |size| '%-15s %-15s %-12s' % ["#{size['memory']}MB", size['vcpus'], size['slug']] }
|
55
|
+
@env.ui.info I18n.t('vagrant_simple_cloud.info.sizes', sizes: sizes_table.sort_by{|s| s['memory']}.join("\r\n"))
|
56
|
+
else
|
57
|
+
usage
|
58
|
+
return 1
|
59
|
+
end
|
60
|
+
|
61
|
+
0
|
62
|
+
rescue Faraday::Error::ConnectionFailed, RuntimeError => e
|
63
|
+
@env.ui.error I18n.t('vagrant_simple_cloud.info.list_error', message: e.message)
|
64
|
+
1
|
65
|
+
end
|
66
|
+
|
67
|
+
def query(path)
|
68
|
+
connection = Faraday.new({
|
69
|
+
:url => "https://api.simplecloud.ru/"
|
70
|
+
})
|
71
|
+
|
72
|
+
result = connection.get(path, per_page: 100) do |req|
|
73
|
+
req.headers['Authorization'] = "Bearer #{@token}"
|
74
|
+
end
|
75
|
+
|
76
|
+
case result.status
|
77
|
+
when 200 then JSON.parse(result.body)
|
78
|
+
when 401 then raise("unauthorized access — is the token correct?")
|
79
|
+
else raise("call returned with status #{result.status}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def usage
|
84
|
+
@env.ui.info(@opts)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module SimpleCloud
|
5
|
+
module Commands
|
6
|
+
class Rebuild < Vagrant.plugin('2', :command)
|
7
|
+
|
8
|
+
# Show description when `vagrant list-commands` is triggered
|
9
|
+
def self.synopsis
|
10
|
+
"plugin: vagrant-simplecloud: destroys and ups the vm with the same ip address"
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
opts = OptionParser.new do |o|
|
15
|
+
o.banner = 'Usage: vagrant rebuild [vm-name]'
|
16
|
+
end
|
17
|
+
|
18
|
+
argv = parse_options(opts)
|
19
|
+
|
20
|
+
with_target_vms(argv) do |machine|
|
21
|
+
machine.action(:rebuild)
|
22
|
+
end
|
23
|
+
|
24
|
+
0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module SimpleCloud
|
3
|
+
class Config < Vagrant.plugin('2', :config)
|
4
|
+
attr_accessor :token
|
5
|
+
attr_accessor :image
|
6
|
+
attr_accessor :region
|
7
|
+
attr_accessor :size
|
8
|
+
attr_accessor :private_networking
|
9
|
+
attr_accessor :ipv6
|
10
|
+
attr_accessor :backups_enabled
|
11
|
+
attr_accessor :ca_path
|
12
|
+
attr_accessor :ssh_key_name
|
13
|
+
attr_accessor :setup
|
14
|
+
attr_accessor :user_data
|
15
|
+
|
16
|
+
alias_method :setup?, :setup
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@token = UNSET_VALUE
|
20
|
+
@image = UNSET_VALUE
|
21
|
+
@region = UNSET_VALUE
|
22
|
+
@size = UNSET_VALUE
|
23
|
+
@private_networking = UNSET_VALUE
|
24
|
+
@ipv6 = UNSET_VALUE
|
25
|
+
@backups_enable = UNSET_VALUE
|
26
|
+
@ca_path = UNSET_VALUE
|
27
|
+
@ssh_key_name = UNSET_VALUE
|
28
|
+
@setup = UNSET_VALUE
|
29
|
+
@user_data = UNSET_VALUE
|
30
|
+
end
|
31
|
+
|
32
|
+
def finalize!
|
33
|
+
@token = ENV['DO_TOKEN'] if @token == UNSET_VALUE
|
34
|
+
@image = 'ubuntu-14-04-x64' if @image == UNSET_VALUE
|
35
|
+
@region = 'nyc2' if @region == UNSET_VALUE
|
36
|
+
@size = '512mb' if @size == UNSET_VALUE
|
37
|
+
@private_networking = false if @private_networking == UNSET_VALUE
|
38
|
+
@ipv6 = false if @ipv6 == UNSET_VALUE
|
39
|
+
@backups_enabled = false if @backups_enabled == UNSET_VALUE
|
40
|
+
@ca_path = nil if @ca_path == UNSET_VALUE
|
41
|
+
@ssh_key_name = 'Vagrant' if @ssh_key_name == UNSET_VALUE
|
42
|
+
@setup = true if @setup == UNSET_VALUE
|
43
|
+
@user_data = nil if @user_data == UNSET_VALUE
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate(machine)
|
47
|
+
errors = []
|
48
|
+
errors << I18n.t('vagrant_simple_cloud.config.token') if !@token
|
49
|
+
|
50
|
+
key = machine.config.ssh.private_key_path
|
51
|
+
key = key[0] if key.is_a?(Array)
|
52
|
+
if !key
|
53
|
+
errors << I18n.t('vagrant_simple_cloud.config.private_key')
|
54
|
+
elsif !File.file?(File.expand_path("#{key}.pub", machine.env.root_path))
|
55
|
+
errors << I18n.t('vagrant_simple_cloud.config.public_key', {
|
56
|
+
:key => "#{key}.pub"
|
57
|
+
})
|
58
|
+
end
|
59
|
+
|
60
|
+
{ 'Simple Cloud Provider' => errors }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module SimpleCloud
|
3
|
+
module Errors
|
4
|
+
class SimpleCloudError < Vagrant::Errors::VagrantError
|
5
|
+
error_namespace("vagrant_simple_cloud.errors")
|
6
|
+
end
|
7
|
+
|
8
|
+
class APIStatusError < SimpleCloudError
|
9
|
+
error_key(:api_status)
|
10
|
+
end
|
11
|
+
|
12
|
+
class JSONError < SimpleCloudError
|
13
|
+
error_key(:json)
|
14
|
+
end
|
15
|
+
|
16
|
+
class ResultMatchError < SimpleCloudError
|
17
|
+
error_key(:result_match)
|
18
|
+
end
|
19
|
+
|
20
|
+
class CertificateError < SimpleCloudError
|
21
|
+
error_key(:certificate)
|
22
|
+
end
|
23
|
+
|
24
|
+
class LocalIPError < SimpleCloudError
|
25
|
+
error_key(:local_ip)
|
26
|
+
end
|
27
|
+
|
28
|
+
class PublicKeyError < SimpleCloudError
|
29
|
+
error_key(:public_key)
|
30
|
+
end
|
31
|
+
|
32
|
+
class RsyncError < SimpleCloudError
|
33
|
+
error_key(:rsync)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'vagrant-simplecloud/helpers/result'
|
2
|
+
require 'faraday'
|
3
|
+
require 'json'
|
4
|
+
require 'droplet_kit'
|
5
|
+
require 'uri'
|
6
|
+
require 'net/http'
|
7
|
+
require 'net/https'
|
8
|
+
|
9
|
+
module VagrantPlugins
|
10
|
+
module SimpleCloud
|
11
|
+
module Helpers
|
12
|
+
module Client
|
13
|
+
def client
|
14
|
+
@client ||= ApiClient.new(@machine)
|
15
|
+
end
|
16
|
+
def simple_client
|
17
|
+
@simple_client ||= SimpleClient.new(access_token: @machine.provider_config.token)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
class SimpleClient < DropletKit::Client
|
21
|
+
include Vagrant::Util::Retryable
|
22
|
+
|
23
|
+
def post(path, params = {})
|
24
|
+
uri = URI.parse("#{connection_options[:url]}#{path}")
|
25
|
+
https = Net::HTTP.new(uri.host,uri.port)
|
26
|
+
https.use_ssl = true
|
27
|
+
req = Net::HTTP::Post.new(uri.path)
|
28
|
+
req['Content-Type'] = connection_options[:headers][:content_type]
|
29
|
+
req['Authorization'] = connection_options[:headers][:authorization]
|
30
|
+
req.set_form_data(params)
|
31
|
+
res = https.request(req)
|
32
|
+
unless /^2\d\d$/ =~ result.code.to_s
|
33
|
+
raise "Server response error #{result.code} #{path} #{params} #{result.message} #{result.body}"
|
34
|
+
end
|
35
|
+
JSON.parse(res.body)
|
36
|
+
end
|
37
|
+
|
38
|
+
def wait_for_event(env, id)
|
39
|
+
retryable(:tries => 120, :sleep => 10) do
|
40
|
+
# stop waiting if interrupted
|
41
|
+
next if env[:interrupted]
|
42
|
+
# check action status
|
43
|
+
result = self.actions.find(id: id)
|
44
|
+
yield result if block_given?
|
45
|
+
raise 'not ready' if result.status != 'completed'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def connection_options
|
51
|
+
{
|
52
|
+
url: "https://api.simplecloud.ru",
|
53
|
+
headers: {
|
54
|
+
content_type: 'application/json',
|
55
|
+
authorization: "Bearer #{access_token}"
|
56
|
+
}
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
class ApiClient
|
61
|
+
include Vagrant::Util::Retryable
|
62
|
+
|
63
|
+
def initialize(machine)
|
64
|
+
@logger = Log4r::Logger.new('vagrant::simplecloud::apiclient')
|
65
|
+
@config = machine.provider_config
|
66
|
+
@client = Faraday.new({
|
67
|
+
:url => 'https://api.simplecloud.ru/',
|
68
|
+
:ssl => {
|
69
|
+
:ca_file => @config.ca_path
|
70
|
+
}
|
71
|
+
})
|
72
|
+
end
|
73
|
+
|
74
|
+
def delete(path, params = {}, method = :delete)
|
75
|
+
@client.request :url_encoded
|
76
|
+
request(path, params, :delete)
|
77
|
+
end
|
78
|
+
|
79
|
+
def post(path, params = {}, method = :post)
|
80
|
+
@client.headers['Content-Type'] = 'application/json'
|
81
|
+
request(path, params, :post)
|
82
|
+
end
|
83
|
+
|
84
|
+
def request(path, params = {}, method = :get)
|
85
|
+
begin
|
86
|
+
@logger.info "Request: #{path} #{params}"
|
87
|
+
result = @client.send(method) do |req|
|
88
|
+
req.url path, params
|
89
|
+
req.headers['Authorization'] = "Bearer #{@config.token}"
|
90
|
+
end
|
91
|
+
rescue Faraday::Error::ConnectionFailed => e
|
92
|
+
# TODO this is suspect but because farady wraps the exception
|
93
|
+
# in something generic there doesn't appear to be another
|
94
|
+
# way to distinguish different connection errors :(
|
95
|
+
if e.message =~ /certificate verify failed/
|
96
|
+
raise Errors::CertificateError
|
97
|
+
end
|
98
|
+
|
99
|
+
raise e
|
100
|
+
end
|
101
|
+
|
102
|
+
unless method == :delete
|
103
|
+
begin
|
104
|
+
body = JSON.parse(result.body)
|
105
|
+
@logger.info "Response: #{body}"
|
106
|
+
next_page = body["links"]["pages"]["next"] rescue nil
|
107
|
+
unless next_page.nil?
|
108
|
+
uri = URI.parse(next_page)
|
109
|
+
new_path = path.split("?")[0]
|
110
|
+
next_result = self.request("#{new_path}?#{uri.query}")
|
111
|
+
req_target = new_path.split("/")[-1]
|
112
|
+
body["#{req_target}"].concat(next_result["#{req_target}"])
|
113
|
+
end
|
114
|
+
rescue JSON::ParserError => e
|
115
|
+
raise(Errors::JSONError, {
|
116
|
+
:message => e.message,
|
117
|
+
:path => path,
|
118
|
+
:params => params,
|
119
|
+
:response => result.body
|
120
|
+
})
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
unless /^2\d\d$/ =~ result.status.to_s
|
125
|
+
raise(Errors::APIStatusError, {
|
126
|
+
:path => path,
|
127
|
+
:params => params,
|
128
|
+
:status => result.status,
|
129
|
+
:response => body.inspect
|
130
|
+
})
|
131
|
+
end
|
132
|
+
|
133
|
+
Result.new(body)
|
134
|
+
end
|
135
|
+
|
136
|
+
def wait_for_event(env, id)
|
137
|
+
retryable(:tries => 120, :sleep => 10) do
|
138
|
+
# stop waiting if interrupted
|
139
|
+
next if env[:interrupted]
|
140
|
+
|
141
|
+
# check action status
|
142
|
+
result = self.request("/v2/actions/#{id}")
|
143
|
+
|
144
|
+
yield result if block_given?
|
145
|
+
raise 'not ready' if result['action']['status'] != 'completed'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module SimpleCloud
|
3
|
+
module Helpers
|
4
|
+
class Result
|
5
|
+
def initialize(body)
|
6
|
+
@result = body
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](key)
|
10
|
+
@result[key.to_s]
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_id(sub_obj, search) #:ssh_keys, {:name => 'ijin (vagrant)'}
|
14
|
+
find(sub_obj, search)["id"]
|
15
|
+
end
|
16
|
+
|
17
|
+
def find(sub_obj, search)
|
18
|
+
key = search.keys.first #:slug
|
19
|
+
value = search[key].to_s #sfo1
|
20
|
+
key = key.to_s #slug
|
21
|
+
|
22
|
+
result = @result[sub_obj.to_s].inject(nil) do |result, obj|
|
23
|
+
obj[key] == value ? obj : result
|
24
|
+
end
|
25
|
+
|
26
|
+
result || error(sub_obj, key, value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def error(sub_obj, key, value)
|
30
|
+
raise(Errors::ResultMatchError, {
|
31
|
+
:key => key,
|
32
|
+
:value => value,
|
33
|
+
:collection_name => sub_obj.to_s,
|
34
|
+
:sub_obj => @result[sub_obj.to_s]
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module SimpleCloud
|
3
|
+
class Plugin < Vagrant.plugin('2')
|
4
|
+
name 'SimpleCloud'
|
5
|
+
description <<-DESC
|
6
|
+
This plugin installs a provider that allows Vagrant to manage
|
7
|
+
machines using SimpleCloud's API.
|
8
|
+
DESC
|
9
|
+
|
10
|
+
config(:simple_cloud, :provider) do
|
11
|
+
require_relative 'config'
|
12
|
+
Config
|
13
|
+
end
|
14
|
+
|
15
|
+
provider(:simple_cloud) do
|
16
|
+
require_relative 'provider'
|
17
|
+
Provider
|
18
|
+
end
|
19
|
+
|
20
|
+
command(:rebuild) do
|
21
|
+
require_relative 'commands/rebuild'
|
22
|
+
Commands::Rebuild
|
23
|
+
end
|
24
|
+
|
25
|
+
command("simplecloud-list", primary: false) do
|
26
|
+
require_relative 'commands/list'
|
27
|
+
Commands::List
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|