Skip to content

Commit 11779a5

Browse files
committed
Adding garbage removal for cloud uploads
1 parent 596af28 commit 11779a5

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed

src/remote_prune

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
#!/usr/bin/python3 -u
2+
3+
import argparse
4+
import subprocess
5+
import json
6+
import yaml
7+
import collections
8+
import datetime
9+
import os
10+
from dateutil.relativedelta import relativedelta
11+
12+
from cosalib.aliyun import remove_aliyun_image
13+
from cosalib.gcp import remove_gcp_image
14+
from cosalib.azure import remove_azure_image
15+
from cosalib.prune import fetch_build_meta, delete_build
16+
17+
from cosalib.aws import deregister_ami, delete_snapshot
18+
19+
Build = collections.namedtuple("Build", ["id", "timestamp", "images", "arches"])
20+
21+
22+
def main():
23+
parser = argparse.ArgumentParser(prog="coreos-assembler remote-prune")
24+
parser.add_argument("--policy", dest="policy", type=str, help="Path to policy.yaml file")
25+
parser.add_argument("--dry-run", dest='dry_run', help="Don't actually delete anything",
26+
action='store_true')
27+
parser.add_argument("--stream", dest="stream", type=str, help="Fedora stream", required=True)
28+
parser.add_argument('--azure-credentials', help='Path to Azure credentials file',
29+
default=os.environ.get("AZURE_CREDENTIALS"))
30+
parser.add_argument('--azure-resource-group', help='Resource group',
31+
default=os.environ.get('AZURE_RESOURCE_GROUP'))
32+
parser.add_argument("--gcp-json-key", help="GCP Service Account JSON Auth",
33+
default=os.environ.get("GCP_JSON_AUTH"))
34+
parser.add_argument("--gcp-project", help="GCP Project name",
35+
default=os.environ.get("GCP_PROJECT_NAME"))
36+
parser.add_argument("--aws-credentials",help="AWS Credentials",
37+
default=os.environ.get("GCP_PROJECT_NAME"))
38+
39+
# subparsers = parser.add_subparsers(dest='cmd', title='subcommands')
40+
# subparsers.required = True
41+
42+
# s3 = subparsers.add_parser('s3', help='Prune s3 buckets')
43+
# s3.add_argument("--bucket", help="Bucket name")
44+
# s3.add_argument("--prefix", help="Key prefix")
45+
# s3.add_argument("--force", help="Wipe s3 key ignoring the errors",
46+
# action='store_true')
47+
48+
args = parser.parse_args()
49+
50+
cloud_config = {
51+
'azure': {
52+
'credentials': args.azure_credentials,
53+
'resource-group': args.azure_resource_group,
54+
},
55+
'gcp': {
56+
'json-key': args.gcp_json_key,
57+
'project': args.gcp_project,
58+
}
59+
}
60+
61+
policy = open(f"{args.policy}")
62+
policy = yaml.safe_load(policy)
63+
stream = args.stream
64+
65+
if stream in policy:
66+
# If the build key is set in the policy file, then the cloud-uploads key must
67+
# also be present, and the duration of cloud-uploads must be equal or shorter
68+
if "build" in policy[stream].keys():
69+
cloudUploadsCheck(policy[stream])
70+
buildJsonData = getBuildsForStream(stream)
71+
# action is basically whatever is needed to be pruned for the respective stream
72+
for action in policy[stream]:
73+
years = policy[stream][action]
74+
#Ref date: Artifacts older than this date will be deleted
75+
refDate = datetime.datetime.now() - relativedelta(years=int(years[:-1]))
76+
print(f"Reference Date for action {action} is {refDate}")
77+
78+
for index, build in enumerate(buildJsonData["builds"]):
79+
build_id = build["id"]
80+
if "policy-cleanup" in build.keys():
81+
if action in build["policy-cleanup"]:
82+
print(f"The {build_id} has already had {action} pruning completed")
83+
break
84+
timestamp = build_id.split('.')[1]
85+
buildDate = datetime.datetime(int(timestamp[0:4]), int(timestamp[4:6]), int(timestamp[-2:]))
86+
if buildDate < refDate:
87+
# For testing pusposes only using one build
88+
if build_id == "35.20210508.91.0":
89+
for arch in build["arches"]:
90+
print(f"Pruning {years} old {action} for {build_id} for {arch} in {stream}")
91+
# buildFetch(args.stream, build_id, arch)
92+
metaJson = open(f"builds/{build_id}/{arch}/meta.json")
93+
metaData = json.load(metaJson)
94+
95+
images = {
96+
"amis": metaData.get("amis") or [],
97+
"azure": metaData.get("azure") or [],
98+
"gcp": metaData.get("gcp") or [],
99+
}
100+
currentBuild = Build(
101+
id=build_id,
102+
timestamp=timestamp,
103+
images=images,
104+
arches=arch,
105+
)
106+
match action:
107+
case "cloud-uploads":
108+
delete_cloud_resource(currentBuild, cloud_config, args.dry_run)
109+
if not args.dry_run:
110+
build.setdefault("policy-cleanup", []).append(action)
111+
buildJsonData["builds"][index] = build
112+
case "build":
113+
print(f"Will delete everything for {build_id}")
114+
# pprint.pprint(buildJsonData['builds'], compact=True)
115+
with open(f"builds/builds.json", "w") as json_file:
116+
json_file.write(json.dumps(buildJsonData))
117+
118+
def delete_cloud_resource(build, cloud_config, dry_run):
119+
print(f"Deleting cloud uploads for {build.id}")
120+
errors = []
121+
# Unregister AMIs and snapshots
122+
for ami in build.images.get("amis", []):
123+
region_name = ami.get("name")
124+
ami_id = ami.get("hvm")
125+
snapshot_id = ami.get("snapshot")
126+
if dry_run:
127+
print(f"Will delete {ami_id} and {snapshot_id}")
128+
else:
129+
if ami_id and region_name:
130+
try:
131+
deregister_ami(ami_id, region=region_name)
132+
except Exception as e:
133+
errors.append(e)
134+
if snapshot_id and region_name:
135+
try:
136+
delete_snapshot(snapshot_id, region=region_name)
137+
except Exception as e:
138+
errors.append(e)
139+
140+
aliyun = build.images.get("aliyun")
141+
if aliyun:
142+
region_name = aliyun.get("name")
143+
aliyun_id = aliyun.get("hvm")
144+
if dry_run:
145+
print(f"Will delete {aliyun_id} in {region_name}")
146+
else:
147+
if region_name and aliyun_id:
148+
try:
149+
remove_aliyun_image(aliyun_id, region=region_name)
150+
except Exception as e:
151+
errors.append(e)
152+
153+
azure = build.images.get("azure")
154+
if azure:
155+
image = azure.get("image")
156+
resource_group = cloud_config.get("azure", {}).get("resource-group")
157+
credentials = cloud_config.get("azure", {}).get("credentials")
158+
if dry_run:
159+
print(f"Will delete Azure {image}")
160+
else:
161+
if image and resource_group and credentials:
162+
try:
163+
remove_azure_image(image, resource_group, credentials)
164+
except Exception as e:
165+
errors.append(e)
166+
167+
gcp = build.images.get("gcp")
168+
if gcp:
169+
gcp_image = gcp.get("image")
170+
json_key = cloud_config.get("gcp", {}).get("json-key")
171+
project = cloud_config.get("gcp", {}).get("project")
172+
if dry_run:
173+
print(f"Will delete {gcp_image}")
174+
else:
175+
if gcp_image and json_key and project:
176+
try:
177+
remove_gcp_image(gcp_image, json_key, project)
178+
except Exception as e:
179+
errors.append(e)
180+
181+
if len(errors) != 0:
182+
print(f"Found errors when removing build {build.id}:")
183+
for e in errors:
184+
print(e)
185+
186+
def cloudUploadsCheck(actions):
187+
if "cloud-uploads" in actions.keys():
188+
cloud_uploads_duration = actions["cloud-uploads"]
189+
build_duration = actions["build"]
190+
# assumption we are keeping the duration in years
191+
assert cloud_uploads_duration < build_duration
192+
else:
193+
print(
194+
"cloud-uploads must be set or be less than builds pruning duration in policy.yaml"
195+
)
196+
197+
198+
def getBuildsForStream(stream):
199+
# buildFetchCmd = 'cosa buildfetch --stream='+ stream + ' --arch=all'
200+
# subprocess.check_output(buildFetchCmd.split(' '), shell=True)
201+
202+
f = open(f"builds/builds.json")
203+
buildJsonData = json.load(f)
204+
return buildJsonData
205+
206+
207+
def buildFetch(stream, build, arch):
208+
buildFetchCmd = (
209+
"cosa buildfetch --stream=" + stream + " --build=" + build + " --arch=" + arch
210+
)
211+
subprocess.check_output(buildFetchCmd.split(" "))
212+
213+
#Ignore this
214+
def awsPrune(amis, dry_run):
215+
print(f"The AMIs are {amis}")
216+
errors = []
217+
for ami in amis:
218+
region_name = ami.get("name")
219+
ami_id = ami.get("hvm")
220+
snapshot_id = ami.get("snapshot")
221+
if dry_run:
222+
print(f"Would prune {ami_id} and {snapshot_id} in {region_name}")
223+
else:
224+
if ami_id and region_name:
225+
try:
226+
deregister_ami(ami_id, region=region_name)
227+
except Exception as e:
228+
errors.append(e)
229+
if snapshot_id and region_name:
230+
try:
231+
delete_snapshot(snapshot_id, region=region_name)
232+
except Exception as e:
233+
errors.append(e)
234+
235+
if __name__ == "__main__":
236+
main()

0 commit comments

Comments
 (0)