docker-distribution-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/README.md +54 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-distribution-api.gemspec +26 -0
- data/lib/docker/distribution/api.rb +55 -0
- data/lib/docker/distribution/api/connection.rb +97 -0
- data/lib/docker/distribution/api/manifest.rb +40 -0
- data/lib/docker/distribution/api/util.rb +16 -0
- data/lib/docker/distribution/api/version.rb +8 -0
- data/lib/docker/distribution/error.rb +56 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTQxNTU3OWExYzg0ZjI5MDkxNTY0OTJhNmI3YmZjZjZkNGUyZDUwZQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NzBiZTIxYmNiZmUwYWUxOGQ5ODg1NmUwOGMwNjMyMWJhNjMzMmUzMg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzVkNmM1OTk4MzAzNmE5MDcyZWM3OGUxMDFkOTI5MjczMjE5OTliNGU0MmU2
|
10
|
+
NTRkZTk3MzA4NTJlZmUzMGJmNmE1MmZkNGJmMDU0MGIxYmMwZjM5OGRmYWEx
|
11
|
+
OGJlNWUwNzUxNjdmOGEzZDE3ODExMDZhMjk1NDA0ODM4YzA3NGU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDc1MTFjYTQ5NGY1MmYxMzZkMDU2YWUxMWVlODZhOGI0N2M4OTM2OTViMjg0
|
14
|
+
YzVkNDFkNGRlMGVmZGIxYWQ1MjIwOWI5NjMyYTAyZTZlY2ZhYjE3MWUwYTgy
|
15
|
+
MWFjZjNlZGUzMjk0ZThkMDNmYTE1YzE2NTI5ZWFlZGY5ODc2NzI=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.2
|
4
|
+
before_install:
|
5
|
+
- gem install bundler -v 1.11.2
|
6
|
+
- docker pull registry:2.5
|
7
|
+
- docker run -d -p 127.0.0.1:5000:5000 -v "$(pwd)/spec/registry.config.yml:/etc/docker/registry/config.yml" registry:2.5
|
8
|
+
sudo: required
|
9
|
+
services:
|
10
|
+
- docker
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Docker::Distribution::Api
|
2
|
+
|
3
|
+
Simple swipley/docker-api like API client for docker-distribution with limited support of endpoints
|
4
|
+
|
5
|
+
|
6
|
+
[](https://travis-ci.org/bazilio91/docker-distribution-api)
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'docker-distribution-api'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install docker-distribution-api
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
API:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Docker::Distribution::Api.logger # to set your logger
|
30
|
+
Docker::Distribution::Api.url= # set registry url
|
31
|
+
Docker::Distribution::Api.options= # set registry connection options
|
32
|
+
Docker::Distribution::Api.version # get registry version
|
33
|
+
Docker::Distribution::Api.tags('test') # get tags for repository 'test'
|
34
|
+
```
|
35
|
+
|
36
|
+
Manifest:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Docker::Distribution::Api.url='http://localhost:5000'
|
40
|
+
Docker::Distribution::Api.options={:user => 'username', :password => 'password'} # basic auth
|
41
|
+
manifest = Docker::Distribution::Api::Manifest.find_by_tag('repository', 'tag') # get manifest
|
42
|
+
manifest.delete # delete manifest
|
43
|
+
```
|
44
|
+
|
45
|
+
## Development
|
46
|
+
|
47
|
+
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.
|
48
|
+
|
49
|
+
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).
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/bazilio91/docker-distribution-api.
|
54
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "docker/distribution/api"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'docker/distribution/api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'docker-distribution-api'
|
8
|
+
spec.version = Docker::Distribution::Api::VERSION
|
9
|
+
spec.authors = ['Vasily Ostanin']
|
10
|
+
spec.email = ['bazilio91@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = %q{API client for docker distribution.}
|
13
|
+
spec.description = %q{docker-api like client for docker self-hosted registry (distribution).}
|
14
|
+
spec.homepage = 'https://github.com/bazilio91/docker-distribution-api'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'excon', '~> 0.38'
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.11'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Docker
|
2
|
+
module Distribution
|
3
|
+
module Api
|
4
|
+
attr_accessor :creds, :logger
|
5
|
+
|
6
|
+
require 'docker/distribution/api/connection'
|
7
|
+
require 'docker/distribution/api/manifest'
|
8
|
+
require 'docker/distribution/api/util'
|
9
|
+
require 'docker/distribution/api/version'
|
10
|
+
require 'docker/distribution/error'
|
11
|
+
|
12
|
+
def version(connection = self.connection)
|
13
|
+
response = connection.get('/')
|
14
|
+
response.headers['Docker-Distribution-Api-Version']
|
15
|
+
end
|
16
|
+
|
17
|
+
def tags(repository, connection = self.connection)
|
18
|
+
response = connection.get("/#{repository}/tags/list")
|
19
|
+
Util.parse_json(response.body)['tags']
|
20
|
+
end
|
21
|
+
|
22
|
+
def url
|
23
|
+
@url ||= 'localhost:5000'
|
24
|
+
end
|
25
|
+
|
26
|
+
def url=(new_url)
|
27
|
+
@url = new_url
|
28
|
+
reset_connection!
|
29
|
+
end
|
30
|
+
|
31
|
+
def options
|
32
|
+
@options ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def options=(new_options)
|
36
|
+
@options = @options.merge(new_options || {})
|
37
|
+
reset_connection!
|
38
|
+
end
|
39
|
+
|
40
|
+
def connection
|
41
|
+
@connection ||= Connection.new(url, options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset_connection!
|
45
|
+
@connection = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
module_function :url, :url=,
|
49
|
+
:options, :options=,
|
50
|
+
:connection, :reset_connection!,
|
51
|
+
:logger, :logger=,
|
52
|
+
:version, :tags
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Docker
|
2
|
+
module Distribution
|
3
|
+
class Connection
|
4
|
+
require 'excon'
|
5
|
+
require 'docker/distribution/error'
|
6
|
+
|
7
|
+
include Docker::Distribution::Error
|
8
|
+
|
9
|
+
attr_reader :url, :options
|
10
|
+
|
11
|
+
# Create a new Connection. This method takes a url (String) and options
|
12
|
+
# (Hash). These are passed to Excon, so any options valid for `Excon.new`
|
13
|
+
# can be passed here.
|
14
|
+
def initialize(url, opts)
|
15
|
+
case
|
16
|
+
when !url.is_a?(String)
|
17
|
+
raise ArgumentError, "Expected a String, got: '#{url}'"
|
18
|
+
when !opts.is_a?(Hash)
|
19
|
+
raise ArgumentError, "Expected a Hash, got: '#{opts}'"
|
20
|
+
else
|
21
|
+
uri = URI.parse(url)
|
22
|
+
if uri.scheme == 'https'
|
23
|
+
@url, @options = url, opts
|
24
|
+
else
|
25
|
+
@url, @options = "http://#{uri}", opts
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# The actual client that sends HTTP methods to the Docker server. This value
|
31
|
+
# is not cached, since doing so may cause socket errors after bad requests.
|
32
|
+
def resource
|
33
|
+
Excon.new(url, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
private :resource
|
37
|
+
|
38
|
+
# Send a request to the server with the `
|
39
|
+
def request(*args, &block)
|
40
|
+
request = compile_request_params(*args, &block)
|
41
|
+
log_request(request)
|
42
|
+
resource.request(request)
|
43
|
+
rescue Excon::Errors::BadRequest => ex
|
44
|
+
raise ClientError, ex.response.body
|
45
|
+
rescue Excon::Errors::Unauthorized => ex
|
46
|
+
raise UnauthorizedError, ex.response.body
|
47
|
+
rescue Excon::Errors::NotFound => ex
|
48
|
+
raise NotFoundError, ex.response.body
|
49
|
+
rescue Excon::Errors::Conflict => ex
|
50
|
+
raise ConflictError, ex.response.body
|
51
|
+
rescue Excon::Errors::InternalServerError => ex
|
52
|
+
raise ServerError, ex.response.body
|
53
|
+
rescue Excon::Errors::Timeout => ex
|
54
|
+
raise TimeoutError, ex.message
|
55
|
+
end
|
56
|
+
|
57
|
+
def log_request(request)
|
58
|
+
if Api.logger
|
59
|
+
Api.logger.debug(
|
60
|
+
[request[:method], request[:path], request[:query], request[:body]]
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Delegate all HTTP methods to the #request.
|
66
|
+
[:get, :put, :post, :delete].each do |method|
|
67
|
+
define_method(method) { |*args, &block| request(method, *args, &block) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
"Docker::Distribution::Connection { :url => #{url}, :options => #{options} }"
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
# Given an HTTP method, path, optional query, extra options, and block,
|
76
|
+
# compiles a request.
|
77
|
+
def compile_request_params(http_method, path, query = nil, opts = nil, &block)
|
78
|
+
query ||= {}
|
79
|
+
opts ||= {}
|
80
|
+
headers = opts.delete(:headers) || {}
|
81
|
+
content_type = opts[:body].nil? ? 'text/plain' : 'application/json'
|
82
|
+
user_agent = "bazilio91/docker-distribution-api #{Api::VERSION}"
|
83
|
+
{
|
84
|
+
:method => http_method,
|
85
|
+
:path => "/v#{Api::API_VERSION}#{path}",
|
86
|
+
:query => query,
|
87
|
+
:headers => {'Content-Type' => content_type,
|
88
|
+
'User-Agent' => user_agent,
|
89
|
+
}.merge(headers),
|
90
|
+
:expects => (200..204).to_a << 301 << 304,
|
91
|
+
:idempotent => http_method == :get,
|
92
|
+
:request_block => block,
|
93
|
+
}.merge(opts).reject { |_, v| v.nil? }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Docker
|
2
|
+
module Distribution
|
3
|
+
module Api
|
4
|
+
class Manifest
|
5
|
+
attr_accessor :connection, :info
|
6
|
+
|
7
|
+
require 'docker/distribution/error'
|
8
|
+
require 'docker/distribution/api/util'
|
9
|
+
|
10
|
+
def initialize(connection, hash={})
|
11
|
+
unless connection.is_a?(Docker::Distribution::Connection)
|
12
|
+
raise ArgumentError, "Expected a Docker::Distribution::Connection, got: #{connection}."
|
13
|
+
end
|
14
|
+
@connection, @info = connection, hash
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete
|
18
|
+
@connection.delete(
|
19
|
+
"/#{@info['repository']}/manifests/#{@info['digest']}",
|
20
|
+
nil,
|
21
|
+
:expects => [202]
|
22
|
+
)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get_by_tag(repository, tag, connection = Api.connection)
|
27
|
+
response = connection.get(
|
28
|
+
"/#{repository}/manifests/#{tag}", nil, :headers => {:Accept => 'application/vnd.docker.distribution.manifest.v2+json'}
|
29
|
+
)
|
30
|
+
manifest_json = Util.parse_json(response.body)
|
31
|
+
hash = manifest_json
|
32
|
+
hash['digest'] = response.headers['Docker-Content-Digest']
|
33
|
+
hash['repository'] = repository
|
34
|
+
hash['tag'] = tag
|
35
|
+
new(connection, hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Docker
|
2
|
+
module Distribution
|
3
|
+
module Api
|
4
|
+
module Util
|
5
|
+
require 'json'
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def parse_json(body)
|
9
|
+
JSON.parse(body) unless body.nil? || body.empty? || (body == 'null')
|
10
|
+
rescue JSON::ParserError => ex
|
11
|
+
raise UnexpectedResponseError, ex.message
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Docker
|
2
|
+
module Distribution
|
3
|
+
# This module holds the Errors for the gem.
|
4
|
+
module Error
|
5
|
+
|
6
|
+
# The default error. It's never actually raised, but can be used to catch all
|
7
|
+
# gem-specific errors that are thrown as they all subclass from this.
|
8
|
+
class DockerDistributionError < StandardError;
|
9
|
+
end
|
10
|
+
|
11
|
+
# Raised when invalid arguments are passed to a method.
|
12
|
+
class ArgumentError < DockerDistributionError;
|
13
|
+
end
|
14
|
+
|
15
|
+
# Raised when a request returns a 400.
|
16
|
+
class ClientError < DockerDistributionError;
|
17
|
+
end
|
18
|
+
|
19
|
+
# Raised when a request returns a 401.
|
20
|
+
class UnauthorizedError < DockerDistributionError;
|
21
|
+
end
|
22
|
+
|
23
|
+
# Raised when a request returns a 404.
|
24
|
+
class NotFoundError < DockerDistributionError;
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when a request returns a 409.
|
28
|
+
class ConflictError < DockerDistributionError;
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when a request returns a 500.
|
32
|
+
class ServerError < DockerDistributionError;
|
33
|
+
end
|
34
|
+
|
35
|
+
# Raised when there is an unexpected response code / body.
|
36
|
+
class UnexpectedResponseError < DockerDistributionError;
|
37
|
+
end
|
38
|
+
|
39
|
+
# Raised when there is an incompatible version of Docker.
|
40
|
+
class VersionError < DockerDistributionError;
|
41
|
+
end
|
42
|
+
|
43
|
+
# Raised when a request times out.
|
44
|
+
class TimeoutError < DockerDistributionError;
|
45
|
+
end
|
46
|
+
|
47
|
+
# Raised when login fails.
|
48
|
+
class AuthenticationError < DockerDistributionError;
|
49
|
+
end
|
50
|
+
|
51
|
+
# Raised when an IO action fails.
|
52
|
+
class IOError < DockerDistributionError;
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docker-distribution-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vasily Ostanin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: excon
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.38'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.38'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.11'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.11'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: docker-api like client for docker self-hosted registry (distribution).
|
70
|
+
email:
|
71
|
+
- bazilio91@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .travis.yml
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/console
|
83
|
+
- bin/setup
|
84
|
+
- docker-distribution-api.gemspec
|
85
|
+
- lib/docker/distribution/api.rb
|
86
|
+
- lib/docker/distribution/api/connection.rb
|
87
|
+
- lib/docker/distribution/api/manifest.rb
|
88
|
+
- lib/docker/distribution/api/util.rb
|
89
|
+
- lib/docker/distribution/api/version.rb
|
90
|
+
- lib/docker/distribution/error.rb
|
91
|
+
homepage: https://github.com/bazilio91/docker-distribution-api
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ! '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.4.8
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: API client for docker distribution.
|
115
|
+
test_files: []
|
116
|
+
has_rdoc:
|