def apply_default_apps(self, default_apps): # These annoying names are to prevent conflicts with fields in the default app definitions PLACEMENT_KEY = 'default_placement' CONDITION_KEY = 'default_condition' for section in default_apps: if section not in self._data: self._data[section] = [] insert_idx = 0 for app in default_apps[section]: placement = app.get(PLACEMENT_KEY, 'before') if PLACEMENT_KEY in app: del app[PLACEMENT_KEY] if CONDITION_KEY in app: condition = app[CONDITION_KEY] del app[CONDITION_KEY] template = Template() tmp_vars = dict(VARS=dict(self._vars)) # Continue to next item if we have a condition and it evaluated to False if not template.evaluate_condition(condition, tmp_vars): continue if placement in ('before', 'pre'): self._data[section].insert(insert_idx, app) insert_idx += 1 elif placement in ('after', 'post'): self._data[section].append(app) else: raise DeployConfigError( 'invalid default app placement: %s' % placement)
def validate_check_type(self, value, expected_type=None): ''' Determine the type of the passed value ''' if isinstance(value, list): return 'list' if isinstance(value, dict): return 'dict' if isinstance(value, bool): return 'bool' if isinstance(value, int): return 'int' if isinstance(value, float): return 'float' if isinstance(value, str): # Values from variables always come in as a string, so we need special # logic to determine their actual type based on the field type try: if expected_type == 'float' and float(value) is not None: return 'float' if expected_type == 'int' and int(value) is not None: return 'int' if expected_type == 'bool' and self.convert_bool( value) is not None: return 'bool' except Exception: pass return 'str' raise DeployConfigError('unsupported type: %s' % type(value))
def app_validate_fields(app, app_index, output_plugins): try: unmatched = {} plugins_used = [] for plugin in output_plugins: if plugin.is_needed(app): plugins_used.append(plugin.NAME) plugin_unmatched = plugin.validate_fields(app) unmatched[plugin.NAME] = plugin_unmatched # Compare unmatched from all plugins and compile final list # We are looking for fields that were unmatched by all active plugins, # with the added twist that we need to match what may be an unmatched # sub-field in one plugin and a top-level field in another. final_unmatched = [] for plugin in unmatched: for entry in unmatched[plugin]: entry_keep = True # Check entry against unmatched entries from other plugins for plugin2 in unmatched: if plugin2 == plugin: continue plugin2_matched = False for entry2 in unmatched[plugin2]: # Check for exact match or a parent/sub-field match if entry2 == entry or entry.startswith('%s.' % entry2): plugin2_matched = True break if not plugin2_matched: entry_keep = False break # Add entry to final list if it existed for all plugins if entry_keep and entry not in final_unmatched: final_unmatched.append(entry) if final_unmatched: raise DeployConfigError('found the following unknown fields: %s' % ', '.join(sorted(final_unmatched))) if not plugins_used: raise DeployConfigError( 'no output plugins were available for provided fields') except DeployConfigError as e: DISPLAY.display('Failed to validate fields in deploy config: %s' % str(e)) sys.exit(1)
def load(self, path): self._path = path try: self._display.v('Loading deploy config file %s' % path) with open(path) as f: self._data = yaml_load(f.read()) if not isinstance(self._data, dict): raise DeployConfigError( 'YAML file should contain a top-level dict', path=path) if 'version' in self._data: self._version = self._data['version'] del self._data['version'] for k, v in self._data.items(): # Wrap the config in a list if it's not already a list # This makes it easier to process if not isinstance(v, list): self._data[k] = [v] except DeployConfigError: raise except Exception as e: raise DeployConfigError('unexpected exception: %s' % str(e))
def validate_fields(self, app): ''' Validate the provided app config against plugin field definitions ''' # Check that all required top-level fields are provided req_fields = self.get_required_fields() for field in req_fields: if field not in app: raise DeployConfigError("required field '%s' not defined" % field) # Check field/subfield types, required, and if field is locked unmatched = [] for field, value in app.items(): if self.has_field(field): if self.is_field_locked(field): raise DeployConfigError( "the field '%s' has been locked by the plugin config and cannot be overridden" % field) field_unmatched = self._fields[self._section][field].validate( value) unmatched.extend(field_unmatched) else: unmatched.append(field) return unmatched
def validate_sections(self, valid_sections): for section in self._data: if section not in valid_sections: raise DeployConfigError( "section name '%s' is not valid for available plugins" % section)
def validate(self, value, use_subtype=False): ''' Validate passed value against field config ''' unmatched = [] if value is None: return unmatched field_type = self.type if use_subtype: # Use the field subtype field_type = self.subtype # Nothing to validate if no field type is specified if field_type is None: return unmatched value_type = self.validate_check_type(value, field_type) if value_type != field_type: # TODO: replace this with the ability to specify multiple types for a field # Hack to allow an int value to satisfy a float if field_type == 'float' and value_type == 'int': pass else: raise DeployConfigError( "value for field '%s' is wrong type, expected '%s' and got: %s" % (self.get_full_name(), field_type, value_type)) if field_type == 'list' and self.subtype is not None: # Validate each list item separately if a field subtype is specified for value_item in value: # Use field's subtype for list items item_unmatched = self.validate(value_item, use_subtype=True) unmatched.extend(item_unmatched) elif field_type == 'dict': # Recursively validate sub-field values if self.fields is not None: for k, v in value.items(): if k not in self.fields or not self.fields[ k].is_valid_for_config_version(): unmatched.append('%s.%s' % (self.get_full_name(), k)) continue field_unmatched = self.fields[k].validate(v) unmatched.extend(field_unmatched) # Check for required and locked sub-fields for tmp_field_name, tmp_field in self.fields.items(): if tmp_field.required and value.get( tmp_field_name, None) is None and tmp_field.default is None: raise DeployConfigError( "field '%s' is required, but no value provided" % tmp_field.get_full_name()) if tmp_field.locked and value.get(tmp_field_name, None) is not None: raise DeployConfigError( "field '%s' is locked, but a value was provided" % tmp_field.get_full_name()) # Validate free-form value type elif self.subtype is not None and not use_subtype: for value_item in value.values(): item_unmatched = self.validate(value_item, use_subtype=True) unmatched.extend(item_unmatched) elif field_type == 'str': if self.validation_pattern is not None: if not re.match(self.validation_pattern, value): raise DeployConfigError( "value for field '%s' did not match validation pattern: %s" % (self.get_full_name(), self.validation_pattern)) return unmatched