diff --git a/.travis.yml b/.travis.yml index 891beb206..11e6a914d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ rvm: - 2.0 - 1.9.3 env: + - DOCKER_VERSION=1.12.0 - DOCKER_VERSION=1.11.1 - DOCKER_VERSION=1.10.3 - DOCKER_VERSION=1.9.1 diff --git a/lib/docker.rb b/lib/docker.rb index 6d72bb679..03aabf880 100644 --- a/lib/docker.rb +++ b/lib/docker.rb @@ -29,6 +29,10 @@ module Docker require 'docker/image' require 'docker/messages_stack' require 'docker/messages' + require 'docker/node' + require 'docker/service' + require 'docker/swarm' + require 'docker/task' require 'docker/util' require 'docker/version' require 'docker/volume' diff --git a/lib/docker/connection.rb b/lib/docker/connection.rb index b99a3ed89..00a8e6831 100644 --- a/lib/docker/connection.rb +++ b/lib/docker/connection.rb @@ -38,7 +38,7 @@ def request(*args, &block) request = compile_request_params(*args, &block) log_request(request) resource.request(request).body - rescue Excon::Errors::BadRequest => ex + rescue Excon::Errors::BadRequest, Excon::Error::NotAcceptable => ex raise ClientError, ex.response.body rescue Excon::Errors::Unauthorized => ex raise UnauthorizedError, ex.response.body diff --git a/lib/docker/node.rb b/lib/docker/node.rb new file mode 100644 index 000000000..467c47ad5 --- /dev/null +++ b/lib/docker/node.rb @@ -0,0 +1,22 @@ +# Class to interface with Docker 1.12 #{RESOURCE_BASE} endpoints. +class Docker::Node + include Docker::Base + private_class_method :new + + RESOURCE_BASE = '/nodes' + + # Return the node with specified ID + def self.get(id, opts = {}, conn = Docker.connection) + json = conn.get("#{RESOURCE_BASE}/#{URI.encode(id)}", opts) + hash = Docker::Util.parse_json(json) || {} + new(conn, hash) + end + + # Return all of the nodes. + def self.all(opts = {}, conn = Docker.connection) + hashes = Docker::Util.parse_json(conn.get("#{RESOURCE_BASE}", opts)) || [] + hashes.map { |hash| new(conn, hash) } + end + +end + diff --git a/lib/docker/service.rb b/lib/docker/service.rb new file mode 100644 index 000000000..df763b843 --- /dev/null +++ b/lib/docker/service.rb @@ -0,0 +1,48 @@ +# Class to interface with Docker 1.12 #{RESOURCE_BASE} endpoints. +class Docker::Service + include Docker::Base + private_class_method :new + + RESOURCE_BASE = '/services' + + # Create a new service. + def self.create(opts = {}, conn = Docker.connection) + name = opts.delete('name') + query = {} + query['name'] = name if name + resp = conn.post("#{RESOURCE_BASE}/create", query, :body => opts.to_json) + hash = Docker::Util.parse_json(resp) || {} + new(conn, hash) + end + + # Return the service with specified ID + def self.get(id, opts = {}, conn = Docker.connection) + services_json = conn.get("#{RESOURCE_BASE}/#{URI.encode(id)}", opts) + hash = Docker::Util.parse_json(services_json) || {} + new(conn, hash) + end + + # Return all of the services. + def self.all(opts = {}, conn = Docker.connection) + hashes = Docker::Util.parse_json(conn.get("#{RESOURCE_BASE}", opts)) || [] + hashes.map { |hash| new(conn, hash) } + end + + # remove service + def remove(opts = {}) + connection.delete("#{RESOURCE_BASE}/#{self.id}", opts) + nil + end + alias_method :delete, :remove + + def update(query, opts) + connection.post(path_for(:update), query, body: opts.to_json) + end + + # Convenience method to return the path for a particular resource. + def path_for(resource) + "#{RESOURCE_BASE}/#{self.id}/#{resource}" + end + private :path_for +end + diff --git a/lib/docker/swarm.rb b/lib/docker/swarm.rb new file mode 100644 index 000000000..a17c7e58d --- /dev/null +++ b/lib/docker/swarm.rb @@ -0,0 +1,68 @@ +# Class to interface with Docker 1.12 #{RESOURCE_BASE} endpoints. +class Docker::Swarm + + RESOURCE_BASE='/swarm' + + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/initialize-a-new-swarm + def self.init body = {}, query = {} + new(body, query).init + end + + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/join-an-existing-swarm + def self.join body = {}, query = {} + new(body, query).join + end + + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/leave-a-swarm + def self.leave body = {}, query = {} + new(body, query).leave + end + + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/update-a-swarm + def self.update body = {}, query = {} + new(body, query).update + end + + attr_accessor :connection, :body, :info, :query + + def initialize body = {}, query = {} + @connection = Docker.connection + @body = body + @query = query + end + + def init + call 'init' + end + + def join + call 'join' + end + + def leave + call 'leave' + end + + def update + call 'update' + end + + private + + def call endpoint + @info = Docker::Util.parse_json( + @connection.post "#{RESOURCE_BASE}/#{endpoint}", + @query, + body: @body.to_json, + headers: headers + ) + self + end + + def headers + { + 'Content-Type' => 'application/json', + } + end + +end diff --git a/lib/docker/task.rb b/lib/docker/task.rb new file mode 100644 index 000000000..2ce3af2a7 --- /dev/null +++ b/lib/docker/task.rb @@ -0,0 +1,22 @@ +# Class to interface with Docker 1.12 #{RESOURCE_BASE} endpoints. +class Docker::Task + include Docker::Base + private_class_method :new + + RESOURCE_BASE = '/tasks' + + # Return the task with specified ID + def self.get(id, opts = {}, conn = Docker.connection) + json = conn.get("#{RESOURCE_BASE}/#{URI.encode(id)}", opts) + hash = Docker::Util.parse_json(json) || {} + new(conn, hash) + end + + # Return all of the tasks. + def self.all(opts = {}, conn = Docker.connection) + hashes = Docker::Util.parse_json(conn.get("#{RESOURCE_BASE}", opts)) || [] + hashes.map { |hash| new(conn, hash) } + end + +end + diff --git a/spec/docker/image_spec.rb b/spec/docker/image_spec.rb index ecdb11606..89b4a48fb 100644 --- a/spec/docker/image_spec.rb +++ b/spec/docker/image_spec.rb @@ -259,10 +259,21 @@ context 'when cmd is nil' do let(:cmd) { nil } context 'no command configured in image' do - subject { described_class.create('fromImage' => 'swipely/base') } - it 'should raise an error if no command is specified' do - expect {container}.to raise_error(Docker::Error::ServerError, - "No command specified.") + + describe '(< docker 1.12 will be a ServerError)', :pre_1_12 do + subject { described_class.create('fromImage' => 'swipely/base') } + it 'should raise an error if no command is specified' do + expect {container}.to raise_error(Docker::Error::ServerError, + "No command specified.") + end + end + + describe '(>= docker 1.12 will be a ClientError)', :docker_1_12 do + subject { described_class.create('fromImage' => 'swipely/base') } + it 'should raise an error if no command is specified' do + expect {container}.to raise_error(Docker::Error::ClientError, + /No command specified/) + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f5f969284..280ca615d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,19 +23,27 @@ def project_dir config.formatter = :documentation config.tty = true - case ENV['DOCKER_VERSION'] + version = ENV['DOCKER_VERSION'] || Docker.version['Version'] + + case version when /1\.6/ config.filter_run_excluding :docker_1_8 => true config.filter_run_excluding :docker_1_9 => true config.filter_run_excluding :docker_1_10 => true + config.filter_run_excluding :docker_1_12 => true when /1\.7/ config.filter_run_excluding :docker_1_8 => true config.filter_run_excluding :docker_1_9 => true config.filter_run_excluding :docker_1_10 => true + config.filter_run_excluding :docker_1_12 => true when /1\.8/ config.filter_run_excluding :docker_1_9 => true config.filter_run_excluding :docker_1_10 => true + config.filter_run_excluding :docker_1_12 => true when /1\.9/ config.filter_run_excluding :docker_1_10 => true + config.filter_run_excluding :docker_1_12 => true + when /1\.12/ + config.filter_run_excluding :pre_1_12 => true end end