304 lines
8.7 KiB
Python
304 lines
8.7 KiB
Python
import contextlib
|
|
import os
|
|
import shutil
|
|
import ssl
|
|
import urllib
|
|
|
|
from urllib.request import urlopen
|
|
|
|
from dcos import config, constants, http, util
|
|
from dcos.errors import DCOSException
|
|
|
|
|
|
logger = util.get_logger(__name__)
|
|
|
|
|
|
def move_to_cluster_config():
|
|
"""Create a cluster specific config file + directory
|
|
from a global config file. This will move users from global config
|
|
structure (~/.dcos/dcos.toml) to the cluster specific one
|
|
(~/.dcos/clusters/CLUSTER_ID/dcos.toml) and set that cluster as
|
|
the "attached" cluster.
|
|
|
|
:rtype: None
|
|
"""
|
|
|
|
global_config = config.get_global_config()
|
|
dcos_url = config.get_config_val("core.dcos_url", global_config)
|
|
|
|
# if no cluster is set, do not move the cluster yet
|
|
if dcos_url is None:
|
|
return
|
|
|
|
try:
|
|
# find cluster id
|
|
cluster_url = dcos_url.rstrip('/') + '/metadata'
|
|
res = http.get(cluster_url, timeout=1)
|
|
cluster_id = res.json().get("CLUSTER_ID")
|
|
|
|
# don't move cluster if dcos_url is not valid
|
|
except DCOSException as e:
|
|
logger.error(
|
|
"Error trying to find cluster id: {}".format(e))
|
|
return
|
|
|
|
# create cluster id dir
|
|
cluster_path = os.path.join(config.get_config_dir_path(),
|
|
constants.DCOS_CLUSTERS_SUBDIR,
|
|
cluster_id)
|
|
|
|
util.ensure_dir_exists(cluster_path)
|
|
|
|
# move config file to new location
|
|
global_config_path = config.get_global_config_path()
|
|
util.sh_copy(global_config_path, cluster_path)
|
|
|
|
# set cluster as attached
|
|
util.ensure_file_exists(os.path.join(
|
|
cluster_path, constants.DCOS_CLUSTER_ATTACHED_FILE))
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def setup_directory():
|
|
"""
|
|
A context manager for the temporary setup directory created as a
|
|
placeholder before we find the cluster's CLUSTER_ID.
|
|
|
|
:returns: path of setup directory
|
|
:rtype: str
|
|
"""
|
|
|
|
try:
|
|
temp_path = os.path.join(config.get_config_dir_path(),
|
|
constants.DCOS_CLUSTERS_SUBDIR,
|
|
"setup")
|
|
util.ensure_dir_exists(temp_path)
|
|
|
|
yield temp_path
|
|
finally:
|
|
shutil.rmtree(temp_path, ignore_errors=True)
|
|
|
|
|
|
def setup_cluster_config(dcos_url, temp_path, stored_cert):
|
|
"""
|
|
Create a cluster directory for cluster specified in "temp_path"
|
|
directory.
|
|
|
|
:param dcos_url: url to DC/OS cluster
|
|
:type dcos_url: str
|
|
:param temp_path: path to temporary config dir
|
|
:type temp_path: str
|
|
:param stored_cert: whether we stored cert bundle in 'setup' dir
|
|
:type stored_cert: bool
|
|
:returns: path to cluster specific directory
|
|
:rtype: str
|
|
"""
|
|
|
|
try:
|
|
# find cluster id
|
|
cluster_url = dcos_url.rstrip('/') + '/metadata'
|
|
res = http.get(cluster_url, timeout=1)
|
|
cluster_id = res.json().get("CLUSTER_ID")
|
|
|
|
except DCOSException as e:
|
|
msg = ("Error trying to find cluster id: {}\n "
|
|
"Please make sure the provided DC/OS URL is valid: {}".format(
|
|
e, dcos_url))
|
|
raise DCOSException(msg)
|
|
|
|
# create cluster id dir
|
|
cluster_path = os.path.join(config.get_config_dir_path(),
|
|
constants.DCOS_CLUSTERS_SUBDIR,
|
|
cluster_id)
|
|
if os.path.exists(cluster_path):
|
|
raise DCOSException("Cluster [{}] is already setup".format(dcos_url))
|
|
|
|
util.ensure_dir_exists(cluster_path)
|
|
|
|
# move contents of setup dir to new location
|
|
for (path, dirnames, filenames) in os.walk(temp_path):
|
|
for f in filenames:
|
|
util.sh_copy(os.path.join(path, f), cluster_path)
|
|
|
|
cluster = Cluster(cluster_id)
|
|
config_path = cluster.get_config_path()
|
|
if stored_cert:
|
|
cert_path = os.path.join(cluster_path, "dcos_ca.crt")
|
|
config.set_val("core.ssl_verify", cert_path, config_path=config_path)
|
|
|
|
cluster_name = cluster_id
|
|
try:
|
|
url = dcos_url.rstrip('/') + '/mesos/state-summary'
|
|
name_query = http.get(url, timeout=1, toml_config=cluster.get_config())
|
|
cluster_name = name_query.json().get("cluster")
|
|
|
|
except DCOSException:
|
|
pass
|
|
|
|
config.set_val("cluster.name", cluster_name, config_path=config_path)
|
|
|
|
return cluster_path
|
|
|
|
|
|
def set_attached(cluster_path):
|
|
"""
|
|
Set the cluster specified in `cluster_path` as the attached cluster
|
|
|
|
:param cluster_path: path to cluster directory
|
|
:type cluster_path: str
|
|
:rtype: None
|
|
"""
|
|
|
|
# get currently attached cluster
|
|
attached_cluster_path = config.get_attached_cluster_path()
|
|
|
|
if attached_cluster_path and attached_cluster_path != cluster_path:
|
|
# set cluster as attached
|
|
attached_file = os.path.join(
|
|
attached_cluster_path, constants.DCOS_CLUSTER_ATTACHED_FILE)
|
|
try:
|
|
util.sh_move(attached_file, cluster_path)
|
|
except DCOSException as e:
|
|
msg = "Failed to attach cluster: {}".format(e)
|
|
raise DCOSException(msg)
|
|
|
|
else:
|
|
util.ensure_file_exists(os.path.join(
|
|
cluster_path, constants.DCOS_CLUSTER_ATTACHED_FILE))
|
|
|
|
|
|
def get_cluster_cert(dcos_url):
|
|
"""Get CA bundle from specified cluster.
|
|
|
|
This is an insecure request.
|
|
|
|
:param dcos_url: url to DC/OS cluster
|
|
:type dcos_url: str
|
|
:returns: cert
|
|
:rtype: str
|
|
"""
|
|
|
|
cert_bundle_url = dcos_url.rstrip() + "/ca/dcos-ca.crt"
|
|
|
|
unverified = ssl.create_default_context()
|
|
unverified.check_hostname = False
|
|
unverified.verify_mode = ssl.CERT_NONE
|
|
|
|
error_msg = ("Error downloading CA certificate from cluster. "
|
|
"Please check the provided DC/OS URL.")
|
|
try:
|
|
with urlopen(cert_bundle_url, context=unverified) as f:
|
|
return f.read().decode('utf-8')
|
|
except urllib.error.HTTPError as e:
|
|
# Open source DC/OS does not currently expose its root CA certificate
|
|
if e.code == 404:
|
|
return False
|
|
else:
|
|
logger.debug(e)
|
|
raise DCOSException(error_msg)
|
|
except Exception as e:
|
|
logger.debug(e)
|
|
raise DCOSException(error_msg)
|
|
|
|
|
|
def get_clusters():
|
|
"""
|
|
:returns: list of configured Clusters
|
|
:rtype: [Clusters]
|
|
"""
|
|
|
|
clusters_path = config.get_clusters_path()
|
|
util.ensure_dir_exists(clusters_path)
|
|
clusters = os.listdir(clusters_path)
|
|
return [Cluster(cluster_id) for cluster_id in clusters]
|
|
|
|
|
|
def get_cluster(name):
|
|
"""
|
|
:param name: name of cluster
|
|
:type name: str
|
|
:returns: Cluster identified by name
|
|
:rtype: Cluster
|
|
"""
|
|
|
|
return next((c for c in get_clusters()
|
|
if c.get_cluster_id() == name or c.get_name() == name), None)
|
|
|
|
|
|
def remove(name):
|
|
"""
|
|
Remove cluster `name` from the CLI.
|
|
|
|
:param name: name of cluster
|
|
:type name: str
|
|
:rtype: None
|
|
"""
|
|
|
|
def onerror(func, path, excinfo):
|
|
raise DCOSException("Error trying to remove cluster")
|
|
|
|
cluster = get_cluster(name)
|
|
if cluster:
|
|
shutil.rmtree(cluster.get_cluster_path(), onerror)
|
|
return
|
|
else:
|
|
raise DCOSException("Cluster [{}] does not exist".format(name))
|
|
|
|
|
|
class Cluster():
|
|
"""Interface for a configured cluster"""
|
|
|
|
def __init__(self, cluster_id):
|
|
self.cluster_id = cluster_id
|
|
self.cluster_path = os.path.join(
|
|
config.get_clusters_path(), cluster_id)
|
|
|
|
def get_cluster_path(self):
|
|
return self.cluster_path
|
|
|
|
def get_cluster_id(self):
|
|
return self.cluster_id
|
|
|
|
def get_config_path(self):
|
|
return os.path.join(self.cluster_path, "dcos.toml")
|
|
|
|
def get_config(self, mutable=False):
|
|
return config.load_from_path(self.get_config_path(), mutable)
|
|
|
|
def get_name(self):
|
|
return config.get_config_val(
|
|
"cluster.name", self.get_config()) or self.cluster_id
|
|
|
|
def get_url(self):
|
|
return config.get_config_val("core.dcos_url", self.get_config())
|
|
|
|
def get_dcos_version(self):
|
|
dcos_url = self.get_url()
|
|
if dcos_url:
|
|
url = os.path.join(
|
|
self.get_url(), "dcos-metadata/dcos-version.json")
|
|
try:
|
|
resp = http.get(url, timeout=1, toml_config=self.get_config())
|
|
return resp.json().get("version", "N/A")
|
|
except DCOSException:
|
|
pass
|
|
|
|
return "N/A"
|
|
|
|
def is_attached(self):
|
|
return os.path.exists(os.path.join(
|
|
self.cluster_path, constants.DCOS_CLUSTER_ATTACHED_FILE))
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, Cluster) and \
|
|
other.get_cluster_id() == self.get_cluster_id()
|
|
|
|
def dict(self):
|
|
return {
|
|
"cluster_id": self.get_cluster_id(),
|
|
"name": self.get_name(),
|
|
"url": self.get_url(),
|
|
"version": self.get_dcos_version(),
|
|
"attached": self.is_attached()
|
|
}
|