Check if image:tag combination already exists on docker hub - bash

As part of a bash script, I want to check if a particularly docker image:tag combination exists on docker hub. Also, it will be a private repository.
i.e. the pseudocode would be like:
tag = something
if image:tag already exists on docker hub:
Do nothing
else
Build and push docker image with that tag

Update: Docker-Free solution see below
Using Docker
This is the solution I use with gitlab using the docker:stable image.
Login
docker login -u $USER -p $PASSWORD $REGISTRY
Check whether it's there:
docker manifest inspect $IMGNAME:$IMGTAG > /dev/null ; echo $?
docker will return 0 on success or 1 on failure.
If you get a warning: Update Docker or enable experimental client-features:
Set the environment variable DOCKER_CLI_EXPERIMENTAL to enabled (See Matěj's answer below)
Alternatively adjust the config (original answer):
echo '{"experimental": "enabled"}' > ~/.docker/config.json
This will also overwrite your config. If that is not an option you need to do that manually or use jq, sed or whatever you have available.
Testing without Docker
If you don't have access to a docker-daemon you can give Skopeo or crane a try.

Please try this one
function docker_tag_exists() {
curl --silent -f -lSL https://index.docker.io/v1/repositories/$1/tags/$2 > /dev/null
}
if docker_tag_exists library/nginx 1.7.5; then
echo exist
else
echo not exists
fi
Update:
In case of usage Docker Registry v2 (based on this):
# set username and password
UNAME="user"
UPASS="password"
function docker_tag_exists() {
TOKEN=$(curl -s -H "Content-Type: application/json" -X POST -d '{"username": "'${UNAME}'", "password": "'${UPASS}'"}' https://hub.docker.com/v2/users/login/ | jq -r .token)
curl --silent -f --head -lL https://hub.docker.com/v2/repositories/$1/tags/$2/ > /dev/null
}
if docker_tag_exists library/nginx 1.7.5; then
echo exist
else
echo not exists
fi

To build on morty's answer, notice that docker supports setting the experimental flag using environment variable:
DOCKER_CLI_EXPERIMENTAL Enable experimental features for the cli (e.g. enabled or disabled)
The snippet therefore becomes:
tag=something
if DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect $image:$tag >/dev/null; then
Do nothing
else
Build and push docker image with that tag
fi

Easiest:
docker pull alpine:invalid > /dev/null && echo "success" || echo "failed"
Pulls & prints success if image exists, or prints failed if it doesn't:
You can even export it in a var if using in bash script:
Note that this will pull the image if it exists. Beware of the overhead cost before using this solution.

I have a docker private repo stood up on my LAN using registry:2, private CA, and basic auth.
I just looked at the official docker API docs (https://docs.docker.com/registry/spec/api/) and came up with this solution which seems pretty elegant, easy to debug, customize, and is CICD/scripting friendly.
curl --silent -i -u "demoadmin":"demopassword" https://mydockerrepo.local:5000/v2/rancher/pause/manifests/3.1 | grep "200 OK"
--silient gets rid of some extra text
-i is what makes the return code "200 OK" show up
if it exists return code is 0, if doesn't exist return code is 1
you can verify that using
Bash# echo $?

Here's a Bash function that will help:
docker_image_exists() {
local image_full_name="$1"; shift
local wait_time="${1:-5}"
local search_term='Pulling|is up to date|not found'
local result="$((timeout --preserve-status "$wait_time" docker 2>&1 pull "$image_full_name" &) | grep -v 'Pulling repository' | egrep -o "$search_term")"
test "$result" || { echo "Timed out too soon. Try using a wait_time greater than $wait_time..."; return 1 ;}
echo $result | grep -vq 'not found'
}
Usage example:
docker_image_exists elifarley/docker-dev-env:alpine-sshd && \
echo EXISTS || \
echo "Image does not exist"

Just a small improvement of Evgeny Oskin's solution. When it comes to a user repo that hasn't been created yet, jq says that it "Cannot iterate over null". To overcome it. one can skip not present blocks with ?
Here is a modification to above mentioned solution that is applicable to a public repo in particular:
#!/usr/bin/env bash
function docker_image_tag_exists() {
EXISTS=$(curl -s https://hub.docker.com/v2/repositories/$1/tags/?page_size=10000 | jq -r "[.results? | .[]? | .name == \"$2\"] | any")
test ${EXISTS} = true
}
if docker_image_tag_exists $1 $2; then
echo "true"
else
echo "false"
fi

I was struggling getting this to work for a private docker hub repository and finally decided to write a ruby script instead, which works as of today. Feel free to use!
#!/usr/bin/env ruby
require 'base64'
require 'net/http'
require 'uri'
def docker_tag_exists? repo, tag
auth_string = Base64.strict_encode64 "#{ENV['DOCKER_USER']}:#{ENV['DOCKER_PASSWORD']}"
uri = URI.parse("https://registry.hub.docker.com/v1/repositories/#{repo}/tags/#{tag}")
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Basic #{auth_string}"
request['Accept'] = 'application/json'
request['Content-Type'] = 'application/json'
response = Net::HTTP.start(request.uri.hostname, request.uri.port, use_ssl: true) do |http|
http.request(request)
end
(response.body == 'Tag not found') ? 0 : 1
end
exit docker_tag_exists? ARGV[0], ARGV[1]
Note: you need to specify DOCKER_USER and DOCKER_PASSWORD when calling this like...
DOCKER_USER=XXX DOCKER_PASSWORD=XXX config/docker/docker_hub.rb "NAMESPACE/REPO" "TAG" && echo 'latest'
This line would print out 'latest', if authentication is successful and the specified tag does not exists! I was using this in my Vagrantfile when trying to fetch a tag based on the current git branch:
git rev-parse --symbolic-full-name --abbrev-ref HEAD

All of the options above assume that you can authenticate using username/password. There are a lot of cases where this is inconvenient, for example when using Google Container Registry, for which one would run gcloud auth configure-docker gcr.io first. That command installs an authentication helper for Docker, and you wouldn't want to manage that token yourself.
One tool that supports these docker authentication helpers, and also allows getting a manifest - like experimental Docker - is crane.
Example using crane:
# you would have done this already
gcloud auth configure-docker gcr.io;
# ensure we have crane installed
which crane || (echo 'installing crane' && GO111MODULE=on go get -u github.com/google/go-containerregistry/cmd/crane)
# check manifest
crane manifest ubuntu || echo "does not exist"

If you are querying Hub for the existence of a tag, make sure you use a HEAD rather than a GET request. The GET request count against your rate limit. A script that does this specific to Docker Hub, only supports the Docker media types, and anonymous logins that are rate limited by your requesting IP, looks like:
$ more ~/data/docker/registry-api/manifest-v2-head.sh
#!/bin/sh
ref="${1:-library/ubuntu:latest}"
sha="${ref#*#}"
if [ "$sha" = "$ref" ]; then
sha=""
fi
wosha="${ref%%#*}"
repo="${wosha%:*}"
tag="${wosha##*:}"
if [ "$tag" = "$wosha" ]; then
tag="latest"
fi
api="application/vnd.docker.distribution.manifest.v2+json"
apil="application/vnd.docker.distribution.manifest.list.v2+json"
token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull"
\
| jq -r '.token')
curl -H "Accept: ${api}" -H "Accept: ${apil}" \
-H "Authorization: Bearer $token" \
-I -s "https://registry-1.docker.io/v2/${repo}/manifests/${sha:-$tag}"
To work on other registries, handle more media types (like the OCI types), and handle logins, use a tool like crane, skopeo, or my own regclient:
# the "image digest" command uses a HEAD instead of a GET
if regctl image digest registry.example.com/repo:tag >/dev/null 2>&1; then
echo tag exists
else
echo tag does not exist
fi

For the local docker registry, you can try this:
function docker_tag_exists() {
curl --silent -f -lSL http://localhost:5000/v2/$1/manifests/$2 > /dev/null
}
if docker_tag_exists image_on_local latest; then
echo exists
else
echo not exists
fi

I like solutions based on docker.
This oneliner is what I use in our CI:
docker run --rm anoxis/registry-cli -l user:password -r registry-url -i docker-name | grep -q docker-tag || echo do something if not found

Have you tried something like that, simply trying to pull the tag and deciding to push or not according to the return code?
#! /bin/bash
if docker pull hello-world:linux > /dev/null; then
echo "already exist -> do not push"
else
echo "does not exist -> push"
fi

Related

Does not getting build failure status even the build not successful run(cloud-build remote builder)

Cloud-build is not showing build failure status
I created my own remote-builder which scp all files from /workspace to my Instance and running build on using gcloud compute ssh -- COMMAND
remote-builder
#!/bin/bash
USERNAME=${USERNAME:-admin}
REMOTE_WORKSPACE=${REMOTE_WORKSPACE:-/home/${USERNAME}/workspace/}
GCLOUD=${GCLOUD:-gcloud}
KEYNAME=builder-key
ssh-keygen -t rsa -N "" -f ${KEYNAME} -C ${USERNAME} || true
chmod 400 ${KEYNAME}*
cat > ssh-keys <<EOF
${USERNAME}:$(cat ${KEYNAME}.pub)
EOF
${GCLOUD} compute scp --compress --recurse \
$(pwd)/ ${USERNAME}#${INSTANCE_NAME}:${REMOTE_WORKSPACE} \
--ssh-key-file=${KEYNAME}
${GCLOUD} compute ssh --ssh-key-file=${KEYNAME} \
${USERNAME}#${INSTANCE_NAME} -- ${COMMAND}
below is the example of the code to run build(cloudbuild.yaml)
steps:
- name: gcr.io/$PROJECT_ID/remote-builder
env:
- COMMAND="docker build -t [image_name]:[tagname] -f Dockerfile ."
During docker build inside Dockerfile it got failure and show errors in log but status showing SUCCESS
can any help me how to resolve it.
Thanks in advance.
try adding
|| exit 1
at the end of your docker command... alternatively, you might just need to change the entrypoint to 'bash' and run the script manually
To confirm -- the first part was the run-on.sh script, and the second part was your cloudbuild.yaml right? I assume you trigger the build manually via UI and/or REST API?
I wrote all docker commands on bash script and add below error handling code to it.
handle_error() {
echo "FAILED: line $1, exit code $2"
exit 1
}
trap 'handle_error $LINENO $?' ERR
It works!

Login via curl fails inside bash script, same curl succeeds on command line

I'm running this login via curl in my bash script. I want to make sure I can login before executing the rest of the script, where I actually log in and store the cookie in a cookie jar and then execute another curl in the API thousands of times. I don't want to run all that if I've failed to login.
Problem is, the basic login returns 401 when it runs inside the script. But when I run the exact same curl command on the command line, it returns 200!
basic_login_curl="curl -w %{http_code} -s -o /dev/null -X POST -d \"username=$username&password=$password\" $endpoint/login"
echo $basic_login_curl
outcome=`$basic_login_curl`
echo $outcome
if [ "$outcome" == "401" ]; then
echo "Failed login. Please try again."; exit 1;
fi
This outputs:
curl -w %{http_code} -s -o /dev/null -X POST -d "username=bdunn&password=xxxxxx" http://stage.mysite.it:9301/login
401
Failed login. Please try again.
Copied the output and ran it on the cmd line:
$ curl -w %{http_code} -s -o /dev/null -X POST -d "username=bdunn&password=xxxxxx" http://stage.mysite.it:9301/login
200$
Any ideas? LMK if there's more from the code you need to see.
ETA: Please note: The issue's not that it doesn't match 401, it's that running the same curl login command inside the script fails to authenticate, whereas it succeeds when I run it on the actual CL.
Most of the issues reside in how you are quoting/not quoting variables and the subshell execution. Setting up your command like the following is what I would recommend:
basic_login_curl=$(curl -w "%{http_code}" -s -o /dev/null -X POST -d "username=$username&password=$password" "$endpoint/login")
The rest basically involves quoting everything properly:
basic_login_curl=$(curl -w "%{http_code}" -s -o /dev/null -X POST -d "username=$username&password=$password" "$endpoint/login")
# echo "$basic_login_curl" # not needed since what follows repeats it.
outcome="$basic_login_curl"
echo "$outcome"
if [ "$outcome" = "401" ]; then
echo "Failed login. Please try again."; exit 1;
fi
Running the script through shellcheck.net can be helpful in resolving issues like this as well.

If proxy is down, get a new one

I'm writing my first bash script
LANG="en_US.UTF8" ; export LANG
PROXY=$(shuf -n 1 proxy.txt)
export https_proxy=$PROXY
RUID=$(php -f randuid.php)
curl --data "mydata${RUID}" --user-agent "myuseragent" https://myurl.com/url -o "ticket.txt"
This script also use curl, but if proxy is down it gives me this error:
failed to connect PROXY:PORT
How can I make bash script run again, so it can get another proxy address from proxy.txt
Thanks in advance
Run it in a loop until the curl succeeds, for example:
export LANG="en_US.UTF8"
while true; do
PROXY=$(shuf -n 1 proxy.txt)
export https_proxy=$PROXY
RUID=$(php -f randuid.php)
curl --data "mydata${RUID}" --user-agent "myuseragent" https://myurl.com/url -o "ticket.txt" && break
done
Notice the && break at the end of the curl command.
That is, if the curl succeeds, break out of the infinite loop.
If you have multiple curl commands and you need all of them to succeed,
then chain them all together with &&, and add the break after the last one:
curl url1 && \
curl url2 && \
break
Lastly, as #Inian pointed out,
you could use the --proxy flag to pass a proxy URL to curl without the extra step of setting https_proxy, for example:
curl --proxy "$(shuf -n 1 proxy.txt)" --data "mydata${RUID}" --user-agent "myuseragent"
Lastly, note that due to the randomness, a randomly selected proxy may come up more than once until you find one that works.
Avoid that, you could read iterate over the shuffled proxies instead of an infinite loop:
export LANG="en_US.UTF8"
shuf proxy.txt | while read -r proxy; do
ruid=$(php -f randuid.php)
curl --proxy "$proxy" --data "mydata${ruid}" --user-agent "myuseragent" https://myurl.com/url -o "ticket.txt" && break
done
I also lowercased your user-defined variables,
as capitalization is not recommended for those.
I know i accepted #janos answer but since I can't edit his I'm going to add this
response=$(curl --proxy "$proxy" --silent --write-out "\n%{http_code}\n" https://myurl.com/url)
status_code=$(echo "$response" | sed -n '$p')
html=$(echo "$response" | sed '$d')
case "$status_code" in
200) echo 'Working!'
;;
*)
echo 'Not working, trying again!';
exec "$0" "$#"
esac
This will run my script again if it gives 503 status code which i wanted :)
And with #janos code it will run again if proxy is not working.
Thank you everyone i achieved what i wanted.

How to use curl -w switch with multiple data token as format parameter?

I want to get two things from curl: http_code and time_total from a single curl request. How should I formulate the -w %{insert_formatting_here} ?
These works:
result = $(curl -s -w %{http_code} -o temp.txt) "http://127.0.0.1"
echo "$result"
result = $(curl -s -w %{time_total} -o temp.txt) "http://127.0.0.1"
echo "$result"
Result:
200
0.004
But this didn't work as I expected:
result = $(curl -s -w %{http_code time_total} -o temp.txt) "http://127.0.0.1"
echo "$result"
Result:
<p>where "$CATALINA_HOME" is the root of the Tomcat installation directory. If you're seeing this page, and you don't think you should be, then you're either a user who has arrived at new installation of Tomcat, or you're an administrator who hasn't got his/her setup quite right. Providing the latter is the case, please refer to the Tomcat Documentation for more detailed setup and administration in %{http_codeReserved99-2014 Apache Software Foundation<br/>ht="80" alt="Powered by Tomcat"/><br/>s working on Tomcat</li>configuring and using Tomcat</li> developing web applications.</p>
I cannot find any tutorial that helps me to put multiple token on the format parameter. They only list the format token, but there's no example or anything so far.
Each placeholder needs to be in brackets, i.e.:
curl -s -w "%{http_code}:%{time_total}" http://127.0.0.1

How to check if a Docker image with a specific tag exist locally?

I'd like to find out if a Docker image with a specific tag exists locally. I'm fine by using a bash script if the Docker client cannot do this natively.
Just to provide some hints for a potential bash script the result of running the docker images command returns the following:
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
rabbitmq latest e8e654c05c91 5 weeks ago 143.5 MB
busybox latest 8c2e06607696 6 weeks ago 2.433 MB
rabbitmq 3.4.4 a4fbaad9f996 11 weeks ago 131.5 MB
I usually test the result of docker images -q (as in this script):
if [[ "$(docker images -q myimage:mytag 2> /dev/null)" == "" ]]; then
# do something
fi
But since docker images only takes REPOSITORY as parameter, you would need to grep on tag, without using -q.
docker images takes tags now (docker 1.8+) [REPOSITORY[:TAG]]
The other approach mentioned below is to use docker inspect.
But with docker 17+, the syntax for images is: docker image inspect (on an non-existent image, the exit status will be non-0)
As noted by iTayb in the comments:
The docker images -q method can get really slow on a machine with lots of images. It takes 44s to run on a 6,500 images machine.
The docker image inspect returns immediately.
As noted in the comments by Henry Blyth:
If you use docker image inspect my_image:my_tag, and you want to ignore the output, you can add --format="ignore me" and it will print that literally.
You can also redirect stdout by adding >/dev/null but, if you can't do that in your script, then the format option works cleanly.
Try docker inspect, for example:
$ docker inspect --type=image treeder/hello.rb:nada
Error: No such image: treeder/hello.rb:nada
[]
But now with an image that exists, you'll get a bunch of information, eg:
$ docker inspect --type=image treeder/hello.rb:latest
[
{
"Id": "85c5116a2835521de2c52f10ab5dda0ff002a4a12aa476c141aace9bc67f43ad",
"Parent": "ecf63f5eb5e89e5974875da3998d72abc0d3d0e4ae2354887fffba037b356ad5",
"Comment": "",
"Created": "2015-09-23T22:06:38.86684783Z",
...
}
]
And it's in a nice json format.
tldr:
docker image inspect myimage:mytag
By way of demonstration...
success, found image:
$ docker image pull busybox:latest
latest: Pulling from library/busybox
Digest: sha256:32f093055929dbc23dec4d03e09dfe971f5973a9ca5cf059cbfb644c206aa83f
Status: Image is up to date for busybox:latest
$ docker image inspect busybox:latest >/dev/null 2>&1 && echo yes || echo no
yes
failure, missing image:
$ docker image rm busybox:latest
Untagged: busybox:latest
Untagged: busybox#sha256:32f093055929dbc23dec4d03e09dfe971f5973a9ca5cf059cbfb644c206aa83f
$ docker image inspect busybox:latest >/dev/null 2>&1 && echo yes || echo no
no
Reference:
https://docs.docker.com/engine/reference/commandline/image_inspect/
You can use like the following:
[ -n "$(docker images -q someimage:sometag)" ] || echo "does not exist"
Or:
[ -z "$(docker images -q someimage:sometag)" ] || echo "already exists"
With the help of Vonc's answer above I created the following bash script named check.sh:
#!/bin/bash
image_and_tag="$1"
image_and_tag_array=(${image_and_tag//:/ })
if [[ "$(docker images ${image_and_tag_array[0]} | grep ${image_and_tag_array[1]} 2> /dev/null)" != "" ]]; then
echo "exists"
else
echo "doesn't exist"
fi
Using it for an existing image and tag will print exists, for example:
./check.sh rabbitmq:3.4.4
Using it for a non-existing image and tag will print doesn't exist, for example:
./check.sh rabbitmq:3.4.3
In case you are trying to search for a docker image from a docker registry, I guess the easiest way to check if a docker image is present is by using the Docker V2 REST API Tags list service
Example:-
curl $CURLOPTS -H "Authorization: Bearer $token" "https://hub.docker.com:4443/v2/your-repo-name/tags/list"
if the above result returns 200Ok with a list of image tags, then we know that image exists
{"name":"your-repo-name","tags":["1.0.0.1533677221","1.0.0.1533740305","1.0.0.1535659921","1.0.0.1535665433","latest"]}
else if you see something like
{"errors":[{"code":"NAME_UNKNOWN","message":"repository name not known to registry","detail":{"name":"your-repo-name"}}]}
then you know for sure that image doesn't exist.
Using test
if test ! -z "$(docker images -q <name:tag>)"; then
echo "Exist"
fi
or in one line
test ! -z "$(docker images -q <name:tag>)" && echo exist
In bash script I do this to check if image exists by tag :
IMAGE_NAME="mysql:5.6"
if docker image ls -a "$IMAGE_NAME" | grep -Fq "$IMAGE_NAME" 1>/dev/null; then
echo "could found image $IMAGE_NAME..."
fi
Example script above checks if mysql image with 5.6 tag exists. If you want just check if any mysql image exists without specific version then just pass repository name without tag as this :
IMAGE_NAME="mysql"
Inspired by #rubicks's response above.
To check if the image exists already
image_name_tag="alpine:3.13.3"
docker image inspect ${image_name_tag} > /dev/null
echo $?
Pull if image does not exist
docker image inspect ${image_name_tag} > /dev/null || docker pull ${image_name_tag}
Demo execution
# image_name_tag="alpine:3.13.3"
#
# docker image inspect ${image_name_tag} > /dev/null
echo $?
Error: No such image: alpine:3.13.3
# echo $?
1
# docker image inspect ${image_name_tag} > /dev/null || docker pull ${image_name_tag}
Error: No such image: alpine:3.13.3
3.13.3: Pulling from library/alpine
9aae54b2144e: Pull complete
Digest: sha256:826f70e0ac33e99a72cf20fb0571245a8fee52d68cb26d8bc58e53bfa65dcdfa
Status: Downloaded newer image for alpine:3.13.3
docker.io/library/alpine:3.13.3
# docker image inspect ${image_name_tag} > /dev/null || docker pull ${image_name_tag}
$
Just a bit from me to very good readers:
Build
#!/bin/bash -e
docker build -t smpp-gateway smpp
(if [ $(docker ps -a | grep smpp-gateway | cut -d " " -f1) ]; then \
echo $(docker rm -f smpp-gateway); \
else \
echo OK; \
fi;);
docker run --restart always -d --network="host" --name smpp-gateway smpp-gateway:latest
Watch
docker logs --tail 50 --follow --timestamps smpp-gateway
Run
sudo docker exec -it \
$(sudo docker ps | grep "smpp-gateway:latest" | cut -d " " -f1) \
/bin/bash
for specific tag name
$ docker images --filter reference='<REPOSITORY>:TAG'
for "like clause" tagname:my_image_tag --> start my_ima*
$ docker images --filter reference='<REPOSITORY>:my_ima*'
if you want to someting "the image" for example delete all images tag started "my_ima" try this
docker rmi -f $(docker images -q --filter reference='myreponame:my_ima*')
I think this functionality should be implemented inside the docker build command (using a flag?), so that it avoids a lot of code duplication.
I used the same condition as the accepted answer inside a wrapper function called docker_build so that it does the necessary checks before calling the original docker build command.
# Usage: docker_build <...> (instead of docker build)
docker_build()
{
local arguments=("$#")
local index
for (( index=0; index<$#; index++ )); do
case ${arguments[index]} in
--tag)
local tag=${arguments[index+1]}
if [[ ! -z $(docker images -q "${tag}" 2> /dev/null) ]]; then
echo "Image ${tag} already exists."
return
fi
;;
esac
done
command docker build "$#"
}
Disclaimer: This is not ready for production because it works only with space-separated arguments in long format i.e --tag hello-world:latest. Also, this just modifies the docker build command only, all other commands remain same. If anyone has improvements, please let me know.
I felt like sharing this snippet because the idea of wrapping-standard-commands-in-bash-functions, to avoid code repetition, seemed more elegant and scalable than writing wrapper statements.
I like this because it is concise and has the proper semantics. If the image exists it is true what could be easier?
if [ $(docker image ls ${build_env} --format="true") ] ;
then
echo "does exist"
fi

Resources