vagrant-haipa 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 +19 -0
  3. data/.vscode/launch.json +29 -0
  4. data/Gemfile +20 -0
  5. data/LICENSE +373 -0
  6. data/README.md +111 -0
  7. data/Rakefile +24 -0
  8. data/box/haipa.box +0 -0
  9. data/box/metadata.json +3 -0
  10. data/lib/vagrant-haipa.rb +20 -0
  11. data/lib/vagrant-haipa/actions.rb +209 -0
  12. data/lib/vagrant-haipa/actions/check_state.rb +19 -0
  13. data/lib/vagrant-haipa/actions/connect_haipa.rb +24 -0
  14. data/lib/vagrant-haipa/actions/create_machine.rb +56 -0
  15. data/lib/vagrant-haipa/actions/delete_machine.rb +31 -0
  16. data/lib/vagrant-haipa/actions/is_created.rb +18 -0
  17. data/lib/vagrant-haipa/actions/is_stopped.rb +18 -0
  18. data/lib/vagrant-haipa/actions/message_already_created.rb +16 -0
  19. data/lib/vagrant-haipa/actions/message_not_created.rb +16 -0
  20. data/lib/vagrant-haipa/actions/message_will_not_destroy.rb +16 -0
  21. data/lib/vagrant-haipa/actions/modify_provision_path.rb +38 -0
  22. data/lib/vagrant-haipa/actions/set_name.rb +53 -0
  23. data/lib/vagrant-haipa/actions/shut_down.rb +33 -0
  24. data/lib/vagrant-haipa/actions/start_machine.rb +34 -0
  25. data/lib/vagrant-haipa/actions/stop_machine.rb +33 -0
  26. data/lib/vagrant-haipa/actions/wait_for_ip_address.rb +32 -0
  27. data/lib/vagrant-haipa/config.rb +54 -0
  28. data/lib/vagrant-haipa/errors.rb +41 -0
  29. data/lib/vagrant-haipa/helpers/client.rb +111 -0
  30. data/lib/vagrant-haipa/helpers/result.rb +40 -0
  31. data/lib/vagrant-haipa/plugin.rb +22 -0
  32. data/lib/vagrant-haipa/provider.rb +118 -0
  33. data/lib/vagrant-haipa/version.rb +5 -0
  34. data/locales/en.yml +95 -0
  35. data/test/Vagrantfile +43 -0
  36. data/test/cookbooks/test/recipes/default.rb +1 -0
  37. data/test/scripts/provision.sh +3 -0
  38. data/test/test.sh +13 -0
  39. data/test/test_id_rsa +27 -0
  40. data/test/test_id_rsa.pub +1 -0
  41. data/vagrant +27 -0
  42. data/vagrant-haipa.gemspec +21 -0
  43. metadata +132 -0
