* added "no-validate-ssl" parameter to disable ssl verification messages for self signed certificates
* reworked tag selection routine ** "tags-like" parameter adds regexp like filtering ** "keep-tags" parameter specifies tag names that are to keep, regardless of selection ** "keep-tags-like" dito, but regexp based The keep tags routine works by comparing needed manifest digests - they are read and compared before deleting a manifest.
This commit is contained in:
25
README.md
25
README.md
@@ -51,6 +51,23 @@ You can change the number of tags to keep, e.g. 5:
|
|||||||
registry.py -l user:pass -r https://example.com:5000 --delete --num 5
|
registry.py -l user:pass -r https://example.com:5000 --delete --num 5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You may also specify tags to be deleted using a list of regexp based names.
|
||||||
|
The following command would delete all tags containing "snapshot-" and beginning with "stable-" and a 4 digit number:
|
||||||
|
|
||||||
|
```
|
||||||
|
registry.py -l user:pass -r https://example.com:5000 --delete --tags-like "snapshot-" "^stable-[0-9]{4}.*"
|
||||||
|
```
|
||||||
|
|
||||||
|
As one manifest may be referenced by more than one tag, you may add tags, whose manifests should NOT be deleted.
|
||||||
|
A tag that would otherwise be deleted, but whose manifest references one of those "kept" tags, is spared for deletion.
|
||||||
|
In the following case, all tags beginning with "snapshot-" will be deleted, safe those whose manifest point to "stable" or "latest"
|
||||||
|
|
||||||
|
```
|
||||||
|
registry.py -l user:pass -r https://example.com:5000 --delete --tags-like "snapshot-" --keep-tags "stable" "latest"
|
||||||
|
```
|
||||||
|
The last parameter is also available as regexp option with "--keep-tags-like".
|
||||||
|
|
||||||
|
|
||||||
Delete all tags for particular image (e.g. delete all ubuntu tags):
|
Delete all tags for particular image (e.g. delete all ubuntu tags):
|
||||||
```
|
```
|
||||||
registry.py -l user:pass -r https://example.com:5000 -i ubuntu --delete-all
|
registry.py -l user:pass -r https://example.com:5000 -i ubuntu --delete-all
|
||||||
@@ -61,6 +78,14 @@ Delete all tags for all images (do you really want to do it?):
|
|||||||
registry.py -l user:pass -r https://example.com:5000 --delete-all
|
registry.py -l user:pass -r https://example.com:5000 --delete-all
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disable ssl verification
|
||||||
|
|
||||||
|
If you are using docker registry with a self signed ssl certificate, you can disable ssl verification:
|
||||||
|
```
|
||||||
|
registry.py -l user:pass -r --no-validate-ssl https://example.com:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Important notes:
|
## Important notes:
|
||||||
|
|
||||||
### garbage-collection in docker-registry
|
### garbage-collection in docker-registry
|
||||||
|
|||||||
112
registry.py
112
registry.py
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import argparse
|
import argparse
|
||||||
@@ -39,6 +40,7 @@ class Registry:
|
|||||||
username = ""
|
username = ""
|
||||||
password = ""
|
password = ""
|
||||||
hostname = ""
|
hostname = ""
|
||||||
|
no_validate_ssl = False;
|
||||||
|
|
||||||
# this is required for proper digest processing
|
# this is required for proper digest processing
|
||||||
HEADERS = {"Accept":
|
HEADERS = {"Accept":
|
||||||
@@ -47,7 +49,7 @@ class Registry:
|
|||||||
# store last error if any
|
# store last error if any
|
||||||
__error = None
|
__error = None
|
||||||
|
|
||||||
def __init__(self, host, login):
|
def __init__(self, host, login, no_validate_ssl):
|
||||||
if login != None:
|
if login != None:
|
||||||
if not ':' in login:
|
if not ':' in login:
|
||||||
print "Please provide -l in the form USER:PASSWORD"
|
print "Please provide -l in the form USER:PASSWORD"
|
||||||
@@ -56,6 +58,7 @@ class Registry:
|
|||||||
(self.username, self.password) = login.split(':')
|
(self.username, self.password) = login.split(':')
|
||||||
|
|
||||||
self.hostname = host
|
self.hostname = host
|
||||||
|
self.no_validate_ssl = no_validate_ssl
|
||||||
|
|
||||||
def __atoi(self, text):
|
def __atoi(self, text):
|
||||||
return int(text) if text.isdigit() else text
|
return int(text) if text.isdigit() else text
|
||||||
@@ -74,7 +77,8 @@ class Registry:
|
|||||||
method, "{0}{1}".format(self.hostname, path),
|
method, "{0}{1}".format(self.hostname, path),
|
||||||
headers = self.HEADERS,
|
headers = self.HEADERS,
|
||||||
auth=(None if self.username == ""
|
auth=(None if self.username == ""
|
||||||
else (self.username, self.password)))
|
else (self.username, self.password)),
|
||||||
|
verify = not self.no_validate_ssl)
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
print "cannot connect to {0}\nerror {1}".format(
|
print "cannot connect to {0}\nerror {1}".format(
|
||||||
@@ -120,13 +124,17 @@ class Registry:
|
|||||||
|
|
||||||
return tag_digest
|
return tag_digest
|
||||||
|
|
||||||
def delete_tag(self, image_name, tag, dry_run):
|
def delete_tag(self, image_name, tag, dry_run, tag_digests_to_ignore):
|
||||||
if dry_run:
|
if dry_run:
|
||||||
print 'would delete tag {0}'.format(tag)
|
print 'would delete tag {0}'.format(tag)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
tag_digest = self.get_tag_digest(image_name, tag)
|
tag_digest = self.get_tag_digest(image_name, tag)
|
||||||
|
|
||||||
|
if tag_digest in tag_digests_to_ignore:
|
||||||
|
print "Digest {0} for tag {1} is referenced by another tag or has already been deleted and will be ignored".format(tag_digest, tag)
|
||||||
|
return True
|
||||||
|
|
||||||
if tag_digest == None:
|
if tag_digest == None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -137,6 +145,8 @@ class Registry:
|
|||||||
print "failed, error: {0}".format(self.__error)
|
print "failed, error: {0}".format(self.__error)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
tag_digests_to_ignore.append(tag_digest)
|
||||||
|
|
||||||
print "done"
|
print "done"
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -235,6 +245,34 @@ for more detail on garbage collection read here:
|
|||||||
nargs='+',
|
nargs='+',
|
||||||
metavar="IMAGE:[TAG]")
|
metavar="IMAGE:[TAG]")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--keep-tags',
|
||||||
|
nargs='+',
|
||||||
|
help="List of tags that will be omitted from deletion if used in combination with --delete or --delete-all",
|
||||||
|
required=False,
|
||||||
|
default=[])
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--tags-like',
|
||||||
|
nargs='+',
|
||||||
|
help="List of tags (regexp check) that will be handled",
|
||||||
|
required=False,
|
||||||
|
default=[])
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--keep-tags-like',
|
||||||
|
nargs='+',
|
||||||
|
help="List of tags (regexp check) that will be omitted from deletion if used in combination with --delete or --delete-all",
|
||||||
|
required=False,
|
||||||
|
default=[])
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--no-validate-ssl',
|
||||||
|
help="Disable ssl validation",
|
||||||
|
action='store_const',
|
||||||
|
default=False,
|
||||||
|
const=True)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--delete-all',
|
'--delete-all',
|
||||||
help="Will delete all tags. Be careful with this!",
|
help="Will delete all tags. Be careful with this!",
|
||||||
@@ -254,9 +292,28 @@ for more detail on garbage collection read here:
|
|||||||
|
|
||||||
|
|
||||||
def delete_tags(
|
def delete_tags(
|
||||||
registry, image_name, dry_run, tags_to_delete):
|
registry, image_name, dry_run, tags_to_delete, tags_to_keep):
|
||||||
|
|
||||||
|
keep_tag_digests = []
|
||||||
|
|
||||||
|
if tags_to_keep:
|
||||||
|
print "Getting digests for tags to keep:"
|
||||||
|
for tag in tags_to_keep:
|
||||||
|
|
||||||
|
print "Getting digest for tag {0}".format(tag)
|
||||||
|
digest = registry.get_tag_digest(image_name, tag)
|
||||||
|
if digest is None:
|
||||||
|
print "Tag {0} does not exist for image {1}. Ignore here.".format(tag, image_name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
print "Keep digest {0} for tag {1}".format(digest, tag)
|
||||||
|
|
||||||
|
keep_tag_digests.append(digest)
|
||||||
|
|
||||||
for tag in tags_to_delete:
|
for tag in tags_to_delete:
|
||||||
|
if tag in tags_to_keep:
|
||||||
|
continue
|
||||||
|
|
||||||
print " deleting tag {0}".format(tag)
|
print " deleting tag {0}".format(tag)
|
||||||
|
|
||||||
## deleting layers is disabled because
|
## deleting layers is disabled because
|
||||||
@@ -266,14 +323,17 @@ def delete_tags(
|
|||||||
## layer_digest = layer['digest']
|
## layer_digest = layer['digest']
|
||||||
## registry.delete_tag_layer(image_name, layer_digest, dry_run)
|
## registry.delete_tag_layer(image_name, layer_digest, dry_run)
|
||||||
|
|
||||||
registry.delete_tag(image_name, tag, dry_run)
|
registry.delete_tag(image_name, tag, dry_run, keep_tag_digests)
|
||||||
|
|
||||||
|
|
||||||
def main_loop(args):
|
def main_loop(args):
|
||||||
|
|
||||||
keep_last_versions = int(args.num)
|
keep_last_versions = int(args.num)
|
||||||
|
|
||||||
registry = Registry(args.host, args.login)
|
if args.no_validate_ssl:
|
||||||
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
|
registry = Registry(args.host, args.login, args.no_validate_ssl)
|
||||||
if args.delete:
|
if args.delete:
|
||||||
print "Will delete all but {0} last tags".format(keep_last_versions)
|
print "Will delete all but {0} last tags".format(keep_last_versions)
|
||||||
|
|
||||||
@@ -285,18 +345,32 @@ def main_loop(args):
|
|||||||
# loop through registry's images
|
# loop through registry's images
|
||||||
# or through the ones given in command line
|
# or through the ones given in command line
|
||||||
for image_name in image_list:
|
for image_name in image_list:
|
||||||
|
print "---------------------------------"
|
||||||
print "Image: {0}".format(image_name)
|
print "Image: {0}".format(image_name)
|
||||||
|
|
||||||
|
tags_list = set()
|
||||||
|
all_tags_list = registry.list_tags(image_name)
|
||||||
|
|
||||||
|
if not all_tags_list:
|
||||||
|
print " no tags!"
|
||||||
|
continue
|
||||||
|
|
||||||
|
if args.tags_like:
|
||||||
|
for tag_like in args.tags_like:
|
||||||
|
print "tag like: {0}".format(tag_like)
|
||||||
|
for tag in all_tags_list:
|
||||||
|
if re.search(tag_like, tag):
|
||||||
|
print "Adding {0} to tags list".format(tag)
|
||||||
|
tags_list.add(tag)
|
||||||
|
|
||||||
# get tags from arguments if any
|
# get tags from arguments if any
|
||||||
if ":" in image_name:
|
if ":" in image_name:
|
||||||
(image_name, tag_name) = image_name.split(":")
|
(image_name, tag_name) = image_name.split(":")
|
||||||
tags_list = [tag_name]
|
tags_list.add(tag_name)
|
||||||
else:
|
|
||||||
tags_list = registry.list_tags(image_name)
|
|
||||||
|
|
||||||
if tags_list == None or tags_list == []:
|
|
||||||
print " no tags!"
|
if len(tags_list) == 0:
|
||||||
continue
|
tags_list.update(all_tags_list)
|
||||||
|
|
||||||
# print tags and optionally layers
|
# print tags and optionally layers
|
||||||
for tag in tags_list:
|
for tag in tags_list:
|
||||||
@@ -310,6 +384,18 @@ def main_loop(args):
|
|||||||
print " layer: {0}".format(
|
print " layer: {0}".format(
|
||||||
layer['blobSum'])
|
layer['blobSum'])
|
||||||
|
|
||||||
|
# add tags to "tags_to_keep" list, if we have regexp "tags_to_keep" entries:
|
||||||
|
if args.keep_tags_like:
|
||||||
|
for keep_like in args.keep_tags_like:
|
||||||
|
print "keep tag like: {0}".format(keep_like)
|
||||||
|
for tag in tags_list:
|
||||||
|
if re.search(keep_like, tag):
|
||||||
|
print "Adding {0} to keep tags list".format(tag)
|
||||||
|
args.keep_tags.append(tag)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# delete tags if told so
|
# delete tags if told so
|
||||||
if args.delete or args.delete_all:
|
if args.delete or args.delete_all:
|
||||||
if args.delete_all:
|
if args.delete_all:
|
||||||
@@ -319,7 +405,7 @@ def main_loop(args):
|
|||||||
|
|
||||||
delete_tags(
|
delete_tags(
|
||||||
registry, image_name, args.dry_run,
|
registry, image_name, args.dry_run,
|
||||||
tags_list_to_delete)
|
tags_list_to_delete, args.keep_tags)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|||||||
Reference in New Issue
Block a user