Files
tamarrow 63bcddaa20 cluster: add subcommand for easy setup of multiple clusters (#983)
We are introducing a new subcommand for managing your clusters. Configuring your CLI to talk to your cluster is a single command now `dcos cluster setup`. Moreover, the CLI can now be aware of multiple clusters with cluster specific configuration managed by the CLI. 

Subcommands will be installed for the current "attached" cluster only. To install a subcommand for all your configured clusters, use `--global`. 

Note that `DCOS_CONFIG` environment variable will not take effect in
"cluster" mode since we are now managing different clusters in the CLI.
2017-05-03 10:41:45 -07:00

291 lines
7.9 KiB
Python

import os
import docopt
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
import dcoscli
from dcos import cluster, cmds, config, emitting, http, util
from dcos.errors import DCOSAuthenticationException, DCOSException
from dcoscli.auth.main import login
from dcoscli.subcommand import default_command_info, default_doc
from dcoscli.tables import clusters_table
from dcoscli.util import confirm, decorate_docopt_usage
emitter = emitting.FlatEmitter()
logger = util.get_logger(__name__)
def main(argv):
try:
return _main(argv)
except DCOSException as e:
emitter.publish(e)
return 1
@decorate_docopt_usage
def _main(argv):
args = docopt.docopt(
default_doc("cluster"),
argv=argv,
version='dcos-cluster version {}'.format(dcoscli.version))
http.silence_requests_warnings()
return cmds.execute(_cmds(), args)
def _cmds():
"""
:returns: all the supported commands
:rtype: list of dcos.cmds.Command
"""
return [
cmds.Command(
hierarchy=['cluster', 'setup'],
arg_keys=['<dcos_url>',
'--insecure', '--no-check', '--ca-certs',
'--password', '--password-env', '--password-file',
'--provider', '--username', '--private-key'],
function=setup),
cmds.Command(
hierarchy=['cluster', 'list'],
arg_keys=['--json', '--attached'],
function=_list),
cmds.Command(
hierarchy=['cluster', 'remove'],
arg_keys=['<name>'],
function=_remove),
cmds.Command(
hierarchy=['cluster', 'attach'],
arg_keys=['<name>'],
function=_attach),
cmds.Command(
hierarchy=['cluster', 'rename'],
arg_keys=['<name>', '<new_name>'],
function=_rename),
cmds.Command(
hierarchy=['cluster'],
arg_keys=['--info'],
function=_info),
]
def _info(info):
"""
:param info: Whether to output a description of this subcommand
:type info: boolean
:returns: process status
:rtype: int
"""
emitter.publish(default_command_info("cluster"))
return 0
def _list(json_, attached):
"""
List configured clusters.
:param json_: output json if True
:type json_: bool
:param attached: return only attached cluster
:type attached: True
:rtype: None
"""
clusters = [c.dict() for c in cluster.get_clusters()
if not attached or c.is_attached()]
if json_:
emitter.publish(clusters)
elif len(clusters) == 0:
if attached:
msg = ("No cluster is attached. "
"Please run `dcos cluster attach <cluster-name>")
else:
msg = ("No clusters are currently configured. "
"To configure one, run `dcos cluster setup <dcos_url>`")
raise DCOSException(msg)
else:
emitter.publish(clusters_table(clusters))
return
def _remove(name):
"""
:param name: name of cluster
:type name: str
:rtype: None
"""
return cluster.remove(name)
def _attach(name):
"""
:param name: name of cluster
:type name: str
:rtype: None
"""
c = cluster.get_cluster(name)
if c is not None:
return cluster.set_attached(c.get_cluster_path())
else:
raise DCOSException("Cluster [{}] does not exist".format(name))
def _rename(name, new_name):
"""
:param name: name of cluster
:type name: str
:param new_name: new_name of cluster
:type new_name: str
:rtype: None
"""
c = cluster.get_cluster(name)
other = cluster.get_cluster(new_name)
if c is None:
raise DCOSException("Cluster [{}] does not exist".format(name))
elif other and other != c:
msg = "A cluster with name [{}] already exists"
raise DCOSException(msg.format(new_name))
else:
config.set_val("cluster.name", new_name, c.get_config_path())
def setup(dcos_url,
insecure=False, no_check=False, ca_certs=None,
password_str=None, password_env=None, password_file=None,
provider=None, username=None, key_path=None):
"""
Setup the CLI to talk to your DC/OS cluster.
:param dcos_url: master ip of cluster
:type dcos_url: str
:param insecure: whether or not to verify ssl certs
:type insecure: bool
:param no_check: whether or not to verify downloaded ca cert
:type no_check: bool
:param ca_certs: path to root CA to verify requests
:type ca_certs: str
:param password_str: password
:type password_str: str
:param password_env: name of environment variable with password
:type password_env: str
:param password_file: path to file with password
:type password_file: bool
:param provider: name of provider to authentication with
:type provider: str
:param username: username
:type username: str
:param key_path: path to file with private key
:type param: str
:returns: process status
:rtype: int
"""
with cluster.setup_directory() as temp_path:
# set cluster as attached
cluster.set_attached(temp_path)
# authenticate
config.set_val("core.dcos_url", dcos_url)
# get validated dcos_url
dcos_url = config.get_config_val("core.dcos_url")
# configure ssl settings
stored_cert = False
if insecure:
config.set_val("core.ssl_verify", "false")
elif ca_certs:
config.set_val("core.ssl_verify", ca_certs)
else:
cert = cluster.get_cluster_cert(dcos_url)
# if we don't have a cert don't try to verify one
if cert is False:
config.set_val("core.ssl_verify", "false")
else:
stored_cert = _store_cluster_cert(cert, no_check)
try:
login(dcos_url,
password_str, password_env, password_file,
provider, username, key_path)
except DCOSAuthenticationException:
msg = ("Authentication failed. "
"Please run `dcos cluster setup <dcos_url>`")
raise DCOSException(msg)
# configure cluster directory
cluster.setup_cluster_config(dcos_url, temp_path, stored_cert)
return 0
def _user_cert_validation(cert_str):
"""Prompt user for validation of certification from cluster
:param cert_str: cluster certificate bundle
:type cert_str: str
:returns whether or not user validated cert
:rtype: bool
"""
cert = x509.load_pem_x509_certificate(
cert_str.encode('utf-8'), default_backend())
fingerprint = cert.fingerprint(hashes.SHA256())
pp_fingerprint = ":".join("{:02x}".format(c) for c in fingerprint).upper()
msg = "SHA256 fingerprint of cluster certificate bundle:\n{}".format(
pp_fingerprint)
return confirm(msg, False)
def _store_cluster_cert(cert, no_check):
"""Store cluster certificate bundle downloaded from cluster and store
settings in core.ssl_verify
:param cert: ca cert from cluster
:type cert: str
:param no_check: whether to verify downloaded cert
:type no_check: bool
:returns: whether or not we are storing the downloaded cert bundle
:rtype: bool
"""
if not no_check:
if not _user_cert_validation(cert):
# we don't have a cert, but we still want to validate SSL
config.set_val("core.ssl_verify", "true")
return False
with util.temptext() as temp_file:
_, temp_path = temp_file
with open(temp_path, 'w') as f:
f.write(cert)
cert_path = os.path.join(
config.get_attached_cluster_path(), "dcos_ca.crt")
util.sh_copy(temp_path, cert_path)
config.set_val("core.ssl_verify", cert_path)
return True