diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87f108d..6f716f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,41 +13,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: 3.3 - - name: Install Docker - run: curl https://get.docker.com | sh - name: Install dependencies - run: | - bin/setup - sudo bin/setup - - name: Test misc - run: rspec spec/misc/*.rb - - name: Test Image - run: rspec spec/endpoints/image_spec.rb:1 - - name: Test Image authentication - run: rspec spec/endpoints/image_spec.rb:196 - continue-on-error: true - - name: Test Container - run: rspec spec/endpoints/container_spec.rb - - name: Test Volume - run: rspec spec/endpoints/volume_spec.rb - - name: Test Network - run: rspec spec/endpoints/network_spec.rb - - name: Test System - run: rspec spec/endpoints/system_spec.rb - - name: Test Exec - run: rspec spec/endpoints/exec_spec.rb - - name: Test Swarm - run: rspec spec/endpoints/swarm_spec.rb - - name: Test Node - run: rspec spec/endpoints/node_spec.rb - - name: Test Service - run: rspec spec/endpoints/service_spec.rb - - name: Test Task - run: rspec spec/endpoints/task_spec.rb - - name: Test Secret - run: rspec spec/endpoints/secret_spec.rb - - name: Test Config - run: rspec spec/endpoints/config_spec.rb - - name: Test Plugin - run: rspec spec/endpoints/plugin_spec.rb - \ No newline at end of file + run: bundle install + - name: Run unit tests + run: rspec diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 56c1c17..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,234 +0,0 @@ -# 0.18.0 - -Method `Docker::API::Image#distribution` now accepts authentication parameters. Feature introduced in [PR#3](https://github.com/nu12/dockerapi/pull/3) - -Contributors: @zarqman - -# 0.17.0 - -New block execution introduced in [PR#1](https://github.com/nu12/dockerapi/pull/1). - -Contributors: @zarqman - -# 0.16.0 - -`Docker::API::Task#logs` method can now receive a block to replace standard output to stdout behavior. - -Add `auth_encoder` to provide standard implementation for the authentication header where needed. - -# 0.15.0 - -`Docker::API::System#events` and `Docker::API::Exec#start` methods can now receive a block to replace standard output to stdout behavior. - -General refactoring and API documentation. - -# 0.14.0 - -Method `Docker::API::Container#archive` is splitted in `#get_archive` and `#put_archive` as per Docker API documentation. - -The following `Docker::API::Container` methods that can now receive a block: -* logs (output to stdout) -* attach (output to stdout) -* stats (output to stdout) -* export (write file) -* get_archive (write file) - -# 0.13.0 - -Add default behavior for file read, write and output to stdout. Whenever a method can receive a block, this default behavior can be replaced. - -The following `Docker::API::Image` methods that can now receive a block: -* export (write file) -* create (output to stdout) -* build (output to stdout) - -Default output to stdout can be supressed by setting `Docker::API::PRINT_TO_STDOUT` to `false` - -Method parameters `params` and `body` will be automatically evaluated whenever they are present in the method's signature. - -# 0.12.0 - -Add `Docker::API::Plugin` methods: -* list -* privileges -* install -* details -* remove -* enable -* disable -* upgrade -* create -* push -* configure - -# 0.11.0 - -Add `Docker::API::Task` methods: -* list -* details -* logs - -Add `Docker::API::Secret` methods: -* create -* update -* list -* details -* delete - -Add `Docker::API::Config` methods: -* create -* update -* list -* details -* delete - -Add `Docker::API::Image` methods: -* distribution - -# 0.10.0 - -Add `Docker::API::Service` methods: -* create -* update -* list -* details -* logs -* delete - -# 0.9.0 - -Significant change: `#inspect` is now deprecated and replaced by `#details` in the following classes: -* `Docker::API::Container` -* `Docker::API::Image` -* `Docker::API::Network` -* `Docker::API::Volume` -* `Docker::API::Exec` -* `Docker::API::Swarm` -* `Docker::API::Node` - -The method will be removed in the refactoring phase. - -# 0.8.1 - -Restore the default `#inspect` output for `Docker::API` classes. - -Most of the overriding methods take an argument, therefore calling using the expect arguments will return a `Docker::API::Response` object, while calling without arguments will return `Kernel#inspect`. To avoid this confusing schema, next release will rename `#inspect` within `Docker::API` to something else. - -# 0.8.0 - -Add `Docker::API::Swarm` methods: -* init -* update -* ~~inspect~~ details -* unlock_key -* unlock -* join -* leave - -Add `Docker::API::Node` methods: -* list -* ~~inspect~~ details -* update -* delete - -Query parameters and request body json can now skip the validation step with `:skip_validation => true` option. - -# 0.7.0 - -Significant changes: `Docker::API::Connection` is now a regular class intead of a Singleton, allowing multiple connections to be stablished within the same program (replacing the connect_to implementation). To leverage this feature, API-related classes must be initialized and may or may not receive a `Docker::API::Connection` as parameter, or it'll connect to `/var/run/docker.sock` by default. For this reason, class methods were replaced with instance methods. Documentation will reflect this changes of implementation. - -Bug fix: Image push returns a 20X status even when the push is unsucessful. To prevent false positives, it now requires the authentication parameters to be provided, generating a 403 status for invalid credentials or an error if they are absent. - -# 0.6.0 - -Add connection parameters specifications with connect_to in Docker::API::Connection. - -Add `Docker::API::Exec` methods: -* create -* start -* resize -* ~~inspect~~ details - -# 0.5.0 - -Add `Docker::API::System` methods: -* auth -* ping -* info -* version -* events -* df - -Add new response class `Docker::API::Response` with the following methods: -* json -* path -* success? - -Error classes output better error messages. - -# 0.4.0 - -Add `Docker::API::Network` methods: -* list -* ~~inspect~~ details -* create -* remove -* prune -* connect -* disconnect - -# 0.3.0 - -Add `Docker::API::Volume` methods: -* list -* ~~inspect~~ details -* create -* remove -* prune - - -# 0.2.0 - -Add `Docker::API::Image` methods: -* ~~inspect~~ details -* history -* list -* search -* tag -* prune -* remove -* export -* import -* push -* commit -* create -* build -* delete_cache - -Add `Docker::API::System.auth` (untested) for basic authentication - -# 0.1.0 - -Add `Docker::API::Container` methods: -* list -* ~~inspect~~ details -* top -* changes -* start -* stop -* restart -* kill -* wait -* update -* rename -* resize -* prune -* pause -* unpause -* remove -* logs -* attach -* create -* stats -* export -* archive \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index b2f4551..1cea8e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - dockerapi (0.20.0) + dockerapi (0.20.1) base64 excon (>= 0.97, < 2) diff --git a/README.md b/README.md index a564d84..51c1ddf 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,29 @@ container = Docker::API::Container.new # Create container container.create( {name: "nginx"}, {Image: "nginx:latest", HostConfig: {PortBindings: {"80/tcp": [ {HostIp: "0.0.0.0", HostPort: "80"} ]}}}) +# A more complex container creation +container.create( + {name: "nginx"}, + { + Image: "nginx:latest", + HostConfig: { + PortBindings: { + "80/tcp": [ {HostIp: "0.0.0.0", HostPort: "80"} ] + } + }, + Env: ["DOCKER=nice", "DOCKERAPI=awesome"], + Cmd: ["echo", "hello from test"], + Entrypoint: ["sh"], + NetworkingConfig: { + EndpointsConfig: { + EndpointSettings: { + IPAddress: "192.172.0.100" + } + } + } + } +) + # Start container container.start("nginx") @@ -563,13 +586,14 @@ The default blocks can be found in `Docker::API::Base`. ## Development -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +Run `rake spec` to run the tests. + +Run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). ### Release new version -* Update CHANGELOG. * Update README as needed. * Update version. * Run tests. diff --git a/bin/console b/bin/console old mode 100644 new mode 100755 diff --git a/bin/setup b/bin/setup deleted file mode 100644 index a035489..0000000 --- a/bin/setup +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -IFS=$'\n\t' -#set -vx - -# Do any other automated setup that you need to do here -if [ $USER == 'root' ] -then - echo "Enabling TCP port 2375 for external connection to Docker" - echo '{"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]}' > /etc/docker/daemon.json - echo '[Unit]' > /etc/systemd/system/docker.service - echo 'Description=Docker' >> /etc/systemd/system/docker.service - echo '[Service]' >> /etc/systemd/system/docker.service - echo 'ExecStart=' >> /etc/systemd/system/docker.service - echo 'ExecStart=/usr/bin/docker' >> /etc/systemd/system/docker.service - systemctl daemon-reload - systemctl restart docker.service - echo "Done!" -else - echo "Running bundle install" - bundle install - - echo "Creating self-signed certificate to use in tests" - openssl req -newkey rsa:2048 -nodes -keyout resources/registry_authentication/registry_auth.key -x509 -days 365 -out resources/registry_authentication/registry_auth.crt -subj "/C=CL/ST=Santiago/L=Santiago/O=dockerapi/OU=dockerapi/CN=localhost" - - echo "Creating htpasswd file to use in tests" - docker run --rm --entrypoint htpasswd registry:2.7.0 -Bbn janedoe password > resources/registry_authentication/htpasswd - docker image rm registry:2.7.0 - echo "Run this script as root for further configurations" -fi - diff --git a/dockerapi.gemspec b/dockerapi.gemspec index 14f2b22..be1e5fb 100644 --- a/dockerapi.gemspec +++ b/dockerapi.gemspec @@ -14,7 +14,6 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = "https://github.com/nu12/dockerapi.git" - spec.metadata["changelog_uri"] = "https://github.com/nu12/dockerapi/blob/main/CHANGELOG.md" spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/dockerapi" # Specify which files should be added to the gem when it is released. diff --git a/lib/docker/api/config.rb b/lib/docker/api/config.rb index 6aea432..9e4682b 100644 --- a/lib/docker/api/config.rb +++ b/lib/docker/api/config.rb @@ -24,7 +24,7 @@ def list params = {} # # @param body [Hash]: Request body to be sent as json. def create body = {} - @connection.request(method: :post, path: "/configs/create", headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: "/v#{Docker::API::API_VERSION}/configs/create", headers: {"Content-Type": "application/json"}, body: body.to_json) end # Inspect a config @@ -35,7 +35,7 @@ def create body = {} # # @param name [String]: The ID or name of the config. def details name - @connection.get("/configs/#{name}") + @connection.get("/v#{Docker::API::API_VERSION}/configs/#{name}") end # Update a config @@ -50,7 +50,7 @@ def details name # # @param body [Hash]: Request body to be sent as json. def update name, params = {}, body = {} - @connection.request(method: :post, path: build_path("/configs/#{name}/update",params), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/v#{Docker::API::API_VERSION}/configs/#{name}/update",params), headers: {"Content-Type": "application/json"}, body: body.to_json) end # Delete a config @@ -61,6 +61,6 @@ def update name, params = {}, body = {} # # @param name [String]: The ID or name of the config. def delete name - @connection.delete("/configs/#{name}") + @connection.delete("/v#{Docker::API::API_VERSION}/configs/#{name}") end end \ No newline at end of file diff --git a/lib/docker/api/connection.rb b/lib/docker/api/connection.rb index 8a91c6e..40a749e 100644 --- a/lib/docker/api/connection.rb +++ b/lib/docker/api/connection.rb @@ -11,6 +11,7 @@ class Docker::API::Connection # @param params [Hash]: Request parameters. def request params response = Docker::API::Response.new(@connection.request(params).data) + response.request_params = params p response if Docker::API.print_response_to_stdout response end @@ -20,9 +21,9 @@ def request params # # @param url [String]: URL for the connection. # @param params [String]: Additional parameters. - def initialize url = nil, params = nil - return @connection = Excon.new('unix:///', {socket: '/var/run/docker.sock'}) unless url - @connection = Excon.new(url, params || {}) + def initialize url = nil, params = {} + return @connection = Excon.new('unix:///', params.merge({socket: '/var/run/docker.sock'})) unless url + @connection = Excon.new(url, params) end end \ No newline at end of file diff --git a/lib/docker/api/container.rb b/lib/docker/api/container.rb index 6a1b018..3a2942b 100644 --- a/lib/docker/api/container.rb +++ b/lib/docker/api/container.rb @@ -46,7 +46,7 @@ def top name, params = {} # # @param name [String]: The ID or name of the container. def changes name - @connection.get("/containers/#{name}/changes") + @connection.get(build_path("/containers/#{name}/changes")) end ## @@ -118,7 +118,8 @@ def wait name, params = {} # @param name [String]: The ID or name of the container. # @param body [Hash]: Request body to be sent as json. def update name, body = {} - @connection.request(method: :post, path: "/containers/#{name}/update", headers: {"Content-Type": "application/json"}, body: body.to_json) + + @connection.request(method: :post, path: build_path("/containers/#{name}/update"), headers: {"Content-Type": "application/json"}, body: body.to_json) end ## @@ -164,7 +165,7 @@ def prune params = {} # # @param name [String]: The ID or name of the container. def pause name - @connection.post("/containers/#{name}/pause") + @connection.post(build_path("/containers/#{name}/pause")) end ## @@ -175,7 +176,7 @@ def pause name # # @param name [String]: The ID or name of the container. def unpause name - @connection.post("/containers/#{name}/unpause") + @connection.post(build_path("/containers/#{name}/unpause")) end ## diff --git a/lib/docker/api/exec.rb b/lib/docker/api/exec.rb index 933afa6..ddd7745 100644 --- a/lib/docker/api/exec.rb +++ b/lib/docker/api/exec.rb @@ -12,7 +12,7 @@ class Docker::API::Exec < Docker::API::Base # @param name [String]: The ID or name of the container. # @param body [Hash]: Request body to be sent as json. def create name, body = {} - @connection.request(method: :post, path: "/containers/#{name}/exec", headers: {"Content-Type": "application/json"}, body: body.to_json ) + @connection.request(method: :post, path: build_path("/containers/#{name}/exec"), headers: {"Content-Type": "application/json"}, body: body.to_json ) end ## @@ -25,7 +25,7 @@ def create name, body = {} # @param body [Hash]: Request body to be sent as json. # @param &block: Replace the default output to stdout behavior. def start name, body = {}, &block - @connection.request(method: :post, path: "/exec/#{name}/start", headers: {"Content-Type": "application/json"}, body: body.to_json, + @connection.request(method: :post, path: build_path("/exec/#{name}/start"), headers: {"Content-Type": "application/json"}, body: body.to_json, response_block: block_given? ? block : default_streamer ) end @@ -49,7 +49,7 @@ def resize name, params = {} # # @param name [String]: Exec instance ID. def details name - @connection.get("/exec/#{name}/json") + @connection.get(build_path("/exec/#{name}/json")) end end \ No newline at end of file diff --git a/lib/docker/api/image.rb b/lib/docker/api/image.rb index b749b8f..2bea426 100644 --- a/lib/docker/api/image.rb +++ b/lib/docker/api/image.rb @@ -11,7 +11,7 @@ class Docker::API::Image < Docker::API::Base # # @param name [String]: The ID or name of the image. def details name - @connection.get("/images/#{name}/json") + @connection.get(build_path("/images/#{name}/json")) end ## @@ -23,7 +23,7 @@ def details name # @param name [String]: The ID or name of the image. # @param authentication [Hash]: Authentication parameters. def distribution name, authentication = {} - request = {method: :get, path: "/distribution/#{name}/json"} + request = {method: :get, path: build_path("/distribution/#{name}/json")} request[:headers] = {"X-Registry-Auth" => auth_encoder(authentication)} if authentication.any? @connection.request(request) end @@ -36,7 +36,7 @@ def distribution name, authentication = {} # # @param name [String]: The ID or name of the image. def history name - @connection.get("/images/#{name}/history") + @connection.get(build_path("/images/#{name}/history")) end ## @@ -146,7 +146,7 @@ def push name, params = {}, authentication = {} # @param params [Hash]: Parameters that are appended to the URL. # @param body [Hash]: Request body to be sent as json. def commit params = {}, body = {} - container = Docker::API::Container.new.details(params[:container]) + container = Docker::API::Container.new(@connection).details(params[:container]) return container if [404, 301].include? container.status @connection.request(method: :post, path: build_path("/commit", params), headers: {"Content-Type": "application/json"}, body: container.json["Config"].merge(body).to_json) end @@ -185,7 +185,7 @@ def create params = {}, authentication = {}, &block def build path, params = {}, authentication = {}, &block raise Docker::API::Error.new("Expected path or params[:remote]") unless path || params[:remote] - headers = {"Content-type": "application/x-tar"} + headers = {"Content-type" => "application/x-tar"} headers.merge!({"X-Registry-Config": auth_encoder(authentication) }) if authentication.any? if path == nil and params.has_key? :remote diff --git a/lib/docker/api/network.rb b/lib/docker/api/network.rb index ec96b57..48c3ddb 100644 --- a/lib/docker/api/network.rb +++ b/lib/docker/api/network.rb @@ -34,7 +34,7 @@ def details name, params = {} # # @param body [Hash]: Request body to be sent as json. def create body = {} - @connection.request(method: :post, path: build_path("/networks/create"), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/networks/create"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## @@ -68,7 +68,7 @@ def prune params = {} # @param name [String]: The ID or name of the network. # @param body [Hash]: Request body to be sent as json. def connect name, body = {} - @connection.request(method: :post, path: build_path("/networks/#{name}/connect"), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/networks/#{name}/connect"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## @@ -80,7 +80,7 @@ def connect name, body = {} # @param name [String]: The ID or name of the network. # @param body [Hash]: Request body to be sent as json. def disconnect name, body = {} - @connection.request(method: :post, path: build_path("/networks/#{name}/disconnect"), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/networks/#{name}/disconnect"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end end \ No newline at end of file diff --git a/lib/docker/api/node.rb b/lib/docker/api/node.rb index 6b2eb43..e7d389b 100644 --- a/lib/docker/api/node.rb +++ b/lib/docker/api/node.rb @@ -25,7 +25,7 @@ def list params = {} # @param params [Hash]: Parameters that are appended to the URL. # @param body [Hash]: Request body to be sent as json. def update name, params = {}, body = {} - @connection.request(method: :post, path: build_path("/nodes/#{name}/update", params), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/nodes/#{name}/update", params), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## @@ -48,6 +48,6 @@ def delete name, params = {} # # @param name [String]: The ID or name of the node. def details name - @connection.get("/nodes/#{name}") + @connection.get(build_path("/nodes/#{name}")) end end \ No newline at end of file diff --git a/lib/docker/api/plugin.rb b/lib/docker/api/plugin.rb index 946e6cd..ff8a86e 100644 --- a/lib/docker/api/plugin.rb +++ b/lib/docker/api/plugin.rb @@ -39,7 +39,7 @@ def privileges params = {} # # @param authentication [Hash]: Authentication parameters. def install params = {}, privileges = [], authentication = {} - headers = {"Content-Type": "application/json"} + headers = {"Content-Type" => "application/json"} headers.merge!({"X-Registry-Auth" => Base64.urlsafe_encode64(authentication.to_json.to_s)}) if authentication.keys.size > 0 @connection.request(method: :post, path: build_path("/plugins/pull", params), headers: headers, body: privileges.to_json ) end @@ -52,7 +52,7 @@ def install params = {}, privileges = [], authentication = {} # # @param name [String]: The ID or name of the plugin. def details name - @connection.get("/plugins/#{name}/json") + @connection.get(build_path("/plugins/#{name}/json")) end # Remove a plugin @@ -89,7 +89,7 @@ def enable name, params = {} # # @param name [String]: The ID or name of the plugin. def disable name - @connection.post("/plugins/#{name}/disable") + @connection.post(build_path("/plugins/#{name}/disable")) end # Upgrade a plugin @@ -106,7 +106,7 @@ def disable name # # @param authentication [Hash]: Authentication parameters. def upgrade name, params = {}, privileges = [], authentication = {} - headers = {"Content-Type": "application/json"} + headers = {"Content-Type" => "application/json"} headers.merge!({"X-Registry-Auth" => Base64.urlsafe_encode64(authentication.to_json.to_s)}) if authentication.keys.size > 0 @connection.request(method: :post, path: build_path("/plugins/#{name}/upgrade", params), headers: headers, body: privileges.to_json ) end @@ -122,7 +122,7 @@ def upgrade name, params = {}, privileges = [], authentication = {} # @param path [String]: Path to tar file that contains rootfs folder and config.json file. def create name, path file = File.open( File.expand_path( path ) , "r") - response = @connection.request(method: :post, path: "/plugins/create?name=#{name}", body: file.read.to_s ) + response = @connection.request(method: :post, path: build_path("/plugins/create?name=#{name}"), body: file.read.to_s ) file.close response end @@ -138,9 +138,9 @@ def create name, path # @param authentication [Hash]: Authentication parameters. def push name, authentication = {} if authentication.keys.size > 0 - @connection.request(method: :post, path: "/plugins/#{name}/push", headers: {"X-Registry-Auth" => Base64.urlsafe_encode64(authentication.to_json.to_s)}) + @connection.request(method: :post, path: build_path("/plugins/#{name}/push"), headers: {"X-Registry-Auth" => Base64.urlsafe_encode64(authentication.to_json.to_s)}) else - @connection.post("/plugins/#{name}/push") + @connection.post(build_path("/plugins/#{name}/push")) end end @@ -154,7 +154,7 @@ def push name, authentication = {} # # @param config [Array]: Plugin configuration to be sent as json in request body. def configure name, config - @connection.request(method: :post, path: "/plugins/#{name}/set", headers: {"Content-Type": "application/json"}, body:config.to_json) + @connection.request(method: :post, path: build_path("/plugins/#{name}/set"), headers: {"Content-Type" => "application/json"}, body:config.to_json) end end \ No newline at end of file diff --git a/lib/docker/api/response.rb b/lib/docker/api/response.rb index 6de61b3..8719ac7 100644 --- a/lib/docker/api/response.rb +++ b/lib/docker/api/response.rb @@ -2,6 +2,7 @@ # Reponse class. class Docker::API::Response < Excon::Response attr_reader(:json, :path) + attr_accessor(:request_params) ## # Initialize a new Response object. diff --git a/lib/docker/api/secret.rb b/lib/docker/api/secret.rb index d7772c0..fd29430 100644 --- a/lib/docker/api/secret.rb +++ b/lib/docker/api/secret.rb @@ -21,7 +21,7 @@ def list params = {} # # @param body [Hash]: Request body to be sent as json. def create body = {} - @connection.request(method: :post, path: "/secrets/create", headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/secrets/create"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end # Inspect a secret @@ -31,7 +31,7 @@ def create body = {} # # @param name [String]: The ID or name of the secret. def details name - @connection.get("/secrets/#{name}") + @connection.get(build_path("/secrets/#{name}")) end # Update a secret @@ -43,7 +43,7 @@ def details name # @param params [Hash]: Parameters that are appended to the URL. # @param body [Hash]: Request body to be sent as json. def update name, params = {}, body = {} - @connection.request(method: :post, path: build_path("/secrets/#{name}/update",params), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/secrets/#{name}/update",params), headers: {"Content-Type" => "application/json"}, body: body.to_json) end # Delete a secret @@ -53,6 +53,6 @@ def update name, params = {}, body = {} # # @param name [String]: The ID or name of the secret. def delete name - @connection.delete("/secrets/#{name}") + @connection.delete(build_path("/secrets/#{name}")) end end \ No newline at end of file diff --git a/lib/docker/api/service.rb b/lib/docker/api/service.rb index 8c4bad3..13529e9 100644 --- a/lib/docker/api/service.rb +++ b/lib/docker/api/service.rb @@ -22,9 +22,9 @@ def list params = {} # @param body [Hash]: Request body to be sent as json. # @param authentication [Hash]: Authentication parameters. def create body = {}, authentication = {} - headers = {"Content-Type": "application/json"} + headers = {"Content-Type" => "application/json"} headers.merge!({"X-Registry-Auth" => auth_encoder(authentication) }) if authentication.keys.size > 0 - @connection.request(method: :post, path: "/services/create", headers: headers, body: body.to_json) + @connection.request(method: :post, path: build_path("/services/create"), headers: headers, body: body.to_json) end # Update a service @@ -38,7 +38,7 @@ def create body = {}, authentication = {} # @param authentication [Hash]: Authentication parameters. def update name, params = {}, body = {}, authentication = {} # view https://github.com/docker/swarmkit/issues/1394#issuecomment-240850719 - headers = {"Content-Type": "application/json"} + headers = {"Content-Type" => "application/json"} headers.merge!({"X-Registry-Auth" => auth_encoder(authentication) }) if authentication.keys.size > 0 @connection.request(method: :post, path: build_path("/services/#{name}/update", params), headers: headers, body: body.to_json) end @@ -72,6 +72,6 @@ def logs name, params = {} # # @param name [String]: The ID or name of the service. def delete name - @connection.delete("/services/#{name}") + @connection.delete(build_path("/services/#{name}")) end end diff --git a/lib/docker/api/swarm.rb b/lib/docker/api/swarm.rb index 9b42512..e83bea0 100644 --- a/lib/docker/api/swarm.rb +++ b/lib/docker/api/swarm.rb @@ -11,7 +11,7 @@ class Docker::API::Swarm < Docker::API::Base # # @param body [Hash]: Request body to be sent as json. def init body = {} - @connection.request(method: :post, path: build_path("/swarm/init"), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/swarm/init"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## @@ -23,7 +23,7 @@ def init body = {} # @param params [Hash]: Parameters that are appended to the URL. # @param body [Hash]: Request body to be sent as json. def update params = {}, body = {} - @connection.request(method: :post, path: build_path("/swarm/update", params), headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/swarm/update", params), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## @@ -32,7 +32,7 @@ def update params = {}, body = {} # Docker API: GET /swarm # @see https://docs.docker.com/engine/api/v1.40/#operation/SwarmInspect def details - @connection.get("/swarm") + @connection.get(build_path("/swarm")) end ## @@ -41,7 +41,7 @@ def details # Docker API: GET /swarm/unlockkey # @see https://docs.docker.com/engine/api/v1.40/#operation/SwarmUnlockkey def unlock_key - @connection.get("/swarm/unlockkey") + @connection.get(build_path("/swarm/unlockkey")) end ## @@ -52,7 +52,7 @@ def unlock_key # # @param body [Hash]: Request body to be sent as json. def unlock body = {} - @connection.request(method: :post, path: "/swarm/unlock", headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/swarm/unlock"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## @@ -63,7 +63,7 @@ def unlock body = {} # # @param body [Hash]: Request body to be sent as json. def join body = {} - @connection.request(method: :post, path: "/swarm/join", headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/swarm/join"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## diff --git a/lib/docker/api/system.rb b/lib/docker/api/system.rb index 73a9d37..430d4e2 100644 --- a/lib/docker/api/system.rb +++ b/lib/docker/api/system.rb @@ -11,7 +11,7 @@ class Docker::API::System < Docker::API::Base # # @param body [Hash]: Request body to be sent as json. def auth body = {} - @connection.request(method: :post, path: "/auth", headers: { "Content-Type" => "application/json" }, body: body.to_json) + @connection.request(method: :post, path: build_path("/auth"), headers: { "Content-Type" => "application/json" }, body: body.to_json) end ## diff --git a/lib/docker/api/task.rb b/lib/docker/api/task.rb index 39a3e78..cee912b 100644 --- a/lib/docker/api/task.rb +++ b/lib/docker/api/task.rb @@ -21,7 +21,7 @@ def list params = {} # # @param name [String]: The ID or name of the task. def details name - @connection.get("/tasks/#{name}") + @connection.get(build_path("/tasks/#{name}")) end # Get stdout and stderr logs from a task. diff --git a/lib/docker/api/version.rb b/lib/docker/api/version.rb index b1c9e23..73848cb 100644 --- a/lib/docker/api/version.rb +++ b/lib/docker/api/version.rb @@ -1,6 +1,6 @@ module Docker module API - GEM_VERSION = "0.20.0" + GEM_VERSION = "0.20.1" API_VERSION = "1.43" diff --git a/lib/docker/api/volume.rb b/lib/docker/api/volume.rb index ae28578..c993918 100644 --- a/lib/docker/api/volume.rb +++ b/lib/docker/api/volume.rb @@ -22,7 +22,7 @@ def list params = {} # # @param name [String]: The ID or name of the volume. def details name - @connection.get("/volumes/#{name}") + @connection.get(build_path("/volumes/#{name}")) end ## @@ -33,7 +33,7 @@ def details name # # @param body [Hash]: Request body to be sent as json. def create body = {} - @connection.request(method: :post, path: "/volumes/create", headers: {"Content-Type": "application/json"}, body: body.to_json) + @connection.request(method: :post, path: build_path("/volumes/create"), headers: {"Content-Type" => "application/json"}, body: body.to_json) end ## diff --git a/mise.toml b/mise.toml index bf51868..db23b01 100644 --- a/mise.toml +++ b/mise.toml @@ -1,2 +1,2 @@ [tools] -ruby = '3.2' \ No newline at end of file +ruby = "3.2" \ No newline at end of file diff --git a/spec/endpoints/config_spec.rb b/spec/endpoints/config_spec.rb index 9c5b401..c170ed1 100644 --- a/spec/endpoints/config_spec.rb +++ b/spec/endpoints/config_spec.rb @@ -1,8 +1,7 @@ RSpec.describe Docker::API::Config do - ip_address = get_api_ip_address name = "rspec-config" - subject { described_class.new } + subject { described_class.new(stub_connection) } it { is_expected.to respond_to(:list) } it { is_expected.to respond_to(:create) } @@ -10,56 +9,50 @@ it { is_expected.to respond_to(:update) } it { is_expected.to respond_to(:delete) } - it { expect(subject.list.status).to eq(503) } - - context "having a swarm cluster" do - before(:all) { Docker::API::Swarm.new.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567"}) } - after(:all) { Docker::API::Swarm.new.leave(force: true) } + context "with stubs" do + before(:all) do + Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) + Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :path => '/v1.43/configs/rspec-config', :port => 2375 }, {headers: {'Content-Type': 'application/json'}, body: '{"ID": "abc", "Version": {"Index": "abc"}}', status: 200 }) + end + after(:all) { Excon.stubs.clear } describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: {id: { "config-id": true }}).status).to eq(200) } - it { expect(subject.list(filters: {label: { "label=key": true }}).status).to eq(200) } - it { expect(subject.list(filters: {name: { "config-name": true }}).status).to eq(200) } + it { expect(subject.list.request_params[:path]).to eq('/v1.43/configs') } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: {id: { "config-id": true }}).request_params[:path]).to eq('/v1.43/configs?filters={"id":{"config-id":true}}') } + it { expect(subject.list(filters: {label: { "label=key": true }}).request_params[:path]).to eq('/v1.43/configs?filters={"label":{"label=key":true}}') } + it { expect(subject.list(filters: {name: { "config-name": true }}).request_params[:path]).to eq('/v1.43/configs?filters={"name":{"config-name":true}}') } it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.list(invalid: true, skip_validation: true)}.not_to raise_error } end describe ".create" do - it { expect(subject.create.status).to eq(400) } - it { expect(subject.create(Name: name).status).to eq(400) } - it { expect(subject.create({Name: name,Labels: {foo: "bar"}, - Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).status).to eq(201) } - it { expect(subject.create({Name: name, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).status).to eq(409) } + it { expect(subject.create.request_params[:path]).to eq('/v1.43/configs/create') } + it { expect(subject.create.request_params[:method]).to eq(:post) } + it { expect(subject.create(Name: "rspec-config").request_params[:body]).to eq('{"Name":"rspec-config"}') } + it { expect(subject.create({Name: "rspec-config",Labels: {foo: "bar"}, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).request_params[:body]).to eq('{"Name":"rspec-config","Labels":{"foo":"bar"},"Data":"VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}') } it { expect{subject.create(invalid: true)}.to raise_error(Docker::API::InvalidRequestBody) } - it { expect{subject.create(invalid: true, skip_validation: true)}.not_to raise_error } end - context "after .create" do - describe ".details" do - it { expect(subject.details(name).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } - it { expect(subject.details(name).json["ID"]).not_to be(nil) } - it { expect(subject.details(name).json["Version"]["Index"]).not_to be(nil) } - end - - describe ".update" do - let(:version) { subject.details(name).json["Version"]["Index"] } - let(:spec) { subject.details(name).json["Spec"] } - - it { expect(subject.update("doesn-exist", {version: version}, spec).status).to eq(404) } - it { expect(subject.update(name, {version: version}, spec).status).to eq(200) } - it { expect{subject.update(name, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.update(name, {version: version}, {invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } + describe ".details" do + it { expect(subject.details("rspec-config").request_params[:path]).to eq('/v1.43/configs/rspec-config') } + it { expect(subject.details("rspec-config").request_params[:method]).to eq(:get) } + it { expect(subject.details("rspec-config").json["ID"]).not_to be(nil) } + it { expect(subject.details("rspec-config").json["Version"]["Index"]).not_to be(nil) } + end - end + describe ".update" do + let(:version) { subject.details("rspec-config").json["Version"]["Index"] } + let(:spec) { subject.details("rspec-config").json["Spec"] } - describe ".delete" do - it { expect(subject.delete(name).status).to eq(204) } - it { expect(subject.delete(name).status).to eq(404) } - end + it { expect(subject.update("rspec-config", {version: version}, spec).request_params[:path]).to eq('/v1.43/v1.43/configs/rspec-config/update?version=abc') } + it { expect(subject.update("rspec-config", {version: version}, spec).request_params[:method]).to eq(:post) } + it { expect{subject.update("rspec-config", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.update("rspec-config", {version: version}, {invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } end + describe ".delete" do + it { expect(subject.delete("rspec-config").request_params[:path]).to eq('/v1.43/configs/rspec-config') } + it { expect(subject.delete("rspec-config").request_params[:method]).to eq(:delete) } + end end - end \ No newline at end of file diff --git a/spec/endpoints/container_spec.rb b/spec/endpoints/container_spec.rb index 8e69422..a6ab66f 100644 --- a/spec/endpoints/container_spec.rb +++ b/spec/endpoints/container_spec.rb @@ -1,295 +1,197 @@ RSpec.describe Docker::API::Container do - image = "nginx:alpine" - name = "rspec-container" - - before(:all) { Docker::API::Image.new.create({fromImage: image}) } - after(:all) { Docker::API::Image.new.remove(image) } - - subject { described_class.new } - describe ".list" do - it { expect(subject.list.json).to be_kind_of(Array) } - it { expect { subject.list( { invalid: "invalid" } ) }.to raise_error(Docker::API::InvalidParameter) } - - describe "status code" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list( { all: true, limit: 1 } ).status).to eq(200) } - it { expect(subject.list( { all: true, size: true } ).status).to eq(200) } - it { expect(subject.list( { all: true, filters: {status: ["running", "paused"]} } ).status).to eq(200) } - it { expect(subject.list( { all: true, filters: {name: {"#{name}": true}} } ).status).to eq(200) } - it { expect(subject.list( { all: true, filters: {exited: {"0": true}} } ).status).to eq(200) } + subject { described_class.new(stub_connection) } + + it { is_expected.to respond_to(:list) } + it { is_expected.to respond_to(:create) } + it { is_expected.to respond_to(:details) } + it { is_expected.to respond_to(:top) } + it { is_expected.to respond_to(:changes) } + it { is_expected.to respond_to(:start) } + it { is_expected.to respond_to(:stop) } + it { is_expected.to respond_to(:restart) } + it { is_expected.to respond_to(:kill) } + it { is_expected.to respond_to(:wait) } + it { is_expected.to respond_to(:update) } + it { is_expected.to respond_to(:rename) } + it { is_expected.to respond_to(:resize) } + it { is_expected.to respond_to(:prune) } + it { is_expected.to respond_to(:pause) } + it { is_expected.to respond_to(:unpause) } + it { is_expected.to respond_to(:remove) } + it { is_expected.to respond_to(:logs) } + it { is_expected.to respond_to(:attach) } + it { is_expected.to respond_to(:export) } + it { is_expected.to respond_to(:get_archive) } + it { is_expected.to respond_to(:put_archive) } + + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } + + describe ".list" do + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, {headers: {'Content-Type': 'application/json'}, body: '[]', status: 200 }) } + after(:all) { Excon.unstub({ :method => :get }) } + it { expect(subject.list.json).to be_kind_of(Array) } + it { expect(subject.list( { all: true, filters: {name: {"test": true}} } ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/json?all=true&filters={\"name\":{\"test\":true}}") } + it { expect(subject.list( { all: true, filters: {exited: {"0": true} } } ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/json?all=true&filters={\"exited\":{\"0\":true}}") } + it { expect(subject.list( { all: true, filters: {status: ["running"] } } ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/json?all=true&filters={\"status\":[\"running\"]}") } + it { expect { subject.list( { invalid: "invalid" } ) }.to raise_error(Docker::API::InvalidParameter) } + end + + describe ".create" do + it { expect(subject.create({name: "dockerapi", platform: "linux/amd64"}, {Image: "nginx"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/create?name=dockerapi&platform=linux/amd64") } + it { expect(subject.create({name: "dockerapi", platform: "linux/amd64"}, {Image: "nginx"}).request_params[:method]).to eq(:post) } + it { expect(subject.create({name: "dockerapi", platform: "linux/amd64"}, {Image: "nginx"}).request_params[:body]).to eq('{"Image":"nginx"}') } + it { expect{subject.remove({invalid: "invalid", platform: "linux/amd64"}, {Image: "nginx"})}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.create({name: "dockerapi", platform: "linux/amd64"}, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidRequestBody) } + end + + describe ".remove" do + it { expect(subject.remove("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi") } + it { expect(subject.remove("dockerapi").request_params[:method]).to eq(:delete) } + it { expect(subject.remove("dockerapi", {v: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi?v=true") } + it { expect(subject.remove("dockerapi", {force: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi?force=true") } + it { expect{subject.remove("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end - describe "request path" do - it { expect(subject.list( { all: true, filters: {name: {"#{name}": true}} } ).path).to eq("/v#{Docker::API::API_VERSION}/containers/json?all=true&filters={\"name\":{\"#{name}\":true}}") } - it { expect(subject.list( { all: true, filters: {exited: {"0": true} } } ).path).to eq("/v#{Docker::API::API_VERSION}/containers/json?all=true&filters={\"exited\":{\"0\":true}}") } - it { expect(subject.list( { all: true, filters: {status: ["running"] } } ).path).to eq("/v#{Docker::API::API_VERSION}/containers/json?all=true&filters={\"status\":[\"running\"]}") } + describe ".start" do + it { expect(subject.start("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/start") } + it { expect(subject.start("dockerapi", {detachKeys: "ctrl-c"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/start?detachKeys=ctrl-c") } + it { expect(subject.start("dockerapi").request_params[:method]).to eq(:post) } + it { expect{subject.start("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end - end - describe ".create" do - context "no name given" do - subject { described_class.new.create( {}, { Image: image }) } - let(:id) { subject.json["Id"] } - it { expect(subject.status).to eq(201) } - it { expect(id).not_to be(nil) } - it { expect(described_class.new.remove(id).success?).to eq(true) } - - end - context "still no name given" do - it { expect(subject.create( {platform: "os/no-arch"}, {Image: image}).status).to eq(404) } + describe ".stop" do + it { expect(subject.stop("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/stop") } + it { expect(subject.stop("dockerapi", {signal: "SIGINT"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/stop?signal=SIGINT") } + it { expect(subject.stop("dockerapi").request_params[:method]).to eq(:post) } + it { expect{subject.stop("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end - after(:each) { described_class.new.remove(name) } - it { expect{subject.create( {name: name}, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidRequestBody) } - it { expect{subject.create( {name: name, invalid: "invalid"}, {Image: image})}.to raise_error(Docker::API::InvalidParameter) } - it { expect(subject.create( {name: name, platform: "linux/amd64"}, {Image: image}).status).to eq(201) } - it { expect(subject.create( {name: name, platform: "os/no-arch"}, {Image: image}).status).to eq(404) } - it { expect(subject.create( {name: name}, {Image: image}).status).to eq(201) } - it do - response = subject.create( - {name: name}, - { - Image: image, - HostConfig: { - #Memory: 6000000, - PortBindings: { - "80/tcp": [ {HostIp: "0.0.0.0", HostPort: "80"} ] - } - }, - Env: ["DOCKER=nice", "DOCKERAPI=awesome"], - Cmd: ["echo", "hello from test"], - Entrypoint: ["sh"], - NetworkingConfig: { - EndpointsConfig: { - EndpointSettings: { - IPAddress: "192.172.0.100" - } - } - } - } - ) - expect(response.status).to eq(201) + describe ".kill" do + it { expect(subject.kill("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/kill") } + it { expect(subject.kill("dockerapi", {signal: "SIGINT"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/kill?signal=SIGINT") } + it { expect(subject.kill("dockerapi").request_params[:method]).to eq(:post) } + it { expect{subject.kill("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end - end - describe ".remove" do - before(:each) { subject.create( {name: name}, {Image: image}) } - after(:all) { described_class.new.remove(name, {force: true}) } - - it { expect(subject.remove(name).status).to eq(204) } - it { expect(subject.remove("doesn-exist").status).to eq(404) } - it { expect(subject.remove(name, {v: true}).status).to eq(204) } - it { expect(subject.remove(name, {force: true}).status).to eq(204) } - it { expect{subject.remove(name, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - it do - subject.start(name) - expect(subject.remove(name).status).to eq(409) + describe ".restart" do + it { expect(subject.restart("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/restart") } + it { expect(subject.restart("dockerapi", {signal: "SIGINT"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/restart?signal=SIGINT") } + it { expect(subject.restart("dockerapi").request_params[:method]).to eq(:post) } + it { expect{subject.restart("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end - end - - context "after .create" do - before(:all) { described_class.new.create( {name: name}, {Image: image}) } - after(:all) { described_class.new.remove(name, {force: true}) } - describe ".start" do - before(:each) { subject.stop(name) } - after(:all) { described_class.new.stop(name) } - - it { expect(subject.start(name).status).to eq(204 )} - it { expect(subject.start("doesn-exist").status).to eq(404 )} - it { expect(subject.start(name, {detachKeys: "ctrl-c"}).status).to eq(204 )} - it { expect(subject.start(name, {detachKeys: "ctrl-c"}).path).to eq("/v#{Docker::API::API_VERSION}/containers/#{name}/start?detachKeys=ctrl-c" )} - it { expect{subject.start(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter )} - it do - subject.start(name) - expect(subject.start(name).status).to eq(304) - end + describe ".pause" do + it { expect(subject.pause("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/pause") } end - - context "after .start" do - before(:each) { subject.start(name) } - after(:all) { described_class.new.stop(name) } - - describe ".stop" do - it { expect(subject.stop(name).status).to be(204) } - it { expect(subject.stop(name, {t: 1}).status).to be(204) } - it { expect(subject.stop(name, {signal: "SIGINT"}).status).to be(204) } - it { expect{subject.stop(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - it { expect(subject.stop("doesn-exist").status).to be(404) } - it do - subject.stop(name) - expect(subject.stop(name).status).to be(304) - end - end - - describe ".kill" do - it { expect(subject.kill(name).status).to be(204) } - it { expect(subject.kill("doesn-exist").status).to be(404) } - it { expect(subject.kill(name, {signal: "SIGKILL"}).status).to eq(204) } - it { expect(subject.kill(name, {signal: "SIGKILL"}).path).to eq("/v#{Docker::API::API_VERSION}/containers/#{name}/kill?signal=SIGKILL") } - it { expect{subject.kill(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - it do - subject.stop(name) - expect(subject.kill(name).status).to be(409) - end - end - - describe ".restart" do - it { expect(subject.restart(name).status).to be(204) } - it { expect(subject.restart("doesn-exist").status).to be(404) } - it { expect(subject.restart(name, {t: 2}).status).to eq(204) } - it { expect(subject.restart(name, {signal: "SIGINT"}).status).to eq(204) } - it { expect(subject.restart(name, {t: 2}).path).to eq("/v#{Docker::API::API_VERSION}/containers/#{name}/restart?t=2") } - it { expect{subject.restart(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - end - describe ".pause" do - it { expect(subject.pause(name).status).to be(204) } - it { expect(subject.pause("doesn-exist").status).to be(404) } - end + describe ".unpause" do + it { expect(subject.unpause("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/unpause") } + end - describe ".unpause" do - it { expect(subject.unpause(name).status).to be(204) } - it { expect(subject.unpause("doesn-exist").status).to be(404) } - end + describe ".top" do + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, { headers: {'Content-Type': 'application/json'}, body: '{}', status: 200 }) } + after(:all) { Excon.unstub({ :method => :get }) } - describe ".top" do - it { expect(subject.top(name).status).to be(200) } - it { expect(subject.top(name).json).to be_kind_of(Hash) } - it { expect(subject.top("doesn-exist").status).to be(404) } - it { expect(subject.top(name, {ps_args: "aux"} ).status).to be(200) } - it { expect(subject.top(name, {ps_args: "-ef"} ).status).to be(200) } - it { expect{subject.top(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - end + it { expect(subject.top("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/top") } + it { expect(subject.top("dockerapi").request_params[:method]).to eq(:get) } + it { expect(subject.top("dockerapi").json).to be_kind_of(Hash) } + it { expect{subject.top("dockerapi", {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } + end - describe ".wait" do - it do - Thread.new do - subject.stop(name, {t: 2}) - end - expect(subject.wait(name).status).to be(200) - end - it do - Thread.new do - subject.stop(name, {t: 2}) - end - expect(subject.wait(name).json["StatusCode"]).to be_kind_of(Integer) - end - end + describe ".wait" do + it { expect(subject.wait("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/wait") } + it { expect(subject.wait("dockerapi").request_params[:method]).to eq(:post) } + end - context ".archive" do - after(:all) { File.delete(File.expand_path("~/archive.tar")) } - it { expect(subject.get_archive(name, "file").status).to eq(400) } - it { expect(subject.get_archive("doesn-exist", "file").status).to eq(400) } - it { expect{subject.get_archive(name, "file", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - - describe ".get_archive" do - it { expect(subject.get_archive(name, "~/archive.tar", { path: "/usr/share/nginx/html/" }).status).to eq(200) } - it { expect{File.open(File.expand_path("~/archive.tar"))}.not_to raise_error } - it { expect(subject.get_archive(name, "~/wont_exist.tar", { path: "/this-path-doesnt-exist" }).status).to eq(404) } - it { expect{File.open(File.expand_path("~/wont_exist.tar"))}.to raise_error(Errno::ENOENT) } - end + describe ".get_archive" do + it { expect(subject.get_archive("dockerapi", "~/archive.tar", { path: "/usr/share/nginx/html/" }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/archive?path=/usr/share/nginx/html/") } + it { expect(subject.get_archive("dockerapi", "~/archive.tar", { path: "/usr/share/nginx/html/" }).request_params[:method]).to eq(:get) } + end - describe ".put_archive" do - it { expect(subject.put_archive(name, "~/archive.tar", { path: "/home" }).status).to eq(200) } - it { expect(subject.put_archive(name, "~/archive.tar", { path: "/this-path-doesnt-exist" }).status).to eq(404) } - end - end + describe ".put_archive" do + it { expect(subject.put_archive("dockerapi", "~/archive.tar", { path: "/home" }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/archive?path=/home") } + it { expect(subject.put_archive("dockerapi", "~/archive.tar", { path: "/home" }).request_params[:method]).to eq(:put) } + end - describe ".resize" do - it { expect(subject.resize(name).status).to eq(400) } - it { expect(subject.resize(name, {h: 100, w: 100}).status).to eq(200) } - it { expect(subject.resize("doesn-exist", {h: 100, w: 100}).status).to eq(404) } - it { expect{subject.resize(name, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - end + describe ".resize" do + it { expect(subject.resize("dockerapi", {h: 100, w: 100}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/resize?h=100&w=100") } + it { expect(subject.resize("dockerapi", {h: 100, w: 100}).request_params[:method]).to eq(:post) } + it { expect{subject.resize("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end describe ".details" do - it { expect(subject.details(name).status).to eq(200) } - it { expect(subject.details(name).body).to match(/\"Name\":\"\/#{name}\"/) } - it { expect(subject.details("doesn-exist").status).to eq(404) } - it { expect(subject.details(name, {size: true}).status).to eq(200) } - it { expect(subject.details(name, {size: false}).status).to eq(200) } - it { expect{subject.details(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, { headers: {'Content-Type': 'application/json'}, body: '{"Name":"/dockerapi"}', status: 200 }) } + after(:all) { Excon.unstub({ :method => :get }) } + it { expect(subject.details("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/json") } + it { expect(subject.details("dockerapi", {size: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/json?size=true") } + it { expect(subject.details("dockerapi").request_params[:method]).to eq(:get) } + it { expect(subject.details("dockerapi").body).to match(/\"Name\":\"\/dockerapi\"/) } + it { expect{subject.details("dockerapi", {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end describe ".logs" do - it { expect(subject.logs(name).status).to eq(400) } - it { expect(subject.logs(name, {stdout: false, stderr: false}).status).to eq(400) } - it { expect(subject.logs(name, {stdout: true}).status).to eq(200) } - it { expect(subject.logs(name, {stderr: true}).status).to eq(200) } - it { expect(subject.logs(name, {follow: false, stdout: true, stderr: true}).status).to eq(200) } - it { expect(subject.logs(name, {stdout: true, since: 0}).status).to eq(200) } - it { expect(subject.logs(name, {stdout: true, until: 999999999}).status).to eq(200) } - it { expect(subject.logs(name, {stdout: true, timestamps: true}).status).to eq(200) } - it { expect(subject.logs(name, {stdout: true, tail: "all"}).status).to eq(200) } - it { expect(subject.logs("doesn-exist", {stdout: true, stderr: true}).status).to eq(404) } - it { expect(subject.logs("doesn-exist", {stdout: false, stderr: false}).status).to eq(400) } - it { expect{subject.logs(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } + it { expect(subject.logs("dockerapi", {stdout: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?stdout=true") } + it { expect(subject.logs("dockerapi", {stderr: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?stderr=true") } + it { expect(subject.logs("dockerapi", {follow: false, stdout: true, stderr: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?follow=false&stdout=true&stderr=true") } + it { expect(subject.logs("dockerapi", {stdout: true, since: 0}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?stdout=true&since=0") } + it { expect(subject.logs("dockerapi", {stdout: true, until: 999999999}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?stdout=true&until=999999999") } + it { expect(subject.logs("dockerapi", {stdout: true, timestamps: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?stdout=true×tamps=true") } + it { expect(subject.logs("dockerapi", {stdout: true, tail: "all"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/logs?stdout=true&tail=all") } + it { expect{subject.logs("dockerapi", {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end describe ".changes" do - it { expect(subject.changes(name).status).to eq(200) } - it { expect(subject.changes("doesn-exist").status).to eq(404) } + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, { headers: {'Content-Type': 'application/json'}, body: '[]', status: 200 }) } + after(:all) { Excon.unstub({ :method => :get }) } + it { expect(subject.changes("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/changes") } + it { expect(subject.changes("dockerapi").request_params[:method]).to eq(:get) } + it { expect(subject.changes("dockerapi").json).to be_kind_of(Array) } end describe ".stats" do - #TODO: implement test to stream response - it { expect(subject.stats(name, {stream: false}).status).to eq(200) } - it { expect(subject.stats(name, {stream: false}).body).not_to be(nil) } - it { expect(subject.stats(name, {stream: false, "one-shot": true}).status).to eq(200) } - it { expect(subject.stats(name, {stream: false, "one-shot": true}).body).not_to be(nil) } - it { expect(subject.stats("doesn-exist", {stream: false}).status).to eq(404) } - it { expect{subject.stats(name, {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } + it { expect(subject.stats("dockerapi", {stream: false}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/stats?stream=false") } + it { expect(subject.stats("dockerapi", {stream: false}).request_params[:method]).to eq(:get) } + it { expect{subject.stats("dockerapi", {invalid_value: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end describe ".export" do - after(:all) { File.delete(File.expand_path("~/exported_container")) } + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, { headers: {'Content-Type': 'text'}, body: 'a', status: 200 }) } + after(:all) do + Excon.unstub({ :method => :get }) + File.delete(File.expand_path("~/exported_container")) + end it { expect{File.open(File.expand_path("~/exported_container"))}.to raise_error(Errno::ENOENT) } - it { expect(subject.export(name, "~/exported_container").status).to eq(200) } + it { expect(subject.export("dockerapi", "~/exported_container").status).to eq(200) } it { expect{File.open(File.expand_path("~/exported_container"))}.not_to raise_error } - it { expect(subject.export("doesn-exist", "~/wont_exist").status).to eq(404) } - it { expect{File.open(File.expand_path("~/wont_exist"))}.to raise_error(Errno::ENOENT) } end describe ".update" do - it { expect(subject.update("doesn-exist").status).to eq(404) } - it { expect{subject.update(name, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidRequestBody) } - it do - expect(subject.update(name, - { - #Memory: 8000000, - #CpuShares: 2, - RestartPolicy: { - Name: "unless-stopped" - } - } - ).status).to eq(200) - end + it { expect(subject.update("dockerapi", {RestartPolicy: {Name: "unless-stopped"}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/update") } + it { expect(subject.update("dockerapi", {RestartPolicy: {Name: "unless-stopped"}}).request_params[:method]).to eq(:post) } + it { expect(subject.update("dockerapi", {RestartPolicy: {Name: "unless-stopped"}}).request_params[:body]).to eq('{"RestartPolicy":{"Name":"unless-stopped"}}') } + it { expect{subject.update("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidRequestBody) } end describe ".rename" do - after(:all) { described_class.new.remove("already-in-use") } - it { expect(subject.rename(name).status).to eq(400) } - it { expect(subject.rename(name, {name: "#{name}2"}).status).to eq(204) } - it { expect(subject.rename("#{name}2", {name: name}).status).to eq(204) } - it { expect(subject.rename("doesn-exist", {name: "#{name}2"}).status).to eq(404) } - it { expect{subject.rename(name, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } - it do - subject.create( {name: "already-in-use"}, {Image: image}) - expect(subject.rename(name, {name: "already-in-use"}).status).to eq(409) - end + it { expect(subject.rename("dockerapi", {name: "new_name"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/rename?name=new_name") } + it { expect(subject.rename("dockerapi", {name: "new_name"}).request_params[:method]).to eq(:post) } end describe ".attach" do - it { expect(subject.attach(name).status).to eq(200) } - it { expect{subject.attach(name, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } + it { expect(subject.attach("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/attach") } + it { expect(subject.attach("dockerapi").request_params[:method]).to eq(:post) } + it { expect{subject.attach("dockerapi", {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end describe ".prune" do - subject { described_class.new.prune } - it { expect(subject.status).to eq(200) } - it { expect(subject.json).to be_kind_of(Hash) } + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :post, :port => 2375 }, { headers: {'Content-Type': 'application/json'}, body: '{}', status: 200 }) } + after(:all) { Excon.unstub({ :method => :post }) } + + it { expect(subject.prune.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/prune") } + it { expect(subject.prune.request_params[:method]).to eq(:post) } + it { expect(subject.prune.json).to be_kind_of(Hash) } it { expect{described_class.new.prune( {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } end end diff --git a/spec/endpoints/exec_spec.rb b/spec/endpoints/exec_spec.rb index 96164d0..d34254f 100644 --- a/spec/endpoints/exec_spec.rb +++ b/spec/endpoints/exec_spec.rb @@ -1,55 +1,36 @@ RSpec.describe Docker::API::Exec do - image = "busybox:1.31.1-uclibc" - container = "rspec-container" + subject { described_class.new(stub_connection) } - # A running container is needed + it { is_expected.to respond_to(:create) } + it { is_expected.to respond_to(:start) } + it { is_expected.to respond_to(:resize) } + it { is_expected.to respond_to(:details) } - before(:all) do - Docker::API::Image.new.create(fromImage: image) - Docker::API::Container.new.create({name: container}, {Image: image, Cmd: ["tail","-f","/dev/null"]}) - Docker::API::Container.new.start(container) - end - - after(:all) do - Docker::API::Container.new.stop(container) - Docker::API::Container.new.remove(container) - Docker::API::Image.new.remove(image) - end + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } - describe ".create" do - it { expect(described_class.new.create(container).status).to eq(400) } - it { expect(described_class.new.create("doesn-exist", Cmd: ["ls", "-l"]).status).to eq(404) } - it do - Docker::API::Container.new.pause(container) - expect(described_class.new.create(container, Cmd: ["ls", "-l"]).status).to eq(409) + describe ".create" do + it { expect(subject.create("dockerapi", Cmd: ["ls", "-l"]).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/containers/dockerapi/exec") } + it { expect(subject.create("dockerapi", Cmd: ["ls", "-l"]).request_params[:method]).to eq(:post) } + it { expect(subject.create("dockerapi", Cmd: ["ls", "-l"]).request_params[:body]).to eq('{"Cmd":["ls","-l"]}') } + it { expect(subject.create("dockerapi", AttachStdout:true, WorkingDir: "/etc", Cmd: ["ls", "-l"]).request_params[:body]).to eq('{"AttachStdout":true,"WorkingDir":"/etc","Cmd":["ls","-l"]}') } end - subject do - Docker::API::Container.new.unpause(container) - described_class.new.create(container, Cmd: ["ls", "-l"]) - end - it { expect(subject.status).to eq(201) } - it { expect(subject.json).not_to be(nil) } - it { expect(subject.json["Id"]).not_to be(nil) } - - end - context "after .create" do - subject { described_class.new.create(container, AttachStdout:true, WorkingDir: "/etc", Cmd: ["ls", "-l"]) } describe ".start" do - it { expect(described_class.new.start(subject.json["Id"]).status).to eq(200) } - it { expect(described_class.new.start("doesn-exist").status).to eq(404) } + it { expect(subject.start("id").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/exec/id/start") } + it { expect(subject.start("id").request_params[:method]).to eq(:post) } + it { expect(subject.start("id").request_params[:body]).to eq("{}") } end - + describe ".resize" do - #it { expect(described_class.resize(subject.json["Id"], h:100, w:100).status).to eq(201) } - it { expect(described_class.new.resize(subject.json["Id"]).status).to eq(400) } - it { expect(described_class.new.resize("doesn-exist", h:100, w:100).status).to eq(404) } + it { expect(subject.resize("id").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/exec/id/resize") } + it { expect(subject.resize("id").request_params[:method]).to eq(:post) } end - + describe ".details" do - it { expect(described_class.new.details(subject.json["Id"]).status).to eq(200) } - it { expect(described_class.new.details("doesn-exist").status).to eq(404) } + it { expect(subject.details("id").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/exec/id/json") } + it { expect(subject.details("id").request_params[:method]).to eq(:get) } end - end end \ No newline at end of file diff --git a/spec/endpoints/image_spec.rb b/spec/endpoints/image_spec.rb index 452f0cc..81699fd 100644 --- a/spec/endpoints/image_spec.rb +++ b/spec/endpoints/image_spec.rb @@ -1,260 +1,179 @@ RSpec.describe Docker::API::Image do - image = "busybox:1.31.1-uclibc" + subject { described_class.new(stub_connection) } + + it { is_expected.to respond_to(:details) } + it { is_expected.to respond_to(:distribution) } + it { is_expected.to respond_to(:history) } + it { is_expected.to respond_to(:list) } + it { is_expected.to respond_to(:search) } + it { is_expected.to respond_to(:tag) } + it { is_expected.to respond_to(:prune) } + it { is_expected.to respond_to(:remove) } + it { is_expected.to respond_to(:export) } + it { is_expected.to respond_to(:import) } + it { is_expected.to respond_to(:push) } + it { is_expected.to respond_to(:commit) } + it { is_expected.to respond_to(:create) } + it { is_expected.to respond_to(:build) } + it { is_expected.to respond_to(:delete_cache) } + + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } - after(:all) { described_class.new.prune(filters: {dangling: {"false": true}}) } - - subject { described_class.new } - describe ".create" do - after(:each) { described_class.new.remove(image) } - it { expect{subject.create(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - context "from repository without authentication" do - it { expect(subject.create(fromImage: image).status).to eq(200) } - it { expect(subject.create(fromImage: "doesn-exist").status).to eq(404) } + describe ".details" do + it { expect(subject.details("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/dockerapi/json") } + it { expect(subject.details("dockerapi").request_params[:method]).to eq(:get) } end - context "from local tar file" do - let(:path) { "resources/busybox.tar" } - it { expect(subject.create(fromSrc: path).status).to eq(200) } - it { expect(subject.create(fromSrc: path, repo: image, message: "Imported with dockerapi").status).to eq(200) } + describe ".distribution" do + it { expect(subject.distribution("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/distribution/dockerapi/json") } + it { expect(subject.distribution("dockerapi", { username: "docker", password: "api" }).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImRvY2tlciIsInBhc3N3b3JkIjoiYXBpIn0=") } + it { expect(subject.distribution("dockerapi").request_params[:method]).to eq(:get) } end - context "from remote tar file" do - let(:url) { "https://github.com/nu12/dockerapi/raw/refs/heads/main/resources/busybox.tar" } - it { expect(subject.create(fromSrc: url).status).to eq(200) } - it { expect(subject.create(fromSrc: url, repo: image, message: "Imported with dockerapi").status).to eq(200) } + describe ".history" do + it { expect(subject.history("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/dockerapi/history") } + it { expect(subject.history("dockerapi").request_params[:method]).to eq(:get) } end - end - - context "after .create" do - before(:all) { described_class.new.create(fromImage: image) } - describe ".list" do - describe "status code" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(all: true).status).to eq(200) } - it { expect(subject.list(all: true, "shared-size": true).status).to eq(200) } - it { expect(subject.list(digests: true).status).to eq(200) } - it { expect(subject.list(all: true, digests: true).status).to eq(200) } - it { expect(subject.list(all: true, filters: {dangling: {"true": true}}).status).to eq(200) } - it { expect(subject.list(all: true, filters: {label: {"label-here": true}}).status).to eq(200) } - it { expect(subject.list(all: true, filters: {reference: {"#{image}": true}}).status).to eq(200) } - it { expect(subject.list(all: true, filters: {before: {"#{image}": true}}).status).to eq(200) } - it { expect(subject.list(all: true, filters: {since: {"#{image}": true}}).status).to eq(200) } - end - describe "request path" do - it { expect(subject.list(all: true).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true") } - it { expect(subject.list(all: true, "shared-size": true).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&shared-size=true") } - it { expect(subject.list(digests: true).path).to eq("/v#{Docker::API::API_VERSION}/images/json?digests=true") } - it { expect(subject.list(all: true, digests: true).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&digests=true") } - it { expect(subject.list(all: true, filters: {dangling: {"true": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"dangling\":{\"true\":true}}") } - it { expect(subject.list(all: true, filters: {label: {"label-here": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"label\":{\"label-here\":true}}") } - it { expect(subject.list(all: true, filters: {reference: {"#{image}": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"reference\":{\"#{image}\":true}}") } - it { expect(subject.list(all: true, filters: {before: {"#{image}": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"before\":{\"#{image}\":true}}") } - it { expect(subject.list(all: true, filters: {since: {"#{image}": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"since\":{\"#{image}\":true}}") } - it { expect(subject.list(all: true, invalid: true, skip_validation: true).path).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&invalid=true") } - end - it { expect{subject.list(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - end - - describe ".details" do - it { expect(subject.details(image).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json") } + it { expect(subject.list.request_params[:method]).to eq(:get) } - end - describe ".history" do - it { expect(subject.history(image).status).to eq(200) } - it { expect(subject.history("doesn-exist").status).to eq(404) } + it { expect(subject.list(all: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true") } + it { expect(subject.list(all: true, "shared-size": true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&shared-size=true") } + it { expect(subject.list(digests: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?digests=true") } + it { expect(subject.list(all: true, digests: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&digests=true") } + it { expect(subject.list(all: true, filters: {dangling: {"true": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"dangling\":{\"true\":true}}") } + it { expect(subject.list(all: true, filters: {label: {"label-here": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"label\":{\"label-here\":true}}") } + it { expect(subject.list(all: true, filters: {reference: {"nginx": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"reference\":{\"nginx\":true}}") } + it { expect(subject.list(all: true, filters: {before: {"nginx": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"before\":{\"nginx\":true}}") } + it { expect(subject.list(all: true, filters: {since: {"nginx": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/json?all=true&filters={\"since\":{\"nginx\":true}}") } + end + describe ".search" do + it { expect(subject.search.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/search") } + it { expect(subject.search.request_params[:method]).to eq(:get) } + + it { expect(subject.search(term: "busybox").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/search?term=busybox") } + it { expect(subject.search(term: "busybox", limit: 2).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/search?term=busybox&limit=2") } + it { expect(subject.search(term: "busybox", filters: {"is-automated": {"true": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/search?term=busybox&filters={\"is-automated\":{\"true\":true}}") } + it { expect(subject.search(term: "busybox", filters: {"is-official": {"true": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/search?term=busybox&filters={\"is-official\":{\"true\":true}}") } + it { expect(subject.search(term: "busybox", filters: {stars: {"20": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/search?term=busybox&filters={\"stars\":{\"20\":true}}") } + it { expect{subject.search(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } end describe ".tag" do - it { expect(subject.tag(image).status).to eq(200) } - it { expect(subject.tag(image, repo: "dockerapi/tag:1").status).to eq(201) } - it { expect(subject.tag(image, repo: "dockerapi/tag", tag: "2").status).to eq(201) } - it { expect(subject.tag("doesn-exist", repo: "dockerapi/tag").status).to eq(404) } - it { expect{subject.tag(image, invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } + it { expect(subject.tag("dockerapi").request_params[:path]).to eq ("/v#{Docker::API::API_VERSION}/images/dockerapi/tag") } + it { expect(subject.tag("dockerapi", repo: "dockerapi/tag:1").request_params[:path]).to eq ("/v#{Docker::API::API_VERSION}/images/dockerapi/tag?repo=dockerapi/tag:1") } + it { expect(subject.tag("dockerapi", repo: "dockerapi/tag", tag: "2").request_params[:path]).to eq ("/v#{Docker::API::API_VERSION}/images/dockerapi/tag?repo=dockerapi/tag&tag=2") } + it { expect{subject.tag("dockerapi", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } + end + describe ".prune" do + it { expect(subject.prune.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/prune") } + it { expect(subject.prune.request_params[:method]).to eq(:post) } + + it { expect(subject.prune(filters: {dangling: {"true": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"dangling\":{\"true\":true}}") } + it { expect(subject.prune(filters: {dangling: {"1": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"dangling\":{\"1\":true}}") } + it { expect(subject.prune(filters: {until: {"10m": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"until\":{\"10m\":true}}") } + it { expect(subject.prune(filters: {label: {"LABEL": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"label\":{\"LABEL\":true}}") } + it { expect(subject.prune(filters: {label: {"LABEL": true}, dangling: {"1": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"label\":{\"LABEL\":true},\"dangling\":{\"1\":true}}") } + end + describe ".remove" do + it { expect(subject.remove("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/dockerapi") } + it { expect(subject.remove("dockerapi").request_params[:method]).to eq(:delete) } + it { expect(subject.remove("dockerapi", force: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/dockerapi?force=true") } + it { expect(subject.remove("dockerapi", noprune: false).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/dockerapi?noprune=false") } + it { expect{subject.remove("dockerapi", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.remove("dockerapi", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } end + describe ".export" do + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, { headers: {'Content-Type': 'text'}, body: 'a', status: 200 }) } + after(:all) do + Excon.unstub({ :method => :get }) + File.delete(File.expand_path("~/exported_image.tar")) + end + it { expect{File.open(File.expand_path("~/exported_image.tar"))}.to raise_error(Errno::ENOENT) } + it { expect(subject.export("dockerapi", "~/exported_image.tar").status).to eq(200) } + it { expect{File.open(File.expand_path("~/exported_image.tar"))}.not_to raise_error } + end + describe ".import" do + before(:all) do + Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :post, :port => 2375 }, { status: 200 }) + File.write("import.tar", "\u0000") + end + after(:all) do + Excon.unstub({ :method => :post }) + File.delete(File.expand_path("import.tar")) + end + it { expect(subject.import("import.tar").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/load") } + it { expect(subject.import("import.tar", quiet: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/load?quiet=true") } + it { expect(subject.import("import.tar", quiet: true).request_params[:method]).to eq(:post) } + it { expect(subject.import("import.tar", quiet: true).request_params[:headers]["Content-Type"]).to eq("application/x-tar") } + it { expect{subject.import("import.tar", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } + end describe ".push" do + it { expect(subject.push("localhost:5000/dockerapi", {},{username: "janedoe", password: "password"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/localhost:5000/dockerapi/push") } + it { expect(subject.push("localhost:5000/dockerapi", {},{username: "janedoe", password: "password"}).request_params[:method]).to eq(:post) } + it { expect(subject.push("localhost:5000/dockerapi", {},{username: "janedoe", password: "password"}).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImphbmVkb2UiLCJwYXNzd29yZCI6InBhc3N3b3JkIn0=") } it { expect{subject.push("localhost:5000/push:1", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } it { expect{subject.push("localhost:5000/push:1")}.to raise_error(Docker::API::Error, "Provide authentication parameters to push an image") } - - describe "returns status 200 with error message" do - subject { described_class.new.push("localhost:5000/doesn-exist", {},{username: "janedoe", password: "password"}) } - it { expect(subject.status).to be(200) } - it { expect(subject.body).to match(/(An image does not exist locally with the tag)/) } - end end - describe ".commit" do - container = "rspec-test" - before(:all) { Docker::API::Container.new.create({name: container}, {Image: image}) } - after(:all) { Docker::API::Container.new.remove(container) } - it { expect(subject.commit.status).to eq(301) } - it { expect(subject.commit(container: container).status).to eq(201) } - it { expect(subject.commit(container: container, repo: "dockerapi/#{container}:1" ).status).to eq(201) } - it { expect(subject.commit(container: container, repo: "dockerapi/#{container}", tag: "2" ).status).to eq(201) } - it { expect(subject.commit(container: container, repo: "dockerapi/#{container}", tag: "3", comment: "Comment from commit" ).status).to eq(201) } - it { expect(subject.commit(container: container, repo: "dockerapi/#{container}", tag: "4", author: "dockerapi" ).status).to eq(201) } - it { expect(subject.commit(container: container, repo: "dockerapi/#{container}", tag: "5", pause: false ).status).to eq(201) } - it { expect(subject.commit({container: container, repo: "dockerapi/#{container}:6"}, {OpenStdin: false, Cmd: "echo dockerapi", Entrypoint: [""]} ).status).to eq(201) } - it { expect(subject.commit(container: "doesn-exist").status).to eq(404) } + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :method => :get, :port => 2375 }, { headers: {'Content-Type': 'application/json'}, body: '{"Config": {}}', status: 200 }) } + after(:all) { Excon.unstub({ :method => :get }) } + it { expect(subject.commit(container: "dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi") } + it { expect(subject.commit(container: "dockerapi").request_params[:method]).to eq(:post) } + it { expect(subject.commit(container: "dockerapi").request_params[:body]).to eq("{}") } + + it { expect(subject.commit(container: "dockerapi", repo: "dockerapi/dockerapi:1" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi&repo=dockerapi/dockerapi:1") } + it { expect(subject.commit(container: "dockerapi", repo: "dockerapi/dockerapi", tag: "2" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi&repo=dockerapi/dockerapi&tag=2") } + it { expect(subject.commit(container: "dockerapi", repo: "dockerapi/dockerapi", tag: "3", comment: "Comment from commit" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi&repo=dockerapi/dockerapi&tag=3&comment=Commentfromcommit") } + it { expect(subject.commit(container: "dockerapi", repo: "dockerapi/dockerapi", tag: "4", author: "dockerapi" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi&repo=dockerapi/dockerapi&tag=4&author=dockerapi") } + it { expect(subject.commit(container: "dockerapi", repo: "dockerapi/dockerapi", tag: "5", pause: false ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi&repo=dockerapi/dockerapi&tag=5&pause=false") } + it { expect(subject.commit({container: "dockerapi", repo: "dockerapi/dockerapi:6"}, {OpenStdin: false, Cmd: "echo dockerapi", Entrypoint: [""]} ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/commit?container=dockerapi&repo=dockerapi/dockerapi:6") } + it { expect(subject.commit({container: "dockerapi", repo: "dockerapi/dockerapi:6"}, {OpenStdin: false, Cmd: "echo dockerapi", Entrypoint: [""]} ).request_params[:body]).to eq("{\"OpenStdin\":false,\"Cmd\":\"echo dockerapi\",\"Entrypoint\":[\"\"]}") } it { expect{subject.commit(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } it { expect{subject.commit({invalid: "invalid"}, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidParameter) } it { expect{subject.commit({}, {invalid: "invalid"})}.to raise_error(Docker::API::InvalidRequestBody) } end - - describe ".export" do - after(:all) { File.delete(File.expand_path("~/exported_image.tar")) } - it { expect{File.open(File.expand_path("~/exported_image.tar"))}.to raise_error(Errno::ENOENT) } - it { expect(subject.export(image, "~/exported_image.tar").status).to eq(200) } - it { expect{File.open(File.expand_path("~/exported_image.tar"))}.not_to raise_error } - it { expect(subject.export("doesn-exist", "~/wont-exist.tar").status).to eq(404) } - it { expect{File.open(File.expand_path("~/wont-exist.tar"))}.to raise_error(Errno::ENOENT) } - - context "having the exported image" do - describe ".import" do - it { expect(subject.import("resources/import.tar").status).to eq(200) } - it { expect(subject.import("resources/import.tar", quiet: true).status).to eq(200) } - it { expect{subject.import("resources/import.tar", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - end - end - end - end - - describe ".search" do - it { expect(subject.search.status).to eq(200) } - it { expect(subject.search(term: "busybox").status).to eq(200) } - it { expect(subject.search(term: "busybox", limit: 2).status).to eq(200) } - it { expect(subject.search(term: "busybox", filters: {"is-automated": {"true": true}}).status).to eq(200) } - it { expect(subject.search(term: "busybox", filters: {"is-official": {"true": true}}).status).to eq(200) } - it { expect(subject.search(term: "busybox", filters: {stars: {"20": true}}).status).to eq(200) } - it { expect{subject.search(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - end - - describe ".remove" do - before(:all) { described_class.new.prune(filters: {dangling: {"false": true}}) } - before(:each) { described_class.new.create(fromImage: image) } - - context "having a container" do - before(:all) { Docker::API::Container.new.create( {}, { Image: image }) } - after(:all) { Docker::API::Container.new.prune } - - it { expect(subject.remove(image).status).to eq(409) } - it { expect(subject.remove(image, force: true).status).to eq(200) } - end - it { expect(subject.remove(image).status).to eq(200) } - it { expect(subject.remove("doesn-exist").status).to eq(404) } - it { expect(subject.remove(image, noprune: false).status).to eq(200) } - it { expect(subject.remove("doesn-exist", noprune: true).status).to eq(404) } - it { expect{subject.remove(image, invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.remove("doesn-exist", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - end - - describe ".prune" do - describe "status code" do - it { expect(subject.prune.status).to eq(200) } - it { expect(subject.prune(filters: {dangling: {"true": true}}).status).to eq(200) } - it { expect(subject.prune(filters: {dangling: {"1": true}}).status).to eq(200) } - it { expect(subject.prune(filters: {dangling: {"false": true}}).status).to eq(200) } - it { expect(subject.prune(filters: {until: {"10m": true}}).status).to eq(200) } - it { expect(subject.prune(filters: {label: {"LABEL": true}}).status).to eq(200) } - it { expect(subject.prune(filters: {label: {"LABEL": true}, dangling: {"1": true}}).status).to eq(200) } - end - describe "request path" do - it { expect(subject.prune(filters: {dangling: {"true": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"dangling\":{\"true\":true}}") } - it { expect(subject.prune(filters: {dangling: {"1": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"dangling\":{\"1\":true}}") } - it { expect(subject.prune(filters: {until: {"10m": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"until\":{\"10m\":true}}") } - it { expect(subject.prune(filters: {label: {"LABEL": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"label\":{\"LABEL\":true}}") } - it { expect(subject.prune(filters: {label: {"LABEL": true}, dangling: {"1": true}}).path).to eq("/v#{Docker::API::API_VERSION}/images/prune?filters={\"label\":{\"LABEL\":true},\"dangling\":{\"1\":true}}") } - end - it { expect{subject.prune( invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - end - - describe ".build" do - it { expect(subject.build("resources/build.tar.xz").status).to eq(200) } - it { expect(subject.build("resources/build.tar.xz", q: true).status).to eq(200) } - it { expect(subject.build("resources/build.tar.xz", q: true, rm: false).status).to eq(200) } - it { expect(subject.build("resources/build.tar.xz", memory: 4000000, rm: true, forcerm:true).status).to eq(200) } - it { expect(subject.build("resources/build.tar.xz", memory: 4000000, rm: true, forcerm:true, pull:true).status).to eq(200) } - it { expect(subject.build(nil, remote: "https://github.com/nu12/dockerapi/raw/refs/heads/main/resources/build.tar.xz").status).to eq(200) } - it { expect(subject.build(nil, remote: "https://raw.githubusercontent.com/nu12/dockerapi/main/resources/Dockerfile").status).to eq(200) } - it { expect{subject.build("resources/build.tar.xz", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.build(nil, remote: "https://github.com/nu12/dockerapi/raw/refs/heads/main/resources/build.tar.xz", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.build(nil, invalid: "invalid", skip_validation: true)}.to raise_error(Docker::API::Error) } - end - - describe ".delete_cache" do - it { expect(subject.delete_cache.status).to eq(200) } - it { expect(subject.delete_cache(all:true, "keep-storage": 100000, filters: {until: {"24h": true}, inuse: {"true": true}, shared: {"true": true}}).status).to eq(200) } - it { expect{subject.delete_cache(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } - end - - describe ".distribution" do - it { expect(subject.distribution(image).status).to eq(200) } - it { expect(subject.distribution("doesn-exist").status).to eq(403) } - end -end - -RSpec.describe Docker::API::Image do - subject { described_class.new } - - describe "authentication" do - original = "registry:2.7.0" - local = "localhost:5000/janedoe/test:latest" - before(:all) do - described_class.new.create(fromImage: original) - - container = Docker::API::Container.new - container.create( {name: "registry"}, { - Image: original, - HostConfig: { - PortBindings: {"5000/tcp": [ {HostIp: "0.0.0.0", HostPort: "5000"} ] }, - Binds: [ - "#{File.expand_path(File.dirname(__FILE__))}/../../resources/registry_authentication:/auth", - "#{File.expand_path(File.dirname(__FILE__))}/../../resources/registry_authentication:/certs"]}, - Env: [ - "REGISTRY_AUTH=htpasswd", - "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm", - "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd", - "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry_auth.crt", - "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key", - - ], - }) - container.start("registry") - - described_class.new.tag(original, repo: local) - - Docker::API.print_response_to_stdout = true - end - - it { expect(Docker::API::Container.new.list.json.size).to be > 0 } - - describe ".push" do - it { expect(subject.push(local, {}, {username: "janedoe", password: "password"}).status).to eq(200) } - it { expect(subject.push(local, {}, {username: "janedoe", password: "wrong-password"}).status).to eq(200) } - end - describe ".create" do - it { expect(subject.create({fromImage: local}, {username: "janedoe", password: "password"}).status).to eq(200) } - it { expect(subject.create({fromImage: "localhost:5000/janedoe/doesnt-exist:latest"}, {username: "janedoe", password: "password"}).status).to eq(404) } - it { expect(subject.create({fromImage: local}, {username: "janedoe", password: "wrong-password"}).status).to eq(500) } - end - - describe ".distribute" do - it { expect(subject.distribution(local, {username: "janedoe", password: "password"}).status).to eq(200) } - it { expect(subject.distribution("localhost:5000/janedoe/doesnt-exist:latest", {username: "janedoe", password: "password"}).status).to eq(404) } - it { expect(subject.distribution(local, {username: "janedoe", password: "wrong-password"}).status).to eq(401) } + context "from repository without authentication" do + it { expect(subject.create(fromImage: "nginx").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/create?fromImage=nginx") } + it { expect(subject.create(fromImage: "nginx").request_params[:method]).to eq(:post) } + end + context "from local tar file" do + before(:all) { File.write("create.tar", "\u0000") } + after(:all) { File.delete(File.expand_path("create.tar")) } + it { expect(subject.create(fromSrc: "create.tar").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/create?fromSrc=-") } + it { expect(subject.create(fromSrc: "create.tar", repo: "dockerapi", message: "Imported with dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/create?fromSrc=-&repo=dockerapi&message=Importedwithdockerapi") } + end + context "from remote tar file" do + let(:url) { "https://address/to/image.tar" } + it { expect(subject.create(fromSrc: url).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/create?fromSrc=https://address/to/image.tar") } + it { expect(subject.create(fromSrc: url, repo: "dockerapi", message: "Imported with dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/images/create?fromSrc=https://address/to/image.tar&repo=dockerapi&message=Importedwithdockerapi") } + end end - - after(:all) do - Docker::API.print_response_to_stdout = false - - container = Docker::API::Container.new - container.stop("registry") - container.remove("registry") - - described_class.new.remove(original) - - Docker::API::Volume.new.prune + describe ".build" do + before(:all) { File.write("build.tar.xz", "\u0000") } + after(:all) { File.delete(File.expand_path("build.tar.xz")) } + + it { expect(subject.build("build.tar.xz").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build") } + it { expect(subject.build("build.tar.xz").request_params[:method]).to eq(:post) } + it { expect(subject.build("build.tar.xz").request_params[:headers]["Content-type"]).to eq("application/x-tar") } + it { expect(subject.build("build.tar.xz", q: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build?q=true") } + it { expect(subject.build("build.tar.xz", q: true, rm: false).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build?q=true&rm=false") } + it { expect(subject.build("build.tar.xz", memory: 4000000, rm: true, forcerm:true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build?memory=4000000&rm=true&forcerm=true") } + it { expect(subject.build("build.tar.xz", memory: 4000000, rm: true, forcerm:true, pull:true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build?memory=4000000&rm=true&forcerm=true&pull=true") } + it { expect(subject.build(nil, remote: "https://address/to/image.tar.xz").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build?remote=https://address/to/image.tar.xz") } + it { expect(subject.build(nil, remote: "https://address/to/Dockerfile").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build?remote=https://address/to/Dockerfile") } + + it { expect{subject.build("build.tar.xz", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.build(nil, remote: "https://address/to/image.tar.xz", invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.build(nil, invalid: "invalid", skip_validation: true)}.to raise_error(Docker::API::Error) } + end + describe ".delete_cache" do + it { expect(subject.delete_cache.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build/prune") } + it { expect(subject.delete_cache.request_params[:method]).to eq(:post) } + it { expect(subject.delete_cache(all:true, "keep-storage": 100000, filters: {until: {"24h": true}, inuse: {"true": true}, shared: {"true": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/build/prune?all=true&keep-storage=100000&filters={\"until\":{\"24h\":true},\"inuse\":{\"true\":true},\"shared\":{\"true\":true}}") } + it { expect{subject.delete_cache(invalid: "invalid")}.to raise_error(Docker::API::InvalidParameter) } end - end end \ No newline at end of file diff --git a/spec/endpoints/network_spec.rb b/spec/endpoints/network_spec.rb index 4e06fd0..146420c 100644 --- a/spec/endpoints/network_spec.rb +++ b/spec/endpoints/network_spec.rb @@ -1,79 +1,74 @@ RSpec.describe Docker::API::Network do + subject { described_class.new(stub_connection) } - subject { described_class.new } - describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: { dangling: {"true": true} }).status).to eq(200) } - it { expect(subject.list(filters: { driver: {"bridge": true} }).status).to eq(200) } - it { expect(subject.list(filters: { name: {"bridge": true} }).status).to eq(200) } - it { expect(subject.list(filters: { scope: {"local": true} }).status).to eq(200) } - it { expect(subject.list(filters: { type: {"custom": true} }).status).to eq(200) } - it { expect(subject.list(filters: { type: {"builtin": true} }).status).to eq(200) } - it { expect{subject.list( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - end + it { is_expected.to respond_to(:list) } + it { is_expected.to respond_to(:details) } + it { is_expected.to respond_to(:create) } + it { is_expected.to respond_to(:connect) } + it { is_expected.to respond_to(:disconnect) } + it { is_expected.to respond_to(:remove) } + it { is_expected.to respond_to(:prune) } - describe ".details" do - it { expect(subject.details( "bridge" ).status).to eq(200) } - it { expect(subject.details( "doesn-exist" ).status).to eq(404) } - it { expect(subject.details( "bridge", verbose: true ).status).to eq(200) } - it { expect(subject.details( "bridge", scope: "local" ).status).to eq(200) } - it { expect{subject.details( "bridge", invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - end + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } - describe ".create" do - it { expect(subject.create.status).to eq(400) } - it do - expect(subject.create( - Name: "rspec-network", - CheckDuplicate: true, - Driver: "bridge", - Internal: true, - Attachable: true, - EnableIPv6: false - ).status).to eq(201) + describe ".list" do + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: { dangling: {"true": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks?filters={\"dangling\":{\"true\":true}}") } + it { expect(subject.list(filters: { driver: {"bridge": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks?filters={\"driver\":{\"bridge\":true}}") } + it { expect(subject.list(filters: { name: {"bridge": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks?filters={\"name\":{\"bridge\":true}}") } + it { expect(subject.list(filters: { scope: {"local": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks?filters={\"scope\":{\"local\":true}}") } + it { expect(subject.list(filters: { type: {"custom": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks?filters={\"type\":{\"custom\":true}}") } + it { expect(subject.list(filters: { type: {"builtin": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks?filters={\"type\":{\"builtin\":true}}") } + it { expect{subject.list( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } end - it { expect{subject.create( invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } - - end - context "having a container to connect" do - before(:all) do - Docker::API::Image.new.create(fromImage: "busybox:1.31.1-uclibc") - Docker::API::Container.new.create({name: "rspec-container"}, {Image: "busybox:1.31.1-uclibc"} ) + describe ".details" do + it { expect(subject.details( "bridge" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/bridge") } + it { expect(subject.details( "bridge" ).request_params[:method]).to eq(:get) } + it { expect(subject.details( "bridge", verbose: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/bridge?verbose=true") } + it { expect(subject.details( "bridge", scope: "local" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/bridge?scope=local") } + it { expect{subject.details( "bridge", invalid: true )}.to raise_error(Docker::API::InvalidParameter) } end - after(:all) do - Docker::API::Container.new.remove("rspec-container") - Docker::API::Image.new.remove("busybox:1.31.1-uclibc") + + describe ".create" do + it { expect(subject.create( Name: "rspec-network",CheckDuplicate: true,Driver: "bridge",Internal: true,Attachable: true,EnableIPv6: false).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/create") } + it { expect(subject.create( Name: "rspec-network",CheckDuplicate: true,Driver: "bridge",Internal: true,Attachable: true,EnableIPv6: false).request_params[:method]).to eq(:post) } + it { expect(subject.create( Name: "rspec-network",CheckDuplicate: true,Driver: "bridge",Internal: true,Attachable: true,EnableIPv6: false).request_params[:body]).to eq("{\"Name\":\"rspec-network\",\"CheckDuplicate\":true,\"Driver\":\"bridge\",\"Internal\":true,\"Attachable\":true,\"EnableIPv6\":false}") } + it { expect(subject.create( Name: "rspec-network",CheckDuplicate: true,Driver: "bridge",Internal: true,Attachable: true,EnableIPv6: false).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.create( invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } end describe ".connect" do - it { expect(subject.connect("rspec-network").status).to eq(400) } - it { expect(subject.connect("rspec-network", Container: "rspec-container").status).to eq(200) } + it { expect(subject.connect("rspec-network", Container: "rspec-container").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/rspec-network/connect") } + it { expect(subject.connect("rspec-network", Container: "rspec-container").request_params[:method]).to eq(:post) } + it { expect(subject.connect("rspec-network", Container: "rspec-container").request_params[:body]).to eq("{\"Container\":\"rspec-container\"}") } + it { expect(subject.connect("rspec-network", Container: "rspec-container").request_params[:headers]["Content-Type"]).to eq("application/json") } it { expect{subject.connect( "rspec-network", invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } end describe ".disconnect" do - it { expect(subject.disconnect("rspec-network").status).to eq(400) } - it do - subject.connect("rspec-network", Container: "rspec-container") - expect(subject.disconnect("rspec-network", Container: "rspec-container", Force: true).status).to eq(200) - end - it { expect(subject.disconnect("rspec-network", Container: "doesn-exist").status).to eq(404) } - it { expect(subject.disconnect("doesn-exist", Container: "rspec-container").status).to eq(500) } - it { expect{subject.disconnect( "rspec-network", invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } + it { expect(subject.disconnect("rspec-network", Container: "rspec-container").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/rspec-network/disconnect") } + it { expect(subject.disconnect("rspec-network", Container: "rspec-container").request_params[:method]).to eq(:post) } + it { expect(subject.disconnect("rspec-network", Container: "rspec-container").request_params[:body]).to eq("{\"Container\":\"rspec-container\"}") } + it { expect(subject.disconnect("rspec-network", Container: "rspec-container").request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.disconnect( "rspec-network", invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } end - end - describe ".remove" do - it { expect(subject.remove( "rspec-network" ).status).to eq(204) } - it { expect(subject.remove( "doesn-exist" ).status).to eq(404) } - end + describe ".remove" do + it { expect(subject.remove("rspec-network").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/rspec-network") } + it { expect(subject.remove("rspec-network").request_params[:method]).to eq(:delete) } + end - describe ".prune" do - it { expect(subject.prune.status).to eq(200) } - it { expect(subject.prune(filters: { until: {"10m": true} }).status).to eq(200) } - it { expect(subject.prune(filters: { until: {"1h30m": true} }).status).to eq(200) } - it { expect(subject.prune(filters: { label: {"key=value": true} }).status).to eq(200) } - it { expect{subject.prune( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + describe ".prune" do + it { expect(subject.prune.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/prune") } + it { expect(subject.prune.request_params[:method]).to eq(:post) } + it { expect(subject.prune(filters: { until: {"10m": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/prune?filters={\"until\":{\"10m\":true}}") } + it { expect(subject.prune(filters: { until: {"1h30m": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/prune?filters={\"until\":{\"1h30m\":true}}") } + it { expect(subject.prune(filters: { label: {"key=value": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/networks/prune?filters={\"label\":{\"key=value\":true}}") } + it { expect{subject.prune( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + end end end \ No newline at end of file diff --git a/spec/endpoints/node_spec.rb b/spec/endpoints/node_spec.rb index effbbc5..541dd6f 100644 --- a/spec/endpoints/node_spec.rb +++ b/spec/endpoints/node_spec.rb @@ -1,57 +1,47 @@ RSpec.describe Docker::API::Node do - ip_address = get_api_ip_address - subject { described_class.new } + subject { described_class.new(stub_connection) } + it { is_expected.to respond_to(:list) } it { is_expected.to respond_to(:details) } it { is_expected.to respond_to(:delete) } it { is_expected.to respond_to(:update) } - it { expect(subject.list.status).to eq(503) } - context "having a swarm cluster" do - before(:all) { Docker::API::Swarm.new.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567"}) } - after(:all) { Docker::API::Swarm.new.leave(force: true) } - let(:id) { Docker::API::Node.new.list.json.first["ID"] } - + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } + describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: {label: ["key=value"]}).status).to eq(200) } - it { expect(subject.list(filters: {"node.label": ["key=value"]}).status).to eq(200) } - it { expect(subject.list(filters: {membership: {"accepted": true}}).status).to eq(200) } - it { expect(subject.list(filters: {membership: {"pending": true}}).status).to eq(200) } - it { expect(subject.list(filters: {name: {"node_name": true}}).status).to eq(200) } + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: {label: ["key=value"]}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes?filters={\"label\":[\"key=value\"]}") } + it { expect(subject.list(filters: {"node.label": ["key=value"]}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes?filters={\"node.label\":[\"key=value\"]}") } + it { expect(subject.list(filters: {membership: {"accepted": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes?filters={\"membership\":{\"accepted\":true}}") } + it { expect(subject.list(filters: {membership: {"pending": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes?filters={\"membership\":{\"pending\":true}}") } + it { expect(subject.list(filters: {name: {"node_name": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes?filters={\"name\":{\"node_name\":true}}") } it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } end describe ".details" do - it { expect(subject.details(id).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } + it { expect(subject.details("id").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes/id") } + it { expect(subject.details("id").request_params[:method]).to eq(:get) } end describe ".update" do - let(:version) { Docker::API::Node.new.list.json.first["Version"]["Index"] } - - it { expect(subject.update(id, {version: version}, {Role: "manager", Availability: "drain" }).status).to eq(200) } - it { expect(subject.update(id, {version: version}, {Role: "manager", Availability: "pause" }).status).to eq(200) } - it { expect(subject.update(id, {version: version}, {Role: "manager", Availability: "active" }).status).to eq(200) } - it { expect(subject.update(id, {version: version}, {Role: "worker", Availability: "active" }).data[:body]).to match(/attempting to demote the last manager of the swarm/) } - it { expect(subject.update(id, {version: version}, {Name: "node-name", Role: "manager", Availability: "active" }).status).to eq(200) } - it { expect(subject.update(id, {version: version}, {Labels: {"KEY": "VALUE"}, Role: "manager", Availability: "active" }).status).to eq(200) } - it { expect(subject.update(id, {}, {Labels: {"KEY": "VALUE"}, Role: "manager", Availability: "active" }).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Name: "node-name" }).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Labels: {"KEY": "VALUE"}}).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Role: "manager"}).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Role: "worker"}).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Availability: "pause"}).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Availability: "drain"}).status).to eq(400) } - it { expect(subject.update(id, {version: version}, {Availability: "active"}).status).to eq(400) } - it { expect{subject.update(id, {invalid: true}, {})}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.update(id, {version: version}, {invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } + it { expect(subject.update("id", {version: "version"}, {Role: "manager", Availability: "drain" }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes/id/update?version=version") } + it { expect(subject.update("id", {version: "version"}, {Role: "manager", Availability: "pause" }).request_params[:method]).to eq(:post) } + it { expect(subject.update("id", {version: "version"}, {Role: "manager", Availability: "active" }).request_params[:body]).to eq("{\"Role\":\"manager\",\"Availability\":\"active\"}") } + it { expect(subject.update("id", {version: "version"}, {Role: "manager", Availability: "drain" }).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect(subject.update("id", {version: "version"}, {Name: "node-name", Role: "manager", Availability: "active" }).request_params[:body]).to eq("{\"Name\":\"node-name\",\"Role\":\"manager\",\"Availability\":\"active\"}") } + it { expect(subject.update("id", {version: "version"}, {Labels: {"KEY": "VALUE"}, Role: "manager", Availability: "active" }).request_params[:body]).to eq("{\"Labels\":{\"KEY\":\"VALUE\"},\"Role\":\"manager\",\"Availability\":\"active\"}") } + it { expect{subject.update("id", {invalid: true}, {})}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.update("id", {version: "version"}, {invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } end describe ".delete" do - it { expect(subject.delete(id).status).to eq(400) } - it { expect(subject.delete(id, force: true).status).to eq(400) } - it { expect{subject.delete(id, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + it { expect(subject.delete("id").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes/id") } + it { expect(subject.delete("id").request_params[:method]).to eq(:delete) } + it { expect(subject.delete("id", force: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/nodes/id?force=true") } + it { expect{subject.delete("id", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } end end diff --git a/spec/endpoints/plugin_spec.rb b/spec/endpoints/plugin_spec.rb index e27e2e3..c062348 100644 --- a/spec/endpoints/plugin_spec.rb +++ b/spec/endpoints/plugin_spec.rb @@ -1,8 +1,6 @@ RSpec.describe Docker::API::Plugin do - plugin = "vieux/sshfs" - myplugin = "rspec-plugin" - privileges = Docker::API::Plugin.new.privileges(remote: plugin).json - subject { described_class.new } + subject { described_class.new(stub_connection) } + it { is_expected.to respond_to(:list) } it { is_expected.to respond_to(:privileges) } it { is_expected.to respond_to(:install) } @@ -15,95 +13,84 @@ it { is_expected.to respond_to(:push) } it { is_expected.to respond_to(:configure) } - describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: {capability: { "name": true }}).status).to eq(200) } - it { expect(subject.list(filters: {enable: { "true": true }}).status).to eq(400) } - it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.list(invalid: true, skip_validation: true)}.not_to raise_error } - end + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } - describe ".privileges" do - it { expect(subject.privileges.status).to eq(500) } - it { expect(subject.privileges(remote: plugin).status).to eq(200) } - it { expect{subject.privileges(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.privileges(invalid: true, skip_validation: true)}.not_to raise_error } - end + describe ".list" do + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: {capability: { "name": true }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins?filters={\"capability\":{\"name\":true}}") } + it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + end - describe ".install" do - after(:each) { subject.remove(plugin) } - it { expect(subject.install.status).to eq(500) } - it { expect(subject.install(remote: plugin).status).to eq(200) } - it { expect(subject.install(remote: plugin).body).to match(/incorrect/) } - it { expect(subject.install(remote: plugin, name: "local-name").status).to eq(200) } - it { expect(subject.install(remote: plugin, name: "local-name").body).to match(/incorrect/) } - it { expect(subject.install({remote: plugin}, privileges).status).to eq(200) } - it { expect(subject.install({remote: plugin}, privileges).body).not_to match(/incorrect/) } - it { expect{subject.install(remote: plugin, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.install(remote: plugin, invalid: true, skip_validation: true)}.not_to raise_error } - end + describe ".privileges" do + it { expect(subject.privileges(remote: "remote").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/privileges?remote=remote") } + it { expect(subject.privileges(remote: "remote").request_params[:method]).to eq(:get) } + it { expect{subject.privileges(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + end - context "after .install" do - before(:all) { described_class.new.install({remote: plugin}, privileges) } - after(:all) { described_class.new.remove(plugin, force: true) } + describe ".install" do + it { expect(subject.install(remote: "plugin", name: "local-name").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/pull?remote=plugin&name=local-name") } + it { expect(subject.install(remote: "plugin", name: "local-name").request_params[:method]).to eq(:post) } + it { expect(subject.install({remote: "plugin", name: "local-name"}, {a: "b"}).request_params[:body]).to eq("{\"a\":\"b\"}") } + it { expect(subject.install({remote: "plugin", name: "local-name"}, {a: "b"}).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect(subject.install({remote: "plugin", name: "local-name"}, {a: "b"}, {username: "janedoe", password: "password"}).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImphbmVkb2UiLCJwYXNzd29yZCI6InBhc3N3b3JkIn0=") } + it { expect{subject.install(remote: "plugin", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + end describe ".details" do - it { expect(subject.details(plugin).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } + it { expect(subject.details("plugin").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin/json") } + it { expect(subject.details("plugin").request_params[:method]).to eq(:get) } end describe ".configure" do - it { expect(subject.configure(plugin, ["DEBUG=1"]).status).to eq(204) } - it { expect(subject.configure("doesn-exist", ["DEBUG=1"]).status).to eq(404) } + it { expect(subject.configure("plugin", ["DEBUG=1"]).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin/set") } + it { expect(subject.configure("plugin", ["DEBUG=1"]).request_params[:method]).to eq(:post) } + it { expect(subject.configure("plugin", ["DEBUG=1"]).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect(subject.configure("plugin", ["DEBUG=1"]).request_params[:body]).to eq("[\"DEBUG=1\"]") } end describe ".enable" do - before(:each) { subject.disable(plugin) } - it { expect(subject.enable(plugin).status).to eq(500) } - it { expect(subject.enable(plugin, timeout: 0).status).to eq(200) } - it { expect(subject.enable("doesn-exist").status).to eq(500) } - it { expect(subject.enable("doesn-exist", timeout: 10).status).to eq(404) } - it { expect{subject.enable(plugin, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.enable(plugin, invalid: true, skip_validation: true)}.not_to raise_error } + it { expect(subject.enable("plugin", timeout: 0).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin/enable?timeout=0") } + it { expect(subject.enable("plugin", timeout: 0).request_params[:method]).to eq(:post) } + it { expect{subject.enable("plugin", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + end describe ".disable" do - before(:each) { subject.enable(plugin, timeout: 0) } - it { expect(subject.disable(plugin).status).to eq(200) } - it { expect(subject.enable("doesn-exist").status).to eq(500) } + it { expect(subject.disable("plugin").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin/disable") } + it { expect(subject.disable("plugin").request_params[:method]).to eq(:post) } end describe ".upgrade" do - before(:all) { described_class.new.disable(plugin) } - it { expect(subject.upgrade(plugin, {remote: plugin}).status).to eq(200) } - it { expect(subject.upgrade(plugin, {remote: plugin}).body).to match(/incorrect/) } - it { expect(subject.upgrade(plugin, {remote: plugin}, privileges).status).to eq(200) } - it { expect(subject.upgrade(plugin, {remote: plugin}, privileges).body).not_to match(/incorrect/) } - it { expect{subject.upgrade(plugin, remote: plugin, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.upgrade(plugin, remote: plugin, invalid: true, skip_validation: true)}.not_to raise_error } + it { expect(subject.upgrade("plugin", {remote: "remote"}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin/upgrade?remote=remote") } + it { expect(subject.upgrade("plugin", {remote: "remote"}).request_params[:method]).to eq(:post) } + it { expect(subject.upgrade("plugin", {remote: "remote"}).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect(subject.upgrade("plugin", {remote: "remote"}, nil, {username: "janedoe", password: "password"}).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImphbmVkb2UiLCJwYXNzd29yZCI6InBhc3N3b3JkIn0=") } + it { expect(subject.upgrade("plugin", {remote: "remote"}, nil, {username: "janedoe", password: "password"}).request_params[:body]).to eq("null") } + it { expect{subject.upgrade("plugin", remote: "remote", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } end describe ".remove" do - it { expect(subject.remove(plugin).status).to eq(200) } - it { expect(subject.remove(plugin).status).to eq(404) } - it { expect(subject.remove(plugin, force: true).status).to eq(404) } - it { expect{subject.remove(plugin, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.remove(plugin, invalid: true, skip_validation: true)}.not_to raise_error } + it { expect(subject.remove("plugin").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin") } + it { expect(subject.remove("plugin").request_params[:method]).to eq(:delete) } + it { expect(subject.remove("plugin", force: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/plugin?force=true") } + it { expect{subject.remove("plugin", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } end - end - context "create a plugin" do - after(:all) { described_class.new.remove(myplugin) } describe ".create" do - it { expect(subject.create(myplugin, "resources/plugin.tar").status).to eq(204) } - it { expect(subject.create(myplugin, "resources/plugin.tar").status).to eq(409) } - it { expect{subject.create(myplugin, "doesn-exist")}.to raise_error(Errno::ENOENT) } + before(:all) { File.write("plugin.tar", "\u0000") } + after(:all) { File.delete(File.expand_path("plugin.tar")) } + it { expect(subject.create("myplugin", "plugin.tar").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/create?name=myplugin") } + it { expect(subject.create("myplugin", "plugin.tar").request_params[:method]).to eq(:post) } + it { expect(subject.create("myplugin", "plugin.tar").request_params[:body]).to eq("\u0000") } end describe ".push" do - it { expect(subject.push(myplugin).status).to eq(200) } - it { expect(subject.push(myplugin).json.last[:error]).not_to be(nil) } - it { expect(subject.push("doesn-exist").status).to eq(404) } + it { expect(subject.push("myplugin").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/plugins/myplugin/push") } + it { expect(subject.push("myplugin").request_params[:method]).to eq(:post) } + it { expect(subject.push("myplugin", {username: "janedoe", password: "password"}).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImphbmVkb2UiLCJwYXNzd29yZCI6InBhc3N3b3JkIn0=") } end end end \ No newline at end of file diff --git a/spec/endpoints/secret_spec.rb b/spec/endpoints/secret_spec.rb index e7cc9aa..9b14020 100644 --- a/spec/endpoints/secret_spec.rb +++ b/spec/endpoints/secret_spec.rb @@ -1,8 +1,5 @@ RSpec.describe Docker::API::Secret do - ip_address = get_api_ip_address - name = "rspec-secret" - - subject { described_class.new } + subject { described_class.new(stub_connection) } it { is_expected.to respond_to(:list) } it { is_expected.to respond_to(:create) } @@ -10,57 +7,47 @@ it { is_expected.to respond_to(:update) } it { is_expected.to respond_to(:delete) } - it { expect(subject.list.status).to eq(503) } - - context "having a swarm cluster" do - before(:all) { Docker::API::Swarm.new.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567"}) } - after(:all) { Docker::API::Swarm.new.leave(force: true) } + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: {id: { "secret-id": true }}).status).to eq(200) } - it { expect(subject.list(filters: {label: { "label=key": true }}).status).to eq(200) } - it { expect(subject.list(filters: {name: { "secret-name": true }}).status).to eq(200) } - it { expect(subject.list(filters: {names: { "secret-name": true }}).status).to eq(200) } + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: {id: { "secret-id": true }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets?filters={\"id\":{\"secret-id\":true}}") } + it { expect(subject.list(filters: {label: { "label=key": true }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets?filters={\"label\":{\"label=key\":true}}") } + it { expect(subject.list(filters: {name: { "secret-name": true }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets?filters={\"name\":{\"secret-name\":true}}") } + it { expect(subject.list(filters: {names: { "secret-name": true }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets?filters={\"names\":{\"secret-name\":true}}") } it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } it { expect{subject.list(invalid: true, skip_validation: true)}.not_to raise_error } end describe ".create" do - it { expect(subject.create.status).to eq(400) } - it { expect(subject.create(Name: name).status).to eq(400) } - it { expect(subject.create({Name: name,Labels: {foo: "bar"}, - Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).status).to eq(201) } - it { expect(subject.create({Name: name, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).status).to eq(409) } + it { expect(subject.create({Name: "secret",Labels: {foo: "bar"}, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets/create") } + it { expect(subject.create({Name: "secret",Labels: {foo: "bar"}, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).request_params[:method]).to eq(:post) } + it { expect(subject.create({Name: "secret",Labels: {foo: "bar"}, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).request_params[:body]).to eq("{\"Name\":\"secret\",\"Labels\":{\"foo\":\"bar\"},\"Data\":\"VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg==\"}") } + it { expect(subject.create({Name: "secret",Labels: {foo: "bar"}, Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="}).request_params[:headers]["Content-Type"]).to eq("application/json") } it { expect{subject.create(invalid: true)}.to raise_error(Docker::API::InvalidRequestBody) } it { expect{subject.create(invalid: true, skip_validation: true)}.not_to raise_error } end - context "after .create" do - describe ".details" do - it { expect(subject.details(name).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } - it { expect(subject.details(name).json["ID"]).not_to be(nil) } - it { expect(subject.details(name).json["Version"]["Index"]).not_to be(nil) } - end - - describe ".update" do - let(:version) { subject.details(name).json["Version"]["Index"] } - let(:spec) { subject.details(name).json["Spec"] } - - it { expect(subject.update("doesn-exist", {version: version}, spec).status).to eq(404) } - it { expect(subject.update(name, {version: version}, spec).status).to eq(200) } - it { expect{subject.update(name, invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.update(name, {version: version}, {invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } - - end + describe ".details" do + it { expect(subject.details("secret").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets/secret") } + it { expect(subject.details("secret").request_params[:method]).to eq(:get) } + end - describe ".delete" do - it { expect(subject.delete(name).status).to eq(204) } - it { expect(subject.delete(name).status).to eq(404) } - end + describe ".update" do + it { expect(subject.update("secret", {version: "version"}, {}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets/secret/update?version=version") } + it { expect(subject.update("secret", {version: "version"}, {}).request_params[:method]).to eq(:post) } + it { expect(subject.update("secret", {version: "version"}, {}).request_params[:body]).to eq("{}") } + it { expect(subject.update("secret", {version: "version"}, {}).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.update("secret", invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.update("secret", {version: "version"}, {invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } end + describe ".delete" do + it { expect(subject.delete("secret").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/secrets/secret") } + it { expect(subject.delete("secret").request_params[:method]).to eq(:delete) } + end end - end \ No newline at end of file diff --git a/spec/endpoints/service_spec.rb b/spec/endpoints/service_spec.rb index 8831a1f..89a7f92 100644 --- a/spec/endpoints/service_spec.rb +++ b/spec/endpoints/service_spec.rb @@ -1,84 +1,69 @@ RSpec.describe Docker::API::Service do - service = "rspec-service" - image = "busybox:1.31.1-uclibc" - ip_address = get_api_ip_address - subject { described_class.new } + it { is_expected.to respond_to(:list) } it { is_expected.to respond_to(:details) } it { is_expected.to respond_to(:create) } it { is_expected.to respond_to(:delete) } it { is_expected.to respond_to(:update) } it { is_expected.to respond_to(:logs) } - it { expect(subject.list.status).to eq(503) } - context "having a swarm cluster" do - before(:all) { Docker::API::Swarm.new.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567"}) } - after(:all) do - Docker::API::Swarm.new.leave(force: true) - Docker::API::Image.new.prune(filters: {dangling: {"true": true}}) - end + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(status:true).status).to eq(200) } - it { expect(subject.list(filters: {id: ["9mnpnzenvg8p8tdbtq4wvbkcz"]}).status).to eq(200) } - it { expect(subject.list(filters: {label: ["key=value"]}).status).to eq(200) } - it { expect(subject.list(filters: {mode: ["replicated", "global"]}).status).to eq(200) } - it { expect(subject.list(filters: {name: ["service-name"]}).status).to eq(200) } + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(status:true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services?status=true") } + it { expect(subject.list(filters: {id: ["9mnpnzenvg8p8tdbtq4wvbkcz"]}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services?filters={\"id\":[\"9mnpnzenvg8p8tdbtq4wvbkcz\"]}") } + it { expect(subject.list(filters: {label: ["key=value"]}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services?filters={\"label\":[\"key=value\"]}") } + it { expect(subject.list(filters: {mode: ["replicated", "global"]}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services?filters={\"mode\":[\"replicated\",\"global\"]}") } + it { expect(subject.list(filters: {name: ["service-name"]}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services?filters={\"name\":[\"service-name\"]}") } it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } end describe ".create" do - it { expect(subject.create({Name: service, Labels: ["KEY=VALUE"]}).status).to eq(400) } - - it { expect(subject.create({Name: service, - TaskTemplate: {ContainerSpec: { Image: image }}, - Mode: { Replicated: { Replicas: 2 } }, - EndpointSpec: { Ports: [ - {Protocol: "tcp", PublishedPort: 8080, TargetPort: 80} - ] } - }).status).to eq(201) } - - it { expect(subject.create({Name: service, TaskTemplate: {ContainerSpec: { Image: image }}}).status).to eq(409) } + it { expect(subject.create({Name: "dockerapi", TaskTemplate: {ContainerSpec: { Image: "busybox:1.31.1-uclibc" }},Mode: { Replicated: { Replicas: 2 } },EndpointSpec: { Ports: [{Protocol: "tcp", PublishedPort: 8080, TargetPort: 80}] }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/create") } + it { expect(subject.create({Name: "dockerapi", TaskTemplate: {ContainerSpec: { Image: "busybox:1.31.1-uclibc" }},Mode: { Replicated: { Replicas: 2 } },EndpointSpec: { Ports: [{Protocol: "tcp", PublishedPort: 8080, TargetPort: 80}] }}).request_params[:method]).to eq(:post) } + it { expect(subject.create({Name: "dockerapi", TaskTemplate: {ContainerSpec: { Image: "busybox:1.31.1-uclibc" }},Mode: { Replicated: { Replicas: 2 } },EndpointSpec: { Ports: [{Protocol: "tcp", PublishedPort: 8080, TargetPort: 80}] }}).request_params[:body]).to eq("{\"Name\":\"dockerapi\",\"TaskTemplate\":{\"ContainerSpec\":{\"Image\":\"busybox:1.31.1-uclibc\"}},\"Mode\":{\"Replicated\":{\"Replicas\":2}},\"EndpointSpec\":{\"Ports\":[{\"Protocol\":\"tcp\",\"PublishedPort\":8080,\"TargetPort\":80}]}}") } + it { expect(subject.create({Name: "dockerapi", TaskTemplate: {ContainerSpec: { Image: "busybox:1.31.1-uclibc" }},Mode: { Replicated: { Replicas: 2 } },EndpointSpec: { Ports: [{Protocol: "tcp", PublishedPort: 8080, TargetPort: 80}] }}).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect(subject.create({Name: "dockerapi", TaskTemplate: {ContainerSpec: { Image: "busybox:1.31.1-uclibc" }},Mode: { Replicated: { Replicas: 2 } },EndpointSpec: { Ports: [{Protocol: "tcp", PublishedPort: 8080, TargetPort: 80}] }}, {username: "janedoe", password: "password"}).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImphbmVkb2UiLCJwYXNzd29yZCI6InBhc3N3b3JkIn0=") } it { expect{subject.create({ invalid: true })}.to raise_error(Docker::API::InvalidRequestBody) } end - context "after .create" do - describe ".details" do - it { expect(subject.details( service ).status).to eq(200) } - it { expect(subject.details( service, insertDefaults: true ).status).to eq(200) } - it { expect(subject.details( "doesn-exist" ).status).to eq(404) } - it { expect{subject.details( service, invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - end + describe ".details" do + it { expect(subject.details( "dockerapi" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi") } + it { expect(subject.details( "dockerapi" ).request_params[:method]).to eq(:get) } + it { expect(subject.details( "dockerapi", insertDefaults: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi?insertDefaults=true") } + it { expect{subject.details( "dockerapi", invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + end - describe ".logs" do - it { expect(subject.logs( service ).status).to eq(500) } - it { expect(subject.logs( service, details: true ).status).to eq(500) } - it { expect(subject.logs( service, details: true, stdout: true ).status).to eq(200) } - it { expect(subject.logs( service, details: true, stderr: true ).status).to eq(200) } - it { expect(subject.logs( service, since: 0, stdout: true ).status).to eq(200) } - it { expect(subject.logs( service, timestamps: 0, stdout: true ).status).to eq(200) } - it { expect(subject.logs( service, tail: 10, stdout: true ).status).to eq(200) } - it { expect(subject.logs( service, tail: "all", stdout: true ).status).to eq(200) } - it { expect{subject.details( service, invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - end + describe ".logs" do + it { expect(subject.logs( "dockerapi", details: true, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/logs?details=true&stdout=true") } + it { expect(subject.logs( "dockerapi", details: true, stdout: true ).request_params[:method]).to eq(:get) } + it { expect(subject.logs( "dockerapi", details: true, stderr: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/logs?details=true&stderr=true") } + it { expect(subject.logs( "dockerapi", since: 0, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/logs?since=0&stdout=true") } + it { expect(subject.logs( "dockerapi", timestamps: 0, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/logs?timestamps=0&stdout=true") } + it { expect(subject.logs( "dockerapi", tail: 10, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/logs?tail=10&stdout=true") } + it { expect(subject.logs( "dockerapi", tail: "all", stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/logs?tail=all&stdout=true") } + it { expect{subject.details( "dockerapi", invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + end - describe ".update" do - let(:spec) { subject.details(service).json["Spec"] } - let(:version) { subject.details( service ).json["Version"]["Index"] } - it { expect(subject.update(service, {version: version}, spec).status).to eq(200) } - it { expect(subject.update(service, {version: version}, - spec.merge!({TaskTemplate: {RestartPolicy: { Condition: "any", MaxAttempts: 2 }}, Mode: { Replicated: { Replicas: 1 } }}) - ).status).to eq(200) } - it { expect{subject.update(service, {version: version, invalid: true })}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.update(service, {version: version}, { invalid: true })}.to raise_error(Docker::API::InvalidRequestBody) } - end + describe ".update" do + it { expect(subject.update("dockerapi", {version: "version"}, {}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi/update?version=version") } + it { expect(subject.update("dockerapi", {version: "version"}, {}).request_params[:method]).to eq(:post) } + it { expect(subject.update("dockerapi", {version: "version"}, {}).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect(subject.update("dockerapi", {version: "version"}, {}, {username: "janedoe", password: "password"}).request_params[:headers]["X-Registry-Auth"]).to eq("eyJ1c2VybmFtZSI6ImphbmVkb2UiLCJwYXNzd29yZCI6InBhc3N3b3JkIn0=") } + it { expect(subject.update("dockerapi", {version: "version"}, {}.merge!({TaskTemplate: {RestartPolicy: { Condition: "any", MaxAttempts: 2 }}, Mode: { Replicated: { Replicas: 1 } }})).request_params[:body]).to eq("{\"TaskTemplate\":{\"RestartPolicy\":{\"Condition\":\"any\",\"MaxAttempts\":2}},\"Mode\":{\"Replicated\":{\"Replicas\":1}}}") } + it { expect{subject.update("dockerapi", {version: "version", invalid: true })}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.update("dockerapi", {version: "version"}, { invalid: true })}.to raise_error(Docker::API::InvalidRequestBody) } + end - describe ".delete" do - it { expect(subject.delete(service).status).to eq(200) } - it { expect(subject.delete(service).status).to eq(404) } - end + describe ".delete" do + it { expect(subject.delete("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/services/dockerapi") } + it { expect(subject.delete("dockerapi").request_params[:method]).to eq(:delete) } end + end end \ No newline at end of file diff --git a/spec/endpoints/swarm_spec.rb b/spec/endpoints/swarm_spec.rb index e739384..bcb6b5e 100644 --- a/spec/endpoints/swarm_spec.rb +++ b/spec/endpoints/swarm_spec.rb @@ -1,57 +1,65 @@ RSpec.describe Docker::API::Swarm do - ip_address = get_api_ip_address + subject { described_class.new(stub_connection) } - subject { described_class.new } - describe ".init" do - it { expect(subject.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567", SubnetSize: 24, Spec: { Name: "must-be-default" }}).status).to eq(400) } - it { expect(subject.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567", SubnetSize: 24, Spec: { Name: "default" }}).status).to eq(200) } - it { expect(subject.init.status).to eq(503) } - it { expect{subject.init(invalid: true)}.to raise_error(Docker::API::InvalidRequestBody) } - end + it { is_expected.to respond_to(:init) } + it { is_expected.to respond_to(:details) } + it { is_expected.to respond_to(:update) } + it { is_expected.to respond_to(:unlock_key) } + it { is_expected.to respond_to(:unlock) } + it { is_expected.to respond_to(:join) } + it { is_expected.to respond_to(:leave) } + + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } + + describe ".init" do + it { expect(subject.init({AdvertiseAddr: "127.0.0.1:2375", ListenAddr: "0.0.0.0:4567", SubnetSize: 24, Spec: { Name: "default" }}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/init") } + it { expect(subject.init({AdvertiseAddr: "127.0.0.1:2375", ListenAddr: "0.0.0.0:4567", SubnetSize: 24, Spec: { Name: "default" }}).request_params[:method]).to eq(:post) } + it { expect(subject.init({AdvertiseAddr: "127.0.0.1:2375", ListenAddr: "0.0.0.0:4567", SubnetSize: 24, Spec: { Name: "default" }}).request_params[:body]).to eq("{\"AdvertiseAddr\":\"127.0.0.1:2375\",\"ListenAddr\":\"0.0.0.0:4567\",\"SubnetSize\":24,\"Spec\":{\"Name\":\"default\"}}") } + it { expect(subject.init({AdvertiseAddr: "127.0.0.1:2375", ListenAddr: "0.0.0.0:4567", SubnetSize: 24, Spec: { Name: "default" }}).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.init(invalid: true)}.to raise_error(Docker::API::InvalidRequestBody) } + end - context "after .init" do describe ".details" do - subject { described_class.new.details } - it { expect(subject.status).to eq(200) } - it { expect(subject.json["ID"]).not_to be nil } - it { expect(subject.json["Version"]).not_to be nil } - it { expect(subject.json["Version"]["Index"]).not_to be nil } - it { expect(subject.json["JoinTokens"]).not_to be nil } - it { expect(subject.json["JoinTokens"]["Worker"]).not_to be nil } - it { expect(subject.json["JoinTokens"]["Manager"]).not_to be nil } + it { expect(subject.details.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm") } + it { expect(subject.details.request_params[:method]).to eq(:get) } end describe ".update" do - it { expect(subject.update.status).to eq(400) } - it { expect(subject.update({version: described_class.new.details.json["Version"]["Index"]}, {}).status).to eq(200) } - it { expect(subject.update({version: described_class.new.details.json["Version"]["Index"], rotateWorkerToken: true}).status).to eq(200) } - it { expect(subject.update({version: described_class.new.details.json["Version"]["Index"], rotateManagerToken: true}).status).to eq(200) } - it { expect(subject.update({version: described_class.new.details.json["Version"]["Index"], rotateManagerUnlockKey: true}).status).to eq(200) } - it { expect{subject.update({version: described_class.new.details.json["Version"]["Index"], invalid: true})}.to raise_error(Docker::API::InvalidParameter) } - it { expect(subject.update({version: described_class.new.details.json["Version"]["Index"]}, {EncryptionConfig: { AutoLockManagers: true } }).status).to eq(200) } - it { expect{subject.update({version: described_class.new.details.json["Version"]["Index"]},{invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } + it { expect(subject.update({version: "version"}, {}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/update?version=version") } + it { expect(subject.update({version: "version"}, {}).request_params[:method]).to eq(:post) } + it { expect(subject.update({version: "version", rotateWorkerToken: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/update?version=version&rotateWorkerToken=true") } + it { expect(subject.update({version: "version", rotateManagerToken: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/update?version=version&rotateManagerToken=true") } + it { expect(subject.update({version: "version", rotateManagerUnlockKey: true}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/update?version=version&rotateManagerUnlockKey=true") } + it { expect{subject.update({version: "version", invalid: true})}.to raise_error(Docker::API::InvalidParameter) } + it { expect(subject.update({version: "version"}, {EncryptionConfig: { AutoLockManagers: true } }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/update?version=version") } + it { expect(subject.update({version: "version"}, {EncryptionConfig: { AutoLockManagers: true } }).request_params[:body]).to eq("{\"EncryptionConfig\":{\"AutoLockManagers\":true}}") } + it { expect(subject.update({version: "version"}, {EncryptionConfig: { AutoLockManagers: true } }).request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.update({version: "version"},{invalid: true})}.to raise_error(Docker::API::InvalidRequestBody) } end describe ".unlock_key" do - subject { described_class.new.unlock_key } - it { expect(subject.status).to eq(200) } - it { expect(subject.json["UnlockKey"]).not_to be nil } + it { expect(subject.unlock_key.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/unlockkey") } + it { expect(subject.unlock_key.request_params[:method]).to eq(:get) } end describe ".unlock" do - it { is_expected.to respond_to(:unlock) } - it { expect(subject.unlock.status).to eq(409) } + it { expect(subject.unlock.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/unlock") } + it { expect(subject.unlock.request_params[:method]).to eq(:post) } end describe ".join" do - it { expect(subject.join.status).to eq(503) } - it { expect(subject.join(RemoteAddrs: ["#{ip_address}:2377"], JoinToken: subject.details.json["JoinTokens"]["Worker"] ).status).to eq(503) } + it { expect(subject.join(RemoteAddrs: ["127.0.0.1:2377"], JoinToken: "token" ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/join") } + it { expect(subject.join(RemoteAddrs: ["127.0.0.1:2377"], JoinToken: "token" ).request_params[:method]).to eq(:post) } + it { expect(subject.join(RemoteAddrs: ["127.0.0.1:2377"], JoinToken: "token" ).request_params[:body]).to eq("{\"RemoteAddrs\":[\"127.0.0.1:2377\"],\"JoinToken\":\"token\"}") } + it { expect(subject.join(RemoteAddrs: ["127.0.0.1:2377"], JoinToken: "token" ).request_params[:headers]["Content-Type"]).to eq("application/json") } end describe ".leave" do - it { expect(subject.leave.status).to eq(503) } - it { expect(subject.leave(force: true).status).to eq(200) } + it { expect(subject.leave(force: true).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/swarm/leave?force=true") } + it { expect(subject.leave(force: true).request_params[:method]).to eq(:post) } it { expect{subject.leave(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } end end diff --git a/spec/endpoints/system_spec.rb b/spec/endpoints/system_spec.rb index 4e57df5..1b1c106 100644 --- a/spec/endpoints/system_spec.rb +++ b/spec/endpoints/system_spec.rb @@ -1,53 +1,60 @@ RSpec.describe Docker::API::System do - subject { described_class.new } - describe ".auth" do - it { expect(subject).to respond_to(:auth) } - it { expect{subject.auth(username: "", password: "", email: "", serveraddress: "", identitytoken: "")}.not_to raise_error } - it { expect{subject.auth(invalid: true)}.to raise_error(Docker::API::InvalidRequestBody) } - it { expect{subject.auth(invalid: true, skip_validation: true)}.not_to raise_error } - end + subject { described_class.new(stub_connection) } - describe ".ping" do - it { expect(subject).to respond_to(:ping) } - it { expect(subject.ping.status).to eq(200) } - it { expect(subject.ping.path).to eq("/v#{Docker::API::API_VERSION}/_ping") } - end + it { is_expected.to respond_to(:auth) } + it { is_expected.to respond_to(:ping) } + it { is_expected.to respond_to(:info) } + it { is_expected.to respond_to(:version) } + it { is_expected.to respond_to(:events) } + it { is_expected.to respond_to(:df) } + + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } - describe ".info" do - it { expect(subject).to respond_to(:info) } - it { expect(subject.info.status).to eq(200) } - it { expect(subject.info.success?).to eq(true) } - it { expect(subject.info.json).to be_kind_of(Hash) } - it { expect(subject.info.path).to eq("/v#{Docker::API::API_VERSION}/info") } - end + describe ".auth" do + it { expect(subject.auth(username: "janedoe", password: "password", email: "janedow@email.com", serveraddress: "docker.io", identitytoken: "token").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/auth") } + it { expect(subject.auth(username: "janedoe", password: "password", email: "janedow@email.com", serveraddress: "docker.io", identitytoken: "token").request_params[:method]).to eq(:post) } + it { expect(subject.auth(username: "janedoe", password: "password", email: "janedow@email.com", serveraddress: "docker.io", identitytoken: "token").request_params[:body]).to eq("{\"username\":\"janedoe\",\"password\":\"password\",\"email\":\"janedow@email.com\",\"serveraddress\":\"docker.io\",\"identitytoken\":\"token\"}") } + it { expect(subject.auth(username: "janedoe", password: "password", email: "janedow@email.com", serveraddress: "docker.io", identitytoken: "token").request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.auth(username: "janedoe", password: "password", email: "janedow@email.com", serveraddress: "docker.io", identitytoken: "token")}.not_to raise_error } + it { expect{subject.auth(invalid: true)}.to raise_error(Docker::API::InvalidRequestBody) } + it { expect{subject.auth(invalid: true, skip_validation: true)}.not_to raise_error } + end - describe ".version" do - it { expect(subject).to respond_to(:version) } - it { expect(subject.version.status).to eq(200) } - it { expect(subject.version.success?).to eq(true) } - it { expect(subject.version.json).to be_kind_of(Hash) } - it { expect(subject.version.path).to eq("/v#{Docker::API::API_VERSION}/version") } - end + describe ".ping" do + it { expect(subject.ping.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/_ping") } + it { expect(subject.ping.request_params[:method]).to eq(:get) } + end - describe ".events" do - let(:now) { Time.now.to_i } - subject { described_class.new.events(until: now ) } - it { expect(described_class.new).to respond_to(:events) } - it { expect(subject.status).to eq(200) } - it { expect(subject.success?).to eq(true) } - it { expect(subject.path).to eq("/v#{Docker::API::API_VERSION}/events?until=#{now}") } - it { expect{described_class.new.events(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } - it { expect{described_class.new.events(invalid: true, skip_validation: false)}.to raise_error(Docker::API::InvalidParameter) } - end + describe ".info" do + it { expect(subject.info.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/info") } + it { expect(subject.info.request_params[:method]).to eq(:get) } + end + + describe ".version" do + it { expect(subject.version.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/version") } + it { expect(subject.version.request_params[:method]).to eq(:get) } + end + + describe ".events" do + let(:now) { Time.now.to_i } + subject { described_class.new.events(until: now ) } + it { expect(described_class.new).to respond_to(:events) } + it { expect(subject.status).to eq(200) } + it { expect(subject.success?).to eq(true) } + it { expect(subject.path).to eq("/v#{Docker::API::API_VERSION}/events?until=#{now}") } + it { expect{described_class.new.events(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } + it { expect{described_class.new.events(invalid: true, skip_validation: false)}.to raise_error(Docker::API::InvalidParameter) } + end - describe ".df" do - it { expect(subject).to respond_to(:df) } - it { expect(subject.df.status).to eq(200) } - it { expect(subject.df.success?).to eq(true) } - it { expect(subject.df.json).to be_kind_of(Hash) } - it { expect(subject.df.path).to eq("/v#{Docker::API::API_VERSION}/system/df") } - it { expect{subject.df(invalid: "true")}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.df(type: "container")}.not_to raise_error } - it { expect(subject.df(type: "container").path).to eq("/v#{Docker::API::API_VERSION}/system/df?type=container" )} + describe ".df" do + it { expect(subject.df.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/system/df") } + it { expect(subject.df.request_params[:method]).to eq(:get) } + it { expect(subject.df(type: "container").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/system/df?type=container" )} + it { expect{subject.df(invalid: "true")}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.df(type: "container")}.not_to raise_error } + + end end end \ No newline at end of file diff --git a/spec/endpoints/task_spec.rb b/spec/endpoints/task_spec.rb index 47d1c7d..a9598fa 100644 --- a/spec/endpoints/task_spec.rb +++ b/spec/endpoints/task_spec.rb @@ -1,62 +1,40 @@ RSpec.describe Docker::API::Task do - ip_address = get_api_ip_address - - subject { described_class.new } + subject { described_class.new(stub_connection) } it { is_expected.to respond_to(:list) } it { is_expected.to respond_to(:details) } it { is_expected.to respond_to(:logs) } - it { expect(subject.list.status).to eq(503) } - - context "having a swarm cluster" do - before(:all) do - Docker::API::Swarm.new.init({AdvertiseAddr: "#{ip_address}:2377", ListenAddr: "0.0.0.0:4567"}) - Docker::API::Service.new.create({Name: "rspec-service", - TaskTemplate: {ContainerSpec: { Image: "nginx:alpine" }, LogDriver: {Name: "json-file"}}, - Mode: { Replicated: { Replicas: 2 } }, - EndpointSpec: { Ports: [ - {Protocol: "tcp", PublishedPort: 8080, TargetPort: 80} - ] } - }) - end - after(:all) do - Docker::API::Swarm.new.leave(force: true) - Docker::API::Image.new.prune(filters: {dangling: {"true": true}}) - end + + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: { "desired-state": {"running": true} }).status).to eq(200) } - it { expect(subject.list(filters: { "desired-state": {"shutdown": true} }).status).to eq(200) } - it { expect(subject.list(filters: { "desired-state": {"accepted": true} }).status).to eq(200) } - it { expect(subject.list(filters: { "id": {"id-here": true} }).status).to eq(200) } - it { expect(subject.list(filters: { "name": {"task-name": true} }).status).to eq(200) } - it { expect(subject.list(filters: { "node": {"if-doesn-exist": true} }).status).to eq(404) } - it { expect(subject.list(filters: { "service": {"if-doesn-exist": true} }).status).to eq(404) } + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: { "desired-state": {"running": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks?filters={\"desired-state\":{\"running\":true}}") } + it { expect(subject.list(filters: { "desired-state": {"shutdown": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks?filters={\"desired-state\":{\"shutdown\":true}}") } + it { expect(subject.list(filters: { "desired-state": {"accepted": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks?filters={\"desired-state\":{\"accepted\":true}}") } + it { expect(subject.list(filters: { "id": {"id-here": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks?filters={\"id\":{\"id-here\":true}}") } + it { expect(subject.list(filters: { "name": {"task-name": true} }).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks?filters={\"name\":{\"task-name\":true}}") } it { expect{subject.list(invalid: true)}.to raise_error(Docker::API::InvalidParameter) } it { expect{subject.list(invalid: true, skip_validation: true)}.not_to raise_error } end describe ".details" do - it { expect(subject.details(subject.list.json.first["ID"]).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } + it { expect(subject.details("id").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id") } + it { expect(subject.details("id").request_params[:method]).to eq(:get) } end describe ".logs" do - let(:id) { subject.list.json.first["ID"] } - it { expect(subject.logs( id ).status).to eq(500) } - it { expect(subject.logs( id, details: true ).status).to eq(500) } - it { expect(subject.logs( id, details: true, stdout: true ).status).to eq(200) } - it { expect(subject.logs( id, details: true, stderr: true ).status).to eq(200) } - it { expect(subject.logs( id, since: 0, stdout: true ).status).to eq(200) } - it { expect(subject.logs( id, timestamps: 0, stdout: true ).status).to eq(200) } - it { expect(subject.logs( id, tail: 10, stdout: true ).status).to eq(200) } - it { expect(subject.logs( id, tail: "all", stdout: true ).status).to eq(200) } - it { expect{subject.logs( id, invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - it { expect{subject.logs( id, invalid: true, skip_validation: true )}.not_to raise_error } + it { expect(subject.logs( "id", details: true, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id/logs?details=true&stdout=true") } + it { expect(subject.logs( "id", details: true, stderr: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id/logs?details=true&stderr=true") } + it { expect(subject.logs( "id", since: 0, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id/logs?since=0&stdout=true") } + it { expect(subject.logs( "id", timestamps: 0, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id/logs?timestamps=0&stdout=true") } + it { expect(subject.logs( "id", tail: 10, stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id/logs?tail=10&stdout=true") } + it { expect(subject.logs( "id", tail: "all", stdout: true ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/tasks/id/logs?tail=all&stdout=true") } + it { expect{subject.logs( "id", invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + it { expect{subject.logs( "id", invalid: true, skip_validation: true )}.not_to raise_error } end - - end - end \ No newline at end of file diff --git a/spec/endpoints/volume_spec.rb b/spec/endpoints/volume_spec.rb index e810404..5c6569c 100644 --- a/spec/endpoints/volume_spec.rb +++ b/spec/endpoints/volume_spec.rb @@ -1,55 +1,50 @@ RSpec.describe Docker::API::Volume do - volume = "rspec-volume" + subject { described_class.new(stub_connection) } - subject { described_class.new } + it { is_expected.to respond_to(:list) } + it { is_expected.to respond_to(:create) } + it { is_expected.to respond_to(:details) } + it { is_expected.to respond_to(:remove) } + it { is_expected.to respond_to(:prune) } - describe ".list" do - it { expect(subject.list.status).to eq(200) } - it { expect(subject.list(filters: {dangling: {"true": true}}).status).to eq(200) } - it { expect(subject.list(filters: {driver: {"local": true}}).status).to eq(200) } - it { expect(subject.list(filters: {name: {"bridge": true}}).status).to eq(200) } - it { expect{subject.list( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - it { expect(subject.list(filters: {invalid: {"true": true}}).status).to eq(400) } - end + context "with stubs" do + before(:all) {Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { }) } + after(:all) { Excon.stubs.clear } - describe ".create" do - context "no name given" do - subject { described_class.new.create } - it { expect(subject.status).to eq(201) } - it { expect(subject.json["Name"]).not_to eq(nil) } + describe ".list" do + it { expect(subject.list.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes") } + it { expect(subject.list.request_params[:method]).to eq(:get) } + it { expect(subject.list(filters: {dangling: {"true": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes?filters={\"dangling\":{\"true\":true}}") } + it { expect(subject.list(filters: {driver: {"local": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes?filters={\"driver\":{\"local\":true}}") } + it { expect(subject.list(filters: {name: {"bridge": true}}).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes?filters={\"name\":{\"bridge\":true}}") } + it { expect{subject.list( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } end - it { expect(subject.create(Name: volume, Driver: "local").status).to eq(201) } - it { expect{subject.create( invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } - end - describe ".details" do - it { expect(subject.details(volume).status).to eq(200) } - it { expect(subject.details("doesn-exist").status).to eq(404) } - end + describe ".create" do + it { expect(subject.create(Name: "dockerapi", Driver: "local").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes/create") } + it { expect(subject.create(Name: "dockerapi", Driver: "local").request_params[:method]).to eq(:post) } + it { expect(subject.create(Name: "dockerapi", Driver: "local").request_params[:body]).to eq("{\"Name\":\"dockerapi\",\"Driver\":\"local\"}") } + it { expect(subject.create(Name: "dockerapi", Driver: "local").request_params[:headers]["Content-Type"]).to eq("application/json") } + it { expect{subject.create( invalid: true )}.to raise_error(Docker::API::InvalidRequestBody) } + end - describe ".remove" do - before(:each) { subject.create(Name: volume, Driver: "local") } - context "with container attached" do - before(:all) do - Docker::API::Image.new.create(fromImage: "busybox:1.31.1-uclibc") - Docker::API::Container.new.create({name: "rspec-container"}, {Image: "busybox:1.31.1-uclibc", HostConfig: {Binds: ["#{volume}:/home"]}}) - end - after(:all) do - Docker::API::Container.new.remove("rspec-container") - Docker::API::Image.new.remove("busybox:1.31.1-uclibc") - end - it { expect(subject.remove(volume).status).to eq(409) } - it { expect(subject.remove(volume, force: true).status).to eq(409) } + describe ".details" do + it { expect(subject.details("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes/dockerapi") } + it { expect(subject.details("dockerapi").request_params[:method]).to eq(:get) } + end + + describe ".remove" do + it { expect(subject.remove("dockerapi").request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes/dockerapi") } + it { expect(subject.remove("dockerapi").request_params[:method]).to eq(:delete) } + it { expect{subject.remove( "dockerapi", invalid: true )}.to raise_error(Docker::API::InvalidParameter) } end - it { expect(subject.remove(volume).status).to eq(204) } - it { expect(subject.remove("doesn-exist").status).to eq(404) } - it { expect{subject.remove( volume, invalid: true )}.to raise_error(Docker::API::InvalidParameter) } - end - describe ".prune" do - it { expect(subject.prune.status).to eq(200) } - it { expect(subject.prune( filters: {label: {"key": true}} ).status).to eq(200) } - it { expect(subject.prune( filters: {label: {"key=value": true}} ).status).to eq(200) } - it { expect{subject.prune( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + describe ".prune" do + it { expect(subject.prune.request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes/prune") } + it { expect(subject.prune.request_params[:method]).to eq(:post) } + it { expect(subject.prune( filters: {label: {"key": true}} ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes/prune?filters={\"label\":{\"key\":true}}") } + it { expect(subject.prune( filters: {label: {"key=value": true}} ).request_params[:path]).to eq("/v#{Docker::API::API_VERSION}/volumes/prune?filters={\"label\":{\"key=value\":true}}") } + it { expect{subject.prune( invalid: true )}.to raise_error(Docker::API::InvalidParameter) } + end end end \ No newline at end of file diff --git a/spec/misc/connection_spec.rb b/spec/misc/connection_spec.rb index c86b77a..c9edb50 100644 --- a/spec/misc/connection_spec.rb +++ b/spec/misc/connection_spec.rb @@ -2,6 +2,13 @@ it { expect(described_class.new).not_to be nil } it { expect(described_class.new.inspect).to match(/socket_key=\"unix:\/\/\/var\/run\/docker.sock\"/) } it { expect(described_class.new("http://localhost:2375").inspect).to match(/socket_key=\"http:\/\/localhost:2375\"/) } - it { expect(Docker::API::System.new.ping.status).to eq(200) } it { expect{Docker::API::System.new(Excon.new("http://localhost:2375"))}.to raise_error(Docker::API::Error, "Expected connection to be a Docker::API::Connection class") } + + context "with stubs" do + subject {Docker::API::System.new(stub_connection)} + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :path => '/v1.43/_ping', :port => 2375 }, { status: 200 }) } + after(:all) { Excon.stubs.clear } + it { expect(subject.ping.status).to eq(200) } + + end end \ No newline at end of file diff --git a/spec/misc/response_spec.rb b/spec/misc/response_spec.rb index 79c9986..77d76ab 100644 --- a/spec/misc/response_spec.rb +++ b/spec/misc/response_spec.rb @@ -1,9 +1,14 @@ RSpec.describe Docker::API::Response do it { expect(described_class).to be < Excon::Response } - describe Docker::API::System.new.ping do - it { is_expected.to respond_to(:json) } - it { is_expected.to respond_to(:path) } - it { is_expected.to respond_to(:success?) } + context "with stubs" do + subject {Docker::API::System.new(stub_connection)} + before(:all) { Excon.stub({ :scheme => 'http', :host => '127.0.0.1', :port => 2375 }, { status: 200 }) } + after(:all) { Excon.stubs.clear } + it { expect(subject.ping).to respond_to(:json) } + it { expect(subject.ping).to respond_to(:path) } + it { expect(subject.ping).to respond_to(:success?) } + it { expect(subject.ping).to respond_to(:request_params) } + it { expect(subject.ping.request_params[:path]).to eq('/v1.43/_ping') } end end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d4d7b2f..6aab492 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,10 +17,6 @@ end end -def get_api_ip_address - - Socket.ip_address_list.each do |addr| - return addr.ip_address if addr.ipv4? && addr.ipv4_loopback? - end - +def stub_connection + return Docker::API::Connection.new('http://127.0.0.1:2375', {mock: true}) end \ No newline at end of file