docker_registry_cli 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []