hyperkit 1.0.0 → 1.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4c987dcdd24e8fd164c8c1aa0ba7d6424413afbf
4
- data.tar.gz: f1cb3dccfbe526352a95b237e3f02e09371df577
3
+ metadata.gz: 31c1d9b05f7078a592ef2e0fcfaee2715cd16484
4
+ data.tar.gz: 12e2f1eabed87b1691627e1c1a884eaaaaedc94f
5
5
  SHA512:
6
- metadata.gz: 21984f55160a78b75d64971d67ea3ff45d8d5017ff3f34c7b985a265c34961844f0717680fa0c7e3061a67328cbb3c68bc65219c15e2f73687b0328c0e38acd0
7
- data.tar.gz: 37e780a30ca13a612637de881d91d8ea86ca83c8fb9e1f89faff770313458bbfee028c9c382dd6036241e7ea524ebec8641baa565f5380a347ed1b4bc1e59f70
6
+ metadata.gz: 7608210cba76349bf7530ef5ea62e05be8b4e4e4f17654e0bbf772cc38554a0d7caf69297b483eccdba2bf0a61a8747996ca535b1d40584efa8456621e9edf25
7
+ data.tar.gz: a38a4bd0fb00a4368d8d2919472e3e3378123cc358fe9dd3f58ffe70f8b6b4a7bd95b06a543e574ebdf6baa5997abf20cbd617da4dc88b98ab4575662aa6a78f
@@ -1,4 +1,22 @@
1
1
  language: ruby
2
+
2
3
  rvm:
4
+ - jruby
5
+ - rbx-2
3
6
  - 1.9.3
4
- before_install: gem install bundler -v 1.11.2
7
+ - 2.0
8
+ - 2.1
9
+ - 2.2
10
+
11
+ bundler_args: --without development
12
+
13
+ sudo: false
14
+
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: jruby
18
+ - rvm: rbx-2
19
+ - rvm: 1.9.3
20
+
21
+ notifications:
22
+ emails: true
data/Gemfile CHANGED
@@ -1,19 +1,20 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'rake'
4
+
3
5
  group :development do
4
6
  gem 'awesome_print', :require => 'ap'
5
7
  gem 'guard-rspec', '~> 4.6'
6
8
  gem 'hirb-unicode'
7
9
  gem 'pry'
8
10
  gem 'yard'
9
- gem 'rake'
10
11
  end
11
12
 
12
13
  group :test do
13
14
  gem 'coveralls', :require => false
14
15
  gem 'multi_json', '~> 1.11.0'
15
16
  gem 'rb-fsevent', '~> 0.9'
16
- gem 'rspec', '~> 3.0.0'
17
+ gem 'rspec', '~> 3.0'
17
18
  gem 'simplecov', :require => false
18
19
  gem 'vcr', '~> 3.0'
