Skip to content

Commit aea950c

Browse files
committed
cmd/container-prune: add a GC script for containers images
This script calls skopeo delete to prune image from a remote directory. Currently only supports the FCOS tag structure. This consumes the same policy.yaml defined in #3798 See coreos/fedora-coreos-tracker#1367 See coreos/fedora-coreos-pipeline#995
1 parent 523c2a8 commit aea950c

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

cmd/coreos-assembler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var buildCommands = []string{"init", "fetch", "build", "run", "prune", "clean",
1616
var advancedBuildCommands = []string{"buildfetch", "buildupload", "oc-adm-release", "push-container"}
1717
var buildextendCommands = []string{"aliyun", "applehv", "aws", "azure", "digitalocean", "exoscale", "extensions-container", "gcp", "hashlist-experimental", "hyperv", "ibmcloud", "kubevirt", "live", "metal", "metal4k", "nutanix", "openstack", "qemu", "secex", "virtualbox", "vmware", "vultr"}
1818

19-
var utilityCommands = []string{"aws-replicate", "compress", "copy-container", "koji-upload", "kola", "push-container-manifest", "remote-build-container", "cloud-prune", "remote-session", "sign", "tag", "update-variant"}
19+
var utilityCommands = []string{"aws-replicate", "cloud-prune", "compress", "container-prune", "copy-container", "koji-upload", "kola", "push-container-manifest", "remote-build-container", "remote-session", "sign", "tag", "update-variant"}
2020
var otherCommands = []string{"shell", "meta"}
2121

2222
func init() {

src/cmd-container-prune

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/python3 -u
2+
3+
"""
4+
Prune containers from a remote registry
5+
according to the images age
6+
See cmd-cloud-prune for a policy file example
7+
"""
8+
9+
import argparse
10+
import datetime
11+
import json
12+
import os
13+
import subprocess
14+
from dateutil.relativedelta import relativedelta
15+
import requests
16+
import yaml
17+
from cosalib.cmdlib import parse_fcos_version_to_timestamp_and_stream
18+
from cosalib.cmdlib import convert_duration_to_days
19+
20+
# Dict of known streams
21+
STREAMS = {"next": 1, "testing": 2, "stable": 3,
22+
"next-devel": 10, "testing-devel": 20,
23+
"rawhide": 91, "branched": 92}
24+
25+
26+
def parse_args():
27+
parser = argparse.ArgumentParser(prog="coreos-assembler container-prune")
28+
parser.add_argument("--policy", required=True, type=str, help="Path to policy YAML file")
29+
parser.add_argument("--dry-run", help="Don't actually delete anything", action='store_true')
30+
parser.add_argument("-v", help="Increase verbosity", action='store_true')
31+
parser.add_argument("--registry-auth-file", default=os.environ.get("REGISTRY_AUTH_FILE"),
32+
help="Path to docker registry auth file. Directly passed to skopeo.")
33+
parser.add_argument("--stream", type=str, help="CoreOS stream", required=True, choices=STREAMS.keys())
34+
parser.add_argument("repository_url", help="container images URL")
35+
return parser.parse_args()
36+
37+
38+
def skopeo_delete(repo, image, auth):
39+
40+
skopeo_args = ["skopeo", "delete", f"docker://{repo}:{image}"]
41+
if auth is not None:
42+
skopeo_args.append(f"--authfile {auth}")
43+
44+
subprocess.check_output(skopeo_args)
45+
46+
47+
def get_update_graph(stream):
48+
49+
url = f"https://builds.coreos.fedoraproject.org/updates/{stream}.json"
50+
r = requests.get(url, timeout=5)
51+
if r.status_code != 200:
52+
raise Exception(f"Could not download update graph for {stream}. HTTP {r.status_code}")
53+
return r.json()
54+
55+
56+
def main():
57+
58+
args = parse_args()
59+
60+
# Load the policy file
61+
with open(args.policy, "r") as f:
62+
policy = yaml.safe_load(f)
63+
if args.stream not in policy:
64+
print(f"Stream {args.stream} is not defined in policy file; exiting...")
65+
return
66+
if 'containers' not in policy[args.stream]:
67+
print(f"No containers section for {args.stream} stream in policy; exiting...")
68+
return
69+
policy = policy[args.stream]["containers"]
70+
71+
print(f"Pulling tags from {args.repository_url}")
72+
# This is a JSON object:
73+
# {"Repository": "quay.io/jbtrystramtestimages/fcos",
74+
# "Tags": [
75+
# "40.20"40.20240301.1.0",.....]}
76+
tags_data = subprocess.check_output(["skopeo", "list-tags",
77+
f"docker://{args.repository_url}"])
78+
79+
tags_json = json.loads(tags_data)
80+
tags = tags_json['Tags']
81+
# Compute the date before we should prune images
82+
# today - prune-policy
83+
today = datetime.datetime.now()
84+
date_limit = today - relativedelta(days=convert_duration_to_days(policy))
85+
print(f"This will delete any images older than {date_limit} from the stream {args.stream}")
86+
87+
stream_id = STREAMS[args.stream]
88+
barrier_releases = set()
89+
# Get the update graph for stable streams
90+
if args.stream in ['stable', 'testing', 'next']:
91+
update_graph = get_update_graph(args.stream)['releases']
92+
# Keep only the barrier releases
93+
barrier_releases = set([release["version"] for release in update_graph if "barrier" in release])
94+
95+
for tag in tags:
96+
# silently skip known moving tags (next, stable...)
97+
if tag in STREAMS:
98+
continue
99+
100+
try:
101+
(build_date, tag_stream) = parse_fcos_version_to_timestamp_and_stream(tag)
102+
except Exception:
103+
print(f"WARNING: Ignoring unexpected tag: {tag}")
104+
continue
105+
if stream_id != tag_stream:
106+
if args.v:
107+
print(f"Skipping tag {tag} not in {args.stream} stream")
108+
continue
109+
# Make sure this is not a barrier release (for stable streams)
110+
# For non-production streams barrier_releases will be empty so
111+
# this will be no-op
112+
if tag in barrier_releases:
113+
print(f"Release {tag} is a barrier release, keeping.")
114+
continue
115+
116+
if build_date < date_limit:
117+
if args.dry_run:
118+
print(f"Dry-run: would prune image {args.repository_url}:{tag}")
119+
else:
120+
print(f"Production tag {tag} is older than {date_limit.strftime("%Y%m%d")}, pruning.")
121+
skopeo_delete(args.repository_url, tag, args.registry_auth_file)
122+
123+
124+
if __name__ == "__main__":
125+
main()

0 commit comments

Comments
 (0)