docker_registry_cli 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 996d8a4754101bc04171017e1cd8173e49950333
4
+ data.tar.gz: a17f24f12d11e5a4249acae8d8a7f175a80cc803
5
+ SHA512:
6
+ metadata.gz: 72fde0e8eccd506b3da0f970f032f66376db75a68da743a3972cdd94e944ac0dae5a8e6d68b31a4fb4b8ff2478c952e85025a338ae075f72a38d7d590a09deea
7
+ data.tar.gz: 4c45321b3a9dc5a301ac13d1feb31bb2cb444069ff82c95493e3e6d90eb74ce471b9f1b4cf6193f50035078b30a6745648aca262f719defd3ebb90398f36faf9
@@ -0,0 +1,26 @@
1
+ class BasicAuthService
2
+
3
+ @@requestToBeAuthed = nil
4
+ def initialize(requestToBeAuthed)
5
+ @@requestToBeAuthed = requestToBeAuthed
6
+ end
7
+ def byCredentials(user, pass)
8
+ @@requestToBeAuthed.class.basic_auth user, pass
9
+ end
10
+
11
+ def byToken(domain)
12
+ # load the docker config and see, if the domain is included there - reuse the auth token
13
+ config = JSON.parse(File.read(File.join(ENV['HOME'], '.docker/config.json')))
14
+ token = config['auths'][domain]['auth']
15
+ if(!token)
16
+ pp config if @@debug
17
+ throw('No auth token found for this domain')
18
+ end
19
+
20
+ pp "Using existing token from config.json "
21
+
22
+ # decorate our request object
23
+ # set the Authorization header directly, since it is already base64 encoded
24
+ @@requestToBeAuthed.class.headers['Authorization'] = "Basic #{token}"
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ class TokenAuthService
2
+ @@requestToBeAuthed = nil
3
+ def initialize(requestToBeAuthed)
4
+ @@requestToBeAuthed = requestToBeAuthed
5
+ end
6
+
7
+ def tokenAuth(responseToBeAuthed)
8
+ # retrieve how we will contact the token service
9
+ auth_description = responseToBeAuthed.headers()['www-authenticate']
10
+ options = {
11
+ :query => {
12
+ }
13
+ }
14
+ # get the url
15
+ m = /Bearer realm="(?<url>[^"]+)"/.match(auth_description)
16
+ url = m['url']
17
+
18
+ # get the service we will generate the tokens for
19
+ m = /service="(?<service>[^"]+)"/.match(auth_description)
20
+ if(m)
21
+ options[:query][:service] = m['service']
22
+ end
23
+ # the scope, importent if scopes are used in the token service
24
+ # this is not always set
25
+ m = /scope="(?<scope>[^"]+)"/.match(auth_description)
26
+ if(m)
27
+ options[:query][:scope] = m['scope']
28
+ end
29
+
30
+ unless(url)
31
+ puts "Could not extract auth information for Bearer token".colorize(:red) if @@debug
32
+ throw "Could not extract auth information for Bearer token"
33
+ end
34
+ # get the bearer token
35
+ response = HTTParty.get(url,options)
36
+
37
+ throw "Failed to authenticate, code #{response.code}" unless response.code == 200 || response.has_key?('token')
38
+ # decorate our request object
39
+ @@requestToBeAuthed.class.headers['Authorization'] = "Bearer #{response['token']}"
40
+ end
41
+ end
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'colorize'
5
+ require 'pp'
6
+
7
+ require_relative '../commands/DockerRegistryCommand'
8
+
9
+ # our defaults / defines
10
+ options = {:user => nil,:password => nil, :domain => nil, :debug => false}
11
+ ops = ['list','search', 'tags', 'delete_image', 'delete_tag']
12
+
13
+ # define our options and help
14
+ OptionParser.new do |opts|
15
+ opts.banner = "Usage: registry.rb [options]"
16
+
17
+ opts.on("-u", "--user USER", "optional, user to login") do |v|
18
+ options[:user] = v
19
+ end
20
+ opts.on("-p", "--password PASSWORD", "optional, password to login") do |v|
21
+ options[:password] = v
22
+ end
23
+ opts.on("--domain DOMAIN", "Set this to override the domain you defined in ~./docker_registry.yml") do |v|
24
+ options[:domain] = v
25
+ end
26
+ opts.on("-d", "--debug", "debug mode") do |v|
27
+ options[:debug] = v
28
+ end
29
+ opts.on("-h", "--help", "Prints this help") do
30
+
31
+ puts opts
32
+ puts "\nArguments:"
33
+ puts '<registry-domain> <operation> <value(optional)>'.colorize(:green)
34
+ puts "\nif you have set domain in ~/.docker_registry.yml you can ease it up like this:"
35
+ puts '<operation> <value(optional)>'.colorize(:green)
36
+
37
+ puts "\nOperations:"
38
+ puts 'list: list all available repositorys'
39
+ puts 'search <key>: search for a repository'
40
+ puts 'tags <repo-name>: list all tags of a repository'
41
+ puts 'delete_image <repo-name> <tag>: delete a image with all its tags'
42
+ puts 'delete_tag <repo-name> <tag>: delete a tag'
43
+ exit
44
+ end
45
+ end.parse!
46
+
47
+ # try to load values from tour configuration. Those get superseded by the arguments though
48
+ begin
49
+ config = YAML::load(File.read(File.join(ENV['HOME'], '.docker_registry.yml')))
50
+ if options[:debug]
51
+ puts 'Found config:'.colorize(:blue)
52
+ pp config
53
+ end
54
+
55
+ options[:domain] = config['domain'] if config['domain'] && !options[:domain]
56
+ options[:user] = config['user'] if config['user'] && !options[:user]
57
+ options[:password] = config['password'] if config['password'] && !options[:password]
58
+ rescue
59
+ # just no config, move on
60
+ puts 'No config found in ~/.docker_registry.yml . Create it and add domain:<> and optional user/password to avoid adding any arguments'.colorize(:light_white)
61
+ if !ARGV[0]
62
+ puts 'The first argument should be your registry domain without schema (HTTPS mandatory), optional with :port'.colorize(:red)
63
+ exit 1
64
+ else
65
+ options[:domain] = ARGV.shift
66
+ end
67
+ end
68
+
69
+ # ensure a operation is set. Be aware, we used shift up there - so we always stick with 0
70
+ if !ARGV[0]
71
+ puts "Define the operation: #{ops.join(', ')}".colorize(:red)
72
+ exit 1
73
+ else
74
+ if !ops.include?(ARGV[0])
75
+ puts "This operation is yet not implemented. Select on of: #{ops.join(', ')}".colorize(:red)
76
+ exit 1
77
+ end
78
+
79
+ op = ARGV.shift
80
+ end
81
+
82
+ # print out some informations debug mode
83
+ if options[:debug]
84
+ pp options
85
+ puts "Operation: #{op}".colorize(:blue)
86
+ end
87
+
88
+ # configure our request handler
89
+ registry = DockerRegistryCommand.new(options[:domain], options[:user], options[:password], options[:debug])
90
+
91
+ # run the operations, which can actually have different amounts of mandatory arguments
92
+ case op
93
+ when 'delete_image'
94
+ if !ARGV[0]
95
+ puts 'Please define a image name'
96
+ exit 1
97
+ else
98
+ image_name = ARGV.shift
99
+ end
100
+ registry.delete_image(image_name)
101
+ when 'delete_tag'
102
+ if !ARGV[0]
103
+ puts 'Please define a image name'
104
+ exit 1
105
+ else
106
+ image_name = ARGV.shift
107
+ end
108
+ if !ARGV[0]
109
+ puts 'Please define a tag'
110
+ exit 1
111
+ else
112
+ tag = ARGV.shift
113
+ end
114
+ registry.delete_tag(image_name, tag)
115
+ when 'list'
116
+ registry.list
117
+ when 'search'
118
+ if !ARGV[0]
119
+ puts 'Please define a search key'
120
+ exit 1
121
+ else
122
+ key = ARGV.shift
123
+ registry.search(key)
124
+ end
125
+ when 'tags'
126
+ if !ARGV[0]
127
+ puts 'Please define a repo name'
128
+ exit 1
129
+ else
130
+ repo = ARGV.shift
131
+ tags = registry.tags(repo)
132
+ unless tags.nil?
133
+ tags.each { |tag|
134
+ puts tag
135
+ }
136
+ else
137
+ puts 'Not tags found'
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require_relative '../requests/DockerRegistryRequest'
4
+
5
+ class DockerRegistryCommand < DockerRegistryRequest
6
+ ### list all available repos
7
+ def list(search_key = nil)
8
+ response = send_get_request("/_catalog")
9
+ if response['repositories'].nil?
10
+ puts "no repositories found"
11
+ exit 1
12
+ else
13
+ response['repositories'].each { |repo|
14
+ puts repo if search_key.nil? || repo.include?(search_key)
15
+ }
16
+ end
17
+ end
18
+
19
+ ### search for a specific repo using a key ( wildcard )
20
+ def search(search_key)
21
+ list(search_key)
22
+ end
23
+
24
+ ### delete an image, so all its tags
25
+ ### @see https://docs.docker.com/registry/spec/api/#deleting-an-image
26
+ def delete_image(image_name)
27
+ # be sure to enabel storage->delete->true in your registry, see https://github.com/docker/distribution/blob/master/docs/configuration.md
28
+ tags = tags(image_name)
29
+ if tags.nil?
30
+ puts "Image #{image_name} has no current tags, nothing to delete".red
31
+ exit
32
+ end
33
+ tags.each { |tag|
34
+ begin
35
+ puts "Deleting tag : #{tag}"
36
+ delete_tag(image_name, tag)
37
+ puts 'success'.green
38
+ rescue
39
+ puts 'failed'.red
40
+ end
41
+ }
42
+
43
+ end
44
+
45
+
46
+ ### delete a tag, identified by image name and tag
47
+ ### @see https://docs.docker.com/registry/spec/api/#deleting-an-image
48
+ def delete_tag(image_name, tag)
49
+ # be sure to enabel storage->delete->true in your registry, see https://github.com/docker/distribution/blob/master/docs/configuration.md
50
+
51
+ # fetch digest
52
+ digest = digest(image_name, tag)
53
+ unless digest
54
+ puts "Could not find digest from tag #{tag}".colorize(:red)
55
+ exit 1
56
+ end
57
+ result = send_delete_request("/#{image_name}/manifests/#{digest}")
58
+
59
+ unless result.code == 202
60
+ puts "Could not delete tag #{tag}".colorize(:red)
61
+ exit 1
62
+ end
63
+
64
+ #result = send_delete_request("/#{image_name}/blobs/#{digest}")
65
+ #unless result.code == 202
66
+ # puts "Could not delete blob from digest #{digest}".colorize(:red)
67
+ # exit 1
68
+ #end
69
+ end
70
+
71
+ ### list all tags of a repo
72
+ def tags(repo)
73
+ result = send_get_request("/#{repo}/tags/list")
74
+ return result['tags']
75
+ end
76
+ end
data/install.sh ADDED
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+
3
+ echo "Please enter your registry domain like docker.mycompany.com - do not add https://, you can add a port"
4
+ read domain
5
+ echo "domain: $domain" > ~/.docker_registry.yml
6
+ echo "created configuration"
7
+ echo "installing gems"
8
+ bundle install
9
+ echo "Should i create a symlink into /usr/local/bin (needs sudo permissions)"
10
+ select yn in "Yes" "No"; do
11
+ case $yn in
12
+ Yes ) sudo ln -fs `pwd`/bin/docker_registry.rb /usr/local/bin/docker_registry; break;;
13
+ No ) echo "skipped";;
14
+ esac
15
+ done
16
+
17
+ echo "HINT: Ensure you run docker login $domain to save your credentials encrypted"
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'json'
5
+ require 'httparty'
6
+ require 'yaml'
7
+ require 'pp'
8
+
9
+ require_relative '../auth/TokenAuthService'
10
+ require_relative '../auth/BasicAuthService'
11
+
12
+ class DockerRegistryRequest
13
+ include HTTParty
14
+ format :json
15
+ headers 'Content-Type' => 'application/json'
16
+ headers 'Accept' => 'application/json'
17
+ @@debug = false
18
+
19
+ def initialize(domain, user = nil, pass = nil, debug = false)
20
+ @@debug = debug
21
+ self.class.base_uri "https://#{domain}/v2"
22
+ handle_preauth(domain, user, pass)
23
+ end
24
+
25
+ def handle_preauth(domain, user = nil, pass = nil)
26
+ # Only BasicAuth is supported
27
+ authService = BasicAuthService.new(self)
28
+ begin
29
+ if user && pass
30
+ # this will base64 encode automatically
31
+ authService.byCredentials(user, pass)
32
+ else
33
+ authService.byToken(domain)
34
+ end
35
+ rescue Exception
36
+ puts "No BasicAuth pre-auth available, will try to use different auth-services later".green
37
+ end
38
+
39
+ end
40
+
41
+ ### check if the login actually will succeed
42
+ def authenticate(response)
43
+ headers = response.headers()
44
+ begin
45
+ if headers.has_key?('www-authenticate')
46
+ auth_description = headers['www-authenticate']
47
+ if auth_description.match('Bearer realm=')
48
+ authService = TokenAuthService.new(self)
49
+ authService.tokenAuth(response)
50
+ else
51
+ throw "Auth method not supported #{auth_description}"
52
+ end
53
+ end
54
+ rescue Exception
55
+ puts "Authentication failed".colorize(:red)
56
+ end
57
+ end
58
+
59
+ ## sends a get request, authenticates if needed
60
+ def send_get_request(path, options = {})
61
+ # we try to send the request. if it fails due to auth, we need the returned scope
62
+ # thats why we first try to do it without auth, then reusing the scope from the response
63
+ response = self.class.get(path, options)
64
+ # need auth
65
+ case (response.code)
66
+ when 200
67
+ # just continue
68
+ when 401
69
+ authenticate(response)
70
+ response = self.class.get(path, options)
71
+ else
72
+ end
73
+ unless response.code == 200
74
+ throw "Could not finish request, status #{response.code}"
75
+ end
76
+ return response
77
+ end
78
+
79
+ ## sends a delete request, authenticates if needed
80
+ def send_delete_request(path)
81
+ response = self.class.delete(path)
82
+ # need auth
83
+ case (response.code)
84
+ when 200
85
+ # just continue
86
+ when 401
87
+ authenticate(response)
88
+ response = self.class.delete(path)
89
+ else
90
+ end
91
+ if response.code != 200 && response.code != 202
92
+ throw "Could not finish request, status #{response.code}"
93
+ end
94
+ return response
95
+ end
96
+
97
+ ### returns the digest for a tag
98
+ ### @see https://docs.docker.com/registry/spec/api/#pulling-an-image
99
+ def digest(image_name, tag)
100
+ options = {
101
+ :headers => {
102
+ 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json'
103
+ }
104
+ }
105
+ begin
106
+ response = send_get_request("/#{image_name}/manifests/#{tag}", options)
107
+ rescue
108
+ puts "Could not find digest for image #{image_name} with tag #{tag}".colorize(:red)
109
+ exit 1
110
+ end
111
+
112
+ unless response.code == 200
113
+ puts "Could not find digest for image #{image_name} with tag #{tag}".colorize(:red)
114
+ exit 1
115
+ end
116
+
117
+ return response.headers['docker-content-digest']
118
+ end
119
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: docker_registry_cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Eugen Mayer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: This cli-tool lets you query your private docker registry for different
14
+ things. For now, there is no tool provided by docker to do so
15
+ email: eugen.mayer@kontextwork.de
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - auth/BasicAuthService.rb
21
+ - auth/TokenAuthService.rb
22
+ - bin/docker_registry.rb
23
+ - commands/DockerRegistryCommand.rb
24
+ - install.sh
25
+ - requests/DockerRegistryRequest.rb
26
+ homepage:
27
+ licenses:
28
+ - GPL
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 2.4.5.1
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: Docker Registry Cli - Search your docker registry from the cli
50
+ test_files: []