kolla-kubernetes/kolla_kubernetes/commands/cmd_resource.py

435 lines
17 KiB
Python

# 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.
from __future__ import print_function
import copy
import json
import os
import subprocess
import sys
import tempfile
import yaml
from kolla_kubernetes.commands import base_command
from kolla_kubernetes import pathfinder
from kolla_kubernetes import service_resources
from kolla_kubernetes import utils
KKR = service_resources.KollaKubernetesResources.Get()
class ResourceBase(base_command.KollaKubernetesBaseCommand):
"""Create, delete, or query status for kolla-kubernetes resources"""
def get_parser(self, prog_name, skip_action=False):
parser = super(ResourceBase, self).get_parser(prog_name)
if not skip_action:
parser.add_argument(
"action",
metavar="<action>",
help=("One of [%s]" % ("|".join(service_resources.Service.
VALID_ACTIONS)))
)
parser.add_argument(
"resource_type",
metavar="<resource-type>",
help=("One of [%s]" % ("|".join(service_resources.Service.
VALID_RESOURCE_TYPES)))
)
return parser
def validate_args(self, args, skip_action=False):
if not skip_action and args.action not in service_resources.Service.\
VALID_ACTIONS:
msg = ("action [{}] not in valid actions [{}]".format(
args.action,
"|".join(service_resources.Service.VALID_ACTIONS)))
raise Exception(msg)
if args.resource_type not in service_resources.Service.\
VALID_RESOURCE_TYPES:
msg = ("resource_type [{}] not in valid resource_types [{}]"
.format(args.resource_type,
"|".join(service_resources.Service.
VALID_RESOURCE_TYPES)))
raise Exception(msg)
class ResourceTemplate(ResourceBase):
"""Jinja process kolla-kubernetes resource template files"""
# This command adds the CLI params as part of the Jinja vars for processing
# templates. This is needed because some of the templates will need to know
# the arguments with which this CLI is called. For example, some
# resource-type "disk" templates may reference '{{
# kolla_kubernetes.cli.args.action }}' to produce output such as "gcloud
# disk create" or "gcloud disk delete" based on the CLI params. Most
# templates will not require this, but it is needed for some.
def get_parser(self, prog_name, skip_action=False):
parser = super(ResourceTemplate, self).get_parser(prog_name,
skip_action)
parser.add_argument(
"resource_name",
metavar="<resource-name>",
nargs='+',
help=("The unique resource-name under service->resource_type")
)
parser.add_argument(
'--print-jinja-vars',
action='store_true',
help=("If this boolean is set, the final jinja vars dict used as"
" input for template processing will be printed to stderr. "
" The vars dict is created by merging configuration files "
" from several sources before applying the dict to itself.")
),
parser.add_argument(
"-o",
"--output",
metavar="output",
default="yaml",
help=("Format output into one of [%s]" % (
"|".join(['yaml', 'json'])))
),
parser.add_argument(
"-d",
"--debug-container",
metavar="container",
dest='debug_container',
action='append',
help=("Assist in the debugging of the specified container")
),
parser.add_argument(
'--print-jinja-keys-regex',
metavar='<print-jinja-keys-regex>',
type=str,
default=None,
help=("If this regex string is set, all matching keys encountered"
" during the creation of the jinja vars dict will be printed"
" to stderr at each stage of processing. The vars dict is"
" created by merging configuration files from several"
" sources before applying the dict to itself.")
)
return parser
def take_action(self, args, skip_and_return=False):
# Validate input arguments
self.validate_args(args)
multi = len(args.resource_name) != 1
multidoc = {
'apiVersion': 'v1',
'kind': 'List',
'items': []
}
if args.resource_name[0] == 'all':
services = KKR.getServices()
for service in services.keys():
service_object = services.get(service)
service_object.do_apply(args.action, args.resource_type)
return
for resource_name in args.resource_name:
service_name = KKR.getServiceNameByResourceTypeName(
args.resource_type,
resource_name)
service = KKR.getServiceByName(service_name)
rt = service.getResourceTemplateByTypeAndName(
args.resource_type, resource_name)
tmpargs = copy.deepcopy(vars(args))
tmpargs['resource_name'] = resource_name
variables = KKR.GetJinjaDict(service_name, tmpargs,
args.print_jinja_keys_regex)
# Merge the template vars with the jinja vars before processing
variables['kolla_kubernetes'].update(
{"template": {"vars": rt.getVars()}})
# handle the debug option --print-jinja-vars
if args.print_jinja_vars is True:
print(utils.YamlUtils.yaml_dict_to_string(variables),
file=sys.stderr)
if args.resource_type == 'configmap' and \
rt.getTemplate() == 'auto':
nsname = 'kolla_kubernetes_namespace'
cmd = "kubectl create configmap {} -o yaml --dry-run"
cmd = cmd.format(resource_name)
for f in pathfinder.PathFinder.find_config_files(
resource_name):
cmd += ' --from-file={}={}'.format(
os.path.basename(f), f)
# Execute the command
out, err = utils.ExecUtils.exec_command(cmd)
y = yaml.safe_load(out)
y['metadata']['namespace'] = variables[nsname]
res = y
else:
# process the template
raw_doc = utils.JinjaUtils.render_jinja(
variables,
utils.FileUtils.read_string_from_file(
rt.getTemplatePath()))
res = yaml.safe_load(raw_doc)
if args.debug_container is not None:
y = res
kind = y['kind']
if kind not in ('PetSet', 'Deployment', 'Job', 'DaemonSet',
'ReplicationController', 'Pod'):
raise Exception("Template doesn't have containers.")
pod = y
if kind != 'Pod':
pod = y['spec']['template']
alpha_init_containers = None
annotation = 'pod.alpha.kubernetes.io/init-containers'
if 'metadata' in pod and 'annotations' in pod['metadata'] and \
annotation in pod['metadata']['annotations']:
j = json.loads(pod['metadata']['annotations'][annotation])
alpha_init_containers = {}
for c in j:
alpha_init_containers[c['name']] = c
containers = {}
for c in pod['spec']['containers']:
containers[c['name']] = c
for c in args.debug_container:
found = False
warn_msg = "WARNING: container [{}] already has a" + \
" command override."
warn_msg = warn_msg.format(c)
if alpha_init_containers and c in alpha_init_containers:
if 'command' in alpha_init_containers[c]:
print(warn_msg, file=sys.stderr)
if 'args' in alpha_init_containers[c]:
del alpha_init_containers[c]['args']
alpha_init_containers[c]['command'] = \
['/bin/bash', '-c',
'while true; do sleep 1000; done']
found = True
if c in containers:
if 'command' in containers[c]:
print(warn_msg, file=sys.stderr)
if 'args' in containers[c]:
del containers[c]['args']
containers[c]['command'] = \
['/bin/bash', '-c',
'while true; do sleep 1000; done']
found = True
if not found:
raise Exception("Failed to find container: %s" % c)
if alpha_init_containers:
annotation = 'pod.alpha.kubernetes.io/init-containers'
v = alpha_init_containers.values()
pod['metadata']['annotations'][annotation] = json.dumps(v)
multidoc['items'].append(res)
if skip_and_return:
if multi:
return yaml.safe_dump(multidoc)
else:
return yaml.safe_dump(res)
if args.output == 'json':
if multi:
print(json.dumps(multidoc, indent=4), end="")
else:
print(json.dumps(res, indent=4), end="")
elif multi:
print(yaml.safe_dump(multidoc))
else:
if args.debug_container is not None:
print(yaml.safe_dump(res), end="")
else:
print(raw_doc, end="")
class Template(ResourceTemplate):
"""Jinja process kolla-kubernetes resource template files"""
def get_parser(self, prog_name):
parser = super(Template, self).get_parser(prog_name,
skip_action=True)
return parser
def validate_args(self, args):
super(Template, self).validate_args(args, skip_action=True)
class Resource(ResourceTemplate):
"""Create kolla-kubernetes resources"""
def validate_args(self, args):
super(Resource, self).validate_args(args)
if args.action not in ['create', 'delete', 'status']:
msg = ("action [{}] currently not supported".format(
args.action))
raise Exception(msg)
def _kind_to_cli(self, kind):
kind_map = {
'PetSet': 'petset',
'StatefulSet': 'statefulset',
'Pod': 'pod',
'ReplicationController': 'rc',
'DaemonSet': 'daemonset',
'Job': 'job',
'Deployment': 'deployment',
'ConfigMap': 'configmap',
'Secret': 'secret',
'Service': 'svc',
'PersistentVolume': 'pv',
'PersistentVolumeClaim': 'pvc',
}
if kind not in kind_map:
msg = ("unknown template kind [{}].".format(kind))
raise Exception(msg)
return kind_map[kind]
def _process_template(self, kind, namespace, template, names, action):
nsflag = ""
if kind != 'pv':
nsflag = " --namespace={}".format(namespace)
if action == 'create':
with tempfile.NamedTemporaryFile() as tf:
tf.write(template)
tf.flush()
s = "kubectl {} -f {}{}".format(
action, tf.name, nsflag)
subprocess.call(s, shell=True)
elif action == "delete":
s = "kubectl delete {} {}{}".format(
kind, names, nsflag)
subprocess.call(s, shell=True)
elif action == 'status':
s = "kubectl get {} {}{}".format(
kind, names, nsflag)
subprocess.call(s, shell=True)
def _get_ns(self, y):
template_ns = ''
try:
template_ns = y['metadata']['namespace']
except Exception:
pass
return template_ns
def take_action(self, args):
tmpl = super(Resource, self).take_action(args, skip_and_return=True)
y = yaml.safe_load(tmpl)
kind = y['kind']
if kind == 'List':
first_item = y['items'][0]
ns = self._get_ns(first_item)
type_map = {}
for item in y['items']:
if self._get_ns(item) != ns:
msg = "Bad template in list. Different namespaces."
raise Exception(msg)
kind_cli = self._kind_to_cli(item['kind'])
t = type_map.get(kind_cli)
if t is None:
type_map[kind_cli] = t = []
t.append(item['metadata']['name'])
if args.action in ('status', 'delete'):
for (kind_cli, names) in type_map.items():
self._process_template(kind_cli, ns, tmpl, ' '.join(names),
args.action)
else:
self._process_template(self._kind_to_cli(first_item['kind']),
ns, tmpl, '', args.action)
else:
ns = self._get_ns(y)
self._process_template(self._kind_to_cli(kind),
ns,
tmpl, y['metadata']['name'],
args.action)
class ResourceMap(base_command.KollaKubernetesBaseCommand):
"""List available kolla-kubernetes resources to be created or deleted"""
# If the operator has any question on what Services have what resources,
# and what resources reference which resourcefiles (on disk), then this
# command is helpful. This command prints the available resources in a
# tree of Service->ResourceType->ResourceFiles.
def get_parser(self, prog_name):
parser = super(ResourceMap, self).get_parser(prog_name)
parser.add_argument(
"--resource-type",
metavar="<resource-type>",
help=("Filter by one of [%s]" % (
"|".join(service_resources.Service.VALID_RESOURCE_TYPES)))
)
parser.add_argument(
"--service-name",
metavar="<service-name>",
help=("Filter by one of [%s]" % (
"|".join(KKR.getServices().keys())))
)
parser.add_argument(
"-o",
"--output",
metavar="output",
default="text",
help=("Format output into one of [%s]" % (
"|".join(['txt', 'json', 'yaml'])))
)
return parser
def take_action(self, args):
resources = []
for service_name, s in KKR.getServices().items():
# Skip specific services if the user has defined a filter
if (args.service_name is not None) and (
args.service_name != service_name):
continue
if args.output == 'text':
print('service[{}]'.format(s.getName()))
for t in service_resources.Service.VALID_RESOURCE_TYPES:
# Skip specific resource_types if the user has defined a filter
if args.resource_type is not None and args.resource_type != t:
continue
resourceTemplates = s.getResourceTemplatesByType(t)
if args.output == 'text':
print(' resource_type[{}] num_items[{}]'.format(
t, len(resourceTemplates)))
# Print the resource files
for rt in s.getResourceTemplatesByType(t):
if args.output == 'text':
print(' ' + str(rt))
resources.append({
'resource_type': t,
'service_name': service_name,
'resource_name': rt.getName(),
'template': rt.getTemplate(),
'vars': rt.getVars(),
})
if args.output == 'json':
print(json.dumps(resources))
if args.output == 'yaml':
print(yaml.safe_dump(resources))