c7e72942a9
The extraction of the monolithic hyperkube binary from its container image to be used as kubelet was last relevant in Kubernetes 1.16. Since then, the hyperkube image has been deprecated, the structure of the image has been changed, and it has ultimately been eliminated in Kubernetes 1.19. This change cleans up promenade accordingly. Reverts the following commits: *886007b
New CLI option to extract hyperkube *32a6c15
hyperkube image in promenade init *955deed
New source for hyperkube binary definition Change-Id: Ib62ecdf1af13abe8202a4ba4f86c39b9042ed13f
291 lines
9.5 KiB
Python
291 lines
9.5 KiB
Python
from . import exceptions, logging, validation
|
|
from . import design_ref as dr
|
|
import jinja2
|
|
import jsonpath_ng
|
|
import yaml
|
|
|
|
from deckhand.engine import layering
|
|
from deckhand import errors as dh_errors
|
|
|
|
__all__ = ['Configuration']
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class Configuration:
|
|
def __init__(self,
|
|
*,
|
|
documents,
|
|
debug=False,
|
|
substitute=True,
|
|
allow_missing_substitutions=True,
|
|
leave_kubectl=False,
|
|
validate=True):
|
|
LOG.info("Parsing document schemas.")
|
|
LOG.info("Building config from %d documents." % len(documents))
|
|
if substitute:
|
|
LOG.info("Rendering documents via Deckhand engine.")
|
|
try:
|
|
deckhand_eng = layering.DocumentLayering(
|
|
documents,
|
|
fail_on_missing_sub_src=not allow_missing_substitutions)
|
|
documents = [dict(d) for d in deckhand_eng.render()]
|
|
except dh_errors.DeckhandException as e:
|
|
LOG.exception(
|
|
'An unknown Deckhand exception occurred while trying'
|
|
' to render documents.')
|
|
raise exceptions.DeckhandException(str(e))
|
|
|
|
LOG.info("Deckhand engine returned %d documents." % len(documents))
|
|
self.debug = debug
|
|
self.documents = documents
|
|
self.leave_kubectl = leave_kubectl
|
|
|
|
if validate:
|
|
validation.validate_all(self)
|
|
|
|
@classmethod
|
|
def from_streams(cls, *, streams, **kwargs):
|
|
documents = []
|
|
for stream in streams:
|
|
stream_name = getattr(stream, 'name')
|
|
if stream_name is not None:
|
|
LOG.info('Loading documents from %s', stream_name)
|
|
stream_documents = list(yaml.safe_load_all(stream))
|
|
if stream_name is not None:
|
|
LOG.info('Successfully loaded %d documents from %s',
|
|
len(stream_documents), stream_name)
|
|
documents.extend(stream_documents)
|
|
|
|
return cls(documents=documents, **kwargs)
|
|
|
|
@classmethod
|
|
def from_design_ref(cls, design_ref, ctx=None, **kwargs):
|
|
documents, use_dh_engine = dr.get_documents(design_ref, ctx)
|
|
|
|
return cls(
|
|
documents=documents,
|
|
substitute=use_dh_engine,
|
|
validate=use_dh_engine,
|
|
**kwargs)
|
|
|
|
def __getitem__(self, path):
|
|
return self.get_path(
|
|
path, jinja2.StrictUndefined('No match found for path %s' % path))
|
|
|
|
def get_first(self, *paths, default=None):
|
|
result = self._get_first(*paths)
|
|
if result:
|
|
return result
|
|
else:
|
|
if default is not None:
|
|
return default
|
|
else:
|
|
return jinja2.StrictUndefined(
|
|
'Nothing found matching paths: %s' % ','.join(paths))
|
|
|
|
def get(self, *, kind=None, name=None, schema=None, default=None):
|
|
result = _get(self.documents, kind=kind, schema=schema, name=name)
|
|
|
|
if result:
|
|
return result['data']
|
|
else:
|
|
if default is not None:
|
|
return default
|
|
else:
|
|
return jinja2.StrictUndefined(
|
|
'No document found matching kind=%s schema=%s name=%s' %
|
|
(kind, schema, name))
|
|
|
|
def iterate(self, *, kind=None, schema=None, labels=None, name=None):
|
|
if kind is not None:
|
|
if schema is not None:
|
|
raise AssertionError(
|
|
'Logic error: specified both kind and schema')
|
|
schema = 'promenade/%s/v1' % kind
|
|
|
|
for document in self.documents:
|
|
if _matches_filter(
|
|
document, schema=schema, labels=labels, name=name):
|
|
yield document
|
|
|
|
def find(self, *args, **kwargs):
|
|
for doc in self.iterate(*args, **kwargs):
|
|
return doc
|
|
|
|
def extract_genesis_config(self):
|
|
LOG.debug('Extracting genesis config.')
|
|
documents = []
|
|
for document in self.documents:
|
|
if document['schema'] != 'promenade/KubernetesNode/v1':
|
|
documents.append(document)
|
|
else:
|
|
LOG.debug('Excluding schema=%s metadata.name=%s',
|
|
document['schema'], _mg(document, 'name'))
|
|
return Configuration(
|
|
debug=self.debug,
|
|
documents=documents,
|
|
leave_kubectl=self.leave_kubectl,
|
|
substitute=False,
|
|
validate=False)
|
|
|
|
def extract_node_config(self, name):
|
|
LOG.debug('Extracting node config for %s.', name)
|
|
documents = []
|
|
for document in self.documents:
|
|
schema = document['schema']
|
|
if schema == 'promenade/Genesis/v1':
|
|
LOG.debug('Excluding schema=%s metadata.name=%s', schema,
|
|
_mg(document, 'name'))
|
|
continue
|
|
elif schema == 'promenade/KubernetesNode/v1' and _mg(
|
|
document, 'name') != name:
|
|
LOG.debug('Excluding schema=%s metadata.name=%s', schema,
|
|
_mg(document, 'name'))
|
|
continue
|
|
else:
|
|
documents.append(document)
|
|
return Configuration(
|
|
debug=self.debug,
|
|
documents=documents,
|
|
leave_kubectl=self.leave_kubectl,
|
|
substitute=False,
|
|
validate=False)
|
|
|
|
@property
|
|
def kubelet_name(self):
|
|
for document in self.iterate(kind='Genesis'):
|
|
return 'genesis'
|
|
|
|
for document in self.iterate(kind='KubernetesNode'):
|
|
return document['data']['hostname']
|
|
|
|
return jinja2.StrictUndefined(
|
|
'No Genesis or KubernetesNode found while getting kubelet name')
|
|
|
|
def _get_first(self, *paths):
|
|
for path in paths:
|
|
value = self.get_path(path)
|
|
if value:
|
|
return value
|
|
|
|
@property
|
|
def enable_units(self):
|
|
""" Get systemd unit names where enable is ``true``."""
|
|
return self.get_units_by_action('enable')
|
|
|
|
@property
|
|
def start_units(self):
|
|
""" Get systemd unit names where start is ``true``."""
|
|
return self.get_units_by_action('start')
|
|
|
|
@property
|
|
def stop_units(self):
|
|
""" Get systemd unit names where stop is ``true``."""
|
|
return self.get_units_by_action('stop')
|
|
|
|
@property
|
|
def disable_units(self):
|
|
""" Get systemd unit names where disable is ``true``."""
|
|
return self.get_units_by_action('disable')
|
|
|
|
def get_units_by_action(self, action):
|
|
""" Select systemd unit names by ``action``
|
|
|
|
Get all units that are ``true`` for ``action``.
|
|
"""
|
|
return [
|
|
k for k, v in self.systemd_units.items() if v.get(action, False)
|
|
]
|
|
|
|
@property
|
|
def systemd_units(self):
|
|
""" Return a dictionary of systemd units to be managed during join.
|
|
|
|
The dictionary key is the systemd unit name, each will have a four
|
|
boolean keys: ``enable``, ``disable``, ``start``, ``stop`` on the
|
|
actions to be taken at the end of genesis/node join. The steps
|
|
are ordered: enable, start, stop, disable.
|
|
"""
|
|
all_units = {}
|
|
|
|
for document in self.iterate(kind='HostSystem'):
|
|
all_units.update(document['data'].get('systemd_units', {}))
|
|
|
|
return all_units
|
|
|
|
@property
|
|
def join_ips(self):
|
|
maybe_ips = self.get_path('KubernetesNode:join_ips')
|
|
if maybe_ips is not None:
|
|
return maybe_ips
|
|
else:
|
|
maybe_ip = self._get_first('KubernetesNode:join_ip', 'Genesis:ip')
|
|
if maybe_ip:
|
|
return [maybe_ip]
|
|
else:
|
|
return jinja2.StrictUndefined('Could not find join IPs')
|
|
|
|
def get_path(self, path, default=None):
|
|
kind, jsonpath = path.split(':')
|
|
document = _get(self.documents, kind=kind)
|
|
if document:
|
|
data = _extract(document['data'], jsonpath)
|
|
if data:
|
|
return data
|
|
return default
|
|
|
|
def append(self, item):
|
|
validation.check_schema(item)
|
|
self.documents.append(item)
|
|
|
|
def bootstrap_apiserver_prefix(self):
|
|
return self.get_path('Genesis:apiserver.command_prefix',
|
|
['kube-apiserver'])
|
|
|
|
|
|
def _matches_filter(document, *, schema, labels, name):
|
|
matches = True
|
|
if schema is not None and not document.get('schema',
|
|
'').startswith(schema):
|
|
matches = False
|
|
|
|
if labels is not None:
|
|
document_labels = _mg(document, 'labels', [])
|
|
for key, value in labels.items():
|
|
if key not in document_labels:
|
|
matches = False
|
|
else:
|
|
if document_labels[key] != value:
|
|
matches = False
|
|
|
|
if name is not None:
|
|
if _mg(document, 'name') != name:
|
|
matches = False
|
|
|
|
return matches
|
|
|
|
|
|
def _get(documents, kind=None, schema=None, name=None):
|
|
if kind is not None:
|
|
if schema is not None:
|
|
msg = "Only kind or schema may be specified, not both"
|
|
raise exceptions.ValidationException(msg)
|
|
schema = 'promenade/%s/v1' % kind
|
|
|
|
for document in documents:
|
|
if (schema == document.get('schema')
|
|
and (name is None or name == _mg(document, 'name'))):
|
|
return document
|
|
|
|
|
|
def _extract(document, jsonpath):
|
|
p = jsonpath_ng.parse(jsonpath)
|
|
matches = p.find(document)
|
|
if matches:
|
|
return matches[0].value
|
|
|
|
|
|
def _mg(document, field, default=None):
|
|
return document.get('metadata', {}).get(field, default)
|