hyperkit 1.0.0

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.
@@ -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