I would like to find out the latest timestamp for an image in a private Docker registry using the v2 API without first pulling the image to my local host.
So after some hacking around, I got the following to work using the curl
and jq
tools:
curl -X GET http://registry:5000/v2/<IMAGE>/manifests/<TAG> \
| jq -r '.history[].v1Compatibility' \
| jq '.created' \
| sort \
| tail -n1
This seems to work but I don't really know how the v1 Compatibility representation is to be interpreted so I don't know if I am really getting the correct number out of this.
Comments on this are welcome!
jq -r '[.history[]]|map(.v1Compatibility|fromjson|.created)|sort|.[0]
–
Gilles jq -r '[.history[]]|map(.v1Compatibility|fromjson|.created)|sort|reverse|.[0]
–
Gilles jq -r '[.history[0]]|map(.v1Compatibility|fromjson|.created)
–
Lenna curl -X GET -H 'Accept: application/vnd.docker.distribution.manifest.v1+json' ...
–
Nannie With the V2 image manifest, schema version 2, the response from http://registry:5000/v2/<IMAGE>/manifests/<TAG>
does not contain the 'history' field, and I haven't found a way to retrieve the image creation date in one request. However one can use another request to obtain it, based on the image manifest config's digest.
First, get the config's digest (notice the header which specifies the schema version):
digest=$(curl -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
http://registry:5000/v2/<IMAGE>/manifests/<TAG> \
| jq -r '.config.digest')
The value of digest
will be something similar to
sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736
.
Then, get the blob of the object identified by the digest, i.e. the configuration object for a container, and retrieve the creation date from there:
curl -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
http://registry:5000/v2/<IMAGE>/blobs/$digest | jq -r '.created'
This should produce:
2021-10-27T12:18:24.105617451Z
Bonus: Getting the creation dates using skopeo
I'm not sure if the above is the only or the best way, but by the way, it is apparently more or less what skopeo does under the hood when one runs:
skopeo inspect docker://registry:5000/<IMAGE>:<TAG>
The above command returns information about the image tag, among them the creation date:
{
"Name": "registry:5000/<IMAGE>",
"Digest": "sha256:655721ff613ee766a4126cb5e0d5ae81598e1b0c3bcf7017c36c4d72cb092fe9",
"RepoTags": [...],
"Created": "2021-10-27T12:18:24.105617451Z",
...
}
A minor improvement to the answer from @snth : print the date more user friendly by using date
:
date --date=$(curl -s -X GET http://$REGISTRY:5000/v2/$IMAGE/manifests/$TAG | \
jq -r '.history[].v1Compatibility' | jq -r '.created' | sort | tail -n 1 )
Prints something like:
Fr 12. Okt 15:26:03 CEST 2018
Compiling the comments and answers above here is a complete solution
With Authentication
# Setup variables to make this more usalbe
USERNAME=docker_user
PASSWORD=docker_pass
DOCKER_REGISTRY=http://my-registry.myorg.org:9080
REPO=myrepo
TAG=atag
# Query Registry and pipe results to jq
DOCKER_DATE=$(curl -s -u $USERNAME:$PASSWORD -H 'Accept: application/vnd.docker.distribution.manifest.v1+json' -X GET http://$REGISTRY_URL/v2/circle-lb/manifests/master | jq -r '[.history[]]|map(.v1Compatibility|fromjson|.created)|sort|reverse|.[0]')
echo "Date for $REPO:$TAG is $DOCKER_DATE"
Without Authentication
DOCKER_DATE=$(curl -s -H 'Accept: application/vnd.docker.distribution.manifest.v1+json' -X GET http://$REGISTRY_URL/v2/circle-lb/manifests/master | jq -r '[.history[]]|map(.v1Compatibility|fromjson|.created)|sort|reverse|.[0]')
And lastly if you want to parse with the date command (gnu date)
on linux:
date -d $DOCKER_DATE
on mac:
gdate -d $DOCKER_DATE
Here is dotnet core (C#) implementation in case anyone is interested:
public class DockerHub{
public DockerRegistryToken GetRegistryToken(string image){
Console.WriteLine("authenticateing with dockerhub");
using HttpClient client = new ();
var url = string.Format($"https://auth.docker.io/token?service=registry.docker.io&scope=repository:{image}:pull");
//var response = client.Send(new HttpRequestMessage(HttpMethod.Get, url));
var result = client.GetStringAsync(url);
var drt = JsonSerializer.Deserialize<DockerRegistryToken>(result.Result);
return drt;
}
public DateTime GetRemoteImageDate(string image, string tag, DockerRegistryToken token){
using HttpClient client = new ();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.docker.distribution.manifest.list.v2+json"));
var manifestResult = client.GetStringAsync(string.Format($"https://registry-1.docker.io/v2/{image}/manifests/{tag}"));
var json = JsonDocument.Parse(manifestResult.Result);
var created = json.RootElement.GetProperty("history").EnumerateArray()
.Select(m => JsonDocument.Parse(m.GetProperty("v1Compatibility").ToString()).RootElement.GetProperty("created"))
.Select(m => DateTime.Parse(m.GetString()))
.OrderByDescending(m => m)
.FirstOrDefault(); // I challange you to improve this
Console.WriteLine("Date recieved: {0}",created);
return created.ToUniversalTime();
}
}
public class DockerRegistryToken{
[JsonPropertyName("token")]
public string Token { get; set; }
/// always null
[JsonPropertyName("access_token")]
public string AccessToken {get; set; }
[JsonPropertyName("expires_in")]
public int ExpiresInSeconds { get; set; }
[JsonPropertyName("issued_at")]
public DateTime IssuedAt { get; set; }
}
To use
var client = new DockerHub();
var tok = client.GetRegistryToken("your/reponame");
var remoteImageDate = client.GetRemoteImageDate("your/reponame","latest",tok);
Some idea for C#:
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace DockerApi;
public class Manifests
{
[JsonPropertyName("schemaVersion")]
public long SchemaVersion { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("tag")]
public string Tag { get; set; }
[JsonPropertyName("architecture")]
public string Architecture { get; set; }
[JsonPropertyName("fsLayers")]
public FsLayer[] FsLayers { get; set; }
[JsonPropertyName("history")]
public History[] History { get; set; }
[JsonPropertyName("signatures")]
public Signature[] Signatures { get; set; }
public V1Compatibility V1Compatibility
{
get
{
var jsonNode = JsonNode.Parse(History.Where(m => m.V1Compatibility.Contains("architecture")).First().V1Compatibility);
return JsonSerializer.Deserialize<V1Compatibility>(jsonNode);
}
}
}
public partial class FsLayer
{
[JsonPropertyName("blobSum")]
public string BlobSum { get; set; }
}
public partial class History
{
[JsonPropertyName("v1Compatibility")]
public string V1Compatibility { get; set; }
}
public partial class Signature
{
[JsonPropertyName("header")]
public Header Header { get; set; }
[JsonPropertyName("signature")]
public string SignatureSignature { get; set; }
[JsonPropertyName("protected")]
public string Protected { get; set; }
}
public partial class Header
{
[JsonPropertyName("jwk")]
public Jwk Jwk { get; set; }
[JsonPropertyName("alg")]
public string Alg { get; set; }
}
public partial class Jwk
{
[JsonPropertyName("crv")]
public string Crv { get; set; }
[JsonPropertyName("kid")]
public string Kid { get; set; }
[JsonPropertyName("kty")]
public string Kty { get; set; }
[JsonPropertyName("x")]
public string X { get; set; }
[JsonPropertyName("y")]
public string Y { get; set; }
}
public partial class V1Compatibility
{
[JsonPropertyName("architecture")]
public string Architecture { get; set; }
[JsonPropertyName("config")]
public Config Config { get; set; }
[JsonPropertyName("created")]
public DateTimeOffset Created { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("os")]
public string Os { get; set; }
[JsonPropertyName("parent")]
public string Parent { get; set; }
[JsonPropertyName("throwaway")]
public bool Throwaway { get; set; }
}
public partial class Config
{
[JsonPropertyName("Env")]
public string[] Env { get; set; }
[JsonPropertyName("Cmd")]
public string[] Cmd { get; set; }
[JsonPropertyName("WorkingDir")]
public string WorkingDir { get; set; }
[JsonPropertyName("ArgsEscaped")]
public bool ArgsEscaped { get; set; }
[JsonPropertyName("OnBuild")]
public object OnBuild { get; set; }
}
© 2022 - 2024 — McMap. All rights reserved.