hyperkit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ module Hyperkit
2
+
3
+ class Client
4
+
5
+ # Methods for the networks API
6
+ #
7
+ # @see Hyperkit::Client
8
+ # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
9
+ module Networks
10
+
11
+ # List of networks defined on the host
12
+ #
13
+ # @return [Array<String>] An array of networks defined on the host
14
+ #
15
+ # @example Get list of networks
16
+ # Hyperkit.networks #=> ["lo", "eth0", "lxcbr0"]
17
+ def networks
18
+ response = get(networks_path)
19
+ response.metadata.map { |path| path.split('/').last }
20
+ end
21
+
22
+ # Get information on a network
23
+ #
24
+ # @return [Sawyer::Resource] Network information
25
+ #
26
+ # @example Get information about lxcbr0
27
+ # Hyperkit.network("lxcbr0") #=> {:name=>"lxcbr0", :type=>"bridge", :used_by=>[]}
28
+ def network(name)
29
+ get(network_path(name)).metadata
30
+ end
31
+
32
+ private
33
+
34
+ def network_path(name)
35
+ File.join(networks_path, name)
36
+ end
37
+
38
+ def networks_path
39
+ "/1.0/networks"
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+
@@ -0,0 +1,123 @@
1
+ require 'active_support/core_ext/hash/except'
2
+
3
+ module Hyperkit
4
+
5
+ class Client
6
+
7
+ # Methods for the operations API
8
+ #
9
+ # @see Hyperkit::Client
10
+ # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
11
+ module Operations
12
+
13
+ # List of operations active on the server
14
+ #
15
+ # This will include operations that are currently executing, as well as
16
+ # operations that are paused until {#wait_for_operation} is called, at
17
+ # which time they will begin executing.
18
+ #
19
+ # Additionally, since LXD keeps completed operations around for 5 seconds,
20
+ # the list returned may include recently completed operations.
21
+ #
22
+ # @return [Array<String>] An array of UUIDs identifying waiting, active, and recently completed (<5 seconds) operations
23
+ #
24
+ # @example Get list of operations
25
+ # Hyperkit.operations #=> ["931e27fb-2057-4cbe-a49d-fd114713fa74"]
26
+ def operations
27
+ response = get(operations_path)
28
+ response.metadata.to_h.values.flatten.map { |path| path.split('/').last }
29
+ end
30
+
31
+ # Retrieve information about an operation
32
+ #
33
+ # @param [String] uuid UUID of the operation
34
+ # @return [Sawyer::Resource] Operation information
35
+ #
36
+ # @example Retrieve information about an operation
37
+ # Hyperkit.operation("d5f359ae-ddcb-4f09-a8f8-0cc2f3c8b0df") #=> {
38
+ # :id => "d5f359ae-ddcb-4f09-a8f8-0cc2f3c8b0df",
39
+ # :class => "task",
40
+ # :created_at => 2016-04-14 21:30:59 UTC,
41
+ # :updated_at => 2016-04-14 21:30:59 UTC,
42
+ # :status => "Running",
43
+ # :status_code => 103,
44
+ # :resources => {
45
+ # :containers => ["/1.0/containers/test-container"]
46
+ # },
47
+ # :metadata => nil,
48
+ # :may_cancel => false,
49
+ # :err => ""
50
+ # }
51
+ def operation(uuid)
52
+ get(operation_path(uuid)).metadata
53
+ end
54
+
55
+ # Cancel a running operation
56
+ #
57
+ # Calling this will change the state of the operation to
58
+ # <code>cancelling</code>. Note that the operation must be cancelable,
59
+ # which can be ascertained by calling {#operation} and checking the
60
+ # <code>may_cancel</code> property.
61
+ #
62
+ # @param [String] uuid UUID of the operation
63
+ # @return [Sawyer::Resource]
64
+ #
65
+ # @example Cancel an operation
66
+ # Hyperkit.cancel_operation("8b3dd0c2-9dad-4964-b00d-e21481a47fb8") => {}
67
+ def cancel_operation(uuid)
68
+ delete(operation_path(uuid)).metadata
69
+ end
70
+
71
+ # Wait for an asynchronous operation to complete
72
+ #
73
+ # Note that this is only needed if {#Hyperkit::auto_sync} has been
74
+ # set to <code>false</code>, or if the option <code>sync: false</code>
75
+ # has been passed to an asynchronous method.
76
+ #
77
+ # @param [String] uuid UUID of the operation
78
+ # @param [Fixnum] timeout Maximum time to wait (default: indefinite)
79
+ # @return [Sawyer::Resource] Operation result
80
+ #
81
+ # @example Wait for the creation of a container
82
+ # Hyperkit.auto_sync = false
83
+ # op = Hyperkit.create_container("test-container", alias: "ubuntu/amd64/default")
84
+ # Hyperkit.wait_for_operation(op.id)
85
+ #
86
+ # @example Wait, but time out if the operation is not complete after 30 seconds
87
+ # op = Hyperkit.copy_container("test1", "test2", sync: false)
88
+ # Hyperkit.wait_for_operation(op.id, timeout: 30)
89
+ def wait_for_operation(uuid, timeout=nil)
90
+ url = File.join(operation_path(uuid), "wait")
91
+ url += "?timeout=#{timeout}" if timeout.to_i > 0
92
+
93
+ get(url).metadata
94
+ end
95
+
96
+ private
97
+
98
+ def handle_async(response, sync)
99
+
100
+ sync = sync.nil? ? auto_sync : sync
101
+
102
+ if sync
103
+ wait_for_operation(response.id)
104
+ else
105
+ response
106
+ end
107
+
108
+ end
109
+
110
+ def operation_path(uuid)
111
+ File.join(operations_path, uuid)
112
+ end
113
+
114
+ def operations_path
115
+ "/1.0/operations"
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+
@@ -0,0 +1,59 @@
1
+ require 'active_support/core_ext/hash/except'
2
+
3
+ module Hyperkit
4
+
5
+ class Client
6
+
7
+ # Methods for the profiles API
8
+ #
9
+ # @see Hyperkit::Client
10
+ # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
11
+ module Profiles
12
+
13
+ # GET /profiles
14
+ def profiles
15
+ response = get(profiles_path)
16
+ response.metadata.map { |path| path.split('/').last }
17
+ end
18
+
19
+ # POST /profiles
20
+ def create_profile(name, options={})
21
+ options = options.merge(name: name)
22
+ post(profiles_path, options).metadata
23
+ end
24
+
25
+ # GET /profiles/<name>
26
+ def profile(name)
27
+ get(profile_path(name)).metadata
28
+ end
29
+
30
+ # PUT /profiles/<name>
31
+ def update_profile(name, options={})
32
+ put(profile_path(name), options.except(:name)).metadata
33
+ end
34
+
35
+ # POST /profiles/<name>
36
+ def rename_profile(old_name, new_name)
37
+ post(profile_path(old_name), { name: new_name }).metadata
38
+ end
39
+
40
+ # DELETE /profiles/<name>
41
+ def delete_profile(name)
42
+ delete(profile_path(name)).metadata
43
+ end
44
+
45
+ private
46
+
47
+ def profiles_path
48
+ "/1.0/profiles"
49
+ end
50
+
51
+ def profile_path(name)
52
+ File.join(profiles_path, name)
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,110 @@
1
+ ################################################################################
2
+ # #
3
+ # Modeled on Octokit::Configurable #
4
+ # #
5
+ # Original Octokit license #
6
+ # ---------------------------------------------------------------------------- #
7
+ # Copyright (c) 2009-2016 Wynn Netherland, Adam Stacoviak, Erik Michaels-Ober #
8
+ # #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a #
10
+ # copy of this software and associated documentation files (the "Software"), #
11
+ # to deal in the Software without restriction, including without limitation #
12
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense, #
13
+ # and/or sell copies of the Software, and to permit persons to whom the #
14
+ # Software is furnished to do so, subject to the following conditions: #
15
+ # #
16
+ # The above copyright notice and this permission notice shall be included #
17
+ # in all copies or substantial portions of the Software. #
18
+ # ---------------------------------------------------------------------------- #
19
+ # #
20
+ ################################################################################
21
+
22
+
23
+ module Hyperkit
24
+
25
+ # Configuration options for {Client}, defaulting to values
26
+ # in {Default}
27
+ module Configurable
28
+
29
+ # @!attribute api_endpoint
30
+ # @return [String] the base URL for API requests (default: <code>https://localhost:8443/</code>)
31
+ # @!attribute auto_sync
32
+ # @return [String] whether to automatically wait on asynchronous events (default: <code>true</code>)
33
+ # @!attribute client_cert
34
+ # @return [String] the client certificate used to authenticate to the LXD server
35
+ # @!attribute client_key
36
+ # @return [String] the client key used to authenticate to the LXD server
37
+ # @!attribute default_media_type
38
+ # @return [String] the preferred media type (for API versioning, for example)
39
+ # @!attribute middleware
40
+ # @see https://github.com/lostisland/faraday
41
+ # @return [Faraday::Builder or Faraday::RackBuilder] middleware for Faraday
42
+ # @!attribute proxy
43
+ # @see https://github.com/lostisland/faraday
44
+ # @return [String] the URI of a proxy server used to connect to the LXD server
45
+ # @!attribute user_agent
46
+ # @return [String] the <code>User-Agent</code> header used for requests made to the LXD server
47
+ # @!attribute verify_ssl
48
+ # @return [Boolean] whether or not to verify the LXD server's SSL certificate
49
+
50
+ attr_accessor :auto_sync, :client_cert, :client_key, :default_media_type,
51
+ :middleware, :proxy, :user_agent, :verify_ssl
52
+
53
+ attr_writer :api_endpoint
54
+
55
+ class << self
56
+
57
+ # List of configurable keys for {Hyperkit::Client}
58
+ # @return [Array] of option keys
59
+ def keys
60
+ @keys ||= [
61
+ :api_endpoint,
62
+ :auto_sync,
63
+ :client_cert,
64
+ :client_key,
65
+ :default_media_type,
66
+ :middleware,
67
+ :proxy,
68
+ :user_agent,
69
+ :verify_ssl
70
+ ]
71
+ end
72
+
73
+ end
74
+
75
+ # Set configuration options using a block
76
+ def configure
77
+ yield self
78
+ end
79
+
80
+ # Reset configuration options to default values
81
+ def reset!
82
+ Hyperkit::Configurable.keys.each do |key|
83
+ instance_variable_set(:"@#{key}", Hyperkit::Default.options[key])
84
+ end
85
+ self
86
+ end
87
+
88
+ alias setup reset!
89
+
90
+ # Compares client options to a Hash of requested options
91
+ #
92
+ # @param opts [Hash] Options to compare with current client options
93
+ # @return [Boolean]
94
+ def same_options?(opts)
95
+ opts.hash == options.hash
96
+ end
97
+
98
+ def api_endpoint
99
+ File.join(@api_endpoint, "")
100
+ end
101
+
102
+ private
103
+
104
+ def options
105
+ Hash[Hyperkit::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
106
+ end
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,196 @@
1
+ ################################################################################
2
+ # #
3
+ # Based on Octokit::Connection #
4
+ # #
5
+ # Original Octokit license #
6
+ # ---------------------------------------------------------------------------- #
7
+ # Copyright (c) 2009-2016 Wynn Netherland, Adam Stacoviak, Erik Michaels-Ober #
8
+ # #
9
+ # Permission is hereby granted, free of charge, to any person obtaining a #
10
+ # copy of this software and associated documentation files (the "Software"), #
11
+ # to deal in the Software without restriction, including without limitation #
12
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense, #
13
+ # and/or sell copies of the Software, and to permit persons to whom the #
14
+ # Software is furnished to do so, subject to the following conditions: #
15
+ # #
16
+ # The above copyright notice and this permission notice shall be included #
17
+ # in all copies or substantial portions of the Software. #
18
+ # ---------------------------------------------------------------------------- #
19
+ # #
20
+ ################################################################################
21
+
22
+ require 'sawyer'
23
+
24
+ module Hyperkit
25
+
26
+ # Network layer for API clients.
27
+ module Connection
28
+
29
+ # Header keys that can be passed in options hash to {#get},{#head}
30
+ CONVENIENCE_HEADERS = Set.new([:accept, :content_type])
31
+
32
+ # Make a HTTP GET request
33
+ #
34
+ # @param url [String] The path, relative to {#api_endpoint}
35
+ # @param options [Hash] Query and header params for request
36
+ # @return [Sawyer::Resource]
37
+ def get(url, options = {})
38
+ request :get, url, parse_query_and_convenience_headers(options)
39
+ end
40
+
41
+ # Make a HTTP POST request
42
+ #
43
+ # @param url [String] The path, relative to {#api_endpoint}
44
+ # @param options [Hash] Body and header params for request
45
+ # @return [Sawyer::Resource]
46
+ def post(url, options = {})
47
+ request :post, url, options
48
+ end
49
+
50
+ # Make a HTTP PUT request
51
+ #
52
+ # @param url [String] The path, relative to {#api_endpoint}
53
+ # @param options [Hash] Body and header params for request
54
+ # @return [Sawyer::Resource]
55
+ def put(url, options = {})
56
+ request :put, url, options
57
+ end
58
+
59
+ # Make a HTTP PATCH request
60
+ #
61
+ # @param url [String] The path, relative to {#api_endpoint}
62
+ # @param options [Hash] Body and header params for request
63
+ # @return [Sawyer::Resource]
64
+ def patch(url, options = {})
65
+ request :patch, url, options
66
+ end
67
+
68
+ # Make a HTTP DELETE request
69
+ #
70
+ # @param url [String] The path, relative to {#api_endpoint}
71
+ # @param options [Hash] Query and header params for request
72
+ # @return [Sawyer::Resource]
73
+ def delete(url, options = {})
74
+ request :delete, url, options
75
+ end
76
+
77
+ # Make a HTTP HEAD request
78
+ #
79
+ # @param url [String] The path, relative to {#api_endpoint}
80
+ # @param options [Hash] Query and header params for request
81
+ # @return [Sawyer::Resource]
82
+ def head(url, options = {})
83
+ request :head, url, parse_query_and_convenience_headers(options)
84
+ end
85
+
86
+ # Hypermedia agent for the GitHub API
87
+ #
88
+ # @return [Sawyer::Agent]
89
+ def agent
90
+ @agent ||= Sawyer::Agent.new(endpoint, sawyer_options) do |http|
91
+ http.headers[:accept] = default_media_type
92
+ http.headers[:content_type] = "application/json"
93
+ http.headers[:user_agent] = user_agent
94
+ end
95
+ end
96
+
97
+ # Fetch the root resource for the API
98
+ #
99
+ # @return [Sawyer::Resource]
100
+ def root
101
+ get "/"
102
+ end
103
+
104
+ # Response for last HTTP request
105
+ #
106
+ # @return [Sawyer::Response]
107
+ def last_response
108
+ @last_response if defined? @last_response
109
+ end
110
+
111
+ protected
112
+
113
+ def endpoint
114
+ api_endpoint
115
+ end
116
+
117
+ private
118
+
119
+ def reset_agent
120
+ @agent = nil
121
+ end
122
+
123
+ def request(method, path, data, options = {})
124
+ if data.is_a?(Hash)
125
+ options[:query] = data.delete(:query) || {}
126
+ options[:headers] = data.delete(:headers) || {}
127
+ url_encode = data.delete(:url_encode) || true
128
+
129
+ if accept = data.delete(:accept)
130
+ options[:headers][:accept] = accept
131
+ end
132
+ if data[:raw_body]
133
+ data = data[:raw_body]
134
+ end
135
+ end
136
+
137
+ path = URI::Parser.new.escape(path.to_s) if url_encode
138
+
139
+ @last_response = response = agent.call(method, path, data, options)
140
+ response.data
141
+ end
142
+
143
+ # Executes the request, checking if it was successful
144
+ #
145
+ # @return [Boolean] True on success, false otherwise
146
+ def boolean_from_response(method, path, options = {})
147
+ request(method, path, options)
148
+ @last_response.status == 204
149
+ rescue Hyperkit::NotFound
150
+ false
151
+ end
152
+
153
+
154
+ def sawyer_options
155
+ opts = {
156
+ :links_parser => Sawyer::LinkParsers::Simple.new,
157
+ }
158
+ conn_opts = {}
159
+ conn_opts[:builder] = @middleware if @middleware
160
+ conn_opts[:proxy] = @proxy if @proxy
161
+
162
+ conn_opts[:ssl] = {
163
+ verify: verify_ssl
164
+ }
165
+
166
+ if client_cert && File.exist?(client_cert)
167
+ conn_opts[:ssl][:client_cert] = OpenSSL::X509::Certificate.new(File.read(client_cert))
168
+ end
169
+
170
+ if client_key && File.exist?(client_key)
171
+ conn_opts[:ssl][:client_key] = OpenSSL::PKey::RSA.new(File.read(client_key))
172
+ end
173
+
174
+ opts[:faraday] = Faraday.new(conn_opts)
175
+
176
+ opts
177
+ end
178
+
179
+ def parse_query_and_convenience_headers(options)
180
+ headers = options.delete(:headers) { Hash.new }
181
+ CONVENIENCE_HEADERS.each do |h|
182
+ if header = options.delete(h)
183
+ headers[h] = header
184
+ end
185
+ end
186
+ query = options.delete(:query)
187
+ opts = {:query => options}
188
+ opts[:query].merge!(query) if query && query.is_a?(Hash)
189
+ opts[:headers] = headers unless headers.empty?
190
+
191
+ opts
192
+ end
193
+
194
+ end
195
+
196
+ end