vagrant-simplecloud 0.0.1

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