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.
291 lines
7.9 KiB
Python
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
|