Skip to content

Commit 8ac5e2c

Browse files
committed
gc: merge container-prune into cloud-prune
Merged the code of the container gc into the cloud one, and update builds.json. Go through the tags in base-oscontainer data in meta.json and prune every tag except the stream-name itself which are moving tags.
1 parent 48fba72 commit 8ac5e2c

File tree

3 files changed

+96
-128
lines changed

3 files changed

+96
-128
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", "cloud-prune", "compress", "container-prune", "copy-container", "koji-upload", "kola", "push-container-manifest", "remote-build-container", "remote-session", "sign", "tag", "update-variant"}
19+
var utilityCommands = []string{"aws-replicate", "cloud-prune", "compress", "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-cloud-prune

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434

3535
import argparse
3636
import json
37+
import subprocess
3738
from urllib.parse import urlparse
3839
import pytz
40+
import requests
3941
import yaml
4042
import collections
4143
import datetime
@@ -59,6 +61,8 @@ CACHE_MAX_AGE_METADATA = 60 * 5
5961
# is up to date.
6062
SUPPORTED = ["amis", "gcp"]
6163
UNSUPPORTED = ["aliyun", "azure", "ibmcloud", "powervs"]
64+
# list of known streams with containers
65+
STREAMS = {"next", "testing", "stable", "next-devel", "testing-devel", "rawhide", "branched"}
6266

6367

6468
def parse_args():
@@ -70,6 +74,8 @@ def parse_args():
7074
parser.add_argument("--gcp-json-key", help="GCP Service Account JSON Auth", default=os.environ.get("GCP_JSON_AUTH"))
7175
parser.add_argument("--acl", help="ACL for objects", action='store', default='private')
7276
parser.add_argument("--aws-config-file", default=os.environ.get("AWS_CONFIG_FILE"), help="Path to AWS config file")
77+
parser.add_argument("--registry-auth-file", default=os.environ.get("REGISTRY_AUTH_FILE"),
78+
help="Path to docker registry auth file. Directly passed to skopeo.")
7379
return parser.parse_args()
7480

7581

@@ -110,6 +116,12 @@ def main():
110116
builds = builds_json_data["builds"]
111117
pruned_build_ids = []
112118
images_to_keep = policy.get(stream, {}).get("images-keep", [])
119+
barrier_releases = set()
120+
# Get the update graph for stable streams
121+
if stream in ['stable', 'testing', 'next']:
122+
update_graph = get_update_graph(stream)['releases']
123+
# Keep only the barrier releases
124+
barrier_releases = set([release["version"] for release in update_graph if "barrier" in release])
113125

114126
# Iterate through builds from oldest to newest
115127
for build in reversed(builds):
@@ -125,7 +137,7 @@ def main():
125137
current_build = Build(id=build_id, images=images, arch=arch, meta_json=meta_json)
126138

127139
# Iterate over actions (policy types) to apply pruning
128-
for action in ['cloud-uploads', 'images', 'build']:
140+
for action in ['cloud-uploads', 'images', 'build', 'containers']:
129141
if action not in policy[stream]:
130142
continue
131143
action_duration = convert_duration_to_days(policy[stream][action])
@@ -162,7 +174,24 @@ def main():
162174
case "build":
163175
prune_build(s3_client, bucket, prefix, build_id, args.dry_run)
164176
pruned_build_ids.append(build_id)
165-
177+
case "containers":
178+
# Only prune for x86_64 as it's required for all builds, covering all architectures.
179+
if arch == "x86_64":
180+
if build_id in barrier_releases:
181+
print(f"Release {build_id} is a barrier release, keeping.")
182+
continue
183+
# Retrieve container tags excluding the stream name since it updates with each release.
184+
container_tags = get_container_tags(meta_json, exclude=[stream])
185+
if container_tags:
186+
containers_config = {
187+
"dry_run": args.dry_run,
188+
"repository_url": container_tags.get("repository_url", ""),
189+
"registry_auth_file": args.registry_auth_file,
190+
}
191+
for tag in container_tags["tags"]:
192+
prune_container(tag, containers_config)
193+
else:
194+
print(f"No container tags to prune for build {build_id} on architecture {arch}.")
166195
# Update policy-cleanup after pruning actions for the architecture
167196
policy_cleanup = build.setdefault("policy-cleanup", {})
168197
for action in policy[stream].keys(): # Only update actions specified in policy[stream]
@@ -174,6 +203,9 @@ def main():
174203
if "images" not in policy_cleanup:
175204
policy_cleanup["images"] = True
176205
policy_cleanup["images-kept"] = images_to_keep
206+
case "containers":
207+
if "containers" not in policy_cleanup:
208+
policy_cleanup["containers"] = True
177209

178210
if pruned_build_ids:
179211
if "tombstone-builds" not in builds_json_data:
@@ -414,5 +446,66 @@ def prune_build(s3_client, bucket, prefix, build_id, dry_run):
414446
raise Exception(f"Error pruning {build_id}: {e.response['Error']['Message']}")
415447

416448

449+
def get_container_tags(meta_json, exclude):
450+
container_tags = {}
451+
base_oscontainer = meta_json.get("base-oscontainer")
452+
if base_oscontainer:
453+
tags = base_oscontainer.get("tags", [])
454+
# Exclude any tags requested by the caller to be excluded
455+
filtered_tags = [tag for tag in tags if tag not in exclude]
456+
if filtered_tags:
457+
container_tags["tags"] = filtered_tags
458+
container_tags["repository_url"] = base_oscontainer.get("image")
459+
return container_tags
460+
461+
462+
def prune_container(tag, containers_config):
463+
if containers_config["dry_run"]:
464+
print(f"Would prune image {containers_config['repository_url']}:{tag}")
465+
else:
466+
skopeo_delete(containers_config["repository_url"], tag, containers_config["registry_auth_file"])
467+
468+
469+
def get_update_graph(stream):
470+
url = f"https://builds.coreos.fedoraproject.org/updates/{stream}.json"
471+
r = requests.get(url, timeout=5)
472+
if r.status_code != 200:
473+
raise Exception(f"Could not download update graph for {stream}. HTTP {r.status_code}")
474+
return r.json()
475+
476+
477+
def skopeo_inspect(repo, image, auth):
478+
skopeo_args = ["skopeo", "inspect", "--no-tags", "--retry-times=10", f"docker://{repo}:{image}"]
479+
if auth:
480+
skopeo_args.extend(["--authfile", auth])
481+
try:
482+
subprocess.check_output(skopeo_args, stderr=subprocess.STDOUT)
483+
return True # Inspection succeeded
484+
except subprocess.CalledProcessError as e:
485+
exit_code = e.returncode
486+
error_message = e.output.decode("utf-8")
487+
488+
# Exit code 2 indicates the image tag does not exist. We will consider it as pruned.
489+
if exit_code == 2:
490+
print(f"Skipping deletion for {repo}:{image} since the tag does not exist.")
491+
return False
492+
else:
493+
# Handle other types of errors
494+
raise Exception(f"Inspection failed for {repo}:{image} with exit code {exit_code}: {error_message}")
495+
496+
497+
def skopeo_delete(repo, image, auth):
498+
if skopeo_inspect(repo, image, auth): # Only proceed if inspection succeeds
499+
skopeo_args = ["skopeo", "delete", f"docker://{repo}:{image}"]
500+
if auth:
501+
skopeo_args.extend(["--authfile", auth])
502+
try:
503+
subprocess.check_output(skopeo_args, stderr=subprocess.STDOUT)
504+
print(f"Image {repo}:{image} deleted successfully.")
505+
except subprocess.CalledProcessError as e:
506+
# Throw an exception in case the delete command fail despite the image existing
507+
raise Exception("An error occurred during deletion:", e.output.decode("utf-8"))
508+
509+
417510
if __name__ == "__main__":
418511
main()

src/cmd-container-prune

Lines changed: 0 additions & 125 deletions
This file was deleted.

0 commit comments

Comments
 (0)