-
Notifications
You must be signed in to change notification settings - Fork 678
/
Copy pathgce-import
executable file
·167 lines (146 loc) · 5.78 KB
/
gce-import
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
163
164
165
166
167
#!/usr/bin/env python3
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import date
import io
import subprocess
import tarfile
from uuid import uuid4
from google.cloud import compute
from google.cloud import exceptions
from google.cloud import storage
IPXE_STORAGE_PREFIX = 'ipxe-upload-temp-'
FEATURE_GVNIC = compute.GuestOsFeature(type_="GVNIC")
FEATURE_IDPF = compute.GuestOsFeature(type_="IDPF")
FEATURE_UEFI = compute.GuestOsFeature(type_="UEFI_COMPATIBLE")
POLICY_PUBLIC = compute.Policy(bindings=[{
"role": "roles/compute.imageUser",
"members": ["allAuthenticatedUsers"],
}])
def delete_temp_bucket(bucket):
"""Remove temporary bucket"""
assert bucket.name.startswith(IPXE_STORAGE_PREFIX)
for blob in bucket.list_blobs(prefix=IPXE_STORAGE_PREFIX):
assert blob.name.startswith(IPXE_STORAGE_PREFIX)
blob.delete()
if not list(bucket.list_blobs()):
bucket.delete()
def create_temp_bucket(location):
"""Create temporary bucket (and remove any stale temporary buckets)"""
client = storage.Client()
for bucket in client.list_buckets(prefix=IPXE_STORAGE_PREFIX):
delete_temp_bucket(bucket)
name = '%s%s' % (IPXE_STORAGE_PREFIX, uuid4())
return client.create_bucket(name, location=location)
def create_tarball(image):
"""Create raw disk image tarball"""
tarball = io.BytesIO()
with tarfile.open(fileobj=tarball, mode='w:gz',
format=tarfile.GNU_FORMAT) as tar:
tar.add(image, arcname='disk.raw')
tarball.seek(0)
return tarball
def upload_blob(bucket, image):
"""Upload raw disk image blob"""
blob = bucket.blob('%s%s.tar.gz' % (IPXE_STORAGE_PREFIX, uuid4()))
tarball = create_tarball(image)
blob.upload_from_file(tarball)
return blob
def detect_uefi(image):
"""Identify UEFI CPU architecture(s)"""
mdir = subprocess.run(['mdir', '-b', '-i', image, '::/EFI/BOOT'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=False)
mapping = {
b'BOOTX64.EFI': 'x86_64',
b'BOOTAA64.EFI': 'arm64',
}
uefi = [
arch
for filename, arch in mapping.items()
if filename in mdir.stdout
]
return uefi
def image_architecture(uefi):
"""Get image architecture"""
return uefi[0] if len(uefi) == 1 else None if uefi else 'x86_64'
def image_features(uefi):
"""Get image feature list"""
features = [FEATURE_GVNIC, FEATURE_IDPF]
if uefi:
features.append(FEATURE_UEFI)
return features
def image_name(base, uefi):
"""Calculate image name or family name"""
suffix = ('-uefi-%s' % uefi[0].replace('_', '-') if len(uefi) == 1 else
'-uefi-multi' if uefi else '')
return '%s%s' % (base, suffix)
def create_image(project, basename, basefamily, overwrite, public, bucket,
image):
"""Create image"""
client = compute.ImagesClient()
uefi = detect_uefi(image)
architecture = image_architecture(uefi)
features = image_features(uefi)
name = image_name(basename, uefi)
family = image_name(basefamily, uefi)
if overwrite:
try:
client.delete(project=project, image=name).result()
except exceptions.NotFound:
pass
blob = upload_blob(bucket, image)
disk = compute.RawDisk(source=blob.public_url)
image = compute.Image(name=name, family=family, architecture=architecture,
guest_os_features=features, raw_disk=disk)
client.insert(project=project, image_resource=image).result()
if public:
request = compute.GlobalSetPolicyRequest(policy=POLICY_PUBLIC)
client.set_iam_policy(project=project, resource=name,
global_set_policy_request_resource=request)
image = client.get(project=project, image=name)
return image
# Parse command-line arguments
#
parser = argparse.ArgumentParser(description="Import Google Cloud image")
parser.add_argument('--name', '-n',
help="Base image name")
parser.add_argument('--family', '-f',
help="Base family name")
parser.add_argument('--public', '-p', action='store_true',
help="Make image public")
parser.add_argument('--overwrite', action='store_true',
help="Overwrite any existing image with same name")
parser.add_argument('--project', '-j', default="ipxe-images",
help="Google Cloud project")
parser.add_argument('--location', '-l',
help="Google Cloud Storage initial location")
parser.add_argument('image', nargs='+', help="iPXE disk image")
args = parser.parse_args()
# Use default family name if none specified
if not args.family:
args.family = 'ipxe'
# Use default name if none specified
if not args.name:
args.name = '%s-%s' % (args.family, date.today().strftime('%Y%m%d'))
# Create temporary upload bucket
bucket = create_temp_bucket(args.location)
# Use one thread per image to maximise parallelism
with ThreadPoolExecutor(max_workers=len(args.image)) as executor:
futures = {executor.submit(create_image,
project=args.project,
basename=args.name,
basefamily=args.family,
overwrite=args.overwrite,
public=args.public,
bucket=bucket,
image=image): image
for image in args.image}
results = {futures[future]: future.result()
for future in as_completed(futures)}
# Delete temporary upload bucket
delete_temp_bucket(bucket)
# Show created images
for image in args.image:
result = results[image]
print("%s (%s) %s" % (result.name, result.family, result.status))