class DomainResourceExtractor: """ Create a domain resource file for use with Kubernetes deployment. """ _class_name = "DomainResourceExtractor" # the name field keys corresponding to named model elements NAME_KEY_MAP = {CHANNELS: 'channelName', CLUSTERS: CLUSTER_NAME} def __init__(self, model, model_context, aliases, logger): self._model = model self._model_context = model_context self._aliases = AliasHelper(aliases, logger, ExceptionType.DEPLOY) self._logger = logger return def extract(self): """ Deploy resource model elements at the domain level, including multi-tenant elements. """ _method_name = 'extract' resource_file = self._model_context.get_domain_resource_file() self._logger.info("WLSDPLY-10000", resource_file, method_name=_method_name, class_name=self._class_name) # create the target file directory, if needed resource_dir = File(resource_file).getParentFile() if (not resource_dir.isDirectory()) and (not resource_dir.mkdirs()): mkdir_ex = exception_helper.create_deploy_exception( 'WLSDPLY-10001', resource_dir) raise mkdir_ex # build the resource file structure from the kubernetes section of the model resource_dict = self._create_domain_resource_dictionary() # revise the resource file structure with values from command line, and elsewhere in model self._update_resource_dictionary(resource_dict) # write the resource file structure to the output file writer = PythonToFile(resource_dict) writer.write_to_file(resource_file) return def _create_domain_resource_dictionary(self): """ Build the resource file structure from the kubernetes section of the model. :return: the resource file structure """ kubernetes_map = self._model.get_model_kubernetes() resource_dict = PyOrderedDict() attribute_location = self._aliases.get_model_section_attribute_location( KUBERNETES) top_attributes_map = self._aliases.get_model_attribute_names_and_types( attribute_location) top_folders = self._aliases.get_model_section_top_level_folder_names( KUBERNETES) self._process_fields(kubernetes_map, top_folders, top_attributes_map, LocationContext(), resource_dict) return resource_dict def _process_fields(self, model_dict, folder_names, attributes_map, location, target_dict): """ Transfer folders and attributes from the model dictionary to the target domain resource dictionary. For the top level, the folders and attributes are not derived directly from the location. :param model_dict: the source model dictionary :param folder_names: the names of the folders at this location :param attributes_map: the map of attribute names to types for this location :param location: the location used for alias processing :param target_dict: the target dictionary for the domain resource file. """ for key, model_value in model_dict.items(): if key in attributes_map.keys(): type_name = attributes_map[key] target_dict[key] = _get_target_value(model_value, type_name) elif key in folder_names: child_location = LocationContext(location).append_location(key) if self._aliases.supports_multiple_mbean_instances( child_location): target_dict[key] = self._build_dictionary_list( key, model_value, child_location) else: if key not in target_dict: target_dict[key] = PyOrderedDict() target_child_dict = target_dict[key] self._process_location_fields(model_value, child_location, target_child_dict) return def _process_location_fields(self, model_dict, location, target_dict): """ Transfer folders and attributes from the model dictionary to the target domain resource dictionary. Below the top level, the folders and attributes can be derived from the location. :param model_dict: the source model dictionary :param location: the location used for alias processing :param target_dict: the target dictionary for the domain resource file. """ attributes_map = self._aliases.get_model_attribute_names_and_types( location) folder_names = self._aliases.get_model_subfolder_names(location) self._process_fields(model_dict, folder_names, attributes_map, location, target_dict) return def _build_dictionary_list(self, model_key, name_dictionary, location): """ Build a dictionary list object based on the name dictionary and location. :param name_dictionary: a dictionary containing named levels :param location: the location used for alias resolution :return: """ child_list = DictionaryList() for name in name_dictionary: model_named_dict = name_dictionary[name] name_key = self._get_name_key(model_key) target_list_dict = PyOrderedDict() target_list_dict[name_key] = name self._process_location_fields(model_named_dict, location, target_list_dict) child_list.append(target_list_dict) return child_list def _get_name_key(self, key): """ Return the key to be used for the name in a dictionary list element. :param key: the folder key in the model :return: the name key """ key = dictionary_utils.get_element(self.NAME_KEY_MAP, key) if key is not None: return key return 'name' def _update_resource_dictionary(self, resource_dict): """ Revise the resource file structure with values from defaults, command line, and elsewhere in model :param resource_dict: the resource file dictionary """ _method_name = '_update_resource_dictionary' # add API version if not present if API_VERSION not in resource_dict: resource_dict[API_VERSION] = DEFAULT_API_VERSION # add kind if not present if KIND not in resource_dict: resource_dict[KIND] = DEFAULT_KIND # add a metadata section if not present, since we'll at least add name if METADATA not in resource_dict: resource_dict[METADATA] = PyOrderedDict() metadata_section = resource_dict[METADATA] # if metadata name not present, use the domain name from the model, or default if K_NAME not in metadata_section: domain_name = dictionary_utils.get_element( self._model.get_model_topology(), NAME) if domain_name is None: domain_name = DEFAULT_WLS_DOMAIN_NAME metadata_section[K_NAME] = domain_name # add a spec section if not present, since we'll at least add domain home if SPEC not in resource_dict: resource_dict[SPEC] = PyOrderedDict() spec_section = resource_dict[SPEC] # only set domain home if it is not present in spec section if DOMAIN_HOME not in spec_section: spec_section[DOMAIN_HOME] = self._model_context.get_domain_home() # only set image if it is not present in spec section if IMAGE not in spec_section: spec_section[IMAGE] = DEFAULT_IMAGE # imagePullSecrets is required unless imagePullPolicy is Never pull_secrets_required = True if IMAGE_PULL_POLICY in spec_section: policy = str(spec_section[IMAGE_PULL_POLICY]) pull_secrets_required = (policy != NEVER) # if imagePullSecrets required and not present, add a list with one FIX ME value if pull_secrets_required and (IMAGE_PULL_SECRETS not in spec_section): secrets_list = DictionaryList() secrets_list.append({'name': DEFAULT_IMAGE_PULL_SECRETS}) spec_section[IMAGE_PULL_SECRETS] = secrets_list # if webLogicCredentialsSecret not present, add it using the FIX ME value if WEBLOGIC_CREDENTIALS_SECRET not in spec_section: spec_section[ WEBLOGIC_CREDENTIALS_SECRET] = DEFAULT_WEBLOGIC_CREDENTIALS_SECRET # only update clusters if section is not present in spec section if CLUSTERS not in spec_section: topology = self._model.get_model_topology() model_clusters = dictionary_utils.get_dictionary_element( topology, CLUSTER) if len(model_clusters) > 0: cluster_list = DictionaryList() spec_section[CLUSTERS] = cluster_list for cluster_name, cluster_values in model_clusters.items(): server_count = k8s_helper.get_server_count( cluster_name, cluster_values, self._model.get_model()) cluster_dict = PyOrderedDict() cluster_dict[CLUSTER_NAME] = cluster_name cluster_dict[REPLICAS] = server_count self._logger.info("WLSDPLY-10002", cluster_name, server_count, method_name=_method_name, class_name=self._class_name) cluster_list.append(cluster_dict) return
class ModelHelpPrinter(object): """ Class for printing the recognized model metadata to STDOUT. """ def __init__(self, aliases, logger): """ :param aliases: A reference to an Aliases class instance :param logger: A reference to the platform logger to write to, if a log entry needs to be made """ self._logger = logger self._alias_helper = AliasHelper(aliases, self._logger, ExceptionType.CLA) def print_model_help(self, model_path, control_option, as_sample=False): """ Prints out the help information for a given '''model_path'''. '''model_path''' needs to be specified using the following pattern: <model_section>[:/<section_folder>[/<section_subfolder>|...]] Examples: 'domainInfo', 'domainInfo:' or 'domainInfo:/' (all 3 are equivalent) 'topology:/Server' 'resources:/JDBCSystemResource/JdbcResource/JDBCDriverParams' 'appDeployments:/Application' :param model_path: a formatted string containing the model path :param control_option: a command-line switch that controls what is output :param as_sample: specifies that a model sample should be output :raises CLAException: if a problem is encountered """ # print filter information, if not NORMAL if control_option == ControlOptions.RECURSIVE: print print _format_message('WLSDPLY-10102') elif control_option == ControlOptions.FOLDERS_ONLY: print print _format_message('WLSDPLY-10103') elif control_option == ControlOptions.ATTRIBUTES_ONLY: print print _format_message('WLSDPLY-10104') model_path_tokens = self._parse_model_path(model_path) section_name = model_path_tokens[0] valid_section_folder_keys = self._alias_helper.get_model_section_top_level_folder_names( section_name) if as_sample: sample_printer = ModelSamplePrinter(self._alias_helper, self._logger) sample_printer.print_model_sample(model_path_tokens, control_option) else: if model_path_tokens[0] == 'top': self._print_model_top_level_help() elif len(model_path_tokens) == 1: self._print_model_section_help(section_name, valid_section_folder_keys, control_option) else: self._print_model_folder_help(model_path_tokens, valid_section_folder_keys, control_option) def _parse_model_path(self, model_path): """ Parse the specified model_path into a Python list of elements. The first element will be the section name. If not specified, the section name will be derived from the first path element. :param model_path: a string contain the model path to parse :return: a python list containing the section name, then each folder element :raises: CLAException if the path cannot be parsed, or the section name is invalid """ _method_name = '_parse_model_path' self._logger.entering(model_path, class_name=_class_name, method_name=_method_name) match = MODEL_PATH_PATTERN.match(model_path) if match is None: ex = exception_helper.create_cla_exception('WLSDPLY-10108', model_path) self._logger.throwing(ex, class_name=_class_name, method_name=_method_name) raise ex self._logger.finest('match.group(1)={0}, match.group(2)={1}', str(match.group(1)), str(match.group(2)), class_name=_class_name, method_name=_method_name) section = match.group(1) path = match.group(2) folders = [] if path is not None: for folder in path.split('/'): # trailing or duplicate slashes may cause empty folder name if len(folder) > 0: folders.append(folder) if section is None: section = self._get_section_for_folder_list(folders) section = section.replace(':', '') top_level_keys = model.get_model_top_level_keys() # 'top' is a special case for listing the sections all_section_keys = ['top'] all_section_keys.extend(top_level_keys) if section not in all_section_keys: ex = exception_helper.create_cla_exception( 'WLSDPLY-10109', section, str(', '.join(top_level_keys))) self._logger.throwing(ex, class_name=_class_name, method_name=_method_name) raise ex model_path_tokens = [section] model_path_tokens.extend(folders) return model_path_tokens def _get_section_for_folder_list(self, folder_list): """ Derive the section name from the first folder in the specified list. :param folder_list: the list of folders in the model path :return: the section name :raises: CLAException if the section name cannot be determined """ _method_name = '_get_section_for_folder_list' top_folder = folder_list[0] for section in KNOWN_TOPLEVEL_MODEL_SECTIONS: folder_keys = self._alias_helper.get_model_section_top_level_folder_names( section) if top_folder in folder_keys: return section ex = exception_helper.create_cla_exception('WLSDPLY-10101', top_folder) self._logger.throwing(ex, class_name=_class_name, method_name=_method_name) raise ex def _print_model_top_level_help(self): """ Prints out all the valid section names for a model. The -recursive flag is disregarded for this case. """ _method_name = '_print_model_top_level_help' self._logger.finest('sections={0}', KNOWN_TOPLEVEL_MODEL_SECTIONS, class_name=_class_name, method_name=_method_name) title = _format_message('WLSDPLY-10113') # Print 'All Sections:' header print _print_indent(title, 0) print for section in KNOWN_TOPLEVEL_MODEL_SECTIONS: _print_indent(section, 1) def _print_model_section_help(self, section_name, valid_section_folder_keys, control_option): """ Prints the help for a section of a model, when just the section_name[:] is provided :param section_name: the name of the model section :param valid_section_folder_keys: list of the valid top folders in the specified section :param control_option: A command-line switch that controls what is output to STDOUT """ _method_name = '_print_model_section_help' self._logger.finest('1 section_name={0}', section_name, class_name=_class_name, method_name=_method_name) location_path = '%s:' % section_name self._logger.finest('1 location_path={0}', location_path, class_name=_class_name, method_name=_method_name) model_section = _format_message('WLSDPLY-10106', location_path) # Print 'Section: <model_section>' label and field print _print_indent(model_section, 0) if model_help_utils.show_attributes(control_option): attributes_location = self._alias_helper.get_model_section_attribute_location( section_name) if attributes_location is not None: self._print_attributes_help(attributes_location, 1) if model_help_utils.show_folders(control_option): print _print_indent(_format_message('WLSDPLY-10107'), 1) valid_section_folder_keys.sort() for section_folder_key in valid_section_folder_keys: _print_indent(section_folder_key, 2) if control_option == ControlOptions.RECURSIVE: model_location = LocationContext().append_location( section_folder_key) self._print_subfolders_help(model_location, control_option, 2) def _print_model_folder_help(self, model_path_tokens, valid_section_folder_keys, control_option): """ Prints the help for a folder in a model, when more than just the section_name[:] is provided. The section name in the first token was already validated. :param model_path_tokens: a Python list of path elements built from model path :param valid_section_folder_keys: A list of valid folder names for the model section in the path :param control_option: A command-line switch that controls what is output to STDOUT """ _method_name = '_print_model_folder_help' self._logger.finest( '1 model_path_tokens={0}, control_option={1}, valid_section_folder_keys={0}', str(model_path_tokens), ControlOptions.from_value(control_option), str(valid_section_folder_keys), class_name=_class_name, method_name=_method_name) section_name = model_path_tokens[0] top_folder = model_path_tokens[1] if top_folder not in valid_section_folder_keys: ex = exception_helper.create_cla_exception( 'WLSDPLY-10110', section_name + ':', top_folder, ', '.join(valid_section_folder_keys)) self._logger.throwing(ex, class_name=_class_name, method_name=_method_name) raise ex # Populate the location context using model_path_tokens[1:] model_location = LocationContext() for folder_key in model_path_tokens[1:]: model_location.append_location(folder_key) name_token = self._alias_helper.get_name_token(model_location) if name_token is not None: model_location.add_name_token(name_token, '%s-0' % folder_key) self._logger.finest('2 model_location={0}', model_location, class_name=_class_name, method_name=_method_name) folder_path = '/'.join(model_path_tokens[1:]) model_path = _format_message('WLSDPLY-10105', '%s:/%s' % (section_name, folder_path)) type_name = self._get_folder_type_name(model_location) if type_name is not None: model_path += " (" + type_name + ")" # Print 'Path: <model_section>' header print _print_indent(model_path, 0) if model_help_utils.show_attributes(control_option): # Print the attributes associated with location context self._print_attributes_help(model_location, 1) if model_help_utils.show_folders(control_option): # Print the folders associated with location context print _print_indent(_format_message('WLSDPLY-10107'), 1) self._print_subfolders_help(model_location, control_option, 1) self._logger.exiting(class_name=_class_name, method_name=_method_name) return def _print_subfolders_help(self, model_location, control_option, indent_level): """ Prints the help for the folders in a model location, without header or leading space. :param model_location: the model location being worked on :param control_option: a command-line switch that controls what is output to STDOUT :param indent_level: the level to indent by, before printing output """ _method_name = '_print_subfolders_help' valid_subfolder_keys = self._alias_helper.get_model_subfolder_names( model_location) self._logger.finest( '3 aliases.get_model_subfolder_names(model_location) returned: {0}', str(valid_subfolder_keys), class_name=_class_name, method_name=_method_name) if not valid_subfolder_keys: return valid_subfolder_keys.sort() for key in valid_subfolder_keys: model_location.append_location(key) name_token = self._alias_helper.get_name_token(model_location) if name_token is not None: model_location.add_name_token(name_token, '%s-0' % key) self._logger.finest('3 model_location={0}', model_location, class_name=_class_name, method_name=_method_name) text = key type_name = self._get_folder_type_name(model_location) if type_name is not None: text += " (" + type_name + ")" _print_indent(text, indent_level + 1) if control_option == ControlOptions.RECURSIVE: # Call this method recursively self._print_subfolders_help(model_location, control_option, indent_level + 1) model_location.pop_location() def _print_attributes_help(self, model_location, indent_level): """ Prints out the help for the attributes in a model location :param model_location: An object containing data about the model location being worked on :param indent_level: The level to indent by, before printing output """ _method_name = '_print_attributes_help' attr_infos = self._alias_helper.get_model_attribute_names_and_types( model_location) self._logger.finer('WLSDPLY-05012', str(model_location), str(attr_infos), class_name=_class_name, method_name=_method_name) # Print 'Valid Attributes:' area label print _print_indent(_format_message('WLSDPLY-10111'), indent_level) if attr_infos: maxlen = 0 for key in attr_infos: if len(key) > maxlen: maxlen = len(key) formatted_string = '%-' + str(maxlen) + 's\t%s' attr_list = attr_infos.keys() attr_list.sort() for attr_name in attr_list: msg = formatted_string % (attr_name, attr_infos[attr_name]) _print_indent(msg, indent_level + 1) def _get_folder_type_name(self, location): """ Return text indicating the type of a folder, such as "multiple". :param location: the location to be checked :return: name of the folder type to be displayed, or None """ if self._alias_helper.is_artificial_type_folder(location): return None if self._alias_helper.supports_multiple_mbean_instances(location): return "multiple" return None