-
Notifications
You must be signed in to change notification settings - Fork 7
/
gcp_delete_old_images.py
executable file
·162 lines (129 loc) · 5.85 KB
/
gcp_delete_old_images.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env python3
# Delete images older than 2 weeks, except if an image is the newest image in
# an image family
import json
import os
import subprocess
import sys
def delete_old_vm_images():
print("Deleting VM images")
base_cmd = ['gcloud', 'compute', '--project',
os.environ['GCP_PROJECT'], 'images']
# determine all families, no smarter way than
# listing all images seems to exist
families_cmd = base_cmd + ['list', '--format',
'object value(family)', '--no-standard-images']
res = subprocess.run(families_cmd, capture_output=True,
check=True, text=True)
families = set(res.stdout.split())
# find the newest image for each family
newest_family_members = set()
for family in families:
newest_cmd = base_cmd + ['describe-from-family', '--format',
'object value(name)', family]
res = subprocess.run(newest_cmd, capture_output=True,
check=True, text=True)
newest_family_members.add(res.stdout.strip())
# get all old images, including the newest image in a
# family (will be skipped below)
old_images_cmd = base_cmd + ['list', '--format', 'object value(name)',
'--no-standard-images', '--filter',
'creationTimestamp < -P2W']
res = subprocess.run(old_images_cmd, capture_output=True,
check=True, text=True)
old_images = res.stdout.split()
# filter to-be-deleted images by the newest image in a family
delete_images = []
for old_image in old_images:
if old_image in newest_family_members:
print(f"not deleting {old_image}, it's the newest family member")
else:
delete_images.append(old_image)
if len(delete_images) == 0:
print("no VM images to delete")
return
print("deleting images: ", ', '.join(delete_images))
# finally delete old images
delete_cmd = base_cmd + ['delete', '--quiet'] + delete_images
subprocess.run(delete_cmd, check=True)
def delete_old_docker_images_helper(delete_images, base_cmd,
latest_images_manifests,
with_tags=False):
if not delete_images:
print('no docker images to delete ' +
('with tags' if with_tags else 'without tags'))
return
delete_images = [f"{image['package']}@{image['version']}" for
image in delete_images]
print('deleting docker images ' +
('with tags' if with_tags else 'without tags') +
':\n' + '\n'.join(delete_images))
for image in delete_images:
# Add '--delete-tags' when deleting images with tags, otherwise don't
delete_cmd = base_cmd + ['delete', '--quiet'] + \
(['--delete-tags', image] if with_tags else [image])
subprocess.run(delete_cmd, check=True)
def get_manifests(images):
latest_images_manifests = set()
for image in images:
podman_cmd = ['podman', 'manifest', 'inspect',
f'{image["package"]}@{image["version"]}']
res = subprocess.run(podman_cmd, capture_output=True,
check=True, text=True)
manifest = json.loads(res.stdout)
if manifest.get('manifests', False):
image_manifests = manifest['manifests']
for image_manifest in image_manifests:
latest_images_manifests.add(image_manifest['digest'])
return latest_images_manifests
def delete_old_docker_images():
print("\nDeleting docker images")
base_cmd = ['gcloud', 'artifacts', 'docker', 'images']
get_images_cmd = base_cmd + ['list',
'--include-tags', '--format',
'json(package,version,tags)',
'--filter', 'createTime < -P2W',
os.environ['GCP_REPO']]
res = subprocess.run(get_images_cmd, capture_output=True,
check=True, text=True)
old_images = json.loads(res.stdout)
if len(old_images) == 0:
print("no docker images to delete")
return
images_with_tags = []
images_without_tags = []
latest_images = []
for image in old_images:
if 'latest' in image['tags']:
latest_images.append(image)
elif image['tags']:
images_with_tags.append(image)
else:
images_without_tags.append(image)
# find the latest images' manifests
latest_images_manifests = get_manifests(latest_images)
# filter to-be-deleted images by the latest images' manifests
images_without_tags_filtered = []
for image in images_without_tags:
if image['version'] in latest_images_manifests:
print(f"Not deleting {image['package']}@{image['version']} " +
"because it is manifest of one of the latest images'")
else:
images_without_tags_filtered.append(image)
# first we need to call delete images function with images_with_tags
# because, images_without_tags depends on the images_with_tags and can't
# be deleted if dependent image is not deleted yet.
# So, first delete images_with_tags; then images_without_tags.
delete_old_docker_images_helper(images_with_tags, base_cmd,
latest_images_manifests,
with_tags=True)
delete_old_docker_images_helper(images_without_tags_filtered, base_cmd,
latest_images_manifests)
def main():
if 'GCP_PROJECT' not in os.environ or 'GCP_REPO' not in os.environ:
print("GCP_PROJECT or GCP_REPO are not set", file=sys.stderr)
sys.exit(1)
delete_old_vm_images()
delete_old_docker_images()
if __name__ == "__main__":
main()