blob: f58b8f5dcbb345ef17648f73454f7c3d326c0611 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
import json
import os
import queue
import subprocess
import sys
import tempfile
import threading
import gcloud
import gcloud_utils
DEBUG = False
IMAGE_CREATION_VMS = {
"bk-testing-docker": {
"project": "bazel-public",
"zone": "us-central1-f",
"source_image_project": "ubuntu-os-cloud",
"source_image_family": "ubuntu-2004-lts",
"setup_script": "setup-docker.sh",
"guest_os_features": ["VIRTIO_SCSI_MULTIQUEUE"],
"licenses": [
"https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx"
],
},
"bk-testing-windows": {
"project": "bazel-public",
"zone": "us-central1-f",
"source_image_project": "windows-cloud",
"source_image_family": "windows-2019-core",
"setup_script": "setup-windows.ps1",
"guest_os_features": ["VIRTIO_SCSI_MULTIQUEUE"],
},
"windows-playground": {
"project": "di-cloud-exp",
"zone": "us-central1-f",
"network": "default",
"source_image_project": "windows-cloud",
"source_image_family": "windows-2019",
"setup_script": "setup-windows.ps1",
"guest_os_features": ["VIRTIO_SCSI_MULTIQUEUE"],
},
}
WORK_QUEUE = queue.Queue()
def run(args, **kwargs):
return subprocess.run(args, **kwargs)
def preprocess_setup_script(setup_script, is_windows):
output_file = tempfile.mkstemp()[1]
newline = "\r\n" if is_windows else "\n"
with open(output_file, "w", newline=newline) as f:
with open(setup_script, "r") as setup_script_file:
if is_windows:
f.write("$setup_script = @'\n")
f.write(setup_script_file.read() + "\n")
if is_windows:
f.write("'@\n")
f.write('[System.IO.File]::WriteAllLines("c:\\setup.ps1", $setup_script)\n')
return output_file
def create_instance(instance_name, params):
is_windows = "windows" in instance_name
setup_script = preprocess_setup_script(params["setup_script"], is_windows)
try:
if is_windows:
startup_script = "windows-startup-script-ps1=" + setup_script
else:
startup_script = "startup-script=" + setup_script
if "source_image" in params:
image = {"image": params["source_image"]}
else:
image = {
"image-project": params["source_image_project"],
"image-family": params["source_image_family"],
}
gcloud.create_instance(
instance_name,
project=params["project"],
zone=params["zone"],
machine_type="c2-standard-8",
network=params.get("network", "default"),
metadata_from_file=startup_script,
boot_disk_type="pd-ssd",
boot_disk_size=params.get("boot_disk_size", "500GB"),
**image,
)
finally:
os.remove(setup_script)
# https://stackoverflow.com/a/25802742
def write_to_clipboard(output):
process = subprocess.Popen("pbcopy", env={"LANG": "en_US.UTF-8"}, stdin=subprocess.PIPE)
process.communicate(output.encode("utf-8"))
def print_windows_instructions(project, zone, instance_name):
tail_start = gcloud_utils.tail_serial_console(
instance_name, project=project, zone=zone, until="Finished running startup scripts"
)
pw = json.loads(
gcloud.reset_windows_password(
instance_name, format="json", project=project, zone=zone
).stdout
)
rdp_file = tempfile.mkstemp(suffix=".rdp")[1]
with open(rdp_file, "w") as f:
f.write("full address:s:" + pw["ip_address"] + "\n")
f.write("username:s:" + pw["username"] + "\n")
print("Opening ", rdp_file)
subprocess.run(["open", rdp_file])
write_to_clipboard(pw["password"])
with gcloud.PRINT_LOCK:
print("Use this password to connect to the Windows VM: " + pw["password"])
print("Please run the setup script C:\\setup.ps1 once you're logged in.")
# Wait until the VM reboots once, then open RDP again.
tail_start = gcloud_utils.tail_serial_console(
instance_name,
project=project,
zone=zone,
start=tail_start,
until="GCEGuestAgent: GCE Agent Started",
)
print("Connecting via RDP a second time to finish the setup...")
write_to_clipboard(pw["password"])
run(["open", rdp_file])
return tail_start
def workflow(name, params):
instance_name = "%s-image-%s" % (name, int(datetime.now().timestamp()))
project = params["project"]
zone = params["zone"]
try:
# Create the VM.
create_instance(instance_name, params)
# Wait for the VM to become ready.
gcloud_utils.wait_for_instance(instance_name, project=project, zone=zone, status="RUNNING")
if "windows" in instance_name:
# Wait for VM to be ready, then print setup instructions.
tail_start = print_windows_instructions(project, zone, instance_name)
# Continue printing the serial console until the VM shuts down.
gcloud_utils.tail_serial_console(
instance_name, project=project, zone=zone, start=tail_start
)
else:
# Continuously print the serial console.
gcloud_utils.tail_serial_console(instance_name, project=project, zone=zone)
# Wait for the VM to completely shutdown.
gcloud_utils.wait_for_instance(
instance_name, project=project, zone=zone, status="TERMINATED"
)
# Create a new image from our VM.
gcloud.create_image(
instance_name,
project=project,
family=name,
source_disk=instance_name,
source_disk_zone=zone,
licenses=params.get("licenses", []),
guest_os_features=params.get("guest_os_features", []),
)
finally:
gcloud.delete_instance(instance_name, project=project, zone=zone)
def worker():
while True:
item = WORK_QUEUE.get()
if not item:
break
try:
workflow(**item)
finally:
WORK_QUEUE.task_done()
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
if not argv:
print("Usage: create_images.py {}".format(" ".join(IMAGE_CREATION_VMS.keys())))
return 1
unknown_args = set(argv).difference(IMAGE_CREATION_VMS.keys())
if unknown_args:
print(
"Unknown platforms: {}\nAvailable platforms: {}".format(
", ".join(unknown_args), ", ".join(IMAGE_CREATION_VMS.keys())
)
)
return 1
# Put VM creation instructions into the work queue.
for name in argv:
WORK_QUEUE.put({"name": name, "params": IMAGE_CREATION_VMS[name]})
# Spawn worker threads that will create the VMs.
threads = []
for _ in range(WORK_QUEUE.qsize()):
t = threading.Thread(target=worker)
t.start()
threads.append(t)
# Wait for all VMs to be created.
WORK_QUEUE.join()
# Signal worker threads to exit.
for _ in range(len(threads)):
WORK_QUEUE.put(None)
# Wait for worker threads to exit.
for t in threads:
t.join()
return 0
if __name__ == "__main__":
sys.exit(main())