vagrant-haipa 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 +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!'