Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Add the Image ID in the repositories#show page
Browse files Browse the repository at this point in the history
This is the same Image ID that is shown in the docker client. In order to do
this, a new column has been added to the `tags` table. The registry client has
also been modified to handle all this more gracefully. The image ID being
shown is truncated in the exact same format as in the Docker client. That
being
said, users can hover the mouse over the image ID and they'll get the full
image ID.

Fixes #512

Signed-off-by: Miquel Sabaté Solà <msabate@suse.com>
  • Loading branch information
mssola committed May 2, 2016
1 parent 1823702 commit e57232b
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 34 deletions.
3 changes: 2 additions & 1 deletion app/models/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ def get_tag_from_list(namespace, repository)
#
# Returns the name of the tag if found, nil otherwise.
def get_tag_from_manifest(target)
client.manifest(target["repository"], target["digest"])["tag"]
_, _, manifest = client.manifest(target["repository"], target["digest"])
manifest["tag"]
end

# Create the global namespace for this registry and create the personal
Expand Down
26 changes: 22 additions & 4 deletions app/models/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,29 @@ def self.add_repo(event, namespace, repo, tag)
return
end

digest = event.try(:[], "target").try(:[], "digest")
tag = repository.tags.create(name: tag, author: actor, digest: digest)
# And store the tag and its activity.
id, digest = Repository.id_and_digest_from_event(event, repo)
tag = repository.tags.create(name: tag, author: actor, digest: digest, image_id: id)
repository.create_activity(:push, owner: actor, recipient: tag)
repository
end

# Fetch the image ID and the manifest digest from the given event.
def self.id_and_digest_from_event(event, repo)
digest = event.try(:[], "target").try(:[], "digest")
id = ""

unless digest.blank?
begin
id, = Registry.get.client.manifest(repo, digest)
rescue StandardError => e
logger.warn "Could not fetch manifest for '#{repo}' with digest '#{digest}': " + e.message
end
end

[id, digest]
end

# Create or update the given repository in JSON format. The given repository
# follows the same JSON format as in the one used by the Catalog API.
# Therefore, it's a hash with two keys:
Expand Down Expand Up @@ -141,12 +158,13 @@ def self.create_or_update!(repo)
to_be_created_tags.each do |tag|
# Try to fetch the manifest digest of the tag.
begin
digest = client.manifest(name, tag, true)
id, digest, = client.manifest(name, tag)
rescue
id = ""
digest = ""
end

Tag.create!(name: tag, repository: repository, author: portus, digest: digest)
Tag.create!(name: tag, repository: repository, author: portus, digest: digest, image_id: id)
logger.tagged("catalog") { logger.info "Created the tag '#{tag}'." }
end

Expand Down
1 change: 1 addition & 0 deletions app/models/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# updated_at :datetime not null
# user_id :integer
# digest :string(255)
# image_id :string(255) default("")
#
# Indexes
#
Expand Down
16 changes: 15 additions & 1 deletion app/views/repositories/show.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
.table-responsive.tags
table.table.table-stripped.table-hover
col.col-40
col.col-40
col.col-20
col.col-20
col.col-20
thead
tr
th Tag
th Author
th Image
th Pushed at
tbody
- @tags.each do |tag|
Expand All @@ -35,6 +37,12 @@
.label.label-success
= t.name
td= tag.first.author.username
td
- if tag.first.image_id.empty?
= "-"
- else
span[title="sha256:#{tag.first.image_id}"]
= tag.first.image_id[0, 12]
td= tag.first.created_at
- else
tr
Expand All @@ -43,6 +51,12 @@
.label.label-success
= t.name
td= tag.first.author.username
td
- if tag.first.image_id.empty?
= "-"
- else
span[title="sha256:#{tag.first.image_id}"]
= tag.first.image_id[0, 12]
td= tag.first.created_at

#write_comment_form.collapse
Expand Down
6 changes: 4 additions & 2 deletions bin/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
name, tag = ARGV[1], "latest"
end

puts JSON.pretty_generate(registry.client.manifest(name, tag))
puts "Manifest digest: #{registry.client.manifest(name, tag, true)}"
id, digest, manifest = registry.client.manifest(name, tag)
puts "Image ID: #{id} (truncated as in Docker: #{id[0, 12]})"
puts "Manifest digest: #{digest}"
puts JSON.pretty_generate(manifest)
when "ping"
# No registry was found, trying to ping another one.
if registry.nil?
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20160422075603_add_image_id_to_tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddImageIdToTag < ActiveRecord::Migration
def change
add_column :tags, :image_id, :string, default: ""
end
end
17 changes: 9 additions & 8 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20151215152138) do
ActiveRecord::Schema.define(version: 20160422075603) do

create_table "activities", force: :cascade do |t|
t.integer "trackable_id", limit: 4
Expand Down Expand Up @@ -54,7 +54,7 @@
create_table "crono_jobs", force: :cascade do |t|
t.string "job_id", limit: 255, null: false
t.datetime "last_performed_at"
t.boolean "healthy", limit: 1
t.boolean "healthy"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Expand All @@ -66,9 +66,9 @@
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "team_id", limit: 4
t.boolean "public", limit: 1, default: false
t.boolean "public", default: false
t.integer "registry_id", limit: 4, null: false
t.boolean "global", limit: 1, default: false
t.boolean "global", default: false
t.text "description", limit: 65535
end

Expand All @@ -82,7 +82,7 @@
t.string "hostname", limit: 255, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "use_ssl", limit: 1
t.boolean "use_ssl"
end

add_index "registries", ["hostname"], name: "index_registries_on_hostname", unique: true, using: :btree
Expand Down Expand Up @@ -116,6 +116,7 @@
t.datetime "updated_at", null: false
t.integer "user_id", limit: 4
t.string "digest", limit: 255
t.string "image_id", limit: 255, default: ""
end

