tagfish 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 801be00ea36e0cc2c98c535546c45f1025768ca0
4
+ data.tar.gz: 9ce46a51783f66f42f9da2f4d5a5cebc6e459fd4
5
+ SHA512:
6
+ metadata.gz: ad70b423105855d754bed1ad62d9291dbc18ac4ce113e38e3825aa72765d792572767a6f3df90f2c20c0af5677358cc9281f6499dbb49fb303e5ca312740c182
7
+ data.tar.gz: 33dbd5a8faf479d8a10364b7980731bb9c1823915d4aab0d08a4ac6d3a018fde857b22c22c950359bce3c2086fb9fd58eb92dafc358bb3580a753b5bc2ce6efe
@@ -0,0 +1,14 @@
1
+ *.swp
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /spec/examples.txt
11
+ /tmp/
12
+ /*.gem
13
+ /compiled-gem
14
+ /tests
@@ -0,0 +1,23 @@
1
+ FROM alpine:edge
2
+ MAINTAINER Clement Labbe <clement.labbe@rea-group.com>
3
+
4
+ RUN apk add --update ruby=2.2.3-r1 \
5
+ ruby-dev=2.2.3-r1 \
6
+ ruby-io-console=2.2.3-r1 \
7
+ diffutils \
8
+ linux-headers \
9
+ build-base \
10
+ ca-certificates=20150426-r3 && \
11
+ rm /var/cache/apk/* && \
12
+ rm -rf /usr/share/ri
13
+
14
+ RUN echo -e 'gem: --no-rdoc --no-ri' > /etc/gemrc \
15
+ gem update --system 2.4.8 && \
16
+ gem install bundler -v 1.10.6 && \
17
+ rm -rf /usr/share/ri
18
+
19
+ COPY pkg/tagfish-latest.gem /cwd/
20
+ WORKDIR /cwd
21
+ RUN gem install tagfish-latest.gem
22
+
23
+ ENTRYPOINT ["tagfish"]
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tagfish.gemspec
4
+ gemspec
@@ -0,0 +1,178 @@
1
+ # Tagfish
2
+
3
+ ![Tagfish logo](logo.jpg)
4
+
5
+ Tagfish is a CLI tool to interact with Docker registries.
6
+
7
+ Features include:
8
+
9
+ - List all the tags of a given Docker repository
10
+ - Return the most recent explicit tag of a repository
11
+ - Update a file with the newest tags
12
+ - Search for a repository
13
+ - Authenticate by reading native Docker config file
14
+ - Works against hub.docker.com and private registries
15
+ - Supports Docker Registry/Distribution API v1 and v2
16
+
17
+ To use Tagfish against a registry requiring authentication, you first need to authenticate to the given registry using `docker login <REGISTRY>`.
18
+
19
+ ## Table of contents
20
+
21
+ <!-- MarkdownTOC autolink=true bracket=round depth=4 -->
22
+
23
+ - [Usage](#usage)
24
+ - [`tagfish tags`](#tagfish-tags)
25
+ - [Example](#example)
26
+ - [`tagfish search`](#tagfish-search)
27
+ - [Example](#example-1)
28
+ - [`tagfish update`](#tagfish-update)
29
+ - [Example](#example-2)
30
+ - [Official repositories](#official-repositories)
31
+ - [Installation](#installation)
32
+ - [Gem](#gem)
33
+ - [Limitations](#limitations)
34
+ - [Docker image](#docker-image)
35
+ - [Contributing](#contributing)
36
+ - [Licence](#licence)
37
+
38
+
39
+ <!-- /MarkdownTOC -->
40
+
41
+ ## Usage
42
+ The Tagfish CLI tool has different subcommands for interacting with Docker registries.
43
+
44
+ ### `tagfish tags`
45
+ The `tags` subcommands is used to retrieve tags from a given repository:
46
+
47
+ Usage:
48
+ tagfish tags [OPTIONS] REPOSITORY
49
+
50
+ Parameters:
51
+ REPOSITORY docker repository
52
+
53
+ Options:
54
+ -l, --latest only return latest explicitly tagged image
55
+ -s, --short only return tag, not full image path
56
+ -h, --help print help
57
+
58
+ Where `repository` is a docker repository path, including the docker registry. The tags are returned in alphabetical order.
59
+
60
+ The `--latest` option gets the image ID of the docker image tagged `latest` in the repository, finds a matching image with a tag set manually (e.g: date, version number), and returns that tag. This option will not work if there is no image tagged `latest` in your repository.
61
+
62
+ #### Example
63
+ ```
64
+ $ tagfish tags alpine
65
+ alpine:2.6
66
+ alpine:2.7
67
+ alpine:3.1
68
+ alpine:3.2
69
+ alpine:edge
70
+ alpine:latest
71
+ ```
72
+
73
+ ### `tagfish search`
74
+ The `search` command is used to search for a repository in a given registry.
75
+
76
+ Usage:
77
+ tagfish search [OPTIONS] [KEYWORD]
78
+
79
+ Parameters:
80
+ [KEYWORD] object to search
81
+
82
+ Options:
83
+ -r, --registry REGISTRY Docker registry (default: "index.docker.io")
84
+ -h, --help print help
85
+
86
+ Note: `search` will not work if the search API is disabled on the registry side.
87
+
88
+ #### Example
89
+ ```
90
+ $ tagfish search alpine
91
+ alpine
92
+ 1science/alpine
93
+ webhippie/alpine
94
+ anapsix/alpine-java
95
+ colstrom/alpine
96
+ appelgriebsch/alpine
97
+ [...]
98
+ ```
99
+
100
+ ### `tagfish update`
101
+ The `update` subcommand is used to update a file with the latest tags available:
102
+
103
+ Usage:
104
+ tagfish update [OPTIONS] FILE
105
+
106
+ Parameters:
107
+ FILE file to update
108
+
109
+ Options:
110
+ -d, --dry-run enable dry run
111
+ --only PATTERN Only update repositories matching pattern. Wildcards `*` may be used.
112
+ -h, --help print help
113
+
114
+ #### Example
115
+ ```
116
+ $ tagfish update --dry-run Dockerfile
117
+ -FROM docker-registry.delivery.realestate.com.au/gpde/ubuntu-ruby2.2:201508191500
118
+ +FROM docker-registry.delivery.realestate.com.au/gpde/ubuntu-ruby2.2:201511261833
119
+ ```
120
+
121
+ #### Official repositories
122
+ `tagfish update` will update repositories such as:
123
+ ```
124
+ private.registry/namespace/repository:tag
125
+ namespace/repository:tag
126
+ ```
127
+ However, it will not update the tag of official repositories, such as:
128
+ ```
129
+ ubuntu:tag
130
+ ```
131
+ This is because updating to a new OS automatically might be something you want to avoid, and because it is hard to match a repository without a namespace.
132
+
133
+ ## Installation
134
+ ### Gem
135
+ Tagfish is packaged as a Ruby gem. Install it from the command line:
136
+
137
+ ```
138
+ $ gem install tagfish
139
+ ```
140
+
141
+ #### Limitations
142
+ Tagfish requires Ruby 2.2 or newer if the registry you are accessing restricts TLS to v1.2.
143
+
144
+ ### Docker image
145
+ Tagfish is released as a Docker image as well, and can be run with:
146
+
147
+ ```
148
+ docker run --rm \
149
+ -v ~/.docker/config.json:/root/.docker/config.json:ro \
150
+ -v ${PWD}:/cwd \
151
+ cowbell/tagfish
152
+ ```
153
+
154
+ ## Contributing
155
+
156
+ Bug reports and pull requests are welcome on GitHub at https://github.com/realestate-com-au/tagfish .
157
+
158
+ ## Licence
159
+
160
+ Copyright (c) 2015 REA Group Ltd.
161
+
162
+ Permission is hereby granted, free of charge, to any person obtaining a copy
163
+ of this software and associated documentation files (the "Software"), to deal
164
+ in the Software without restriction, including without limitation the rights
165
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
166
+ copies of the Software, and to permit persons to whom the Software is
167
+ furnished to do so, subject to the following conditions:
168
+
169
+ The above copyright notice and this permission notice shall be included in
170
+ all copies or substantial portions of the Software.
171
+
172
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
173
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
174
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
175
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
176
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
177
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
178
+ THE SOFTWARE.
@@ -0,0 +1,39 @@
1
+ require "bundler/gem_tasks"
2
+ require "fileutils"
3
+
4
+ spec = Gem::Specification::load(Dir.glob("*.gemspec").first)
5
+ gem_file = "pkg/#{spec.name}-#{spec.version}.gem"
6
+ docker_uri = "cowbell/#{spec.name}"
7
+ tag = "#{spec.version}"
8
+
9
+ desc "build docker image locally"
10
+ task build_docker_image: [:build] do
11
+ FileUtils.copy(gem_file, "pkg/tagfish-latest.gem")
12
+ sh "docker build -t #{docker_uri}:#{tag} ."
13
+ sh "docker tag -f #{docker_uri}:#{tag} #{docker_uri}:latest"
14
+ puts "Built image #{docker_uri}"
15
+ end
16
+
17
+ desc "Release docker image"
18
+ task release_docker_image: [:build_docker_image] do
19
+ sh "docker push #{docker_uri}:#{tag}"
20
+ sh "docker push #{docker_uri}:latest"
21
+ end
22
+
23
+ desc "Check for git sync"
24
+ task :no_local_changes do
25
+ sh "git pull"
26
+ sh "git diff HEAD --exit-code"
27
+ end
28
+
29
+ desc "Tag version into git repo"
30
+ task git_tag: [:no_local_changes] do
31
+ puts "blah"
32
+ sh "git tag #{tag}"
33
+ sh "git push origin --tags"
34
+ end
35
+
36
+ desc "Release gem and docker image"
37
+ task release_all: [:git_tag, :release, :release_docker_image] do
38
+ puts "Released all"
39
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
4
+
5
+ require 'clamp'
6
+ require 'tagfish/tags_command'
7
+ require 'tagfish/update/update_command'
8
+ require 'tagfish/search_command'
9
+ require "tagfish/version"
10
+
11
+ module Tagfish
12
+ class MainCommand < Clamp::Command
13
+ option ["-v", "--version"], :flag, "display version" do
14
+ puts "tagfish-#{Tagfish::VERSION}"
15
+ exit 0
16
+ end
17
+ end
18
+
19
+ class MainCommand < Clamp::Command
20
+ subcommand "tags", "find tags for a repository", TagsCommand
21
+ end
22
+
23
+ class MainCommand < Clamp::Command
24
+ subcommand "update", "inspect files for outdated dependencies", Update::UpdateCommand
25
+ end
26
+
27
+ class MainCommand < Clamp::Command
28
+ subcommand "search", "search a registry for repositories", SearchCommand
29
+ end
30
+
31
+ MainCommand.run
32
+ end
@@ -0,0 +1,5 @@
1
+ require "tagfish/version"
2
+
3
+ module Tagfish
4
+ # do nothing here
5
+ end
@@ -0,0 +1,41 @@
1
+ module Tagfish
2
+ class APICall
3
+ attr_accessor :uri
4
+ attr_accessor :http
5
+ attr_accessor :request
6
+
7
+ def initialize(uri_string)
8
+ @uri = URI.parse(uri_string)
9
+ @http = Net::HTTP.new(uri.host, uri.port)
10
+ @http.use_ssl = true if uri.port == 443
11
+ @request = Net::HTTP::Get.new(uri.request_uri)
12
+ end
13
+
14
+ def get_json(http_auth=nil)
15
+ begin
16
+ auth(http_auth) if http_auth
17
+ response = http.request(request)
18
+ if response.code == "200"
19
+ return JSON.parse(response.body)
20
+ else
21
+ abort("Call to the registry API failed, the following resource might not exist:\n#{uri.to_s}")
22
+ end
23
+ rescue SocketError
24
+ puts "ERROR: SocketError"
25
+ end
26
+ end
27
+
28
+ def response_code(http_auth=nil)
29
+ auth(http_auth) if http_auth
30
+ begin
31
+ http.request(request).code.to_i
32
+ rescue SocketError
33
+ abort("Call to the registry API failed, the following resource might not exist:\n#{uri.to_s}")
34
+ end
35
+ end
36
+
37
+ def auth(http_auth)
38
+ @request.basic_auth(http_auth.username, http_auth.password)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,113 @@
1
+ require 'net/http'
2
+ require 'json'
3
+ require 'tagfish/docker_uri'
4
+ require 'tagfish/api_call'
5
+
6
+ module Tagfish
7
+ class DockerAPI
8
+
9
+ attr_accessor :docker_uri
10
+ attr_accessor :api_version
11
+ attr_accessor :http_auth
12
+
13
+ def initialize(docker_uri)
14
+ @docker_uri = docker_uri
15
+ retrieve_api_version_and_auth()
16
+ end
17
+
18
+ def retrieve_api_version_and_auth
19
+ code = try_api('v1')
20
+ if code != 200
21
+ code = try_api('v2')
22
+ end
23
+ if code == 401
24
+ abort("Authentication failed, please `docker login <REGISTRY>` and try again.")
25
+ elsif code != 200
26
+ abort("API version not recognized")
27
+ end
28
+ end
29
+
30
+ def try_api(version)
31
+ code = APICall.new(ping_uri(version)).response_code
32
+ if code == 200
33
+ @api_version = version
34
+ elsif code == 401
35
+ code = init_auth(version)
36
+ if code == 200
37
+ @api_version = version
38
+ end
39
+ end
40
+ return code
41
+ end
42
+
43
+ def init_auth(api_version)
44
+ @http_auth = DockerHttpAuth.new(docker_uri.registry)
45
+ if api_version == 'v2'
46
+ code = APICall.new(ping_v2_uri).response_code(http_auth)
47
+ elsif api_version == 'v1'
48
+ code = APICall.new(ping_v1_uri).response_code(http_auth)
49
+ end
50
+ end
51
+
52
+ def tags_v1
53
+ APICall.new(tags_v1_uri).get_json(http_auth)
54
+ end
55
+
56
+ def tags_v2
57
+ APICall.new(tags_v2_uri).get_json(http_auth)
58
+ end
59
+
60
+ def hash_v2(tag)
61
+ APICall.new(hash_v2_uri(tag)).get_json(http_auth)
62
+ end
63
+
64
+ def catalog_v2
65
+ APICall.new(catalog_v2_uri).get_json(http_auth)
66
+ end
67
+
68
+ def search_v1(keyword)
69
+ APICall.new(search_v1_uri(keyword)).get_json(http_auth)
70
+ end
71
+
72
+ def base_uri
73
+ "#{docker_uri.protocol}#{docker_uri.registry}"
74
+ end
75
+
76
+ def ping_uri(version)
77
+ if version == 'v1'
78
+ ping_v1_uri
79
+ elsif version == 'v2'
80
+ ping_v2_uri
81
+ end
82
+ end
83
+
84
+ def ping_v2_uri
85
+ "#{base_uri}/v2/"
86
+ end
87
+
88
+ def ping_v1_uri
89
+ "#{base_uri}/v1/_ping"
90
+ end
91
+
92
+ def catalog_v2_uri
93
+ "#{base_uri}/v2/_catalog"
94
+ end
95
+
96
+ def search_v1_uri(keyword)
97
+ "#{base_uri}/v1/search?q=#{keyword}"
98
+ end
99
+
100
+ def tags_v1_uri
101
+ "#{base_uri}/v1/repositories/#{docker_uri.repository}/tags"
102
+ end
103
+
104
+ def tags_v2_uri
105
+ "#{base_uri}/v2/#{docker_uri.repository}/tags/list"
106
+ end
107
+
108
+ def hash_v2_uri(tag)
109
+ "#{base_uri}/v2/#{docker_uri.repository}/manifests/#{tag}"
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+ require 'base64'
3
+
4
+ module Tagfish
5
+ class DockerHttpAuth
6
+
7
+ attr_accessor :username
8
+ attr_accessor :password
9
+
10
+ def initialize(registry)
11
+ file_path = '~/.docker/config.json'
12
+
13
+ begin
14
+ config = File.open(File.expand_path(file_path), 'r')
15
+ rescue Exception => e
16
+ abort("Tried to get a SLiP but the file #{file_path} does not exist")
17
+ end
18
+
19
+ json_config = JSON.parse(config.read())
20
+ config.close()
21
+ if json_config['auths'].length == 0
22
+ @username, @password = nil, nil
23
+ else
24
+ b64_auth = json_config['auths'][registry]['auth']
25
+ auth = Base64.decode64(b64_auth)
26
+ @username, @password = auth.split(':')
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,64 @@
1
+ require 'tagfish/docker_http_auth'
2
+ require 'tagfish/docker_api'
3
+
4
+ module Tagfish
5
+ class DockerURI
6
+ URI_PARSER = %r{
7
+ (https?:\/\/)? # Optional protocol
8
+ (?:([\w.\-]+\.[\w.\-]+)\/)? # Optional registry
9
+ ([\w\-]*\/?[\w\-.]+) # Optional namespace, mandatory repository
10
+ :? # Optional delimiter between repository and tag
11
+ ([\w.\-]+)? # Optional tag
12
+ }x
13
+
14
+ def self.parse(docker_string)
15
+ match = docker_string.match(URI_PARSER)
16
+ new(*match.captures)
17
+ end
18
+
19
+ attr_accessor :protocol
20
+ attr_accessor :registry
21
+ attr_accessor :repository
22
+ attr_accessor :tag
23
+
24
+ def initialize(protocol, registry, repository, tag)
25
+ @protocol = protocol
26
+ @registry = registry
27
+ @repository = repository
28
+ @tag = tag
29
+
30
+ if registry.nil?
31
+ @protocol = "https://"
32
+ @registry = "index.docker.io"
33
+ elsif protocol.nil?
34
+ @protocol = "https://"
35
+ end
36
+ end
37
+
38
+ def repo_and_tag
39
+ tag.nil? ? "#{repository}" : "#{repository}:#{tag}"
40
+ end
41
+
42
+ def tag?
43
+ not tag.nil?
44
+ end
45
+
46
+ def tagged_latest?
47
+ tag == 'latest'
48
+ end
49
+
50
+ def with_tag(new_tag)
51
+ self.class.new(protocol, registry, repository, new_tag)
52
+ end
53
+
54
+ def to_s
55
+ reg_sep = registry.nil? ? nil : "/"
56
+ tag_sep = tag.nil? ? nil : ":"
57
+ if registry == "index.docker.io"
58
+ "#{repository}#{tag_sep}#{tag}"
59
+ else
60
+ "#{registry}#{reg_sep}#{repository}#{tag_sep}#{tag}"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,32 @@
1
+ require 'tagfish/docker_uri'
2
+
3
+ module Tagfish
4
+ class SearchCommand < Clamp::Command
5
+ parameter "[KEYWORD]", "object to search"
6
+ option ["-r", "--registry"], "REGISTRY", "Docker registry", :default => "index.docker.io"
7
+
8
+ def execute
9
+ if not registry and not keyword
10
+ abort("You need to specify a REGISTRY and/or a KEYWORD")
11
+ end
12
+
13
+ docker_uri = DockerURI.parse(registry + "/" + "dummy")
14
+ docker_api = DockerAPI.new(docker_uri)
15
+
16
+ if docker_api.api_version == 'v2'
17
+ repos = docker_api.catalog_v2["repositories"]
18
+ if keyword
19
+ repos.select! {|repo| repo.include? keyword}
20
+ end
21
+ repos.each {|repo| puts "#{docker_uri.registry}/#{repo}"}
22
+ else # 'v1'
23
+ if not keyword
24
+ abort("You need to specify a keyword to search a Registry V1")
25
+ end
26
+ repos_raw = docker_api.search_v1(keyword)
27
+ repos = repos_raw["results"].map {|result| result["name"]}
28
+ puts repos
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Tagfish
5
+ class Tags
6
+
7
+ def initialize(tags)
8
+ @tags = tags
9
+ end
10
+
11
+ def tag_names
12
+ @tags.keys.sort
13
+ end
14
+
15
+ def latest_tag
16
+ tag_names.select do |tag_name|
17
+ (@tags[tag_name] == @tags["latest"]) && (tag_name != 'latest')
18
+ end
19
+ end
20
+
21
+ def latest_tag_to_s
22
+ latest_tag.empty? ? nil : latest_tag[0]
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,37 @@
1
+ require "tagfish/tags_logic"
2
+ require "tagfish/docker_uri"
3
+
4
+ module Tagfish
5
+ class TagsCommand < Clamp::Command
6
+ parameter "REPOSITORY", "docker repository"
7
+ option ["-l", "--latest"], :flag, "only return latest explicitly tagged image"
8
+ option ["-s", "--short"], :flag, "only return tag, not full image path"
9
+
10
+ def execute
11
+ tags_only = latest? ? false : true
12
+
13
+ docker_uri = DockerURI.parse(repository)
14
+ docker_api = DockerAPI.new(docker_uri)
15
+ tags = TagsLogic.find_tags_by_repository(docker_api, tags_only)
16
+
17
+ begin
18
+ tags_found = latest? ? tags.latest_tag : tags.tag_names
19
+ rescue Exception => e
20
+ puts e.message
21
+ return
22
+ end
23
+
24
+ if tags_found.size == 0
25
+ puts "ERROR: No image explicitly tagged in this Repository, " +
26
+ "only `latest` tag available."
27
+ return
28
+ end
29
+
30
+ pretty_tags = tags_found.map do |tag_name|
31
+ short? ? tag_name : docker_uri.with_tag(tag_name).to_s
32
+ end
33
+
34
+ puts pretty_tags
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+ require 'tagfish/tags'
3
+
4
+ module Tagfish
5
+ class TagsLogic
6
+ def self.find_tags_by_repository(docker_api, tags_only=false)
7
+ if docker_api.api_version == 'v2'
8
+ tags_list = tags_v2(docker_api, tags_only)
9
+ else
10
+ tags_list = tags_v1(docker_api)
11
+ end
12
+ Tagfish::Tags.new(tags_list)
13
+ end
14
+
15
+ private
16
+
17
+ def self.tags_v1(docker_api)
18
+ tags_json = docker_api.tags_v1
19
+ tags_v1_api(tags_json)
20
+ end
21
+
22
+ def self.tags_v1_api(api_response_data)
23
+ case api_response_data
24
+ when Hash
25
+ api_response_data
26
+ when Array
27
+ api_response_data.reduce({}) do |images, tag|
28
+ images.merge({tag["name"] => tag["layer"]})
29
+ end
30
+ else
31
+ raise "unexpected type #{api_response_data.class}"
32
+ end
33
+ end
34
+
35
+ def self.tags_v2(docker_api, tags_only)
36
+ tags = docker_api.tags_v2["tags"]
37
+ if tags.nil?
38
+ abort("No Tags found for this repository")
39
+ end
40
+
41
+ tags_with_hashes = tags.inject({}) do |dict, tag|
42
+ if tags_only
43
+ dict[tag] = "dummy_hash"
44
+ else
45
+ dict[tag] = docker_api.hash_v2(tag)["fsLayers"][0]["blobSum"]
46
+ end
47
+ dict
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ module Tagfish
2
+ class Tokeniser
3
+
4
+ class Text < String
5
+ def is_a_uri_token?
6
+ false
7
+ end
8
+ end
9
+
10
+ class URI < String
11
+ def is_a_uri_token?
12
+ true
13
+ end
14
+ end
15
+
16
+ def self.tokenise(rest)
17
+ tokens = []
18
+ while true
19
+ match = rest.match /[\w\/:.-]+\/[\w.-]+:[\w.-]+/
20
+ if match.nil?
21
+ tokens << Text.new(rest)
22
+ break
23
+ else
24
+ tokens << Text.new(match.pre_match)
25
+ tokens << URI.new(match.to_s)
26
+ rest = match.post_match
27
+ end
28
+ end
29
+
30
+ tokens
31
+ end
32
+
33
+ def self.dump(tokens)
34
+ tokens.join('')
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require 'diffy'
2
+
3
+ module Tagfish
4
+ module Update
5
+ class Differ
6
+ def self.diff(original_string, updated_string)
7
+ diffy_diff = Diffy::Diff.new(original_string, updated_string, context: 2)
8
+ colour_diff = diffy_diff.to_s(:color).chomp
9
+ colour_diff.empty? ? "Nothing to update here" : colour_diff
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ require 'tagfish/update/updater'
2
+ require 'tagfish/update/differ'
3
+ require 'tagfish/update/uri_filters'
4
+ require 'tagfish/tokeniser'
5
+
6
+ module Tagfish
7
+ module Update
8
+ class UpdateCommand < Clamp::Command
9
+ parameter "FILE", "file to update"
10
+ option ["-d", "--dry-run"], :flag, "enable dry run"
11
+ option "--only", "PATTERN", "Only update repositories matching pattern. Wildcards (*) may be used."
12
+
13
+ def execute
14
+ filters = [
15
+ URIFilters.must_be_tagged,
16
+ URIFilters.must_not_be_tagged_latest,
17
+ URIFilters.must_match_repository(only)
18
+ ]
19
+ updater = Updater.new(filters)
20
+ original = File.read(file)
21
+ updated = Tokeniser.dump(updater.update(Tokeniser.tokenise(original)))
22
+
23
+ puts Differ.diff(original, updated)
24
+
25
+ if not dry_run?
26
+ File.write(file, updated)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,53 @@
1
+ require 'tagfish/tokeniser'
2
+ require 'tagfish/docker_uri'
3
+ require 'tagfish/tags_logic'
4
+
5
+ module Tagfish
6
+ module Update
7
+ class Updater
8
+ def initialize(filters)
9
+ @filters = filters
10
+ end
11
+
12
+ def update(tokens)
13
+ tokens.map do |token|
14
+ if token.is_a_uri_token?
15
+ update_uri_token(token)
16
+ else
17
+ token
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def update_uri_token(token)
25
+ original_uri = DockerURI.parse(token)
26
+ if updatable?(original_uri)
27
+ updated_uri = update_uri(original_uri)
28
+ Tokeniser::URI.new(updated_uri.to_s)
29
+ else
30
+ token
31
+ end
32
+ end
33
+
34
+ def updatable?(uri)
35
+ @filters.all? do |filter|
36
+ filter.call uri
37
+ end
38
+ end
39
+
40
+ def update_uri(docker_uri)
41
+ docker_api = DockerAPI.new(docker_uri)
42
+ tags = TagsLogic.find_tags_by_repository(docker_api)
43
+ newest_tag_name = tags.latest_tag_to_s
44
+ if newest_tag_name.nil?
45
+ docker_uri
46
+ else
47
+ docker_uri.with_tag(newest_tag_name)
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
1
+ module Tagfish
2
+ module Update
3
+ class URIFilters
4
+ def self.must_be_tagged
5
+ lambda do |uri| uri.tag? end
6
+ end
7
+
8
+ def self.must_not_be_tagged_latest
9
+ lambda do |uri| not uri.tagged_latest? end
10
+ end
11
+
12
+ def self.must_match_repository(pattern)
13
+ starts_with_pattern = "#{pattern}*"
14
+ lambda do |uri|
15
+ File.fnmatch(starts_with_pattern, uri.with_tag(nil).to_s)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module Tagfish
2
+ VERSION = "1.0.1"
3
+ end
Binary file
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tagfish/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tagfish"
8
+ spec.version = Tagfish::VERSION
9
+ spec.authors = ["Clement Labbe"]
10
+ spec.email = ["clement.labbe@rea-group.com"]
11
+ spec.summary = %q{Command line utility for docker registries}
12
+ spec.description = "Retrieve repository tags, update dockerfiles, and more!"
13
+ spec.homepage = "https://github.com/realestate-com-au/tagfish"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = ["tagfish"]
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.10"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.3.0"
24
+ spec.add_dependency "clamp", "~> 1.0.0"
25
+ spec.add_dependency "diffy", "~> 3.0.0"
26
+ spec.add_dependency "json", "~> 1.8.0"
27
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tagfish
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Clement Labbe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.3.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.3.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: clamp
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: diffy
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.0.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: json
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.8.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.8.0
97
+ description: Retrieve repository tags, update dockerfiles, and more!
98
+ email:
99
+ - clement.labbe@rea-group.com
100
+ executables:
101
+ - tagfish
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - Dockerfile
107
+ - Gemfile
108
+ - README.md
109
+ - Rakefile
110
+ - bin/tagfish
111
+ - lib/tagfish.rb
112
+ - lib/tagfish/api_call.rb
113
+ - lib/tagfish/docker_api.rb
114
+ - lib/tagfish/docker_http_auth.rb
115
+ - lib/tagfish/docker_uri.rb
116
+ - lib/tagfish/search_command.rb
117
+ - lib/tagfish/tags.rb
118
+ - lib/tagfish/tags_command.rb
119
+ - lib/tagfish/tags_logic.rb
120
+ - lib/tagfish/tokeniser.rb
121
+ - lib/tagfish/update/differ.rb
122
+ - lib/tagfish/update/update_command.rb
123
+ - lib/tagfish/update/updater.rb
124
+ - lib/tagfish/update/uri_filters.rb
125
+ - lib/tagfish/version.rb
126
+ - logo.jpg
127
+ - tagfish.gemspec
128
+ homepage: https://github.com/realestate-com-au/tagfish
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.0.14
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Command line utility for docker registries
152
+ test_files: []