@@ -0,0 +1,111 @@
1
+ require 'vagrant-haipa/helpers/result'
2
+ require 'faraday'
3
+ require 'json'
4
+ module VagrantPlugins
5
+ module Haipa
6
+ module Helpers
7
+ module Client
8
+ def client
9
+ @client ||= ApiClient.new(@machine)
10
+ end
11
+ end
12
+
13
+ class ApiClient
14
+ include Vagrant::Util::Retryable
15
+
16
+ def initialize(machine)
17
+ @logger = Log4r::Logger.new('vagrant::haipa::apiclient')
18
+ @config = machine.provider_config
19
+ @client = Faraday.new({
20
+ :url => 'http://localhost:62189/',
21
+ :ssl => {
22
+ :ca_file => @config.ca_path
23
+ }
24
+ })
25
+ end
26
+
27
+ def delete(path, params = {})
28
+ request(path, params, :delete)
29
+ end
30
+
31
+ def post(path, params = {})
32
+ @client.headers['Content-Type'] = 'application/json'
33
+ request(path, params, :post)
34
+ end
35
+
36
+ def request(path, params = {}, method = :get)
37
+ begin
38
+ @logger.info "Request: #{path}"
39
+ result = @client.send(method) do |req|
40
+ req.url path
41
+ req.body = params.to_json unless method == :get
42
+ req.params = params if method == :get
43
+
44
+ req.headers['Authorization'] = "Bearer #{@config.token}"
45
+ end
46
+ rescue Faraday::Error::ConnectionFailed => e
47
+ # TODO this is suspect but because farady wraps the exception
48
+ # in something generic there doesn't appear to be another
49
+ # way to distinguish different connection errors :(
50
+ if e.message =~ /certificate verify failed/
51
+ raise Errors::CertificateError
52
+ end
53
+
54
+ raise e
55
+ end
56
+
57
+ begin
58
+ body = JSON.parse(result.body)
59
+ body.delete_if { |key, _| key == '@odata.context' }
60
+
61
+ @logger.info "Response: #{body}"
62
+ rescue JSON::ParserError => e
63
+ raise(Errors::JSONError, {
64
+ :message => e.message,
65
+ :path => path,
66
+ :params => params,
67
+ :response => result.body
68
+ })
69
+ end
70
+
71
+ unless /^2\d\d$/ =~ result.status.to_s
72
+ raise(Errors::APIStatusError, {
73
+ :path => path,
74
+ :params => params,
75
+ :status => result.status,
76
+ :response => body.inspect
77
+ })
78
+ end
79
+ Result.new(body)
80
+ end
81
+
82
+ def wait_for_event(env, id)
83
+ timestamp = '2018-09-01T23:47:17.50094+02:00'
84
+
85
+ operation_error = nil
86
+ retryable(:tries => 20, :sleep => 5) do
87
+ # stop waiting if interrupted
88
+ next if env[:interrupted]
89
+
90
+ # check action status
91
+ result = request("odata/Operations(#{id})", '$expand' => "LogEntries($filter=Timestamp gt #{timestamp})")
92
+
93
+ result['LogEntries'].each do |entry|
94
+ env[:ui].info(entry['Message'])
95
+
96
+ timestamp = entry['Timestamp']
97
+ end
98
+
99
+ yield result if block_given?
100
+
101
+ raise 'Operation not completed' if result['Status'] == 'Running' || result['Status'] == 'Queued'
102
+ operation_error = result['StatusMessage'] if result['Status'] == 'Failed'
103
+ end
104
+
105
+ raise "Operation failed: #{operation_error}" if operation_error
106
+
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,40 @@
1
+ module VagrantPlugins
2
+ module Haipa
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 |result2, obj|
23
+ obj[key] == value ? obj : result2
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,22 @@
1
+ module VagrantPlugins
2
+ module Haipa
3
+ class Plugin < Vagrant.plugin('2')
4
+ name 'Haipa'
5
+ description <<-DESC
6
+ This plugin installs a provider that allows Vagrant to manage
7
+ machines using Haipa's API.
8
+ DESC
9
+
10
+ config(:haipa, :provider) do
11
+ require_relative 'config'
12
+ Config
13
+ end
14
+
15
+ provider(:haipa, parallel: true, defaultable: false, box_optional: true) do
16
+ require_relative 'provider'
17
+ Provider
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,118 @@
1
+ require 'vagrant-haipa/actions'
2
+ require 'vagrant-haipa/helpers/client'
3
+
4
+ module VagrantPlugins
5
+ module Haipa
6
+ class Provider < Vagrant.plugin('2', :provider)
7
+
8
+ def self.haipa_machines(machine)
9
+ client = Helpers::ApiClient.new(machine)
10
+
11
+ unless @haipa_machines
12
+ result = client.request('/odata/Machines', {'$expand' => 'Networks' })
13
+ @haipa_machines = result['value']
14
+ end
15
+ return @haipa_machines
16
+ end
17
+
18
+ # This class method caches status for all machines within
19
+ # the Haipa account. A specific machine's status
20
+ # may be refreshed by passing :refresh => true as an option.
21
+ def self.haipa_machine(machine, opts = {})
22
+ client = Helpers::ApiClient.new(machine)
23
+
24
+ # load status of machines if it has not been done before
25
+ haipa_machines(machine)
26
+
27
+ if opts[:refresh] && machine.id
28
+ # refresh the machine status for the given machine
29
+ @haipa_machines.delete_if { |d| d['Id'].to_s == machine.id }
30
+ result = client.request("/odata/Machines(#{machine.id})", {'$expand' => 'Networks' })
31
+ @haipa_machines << haipa_machine = result
32
+ else
33
+ # lookup machine status for the given machine
34
+ haipa_machine = @haipa_machines.find { |d| d['Id'].to_s == machine.id }
35
+ end
36
+
37
+ # if lookup by id failed, check for a machine with a matching name
38
+ # and set the id to ensure vagrant stores locally
39
+ # TODO allow the user to configure this behavior
40
+ unless haipa_machine
41
+ name = machine.config.vm.hostname || machine.name
42
+ haipa_machine = @haipa_machines.find { |d| d['Name'] == name.to_s }
43
+ machine.id = haipa_machine['Id'].to_s if haipa_machine
44
+ end
45
+
46
+ haipa_machine || { 'Status' => 'not_created' }
47
+ end
48
+
49
+ def initialize(machine)
50
+ @machine = machine
51
+ end
52
+
53
+ def action(name)
54
+ # Attempt to get the action method from the Action class if it
55
+ # exists, otherwise return nil to show that we don't support the
56
+ # given action.
57
+ action_method = "action_#{name}"
58
+ return Actions.send(action_method) if Actions.respond_to?(action_method)
59
+ nil
60
+ end
61
+
62
+ # This method is called if the underying machine ID changes. Providers
63
+ # can use this method to load in new data for the actual backing
64
+ # machine or to realize that the machine is now gone (the ID can
65
+ # become `nil`). No parameters are given, since the underlying machine
66
+ # is simply the machine instance given to this object. And no
67
+ # return value is necessary.
68
+ def machine_id_changed
69
+ end
70
+
71
+ # This should return a hash of information that explains how to
72
+ # SSH into the machine. If the machine is not at a point where
73
+ # SSH is even possible, then `nil` should be returned.
74
+ #
75
+ # The general structure of this returned hash should be the
76
+ # following:
77
+ #
78
+ # {
79
+ # :host => "1.2.3.4",
80
+ # :port => "22",
81
+ # :username => "mitchellh",
82
+ # :private_key_path => "/path/to/my/key"
83
+ # }
84
+ #
85
+ # **Note:** Vagrant only supports private key based authenticatonion,
86
+ # mainly for the reason that there is no easy way to exec into an
87
+ # `ssh` prompt with a password, whereas we can pass a private key
88
+ # via commandline.
89
+ def ssh_info
90
+ machine = Provider.haipa_machine(@machine)
91
+
92
+ return nil if machine['Status'].to_sym != :Running
93
+
94
+
95
+ # Run a custom action called "ssh_ip" which does what it says and puts
96
+ # the IP found into the `:machine_ip` key in the environment.
97
+ env = @machine.action("ssh_ip")
98
+
99
+ # If we were not able to identify the machine IP, we return nil
100
+ # here and we let Vagrant core deal with it ;)
101
+ return nil unless env[:machine_ip]
102
+
103
+ return {
104
+ :host => env[:machine_ip],
105
+ }
106
+ end
107
+
108
+ # This should return the state of the machine within this provider.
109
+ # The state must be an instance of {MachineState}. Please read the
110
+ # documentation of that class for more information.
111
+ def state
112
+ state = Provider.haipa_machine(@machine)['Status'].downcase.to_sym
113
+ long = short = state.to_s
114
+ Vagrant::MachineState.new(state, short, long)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module Haipa
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,95 @@
1
+ en:
2
+ vagrant_haipa:
3
+ info:
4
+ off: "Machine is off"
5
+ not_created: "Machine has not been created"
6
+ already_active: "Machine is already active"
7
+ already_off: "Machine is already off"
8
+ creating: "Creating a new machine..."
9
+ machine_ip: "Assigned IP address: %{ip}"
10
+ destroying: "Destroying the machine..."
11
+ shutting_down: "Shutting down the machine..."
12
+ powering_off: "Powering off the machine..."
13
+ powering_on: "Powering on the machine..."
14
+ rebuilding: "Rebuilding the machine..."
15
+ reloading: "Rebooting the machine..."
16
+ creating_user: "Creating user account: %{user}..."
17
+ late_sudo_install_deb8: "Haipa's debian-8 image lacks sudo. Installing now."
18
+ modifying_sudo: "Modifying sudoers file to remove tty requirement..."
19
+ using_key: "Using existing SSH key: %{name}"
20
+ creating_key: "Creating new SSH key: %{name}..."
21
+ trying_rsync_install: "Rsync not found, attempting to install with yum..."
22
+ rsyncing: "Rsyncing folder: %{hostpath} => %{guestpath}..."
23
+ rsync_missing: "The rsync executable was not found in the current path."
24
+ images: "Description Slug ID\n\n%{images}"
25
+ images_with_regions: "Description Slug ID Regions\n\n%{images}"
26
+ regions: "Description Slug\n\n%{regions}"
27
+ sizes: "Memory CPUs Slug\n\n%{sizes}"
28
+ list_error: 'Could not contact the Haipa API: %{message}'
29
+ will_not_destroy: |-
30
+ The instance '%{name}' will not be destroyed, since the confirmation
31
+ was declined.
32
+ already_status: |-
33
+ The machine is already %{status}.
34
+ config:
35
+ token: "Token is required"
36
+ private_key: "SSH private key path is required"
37
+ public_key: "SSH public key not found: %{key}"
38
+ errors:
39
+ public_key: |-
40
+ There was an issue reading the public key at:
41
+
42
+ Path: %{path}
43
+
44
+ Please check the file's permissions.
45
+ api_status: |-
46
+ There was an issue with the request made to the Haipa
47
+ API at:
48
+
49
+ Path: %{path}
50
+ URI Params: %{params}
51
+
52
+ The response status from the API was:
53
+
54
+ Status: %{status}
55
+ Response: %{response}
56
+ rsync: |-
57
+ There was an error when attemping to rsync a share folder.
58
+ Please inspect the error message below for more info.
59
+
60
+ Host path: %{hostpath}
61
+ Guest path: %{guestpath}
62
+ Error: %{stderr}
63
+ json: |-
64
+ There was an issue with the JSON response from the Haipa
65
+ API at:
66
+
67
+ Path: %{path}
68
+ URI Params: %{params}
69
+
70
+ The response JSON from the API was:
71
+
72
+ Response: %{response}
73
+ result_match: |-
74
+ The result collection for %{collection_name}:
75
+
76
+ %{sub_obj}
77
+
78
+ Contained no object with the value "%{value}" for the the
79
+ key "%{key}".
80
+
81
+ Please ensure that the configured value exists in the collection.
82
+ certificate: |-
83
+ The secure connection to the Haipa API has failed. Please
84
+ ensure that your local certificates directory is defined in the
85
+ provider config.
86
+
87
+ config.vm.provider :haipa do |vm|
88
+ vm.ca_path = "/path/to/ssl/ca/cert.crt"
89
+ end
90
+
91
+ This is generally caused by the OpenSSL configuration associated
92
+ with the Ruby install being unaware of the system specific ca
93
+ certs.
94
+ local_ip: |-
95
+ The Haipa provider was unable to determine the host's IP.
data/test/Vagrantfile ADDED
@@ -0,0 +1,43 @@
1
+ REQUIRED_PLUGINS = %w(vagrant-haipa)
2
+
3
+ Vagrant.configure('2') do |config|
4
+
5
+ config.ssh.username = 'ubuntu'
6
+ config.ssh.password = 'vagrant'
7
+
8
+ config.vm.synced_folder ".", "/vagrant", disabled: true
9
+
10
+ #config.vm.provision :shell, :path => 'scripts/provision.sh'
11
+
12
+ config.vm.define :ubuntu do |ubuntu|
13
+ ubuntu.vm.provider :haipa do |provider|
14
+ provider.vm_config = {
15
+ 'Memory' => {
16
+ 'Startup' => 2048
17
+ },
18
+ 'Disks' => [
19
+ {
20
+ "Template" => 'T:\openstack\ubuntu-xenial.vhdx',
21
+ "Size" => 20
22
+ }
23
+ ],
24
+ 'NetworkAdapters' => [
25
+ {
26
+ "Name" => "eth0",
27
+ "SwitchName" => "Default Switch",
28
+ }
29
+ ] ,
30
+ }
31
+
32
+ provider.provision = {
33
+ 'Hostname' => 'basic',
34
+ "UserData" => {
35
+ "password" => config.ssh.password,
36
+ "chpasswd" => {
37
+ "expire"=> "False"
38
+ }
39
+ }
40
+ }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1 @@
1
+ log 'Testing 1 2 3!'