mirror of
https://github.com/google/glazier.git
synced 2025-12-19 18:27:35 -05:00
166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
# Copyright 2016 Google Inc. All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
"""Manages the initial processing of build.yaml files.
|
|
|
|
## Overview
|
|
|
|
The Config Builder is responsible for compiling a custom installation task list
|
|
for the local host. It traces through all yaml configuration files linked to
|
|
the root config, and stores any commands that are determined applicable by
|
|
pinning (or lack thereof).
|
|
|
|
The end result of running ConfigBuilder is a single ordered list of tasks to
|
|
be performed when installing the host.
|
|
|
|
## Use
|
|
|
|
Start is the main entry point for the class. It expects a path to a
|
|
configuration directory and a filename (the base build.yaml).
|
|
|
|
## Include Logic
|
|
|
|
Yaml files can refer to one another via the include directive. Each sub-yaml
|
|
is treated identically to the base file, following the exact same logic. The
|
|
include directive calls Start on each included file, completing that file
|
|
top-to-bottom before returning to the caller.
|
|
|
|
BuildInfo tracks our position in the directory tree. Additional levels
|
|
(includes in sub-directories) are pushed onto the ActiveConfigPath while we
|
|
process the include. At the end of processing the stack is popped, returning us
|
|
to our previous level. This allows us to reference other files relative to the
|
|
location of the active config.
|
|
"""
|
|
|
|
import copy
|
|
from glazier.lib import actions
|
|
from glazier.lib import buildinfo
|
|
from glazier.lib import download
|
|
from glazier.lib.config import base
|
|
from glazier.lib.config import files
|
|
|
|
_ALLOW_IN_TEMPLATE = [
|
|
'include',
|
|
'template',
|
|
'execute',
|
|
'policy',
|
|
] + dir(actions)
|
|
_ALLOW_IN_CONTROL = _ALLOW_IN_TEMPLATE + ['pin']
|
|
|
|
|
|
class ConfigBuilderError(base.ConfigError):
|
|
pass
|
|
|
|
|
|
class ConfigBuilder(base.ConfigBase):
|
|
"""Builds the complete task list for the installation."""
|
|
|
|
def Start(self, out_file, in_path, in_file='build.yaml'):
|
|
"""Start parsing configuration files.
|
|
|
|
Args:
|
|
out_file: The location to store the compiled config data.
|
|
in_path: The path to the root configuration file.
|
|
in_file: The root configuration file name.
|
|
"""
|
|
self._task_list = []
|
|
self._Start(in_path, in_file)
|
|
try:
|
|
files.Dump(out_file, self._task_list, mode='a')
|
|
except files.Error as e:
|
|
raise ConfigBuilderError(e)
|
|
|
|
def _Start(self, conf_path, conf_file):
|
|
"""Pull and process a config file.
|
|
|
|
Args:
|
|
conf_path: The path to the config below root.
|
|
conf_file: A named config file, normally build.yaml.
|
|
"""
|
|
self._build_info.ActiveConfigPath(append=conf_path.rstrip('/'))
|
|
try:
|
|
path = download.PathCompile(self._build_info, file_name=conf_file)
|
|
yaml_config = files.Read(path)
|
|
except (files.Error, buildinfo.BuildInfoError) as e:
|
|
raise ConfigBuilderError(e)
|
|
controls = yaml_config['controls']
|
|
for control in controls:
|
|
if 'pin' not in control or self._MatchPin(control['pin']):
|
|
self._StoreControls(control, yaml_config.get('templates'))
|
|
self._build_info.ActiveConfigPath(pop=True)
|
|
|
|
def _MatchPin(self, pins):
|
|
"""Check all pin entries for a mismatch.
|
|
|
|
Pins can mismatch either by the matching setting being omitted or by
|
|
matching an exclusion (!).
|
|
|
|
Example:
|
|
pins: ['os', ['win7', 'win8']]
|
|
|
|
* Will match os = win7 or os = win8.
|
|
* Will fail to match os = '2012r2'.
|
|
* Will match model = 'vmware' (because model is not pinned).
|
|
|
|
Example 2:
|
|
pins: ['os', ['!win7']]
|
|
|
|
* Will match os = win8 or os = 2012r2.
|
|
* Will fail to match os = 'win7'.
|
|
* Will match model = 'vmware' (because model is not pinned).
|
|
|
|
Args:
|
|
pins: a list of all applicable pin names and acceptable values
|
|
|
|
Returns:
|
|
True if this host passes all pin checks. False if the host fails a match.
|
|
"""
|
|
for pin in pins:
|
|
try:
|
|
if not self._build_info.BuildPinMatch(pin, pins[pin]):
|
|
return False
|
|
except buildinfo.BuildInfoError as e:
|
|
raise ConfigBuilderError('Error gathering system information. %s' % e)
|
|
return True
|
|
|
|
def _StoreControls(self, control, templates):
|
|
"""Process all of the possible sub-sections of a main control section.
|
|
|
|
Args:
|
|
control: The data from this control subsection.
|
|
templates: Any templates declared in the current config.
|
|
|
|
Raises:
|
|
ConfigBuilderError: Attempt to process an unknown command element.
|
|
"""
|
|
for element in control:
|
|
if element == 'pin':
|
|
continue
|
|
elif element == 'template':
|
|
for template in control['template']:
|
|
self._StoreControls(templates[template], templates)
|
|
elif element == 'include':
|
|
for sub_inc in control['include']:
|
|
self._Start(conf_path=sub_inc[0], conf_file=sub_inc[1])
|
|
elif element in _ALLOW_IN_CONTROL:
|
|
if self._IsRealtimeAction(element, control[element]):
|
|
self._ProcessAction(element, control[element])
|
|
else:
|
|
self._task_list.append({
|
|
'path': copy.deepcopy(self._build_info.ActiveConfigPath()),
|
|
'data': {element: control[element]}
|
|
})
|
|
else:
|
|
raise ConfigBuilderError('Unknown imaging action: %s' % str(element))
|