OSC: Add cluster config command
Move certificate generation and config generation in magnum utils so that all clusters_shell, bays_shell and OSC can use. * Remove / from context name (see #1705480) * Use absolute paths for the certificates in kubeconfig It's the same principle like #1614682 Change-Id: I5b8bb11b199b7646a984c7171f3853d3e73923ec Implements: blueprint openstackclient-support Related-Bug: #1705480 Related-Bug: #1614682
This commit is contained in:
parent
a9918d16b8
commit
5f3b69b15c
@ -16,6 +16,14 @@
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
from magnumclient import exceptions as exc
|
||||
from magnumclient.i18n import _
|
||||
@ -142,3 +150,116 @@ def handle_json_from_file(json_arg):
|
||||
raise exc.InvalidAttribute(err)
|
||||
|
||||
return json_arg
|
||||
|
||||
|
||||
def config_cluster(cluster, cluster_template, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given cluster."""
|
||||
if cluster_template.coe == 'kubernetes':
|
||||
return _config_cluster_kubernetes(cluster, cluster_template,
|
||||
cfg_dir, force)
|
||||
elif (cluster_template.coe == 'swarm'
|
||||
or cluster_template.coe == 'swarm-mode'):
|
||||
return _config_cluster_swarm(cluster, cluster_template, cfg_dir, force)
|
||||
|
||||
|
||||
def _config_cluster_kubernetes(cluster, cluster_template,
|
||||
cfg_dir, force=False):
|
||||
"""Return and write configuration for the given kubernetes cluster."""
|
||||
cfg_file = "%s/config" % cfg_dir
|
||||
if cluster_template.tls_disabled:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: %(name)s\n"
|
||||
"current-context: %(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s'\n"
|
||||
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||
else:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" certificate-authority: %(cfg_dir)s/ca.pem\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: %(name)s\n"
|
||||
"current-context: %(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s\n"
|
||||
" user:\n"
|
||||
" client-certificate: %(cfg_dir)s/cert.pem\n"
|
||||
" client-key: %(cfg_dir)s/key.pem\n"
|
||||
% {'name': cluster.name,
|
||||
'api_address': cluster.api_address,
|
||||
'cfg_dir': cfg_dir})
|
||||
|
||||
if os.path.exists(cfg_file) and not force:
|
||||
raise exc.CommandError("File %s exists, aborting." % cfg_file)
|
||||
else:
|
||||
f = open(cfg_file, "w")
|
||||
f.write(cfg)
|
||||
f.close()
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
return "setenv KUBECONFIG %s\n" % cfg_file
|
||||
else:
|
||||
return "export KUBECONFIG=%s\n" % cfg_file
|
||||
|
||||
|
||||
def _config_cluster_swarm(cluster, cluster_template, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given swarm cluster."""
|
||||
tls = "" if cluster_template.tls_disabled else True
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
result = ("setenv DOCKER_HOST %(docker_host)s\n"
|
||||
"setenv DOCKER_CERT_PATH %(cfg_dir)s\n"
|
||||
"setenv DOCKER_TLS_VERIFY %(tls)s\n"
|
||||
% {'docker_host': cluster.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
else:
|
||||
result = ("export DOCKER_HOST=%(docker_host)s\n"
|
||||
"export DOCKER_CERT_PATH=%(cfg_dir)s\n"
|
||||
"export DOCKER_TLS_VERIFY=%(tls)s\n"
|
||||
% {'docker_host': cluster.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_csr_and_key():
|
||||
"""Return a dict with a new csr and key."""
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend())
|
||||
|
||||
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"Magnum User"),
|
||||
])).sign(key, hashes.SHA256(), default_backend())
|
||||
|
||||
result = {
|
||||
'csr': csr.public_bytes(
|
||||
encoding=serialization.Encoding.PEM).decode("utf-8"),
|
||||
'key': key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()).decode("utf-8"),
|
||||
}
|
||||
|
||||
return result
|
||||
|
@ -12,7 +12,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import os
|
||||
|
||||
from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.i18n import _
|
||||
|
||||
from osc_lib.command import command
|
||||
@ -242,3 +245,68 @@ class UpdateCluster(command.Command):
|
||||
patch)
|
||||
print("Request to update cluster %s has been accepted." %
|
||||
parsed_args.cluster)
|
||||
|
||||
|
||||
class ConfigCluster(command.Command):
|
||||
_description = _("Get Configuration for a Cluster")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ConfigCluster, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'cluster',
|
||||
metavar='<cluster>',
|
||||
help=_('The name or UUID of cluster to update'))
|
||||
parser.add_argument(
|
||||
'--dir',
|
||||
metavar='<dir>',
|
||||
default='.',
|
||||
help=_('Directory to save the certificate and config files.'))
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
metavar='<force>',
|
||||
default=False,
|
||||
help=_('Directory to save the certificate and config files.'))
|
||||
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
"""Configure native client to access cluster.
|
||||
|
||||
You can source the output of this command to get the native client of
|
||||
the corresponding COE configured to access the cluster.
|
||||
|
||||
"""
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
mag_client = self.app.client_manager.container_infra
|
||||
|
||||
parsed_args.dir = os.path.abspath(parsed_args.dir)
|
||||
cluster = mag_client.clusters.get(parsed_args.cluster)
|
||||
if cluster.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE',
|
||||
'ROLLBACK_COMPLETE'):
|
||||
raise exceptions.CommandError("cluster in status %s" %
|
||||
cluster.status)
|
||||
cluster_template = mag_client.cluster_templates.get(
|
||||
cluster.cluster_template_id)
|
||||
opts = {
|
||||
'cluster_uuid': cluster.uuid,
|
||||
}
|
||||
|
||||
if not cluster_template.tls_disabled:
|
||||
tls = magnum_utils.generate_csr_and_key()
|
||||
tls['ca'] = mag_client.certificates.get(**opts).pem
|
||||
opts['csr'] = tls['csr']
|
||||
tls['cert'] = mag_client.certificates.create(**opts).pem
|
||||
for k in ('key', 'cert', 'ca'):
|
||||
fname = "%s/%s.pem" % (parsed_args.dir, k)
|
||||
if os.path.exists(fname) and not parsed_args.force:
|
||||
raise Exception("File %s exists, aborting." % fname)
|
||||
else:
|
||||
f = open(fname, "w")
|
||||
f.write(tls[k])
|
||||
f.close()
|
||||
|
||||
print(magnum_utils.config_cluster(cluster,
|
||||
cluster_template,
|
||||
parsed_args.dir,
|
||||
force=parsed_args.force))
|
||||
|
@ -17,12 +17,6 @@ from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.i18n import _
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
import os
|
||||
|
||||
|
||||
@ -242,7 +236,7 @@ def do_bay_config(cs, args):
|
||||
}
|
||||
|
||||
if not baymodel.tls_disabled:
|
||||
tls = _generate_csr_and_key()
|
||||
tls = magnum_utils.generate_csr_and_key()
|
||||
tls['ca'] = cs.certificates.get(**opts).pem
|
||||
opts['csr'] = tls['csr']
|
||||
tls['cert'] = cs.certificates.create(**opts).pem
|
||||
@ -255,112 +249,5 @@ def do_bay_config(cs, args):
|
||||
f.write(tls[k])
|
||||
f.close()
|
||||
|
||||
print(_config_bay(bay, baymodel, cfg_dir=args.dir, force=args.force))
|
||||
|
||||
|
||||
def _config_bay(bay, baymodel, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given bay."""
|
||||
if baymodel.coe == 'kubernetes':
|
||||
return _config_bay_kubernetes(bay, baymodel, cfg_dir, force)
|
||||
elif baymodel.coe == 'swarm':
|
||||
return _config_bay_swarm(bay, baymodel, cfg_dir, force)
|
||||
|
||||
|
||||
def _config_bay_kubernetes(bay, baymodel, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given kubernetes bay."""
|
||||
cfg_file = "%s/config" % cfg_dir
|
||||
if baymodel.tls_disabled:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: default/%(name)s\n"
|
||||
"current-context: default/%(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s'\n"
|
||||
% {'name': bay.name, 'api_address': bay.api_address})
|
||||
else:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" certificate-authority: ca.pem\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: default/%(name)s\n"
|
||||
"current-context: default/%(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s\n"
|
||||
" user:\n"
|
||||
" client-certificate: cert.pem\n"
|
||||
" client-key: key.pem\n"
|
||||
% {'name': bay.name, 'api_address': bay.api_address})
|
||||
|
||||
if os.path.exists(cfg_file) and not force:
|
||||
raise exceptions.CommandError("File %s exists, aborting." % cfg_file)
|
||||
else:
|
||||
f = open(cfg_file, "w")
|
||||
f.write(cfg)
|
||||
f.close()
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
return "setenv KUBECONFIG %s\n" % cfg_file
|
||||
else:
|
||||
return "export KUBECONFIG=%s\n" % cfg_file
|
||||
|
||||
|
||||
def _config_bay_swarm(bay, baymodel, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given swarm bay."""
|
||||
tls = "" if baymodel.tls_disabled else True
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
result = ("setenv DOCKER_HOST %(docker_host)s\n"
|
||||
"setenv DOCKER_CERT_PATH %(cfg_dir)s\n"
|
||||
"setenv DOCKER_TLS_VERIFY %(tls)s\n"
|
||||
% {'docker_host': bay.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
else:
|
||||
result = ("export DOCKER_HOST=%(docker_host)s\n"
|
||||
"export DOCKER_CERT_PATH=%(cfg_dir)s\n"
|
||||
"export DOCKER_TLS_VERIFY=%(tls)s\n"
|
||||
% {'docker_host': bay.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _generate_csr_and_key():
|
||||
"""Return a dict with a new csr and key."""
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend())
|
||||
|
||||
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"Magnum User"),
|
||||
])).sign(key, hashes.SHA256(), default_backend())
|
||||
|
||||
result = {
|
||||
'csr': csr.public_bytes(
|
||||
encoding=serialization.Encoding.PEM).decode("utf-8"),
|
||||
'key': key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()).decode("utf-8"),
|
||||
}
|
||||
|
||||
return result
|
||||
print(magnum_utils.config_cluster(bay, baymodel, cfg_dir=args.dir,
|
||||
force=args.force))
|
||||
|
@ -19,13 +19,6 @@ from magnumclient.common import utils as magnum_utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.i18n import _
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography import x509
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
|
||||
# Maps old parameter names to their new names and whether they are required
|
||||
# e.g. keypair-id to keypair
|
||||
@ -266,7 +259,7 @@ def do_cluster_config(cs, args):
|
||||
}
|
||||
|
||||
if not cluster_template.tls_disabled:
|
||||
tls = _generate_csr_and_key()
|
||||
tls = magnum_utils.generate_csr_and_key()
|
||||
tls['ca'] = cs.certificates.get(**opts).pem
|
||||
opts['csr'] = tls['csr']
|
||||
tls['cert'] = cs.certificates.create(**opts).pem
|
||||
@ -279,115 +272,5 @@ def do_cluster_config(cs, args):
|
||||
f.write(tls[k])
|
||||
f.close()
|
||||
|
||||
print(_config_cluster(cluster, cluster_template,
|
||||
cfg_dir=args.dir, force=args.force))
|
||||
|
||||
|
||||
def _config_cluster(cluster, cluster_template, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given cluster."""
|
||||
if cluster_template.coe == 'kubernetes':
|
||||
return _config_cluster_kubernetes(cluster, cluster_template,
|
||||
cfg_dir, force)
|
||||
elif cluster_template.coe == 'swarm':
|
||||
return _config_cluster_swarm(cluster, cluster_template, cfg_dir, force)
|
||||
|
||||
|
||||
def _config_cluster_kubernetes(cluster, cluster_template,
|
||||
cfg_dir, force=False):
|
||||
"""Return and write configuration for the given kubernetes cluster."""
|
||||
cfg_file = "%s/config" % cfg_dir
|
||||
if cluster_template.tls_disabled:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: %(name)s\n"
|
||||
"current-context: %(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s'\n"
|
||||
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||
else:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
" certificate-authority: ca.pem\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: default/%(name)s\n"
|
||||
"current-context: default/%(name)s\n"
|
||||
"kind: Config\n"
|
||||
"preferences: {}\n"
|
||||
"users:\n"
|
||||
"- name: %(name)s\n"
|
||||
" user:\n"
|
||||
" client-certificate: cert.pem\n"
|
||||
" client-key: key.pem\n"
|
||||
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||
|
||||
if os.path.exists(cfg_file) and not force:
|
||||
raise exceptions.CommandError("File %s exists, aborting." % cfg_file)
|
||||
else:
|
||||
f = open(cfg_file, "w")
|
||||
f.write(cfg)
|
||||
f.close()
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
return "setenv KUBECONFIG %s\n" % cfg_file
|
||||
else:
|
||||
return "export KUBECONFIG=%s\n" % cfg_file
|
||||
|
||||
|
||||
def _config_cluster_swarm(cluster, cluster_template, cfg_dir, force=False):
|
||||
"""Return and write configuration for the given swarm cluster."""
|
||||
tls = "" if cluster_template.tls_disabled else True
|
||||
if 'csh' in os.environ['SHELL']:
|
||||
result = ("setenv DOCKER_HOST %(docker_host)s\n"
|
||||
"setenv DOCKER_CERT_PATH %(cfg_dir)s\n"
|
||||
"setenv DOCKER_TLS_VERIFY %(tls)s\n"
|
||||
% {'docker_host': cluster.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
else:
|
||||
result = ("export DOCKER_HOST=%(docker_host)s\n"
|
||||
"export DOCKER_CERT_PATH=%(cfg_dir)s\n"
|
||||
"export DOCKER_TLS_VERIFY=%(tls)s\n"
|
||||
% {'docker_host': cluster.api_address,
|
||||
'cfg_dir': cfg_dir,
|
||||
'tls': tls}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _generate_csr_and_key():
|
||||
"""Return a dict with a new csr and key."""
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=2048,
|
||||
backend=default_backend())
|
||||
|
||||
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
||||
x509.NameAttribute(NameOID.COMMON_NAME, u"Magnum User"),
|
||||
])).sign(key, hashes.SHA256(), default_backend())
|
||||
|
||||
result = {
|
||||
'csr': csr.public_bytes(
|
||||
encoding=serialization.Encoding.PEM).decode("utf-8"),
|
||||
'key': key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()).decode("utf-8"),
|
||||
}
|
||||
|
||||
return result
|
||||
print(magnum_utils.config_cluster(cluster, cluster_template,
|
||||
cfg_dir=args.dir, force=args.force))
|
||||
|
@ -41,6 +41,7 @@ openstack.container_infra.v1 =
|
||||
coe_cluster_delete = magnumclient.osc.v1.clusters:DeleteCluster
|
||||
coe_cluster_show = magnumclient.osc.v1.clusters:ShowCluster
|
||||
coe_cluster_update = magnumclient.osc.v1.clusters:UpdateCluster
|
||||
coe_cluster_config = magnumclient.osc.v1.clusters:ConfigCluster
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
|
Loading…
x
Reference in New Issue
Block a user