blob: 2407e3110be5fe4f7e5a8a585d76a62e309e7a7c [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.
import collections
import json
import re
import subprocess
import threading
DEBUG = True
PRINT_LOCK = threading.Lock()
def debug(*args, **kwargs):
if DEBUG:
with PRINT_LOCK:
print(*args, **kwargs)
def is_sequence(seq):
return isinstance(seq, collections.Sequence) and not isinstance(seq, str)
def gcloud(*args, **kwargs):
cmd = ["gcloud"]
cmd += args
for flag, value in kwargs.items():
# Optionally strip counter suffixes to make it possible to specify the same flag multiple
# times, even though it's passed here via a dict.
if re.search(r"_\d$", flag):
flag = flag[:-2]
# Python uses underscores as word delimiters in kwargs, but gcloud wants dashes.
flag = flag.replace("_", "-")
if isinstance(value, bool):
cmd += ["--" + ("no-" if not value else "") + flag]
# We convert key=[a, b] into two flags: --key=a --key=b.
elif is_sequence(value):
for item in value:
cmd += ["--" + flag, str(item)]
else:
cmd += ["--" + flag, str(value)]
# Throws `subprocess.CalledProcessError` if the process exits with return code > 0.
if not "get-serial-port-output" in cmd:
debug("Running: " + " ".join(cmd))
return subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, check=True
)
def create_instance(name, **kwargs):
try:
return gcloud("compute", "instances", "create", name, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def delete_instance(name, **kwargs):
try:
return gcloud("compute", "instances", "delete", name, "--quiet", **kwargs)
except subprocess.CalledProcessError as e:
# It's not an error if 'delete' failed, because the object didn't exist in the first place.
# But we do want to error out on other unexpected errors.
if not re.search(r"The resource .* was not found", e.stderr):
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
return e
def describe_instance(name, **kwargs):
try:
return gcloud("compute", "instances", "describe", name, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def create_instance_group(name, **kwargs):
try:
return gcloud("compute", "instance-groups", "managed", "create", name, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def delete_instance_group(name, **kwargs):
try:
return gcloud("compute", "instance-groups", "managed", "delete", name, "--quiet", **kwargs)
except subprocess.CalledProcessError as e:
# It's not an error if 'delete' failed, because the object didn't exist in the first place.
# But we do want to error out on other unexpected errors.
if not re.search(r"The resource .* was not found", e.stderr):
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
return e
def rolling_update_instance_group(name, **kwargs):
try:
return gcloud(
"compute",
"instance-groups",
"managed",
"rolling-action",
"start-update",
name,
**kwargs,
)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def set_autoscaling_instance_groups(name, **kwargs):
try:
return gcloud("compute", "instance-groups", "managed", "set-autoscaling", name, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def create_instance_template(name, **kwargs):
try:
return gcloud("compute", "instance-templates", "create", name, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def delete_instance_template(name, **kwargs):
try:
return gcloud("compute", "instance-templates", "delete", name, "--quiet", **kwargs)
except subprocess.CalledProcessError as e:
# It's not an error if 'delete' failed, because the object didn't exist in the first place.
# But we do want to error out on other unexpected errors.
if not re.search(r"The resource .* was not found", e.stderr):
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
return e
def create_image(name, **kwargs):
try:
return gcloud("compute", "images", "create", name, **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def describe_image_family(name, **kwargs):
try:
return json.loads(
gcloud(
"compute", "images", "describe-from-family", "--format=json", name, **kwargs
).stdout
)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def reset_windows_password(name, **kwargs):
try:
return gcloud("compute", "reset-windows-password", name, "--quiet", **kwargs)
except subprocess.CalledProcessError as e:
raise Exception('"{}" returned unexpected error:\n{}'.format(e.cmd, e.stderr))
def get_serial_port_output(name, **kwargs):
return gcloud("compute", "instances", "get-serial-port-output", name, **kwargs)