# 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. import copy import os import yaml from fuel_external_git import const from fuel_external_git import handlers from fuel_external_git.objects import GitRepo from fuel_external_git.settings import GitExtensionSettings from fuel_external_git import utils from nailgun.extensions import BaseExtension from nailgun.extensions import BasePipeline from nailgun.logger import logger class OpenStackConfigPipeline(BasePipeline): # TODO(dukov) add cluster remove callback @classmethod def process_deployment_for_node(cls, node, node_data): """Updating deployment info Genereate OpenStack configuration hash based on configuration files stored in git repository associated with a particular environment Example of configuration extension: configuration: nova_config: DEFAULT/debug: value: True DEFAULT/amqp_durable_queues: value: False keystone_config: DEFAULT/default_publisher_id: ensure: absent DEFAULT/crypt_strength: value: 6000 """ repo = GitRepo.get_by_cluster_id(node.cluster_id) if not repo: return node_data GitRepo.checkout(repo) repo_path = os.path.join(const.REPOS_DIR, repo.repo_name) resource_mapping = ExternalGit.ext_settings['resource_mapping'] exts_list = utils.get_file_exts_list(resource_mapping) global_config = utils.get_config_hash(repo_path, resource_mapping, exts=exts_list) # Read config for overrides # Overrides file should contain following mapping # - role:config_file_dir # - node_id:config_file_dir # Config options from files for roles should override global # configs (placed in repo root). # Config options from files for nodes should override global # and roles config oprions overrides_file = os.path.join(repo_path, 'overrides.yaml') if os.path.exists(overrides_file): overrides = yaml.load(open(overrides_file)) else: overrides = {'roles': {}, 'nodes': {}} override_configs = {} # ent = roles|nodes for ent, override in overrides.items(): override_configs[ent] = {} # key = role_name|node_id for key, path in override.items(): file_dir = os.path.join(repo_path, path) config_hash = utils.get_config_hash(file_dir, resource_mapping, exts=exts_list) override_configs[ent][key] = config_hash logger.debug("Override configs {}".format(override_configs)) common = copy.deepcopy(global_config) roles = node_data['roles'] uid = node_data['uid'] logger.debug("Node {0} roles {1}".format(uid, roles)) for role in roles: utils.deep_merge(common, override_configs['roles'].get(role, {})) logger.debug("Config Node {0} with roles {1}".format(uid, common)) utils.deep_merge(common, override_configs['nodes'].get(uid, {})) node_data['configuration'] = common logger.info("Node {0} config from git {1}".format(uid, common)) return node_data # TODO(dukov) Remove decorator extension management is available @utils.register_extension(u'fuel_external_git') class ExternalGit(BaseExtension): name = 'fuel_external_git' version = '1.0.0' description = 'Nailgun extension which uses git repo for config files' urls = [{'uri': r'/clusters/git-repos/?$', 'handler': handlers.GitRepoCollectionHandler}, {'uri': r'/clusters/(?P\d+)/git-repos/(?P\d+)?$', 'handler': handlers.GitRepoHandler}, {'uri': r'/clusters/(?P\d+)/git-repos/(?P\d+)/init?$', 'handler': handlers.GitRepoInit}] data_pipelines = [ OpenStackConfigPipeline, ] ext_settings = GitExtensionSettings().config @classmethod def alembic_migrations_path(cls): return os.path.join(os.path.dirname(__file__), 'migrations')