Merge "Image argument validation and declaration"
This commit is contained in:
commit
7749f50ede
|
@ -115,7 +115,6 @@ full sample is presented for context.
|
|||
arguments:
|
||||
java-distro:
|
||||
description: The java distribution.
|
||||
target_variable: JAVA_DISTRO
|
||||
default: openjdk
|
||||
required: false
|
||||
choices:
|
||||
|
@ -167,9 +166,8 @@ to adjust properties of the image:
|
|||
::
|
||||
|
||||
arguments: # The section header
|
||||
- java-distro: # The friendly name of the argument
|
||||
- java-distro: # The friendly name of the argument, and the name of the variable passed to scripts
|
||||
description: The java distribution. # A friendly description to be used in help text
|
||||
target_variable: JAVA_DISTRO # The value of the argument will be passed to scripts as an environment variable with this name
|
||||
default: openjdk # A default value for the argument
|
||||
required: false # Whether or not the argument is required
|
||||
choices: # The argument value must match an element of this list
|
||||
|
@ -245,21 +243,22 @@ as well:
|
|||
- script: simple_script.sh # Runs this file
|
||||
- script:
|
||||
set_java_home: # The name of a script file
|
||||
env_vars: # Only the named environment variables are passed, for clarity
|
||||
- JDK_HOME
|
||||
- JRE_HOME
|
||||
arguments: # Only the named environment arguments are passed, for clarity
|
||||
- jdk-home
|
||||
- jre-home
|
||||
output: OUTPUT_VAR
|
||||
- script:
|
||||
store_nfs_version: # Because inline is set, this is just a friendly name
|
||||
- inline: rpm -q nfs-utils # Runs this text directly, rather than reading a file
|
||||
- output: NFS_VERSION # Places the stdout of this script into a variable
|
||||
# for future scripts to consume
|
||||
- output: nfs-version # Places the stdout of this script into an argument
|
||||
# for future scripts to consume; if none exists, the
|
||||
# argument is created
|
||||
|
||||
Two variables are always available to scripts run under this framework:
|
||||
|
||||
* SIV_DISTRO: The distro of the image, in case you want to switch on distro
|
||||
* ``distro``: The distro of the image, in case you want to switch on distro
|
||||
within your script (rather than by using the os_case validator).
|
||||
* SIV_RECONCILE: If this value equates to boolean true, then the script should
|
||||
* ``reconcile``: If this value equates to boolean true, then the script should
|
||||
attempt to change the image or instance if it does not already meet the
|
||||
specification. If this equates to boolean false, the script should exit with
|
||||
a failure code if the image or instance does not already meet the
|
||||
|
|
|
@ -101,7 +101,7 @@ class ImageRemote(remote.TerminalOnlyRemote):
|
|||
return 1, ex.message
|
||||
|
||||
def get_os_distrib(self):
|
||||
return self.guest.inspect_get_distro()
|
||||
return self.guest.inspect_get_distro(self.root_drive)
|
||||
|
||||
|
||||
def setup_plugins():
|
||||
|
|
|
@ -76,7 +76,7 @@ def add_plugin_parsers(subparsers):
|
|||
dest="version",
|
||||
help=_("Available versions"))
|
||||
for version, args in six.iteritems(args_by_version):
|
||||
if not args:
|
||||
if args is NotImplemented:
|
||||
continue
|
||||
version_parser = version_parsers.add_parser(
|
||||
version, help=_('{plugin} version {version}').format(
|
||||
|
@ -85,12 +85,12 @@ def add_plugin_parsers(subparsers):
|
|||
arg_token = ("--%s" % arg.name if len(arg.name) > 1 else
|
||||
"-%s" % arg.name)
|
||||
version_parser.add_argument(arg_token,
|
||||
dest=arg.target_variable,
|
||||
dest=arg.name,
|
||||
help=arg.description,
|
||||
default=arg.default,
|
||||
required=arg.required,
|
||||
choices=arg.choices)
|
||||
version_parser.set_defaults(args={arg.target_variable
|
||||
version_parser.set_defaults(args={arg.name
|
||||
for arg in args})
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
arguments:
|
||||
fish:
|
||||
description: awesome
|
||||
default: trout
|
||||
|
||||
validators: []
|
|
@ -73,10 +73,72 @@ def validate_instance(instance, validators, reconcile=True, **kwargs):
|
|||
class ImageArgument(object):
|
||||
"""An argument used by an image manifest."""
|
||||
|
||||
def __init__(self, name, target_variable, description=None,
|
||||
default=None, required=False, choices=None):
|
||||
SPEC_SCHEMA = {
|
||||
"type": "object",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target_variable": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"default": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"required": {
|
||||
"type": "boolean",
|
||||
"minLength": 1
|
||||
},
|
||||
"choices": {
|
||||
"type": "array",
|
||||
"minLength": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_spec(cls, spec):
|
||||
"""Constructs and returns a set of arguments from a specification.
|
||||
|
||||
:param spec: The specification for the argument set.
|
||||
:return A dict of arguments built to the specification.
|
||||
"""
|
||||
|
||||
jsonschema.validate(spec, cls.SPEC_SCHEMA)
|
||||
arguments = {name: cls(name,
|
||||
arg.get('description'),
|
||||
arg.get('default'),
|
||||
arg.get('required'),
|
||||
arg.get('choices'))
|
||||
for name, arg in six.iteritems(spec)}
|
||||
reserved_names = ['distro', 'reconcile']
|
||||
for name, arg in six.iteritems(arguments):
|
||||
if name in reserved_names:
|
||||
raise p_ex.ImageValidationSpecificationError(
|
||||
_("The following argument names are reserved: "
|
||||
"{names}").format(reserved_names))
|
||||
if not arg.default and not arg.required:
|
||||
raise p_ex.ImageValidationSpecificationError(
|
||||
_("Argument {name} is not required and must specify a "
|
||||
"default value.").format(name=arg.name))
|
||||
if arg.choices and arg.default and arg.default not in arg.choices:
|
||||
raise p_ex.ImageValidationSpecificationError(
|
||||
_("Argument {name} specifies a default which is not one "
|
||||
"of its choices.").format(name=arg.name))
|
||||
return arguments
|
||||
|
||||
def __init__(self, name, description=None, default=None, required=False,
|
||||
choices=None):
|
||||
self.name = name
|
||||
self.target_variable = target_variable
|
||||
self.description = description
|
||||
self.default = default
|
||||
self.required = required
|
||||
|
@ -105,8 +167,8 @@ class ImageValidator(object):
|
|||
class SaharaImageValidatorBase(ImageValidator):
|
||||
"""Base class for Sahara's native image validation."""
|
||||
|
||||
DISTRO_KEY = 'SIV_DISTRO'
|
||||
RECONCILE_KEY = 'SIV_RECONCILE'
|
||||
DISTRO_KEY = 'distro'
|
||||
RECONCILE_KEY = 'reconcile'
|
||||
|
||||
ORDERED_VALIDATORS_SCHEMA = {
|
||||
"type": "array",
|
||||
|
@ -231,7 +293,8 @@ class SaharaImageValidatorBase(ImageValidator):
|
|||
def __nonzero__(self):
|
||||
return False
|
||||
|
||||
def try_validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def try_validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate, but returns rather than raising on failure.
|
||||
|
||||
:param remote: A remote socket to the instance.
|
||||
|
@ -239,12 +302,14 @@ class SaharaImageValidatorBase(ImageValidator):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:return True if successful, ValidationAttemptFailed object if failed.
|
||||
"""
|
||||
try:
|
||||
self.validate(
|
||||
remote, reconcile=reconcile, env_map=env_map, **kwargs)
|
||||
remote, reconcile=reconcile,
|
||||
image_arguments=image_arguments, **kwargs)
|
||||
return True
|
||||
except p_ex.ImageValidationError as exc:
|
||||
return self.ValidationAttemptFailed(exc)
|
||||
|
@ -266,6 +331,10 @@ class SaharaImageValidator(SaharaImageValidatorBase):
|
|||
"required": ["validators"]
|
||||
}
|
||||
|
||||
def get_argument_list(self):
|
||||
return [argument for name, argument
|
||||
in six.iteritems(self.arguments)]
|
||||
|
||||
@classmethod
|
||||
def from_spec(cls, spec, validator_map, resource_roots):
|
||||
"""Constructs and returns a validator from a specification object.
|
||||
|
@ -280,12 +349,14 @@ class SaharaImageValidator(SaharaImageValidatorBase):
|
|||
:return A SaharaImageValidator containing all specified validators.
|
||||
"""
|
||||
jsonschema.validate(spec, cls.SPEC_SCHEMA)
|
||||
specs = spec['validators']
|
||||
arguments_spec = spec.get('arguments', {})
|
||||
arguments = ImageArgument.from_spec(arguments_spec)
|
||||
validators_spec = spec['validators']
|
||||
validator = SaharaAllValidator.from_spec(
|
||||
specs, validator_map, resource_roots)
|
||||
return cls(validator)
|
||||
validators_spec, validator_map, resource_roots)
|
||||
return cls(validator, arguments)
|
||||
|
||||
def __init__(self, validator):
|
||||
def __init__(self, validator, arguments):
|
||||
"""Constructor method.
|
||||
|
||||
:param validator: A SaharaAllValidator containing the specified
|
||||
|
@ -293,9 +364,11 @@ class SaharaImageValidator(SaharaImageValidatorBase):
|
|||
"""
|
||||
self.validator = validator
|
||||
self.validators = validator.validators
|
||||
self.arguments = arguments
|
||||
|
||||
@transform_exception(ex.RemoteCommandException, p_ex.ImageValidationError)
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate the image.
|
||||
|
||||
Before deferring to contained validators, performs one-time setup
|
||||
|
@ -306,15 +379,31 @@ class SaharaImageValidator(SaharaImageValidatorBase):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
env_map = copy.deepcopy(env_map) if env_map else {}
|
||||
env_map[self.RECONCILE_KEY] = 1 if reconcile else 0
|
||||
raw_distro = remote.execute_command('lsb_release -is')
|
||||
distro = raw_distro[1].strip().lower()
|
||||
env_map[self.DISTRO_KEY] = distro
|
||||
self.validator.validate(remote, reconcile=reconcile, env_map=env_map)
|
||||
argument_values = {}
|
||||
for name, argument in six.iteritems(self.arguments):
|
||||
if name not in image_arguments:
|
||||
if argument.required:
|
||||
raise p_ex.ImageValidationError(
|
||||
_("Argument {name} is required for image "
|
||||
"processing.").format(name=name))
|
||||
else:
|
||||
argument_values[name] = argument.default
|
||||
else:
|
||||
value = image_arguments[name]
|
||||
choices = argument.choices
|
||||
if choices and value not in choices:
|
||||
raise p_ex.ImageValidationError(
|
||||
_("Value for argument {name} must be one of "
|
||||
"{choices}.").format(name=name, choices=choices))
|
||||
else:
|
||||
argument_values[name] = value
|
||||
argument_values[self.DISTRO_KEY] = remote.get_os_distrib()
|
||||
self.validator.validate(remote, reconcile=reconcile,
|
||||
image_arguments=argument_values)
|
||||
|
||||
|
||||
class SaharaPackageValidator(SaharaImageValidatorBase):
|
||||
|
@ -406,7 +495,8 @@ class SaharaPackageValidator(SaharaImageValidatorBase):
|
|||
self.packages = packages
|
||||
|
||||
@transform_exception(ex.RemoteCommandException, p_ex.ImageValidationError)
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate package installation on the image.
|
||||
|
||||
Even if reconcile=True, attempts to verify previous package
|
||||
|
@ -418,10 +508,11 @@ class SaharaPackageValidator(SaharaImageValidatorBase):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
env_distro = env_map[self.DISTRO_KEY]
|
||||
env_distro = image_arguments[self.DISTRO_KEY]
|
||||
env_family = self._DISTRO_FAMILES[env_distro]
|
||||
check, install = self._DISTRO_TOOLS[env_family]
|
||||
if not env_family:
|
||||
|
@ -513,7 +604,7 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
|
|||
env_vars: A list of environment variable names to send to the
|
||||
script.
|
||||
output: A key into which to put the stdout of the script in the
|
||||
env_map of the validation run.
|
||||
image_arguments of the validation run.
|
||||
:param validator_map: A map of validator name to class.
|
||||
:param resource_roots: The roots from which relative paths to
|
||||
resources (scripts and such) will be referenced. Any resource will
|
||||
|
@ -552,7 +643,7 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
|
|||
:param env_vars: A list of environment variables to send to the
|
||||
script.
|
||||
:param output_var: A key into which to put the stdout of the script in
|
||||
the env_map of the validation run.
|
||||
the image_arguments of the validation run.
|
||||
:return: A SaharaScriptValidator.
|
||||
"""
|
||||
self.script_contents = script_contents
|
||||
|
@ -560,7 +651,8 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
|
|||
self.output_var = output_var
|
||||
|
||||
@transform_exception(ex.RemoteCommandException, p_ex.ImageValidationError)
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate by running a script on the image.
|
||||
|
||||
:param remote: A remote socket to the instance.
|
||||
|
@ -568,25 +660,28 @@ class SaharaScriptValidator(SaharaImageValidatorBase):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
Note that the key SIV_RECONCILE will be set to 1 if the script
|
||||
should reconcile and 0 otherwise; all scripts should act on this
|
||||
input if possible. The key SIV_DISTRO will also contain the
|
||||
distro representation, per `lsb_release -is`.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
arguments = copy.deepcopy(image_arguments)
|
||||
arguments[self.RECONCILE_KEY] = 1 if reconcile else 0
|
||||
script = "\n".join(["%(env_vars)s",
|
||||
"bash <<_SIV_",
|
||||
"%(script)s",
|
||||
"_SIV_"])
|
||||
env_vars = "\n".join("export %s=%s" % (key, value) for (key, value)
|
||||
in six.iteritems(env_map)
|
||||
in six.iteritems(image_arguments)
|
||||
if key in self.env_vars)
|
||||
script = script % {"env_vars": env_vars,
|
||||
"script": self.script_contents}
|
||||
code, stdout = _sudo(remote, script)
|
||||
if self.output_var:
|
||||
env_map[self.output_var] = stdout
|
||||
image_arguments[self.output_var] = stdout
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
|
@ -618,17 +713,20 @@ class SaharaAggregateValidator(SaharaImageValidatorBase):
|
|||
class SaharaAnyValidator(SaharaAggregateValidator):
|
||||
"""A list of validators, only one of which must succeed."""
|
||||
|
||||
def _try_all(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def _try_all(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
results = []
|
||||
for validator in self.validators:
|
||||
result = validator.try_validate(remote, reconcile=reconcile,
|
||||
env_map=env_map, **kwargs)
|
||||
image_arguments=image_arguments,
|
||||
**kwargs)
|
||||
results.append(result)
|
||||
if result:
|
||||
break
|
||||
return results
|
||||
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate any of the contained validators.
|
||||
|
||||
Note that if reconcile=True, this validator will first run all
|
||||
|
@ -641,13 +739,15 @@ class SaharaAnyValidator(SaharaAggregateValidator):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
results = self._try_all(remote, reconcile=False, env_map=env_map)
|
||||
results = self._try_all(remote, reconcile=False,
|
||||
image_arguments=image_arguments)
|
||||
if reconcile and not any(results):
|
||||
results = self._try_all(remote, reconcile=True, env_map=env_map)
|
||||
results = self._try_all(remote, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
if not any(results):
|
||||
raise p_ex.AllValidationsFailedError(result.exception for result
|
||||
in results)
|
||||
|
@ -656,7 +756,7 @@ class SaharaAnyValidator(SaharaAggregateValidator):
|
|||
class SaharaAllValidator(SaharaAggregateValidator):
|
||||
"""A list of validators, all of which must succeed."""
|
||||
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True, image_arguments=None, **kwargs):
|
||||
"""Attempts to validate all of the contained validators.
|
||||
|
||||
:param remote: A remote socket to the instance.
|
||||
|
@ -664,12 +764,13 @@ class SaharaAllValidator(SaharaAggregateValidator):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
for validator in self.validators:
|
||||
validator.validate(remote, reconcile=reconcile, env_map=env_map)
|
||||
validator.validate(remote, reconcile=reconcile,
|
||||
image_arguments=image_arguments)
|
||||
|
||||
|
||||
class SaharaOSCaseValidator(SaharaImageValidatorBase):
|
||||
|
@ -718,7 +819,8 @@ class SaharaOSCaseValidator(SaharaImageValidatorBase):
|
|||
"""
|
||||
self.distros = distros
|
||||
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate depending on distro.
|
||||
|
||||
May match the OS by specific distro or by family (centos may match
|
||||
|
@ -731,17 +833,18 @@ class SaharaOSCaseValidator(SaharaImageValidatorBase):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
env_distro = env_map[self.DISTRO_KEY]
|
||||
env_distro = image_arguments[self.DISTRO_KEY]
|
||||
family = self._DISTRO_FAMILES.get(env_distro)
|
||||
matches = {env_distro, family} if family else {env_distro}
|
||||
for distro, validator in self.distros:
|
||||
if distro in matches:
|
||||
validator.validate(
|
||||
remote, reconcile=reconcile, env_map=env_map)
|
||||
remote, reconcile=reconcile,
|
||||
image_arguments=image_arguments)
|
||||
break
|
||||
|
||||
|
||||
|
@ -799,7 +902,8 @@ class SaharaArgumentCaseValidator(SaharaImageValidatorBase):
|
|||
self.argument_name = argument_name
|
||||
self.cases = cases
|
||||
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate depending on argument value.
|
||||
|
||||
:param remote: A remote socket to the instance.
|
||||
|
@ -807,18 +911,19 @@ class SaharaArgumentCaseValidator(SaharaImageValidatorBase):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
:raises ImageValidationError: If validation fails.
|
||||
"""
|
||||
arg = self.argument_name
|
||||
if arg not in env_map:
|
||||
if arg not in image_arguments:
|
||||
raise p_ex.ImageValidationError(
|
||||
_("Argument {name} not found.").format(name=arg))
|
||||
value = env_map[arg]
|
||||
value = image_arguments[arg]
|
||||
if value in self.cases:
|
||||
self.cases[value].validate(
|
||||
remote, reconcile=reconcile, env_map=env_map)
|
||||
remote, reconcile=reconcile,
|
||||
image_arguments=image_arguments)
|
||||
|
||||
|
||||
class SaharaArgumentSetterValidator(SaharaImageValidatorBase):
|
||||
|
@ -868,7 +973,8 @@ class SaharaArgumentSetterValidator(SaharaImageValidatorBase):
|
|||
self.argument_name = argument_name
|
||||
self.value = value
|
||||
|
||||
def validate(self, remote, reconcile=True, env_map=None, **kwargs):
|
||||
def validate(self, remote, reconcile=True,
|
||||
image_arguments=None, **kwargs):
|
||||
"""Attempts to validate depending on argument value.
|
||||
|
||||
:param remote: A remote socket to the instance.
|
||||
|
@ -876,9 +982,10 @@ class SaharaArgumentSetterValidator(SaharaImageValidatorBase):
|
|||
desired state is present, and fail if it is not. If true, all
|
||||
validators will attempt to enforce the desired state if possible,
|
||||
and succeed if this enforcement succeeds.
|
||||
:param env_map: A map of environment variables to pass to scripts.
|
||||
:param image_arguments: A dictionary of image argument values keyed by
|
||||
argument name.
|
||||
"""
|
||||
env_map[self.argument_name] = self.value
|
||||
image_arguments[self.argument_name] = self.value
|
||||
|
||||
|
||||
def _sudo(remote, cmd, **kwargs):
|
||||
|
|
|
@ -122,8 +122,7 @@ class ProvisioningPluginBase(plugins_base.PluginInterface):
|
|||
This can be used to test images without modification. If set to
|
||||
True per the default, this method will modify the image if any
|
||||
requirements are not met.
|
||||
:param image_arguments: A dict of image argument target variable (not
|
||||
the friendly name used for documentation purposes) to argument
|
||||
:param image_arguments: A dict of image argument name to argument
|
||||
value.
|
||||
:raises: sahara.plugins.exceptions.ImageValidationError: If the method
|
||||
fails to modify the image to specification (if reconcile is True),
|
||||
|
@ -146,8 +145,7 @@ class ProvisioningPluginBase(plugins_base.PluginInterface):
|
|||
This can be used to test images without modification. If set to
|
||||
True per the default, this method will modify the image if any
|
||||
requirements are not met.
|
||||
:param image_arguments: A dict of image argument target variable (not
|
||||
the friendly name used for documentation purposes) to argument
|
||||
:param image_arguments: A dict of image argument name to argument
|
||||
value.
|
||||
:raises: sahara.plugins.exceptions.ImageValidationError: If the method
|
||||
fails to modify the image to specification (if reconcile is True),
|
||||
|
|
|
@ -47,15 +47,15 @@ class TestImages(b.SaharaTestCase):
|
|||
|
||||
validator = cls.from_spec('test_images.py', {}, resource_roots)
|
||||
self.assertIsInstance(validator, cls)
|
||||
self.assertEqual(validator.env_vars, ['SIV_RECONCILE', 'SIV_DISTRO'])
|
||||
self.assertEqual(validator.env_vars, ['reconcile', 'distro'])
|
||||
|
||||
validator = cls.from_spec(
|
||||
{'test_images.py': {'env_vars': ['EXTRA_FILE', 'USER']}},
|
||||
{'test_images.py': {'env_vars': ['extra-file', 'user']}},
|
||||
{}, resource_roots)
|
||||
self.assertIsInstance(validator, cls)
|
||||
self.assertEqual(validator.env_vars,
|
||||
['SIV_RECONCILE', 'SIV_DISTRO',
|
||||
'EXTRA_FILE', 'USER'])
|
||||
['reconcile', 'distro',
|
||||
'extra-file', 'user'])
|
||||
|
||||
def test_all_spec(self):
|
||||
cls = images.SaharaAllValidator
|
||||
|
@ -106,6 +106,15 @@ class TestImages(b.SaharaTestCase):
|
|||
resource_roots = ['tests/unit/plugins']
|
||||
|
||||
spec = """
|
||||
arguments:
|
||||
java-version:
|
||||
description: The version of java.
|
||||
default: openjdk
|
||||
required: false
|
||||
choices:
|
||||
- openjdk
|
||||
- oracle-java
|
||||
|
||||
validators:
|
||||
- os_case:
|
||||
- redhat:
|
||||
|
@ -116,12 +125,12 @@ class TestImages(b.SaharaTestCase):
|
|||
- all:
|
||||
- package: java-1.8.0-openjdk-devel
|
||||
- argument_set:
|
||||
argument_name: JAVA_VERSION
|
||||
argument_name: java-version
|
||||
value: 1.8.0
|
||||
- all:
|
||||
- package: java-1.7.0-openjdk-devel
|
||||
- argument_set:
|
||||
argument_name: JAVA_VERSION
|
||||
argument_name: java-version
|
||||
value: 1.7.0
|
||||
- script: test_images.py
|
||||
- package:
|
||||
|
@ -156,33 +165,43 @@ class TestImages(b.SaharaTestCase):
|
|||
self.assertIsInstance(validators[3], images.SaharaPackageValidator)
|
||||
self.assertIsInstance(
|
||||
validators[4], images.SaharaArgumentCaseValidator)
|
||||
self.assertEqual(1, len(validator.arguments))
|
||||
self.assertEqual(validator.arguments['java-version'].required, False)
|
||||
self.assertEqual(validator.arguments['java-version'].default,
|
||||
'openjdk')
|
||||
self.assertEqual(validator.arguments['java-version'].description,
|
||||
'The version of java.')
|
||||
self.assertEqual(validator.arguments['java-version'].choices,
|
||||
['openjdk', 'oracle-java'])
|
||||
|
||||
def test_package_validator_redhat(self):
|
||||
cls = images.SaharaPackageValidator
|
||||
env_map = {"SIV_DISTRO": 'centos'}
|
||||
image_arguments = {"distro": 'centos'}
|
||||
|
||||
packages = [cls.Package("java", "8")]
|
||||
validator = images.SaharaPackageValidator(packages)
|
||||
remote = mock.Mock()
|
||||
validator.validate(remote, reconcile=False, env_map=env_map)
|
||||
validator.validate(remote, reconcile=False,
|
||||
image_arguments=image_arguments)
|
||||
remote.execute_command.assert_called_with(
|
||||
"rpm -q java-8", run_as_root=True)
|
||||
|
||||
env_map = {"SIV_DISTRO": 'fedora'}
|
||||
image_arguments = {"distro": 'fedora'}
|
||||
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
|
||||
validator = images.SaharaPackageValidator(packages)
|
||||
remote = mock.Mock()
|
||||
remote.execute_command.side_effect = (
|
||||
ex.RemoteCommandException("So bad!"))
|
||||
try:
|
||||
validator.validate(remote, reconcile=False, env_map=env_map)
|
||||
validator.validate(remote, reconcile=False,
|
||||
image_arguments=image_arguments)
|
||||
except p_ex.ImageValidationError as e:
|
||||
self.assertIn("So bad!", e.message)
|
||||
remote.execute_command.assert_called_with(
|
||||
"rpm -q java-8 hadoop", run_as_root=True)
|
||||
self.assertEqual(remote.execute_command.call_count, 1)
|
||||
|
||||
env_map = {"SIV_DISTRO": 'redhatenterpriseserver'}
|
||||
image_arguments = {"distro": 'redhatenterpriseserver'}
|
||||
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
|
||||
validator = images.SaharaPackageValidator(packages)
|
||||
remote = mock.Mock()
|
||||
|
@ -193,7 +212,8 @@ class TestImages(b.SaharaTestCase):
|
|||
|
||||
remote.execute_command.side_effect = side_effect
|
||||
try:
|
||||
validator.validate(remote, reconcile=True, env_map=env_map)
|
||||
validator.validate(remote, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
except p_ex.ImageValidationError as e:
|
||||
self.assertIn("So bad!", e.message)
|
||||
self.assertEqual(remote.execute_command.call_count, 3)
|
||||
|
@ -204,37 +224,40 @@ class TestImages(b.SaharaTestCase):
|
|||
|
||||
def test_package_validator_debian(self):
|
||||
cls = images.SaharaPackageValidator
|
||||
env_map = {"SIV_DISTRO": 'ubuntu'}
|
||||
image_arguments = {"distro": 'ubuntu'}
|
||||
|
||||
packages = [cls.Package("java", "8")]
|
||||
validator = images.SaharaPackageValidator(packages)
|
||||
remote = mock.Mock()
|
||||
validator.validate(remote, reconcile=False, env_map=env_map)
|
||||
validator.validate(remote, reconcile=False,
|
||||
image_arguments=image_arguments)
|
||||
remote.execute_command.assert_called_with(
|
||||
"dpkg -s java-8", run_as_root=True)
|
||||
|
||||
env_map = {"SIV_DISTRO": 'ubuntu'}
|
||||
image_arguments = {"distro": 'ubuntu'}
|
||||
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
|
||||
validator = images.SaharaPackageValidator(packages)
|
||||
remote = mock.Mock()
|
||||
remote.execute_command.side_effect = (
|
||||
ex.RemoteCommandException("So bad!"))
|
||||
try:
|
||||
validator.validate(remote, reconcile=False, env_map=env_map)
|
||||
validator.validate(remote, reconcile=False,
|
||||
image_arguments=image_arguments)
|
||||
except p_ex.ImageValidationError as e:
|
||||
self.assertIn("So bad!", e.message)
|
||||
remote.execute_command.assert_called_with(
|
||||
"dpkg -s java-8 hadoop", run_as_root=True)
|
||||
self.assertEqual(remote.execute_command.call_count, 1)
|
||||
|
||||
env_map = {"SIV_DISTRO": 'ubuntu'}
|
||||
image_arguments = {"distro": 'ubuntu'}
|
||||
packages = [cls.Package("java", "8"), cls.Package("hadoop")]
|
||||
validator = images.SaharaPackageValidator(packages)
|
||||
remote = mock.Mock()
|
||||
remote.execute_command.side_effect = (
|
||||
ex.RemoteCommandException("So bad!"))
|
||||
try:
|
||||
validator.validate(remote, reconcile=True, env_map=env_map)
|
||||
validator.validate(remote, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
except p_ex.ImageValidationError as e:
|
||||
self.assertIn("So bad!", e.message)
|
||||
self.assertEqual(remote.execute_command.call_count, 2)
|
||||
|
@ -246,20 +269,22 @@ class TestImages(b.SaharaTestCase):
|
|||
|
||||
def test_script_validator(self):
|
||||
cls = images.SaharaScriptValidator
|
||||
env_map = {"SIV_DISTRO": 'centos'}
|
||||
map_rep = "export SIV_DISTRO=centos\n"
|
||||
image_arguments = {"distro": 'centos'}
|
||||
map_rep = "export distro=centos\n"
|
||||
cmd = "It's dangerous to go alone. Run this."
|
||||
expected_cmd = "bash <<_SIV_\n%s\n_SIV_" % cmd
|
||||
validator = cls(cmd, env_vars=env_map.keys(), output_var="SIV_DISTRO")
|
||||
validator = cls(cmd, env_vars=image_arguments.keys(),
|
||||
output_var="distro")
|
||||
|
||||
remote = mock.Mock(
|
||||
execute_command=mock.Mock(
|
||||
return_value=(0, 'fedora')))
|
||||
|
||||
validator.validate(remote, reconcile=True, env_map=env_map)
|
||||
validator.validate(remote, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
call = [mock.call(map_rep + expected_cmd, run_as_root=True)]
|
||||
remote.execute_command.assert_has_calls(call)
|
||||
self.assertEqual(env_map['SIV_DISTRO'], 'fedora')
|
||||
self.assertEqual(image_arguments['distro'], 'fedora')
|
||||
|
||||
def test_any_validator(self):
|
||||
cls = images.SaharaAnyValidator
|
||||
|
@ -316,7 +341,7 @@ class TestImages(b.SaharaTestCase):
|
|||
validator.validate(None, reconcile=True)
|
||||
self.assertEqual(always_tells_the_truth.validate.call_count, 2)
|
||||
always_tells_the_truth.validate.assert_called_with(
|
||||
None, reconcile=True, env_map=None)
|
||||
None, reconcile=True, image_arguments=None)
|
||||
|
||||
# Second fails
|
||||
always_tells_the_truth = mock.Mock()
|
||||
|
@ -330,9 +355,9 @@ class TestImages(b.SaharaTestCase):
|
|||
self.assertEqual(always_tells_the_truth.validate.call_count, 1)
|
||||
self.assertEqual(always_lies.validate.call_count, 1)
|
||||
always_tells_the_truth.validate.assert_called_with(
|
||||
None, reconcile=False, env_map=None)
|
||||
None, reconcile=False, image_arguments=None)
|
||||
always_lies.validate.assert_called_with(
|
||||
None, reconcile=False, env_map=None)
|
||||
None, reconcile=False, image_arguments=None)
|
||||
|
||||
# First fails
|
||||
always_tells_the_truth = mock.Mock()
|
||||
|
@ -340,12 +365,12 @@ class TestImages(b.SaharaTestCase):
|
|||
side_effect=p_ex.ImageValidationError("Boom!")))
|
||||
validator = cls([always_lies, always_tells_the_truth])
|
||||
try:
|
||||
validator.validate(None, reconcile=False, env_map={})
|
||||
validator.validate(None, reconcile=False, image_arguments={})
|
||||
except p_ex.ImageValidationError:
|
||||
pass
|
||||
self.assertEqual(always_lies.validate.call_count, 1)
|
||||
always_lies.validate.assert_called_with(
|
||||
None, reconcile=False, env_map={})
|
||||
None, reconcile=False, image_arguments={})
|
||||
self.assertEqual(always_tells_the_truth.validate.call_count, 0)
|
||||
|
||||
def test_os_case_validator(self):
|
||||
|
@ -356,33 +381,36 @@ class TestImages(b.SaharaTestCase):
|
|||
centos = Distro("centos", mock.Mock())
|
||||
redhat = Distro("redhat", mock.Mock())
|
||||
distros = [centos, redhat]
|
||||
env_map = {images.SaharaImageValidator.DISTRO_KEY: "centos"}
|
||||
image_arguments = {images.SaharaImageValidator.DISTRO_KEY: "centos"}
|
||||
validator = cls(distros)
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(centos.validator.validate.call_count, 1)
|
||||
self.assertEqual(redhat.validator.validate.call_count, 0)
|
||||
centos.validator.validate.assert_called_with(
|
||||
None, reconcile=True, env_map=env_map)
|
||||
None, reconcile=True, image_arguments=image_arguments)
|
||||
|
||||
# Familes match
|
||||
centos = Distro("centos", mock.Mock())
|
||||
redhat = Distro("redhat", mock.Mock())
|
||||
distros = [centos, redhat]
|
||||
env_map = {images.SaharaImageValidator.DISTRO_KEY: "fedora"}
|
||||
image_arguments = {images.SaharaImageValidator.DISTRO_KEY: "fedora"}
|
||||
validator = cls(distros)
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(centos.validator.validate.call_count, 0)
|
||||
self.assertEqual(redhat.validator.validate.call_count, 1)
|
||||
redhat.validator.validate.assert_called_with(
|
||||
None, reconcile=True, env_map=env_map)
|
||||
None, reconcile=True, image_arguments=image_arguments)
|
||||
|
||||
# Non-matches do nothing
|
||||
centos = Distro("centos", mock.Mock())
|
||||
redhat = Distro("redhat", mock.Mock())
|
||||
distros = [centos, redhat]
|
||||
env_map = {images.SaharaImageValidator.DISTRO_KEY: "ubuntu"}
|
||||
image_arguments = {images.SaharaImageValidator.DISTRO_KEY: "ubuntu"}
|
||||
validator = cls(distros)
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(centos.validator.validate.call_count, 0)
|
||||
self.assertEqual(redhat.validator.validate.call_count, 0)
|
||||
|
||||
|
@ -390,59 +418,59 @@ class TestImages(b.SaharaTestCase):
|
|||
cls = images.SaharaArgumentCaseValidator
|
||||
|
||||
# Match gets called
|
||||
env_map = {"argument": "value"}
|
||||
image_arguments = {"argument": "value"}
|
||||
match = mock.Mock()
|
||||
nomatch = mock.Mock()
|
||||
cases = {"value": match,
|
||||
"another_value": nomatch}
|
||||
validator = cls("argument", cases)
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(match.validate.call_count, 1)
|
||||
self.assertEqual(nomatch.validate.call_count, 0)
|
||||
match.validate.assert_called_with(
|
||||
None, reconcile=True, env_map=env_map)
|
||||
None, reconcile=True, image_arguments=image_arguments)
|
||||
|
||||
# Non-matches do nothing
|
||||
env_map = {"argument": "value"}
|
||||
image_arguments = {"argument": "value"}
|
||||
nomatch = mock.Mock()
|
||||
cases = {"some_value": nomatch,
|
||||
"another_value": nomatch}
|
||||
validator = cls("argument", cases)
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(nomatch.validate.call_count, 0)
|
||||
|
||||
def test_sahara_argument_set_validator(self):
|
||||
cls = images.SaharaArgumentSetterValidator
|
||||
|
||||
# Old variable is overwritten
|
||||
env_map = {"argument": "value"}
|
||||
image_arguments = {"argument": "value"}
|
||||
validator = cls("argument", "new_value")
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
self.assertEqual(env_map["argument"], "new_value")
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(image_arguments["argument"], "new_value")
|
||||
|
||||
# New variable is set
|
||||
env_map = {"argument": "value"}
|
||||
image_arguments = {"argument": "value"}
|
||||
validator = cls("another_argument", "value")
|
||||
validator.validate(None, reconcile=True, env_map=env_map)
|
||||
self.assertEqual(env_map, {"argument": "value",
|
||||
"another_argument": "value"})
|
||||
validator.validate(None, reconcile=True,
|
||||
image_arguments=image_arguments)
|
||||
self.assertEqual(image_arguments,
|
||||
{"argument": "value", "another_argument": "value"})
|
||||
|
||||
def test_sahara_image_validator(self):
|
||||
cls = images.SaharaImageValidator
|
||||
|
||||
sub_validator = mock.Mock(validate=mock.Mock())
|
||||
remote = mock.Mock(execute_command=mock.Mock(
|
||||
return_value=(None, "CENTOS ")))
|
||||
validator = cls(sub_validator)
|
||||
validator.validate(remote, reconcile=True, env_map={})
|
||||
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos",
|
||||
images.SaharaImageValidatorBase.RECONCILE_KEY: 1}
|
||||
remote.execute_command.assert_called_with('lsb_release -is')
|
||||
remote = mock.Mock(get_os_distrib=mock.Mock(
|
||||
return_value="centos"))
|
||||
validator = cls(sub_validator, {})
|
||||
validator.validate(remote, reconcile=True, image_arguments={})
|
||||
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos"}
|
||||
sub_validator.validate.assert_called_with(
|
||||
remote, reconcile=True, env_map=expected_map)
|
||||
|
||||
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos",
|
||||
images.SaharaImageValidatorBase.RECONCILE_KEY: 0}
|
||||
validator.validate(remote, reconcile=False, env_map={})
|
||||
remote, reconcile=True, image_arguments=expected_map)
|
||||
expected_map = {images.SaharaImageValidatorBase.DISTRO_KEY: "centos"}
|
||||
validator.validate(remote, reconcile=False, image_arguments={})
|
||||
sub_validator.validate.assert_called_with(
|
||||
remote, reconcile=False, env_map=expected_map)
|
||||
remote, reconcile=False, image_arguments=expected_map)
|
||||
|
|
Loading…
Reference in New Issue