add_index "tags", ["name", "repository_id"], name: "index_tags_on_name_and_repository_id", unique: true, using: :btree
Expand All @@ -137,7 +138,7 @@
t.string "name", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "hidden", limit: 1, default: false
t.boolean "hidden", default: false
t.text "description", limit: 65535
end

Expand All @@ -157,8 +158,8 @@
t.string "last_sign_in_ip", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "admin", limit: 1, default: false
t.boolean "enabled", limit: 1, default: true
t.boolean "admin", default: false
t.boolean "enabled", default: true
t.string "ldap_name", limit: 255
t.integer "failed_attempts", limit: 4, default: 0
t.datetime "locked_at"
Expand Down
26 changes: 17 additions & 9 deletions lib/portus/registry_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,25 @@ def reachable?
!res.nil? && res.code.to_i == 401
end

# Calls the `/:repository/manifests/:tag` endpoint from the registry. With
# the `digest` parameter you can tell this method whether you want the full
# manifest for this tag, or just the digest. It defaults to false, meaning
# that the caller wants the full manifest. It will raise either a
# ManifestNotFoundError or a RuntimeError if something goes wrong.
def manifest(repository, tag = "latest", digest = false)
method = digest ? "head" : "get"
res = perform_request("#{repository}/manifests/#{tag}", method)
# Calls the `/:repository/manifests/:tag` endpoint from the registry. It
# returns a three-sized array:
#
# - The image ID (without the "sha256:" prefix): only available for v2
# manifests (nil if v1).
# - The manifest digest.
# - The manifest itself as a ruby hash.
#
# It will raise either a ManifestNotFoundError or a RuntimeError if
# something goes wrong.
def manifest(repository, tag = "latest")
res = perform_request("#{repository}/manifests/#{tag}", "get")

if res.code.to_i == 200
digest ? res["Docker-Content-Digest"] : JSON.parse(res.body)
mf = JSON.parse(res.body)
id = mf.try(:[], "config").try(:[], "digest")
id = id.split(":").last if id.is_a? String
digest = res["Docker-Content-Digest"]
[id, digest, mf]
elsif res.code.to_i == 404
handle_error res, repository: repository, tag: tag
else
Expand Down
15 changes: 12 additions & 3 deletions lib/tasks/portus.rake
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ HERE

# Fetch the tags to be updated.
update = args[:update] == "true" || args[:update] == "t"
tags = update ? Tag.all : Tag.where(digest: "")
if update
tags = Tag.all
else
tags = Tag.where("tags.digest='' OR tags.image_id=''")
end

# Some information on the amount of tags to be updated.
if tags.empty?
Expand All @@ -84,8 +88,13 @@ HERE
tags.each_with_index do |t, index|
repo_name = t.repository.name
puts "[#{index + 1}/#{tags.size}] Updating #{repo_name}/#{t.name}"
digest = client.manifest(t.repository.name, t.name, true)
t.update_attributes(digest: digest)

begin
id, digest, = client.manifest(t.repository.name, t.name)
t.update_attributes(digest: digest, image_id: id)
rescue StandardError => e
puts "Could not get the manifest for #{repo_name}: #{e.message}"
end
end
puts
end
Expand Down
9 changes: 6 additions & 3 deletions spec/features/repositories_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,20 @@

scenario "Groupped tags are handled properly", js: true do
["", "", "same", "same", "another", "yet-another"].each_with_index do |digest, idx|
create(:tag, name: "tag#{idx}", author: user, repository: repository, digest: digest)
create(:tag, name: "tag#{idx}", author: user, repository: repository, digest: digest,
image_id: "Image")
end

expect = [["tag0"], ["tag1"], ["tag2", "tag3"], ["tag4"], ["tag5"]]
expectations = [["tag0"], ["tag1"], ["tag2", "tag3"], ["tag4"], ["tag5"]]

visit repository_path(repository)
page.all(".tags tr").each_with_index do |row, idx|
expect(row.text.include?("Image")).to be_truthy

# Skip the header.
next if idx == 0

expect[idx - 1].each { |tag| expect(row.text.include?(tag)).to be_truthy }
expectations[idx - 1].each { |tag| expect(row.text.include?(tag)).to be_truthy }
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/portus/registry_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def fetch_link_test(header)
username,
password)

manifest = registry.manifest(repository, tag)
_, _, manifest = registry.manifest(repository, tag)
expect(manifest["name"]).to eq(repository)
expect(manifest["tag"]).to eq(tag)
end
Expand Down
2 changes: 1 addition & 1 deletion spec/models/registry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def o.tags(*_)
end
else
def o.manifest(*_)
{ "tag" => "latest" }
["id", "digest", { "tag" => "latest" }]
end

def o.tags(*_)
Expand Down
4 changes: 3 additions & 1 deletion spec/models/repository_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,9 @@ def get_url(repo, tag)
let!(:tag3) { create(:tag, name: "tag3", repository: repo2) }

before :each do
allow_any_instance_of(Portus::RegistryClient).to receive(:manifest).and_return("digest")
allow_any_instance_of(Portus::RegistryClient).to receive(:manifest).and_return(
["id", "digest", ""])
allow(Repository).to receive(:id_and_digest_from_event).and_return(["id", "digest"])
end

it "adds and deletes tags accordingly" do
Expand Down
1 change: 1 addition & 0 deletions spec/models/tag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# updated_at :datetime not null
# user_id :integer
# digest :string(255)
# image_id :string(255) default("")
#
# Indexes
#
Expand Down

0 comments on commit e57232b

Please sign in to comment.