vagrant-cloud 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a547859f43c4103fc805d0938a5b68f3dabfd5c3
4
+ data.tar.gz: 76c22a16cd116c63e500800f049303baf356320c
5
+ SHA512:
6
+ metadata.gz: 46bc7d51f635d08fda44e25c87bea6b3b8c77a5a6b7cb306b21ba4fc6b75af4162ef8369e2a9584a78fe66c059aa823ed61bb47c1646164b4e4f28189b206671
7
+ data.tar.gz: aa6f28d042f3b52f2cc5c08f48f06370321dd2326c7a8726c0422f63978abb51fcbafcb51a4ceed972d4821416bcddbac43611dd15bbc9312a8c6b2ddfcc1534
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+
6
+ group :local do
7
+ gem 'pry'
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2014 Seth Vargo <sethvargo@gmail.com>
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,66 @@
1
+ Vagrant Cloud Gem
2
+ =================
3
+ The `vagrant-cloud` gem is a tiny Ruby gem for interacting with the [Vagrant
4
+ Cloud](https://vagrantcloud.com) service.
5
+
6
+ **WARNING!** This gem is still very much a work in progress!
7
+
8
+
9
+ Installation
10
+ ------------
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'vagrant-cloud'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```bash
20
+ $ bundle
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```bash
26
+ $ gem install vagrant-cloud
27
+ ```
28
+
29
+
30
+ Usage
31
+ -----
32
+ TODO: Write usage instructions here
33
+
34
+ ```ruby
35
+ VagrantCloud::Resource::Box.find('chef/centos-6.5') #=> #<VagrantCloud::Resource::Box ...>
36
+ ```
37
+
38
+
39
+ Contributing
40
+ ------------
41
+ 1. Fork it ( http://github.com/<my-github-username>/vagrant-cloud/fork )
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
46
+
47
+
48
+ License & Authors
49
+ -----------------
50
+ - Author: Seth Vargo (<sethvargo@gmail.com>)
51
+
52
+ ```text
53
+ Copyright 2014 Seth Vargo <sethvargo@gmail.com>
54
+
55
+ Licensed under the Apache License, Version 2.0 (the "License");
56
+ you may not use this file except in compliance with the License.
57
+ You may obtain a copy of the License at
58
+
59
+ http://www.apache.org/licenses/LICENSE-2.0
60
+
61
+ Unless required by applicable law or agreed to in writing, software
62
+ distributed under the License is distributed on an "AS IS" BASIS,
63
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
64
+ See the License for the specific language governing permissions and
65
+ limitations under the License.
66
+ ```
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap interrupts to quit cleanly.
4
+ Signal.trap('INT') { exit 1 }
5
+
6
+ $:.unshift File.join(File.dirname(__FILE__), %w{.. lib})
7
+
8
+ require 'vagrant-cloud/cli'
@@ -0,0 +1,69 @@
1
+ require 'logify'
2
+ require 'vagrant-cloud/version'
3
+
4
+ module VagrantCloud
5
+ autoload :Client, 'vagrant-cloud/client'
6
+ autoload :Configurable, 'vagrant-cloud/configurable'
7
+ autoload :Defaults, 'vagrant-cloud/defaults'
8
+
9
+ module Resource
10
+ autoload :Base, 'vagrant-cloud/resources/base'
11
+ autoload :Box, 'vagrant-cloud/resources/box'
12
+ autoload :Providers, 'vagrant-cloud/resources/providers'
13
+ autoload :Versions, 'vagrant-cloud/resources/versions'
14
+ end
15
+
16
+ class << self
17
+ include Configurable
18
+
19
+ #
20
+ #
21
+ #
22
+ def log_level=(level)
23
+ Logify.level = level
24
+ end
25
+
26
+ #
27
+ #
28
+ #
29
+ def io=(io)
30
+ Logify.io = io
31
+ end
32
+
33
+ #
34
+ # Client object based off the configured options in {Configurable}.
35
+ #
36
+ # @return [Client]
37
+ #
38
+ def client
39
+ unless defined?(@client) && @client.same_options?(options)
40
+ @client = Client.new(options)
41
+ end
42
+
43
+ @client
44
+ end
45
+
46
+ #
47
+ # Delegate all methods to the client object, essentially making the module
48
+ # object behave like a {client}.
49
+ #
50
+ def method_missing(m, *args, &block)
51
+ if client.respond_to?(m)
52
+ client.send(m, *args, &block)
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ #
59
+ # Delegating +respond_to+ to the {client}.
60
+ #
61
+ def respond_to_missing?(m, include_private = false)
62
+ client.respond_to?(m) || super
63
+ end
64
+ end
65
+ end
66
+
67
+ VagrantCloud.setup
68
+
69
+ # VagrantCloud.boxes.all
@@ -0,0 +1,26 @@
1
+ require 'clamp'
2
+ require 'vagrant-cloud'
3
+
4
+ module VagrantCloud
5
+ class BoxCommand < Clamp::Command
6
+ parameter 'NAME', 'The name of the box'
7
+
8
+ def execute
9
+ box = VagrantCloud.boxes.find(name)
10
+
11
+ puts "#{box.name}\t#{box.short_description}"
12
+ puts
13
+ puts 'Versions'
14
+ box.versions.each do |version|
15
+ providers = version['providers'].map { |provider| provider['name'] }
16
+ puts " #{version['version']} (#{providers.join(', ')})"
17
+ end
18
+ end
19
+ end
20
+
21
+ class CLI < Clamp::Command
22
+ subcommand 'box', 'Something', BoxCommand
23
+ end
24
+ end
25
+
26
+ VagrantCloud::CLI.run
@@ -0,0 +1,4 @@
1
+ module VagrantCloud
2
+ class Cli::Box
3
+ end
4
+ end
@@ -0,0 +1,361 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'socket'
4
+
5
+ module VagrantCloud
6
+ class Client
7
+ include Configurable
8
+ include Logify
9
+
10
+ #
11
+ # Create a new Vagrant Cloud client (connection) object.
12
+ #
13
+ # @example Create a simple connection
14
+ # VagrantCloud::Client.new
15
+ #
16
+ # @example Create a custom connection
17
+ # VagrantCloud::Client.new(endpoint: '...', access_token: '...')
18
+ #
19
+ # @example Create a custom connection using a block
20
+ # VagrantCloud::Client.new do |client|
21
+ # client.endpoint = '...'
22
+ # client.access_token = '...'
23
+ # end
24
+ #
25
+ # @param [Hash] options
26
+ # the list of options to create the client with
27
+ # @param [Proc] block
28
+ # an optional block that is called against +self+
29
+ #
30
+ # @return [Client]
31
+ #
32
+ def initialize(options = {}, &block)
33
+ Configurable.keys.each do |key|
34
+ value = options[key] || VagrantCloud.instance_variable_get(:"@#{key}")
35
+ instance_variable_set(:"@#{key}", value)
36
+ end
37
+
38
+ block.call(self) if block
39
+
40
+ # Setup log filtering for the access_token
41
+ Logify.filter(access_token) if access_token
42
+ end
43
+
44
+
45
+ def boxes
46
+ Resource::Box
47
+ end
48
+
49
+ #
50
+ # Determine if the given options are the same as ours.
51
+ #
52
+ # @return [Boolean]
53
+ #
54
+ def same_options?(opts)
55
+ opts.hash == options.hash
56
+ end
57
+
58
+ #
59
+ # @see Client#request
60
+ #
61
+ def get(path, params = {})
62
+ request(:get, path, params)
63
+ end
64
+
65
+ #
66
+ # @see Client#request
67
+ #
68
+ def post(path, data)
69
+ request(:post, path, data)
70
+ end
71
+
72
+ #
73
+ # @see Client#request
74
+ #
75
+ def put(path, data)
76
+ request(:put, path, data)
77
+ end
78
+
79
+ #
80
+ # @see Client#request
81
+ #
82
+ def delete(path, params = {})
83
+ request(:delete, path, params)
84
+ end
85
+
86
+ #
87
+ # Make an HTTP request with the given verb, data, params, and headers. If
88
+ # the response has a return type of JSON, the JSON is automatically parsed
89
+ # and returned as a hash; otherwise it is returned as a string.
90
+ #
91
+ # @raise [Error::HTTPError]
92
+ # if the request is not an HTTP 200 OK
93
+ #
94
+ # @param [Symbol] verb
95
+ # the lowercase symbol of the HTTP verb (e.g. :get, :delete)
96
+ # @param [String] path
97
+ # the absolute or relative path from {Defaults.endpoint} to make the
98
+ # request against
99
+ # @param [#read, Hash, nil] data
100
+ # the data to use (varies based on the +verb+)
101
+ # @param [Hash] headers
102
+ # the list of headers to use
103
+ #
104
+ # @return [String, Hash]
105
+ # the response body
106
+ #
107
+ def request(verb, path, data = {}, headers = {})
108
+ log.info { "#{verb.to_s.upcase} #{path}" }
109
+
110
+ # Add the access_token to the data, unless it was explicitly given to us
111
+ # as part of the request object. If you explicitly give an access token,
112
+ # it probably won't be filtered in log output...
113
+ data['access_token'] ||= access_token if access_token
114
+
115
+ # Build the URI and request object from the given information
116
+ uri = build_uri(verb, path, data)
117
+ request = class_for_request(verb).new(uri.request_uri)
118
+
119
+ # Add headers
120
+ add_request_headers(request, headers)
121
+
122
+ # Setup PATCH/POST/PUT
123
+ if [:patch, :post, :put].include?(verb)
124
+ if data.respond_to?(:read)
125
+ log.debug { 'Detected data is a stream or file' }
126
+ log.debug { 'Setting body_stream' }
127
+ request.body_stream = data
128
+ elsif data.is_a?(Hash)
129
+ log.debug { 'Detected data is a hash' }
130
+ log.debug { 'Posting data as form_data' }
131
+ request.form_data = data
132
+ else
133
+ log.debug { 'Detected data as unknown' }
134
+ log.debug { 'Setting body to data and hoping it all works out' }
135
+ request.body = data
136
+ end
137
+ end
138
+
139
+ if proxy_address || proxy_port || proxy_username || proxy_password
140
+ log.info { 'Setting proxy information' }
141
+ log.debug { "address: #{proxy_address}" }
142
+ log.debug { "port: #{proxy_port}" }
143
+ log.debug { "username: #{proxy_username}" }
144
+ log.debug { "password: #{'*'*20}" }
145
+ end
146
+
147
+ # Create the HTTP connection object - since the proxy information defaults
148
+ # to +nil+, we can just pass it to the initializer method instead of doing
149
+ # crazy strange conditionals.
150
+ connection = Net::HTTP.new(uri.host, uri.port,
151
+ proxy_address, proxy_port, proxy_username, proxy_password)
152
+
153
+ # Apply SSL, if applicable
154
+ if uri.scheme == 'https'
155
+ log.info { 'Detected request as SSL' }
156
+
157
+ require 'net/https' unless defined?(Net::HTTPS)
158
+
159
+ # Turn on SSL
160
+ log.debug { 'Enabling SSL' }
161
+ connection.use_ssl = true
162
+
163
+ # Custom pem files, no problem!
164
+ if ssl_pem_file
165
+ log.debug { 'Detected a custom SSL pem file given' }
166
+ log.debug { "Using SSL pem file from #{ssl_pem_file}" }
167
+ pem = File.read(ssl_pem_file)
168
+ connection.cert = OpenSSL::X509::Certificate.new(pem)
169
+ connection.key = OpenSSL::PKey::RSA.new(pem)
170
+ connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
171
+ end
172
+
173
+ # Naughty, naughty, naughty! Don't blame when when someone hops in
174
+ # and executes a MITM attack!
175
+ unless ssl_verify
176
+ log.debug { 'Disabling SSL verification' }
177
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
178
+ end
179
+ end
180
+
181
+ # Create a connection using the block form, which will ensure the socket
182
+ # is properly closed in the event of an error.
183
+ connection.start do |http|
184
+ # Make the request
185
+ response = http.request(request)
186
+
187
+ # Log the raw response
188
+ log.debug { response.inspect }
189
+
190
+ case response
191
+ when Net::HTTPRedirection
192
+ redirect = URI.parse(response['location'])
193
+ log.info { "Performing HTTP redirect to #{redirect}" }
194
+ request(verb, redirect, params, headers)
195
+ when Net::HTTPSuccess
196
+ success(response)
197
+ else
198
+ error(response)
199
+ end
200
+ end
201
+ rescue SocketError, Errno::ECONNREFUSED, EOFError
202
+ log.fatal { 'Something really bad happened with the request' }
203
+ raise RuntimeError, 'Something really bad happened!'
204
+ end
205
+
206
+ private
207
+
208
+ #
209
+ # The function called to process a successful HTTP request.
210
+ #
211
+ # @param [Net::HTTPMessage] response
212
+ # the raw response object
213
+ #
214
+ # @return [Hash]
215
+ # the parsed response object (as JSON)
216
+ #
217
+ def success(response)
218
+ log.info { 'Parsing response as success' }
219
+
220
+ if response['Content-Type'] =~ /json/
221
+ log.debug { 'Parsing response as JSON' }
222
+ JSON.parse(response.body)
223
+ else
224
+ log.debug { 'Returning response as text/plain' }
225
+ response.body
226
+ end
227
+ end
228
+
229
+ #
230
+ # Handler for a failed response.
231
+ #
232
+ # @raise [RuntimeError]
233
+ # @param (see #success)
234
+ # @return (see #success)
235
+ #
236
+ def error(response)
237
+ log.info { 'Parsing response as error' }
238
+
239
+ if response['Content-Type'] =~ /json/
240
+ log.debug { 'Parsing response as JSON' }
241
+ errors = JSON.parse(response.body)
242
+
243
+ message = errors['errors'].map do |key, messages|
244
+ messages.map do |message|
245
+ "'#{key}' #{message}"
246
+ end
247
+ end.compact.join("\n")
248
+ else
249
+ log.debug { 'Returning response as text/plain' }
250
+ messsage = response.body
251
+ end
252
+
253
+ raise RuntimeError, message
254
+ end
255
+
256
+ #
257
+ # Construct a URL from the given verb and path. If the request is a GET or
258
+ # DELETE request, the params are assumed to be query params are are
259
+ # converted as such using {Client#to_query_string}.
260
+ #
261
+ # If the path is relative, it is merged with the {Defaults.endpoint}
262
+ # attribute. If the path is absolute, it is converted to a URI object and
263
+ # returned.
264
+ #
265
+ # @param [Symbol] verb
266
+ # the lowercase HTTP verb (e.g. :+get+)
267
+ # @param [String] path
268
+ # the absolute or relative HTTP path (url) to get
269
+ # @param [Hash] params
270
+ # the list of params to build the URI with (for GET and DELETE requests)
271
+ #
272
+ # @return [URI]
273
+ #
274
+ def build_uri(verb, path, params = {})
275
+ log.info { 'Building URI' }
276
+
277
+ # Add any query string parameters
278
+ if [:delete, :get].include?(verb)
279
+ log.debug { 'Detected verb deserves a querystring' }
280
+ log.debug { "Building querystring using #{params.inspect}" }
281
+ path = [path, to_query_string(params)].compact.join('?')
282
+ end
283
+
284
+ # Parse the URI
285
+ uri = URI.parse(path)
286
+
287
+ # Don't merge absolute URLs
288
+ unless uri.absolute?
289
+ log.debug { 'Detected URI is relative' }
290
+ log.debug { "Appending #{path} to #{endpoint}" }
291
+
292
+ uri = URI.parse(File.join(endpoint.to_s, path.to_s))
293
+
294
+ log.debug { "Final URI is #{uri.to_s}" }
295
+ end
296
+
297
+ # Return the URI object
298
+ uri
299
+ end
300
+
301
+ #
302
+ # Helper method to get the corresponding {Net::HTTP} class from the given
303
+ # HTTP verb.
304
+ #
305
+ # @param [#to_s] verb
306
+ # the HTTP verb to create a class from
307
+ #
308
+ # @return [Class]
309
+ #
310
+ def class_for_request(verb)
311
+ Net::HTTP.const_get(verb.to_s.capitalize)
312
+ end
313
+
314
+ #
315
+ # Adds the default headers to the request object. Headers are merged in the
316
+ # following order of precedence:
317
+ #
318
+ # 1. Shire's default headers:
319
+ # - Connection
320
+ # - Keep-Alive
321
+ # - User-Agent
322
+ # 2. Global default headers defined in the configuration
323
+ # 3. Custom headers passed to this request
324
+ #
325
+ # @param [Net::HTTP::Request] request
326
+ # the Net::HTTP request object
327
+ # param [Hash] headers
328
+ # the list of user-specified headers
329
+ #
330
+ def add_request_headers(request, headers = {})
331
+ log.info { 'Adding request headers' }
332
+
333
+ headers = {
334
+ 'Connection' => 'keep-alive',
335
+ 'Keep-Alive' => '30',
336
+ 'User-Agent' => user_agent,
337
+ }.merge(headers)
338
+
339
+ headers.each do |key, value|
340
+ log.debug { "#{key}: #{value}" }
341
+ request[key] = value
342
+ end
343
+ end
344
+
345
+ #
346
+ # Convert the given hash to a list of query string parameters. Each key and
347
+ # value in the hash is URI-escaped for safety.
348
+ #
349
+ # @param [Hash] hash
350
+ # the hash to create the query string from
351
+ #
352
+ # @return [String, nil]
353
+ # the query string as a string, or +nil+ if there are no params
354
+ #
355
+ def to_query_string(hash)
356
+ hash.map do |key, value|
357
+ "#{URI.escape(key.to_s)}=#{URI.escape(value.to_s)}"
358
+ end.join('&')[/.+/]
359
+ end
360
+ end
361
+ end
@@ -0,0 +1,79 @@
1
+ module VagrantCloud
2
+ module Configurable
3
+ class << self
4
+ #
5
+ # The list of configurable keys.
6
+ #
7
+ # @return [Array<Symbol>]
8
+ #
9
+ def keys
10
+ @keys ||= [
11
+ :endpoint,
12
+ :username,
13
+ :access_token,
14
+ :proxy_address,
15
+ :proxy_port,
16
+ :proxy_password,
17
+ :proxy_username,
18
+ :ssl_pem_file,
19
+ :ssl_verify,
20
+ :user_agent,
21
+ ]
22
+ end
23
+ end
24
+
25
+ #
26
+ # Create one attribute getter and setter for each key.
27
+ # @!parse include Defaults
28
+ #
29
+ Configurable.keys.each do |key|
30
+ attr_accessor key
31
+ end
32
+
33
+ #
34
+ # Set the configuration for this config, using a block.
35
+ #
36
+ # @example Configure the API endpoint
37
+ # Configurable.configure do |config|
38
+ # config.endpoint = "http://www.somewhere.com"
39
+ # config.ssl_verify = false
40
+ # end
41
+ #
42
+ # @return [self]
43
+ #
44
+ def configure
45
+ yield self
46
+ self
47
+ end
48
+
49
+ #
50
+ # Reset all configuration options to their default values.
51
+ #
52
+ # @example Reset all settings
53
+ # Configurable.reset!
54
+ #
55
+ # @return [self]
56
+ #
57
+ def reset!
58
+ Configurable.keys.each do |key|
59
+ instance_variable_set(:"@#{key}", Defaults.options[key])
60
+ end
61
+ self
62
+ end
63
+ alias_method :setup, :reset!
64
+
65
+ private
66
+
67
+ #
68
+ # The list of configurable keys, as an options hash.
69
+ #
70
+ # @return [Hash]
71
+ #
72
+ def options
73
+ map = Configurable.keys.map do |key|
74
+ [key, instance_variable_get(:"@#{key}")]
75
+ end
76
+ Hash[map]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,109 @@
1
+ module VagrantCloud
2
+ module Defaults
3
+ # Default User Agent header string
4
+ USER_AGENT = "Vagrant Cloud Ruby Gem #{VagrantCloud::VERSION}".freeze
5
+
6
+ class << self
7
+ #
8
+ # The list of calculated default options for the configuration.
9
+ #
10
+ # @return [Hash]
11
+ #
12
+ def options
13
+ Hash[Configurable.keys.map { |key| [key, send(key)] }]
14
+ end
15
+
16
+ #
17
+ # The Vagrant Cloud endpoint. For enterprise installations, this must be
18
+ # overriden. Defaults to the public Vagrant Cloud installation.
19
+ #
20
+ # @return [String]
21
+ #
22
+ def endpoint
23
+ ENV['VAGRANT_CLOUD_ENDPOINT'] || 'https://vagrantcloud.com/api/v1'
24
+ end
25
+
26
+ #
27
+ # The User Agent header to send along.
28
+ #
29
+ # @return [String]
30
+ #
31
+ def user_agent
32
+ ENV['VAGRANT_CLOUD_USER_AGENT'] || USER_AGENT
33
+ end
34
+
35
+ #
36
+ # The Vagrant Cloud username or organization to authenticate with.
37
+ #
38
+ # @return [String, nil]
39
+ #
40
+ def username
41
+ ENV['VAGRANT_CLOUD_USERNAME']
42
+ end
43
+
44
+ #
45
+ # The Vagrant Cloud access token to authenticate with.
46
+ #
47
+ # @return [String, nil]
48
+ #
49
+ def access_token
50
+ ENV['VAGRANT_CLOUD_ACCESS_TOKEN']
51
+ end
52
+
53
+ #
54
+ # The HTTP Proxy server address.
55
+ #
56
+ # @return [String, nil]
57
+ #
58
+ def proxy_address
59
+ ENV['VAGRANT_CLOUD_PROXY_ADDRESS']
60
+ end
61
+
62
+ #
63
+ # The HTTP Proxy server user's password.
64
+ #
65
+ # @return [String, nil]
66
+ #
67
+ def proxy_password
68
+ ENV['VAGRANT_CLOUD_PROXY_PASSWORD']
69
+ end
70
+
71
+ #
72
+ # The HTTP Proxy server port.
73
+ #
74
+ # @return [String, nil]
75
+ #
76
+ def proxy_port
77
+ ENV['VAGRANT_CLOUD_PROXY_PORT']
78
+ end
79
+
80
+ #
81
+ # The HTTP Proxy server username.
82
+ #
83
+ # @return [String, nil]
84
+ #
85
+ def proxy_username
86
+ ENV['VAGRANT_CLOUD_PROXY_USERNAME']
87
+ end
88
+
89
+ #
90
+ # The path to a pem file on disk for use with a custom SSL verification
91
+ #
92
+ # @return [String, nil]
93
+ #
94
+ def ssl_pem_file
95
+ ENV['VAGRANT_CLOUD_SSL_PEM_FILE']
96
+ end
97
+
98
+ #
99
+ # Verify SSL requests (default: true)
100
+ #
101
+ # @return [true, false]
102
+ #
103
+ def ssl_verify
104
+ key = ENV.fetch('VAGRANT_CLOUD_SSL_VERIFY', 'yes').downcase[0]
105
+ %w[1 t y].include?(key)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,183 @@
1
+ module VagrantCloud
2
+ class Resource::Base
3
+ include Logify
4
+
5
+ class << self
6
+ #
7
+ # @macro attribute
8
+ # @method $1
9
+ # Return this object's +$1+
10
+ #
11
+ # @return [Object]
12
+ #
13
+ #
14
+ # @method $1=(value)
15
+ # Set this object's +$1+
16
+ #
17
+ # @param [Object] value
18
+ # the value to set for +$1+
19
+ # @param [Object] default
20
+ # the default value for this attribute
21
+ #
22
+ # @method $1?
23
+ # Determines if the +$1+ value exists and is truthy
24
+ #
25
+ # @return [Boolean]
26
+ #
27
+ def attribute(key, default = nil)
28
+ key = key.to_sym unless key.is_a?(Symbol)
29
+
30
+ # Set this attribute in the top-level hash
31
+ attributes[key] = nil
32
+
33
+ define_method(key) do
34
+ value = attributes[key]
35
+ return value unless value.nil?
36
+
37
+ if default.nil?
38
+ value
39
+ elsif default.is_a?(Proc)
40
+ default.call
41
+ else
42
+ default
43
+ end
44
+ end
45
+
46
+ define_method("#{key}?") do
47
+ !!attributes[key]
48
+ end
49
+
50
+ define_method("#{key}=") do |value|
51
+ set(key, value)
52
+ end
53
+ end
54
+
55
+ #
56
+ # The list of attributes defined by this class.
57
+ #
58
+ # @return [Array<Symbol>]
59
+ #
60
+ def attributes
61
+ @attributes ||= {}
62
+ end
63
+
64
+ #
65
+ # Determine if this class has a given attribute.
66
+ #
67
+ # @param [#to_sym] key
68
+ # the key to check as an attribute
69
+ #
70
+ # @return [true, false]
71
+ #
72
+ def has_attribute?(key)
73
+ attributes.has_key?(key.to_sym)
74
+ end
75
+
76
+ #
77
+ # Construct a new object from the hash.
78
+ #
79
+ # @param [Hash] hash
80
+ # the hash to create the object with
81
+ # @param [Hash] options
82
+ # the list options
83
+ #
84
+ # @return [~Resource::Base]
85
+ #
86
+ def from_hash(hash)
87
+ new.tap do |instance|
88
+ hash.each do |key, value|
89
+ method = :"#{key}="
90
+ if instance.respond_to?(method)
91
+ log.debug { "Setting #{method}: #{value}" }
92
+ instance.send(method, value)
93
+ else
94
+ log.debug { "Does not respond to #{method}, skipping" }
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ #
102
+ # Create a new instance
103
+ #
104
+ def initialize(attributes = {})
105
+ attributes.each do |key, value|
106
+ set(key, value)
107
+ end
108
+ end
109
+
110
+ #
111
+ # The list of attributes for this resource.
112
+ #
113
+ # @return [hash]
114
+ #
115
+ def attributes
116
+ @attributes ||= self.class.attributes.dup
117
+ end
118
+
119
+ #
120
+ # Set a given attribute on this resource.
121
+ #
122
+ # @param [#to_sym] key
123
+ # the attribute to set
124
+ # @param [Object] value
125
+ # the value to set
126
+ #
127
+ # @return [Object]
128
+ # the set value
129
+ #
130
+ def set(key, value)
131
+ attributes[key.to_sym] = value
132
+ end
133
+
134
+ #
135
+ # The hash representation
136
+ #
137
+ # @example An example hash response
138
+ # { 'key' => 'local-repo1', 'includesPattern' => '**/*' }
139
+ #
140
+ # @return [Hash]
141
+ #
142
+ def to_hash
143
+ attributes.inject({}) do |hash, (key, value)|
144
+ unless Resource::Base.has_attribute?(key)
145
+ hash[key] = value
146
+ end
147
+
148
+ hash
149
+ end
150
+ end
151
+
152
+ #
153
+ # The JSON representation of this object.
154
+ #
155
+ # @return [String]
156
+ #
157
+ def to_json
158
+ JSON.fast_generate(to_hash)
159
+ end
160
+
161
+ # @private
162
+ def to_s
163
+ "#<#{short_classname}>"
164
+ end
165
+
166
+ # @private
167
+ def inspect
168
+ list = attributes.collect do |key, value|
169
+ unless Resource::Base.has_attribute?(key)
170
+ "#{key}: #{value.inspect}"
171
+ end
172
+ end.compact
173
+
174
+ "#<#{short_classname} #{list.join(', ')}>"
175
+ end
176
+
177
+ private
178
+
179
+ def short_classname
180
+ @short_classname ||= self.class.name.split('::').last
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,51 @@
1
+ module VagrantCloud
2
+ class Resource::Box < Resource::Base
3
+ class << self
4
+ #
5
+ #
6
+ #
7
+ def find(name)
8
+ response = client.get("/box/#{box(name)}")
9
+ new(response)
10
+ end
11
+
12
+ #
13
+ #
14
+ #
15
+ def destroy(name)
16
+ client.delete("/box/#{box(name)}")
17
+ end
18
+
19
+ private
20
+
21
+ #
22
+ #
23
+ #
24
+ def box(name)
25
+ if name =~ /\//
26
+ name
27
+ else
28
+ "#{client.username}/#{name}"
29
+ end
30
+ end
31
+
32
+ def client
33
+ VagrantCloud.client
34
+ end
35
+ end
36
+
37
+ attribute :name
38
+ attribute :tag
39
+ attribute :short_description
40
+ attribute :description_html
41
+ attribute :description_markdown
42
+ attribute :private
43
+
44
+ # TODO make these smarter
45
+ attribute :current_version
46
+ attribute :versions
47
+
48
+ attribute :created_at
49
+ attribute :updated_at
50
+ end
51
+ end
@@ -0,0 +1,8 @@
1
+ module VagrantCloud
2
+ #
3
+ # The version of VagrantCloud.
4
+ #
5
+ # @return [String]
6
+ #
7
+ VERSION = '0.0.1'.freeze
8
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'vagrant-cloud/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'vagrant-cloud'
8
+ spec.version = VagrantCloud::VERSION
9
+ spec.authors = ['Seth Vargo']
10
+ spec.email = ['sethvargo@gmail.com']
11
+ spec.summary = 'A Ruby gem for interacting with the Vagrant Cloud API'
12
+ spec.description = 'A Ruby gem for interacting with the Vagrant Cloud API'
13
+ spec.homepage = 'https://github.com/sethvargo/vagrant-cloud'
14
+ spec.license = 'Apache 2.0'
15
+
16
+ spec.required_ruby_version = '>= 1.9.3'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0")
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_dependency 'logify', '~> 0.1'
24
+ spec.add_dependency 'clamp'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.5'
27
+ spec.add_development_dependency 'rake'
28
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-cloud
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Seth Vargo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logify
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: clamp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A Ruby gem for interacting with the Vagrant Cloud API
70
+ email:
71
+ - sethvargo@gmail.com
72
+ executables:
73
+ - vagrant-cloud
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - bin/vagrant-cloud
83
+ - lib/vagrant-cloud.rb
84
+ - lib/vagrant-cloud/cli.rb
85
+ - lib/vagrant-cloud/cli/box.rb
86
+ - lib/vagrant-cloud/client.rb
87
+ - lib/vagrant-cloud/configurable.rb
88
+ - lib/vagrant-cloud/defaults.rb
89
+ - lib/vagrant-cloud/resources/base.rb
90
+ - lib/vagrant-cloud/resources/box.rb
91
+ - lib/vagrant-cloud/version.rb
92
+ - vagrant-cloud.gemspec
93
+ homepage: https://github.com/sethvargo/vagrant-cloud
94
+ licenses:
95
+ - Apache 2.0
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 1.9.3
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.2.2
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: A Ruby gem for interacting with the Vagrant Cloud API
117
+ test_files: []
118
+ has_rdoc: