From 2f77a9be718cdb23a358c25d1d9538cdcb3fc52d Mon Sep 17 00:00:00 2001 From: Adam Avilla Date: Mon, 8 Aug 2016 09:45:59 -0700 Subject: [PATCH 1/6] First pass at swarm interface. --- lib/docker.rb | 1 + lib/docker/swarm.rb | 56 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 lib/docker/swarm.rb diff --git a/lib/docker.rb b/lib/docker.rb index 6d72bb679..21d252779 100644 --- a/lib/docker.rb +++ b/lib/docker.rb @@ -29,6 +29,7 @@ module Docker require 'docker/image' require 'docker/messages_stack' require 'docker/messages' + require 'docker/swarm' require 'docker/util' require 'docker/version' require 'docker/volume' diff --git a/lib/docker/swarm.rb b/lib/docker/swarm.rb new file mode 100644 index 000000000..babdf2be6 --- /dev/null +++ b/lib/docker/swarm.rb @@ -0,0 +1,56 @@ +class Docker::Swarm + + RESOURCE_BASE='/swarm' + + def self.init opts = {} + new(opts).init + end + + def self.join opts = {} + new(opts).join + end + + def self.leave opts = {} + new(opts).leave + end + + def self.update opts = {} + new(opts).update + end + + attr_accessor :connection, :options, :info + + def initialize opts + @connection = Docker.connection + @options = opts + end + + def init + @info = Docker::Util.parse_json( + @connection.post "#{RESOURCE_BASE}/init", {}, @options.to_json + ) + self + end + + def join + @info = Docker::Util.parse_json( + @connection.post "#{RESOURCE_BASE}/join", {}, @options.to_json + ) + self + end + + def leave + @info = Docker::Util.parse_json( + @connection.post "#{RESOURCE_BASE}/leave", {}, @options.to_json + ) + self + end + + def update + @info = Docker::Util.parse_json( + @connection.post "#{RESOURCE_BASE}/update", {}, @options.to_json + ) + self + end + +end From d10a8cbb04abccb785746adc81bbcda94e14345d Mon Sep 17 00:00:00 2001 From: Adam Avilla Date: Tue, 9 Aug 2016 08:58:49 -0700 Subject: [PATCH 2/6] Adding basic docs, extract method and making it actually work. --- lib/docker/swarm.rb | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/docker/swarm.rb b/lib/docker/swarm.rb index babdf2be6..19c257282 100644 --- a/lib/docker/swarm.rb +++ b/lib/docker/swarm.rb @@ -1,19 +1,24 @@ +# Class to interface with Docker 1.12 /swarm 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 opts = {} new(opts).init end + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/join-an-existing-swarm def self.join opts = {} new(opts).join end + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/leave-a-swarm def self.leave opts = {} new(opts).leave end + # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/update-a-swarm def self.update opts = {} new(opts).update end @@ -26,29 +31,29 @@ def initialize opts end def init - @info = Docker::Util.parse_json( - @connection.post "#{RESOURCE_BASE}/init", {}, @options.to_json - ) - self + call 'init' end def join - @info = Docker::Util.parse_json( - @connection.post "#{RESOURCE_BASE}/join", {}, @options.to_json - ) - self + call 'join' end def leave - @info = Docker::Util.parse_json( - @connection.post "#{RESOURCE_BASE}/leave", {}, @options.to_json - ) - self + call 'leave' end def update + call 'update' + end + + private + + def call endpoint @info = Docker::Util.parse_json( - @connection.post "#{RESOURCE_BASE}/update", {}, @options.to_json + @connection.post "#{RESOURCE_BASE}/#{endpoint}", + {}, + body: @options.to_json, + headers: {'Content-Type' => 'application/json'} ) self end From 43220189ed126b449a24eaf59470df338e3c463e Mon Sep 17 00:00:00 2001 From: Adam Avilla Date: Tue, 9 Aug 2016 08:59:21 -0700 Subject: [PATCH 3/6] Adding 1.12 to travis. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) 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 From a2af1154fb8c126b8b55b97ac61787490d37d6a0 Mon Sep 17 00:00:00 2001 From: Adam Avilla Date: Tue, 9 Aug 2016 09:00:28 -0700 Subject: [PATCH 4/6] Allowing auto config of version, and setup for 1.12. --- spec/spec_helper.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 From 8674026d05c712e0901cc7da1bd57cb13a98c58b Mon Sep 17 00:00:00 2001 From: Adam Avilla Date: Wed, 10 Aug 2016 12:25:37 -0700 Subject: [PATCH 5/6] Continuing to work on this and get existing test suite passing. --- lib/docker/swarm.rb | 25 +++++++++++++++++++++---- spec/docker/image_spec.rb | 19 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/lib/docker/swarm.rb b/lib/docker/swarm.rb index 19c257282..4de07a6ce 100644 --- a/lib/docker/swarm.rb +++ b/lib/docker/swarm.rb @@ -39,7 +39,18 @@ def join end def leave - call 'leave' + # This is undocumented on docker's side and opened: + # + # https://github.com/docker/docker/issues/25543 + # + # to address this. I am assuming the interface will be JSON in the body of + # {"Force":true}. If they end up going with this, then we can delete the + # whole `query` business. + query = { + force: @options.delete('Force') == true, + } + + call 'leave', query end def update @@ -48,14 +59,20 @@ def update private - def call endpoint + def call endpoint, query = {} @info = Docker::Util.parse_json( @connection.post "#{RESOURCE_BASE}/#{endpoint}", - {}, + query, body: @options.to_json, - headers: {'Content-Type' => 'application/json'} + headers: headers ) self end + def headers + { + 'Content-Type' => 'application/json', + } + 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 From c89b7096902e2059a300a039299e8804bd35cc82 Mon Sep 17 00:00:00 2001 From: Adam Avilla Date: Fri, 12 Aug 2016 10:23:35 -0700 Subject: [PATCH 6/6] Finalizing first pass at 1.12 integration. --- lib/docker.rb | 3 +++ lib/docker/connection.rb | 2 +- lib/docker/node.rb | 22 ++++++++++++++++++ lib/docker/service.rb | 48 ++++++++++++++++++++++++++++++++++++++++ lib/docker/swarm.rb | 44 ++++++++++++++---------------------- lib/docker/task.rb | 22 ++++++++++++++++++ 6 files changed, 113 insertions(+), 28 deletions(-) create mode 100644 lib/docker/node.rb create mode 100644 lib/docker/service.rb create mode 100644 lib/docker/task.rb diff --git a/lib/docker.rb b/lib/docker.rb index 21d252779..03aabf880 100644 --- a/lib/docker.rb +++ b/lib/docker.rb @@ -29,7 +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 index 4de07a6ce..a17c7e58d 100644 --- a/lib/docker/swarm.rb +++ b/lib/docker/swarm.rb @@ -1,33 +1,34 @@ -# Class to interface with Docker 1.12 /swarm endpoints. +# 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 opts = {} - new(opts).init + 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 opts = {} - new(opts).join + 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 opts = {} - new(opts).leave + 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 opts = {} - new(opts).update + def self.update body = {}, query = {} + new(body, query).update end - attr_accessor :connection, :options, :info + attr_accessor :connection, :body, :info, :query - def initialize opts + def initialize body = {}, query = {} @connection = Docker.connection - @options = opts + @body = body + @query = query end def init @@ -39,18 +40,7 @@ def join end def leave - # This is undocumented on docker's side and opened: - # - # https://github.com/docker/docker/issues/25543 - # - # to address this. I am assuming the interface will be JSON in the body of - # {"Force":true}. If they end up going with this, then we can delete the - # whole `query` business. - query = { - force: @options.delete('Force') == true, - } - - call 'leave', query + call 'leave' end def update @@ -59,11 +49,11 @@ def update private - def call endpoint, query = {} + def call endpoint @info = Docker::Util.parse_json( @connection.post "#{RESOURCE_BASE}/#{endpoint}", - query, - body: @options.to_json, + @query, + body: @body.to_json, headers: headers ) self 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 +