19
20
  gem 'webmock', '~> 1.24.2'
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Hyperkit
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/hyperkit.svg)](https://badge.fury.io/rb/hyperkit) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/jeffshantz/hyperkit/master) [![Build Status](https://travis-ci.org/jeffshantz/hyperkit.svg?branch=master)](https://travis-ci.org/jeffshantz/hyperkit) [![Coverage Status](https://coveralls.io/repos/github/jeffshantz/hyperkit/badge.svg?branch=master)](https://coveralls.io/github/jeffshantz/hyperkit?branch=master)
4
+
3
5
  Hyperkit is a flat API wrapper for LXD, the next-generation hypervisor. It is
4
6
  shamelessly based on the design of Octokit, the popular wrapper for the GitHub
5
7
  API.
@@ -51,17 +53,17 @@ lxd.create_snapshot("test-container", "test-snapshot")
51
53
  # container.
52
54
  lxd2 = Hyperkit::Client.new(api_endpoint: "https://lxd2.example.com")
53
55
  source = lxd2.init_migration("remote-container")
54
- lxd.migrate_container(source, "migrated-container")
56
+ lxd.migrate(source, "migrated-container")
55
57
  ```
56
58
 
57
59
  Each method in the API documentation has at least one example of its usage. Please see the documentation for the following modules:
58
60
 
59
- * [Certificates]()
60
- * [Containers]()
61
- * [Images]()
62
- * [Networks]()
63
- * [Operations]()
64
- * [Profiles]()
61
+ * [Certificates](http://www.rubydoc.info/github/jeffshantz/hyperkit/master/Hyperkit/Client/Certificates)
62
+ * [Containers](http://www.rubydoc.info/github/jeffshantz/hyperkit/master/Hyperkit/Client/Containers)
63
+ * [Images](http://www.rubydoc.info/github/jeffshantz/hyperkit/master/Hyperkit/Client/Images)
64
+ * [Networks](http://www.rubydoc.info/github/jeffshantz/hyperkit/master/Hyperkit/Client/Networks)
65
+ * [Operations](http://www.rubydoc.info/github/jeffshantz/hyperkit/master/Hyperkit/Client/Operations)
66
+ * [Profiles](http://www.rubydoc.info/github/jeffshantz/hyperkit/master/Hyperkit/Client/Profiles)
65
67
 
66
68
  ## Requirements
67
69
 
@@ -98,7 +100,7 @@ client = Octokit::Client.new(api_endpoint: 'https://lxd.example.com:8443', verif
98
100
  client.create_container("test-container", alias: "ubuntu/trusty/amd64")
99
101
  ```
100
102
 
101
- [API methods]: http://TODO
103
+ [API methods]: http://www.rubydoc.info/list/github/jeffshantz/hyperkit/master/method
102
104
 
103
105
  ## Authentication
104
106
 
@@ -160,13 +162,13 @@ Alternatively, you can simply copy your certificate file to the LXD server and
160
162
  use the `lxc` tool to trust it:
161
163
 
162
164
  ```
163
- lxd-server$ lxc config trust add my-new-cert.crt
165
+ $ lxc config trust add my-new-cert.crt
164
166
  ```
165
167
 
166
168
  ## API coverage
167
169
 
168
170
  Hyperkit supports the entirety of [version 1.0 of the LXD
169
- API](https://github.com/lxc/lxd/blob/master/specs/rest-api.md), but does not
171
+ API](https://github.com/lxc/lxd/blob/master/doc/rest-api.md), but does not
170
172
  support any of the Websocket API calls (e.g. `/1.0/events`).
171
173
 
172
174
  ## Asynchronous Operations
@@ -296,6 +298,8 @@ implementation, you will be responsible for providing patches in a timely
296
298
  fashion. If critical issues for a particular implementation exist at the time
297
299
  of a major release, support for that Ruby version may be dropped.
298
300
 
301
+ [travis]: https://travis-ci.org/jeffshantz/hyperkit
302
+
299
303
  ## Versioning
300
304
 
301
305
  This library aims to adhere to [Semantic Versioning 2.0.0][semver]. Violations
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["hyperkit@jeffshantz.com"]
11
11
 
12
12
  spec.summary = %q{Hyperkit is a flat API wrapper for LXD, the next-generation hypervisor}
13
- spec.homepage = "https://github.com/jeffshantz/hyperkit"
13
+ spec.homepage = "http://jeffshantz.github.io/hyperkit"
14
14
  spec.license = "MIT"
15
15
 
16
16
  # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
@@ -28,6 +28,6 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.add_dependency "activesupport", "~> 4.2.6"
30
30
  spec.add_dependency "sawyer"
31
- spec.add_development_dependency "bundler", "~> 1.11"
31
+ spec.add_development_dependency "bundler", "~> 1.0"
32
32
 
33
33
  end
@@ -25,7 +25,7 @@ require 'hyperkit/default'
25
25
 
26
26
  # Ruby toolkit for the LXD API.
27
27
  # LXD - the next-generation container hypervisor for Linux
28
- module Hyperkit
28
+ module Hyperkit
29
29
 
30
30
  class << self
31
31
  include Hyperkit::Configurable
@@ -22,6 +22,7 @@
22
22
 
23
23
  require 'hyperkit/configurable'
24
24
  require 'hyperkit/connection'
25
+ require 'hyperkit/utility'
25
26
  require 'hyperkit/client/certificates'
26
27
  require 'hyperkit/client/containers'
27
28
  require 'hyperkit/client/images'
@@ -42,6 +43,7 @@ module Hyperkit
42
43
 
43
44
  include Hyperkit::Configurable
44
45
  include Hyperkit::Connection
46
+ include Hyperkit::Utility
45
47
  include Hyperkit::Client::Certificates
46
48
  include Hyperkit::Client::Containers
47
49
  include Hyperkit::Client::Images
@@ -6,7 +6,7 @@ module Hyperkit
6
6
  class Client
7
7
 
8
8
  # Methods for the certificates API
9
- #
9
+ #
10
10
  # @see Hyperkit::Client
11
11
  # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
12
12
  module Certificates
@@ -20,7 +20,7 @@ module Hyperkit
20
20
  # "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
21
21
  # "b7720e1eb839056158cf65d182865491a0403f766983b95f5098d05911bbff89"
22
22
  # ]
23
- def certificates
23
+ def certificates
24
24
  response = get(certificates_path)
25
25
  response.metadata.map { |path| path.split('/').last }
26
26
  end
@@ -30,7 +30,7 @@ module Hyperkit
30
30
  # @param cert [String] Certificate contents in PEM format
31
31
  # @param options [Hash] Additional data to be passed
32
32
  # @option options [String] :name Optional name for the certificate. If not specified, the host in the TLS header for the request is used.
33
- # @option options [String] :password The trust password for that server. Only required if untrusted.
33
+ # @option options [String] :password The trust password for that server. Only required if untrusted.
34
34
  # @return [Sawyer::Resource]
35
35
  #
36
36
  # @example Add trusted certificate
@@ -48,22 +48,20 @@ module Hyperkit
48
48
  #
49
49
  # @param fingerprint [String] Fingerprint of the certificate to retrieve. Can be a prefix, as long as it is unambigous
50
50
  # @return [Sawyer::Resource] Certificate information
51
- #
51
+ #
52
52
  # @example Retrieve a certificate
53
53
  # Hyperkit.certificate("c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82") #=> {
54
- # :certificate => "-----BEGIN CERTIFICATE-----\nMIIEW...ceyg04=\n-----END CERTIFICATE-----\n",
55
- # :fingerprint => "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
56
- # :type => "client"
54
+ # :certificate => "-----BEGIN CERTIFICATE-----\nMIIEW...ceyg04=\n-----END CERTIFICATE-----\n",
55
+ # :fingerprint => "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
56
+ # :type => "client"
57
57
  # }
58
58
  #
59
59
  # @example Retrieve a certificate by specifying a prefix of its fingerprint
60
60
  # Hyperkit.certificate("c7") #=> {
61
- # :certificate => "-----BEGIN CERTIFICATE-----\nMIIEW...ceyg04=\n-----END CERTIFICATE-----\n",
62
- # :fingerprint => "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
63
- # :type => "client"
61
+ # :certificate => "-----BEGIN CERTIFICATE-----\nMIIEW...ceyg04=\n-----END CERTIFICATE-----\n",
62
+ # :fingerprint => "c782c0f3530a04a5b2b78fc5292b7500aef1299370288b5eeb0450a6613a2c82",
63
+ # :type => "client"
64
64
  # }
65
- #
66
- # @todo Write tests for the prefix
67
65
  def certificate(fingerprint)
68
66
  get(certificate_path(fingerprint)).metadata
69
67
  end
@@ -78,8 +76,6 @@ module Hyperkit
78
76
  #
79
77
  # @example Delete a certificate by specifying a prefix of its fingerprint
80
78
  # Hyperkit.delete_certificate("c7")
81
- #
82
- # @todo Write tests for the prefix
83
79
  def delete_certificate(fingerprint)
84
80
  delete(certificate_path(fingerprint)).metadata
85
81
  end
@@ -6,7 +6,7 @@ module Hyperkit
6
6
  class Client
7
7
 
8
8
  # Methods for the containers API
9
- #
9
+ #
10
10
  # @see Hyperkit::Client
11
11
  # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
12
12
  module Containers
@@ -198,7 +198,7 @@ module Hyperkit
198
198
 
199
199
  response = post(containers_path, opts).metadata
200
200
  handle_async(response, options[:sync])
201
-
201
+
202
202
  end
203
203
 
204
204
  # @!endgroup
@@ -233,7 +233,7 @@ module Hyperkit
233
233
  # container = Hyperkit.container("test-container")
234
234
  # container.ephemeral = true
235
235
  # Hyperkit.update_container("test-container", container)
236
- #
236
+ #
237
237
  # @example Change container's AppArmor profile to 'unconfined'.
238
238
  # container = Hyperkit.container("test-container")
239
239
  #
@@ -248,11 +248,7 @@ module Hyperkit
248
248
  def update_container(name, config, options={})
249
249
 
250
250
  config = config.to_hash
251
-
252
- # Stringify values in the config hash, since LXD chokes on non-String values
253
- if config[:config]
254
- config[:config] = config[:config].inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
255
- end
251
+ config[:config] = stringify_hash(config[:config]) if config[:config]
256
252
 
257
253
  response = put(container_path(name), config).metadata
258
254
  handle_async(response, options[:sync])
@@ -271,7 +267,7 @@ module Hyperkit
271
267
  # @example Rename container "test" to "test2"
272
268
  # Hyperkit.rename_container("test", "test2")
273
269
  def rename_container(old_name, new_name, options={})
274
- response = post(container_path(old_name), { "name": new_name }).metadata
270
+ response = post(container_path(old_name), { name: new_name }).metadata
275
271
  handle_async(response, options[:sync])
276
272
  end
277
273
 
@@ -294,7 +290,7 @@ module Hyperkit
294
290
  # Hyperkit.execute_command("test-container", "echo 'hello world'")
295
291
  #
296
292
  # @example Run a command (passed as an array) in container "test-container"
297
- # Hyperkit.execute_command("test-container",
293
+ # Hyperkit.execute_command("test-container",
298
294
  # ["bash", "-c", "echo 'hello world' > /tmp/test.txt"]
299
295
  # )
300
296
  #
@@ -311,10 +307,7 @@ module Hyperkit
311
307
  opts = options.slice(:environment)
312
308
  command = Shellwords.shellsplit(command) if command.is_a?(String)
313
309
 
314
- # Stringify any environment values since LXD croaks on non-String values
315
- if opts[:environment]
316
- opts[:environment] = opts[:environment].inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
317
- end
310
+ opts[:environment] = stringify_hash(opts[:environment]) if opts[:environment]
318
311
 
319
312
  response = post(File.join(container_path(container), "exec"), {
320
313
  command: command,
@@ -504,7 +497,7 @@ module Hyperkit
504
497
  # Prepare to migrate a container or snapshot. Generates source data to be passed to {#migrate}.
505
498
  #
506
499
  # Note that CRIU must be installed on the server to migrate a running container, or LXD will
507
- # return a 500 error. On Ubuntu, you can install it with
500
+ # return a 500 error. On Ubuntu, you can install it with
508
501
  # <code>sudo apt-get install criu</code>.
509
502
  #
510
503
  # @param name [String] Container name
@@ -562,7 +555,7 @@ module Hyperkit
562
555
  source = container(container)
563
556
  end
564
557
 
565
- response = post(url, { "migration": true })
558
+ response = post(url, { migration: true })
566
559
  agent = response.agent
567
560
 
568
561
  source_data = {
@@ -583,7 +576,7 @@ module Hyperkit
583
576
  # Migrate a remote container or snapshot to the server
584
577
  #
585
578
  # Note that CRIU must be installed on the server to migrate a running container, or LXD will
586
- # return a 500 error. On Ubuntu, you can install it with
579
+ # return a 500 error. On Ubuntu, you can install it with
587
580
  # <code>sudo apt-get install criu</code>.
588
581
  #
589
582
  # Also note that, unless overridden with the <code>profiles</code> parameter, if the source
@@ -641,7 +634,7 @@ module Hyperkit
641
634
  opts["base-image"] = source.config["volatile.base_image"]
642
635
  opts[:config] = options[:config] || source.config.to_hash
643
636
 
644
- # If we're only copying the container, and configuration was explicitly
637
+ # If we're only copying the container, and configuration was explicitly
645
638
  # overridden, then remove the volatile entries
646
639
  if ! options[:move] && ! options.has_key?(:config)
647
640
  opts[:config].delete_if { |k,v| k.to_s.start_with?("volatile") }
@@ -662,7 +655,7 @@ module Hyperkit
662
655
  else
663
656
  raise Hyperkit::MissingProfiles.new("Not all profiles applied to source container exist on the target LXD instance")
664
657
  end
665
-
658
+
666
659
  end
667
660
 
668
661
  if options.has_key?(:ephemeral)
@@ -735,11 +728,11 @@ module Hyperkit
735
728
 
736
729
  # Create a snapshot of a container
737
730
  #
738
- # If <code>stateful: true</code> is passed when creating a snapshot of a
731
+ # If <code>stateful: true</code> is passed when creating a snapshot of a
739
732
  # running container, the container's runtime state will be stored in the
740
- # snapshot. Note that CRIU must be installed on the server to create a
733
+ # snapshot. Note that CRIU must be installed on the server to create a
741
734
  # stateful snapshot, or LXD will return a 500 error. On Ubuntu, you can
742
- # install it with
735
+ # install it with
743
736
  # <code>sudo apt-get install criu</code>.
744
737
  #
745
738
  # @async This method is asynchronous. See {Hyperkit::Configurable#auto_sync} for more information.
@@ -795,7 +788,7 @@ module Hyperkit
795
788
  # @example Rename snapshot "test/snap1" to "snap2"
796
789
  # Hyperkit.rename_snapshot("test", "snap1", "snap2")
797
790
  def rename_snapshot(container, old_name, new_name, options={})
798
- response = post(snapshot_path(container, old_name), { "name": new_name }).metadata
791
+ response = post(snapshot_path(container, old_name), { name: new_name }).metadata
799
792
  handle_async(response, options[:sync])
800
793
  end
801
794
 
@@ -812,7 +805,7 @@ module Hyperkit
812
805
  # @example Restore container "test" back to snapshot "snap1"
813
806
  # Hyperkit.restore_snapshot("test", "snap1")
814
807
  def restore_snapshot(container, snapshot, options={})
815
- response = put(container_path(container), { "restore": snapshot }).metadata
808
+ response = put(container_path(container), { restore: snapshot }).metadata
816
809
  handle_async(response, options[:sync])
817
810
  end
818
811
 
@@ -844,21 +837,21 @@ module Hyperkit
844
837
  #
845
838
  # @example Copy /etc/passwd in container "test" to the local file /tmp/passwd
846
839
  # Hyperkit.pull_file("test", "/etc/passwd", "/tmp/passwd") #=> "/tmp/passwd"
847
- def pull_file(container, source_file, dest_file)
848
- contents = get(file_path(container, source_file), url_encode: false)
849
- headers = last_response.headers
840
+ def pull_file(container, source_file, dest_file)
841
+ contents = get(file_path(container, source_file), url_encode: false)
842
+ headers = last_response.headers
850
843
 
851
- File.open(dest_file, "wb") do |f|
852
- f.write(contents)
853
- end
844
+ File.open(dest_file, "wb") do |f|
845
+ f.write(contents)
846
+ end
854
847
 
855
- if headers["x-lxd-mode"]
856
- File.chmod(headers["x-lxd-mode"].to_i(8), dest_file)
857
- end
848
+ if headers["x-lxd-mode"]
849
+ File.chmod(headers["x-lxd-mode"].to_i(8), dest_file)
850
+ end
858
851
 
859
- dest_file
852
+ dest_file
860
853
 
861
- end
854
+ end
862
855
 
863
856
  # Write to a file in a container
864
857
  #
@@ -896,14 +889,14 @@ module Hyperkit
896
889
  headers["X-LXD-gid"] = options[:gid].to_s if options[:gid]
897
890
  headers["X-LXD-mode"] = options[:mode].to_s(8).rjust(4, "0") if options[:mode]
898
891
 
899
- if ! block_given?
900
- content = options[:content].to_s
901
- else
902
- io = StringIO.new
903
- yield io
904
- io.rewind
905
- content = io.read
906
- end
892
+ if ! block_given?
893
+ content = options[:content].to_s
894
+ else
895
+ io = StringIO.new
896
+ yield io
897
+ io.rewind
898
+ content = io.read
899
+ end
907
900
 
908
901
  post(file_path(container, dest_file), {
909
902
  raw_body: content,
@@ -949,7 +942,7 @@ module Hyperkit
949
942
  # Retrieve a list of logs for a container
950
943
  #
951
944
  # @param container [String] Container name
952
- # @return [Array<String>] An array of log filenames
945
+ # @return [Array<String>] An array of log filenames
953
946
  #
954
947
  # @example Get list of logs for container "test-container"
955
948
  # Hyperkit.logs("test-container")
@@ -1022,11 +1015,8 @@ module Hyperkit
1022
1015
  def extract_container_options(name, options)
1023
1016
  opts = options.slice(:architecture, :profiles, :ephemeral, :config).
1024
1017
  merge({ name: name })
1025
-
1026
- # Stringify any config values since LXD croaks on non-String values
1027
- if opts[:config]
1028
- opts[:config] = opts[:config].inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
1029
- end
1018
+
1019
+ opts[:config] = stringify_hash(opts[:config]) if opts[:config]
1030
1020
 
1031
1021
  opts
1032
1022
  end
@@ -1092,7 +1082,7 @@ module Hyperkit
1092
1082
  opts
1093
1083
 
1094
1084
  end
1095
-
1085
+
1096
1086
  end
1097
1087
 
1098
1088
  end
@@ -3,7 +3,7 @@ module Hyperkit
3
3
  class Client
4
4
 
5
5
  # Methods for the images API
6
- #
6
+ #
7
7
  # @see Hyperkit::Client
8
8
  # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
9
9
  module Images
@@ -14,10 +14,10 @@ module Hyperkit
14
14
  #
15
15
  # @return [Array<String>] An array of image fingerprints
16
16
  #
17
- # @example Get list of images
17
+ # @example Get list of images
18
18
  # Hyperkit.images #=> ["54c8caac1f61901ed86c68f24af5f5d3672bdc62c71d04f06df3a59e95684473",
19
19
  # "97d97a3d1d053840ca19c86cdd0596cf1be060c5157d31407f2a4f9f350c78cc"]
20
- def images
20
+ def images
21
21
  response = get(images_path)
22
22
  response.metadata.map { |path| path.split('/').last }
23
23
  end
@@ -171,15 +171,15 @@ module Hyperkit
171
171
  # @return [Sawyer::Resource] Operation or result, depending value of <code>:sync</code> parameter and/or {Hyperkit::Client::auto_sync}
172
172
  #
173
173
  # @example Import image by alias
174
- # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
174
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
175
175
  # alias: "ubuntu/xenial/amd64")
176
- #
176
+ #
177
177
  # @example Import image by fingerprint
178
- # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
178
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
179
179
  # fingerprint: "b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b")
180
180
  #
181
181
  # @example Import image and automatically update it when it is updated on the remote server
182
- # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
182
+ # Hyperkit.create_image_from_remote("https://images.linuxcontainers.org:8443",
183
183
  # alias: "ubuntu/xenial/amd64",
184
184
  # auto_update: true)
185
185
  #
@@ -200,7 +200,7 @@ module Hyperkit
200
200
 
201
201
  opts[:source] = options.slice(:secret, :protocol, :certificate)
202
202
  opts[:source].merge!({
203
- type: "image",
203
+ type: "image",
204
204
  mode: "pull",
205
205
  server: server
206
206
  })
@@ -209,7 +209,7 @@ module Hyperkit
209
209
  raise Hyperkit::ImageIdentifierRequired.new("Please specify either :alias or :fingerprint")
210
210
  end
211
211
 
212
- opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
212
+ opts[:properties] = stringify_hash(options[:properties]) if options[:properties]
213
213
 
214
214
  if options[:alias]
215
215
  opts[:source][:alias] = options[:alias]
@@ -218,12 +218,12 @@ module Hyperkit
218
218
  end
219
219
 
220
220
  response = post(images_path, opts).metadata
221
- handle_async(response, options[:sync])
221
+ handle_async(response, options[:sync])
222
222
  end
223
223
 
224
224
  # Import an image from a remote URL.
225
225
  #
226
- # Note: the URL passed to this method is <b>not</b> the URL of a tarball.
226
+ # Note: the URL passed to this method is <b>not</b> the URL of a tarball.
227
227
  # Instead, the URL must return the following headers:
228
228
  #
229
229
  # * <code>LXD-Image-URL</code> - URL of the image tarball
@@ -277,11 +277,11 @@ module Hyperkit
277
277
  def create_image_from_url(url, options={})
278
278
 
279
279
  opts = options.slice(:filename, :public)
280
- opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
281
- opts[:source] = {
282
- type: "url",
283
- url: url
284
- }
280
+ opts[:properties] = stringify_hash(options[:properties]) if options[:properties]
281
+ opts[:source] = {
282
+ type: "url",
283
+ url: url
284
+ }
285
285
 
286
286
  response = post(images_path, opts).metadata
287
287
  handle_async(response, options[:sync])
@@ -317,14 +317,14 @@ module Hyperkit
317
317
  def create_image_from_container(name, options={})
318
318
 
319
319
  opts = options.slice(:filename, :public, :description)
320
- opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
321
- opts[:source] = {
322
- type: "container",
323
- name: name
324
- }
320
+ opts[:properties] = stringify_hash(options[:properties]) if options[:properties]
321
+ opts[:source] = {
322
+ type: "container",
323
+ name: name
324
+ }
325
325
 
326
326
  response = post(images_path, opts).metadata
327
- handle_async(response, options[:sync])
327
+ handle_async(response, options[:sync])
328
328
  end
329
329
 
330
330
  # Create an image from an existing snapshot.
@@ -358,14 +358,14 @@ module Hyperkit
358
358
  def create_image_from_snapshot(container, snapshot, options={})
359
359
 
360
360
  opts = options.slice(:filename, :public, :description)
361
- opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
362
- opts[:source] = {
363
- type: "snapshot",
364
- name: "#{container}/#{snapshot}"
365
- }
361
+ opts[:properties] = stringify_hash(options[:properties]) if options[:properties]
362
+ opts[:source] = {
363
+ type: "snapshot",
364
+ name: "#{container}/#{snapshot}"
365
+ }
366
366
 
367
367
  response = post(images_path, opts).metadata
368
- handle_async(response, options[:sync])
368
+ handle_async(response, options[:sync])
369
369
  end
370
370
 
371
371
  # Delete an image
@@ -385,7 +385,7 @@ module Hyperkit
385
385
  # Hyperkit.delete_image("b41")
386
386
  def delete_image(fingerprint, options={})
387
387
  response = delete(image_path(fingerprint)).metadata
388
- handle_async(response, options[:sync])
388
+ handle_async(response, options[:sync])
389
389
  end
390
390
 
391
391
  # @!endgroup
@@ -404,7 +404,7 @@ module Hyperkit
404
404
  # @example Set image to be publicly-accessible
405
405
  # Hyperkit.update_image("b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b",
406
406
  # public: true)
407
- #
407
+ #
408
408
  # @example Overwrite image properties (removes all existing properties, sets "hello" property to "world")
409
409
  # Hyperkit.update_image("b1cf3d836196c316897d39872ff25e2d912ea933207b0c591334a67b290a5f1b",
410
410
  # properties: {
@@ -424,15 +424,15 @@ module Hyperkit
424
424
  # )
425
425
  def update_image(fingerprint, options={})
426
426
  opts = options.slice(:public, :auto_update)
427
- opts[:properties] = stringify_properties(options[:properties]) if options[:properties]
427
+ opts[:properties] = stringify_hash(options[:properties]) if options[:properties]
428
428
 
429
429
  put(image_path(fingerprint), opts).metadata
430
430
  end
431
-
432
- # Generate a secret for an image that can be used by an untrusted client
431
+
432
+ # Generate a secret for an image that can be used by an untrusted client
433
433
  # to retrieve information on and/or export a private image.
434
434
  #
435
- # The secret is automatically invalidated 5 seconds after first using it
435
+ # The secret is automatically invalidated 5 seconds after first using it
436
436
  # (e.g. after calling Hyperkit.image(fingerprint, secret: "...").
437
437
  # This allows one to both retrieve the image information and then export it
438
438
  # with the same secret.
@@ -443,7 +443,6 @@ module Hyperkit
443
443
  #
444
444
  # @param fingerprint [String] Fingerprint of the image. This can be a prefix of an image's fingerprint, as long as it is unambiguous.
445
445
  # @return [Sawyer::Response] An asynchronous response containing the generated secret
446
- # @todo Add test for fingerprint prefix
447
446
  #
448
447
  # @example Generate a secret for an image
449
448
  # response = Hyperkit.create_image_secret("878cf0c70e14fec80aaf4d5e923670e68c45aa89fb05a481019bf086aec42649") #=> {
@@ -498,7 +497,7 @@ module Hyperkit
498
497
  #
499
498
  # @example Export private image via secret (created by {#create_image_secret})
500
499
  # image = Hyperkit.image_by_alias("busybox/default/amd64")
501
- # Hyperkit.export_image(image.fingerprint,
500
+ # Hyperkit.export_image(image.fingerprint,
502
501
  # "/tmp", secret: "secret-issued-by-create_image_secret") => "/tmp/busybox-v1.21.1-lxc.tar.xz"
503
502
  def export_image(fingerprint, output_dir, options={})
504
503
 
@@ -541,7 +540,7 @@ module Hyperkit
541
540
  # "ubuntu/xenial/ppc64el",
542
541
  # "ubuntu/xenial/s390x/default",
543
542
  # "ubuntu/xenial/s390x"
544
- # ]
543
+ # ]
545
544
  def image_aliases
546
545
  response = get(image_aliases_path)
547
546
  response.metadata.map { |path| path.sub("#{image_aliases_path}/","") }
@@ -561,7 +560,7 @@ module Hyperkit
561
560
  def image_alias(alias_name)
562
561
  get(image_alias_path(alias_name)).metadata
563
562
  end
564
-
563
+
565
564
  # Assign an alias for an image
566
565
  #
567
566
  # @param fingerprint [String] Fingerprint of the image
@@ -659,12 +658,6 @@ module Hyperkit
659
658
  "/1.0/images"
660
659
  end
661
660
 
662
- # Stringify any property values. LXD returns an error if
663
- # integers are passed, for example
664
- def stringify_properties(properties)
665
- properties.inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
666
- end
667
-
668
661
  end
669
662
 
670
663
  end
@@ -3,7 +3,7 @@ module Hyperkit
3
3
  class Client
4
4
 
5
5
  # Methods for the networks API
6
- #
6
+ #
7
7
  # @see Hyperkit::Client
8
8
  # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
9
9
  module Networks
@@ -38,7 +38,7 @@ module Hyperkit
38
38
  def networks_path
39
39
  "/1.0/networks"
40
40
  end
41
-
41
+
42
42
  end
43
43
 
44
44
  end
@@ -5,7 +5,7 @@ module Hyperkit
5
5
  class Client
6
6
 
7
7
  # Methods for the operations API
8
- #
8
+ #
9
9
  # @see Hyperkit::Client
10
10
  # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
11
11
  module Operations
@@ -14,7 +14,7 @@ module Hyperkit
14
14
  #
15
15
  # This will include operations that are currently executing, as well as
16
16
  # operations that are paused until {#wait_for_operation} is called, at
17
- # which time they will begin executing.
17
+ # which time they will begin executing.
18
18
  #
19
19
  # Additionally, since LXD keeps completed operations around for 5 seconds,
20
20
  # the list returned may include recently completed operations.
@@ -40,7 +40,7 @@ module Hyperkit
40
40
  # :created_at => 2016-04-14 21:30:59 UTC,
41
41
  # :updated_at => 2016-04-14 21:30:59 UTC,
42
42
  # :status => "Running",
43
- # :status_code => 103,
43
+ # :status_code => 103,
44
44
  # :resources => {
45
45
  # :containers => ["/1.0/containers/test-container"]
46
46
  # },
@@ -54,13 +54,13 @@ module Hyperkit
54
54
 
55
55
  # Cancel a running operation
56
56
  #
57
- # Calling this will change the state of the operation to
57
+ # Calling this will change the state of the operation to
58
58
  # <code>cancelling</code>. Note that the operation must be cancelable,
59
- # which can be ascertained by calling {#operation} and checking the
59
+ # which can be ascertained by calling {#operation} and checking the
60
60
  # <code>may_cancel</code> property.
61
61
  #
62
62
  # @param [String] uuid UUID of the operation
63
- # @return [Sawyer::Resource]
63
+ # @return [Sawyer::Resource]
64
64
  #
65
65
  # @example Cancel an operation
66
66
  # Hyperkit.cancel_operation("8b3dd0c2-9dad-4964-b00d-e21481a47fb8") => {}
@@ -70,10 +70,15 @@ module Hyperkit
70
70
 
71
71
  # Wait for an asynchronous operation to complete
72
72
  #
73
- # Note that this is only needed if {#Hyperkit::auto_sync} has been
73
+ # Note that this is only needed if {Hyperkit::Configurable#auto_sync} has been
74
74
  # set to <code>false</code>, or if the option <code>sync: false</code>
75
75
  # has been passed to an asynchronous method.
76
76
  #
77
+ # Note that, after an operation completes, LXD keeps it around for only 5
78
+ # seconds, so if you wait too long to call
79
+ # <code>wait_for_operation</code>, you'll get an exception when you
80
+ # eventually do call it.
81
+ #
77
82
  # @param [String] uuid UUID of the operation
78
83
  # @param [Fixnum] timeout Maximum time to wait (default: indefinite)
79
84
  # @return [Sawyer::Resource] Operation result
@@ -99,7 +104,7 @@ module Hyperkit
99
104
 
100
105
  sync = sync.nil? ? auto_sync : sync
101
106
 
102
- if sync
107
+ if sync
103
108
  wait_for_operation(response.id)
104
109
  else
105
110
  response
@@ -111,10 +116,10 @@ module Hyperkit
111
116
  File.join(operations_path, uuid)
112
117
  end
113
118
 
114
- def operations_path
119
+ def operations_path
115
120
  "/1.0/operations"
116
121
  end
117
-
122
+
118
123
  end
119
124
 
120
125
  end
@@ -5,39 +5,112 @@ module Hyperkit
5
5
  class Client
6
6
 
7
7
  # Methods for the profiles API
8
- #
8
+ #
9
9
  # @see Hyperkit::Client
10
10
  # @see https://github.com/lxc/lxd/blob/master/specs/rest-api.md
11
11
  module Profiles
12
12
 
13
- # GET /profiles
13
+ # List of profiles on the server
14
+ #
15
+ # @return [Array<String>] An array of profile names
16
+ #
17
+ # @example Get list of profiles
18
+ # Hyperkit.profiles #=> ["default", "docker"]
14
19
  def profiles
15
20
  response = get(profiles_path)
16
21
  response.metadata.map { |path| path.split('/').last }
17
22
  end
18
23
 
19
- # POST /profiles
24
+ # Create a profile
25
+ #
26
+ # @param name [String] Profile name
27
+ # @param options [Hash] Additional data to be passed
28
+ # @option options [Hash] :config Profile configuration
29
+ # @option options [String] :description Profile description
30
+ # @option options [Hash] :devices Profile devices
31
+ # @return [Sawyer::Resource]
32
+ #
33
+ # @example Create profile with config
34
+ # Hyperkit.create_profile("test-profile", config: {
35
+ # "limits.memory" => "2GB",
36
+ # "limits.cpu" => 2,
37
+ # "raw.lxc" => "lxc.aa_profile = unconfined"
38
+ # })
39
+ #
40
+ # @example Create profile with devices
41
+ # Hyperkit.create_profile("test-profile", devices: {
42
+ # eth0: {
43
+ # nictype: "bridged",
44
+ # parent: "br-ext",
45
+ # type: "nic"
46
+ # }
47
+ # })
20
48
  def create_profile(name, options={})
21
- options = options.merge(name: name)
22
- post(profiles_path, options).metadata
49
+ opts = options.merge(name: name)
50
+ opts[:config] = stringify_hash(opts[:config]) if opts[:config]
51
+ post(profiles_path, opts).metadata
23
52
  end
24
53
 
25
- # GET /profiles/<name>
54
+ # Retrieve a profile
55
+ #
56
+ # @param name [String] Profile name
57
+ # @return [Sawyer::Resource] Profile
58
+ #
59
+ # @example Retrieve profile 'test-profile'
60
+ # Hyperkit.profile("test-profile")
26
61
  def profile(name)
27
62
  get(profile_path(name)).metadata
28
63
  end
29
64
 
30
- # PUT /profiles/<name>
65
+ # Update an existing profile
66
+ #
67
+ # @param name [String] Profile name
68
+ # @param options [Hash] Additional data to be passed
69
+ # @option options [Hash] :config Profile configuration. Existing configuration will be overwritten.
70
+ # @option options [String] :description Profile description
71
+ # @option options [Hash] :devices Profile devices. Existing devices will be overwritten.
72
+ # @return [Sawyer::Resource]
73
+ #
74
+ # @example Update profile with config (config is overwritten -- not merged)
75
+ # Hyperkit.update_profile("test-profile", config: {
76
+ # "limits.memory" => "4GB",
77
+ # "limits.cpu" => 4,
78
+ # "raw.lxc" => "lxc.aa_profile = unconfined"
79
+ # })
80
+ #
81
+ # @example Create profile with devices (devices are overwritten -- not merged)
82
+ # Hyperkit.create_profile("test-profile", devices: {
83
+ # eth0: {
84
+ # nictype: "bridged",
85
+ # parent: "br-int",
86
+ # type: "nic"
87
+ # }
88
+ # })
31
89
  def update_profile(name, options={})
32
- put(profile_path(name), options.except(:name)).metadata
90
+ opts = options.except(:name)
91
+ opts[:config] = stringify_hash(opts[:config]) if opts[:config]
92
+ put(profile_path(name), opts).metadata
33
93
  end
34
94
 
35
- # POST /profiles/<name>
95
+ # Rename a profile
96
+ #
97
+ # @param old_name [String] Existing profile name
98
+ # @param new_name [String] New profile name
99
+ # @return [Sawyer::Resource]
100
+ #
101
+ # @example Rename profile 'test' to 'test2'
102
+ # Hyperkit.rename_profile("test", "test2")
36
103
  def rename_profile(old_name, new_name)
37
104
  post(profile_path(old_name), { name: new_name }).metadata
38
105
  end
39
106
 
40
- # DELETE /profiles/<name>
107
+ # Delete a profile
108
+ #
109
+ # @param name [String] Profile name
110
+ # @return [Sawyer::Resource]
111
+ #
112
+ # @example Delete profile 'test-profile'
113
+ # Hyperkit.delete_profile("test-profile")
41
114
  def delete_profile(name)
42
115
  delete(profile_path(name)).metadata
43
116
  end
@@ -47,7 +120,7 @@ module Hyperkit
47
120
  def profiles_path
48
121
  "/1.0/profiles"
49
122
  end
50
-
123
+
51
124
  def profile_path(name)
52
125
  File.join(profiles_path, name)
53
126
  end
@@ -29,10 +29,54 @@ module Hyperkit
29
29
  # @!attribute api_endpoint
30
30
  # @return [String] the base URL for API requests (default: <code>https://localhost:8443/</code>)
31
31
  # @!attribute auto_sync
32
+ # Whether to automatically wait for asynchronous operations to complete
33
+ #
34
+ # A good deal of the LXD API calls are asynchronous: you issue the call,
35
+ # and you receive an operation ID. You must then wait on the operation
36
+ # to complete. Each asynchronous method is marked as such in the Hyperkit
37
+ # documentation.
38
+ #
39
+ # <b>By default, Hyperkit provides auto-synchronization</b>. When you
40
+ # initiate an asynchronous operation, Hyperkit will automatically wait for
41
+ # the operation to complete before returning. If you wish to override
42
+ # this functionality, there are two ways to do this:
43
+ #
44
+ # * Pass <code>sync: false</code> to any of the asynchronous methods
45
+ # * Set <code>auto_sync</code> to <code>false</code> at the module or
46
+ # class level (see examples)
47
+ #
48
+ # Any asynchronous calls you issue after setting <code>auto_sync</code>
49
+ # to <code>false</code> will immediately return an operation ID instead of
50
+ # blocking. To ensure that an operation is complete, you will need to
51
+ # call {Hyperkit::Client::Operations#wait_for_operation}.
52
+ #
53
+ # Most users will likely want to keep <code>auto_sync</code> enabled for
54
+ # convenience.
55
+ #
56
+ # @example Create a container and automatically wait for it to complete (auto_sync is <code>true</code> by default)
57
+ # Hyperkit.create_container("test-container", alias: "ubuntu/trusty/amd64")
58
+ #
59
+ # @example Disable auto-synchronization at the module level
60
+ # Hyperkit.auto_sync = false
61
+ # op = Hyperkit.create_container("test-container", alias: "ubuntu/trusty/amd64")
62
+ # Hyperkit.wait_for_operation(op.id)
63
+ #
64
+ # @example Disable auto-synchronization at the class level
65
+ # client = Hyperkit::Client.new(auto_sync: false)
66
+ # op = client.create_container("test-container", alias: "ubuntu/trusty/amd64")
67
+ # client.wait_for_operation(op.id)
68
+ #
69
+ # @example Disable auto-synchronization, but enable it for one call by passing <code>sync: true</code>
70
+ # Hyperkit.auto_sync = false
71
+ # Hyperkit.create_container("test-container", alias: "ubuntu/trusty/amd64", sync: true)
72
+ # @example Enable auto-synchronization, but disable it for one call by passing <code>sync: false</code>
73
+ # Hyperkit.auto_sync = true
74
+ # op = Hyperkit.create_container("test-container", alias: "ubuntu/trusty/amd64", sync: false)
75
+ # Hyperkit.wait_for_operation(op.id)
32
76
  # @return [String] whether to automatically wait on asynchronous events (default: <code>true</code>)
33
77
  # @!attribute client_cert
34
78
  # @return [String] the client certificate used to authenticate to the LXD server
35
- # @!attribute client_key
79
+ # @!attribute client_key
36
80
  # @return [String] the client key used to authenticate to the LXD server
37
81
  # @!attribute default_media_type
38
82
  # @return [String] the preferred media type (for API versioning, for example)
@@ -166,9 +166,9 @@ module Hyperkit
166
166
  if client_cert && File.exist?(client_cert)
167
167
  conn_opts[:ssl][:client_cert] = OpenSSL::X509::Certificate.new(File.read(client_cert))
168
168
  end
169
-
169
+
170
170
  if client_key && File.exist?(client_key)
171
- conn_opts[:ssl][:client_key] = OpenSSL::PKey::RSA.new(File.read(client_key))
171
+ conn_opts[:ssl][:client_key] = OpenSSL::PKey::RSA.new(File.read(client_key))
172
172
  end
173
173
 
174
174
  opts[:faraday] = Faraday.new(conn_opts)
@@ -46,7 +46,7 @@ module Hyperkit
46
46
  # In Faraday 0.9, Faraday::Builder was renamed to Faraday::RackBuilder
47
47
  RACK_BUILDER_CLASS = defined?(Faraday::RackBuilder) ? Faraday::RackBuilder : Faraday::Builder
48
48
 
49
- # Default Faraday middleware stack
49
+ # Default Faraday middleware stack
50
50
  MIDDLEWARE = RACK_BUILDER_CLASS.new do |builder|
51
51
  builder.use Hyperkit::Middleware::FollowRedirects
52
52
  builder.use Hyperkit::Response::RaiseError
@@ -43,7 +43,7 @@ module Hyperkit
43
43
  err
44
44
 
45
45
  end
46
-
46
+
47
47
  def self.from_async_operation(response)
48
48
 
49
49
  return nil if response.nil? || response[:body].empty?
@@ -106,15 +106,15 @@ module Hyperkit
106
106
  end
107
107
 
108
108
  def self.error_for_500(response)
109
-
109
+
110
110
  if response.body =~ /open: no such file or directory/i
111
111
  Hyperkit::NotFound
112
112
  elsif response.body =~ /open: is a directory/i
113
113
  Hyperkit::BadRequest
114
- else
114
+ else
115
115
  Hyperkit::InternalServerError
116
116
  end
117
-
117
+
118
118
  end
119
119
 
120
120
  private
@@ -146,7 +146,7 @@ module Hyperkit
146
146
 
147
147
  def response_error
148
148
  err = nil
149
-
149
+
150
150
  if data.is_a?(Hash) && data[:error]
151
151
  err = data[:error]
152
152
  elsif data.is_a?(Hash) && data[:metadata]
@@ -247,7 +247,7 @@ module Hyperkit
247
247
  # none is provided
248
248
  class ImageIdentifierRequired < StandardError; end
249
249
 
250
- # Raised when a method requires attributes of an alias to be
250
+ # Raised when a method requires attributes of an alias to be
251
251
  # passed (e.g. description, traget), but none is provided
252
252
  class AliasAttributesRequired < StandardError; end
253
253
 
@@ -2,14 +2,14 @@ require 'faraday'
2
2
  require 'set'
3
3
 
4
4
  module Hyperkit
5
-
5
+
6
6
  module Middleware
7
7
 
8
8
  # Public: Exception thrown when the maximum amount of requests is exceeded.
9
9
  #
10
10
  # Taken from Octokit, which was originally adapted from
11
11
  # https://github.com/lostisland/faraday_middleware/blob/138766e/lib/faraday_middleware/response/follow_redirects.rb
12
-
12
+
13
13
  class RedirectLimitReached < Faraday::Error::ClientError
14
14
  attr_reader :response
15
15
 
@@ -0,0 +1,22 @@
1
+ module Hyperkit
2
+
3
+ # Utility methods for Hyperkit
4
+ module Utility
5
+
6
+ private
7
+
8
+ # Stringify the keys and values of a hash
9
+ #
10
+ # LXD often chokes on non-String JSON values. This method simply
11
+ # takes a Hash and stringifies its keys and values. The result
12
+ # can then be converted to JSON and passed to LXD.
13
+ #
14
+ # @param input [Hash] Original Hash
15
+ # @return A copy of the Hash, with its keys and values stringified
16
+ def stringify_hash(input)
17
+ input.inject({}){|h,(k,v)| h[k.to_s] = v.to_s; h}
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Hyperkit
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyperkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Shantz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-15 00:00:00.000000000 Z
11
+ date: 2016-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '1.11'
47
+ version: '1.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '1.11'
54
+ version: '1.0'
55
55
  description:
56
56
  email:
57
57
  - hyperkit@jeffshantz.com
@@ -87,8 +87,9 @@ files:
87
87
  - lib/hyperkit/error.rb
88
88
  - lib/hyperkit/middleware/follow_redirects.rb
89
89
  - lib/hyperkit/response/raise_error.rb
90
+ - lib/hyperkit/utility.rb
90
91
  - lib/hyperkit/version.rb
91
- homepage: https://github.com/jeffshantz/hyperkit
92
+ homepage: http://jeffshantz.github.io/hyperkit
92
93
  licenses:
93
94
  - MIT
94
95
  metadata: {}