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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile +11 -0
  5. data/LICENSE.txt +23 -0
  6. data/README.md +143 -0
  7. data/Rakefile +22 -0
  8. data/box/metadata.json +3 -0
  9. data/box/simple_cloud.box +0 -0
  10. data/lib/vagrant-simplecloud.rb +20 -0
  11. data/lib/vagrant-simplecloud/actions.rb +165 -0
  12. data/lib/vagrant-simplecloud/actions/check_state.rb +19 -0
  13. data/lib/vagrant-simplecloud/actions/create.rb +84 -0
  14. data/lib/vagrant-simplecloud/actions/destroy.rb +32 -0
  15. data/lib/vagrant-simplecloud/actions/modify_provision_path.rb +38 -0
  16. data/lib/vagrant-simplecloud/actions/power_off.rb +33 -0
  17. data/lib/vagrant-simplecloud/actions/power_on.rb +36 -0
  18. data/lib/vagrant-simplecloud/actions/rebuild.rb +56 -0
  19. data/lib/vagrant-simplecloud/actions/reload.rb +33 -0
  20. data/lib/vagrant-simplecloud/actions/setup_key.rb +53 -0
  21. data/lib/vagrant-simplecloud/actions/setup_sudo.rb +48 -0
  22. data/lib/vagrant-simplecloud/actions/setup_user.rb +66 -0
  23. data/lib/vagrant-simplecloud/actions/shut_down.rb +34 -0
  24. data/lib/vagrant-simplecloud/actions/sync_folders.rb +89 -0
  25. data/lib/vagrant-simplecloud/commands/list.rb +89 -0
  26. data/lib/vagrant-simplecloud/commands/rebuild.rb +29 -0
  27. data/lib/vagrant-simplecloud/config.rb +64 -0
  28. data/lib/vagrant-simplecloud/errors.rb +37 -0
  29. data/lib/vagrant-simplecloud/helpers/client.rb +151 -0
  30. data/lib/vagrant-simplecloud/helpers/result.rb +40 -0
  31. data/lib/vagrant-simplecloud/plugin.rb +31 -0
  32. data/lib/vagrant-simplecloud/provider.rb +102 -0
  33. data/lib/vagrant-simplecloud/version.rb +5 -0
  34. data/locales/en.yml +90 -0
  35. data/test/Vagrantfile +41 -0
  36. data/test/cookbooks/test/recipes/default.rb +1 -0
  37. data/test/scripts/provision.sh +3 -0
  38. data/test/test.sh +14 -0
  39. data/test/test_id_rsa +27 -0
  40. data/test/test_id_rsa.pub +1 -0
  41. data/testkit.rb +40 -0
  42. data/vagrant-simplecloud.gemspec +22 -0
  43. 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