Add module to generate CentOS Compose repos
This patch extends yum-config funcionality to provide a module that generates compose repos based on a compose URL. Co-authored-by: Bhagyashri Shewale <bshewale@redhat.com> Change-Id: I9f8dbede85e1c60de3a86991abcd53fc86444155 Signed-off-by: Douglas Viroel <dviroel@redhat.com>
This commit is contained in:
parent
6d878bb79b
commit
9bc5d12529
|
@ -15,8 +15,8 @@ its repository and invoking in command line:
|
||||||
|
|
||||||
This subcommand lets you enable or disable a repo and sets its configuration options.
|
This subcommand lets you enable or disable a repo and sets its configuration options.
|
||||||
The *tripleo-yum-config* module will search for the provided repo name in all *.repo* files at REPO_DIR_PATH.
|
The *tripleo-yum-config* module will search for the provided repo name in all *.repo* files at REPO_DIR_PATH.
|
||||||
Optionally, you can provide a dir path where your repo files live or specify the full path of the repo file.
|
Optionally, you can provide a path where your repo files live or specify the full path of the repo file.
|
||||||
By default REPO_DIR_PATH is set to */etc/yum.repod.d/*.
|
By default REPO_DIR_PATH is set to */etc/yum.repos.d/*.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
|
@ -48,6 +48,19 @@ its repository and invoking in command line:
|
||||||
```
|
```
|
||||||
sudo python -m tripleo_yum_config global --set-opts keepcache=1 cachedir="/var/cache/dnf"
|
sudo python -m tripleo_yum_config global --set-opts keepcache=1 cachedir="/var/cache/dnf"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
* **enable-compose-repos**
|
||||||
|
|
||||||
|
This subcommand will enable a list os yum repos based on the metadata retrieved from the `compose-url`.
|
||||||
|
The *tripleo-yum-config* module will create new repo files at REPO_DIR_PATH and enable them.
|
||||||
|
Optionally, you can provide a path where your repo files live, specify the variants that should be created and which repos need to be disabled afterwards.
|
||||||
|
By default REPO_DIR_PATH is set to */etc/yum.repos.d/*.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
sudo python -m tripleo_yum_config enable-compose-repos --compose-url https://composes.centos.org/latest-CentOS-Stream-8/compose/ --release centos-stream-8 --disable-all-conflicting
|
||||||
|
```
|
||||||
|
|
||||||
#### Install using setup.py
|
#### Install using setup.py
|
||||||
|
|
||||||
Installation using python setup.py requires sudo, because the python source
|
Installation using python setup.py requires sudo, because the python source
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
- name: Converge
|
- name: Converge
|
||||||
hosts: all
|
hosts: all
|
||||||
tasks:
|
tasks:
|
||||||
|
- name: "Test yum_config repo config"
|
||||||
- name: "Test yum_config repo"
|
become: true
|
||||||
tripleo.repos.yum_config:
|
tripleo.repos.yum_config:
|
||||||
type: repo
|
type: repo
|
||||||
name: appstream
|
name: appstream
|
||||||
|
@ -13,12 +13,41 @@
|
||||||
# the line below which disables molecule idemptotence test.
|
# the line below which disables molecule idemptotence test.
|
||||||
- molecule-idempotence-notest
|
- molecule-idempotence-notest
|
||||||
|
|
||||||
|
- name: "Test yum_config global config"
|
||||||
|
become: true
|
||||||
|
tripleo.repos.yum_config:
|
||||||
|
type: global
|
||||||
|
file_path: /etc/dnf/dnf.conf
|
||||||
|
set_options:
|
||||||
|
skip_if_unavailable: "False"
|
||||||
|
keepcache: "0"
|
||||||
|
tags:
|
||||||
|
# TODO: fix yum_config to correctly report changed state and uncomment
|
||||||
|
# the line below which disables molecule idemptotence test.
|
||||||
|
- molecule-idempotence-notest
|
||||||
|
|
||||||
- name: "Check get_hash"
|
- name: "Check get_hash"
|
||||||
tripleo.repos.get_hash:
|
tripleo.repos.get_hash:
|
||||||
release: master
|
release: master
|
||||||
|
|
||||||
- name: "Check get_hash with invalid url"
|
- name: "Check get_hash with invalid url"
|
||||||
tripleo.repos.get_hash:
|
tripleo.repos.get_hash:
|
||||||
release: master
|
release: master
|
||||||
dlrn_url: 'https://httpbin.org/status/404'
|
dlrn_url: 'https://httpbin.org/status/404'
|
||||||
register: result
|
register: result
|
||||||
failed_when: result is success
|
failed_when: result is success
|
||||||
|
|
||||||
|
- name: "Test yum_config enable-compose-repos"
|
||||||
|
become: true
|
||||||
|
tripleo.repos.yum_config:
|
||||||
|
type: enable-compose-repos
|
||||||
|
compose_url: https://composes.centos.org/latest-CentOS-Stream-8/compose/
|
||||||
|
centos_release: centos-stream-8
|
||||||
|
variants:
|
||||||
|
- AppStream
|
||||||
|
- BaseOS
|
||||||
|
disable_repos:
|
||||||
|
- /etc/yum.repos.d/CentOS-Stream-AppStream.repo
|
||||||
|
- /etc/yum.repos.d/CentOS-Stream-BaseOS.repo
|
||||||
|
tags:
|
||||||
|
- molecule-idempotence-notest
|
||||||
|
|
|
@ -17,8 +17,10 @@ import argparse
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import tripleo_repos.yum_config.yum_config as cfg
|
import tripleo_repos.yum_config.compose_repos as compose_repos
|
||||||
|
import tripleo_repos.yum_config.constants as const
|
||||||
import tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
import tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
||||||
|
import tripleo_repos.yum_config.yum_config as cfg
|
||||||
|
|
||||||
|
|
||||||
def options_to_dict(options):
|
def options_to_dict(options):
|
||||||
|
@ -63,8 +65,9 @@ def main():
|
||||||
repo_args_parser.add_argument(
|
repo_args_parser.add_argument(
|
||||||
'--config-dir-path',
|
'--config-dir-path',
|
||||||
dest='config_dir_path',
|
dest='config_dir_path',
|
||||||
|
default=const.YUM_REPO_DIR,
|
||||||
help=(
|
help=(
|
||||||
'set the absolute directory path that holds all repo or module '
|
'set the absolute directory path that holds all repo '
|
||||||
'configuration files')
|
'configuration files')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -98,13 +101,61 @@ def main():
|
||||||
help="sets module profile"
|
help="sets module profile"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Compose repo arguments
|
||||||
|
compose_args_parser = argparse.ArgumentParser(add_help=False)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--compose-url',
|
||||||
|
dest='compose_url',
|
||||||
|
required=True,
|
||||||
|
help='CentOS compose URL'
|
||||||
|
)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--release',
|
||||||
|
dest='release',
|
||||||
|
choices=const.COMPOSE_REPOS_RELEASES,
|
||||||
|
default='centos-stream-8',
|
||||||
|
help='target CentOS release.'
|
||||||
|
)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--arch',
|
||||||
|
choices=const.COMPOSE_REPOS_SUPPORTED_ARCHS,
|
||||||
|
default='x86_64',
|
||||||
|
help='set the architecture for the destination repos.'
|
||||||
|
)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--disable-repos',
|
||||||
|
nargs='+',
|
||||||
|
help='list of repo names or repo absolute file paths to be disabled.'
|
||||||
|
)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--disable-all-conflicting',
|
||||||
|
action='store_true',
|
||||||
|
dest='disable_conflicting',
|
||||||
|
default=False,
|
||||||
|
help='after enabling compose repos, disable all other repos that '
|
||||||
|
'match variant names.'
|
||||||
|
)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--variants',
|
||||||
|
nargs='+',
|
||||||
|
help='Name of the repos to be enabled. Default behavior is to enable '
|
||||||
|
'all that match a specific release and architecture.'
|
||||||
|
)
|
||||||
|
compose_args_parser.add_argument(
|
||||||
|
'--config-dir-path',
|
||||||
|
dest='config_dir_path',
|
||||||
|
default=const.YUM_REPO_DIR,
|
||||||
|
help='set the absolute directory path that holds all repo '
|
||||||
|
'configuration files'
|
||||||
|
)
|
||||||
|
|
||||||
# Common file path argument
|
# Common file path argument
|
||||||
common_parse = argparse.ArgumentParser(add_help=False)
|
common_parse = argparse.ArgumentParser(add_help=False)
|
||||||
common_parse.add_argument(
|
common_parse.add_argument(
|
||||||
'--config-file-path',
|
'--config-file-path',
|
||||||
dest='config_file_path',
|
dest='config_file_path',
|
||||||
help=('set the absolute file path of the configuration file to be '
|
help=('set the absolute file path of the configuration file to be '
|
||||||
'updated')
|
'updated.')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Main parser
|
# Main parser
|
||||||
|
@ -133,6 +184,11 @@ def main():
|
||||||
parents=[common_parse, options_parse],
|
parents=[common_parse, options_parse],
|
||||||
help='updates global yum configuration options'
|
help='updates global yum configuration options'
|
||||||
)
|
)
|
||||||
|
subparsers.add_parser(
|
||||||
|
'enable-compose-repos',
|
||||||
|
parents=[compose_args_parser],
|
||||||
|
help='enable CentOS compose repos based on an compose url.'
|
||||||
|
)
|
||||||
|
|
||||||
args = main_parser.parse_args()
|
args = main_parser.parse_args()
|
||||||
if args.command is None:
|
if args.command is None:
|
||||||
|
@ -146,10 +202,11 @@ def main():
|
||||||
if args.command == 'repo':
|
if args.command == 'repo':
|
||||||
set_dict = options_to_dict(args.set_opts)
|
set_dict = options_to_dict(args.set_opts)
|
||||||
config_obj = cfg.TripleOYumRepoConfig(
|
config_obj = cfg.TripleOYumRepoConfig(
|
||||||
file_path=args.config_file_path,
|
|
||||||
dir_path=args.config_dir_path)
|
dir_path=args.config_dir_path)
|
||||||
|
|
||||||
config_obj.update_section(args.name, set_dict, enable=args.enable)
|
config_obj.update_section(args.name, set_dict,
|
||||||
|
file_path=args.config_file_path,
|
||||||
|
enabled=args.enable)
|
||||||
|
|
||||||
elif args.command == 'module':
|
elif args.command == 'module':
|
||||||
dnf_mod_mgr = dnf_mgr.DnfModuleManager()
|
dnf_mod_mgr = dnf_mgr.DnfModuleManager()
|
||||||
|
@ -163,6 +220,19 @@ def main():
|
||||||
|
|
||||||
config_obj.update_section('main', set_dict)
|
config_obj.update_section('main', set_dict)
|
||||||
|
|
||||||
|
elif args.command == 'enable-compose-repos':
|
||||||
|
repo_obj = compose_repos.TripleOYumComposeRepoConfig(
|
||||||
|
args.compose_url,
|
||||||
|
args.release,
|
||||||
|
dir_path=args.config_dir_path,
|
||||||
|
arch=args.arch)
|
||||||
|
|
||||||
|
repo_obj.enable_compose_repos(variants=args.variants,
|
||||||
|
override_repos=args.disable_conflicting)
|
||||||
|
if args.disable_repos:
|
||||||
|
for file in args.disable_repos:
|
||||||
|
repo_obj.update_all_sections(file, enabled=False)
|
||||||
|
|
||||||
|
|
||||||
def cli_entrypoint():
|
def cli_entrypoint():
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
# Copyright 2021 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
from .constants import (
|
||||||
|
YUM_REPO_DIR,
|
||||||
|
YUM_REPO_FILE_EXTENSION,
|
||||||
|
YUM_REPO_SUPPORTED_OPTIONS,
|
||||||
|
COMPOSE_REPOS_RELEASES,
|
||||||
|
COMPOSE_REPOS_INFO_PATH,
|
||||||
|
COMPOSE_REPOS_URL_PATTERN,
|
||||||
|
COMPOSE_REPOS_URL_REPLACE_STR,
|
||||||
|
)
|
||||||
|
from .exceptions import (
|
||||||
|
TripleOYumConfigInvalidSection,
|
||||||
|
TripleOYumConfigComposeError,
|
||||||
|
)
|
||||||
|
from .yum_config import (
|
||||||
|
TripleOYumConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
class TripleOYumComposeRepoConfig(TripleOYumConfig):
|
||||||
|
"""Manages yum repo configuration files for CentOS Compose."""
|
||||||
|
|
||||||
|
def __init__(self, compose_url, release, dir_path=None, arch=None):
|
||||||
|
conf_dir_path = dir_path or YUM_REPO_DIR
|
||||||
|
self.arch = arch or 'x86_64'
|
||||||
|
|
||||||
|
# 1. validate release name
|
||||||
|
if release not in COMPOSE_REPOS_RELEASES:
|
||||||
|
msg = 'CentOS release not supported.'
|
||||||
|
raise TripleOYumConfigComposeError(error_msg=msg)
|
||||||
|
self.release = release
|
||||||
|
|
||||||
|
# 2. Validate URL
|
||||||
|
pattern = re.compile(COMPOSE_REPOS_URL_PATTERN[self.release])
|
||||||
|
if not pattern.match(compose_url):
|
||||||
|
msg = 'The provided URL does not match the expect pattern.'
|
||||||
|
raise TripleOYumConfigComposeError(error_msg=msg)
|
||||||
|
|
||||||
|
# 3. Get compose info from url
|
||||||
|
segments = [compose_url,
|
||||||
|
COMPOSE_REPOS_INFO_PATH[self.release]]
|
||||||
|
self.compose_info_url = '/'.join(s.strip('/') for s in segments)
|
||||||
|
self.compose_info = self._get_compose_info()
|
||||||
|
|
||||||
|
# 4. Get compose-id from metadata
|
||||||
|
self.compose_id = self.compose_info['compose']['id']
|
||||||
|
|
||||||
|
# 5. Replace the compose-id from url to avoid 'labels'
|
||||||
|
repl_args = {'compose_id': self.compose_id}
|
||||||
|
self.compose_url = (
|
||||||
|
pattern.sub(
|
||||||
|
COMPOSE_REPOS_URL_REPLACE_STR[self.release] % repl_args,
|
||||||
|
compose_url)
|
||||||
|
)
|
||||||
|
|
||||||
|
super(TripleOYumComposeRepoConfig, self).__init__(
|
||||||
|
valid_options=YUM_REPO_SUPPORTED_OPTIONS,
|
||||||
|
dir_path=conf_dir_path,
|
||||||
|
file_extension=YUM_REPO_FILE_EXTENSION)
|
||||||
|
|
||||||
|
def _get_compose_info(self):
|
||||||
|
"""Retrieve compose info for a provided compose-id url."""
|
||||||
|
# NOTE(dviroel): works for both centos 8 and 9
|
||||||
|
try:
|
||||||
|
logging.debug("Retrieving compose info from url: %s",
|
||||||
|
self.compose_info_url)
|
||||||
|
res = urllib.request.urlopen(self.compose_info_url)
|
||||||
|
except Exception:
|
||||||
|
msg = ("Failed to retrieve compose info from url: %s"
|
||||||
|
% self.compose_info_url)
|
||||||
|
raise TripleOYumConfigComposeError(error_msg=msg)
|
||||||
|
compose_info = json.loads(res.read())
|
||||||
|
if compose_info['header']['version'] != "1.2":
|
||||||
|
# NOTE(dviroel): Log a warning just in case we receive a different
|
||||||
|
# version here. Code may fail depending on the change.
|
||||||
|
logging.warning("Expecting compose info version '1.2' but got %s.",
|
||||||
|
compose_info['header']['version'])
|
||||||
|
return compose_info['payload']
|
||||||
|
|
||||||
|
def _get_repo_name(self, variant):
|
||||||
|
return " ".join([self.compose_id, variant])
|
||||||
|
|
||||||
|
def _get_repo_filename(self, variant):
|
||||||
|
return "-".join([self.compose_id, variant]) + '.repo'
|
||||||
|
|
||||||
|
def _get_repo_base_url(self, variant):
|
||||||
|
"""Build the base_url based on variant name and system architecture."""
|
||||||
|
variant_info = self.compose_info['variants'][variant]
|
||||||
|
if not variant_info['paths'].get('repository', {}).get(self.arch):
|
||||||
|
# Variant has no support yet
|
||||||
|
return None
|
||||||
|
segments = [self.compose_url,
|
||||||
|
variant_info['paths']['repository'][self.arch]]
|
||||||
|
return '/'.join(s.strip('/') for s in segments)
|
||||||
|
|
||||||
|
def get_compose_variants(self):
|
||||||
|
return self.compose_info['variants'].keys()
|
||||||
|
|
||||||
|
def enable_compose_repos(self, variants=None, override_repos=False):
|
||||||
|
"""Enable CentOS compose repos of a given variant list.
|
||||||
|
|
||||||
|
This function will build from scratch all repos for a given compose-id
|
||||||
|
url. If a list of variants is not provided, it will enable all for all
|
||||||
|
variants returned from compose info.
|
||||||
|
|
||||||
|
:param variants: A list of variant names to be enabled.
|
||||||
|
:param override_repos: True if all matching variants in the same
|
||||||
|
repo directory should be disable in favor of the new repos.
|
||||||
|
"""
|
||||||
|
if variants:
|
||||||
|
for var in variants:
|
||||||
|
if not (var in self.compose_info['variants'].keys()):
|
||||||
|
msg = 'One or more provided variants are invalid.'
|
||||||
|
raise TripleOYumConfigComposeError(error_msg=msg)
|
||||||
|
|
||||||
|
else:
|
||||||
|
variants = self.compose_info['variants'].keys()
|
||||||
|
|
||||||
|
updated_repos = {}
|
||||||
|
for var in variants:
|
||||||
|
base_url = self._get_repo_base_url(var)
|
||||||
|
if not base_url:
|
||||||
|
continue
|
||||||
|
add_dict = {
|
||||||
|
'name': self._get_repo_name(var),
|
||||||
|
'baseurl': base_url,
|
||||||
|
'enabled': '1',
|
||||||
|
'gpgcheck': '0',
|
||||||
|
}
|
||||||
|
filename = self._get_repo_filename(var)
|
||||||
|
file_path = os.path.join(self.dir_path, filename)
|
||||||
|
# create a file if doesn't exist and add a section to it
|
||||||
|
try:
|
||||||
|
self.add_section(var.lower(), add_dict, file_path)
|
||||||
|
except TripleOYumConfigInvalidSection:
|
||||||
|
logging.debug("Section '%s' that already exists in this file. "
|
||||||
|
"Skipping...", var)
|
||||||
|
# needed to override other repos
|
||||||
|
updated_repos[var.lower()] = file_path
|
||||||
|
|
||||||
|
if override_repos:
|
||||||
|
for var in updated_repos:
|
||||||
|
config_files = self._get_config_files(var)
|
||||||
|
for file in config_files:
|
||||||
|
if file != updated_repos[var]:
|
||||||
|
msg = ("Disabling matching section '%(section)s' in "
|
||||||
|
"configuration file: %(file)s.")
|
||||||
|
msg_args = {
|
||||||
|
'section': var,
|
||||||
|
'file': file,
|
||||||
|
}
|
||||||
|
logging.debug(msg, msg_args)
|
||||||
|
self.update_section(var, enabled=False, file_path=file)
|
||||||
|
|
||||||
|
def add_section(self, section, add_dict, file_path):
|
||||||
|
# Create a new file if it does not exists
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
with open(file_path, '+w'):
|
||||||
|
pass
|
||||||
|
super(TripleOYumComposeRepoConfig, self).add_section(
|
||||||
|
section, add_dict, file_path)
|
||||||
|
|
||||||
|
def update_section(
|
||||||
|
self, section, set_dict=None, enabled=None, file_path=None):
|
||||||
|
update_dict = set_dict or {}
|
||||||
|
if enabled is not None:
|
||||||
|
update_dict['enabled'] = '1' if enabled else '0'
|
||||||
|
if update_dict:
|
||||||
|
super(TripleOYumComposeRepoConfig, self).update_section(
|
||||||
|
section, update_dict, file_path=file_path)
|
||||||
|
|
||||||
|
def update_all_sections(self, file_path, set_dict=None, enabled=None):
|
||||||
|
update_dict = set_dict or {}
|
||||||
|
if enabled is not None:
|
||||||
|
update_dict['enabled'] = '1' if enabled else '0'
|
||||||
|
if update_dict:
|
||||||
|
super(TripleOYumComposeRepoConfig, self).update_all_sections(
|
||||||
|
update_dict, file_path)
|
|
@ -39,3 +39,32 @@ YUM_REPO_FILE_EXTENSION = '.repo'
|
||||||
Default constants for yum/dnf global configurations.
|
Default constants for yum/dnf global configurations.
|
||||||
"""
|
"""
|
||||||
YUM_GLOBAL_CONFIG_FILE_PATH = '/etc/yum.conf'
|
YUM_GLOBAL_CONFIG_FILE_PATH = '/etc/yum.conf'
|
||||||
|
|
||||||
|
"""
|
||||||
|
CentOS Stream compose repos defaults
|
||||||
|
"""
|
||||||
|
COMPOSE_REPOS_RELEASES = [
|
||||||
|
"centos-stream-8",
|
||||||
|
"centos-stream-9"
|
||||||
|
]
|
||||||
|
|
||||||
|
COMPOSE_REPOS_SUPPORTED_ARCHS = [
|
||||||
|
"aarch64",
|
||||||
|
"ppc64le",
|
||||||
|
"x86_64"
|
||||||
|
]
|
||||||
|
|
||||||
|
COMPOSE_REPOS_URL_PATTERN = {
|
||||||
|
"centos-stream-8": r"(^https:.*.centos.org/)([^/]*)(/compose/?$)",
|
||||||
|
"centos-stream-9": r"(^https:.*.centos.org/.*/)(.*)(/compose/?$)",
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPOSE_REPOS_URL_REPLACE_STR = {
|
||||||
|
"centos-stream-8": r"\1%(compose_id)s\3",
|
||||||
|
"centos-stream-9": r"\1%(compose_id)s\3",
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPOSE_REPOS_INFO_PATH = {
|
||||||
|
"centos-stream-8": "metadata/composeinfo.json",
|
||||||
|
"centos-stream-9": "metadata/composeinfo.json",
|
||||||
|
}
|
||||||
|
|
|
@ -60,3 +60,10 @@ class TripleOYumConfigInvalidOption(Base):
|
||||||
|
|
||||||
def __init__(self, error_msg):
|
def __init__(self, error_msg):
|
||||||
super(TripleOYumConfigInvalidOption, self).__init__(error_msg)
|
super(TripleOYumConfigInvalidOption, self).__init__(error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
class TripleOYumConfigComposeError(Base):
|
||||||
|
"""An error occurred while configuring CentOS compose repos."""
|
||||||
|
|
||||||
|
def __init__(self, error_msg):
|
||||||
|
super(TripleOYumConfigComposeError, self).__init__(error_msg)
|
||||||
|
|
|
@ -31,13 +31,18 @@ from .exceptions import (
|
||||||
TripleOYumConfigInvalidOption,
|
TripleOYumConfigInvalidOption,
|
||||||
TripleOYumConfigInvalidSection,
|
TripleOYumConfigInvalidSection,
|
||||||
TripleOYumConfigNotFound,
|
TripleOYumConfigNotFound,
|
||||||
TripleOYumConfigPermissionDenied,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
def validated_file_path(file_path):
|
||||||
|
if os.path.isfile(file_path) and os.access(file_path, os.W_OK):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class TripleOYumConfig:
|
class TripleOYumConfig:
|
||||||
"""
|
"""
|
||||||
This class is a base class for updating yum configuration files in
|
This class is a base class for updating yum configuration files in
|
||||||
|
@ -74,171 +79,230 @@ class TripleOYumConfig:
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
def __init__(self, valid_options=None, file_path=None, dir_path=None,
|
def __init__(self, valid_options=None, dir_path=None, file_extension=None):
|
||||||
file_extension=None):
|
|
||||||
"""
|
"""
|
||||||
Creates a TripleOYumConfig object that holds configuration file
|
Creates a TripleOYumConfig object that holds configuration file
|
||||||
information.
|
information.
|
||||||
|
|
||||||
:param valid_options: A list of options that can be updated on this
|
:param valid_options: A list of options that can be updated on this
|
||||||
file.
|
file.
|
||||||
:param file_path: The file path to configuration file to be updated.
|
|
||||||
:param dir_path: The directory path that this class can use to search
|
:param dir_path: The directory path that this class can use to search
|
||||||
for configuration files to be updated.
|
for configuration files to be updated.
|
||||||
:param: file_extension: File extension to filter configuration files
|
:param: file_extension: File extension to filter configuration files
|
||||||
in the search directory.
|
in the search directory.
|
||||||
"""
|
"""
|
||||||
self.config_file_path = file_path
|
|
||||||
self.dir_path = dir_path
|
self.dir_path = dir_path
|
||||||
self.file_extension = file_extension
|
self.file_extension = file_extension
|
||||||
self.valid_options = valid_options
|
self.valid_options = valid_options
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if not (file_path or dir_path):
|
|
||||||
msg = ('A configuration file path or a directory path must be '
|
|
||||||
'provided.')
|
|
||||||
raise TripleOYumConfigNotFound(error_msg=msg)
|
|
||||||
|
|
||||||
if file_path:
|
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
msg = ('The configuration file "{0}" was not found in the '
|
|
||||||
'provided path.').format(file_path)
|
|
||||||
raise TripleOYumConfigNotFound(error_msg=msg)
|
|
||||||
if not os.access(file_path, os.W_OK):
|
|
||||||
msg = ('The configuration file {0} is not '
|
|
||||||
'writable.'.format(file_path))
|
|
||||||
raise TripleOYumConfigPermissionDenied(error_msg=msg)
|
|
||||||
|
|
||||||
if dir_path:
|
if dir_path:
|
||||||
if not os.path.isdir(dir_path):
|
if not os.path.isdir(dir_path):
|
||||||
msg = ('The configuration dir "{0}" was not found in the '
|
msg = ('The configuration dir "{0}" was not found in the '
|
||||||
'provided path.').format(dir_path)
|
'provided path.').format(dir_path)
|
||||||
raise TripleOYumConfigNotFound(error_msg=msg)
|
raise TripleOYumConfigNotFound(error_msg=msg)
|
||||||
|
|
||||||
def _read_config_file(self, section):
|
def _read_config_file(self, file_path, section=None):
|
||||||
"""Read the configuration file associate with this object.
|
"""Reads a configuration file.
|
||||||
|
|
||||||
If no configuration file is provided, this method will search for
|
:param section: The name of the section that will be update. Only used
|
||||||
'section' name in all files inside the configuration directory. The
|
to fail earlier if the section is not found.
|
||||||
first occurrence of 'section' will be returned, and a warning will be
|
:return: a config parser object and the full file path.
|
||||||
logged if more than one configuration file has the same 'section' set.
|
|
||||||
|
|
||||||
:param section: The name of the section to be updated.
|
|
||||||
:return: a config parser object and the file path.
|
|
||||||
"""
|
"""
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
# A) A configuration file path was provided.
|
file_paths = [file_path]
|
||||||
if self.config_file_path:
|
if self.dir_path:
|
||||||
try:
|
# if dir_path is configured, we can search for filename there
|
||||||
config.read(self.config_file_path)
|
file_paths.append(os.path.join(self.dir_path, file_path))
|
||||||
except configparser.Error:
|
|
||||||
msg = 'Unable to parse configuration file {0}.'.format(
|
|
||||||
self.config_file_path)
|
|
||||||
raise TripleOYumConfigFileParseError(error_msg=msg)
|
|
||||||
|
|
||||||
if section not in config.sections():
|
valid_file_path = None
|
||||||
msg = ('The provided section "{0}" was not found in the '
|
for file in file_paths:
|
||||||
'configuration file {1}.').format(
|
if validated_file_path(file):
|
||||||
section, self.config_file_path)
|
valid_file_path = file
|
||||||
raise TripleOYumConfigInvalidSection(error_msg=msg)
|
break
|
||||||
|
if not valid_file_path:
|
||||||
|
msg = ('The configuration file "{0}" was '
|
||||||
|
'not found.'.format(file_path))
|
||||||
|
raise TripleOYumConfigNotFound(error_msg=msg)
|
||||||
|
|
||||||
return config, self.config_file_path
|
try:
|
||||||
|
config.read(valid_file_path)
|
||||||
|
except configparser.Error:
|
||||||
|
msg = 'Unable to parse configuration file {0}.'.format(
|
||||||
|
valid_file_path)
|
||||||
|
raise TripleOYumConfigFileParseError(error_msg=msg)
|
||||||
|
|
||||||
# B) Search for a configuration file that has the provided section
|
if section and section not in config.sections():
|
||||||
section_found = False
|
msg = ('The provided section "{0}" was not found in the '
|
||||||
config_file_path = None
|
'configuration file {1}.').format(
|
||||||
for file in os.listdir(self.dir_path):
|
section, valid_file_path)
|
||||||
# Skip files that don't match the file extension or are not
|
raise TripleOYumConfigInvalidSection(error_msg=msg)
|
||||||
# writable
|
|
||||||
if self.file_extension and not file.endswith(
|
|
||||||
self.file_extension):
|
|
||||||
continue
|
|
||||||
if not os.access(os.path.join(self.dir_path, file), os.W_OK):
|
|
||||||
continue
|
|
||||||
|
|
||||||
tmp_config = configparser.ConfigParser()
|
return config, valid_file_path
|
||||||
try:
|
|
||||||
tmp_config.read(os.path.join(self.dir_path, file))
|
|
||||||
except configparser.Error:
|
|
||||||
continue
|
|
||||||
if section in tmp_config.sections():
|
|
||||||
if section_found:
|
|
||||||
logging.warning('Section "%s" is listed more than once in '
|
|
||||||
'configuration files.', section)
|
|
||||||
else:
|
|
||||||
# Read the first occurrence of 'section'
|
|
||||||
config_file_path = os.path.join(self.dir_path, file)
|
|
||||||
config.read(config_file_path)
|
|
||||||
section_found = True
|
|
||||||
|
|
||||||
return config, config_file_path
|
def _get_config_files(self, section):
|
||||||
|
"""Gets all configuration file paths for a given section.
|
||||||
|
|
||||||
def update_section(self, section, set_dict):
|
This method will search for a 'section' name in all files inside the
|
||||||
"""Updates a set of options for a specified section.
|
configuration directory. All files with 'section' will be returned.
|
||||||
|
|
||||||
|
:param section: Section to be found inside configuration files.
|
||||||
|
:return: A list of config file paths.
|
||||||
|
"""
|
||||||
|
# Search for a configuration file that has the provided section
|
||||||
|
config_files_path = []
|
||||||
|
if section and self.dir_path:
|
||||||
|
for file in os.listdir(self.dir_path):
|
||||||
|
# Skip files that don't match the file extension or are not
|
||||||
|
# writable
|
||||||
|
if self.file_extension and not file.endswith(
|
||||||
|
self.file_extension):
|
||||||
|
continue
|
||||||
|
if not os.access(os.path.join(self.dir_path, file), os.W_OK):
|
||||||
|
continue
|
||||||
|
|
||||||
|
tmp_config = configparser.ConfigParser()
|
||||||
|
try:
|
||||||
|
tmp_config.read(os.path.join(self.dir_path, file))
|
||||||
|
except configparser.Error:
|
||||||
|
continue
|
||||||
|
if section in tmp_config.sections():
|
||||||
|
config_files_path.append(os.path.join(self.dir_path, file))
|
||||||
|
|
||||||
|
return config_files_path
|
||||||
|
|
||||||
|
def update_section(self, section, set_dict, file_path=None):
|
||||||
|
"""Updates a set of options of a section.
|
||||||
|
|
||||||
|
If a file path is not provided by the caller, this function will search
|
||||||
|
for the section in all files located in the working directory and
|
||||||
|
update each one of them.
|
||||||
|
|
||||||
:param section: Name of the section on the configuration file that will
|
:param section: Name of the section on the configuration file that will
|
||||||
be updated.
|
be updated.
|
||||||
:param set_dict: Dict with all options and values to be updated in the
|
:param set_dict: Dict with all options and values to be updated in the
|
||||||
configuration file section.
|
configuration file section.
|
||||||
|
:param file_path: Path to the configuration file to be updated.
|
||||||
"""
|
"""
|
||||||
if self.valid_options:
|
if self.valid_options:
|
||||||
if not all(key in self.valid_options for key in set_dict.keys()):
|
if not all(key in self.valid_options for key in set_dict.keys()):
|
||||||
msg = 'One or more provided options are not valid.'
|
msg = 'One or more provided options are not valid.'
|
||||||
raise TripleOYumConfigInvalidOption(error_msg=msg)
|
raise TripleOYumConfigInvalidOption(error_msg=msg)
|
||||||
|
|
||||||
config, config_file_path = self._read_config_file(section)
|
files = [file_path] if file_path else self._get_config_files(section)
|
||||||
if not (config and config_file_path):
|
if not files:
|
||||||
msg = ('The provided section "{0}" was not found within any '
|
msg = ('No configuration files were found for the provided '
|
||||||
'configuration file.').format(section)
|
'section {0}'.format(section))
|
||||||
raise TripleOYumConfigNotFound(error_msg=msg)
|
raise TripleOYumConfigNotFound(error_msg=msg)
|
||||||
|
|
||||||
# Update configuration file with dict updates
|
for file in files:
|
||||||
config[section].update(set_dict)
|
config, file = self._read_config_file(file, section=section)
|
||||||
|
# Update configuration file with dict updates
|
||||||
|
config[section].update(set_dict)
|
||||||
|
with open(file, 'w') as f:
|
||||||
|
config.write(f)
|
||||||
|
|
||||||
with open(config_file_path, 'w') as file:
|
logging.info("Section '%s' was successfully "
|
||||||
|
"updated.", section)
|
||||||
|
|
||||||
|
def add_section(self, section, add_dict, file_path):
|
||||||
|
""" Adds a new section with options in a provided config file.
|
||||||
|
|
||||||
|
:param section: Section name to be added to the config file.
|
||||||
|
:param add_dict: Dict with all options and values to be added into the
|
||||||
|
new section.
|
||||||
|
:param file_path: Path to the configuration file to be updated.
|
||||||
|
"""
|
||||||
|
# This section shouldn't exist in the provided file
|
||||||
|
config, file_path = self._read_config_file(file_path=file_path)
|
||||||
|
if section in config.sections():
|
||||||
|
msg = ("Section '%s' already exists in the configuration "
|
||||||
|
"file.", section)
|
||||||
|
raise TripleOYumConfigInvalidSection(error_msg=msg)
|
||||||
|
|
||||||
|
# Add new section
|
||||||
|
config.add_section(section)
|
||||||
|
# Update configuration file with dict updates
|
||||||
|
config[section].update(add_dict)
|
||||||
|
|
||||||
|
with open(file_path, '+w') as file:
|
||||||
config.write(file)
|
config.write(file)
|
||||||
|
|
||||||
logging.info("Section '%s' was successfully updated.", section)
|
logging.info("Section '%s' was successfully "
|
||||||
|
"added.", section)
|
||||||
|
|
||||||
|
def update_all_sections(self, set_dict, file_path):
|
||||||
|
"""Updates all section of a given configuration file.
|
||||||
|
|
||||||
|
:param set_dict: Dict with all options and values to be updated in
|
||||||
|
the configuration file.
|
||||||
|
:param file_path: Path to the configuration file to be updated.
|
||||||
|
"""
|
||||||
|
if self.valid_options:
|
||||||
|
if not all(key in self.valid_options for key in set_dict.keys()):
|
||||||
|
msg = 'One or more provided options are not valid.'
|
||||||
|
raise TripleOYumConfigInvalidOption(error_msg=msg)
|
||||||
|
|
||||||
|
config, file_path = self._read_config_file(file_path)
|
||||||
|
for section in config.sections():
|
||||||
|
config[section].update(set_dict)
|
||||||
|
|
||||||
|
with open(file_path, '+w') as file:
|
||||||
|
config.write(file)
|
||||||
|
|
||||||
|
logging.info("All sections for '%s' were successfully "
|
||||||
|
"updated.", file_path)
|
||||||
|
|
||||||
|
|
||||||
class TripleOYumRepoConfig(TripleOYumConfig):
|
class TripleOYumRepoConfig(TripleOYumConfig):
|
||||||
"""Manages yum repo configuration files."""
|
"""Manages yum repo configuration files."""
|
||||||
|
|
||||||
def __init__(self, file_path=None, dir_path=None):
|
def __init__(self, dir_path=None):
|
||||||
if file_path:
|
|
||||||
logging.info(
|
|
||||||
"Using '%s' as yum repo configuration file.", file_path)
|
|
||||||
conf_dir_path = dir_path or YUM_REPO_DIR
|
conf_dir_path = dir_path or YUM_REPO_DIR
|
||||||
|
|
||||||
super(TripleOYumRepoConfig, self).__init__(
|
super(TripleOYumRepoConfig, self).__init__(
|
||||||
valid_options=YUM_REPO_SUPPORTED_OPTIONS,
|
valid_options=YUM_REPO_SUPPORTED_OPTIONS,
|
||||||
file_path=file_path,
|
|
||||||
dir_path=conf_dir_path,
|
dir_path=conf_dir_path,
|
||||||
file_extension=YUM_REPO_FILE_EXTENSION)
|
file_extension=YUM_REPO_FILE_EXTENSION)
|
||||||
|
|
||||||
def update_section(self, section, set_dict, enable=None):
|
def update_section(
|
||||||
if enable is not None:
|
self, section, set_dict=None, file_path=None, enabled=None):
|
||||||
set_dict['enabled'] = '1' if enable else '0'
|
update_dict = set_dict or {}
|
||||||
|
if enabled is not None:
|
||||||
super(TripleOYumRepoConfig, self).update_section(section, set_dict)
|
update_dict['enabled'] = '1' if enabled else '0'
|
||||||
|
if update_dict:
|
||||||
|
super(TripleOYumRepoConfig, self).update_section(
|
||||||
|
section, update_dict, file_path=file_path)
|
||||||
|
|
||||||
|
|
||||||
class TripleOYumGlobalConfig(TripleOYumConfig):
|
class TripleOYumGlobalConfig(TripleOYumConfig):
|
||||||
"""Manages yum global configuration file."""
|
"""Manages yum global configuration file."""
|
||||||
|
|
||||||
def __init__(self, file_path=None):
|
def __init__(self, file_path=None):
|
||||||
conf_file_path = file_path or YUM_GLOBAL_CONFIG_FILE_PATH
|
self.conf_file_path = file_path or YUM_GLOBAL_CONFIG_FILE_PATH
|
||||||
logging.info("Using '%s' as yum global configuration "
|
logging.info("Using '%s' as yum global configuration "
|
||||||
"file.", conf_file_path)
|
"file.", self.conf_file_path)
|
||||||
if file_path is None:
|
if file_path is not None:
|
||||||
|
# validate user provided file path
|
||||||
|
validated_file_path(file_path)
|
||||||
|
else:
|
||||||
# If there is no default 'yum.conf' configuration file, we need to
|
# If there is no default 'yum.conf' configuration file, we need to
|
||||||
# create it. If the user specify another conf file that doesn't
|
# create it. If the user specify another conf file that doesn't
|
||||||
# exists, the operation will fail.
|
# exists, the operation will fail.
|
||||||
if not os.path.isfile(conf_file_path):
|
if not os.path.isfile(self.conf_file_path):
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(conf_file_path)
|
config.read(self.conf_file_path)
|
||||||
config.add_section('main')
|
config.add_section('main')
|
||||||
with open(conf_file_path, '+w') as file:
|
with open(self.conf_file_path, '+w') as file:
|
||||||
config.write(file)
|
config.write(file)
|
||||||
|
|
||||||
super(TripleOYumGlobalConfig, self).__init__(file_path=conf_file_path)
|
super(TripleOYumGlobalConfig, self).__init__()
|
||||||
|
|
||||||
|
def update_section(self, section, set_dict, file_path=None):
|
||||||
|
super(TripleOYumGlobalConfig, self).update_section(
|
||||||
|
section, set_dict, file_path=(file_path or self.conf_file_path))
|
||||||
|
|
||||||
|
def add_section(self, section, set_dict, file_path=None):
|
||||||
|
add_file_path = file_path or self.conf_file_path
|
||||||
|
super(TripleOYumGlobalConfig, self).add_section(
|
||||||
|
section, set_dict, add_file_path)
|
||||||
|
|
|
@ -24,7 +24,7 @@ options:
|
||||||
- The type of yum configuration to be changed.
|
- The type of yum configuration to be changed.
|
||||||
required: true
|
required: true
|
||||||
type: str
|
type: str
|
||||||
choices: [repo, module, global]
|
choices: [repo, module, global, 'enable-compose-repos']
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- Name of the repo or module to be changed. This options is
|
- Name of the repo or module to be changed. This options is
|
||||||
|
@ -65,6 +65,40 @@ options:
|
||||||
- Absolute path of the directory that contains the configuration
|
- Absolute path of the directory that contains the configuration
|
||||||
file to be changed.
|
file to be changed.
|
||||||
type: path
|
type: path
|
||||||
|
default: /etc/yum.repos.d
|
||||||
|
compose_url:
|
||||||
|
description:
|
||||||
|
- URL that contains CentOS compose repositories.
|
||||||
|
type: str
|
||||||
|
centos_release:
|
||||||
|
description:
|
||||||
|
- Target CentOS release.
|
||||||
|
type: str
|
||||||
|
choices: [centos-stream-8, centos-stream-9]
|
||||||
|
arch:
|
||||||
|
description:
|
||||||
|
- System architecture which the repos will be configure.
|
||||||
|
type: str
|
||||||
|
choices: [aarch64, ppc64le, x86_64]
|
||||||
|
default: x86_64
|
||||||
|
variants:
|
||||||
|
description:
|
||||||
|
- Repository variants that should be configured. If not provided,
|
||||||
|
all available variants will be configured.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
disable_conflicting_variants:
|
||||||
|
description:
|
||||||
|
- Disable all repos from the same directory that match variants'
|
||||||
|
name.
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
disable_repos:
|
||||||
|
description:
|
||||||
|
- List with file path of repos that should be disabled after
|
||||||
|
successfully enabling all compose repos.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
|
||||||
author:
|
author:
|
||||||
- Douglas Viroel (@viroel)
|
- Douglas Viroel (@viroel)
|
||||||
|
@ -113,6 +147,20 @@ EXAMPLES = r'''
|
||||||
set_options:
|
set_options:
|
||||||
skip_if_unavailable: "False"
|
skip_if_unavailable: "False"
|
||||||
keepcache: "0"
|
keepcache: "0"
|
||||||
|
|
||||||
|
- name: Configure a set of repos based on latest CentOS Stream 8 compose
|
||||||
|
become: true
|
||||||
|
become_user: root
|
||||||
|
tripleo_yup_config:
|
||||||
|
compose_url: https://composes.centos.org/latest-CentOS-Stream-8/compose/
|
||||||
|
centos_release: centos-stream-8
|
||||||
|
variants:
|
||||||
|
- AppStream
|
||||||
|
- BaseOS
|
||||||
|
disable_conflicting_variants: true
|
||||||
|
disable_repos:
|
||||||
|
- /etc/yum.repos.d/CentOS-Linux-AppStream.repo
|
||||||
|
- /etc/yum.repos.d/CentOS-Linux-BaseOS.repo
|
||||||
'''
|
'''
|
||||||
|
|
||||||
RETURN = r''' # '''
|
RETURN = r''' # '''
|
||||||
|
@ -121,8 +169,14 @@ from ansible.module_utils.basic import AnsibleModule # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
def run_module():
|
def run_module():
|
||||||
|
try:
|
||||||
|
import ansible_collections.tripleo.repos.plugins.module_utils. \
|
||||||
|
tripleo_repos.yum_config.constants as const
|
||||||
|
except ImportError:
|
||||||
|
import tripleo_repos.yum_config.constants as const
|
||||||
# define available arguments/parameters a user can pass to the module
|
# define available arguments/parameters a user can pass to the module
|
||||||
supported_config_types = ['repo', 'module', 'global']
|
supported_config_types = ['repo', 'module', 'global',
|
||||||
|
'enable-compose-repos']
|
||||||
supported_module_operations = ['install', 'remove', 'reset']
|
supported_module_operations = ['install', 'remove', 'reset']
|
||||||
module_args = dict(
|
module_args = dict(
|
||||||
type=dict(type='str', required=True, choices=supported_config_types),
|
type=dict(type='str', required=True, choices=supported_config_types),
|
||||||
|
@ -133,7 +187,17 @@ def run_module():
|
||||||
profile=dict(type='str'),
|
profile=dict(type='str'),
|
||||||
set_options=dict(type='dict', default={}),
|
set_options=dict(type='dict', default={}),
|
||||||
file_path=dict(type='path'),
|
file_path=dict(type='path'),
|
||||||
dir_path=dict(type='path'),
|
dir_path=dict(type='path', default=const.YUM_REPO_DIR),
|
||||||
|
compose_url=dict(type='str'),
|
||||||
|
centos_release=dict(type='str',
|
||||||
|
choices=const.COMPOSE_REPOS_RELEASES),
|
||||||
|
arch=dict(type='str', choices=const.COMPOSE_REPOS_SUPPORTED_ARCHS,
|
||||||
|
default='x86_64'),
|
||||||
|
variants=dict(type='list', default=[],
|
||||||
|
elements='str'),
|
||||||
|
disable_conflicting_variants=dict(type='bool', default=False),
|
||||||
|
disable_repos=dict(type='list', default=[],
|
||||||
|
elements='str'),
|
||||||
)
|
)
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
|
@ -141,6 +205,7 @@ def run_module():
|
||||||
required_if=[
|
required_if=[
|
||||||
["type", "repo", ["name"]],
|
["type", "repo", ["name"]],
|
||||||
["type", "module", ["name"]],
|
["type", "module", ["name"]],
|
||||||
|
["type", "enable-compose-repos", ["compose_url"]],
|
||||||
],
|
],
|
||||||
supports_check_mode=False
|
supports_check_mode=False
|
||||||
)
|
)
|
||||||
|
@ -162,18 +227,36 @@ def run_module():
|
||||||
tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
||||||
import ansible_collections.tripleo.repos.plugins.module_utils.\
|
import ansible_collections.tripleo.repos.plugins.module_utils.\
|
||||||
tripleo_repos.yum_config.yum_config as cfg
|
tripleo_repos.yum_config.yum_config as cfg
|
||||||
|
import ansible_collections.tripleo.repos.plugins.module_utils. \
|
||||||
|
tripleo_repos.yum_config.compose_repos as repos
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
import tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
||||||
import tripleo_repos.yum_config.yum_config as cfg
|
import tripleo_repos.yum_config.yum_config as cfg
|
||||||
|
import tripleo_repos.yum_config.compose_repos as repos
|
||||||
|
|
||||||
if module.params['type'] == 'repo':
|
if module.params['type'] == 'repo':
|
||||||
config_obj = cfg.TripleOYumRepoConfig(
|
config_obj = cfg.TripleOYumRepoConfig(
|
||||||
file_path=module.params['file_path'],
|
|
||||||
dir_path=module.params['dir_path'])
|
dir_path=module.params['dir_path'])
|
||||||
config_obj.update_section(
|
config_obj.update_section(
|
||||||
module.params['name'],
|
module.params['name'],
|
||||||
m_set_opts,
|
m_set_opts,
|
||||||
enable=module.params['enabled'])
|
file_path=module.params['file_path'],
|
||||||
|
enabled=module.params['enabled'])
|
||||||
|
|
||||||
|
elif module.params['type'] == 'enable-compose-repos':
|
||||||
|
# 1. Create compose repo config object
|
||||||
|
repo_obj = repos.TripleOYumComposeRepoConfig(
|
||||||
|
module.params['compose_url'],
|
||||||
|
module.params['centos_release'],
|
||||||
|
dir_path=module.params['dir_path'],
|
||||||
|
arch=module.params['arch'])
|
||||||
|
# 2. enable CentOS compose repos
|
||||||
|
repo_obj.enable_compose_repos(
|
||||||
|
variants=module.params['variants'],
|
||||||
|
override_repos=module.params['disable_conflicting_variants'])
|
||||||
|
# 3. Disable all repos provided in disable_repos
|
||||||
|
for file in module.params['disable_repos']:
|
||||||
|
repo_obj.update_all_sections(file, enabled=False)
|
||||||
|
|
||||||
elif module.params['type'] == 'module':
|
elif module.params['type'] == 'module':
|
||||||
dnf_mod_mgr = dnf_mgr.DnfModuleManager()
|
dnf_mod_mgr = dnf_mgr.DnfModuleManager()
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
plugins/module_utils/tripleo_repos/get_hash/tripleo_hash_info.py replace-urlopen
|
plugins/module_utils/tripleo_repos/get_hash/tripleo_hash_info.py replace-urlopen
|
||||||
plugins/module_utils/tripleo_repos/get_hash/tripleo_hash_info.py pylint:ansible-bad-import
|
plugins/module_utils/tripleo_repos/get_hash/tripleo_hash_info.py pylint:ansible-bad-import
|
||||||
|
plugins/module_utils/tripleo_repos/yum_config/compose_repos.py replace-urlopen
|
||||||
|
|
|
@ -20,8 +20,9 @@ from unittest import mock
|
||||||
from . import fakes
|
from . import fakes
|
||||||
from . import mock_modules # noqa: F401
|
from . import mock_modules # noqa: F401
|
||||||
import tripleo_repos.yum_config.__main__ as main
|
import tripleo_repos.yum_config.__main__ as main
|
||||||
import tripleo_repos.yum_config.yum_config as yum_cfg
|
import tripleo_repos.yum_config.constants as const
|
||||||
import tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
import tripleo_repos.yum_config.dnf_manager as dnf_mgr
|
||||||
|
import tripleo_repos.yum_config.yum_config as yum_cfg
|
||||||
|
|
||||||
|
|
||||||
class TestTripleoYumConfigBase(unittest.TestCase):
|
class TestTripleoYumConfigBase(unittest.TestCase):
|
||||||
|
@ -56,10 +57,10 @@ class TestTripleoYumConfigMain(TestTripleoYumConfigBase):
|
||||||
main.main()
|
main.main()
|
||||||
expected_dict = {'key1': 'value1', 'key2': 'value2'}
|
expected_dict = {'key1': 'value1', 'key2': 'value2'}
|
||||||
|
|
||||||
mock_yum_repo_obj.assert_called_once_with(
|
mock_yum_repo_obj.assert_called_once_with(dir_path=const.YUM_REPO_DIR)
|
||||||
file_path=fakes.FAKE_FILE_PATH, dir_path=None)
|
|
||||||
mock_update_section.assert_called_once_with(
|
mock_update_section.assert_called_once_with(
|
||||||
'fake_repo', expected_dict, enable=True)
|
'fake_repo', expected_dict, file_path=fakes.FAKE_FILE_PATH,
|
||||||
|
enabled=True)
|
||||||
|
|
||||||
@ddt.data('enable', 'disable', 'reset', 'install', 'remove')
|
@ddt.data('enable', 'disable', 'reset', 'install', 'remove')
|
||||||
def test_main_module(self, operation):
|
def test_main_module(self, operation):
|
||||||
|
|
|
@ -29,51 +29,25 @@ import tripleo_repos.yum_config.yum_config as yum_cfg
|
||||||
class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
"""Tests for TripleYumConfig class and its methods."""
|
"""Tests for TripleYumConfig class and its methods."""
|
||||||
|
|
||||||
def _create_yum_config_obj(self, file_path=None, dir_path=None,
|
def _create_yum_config_obj(self, dir_path=None, valid_options=None,
|
||||||
valid_options=None, file_extension=None):
|
file_extension=None):
|
||||||
self.mock_object(os.path, 'isfile')
|
self.mock_object(os.path, 'isfile')
|
||||||
self.mock_object(os, 'access')
|
self.mock_object(os, 'access')
|
||||||
self.mock_object(os.path, 'isdir')
|
self.mock_object(os.path, 'isdir')
|
||||||
return yum_cfg.TripleOYumConfig(file_path=file_path, dir_path=dir_path,
|
return yum_cfg.TripleOYumConfig(dir_path=dir_path,
|
||||||
valid_options=valid_options,
|
valid_options=valid_options,
|
||||||
file_extension=file_extension)
|
file_extension=file_extension)
|
||||||
|
|
||||||
@ddt.data(
|
def test_tripleo_yum_config_invalid_dir_path(self):
|
||||||
{'file_path': None, 'dir_path': None, 'is_file_ret': None,
|
|
||||||
'access_ret': None, 'is_dir_ret': None,
|
|
||||||
'exception': exc.TripleOYumConfigNotFound},
|
|
||||||
|
|
||||||
{'file_path': 'fake_path', 'dir_path': None, 'is_file_ret': False,
|
|
||||||
'access_ret': None, 'is_dir_ret': None,
|
|
||||||
'exception': exc.TripleOYumConfigNotFound},
|
|
||||||
|
|
||||||
{'file_path': 'fake_path', 'dir_path': None, 'is_file_ret': True,
|
|
||||||
'access_ret': False, 'is_dir_ret': None,
|
|
||||||
'exception': exc.TripleOYumConfigPermissionDenied},
|
|
||||||
|
|
||||||
{'file_path': None, 'dir_path': 'fake_dir', 'is_file_ret': None,
|
|
||||||
'access_ret': None, 'is_dir_ret': False,
|
|
||||||
'exception': exc.TripleOYumConfigNotFound},
|
|
||||||
)
|
|
||||||
@ddt.unpack
|
|
||||||
def test_tripleo_yum_config_invalid_parameters(
|
|
||||||
self, file_path, dir_path, is_file_ret, access_ret, is_dir_ret,
|
|
||||||
exception):
|
|
||||||
self.mock_object(os.path, 'isfile',
|
|
||||||
mock.Mock(return_value=is_file_ret))
|
|
||||||
self.mock_object(os, 'access',
|
|
||||||
mock.Mock(return_value=access_ret))
|
|
||||||
self.mock_object(os.path, 'isdir',
|
self.mock_object(os.path, 'isdir',
|
||||||
mock.Mock(return_value=is_dir_ret))
|
mock.Mock(return_value=False))
|
||||||
|
|
||||||
self.assertRaises(exception,
|
self.assertRaises(exc.TripleOYumConfigNotFound,
|
||||||
yum_cfg.TripleOYumConfig,
|
yum_cfg.TripleOYumConfig,
|
||||||
file_path=file_path,
|
dir_path='fake_dir_path')
|
||||||
dir_path=dir_path)
|
|
||||||
|
|
||||||
def test_read_config_file_path(self):
|
def test_read_config_file_path(self):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj()
|
||||||
file_path=fakes.FAKE_FILE_PATH)
|
|
||||||
|
|
||||||
parser_mock = mock.Mock()
|
parser_mock = mock.Mock()
|
||||||
self.mock_object(configparser, 'ConfigParser',
|
self.mock_object(configparser, 'ConfigParser',
|
||||||
|
@ -83,7 +57,8 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
mock.Mock(return_value=fakes.FAKE_SECTIONS))
|
mock.Mock(return_value=fakes.FAKE_SECTIONS))
|
||||||
|
|
||||||
config_parser, file_path = yum_config._read_config_file(
|
config_parser, file_path = yum_config._read_config_file(
|
||||||
fakes.FAKE_SECTION1
|
fakes.FAKE_FILE_PATH,
|
||||||
|
section=fakes.FAKE_SECTION1
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(parser_mock, config_parser)
|
self.assertEqual(parser_mock, config_parser)
|
||||||
|
@ -91,8 +66,7 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
read_mock.assert_called_once_with(fakes.FAKE_FILE_PATH)
|
read_mock.assert_called_once_with(fakes.FAKE_FILE_PATH)
|
||||||
|
|
||||||
def test_read_config_file_path_parse_error(self):
|
def test_read_config_file_path_parse_error(self):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj()
|
||||||
file_path=fakes.FAKE_FILE_PATH)
|
|
||||||
|
|
||||||
parser_mock = mock.Mock()
|
parser_mock = mock.Mock()
|
||||||
self.mock_object(configparser, 'ConfigParser',
|
self.mock_object(configparser, 'ConfigParser',
|
||||||
|
@ -102,13 +76,13 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
|
|
||||||
self.assertRaises(exc.TripleOYumConfigFileParseError,
|
self.assertRaises(exc.TripleOYumConfigFileParseError,
|
||||||
yum_config._read_config_file,
|
yum_config._read_config_file,
|
||||||
fakes.FAKE_SECTION1)
|
fakes.FAKE_FILE_PATH,
|
||||||
|
section=fakes.FAKE_SECTION1)
|
||||||
|
|
||||||
read_mock.assert_called_once_with(fakes.FAKE_FILE_PATH)
|
read_mock.assert_called_once_with(fakes.FAKE_FILE_PATH)
|
||||||
|
|
||||||
def test_read_config_file_path_invalid_section(self):
|
def test_read_config_file_path_invalid_section(self):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj()
|
||||||
file_path=fakes.FAKE_FILE_PATH)
|
|
||||||
|
|
||||||
parser_mock = mock.Mock()
|
parser_mock = mock.Mock()
|
||||||
self.mock_object(configparser, 'ConfigParser',
|
self.mock_object(configparser, 'ConfigParser',
|
||||||
|
@ -119,11 +93,12 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
|
|
||||||
self.assertRaises(exc.TripleOYumConfigInvalidSection,
|
self.assertRaises(exc.TripleOYumConfigInvalidSection,
|
||||||
yum_config._read_config_file,
|
yum_config._read_config_file,
|
||||||
'invalid_section')
|
fakes.FAKE_FILE_PATH,
|
||||||
|
section='invalid_section')
|
||||||
|
|
||||||
read_mock.assert_called_once_with(fakes.FAKE_FILE_PATH)
|
read_mock.assert_called_once_with(fakes.FAKE_FILE_PATH)
|
||||||
|
|
||||||
def test_read_config_file_dir(self):
|
def test_get_config_files(self):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj(
|
||||||
dir_path=fakes.FAKE_DIR_PATH,
|
dir_path=fakes.FAKE_DIR_PATH,
|
||||||
file_extension='.conf')
|
file_extension='.conf')
|
||||||
|
@ -133,50 +108,28 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
parser_mocks.append(parser_mock)
|
parser_mocks.append(parser_mock)
|
||||||
self.mock_object(parser_mock, 'read')
|
self.mock_object(parser_mock, 'read')
|
||||||
|
|
||||||
self.mock_object(parser_mocks[1], 'sections',
|
self.mock_object(parser_mocks[0], 'sections',
|
||||||
mock.Mock(return_value=[]))
|
mock.Mock(return_value=[]))
|
||||||
# second file inside dir will have the expected sections
|
# second file inside dir will have the expected sections
|
||||||
self.mock_object(parser_mocks[2], 'sections',
|
self.mock_object(parser_mocks[1], 'sections',
|
||||||
mock.Mock(return_value=fakes.FAKE_SECTIONS))
|
mock.Mock(return_value=fakes.FAKE_SECTIONS))
|
||||||
|
self.mock_object(parser_mocks[2], 'sections',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
self.mock_object(os, 'listdir',
|
self.mock_object(os, 'listdir',
|
||||||
mock.Mock(return_value=fakes.FAKE_DIR_FILES))
|
mock.Mock(return_value=fakes.FAKE_DIR_FILES))
|
||||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
||||||
self.mock_object(configparser, 'ConfigParser',
|
self.mock_object(configparser, 'ConfigParser',
|
||||||
mock.Mock(side_effect=parser_mocks))
|
mock.Mock(side_effect=parser_mocks))
|
||||||
|
|
||||||
config_parser, file_path = yum_config._read_config_file(
|
result = yum_config._get_config_files(fakes.FAKE_SECTION1)
|
||||||
fakes.FAKE_SECTION1)
|
expected_dir_path = [os.path.join(fakes.FAKE_DIR_PATH,
|
||||||
expected_dir_path = os.path.join(fakes.FAKE_DIR_PATH,
|
fakes.FAKE_DIR_FILES[1])]
|
||||||
fakes.FAKE_DIR_FILES[1])
|
|
||||||
|
|
||||||
self.assertEqual(parser_mocks[0], config_parser)
|
self.assertEqual(expected_dir_path, result)
|
||||||
self.assertEqual(expected_dir_path, file_path)
|
|
||||||
|
|
||||||
def test_read_config_file_dir_section_not_found(self):
|
|
||||||
yum_config = self._create_yum_config_obj(
|
|
||||||
dir_path=fakes.FAKE_DIR_PATH,
|
|
||||||
file_extension='.conf')
|
|
||||||
parser_mock = mock.Mock()
|
|
||||||
self.mock_object(parser_mock, 'read')
|
|
||||||
self.mock_object(parser_mock, 'sections',
|
|
||||||
mock.Mock(return_value=[]))
|
|
||||||
self.mock_object(configparser, 'ConfigParser',
|
|
||||||
mock.Mock(return_value=parser_mock))
|
|
||||||
|
|
||||||
self.mock_object(os, 'listdir',
|
|
||||||
mock.Mock(return_value=fakes.FAKE_DIR_FILES))
|
|
||||||
self.mock_object(os, 'access', mock.Mock(return_value=True))
|
|
||||||
|
|
||||||
config_parser, file_path = yum_config._read_config_file(
|
|
||||||
fakes.FAKE_SECTION1)
|
|
||||||
|
|
||||||
self.assertEqual(parser_mock, config_parser)
|
|
||||||
self.assertIsNone(file_path)
|
|
||||||
|
|
||||||
@mock.patch('builtins.open')
|
@mock.patch('builtins.open')
|
||||||
def test_update_section(self, open):
|
def test_update_section(self, open):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj(
|
||||||
file_path=fakes.FAKE_FILE_PATH,
|
|
||||||
valid_options=fakes.FAKE_SUPP_OPTIONS)
|
valid_options=fakes.FAKE_SUPP_OPTIONS)
|
||||||
config_parser = fakes.FakeConfigParser({fakes.FAKE_SECTION1: {}})
|
config_parser = fakes.FakeConfigParser({fakes.FAKE_SECTION1: {}})
|
||||||
|
|
||||||
|
@ -186,13 +139,14 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
|
|
||||||
updates = {fakes.FAKE_OPTION1: 'new_fake_value'}
|
updates = {fakes.FAKE_OPTION1: 'new_fake_value'}
|
||||||
|
|
||||||
yum_config.update_section(fakes.FAKE_SECTION1, updates)
|
yum_config.update_section(fakes.FAKE_SECTION1, updates,
|
||||||
|
file_path=fakes.FAKE_FILE_PATH)
|
||||||
|
|
||||||
mock_read_config.assert_called_once_with(fakes.FAKE_SECTION1)
|
mock_read_config.assert_called_once_with(fakes.FAKE_FILE_PATH,
|
||||||
|
section=fakes.FAKE_SECTION1)
|
||||||
|
|
||||||
def test_update_section_invalid_options(self):
|
def test_update_section_invalid_options(self):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj(
|
||||||
file_path=fakes.FAKE_FILE_PATH,
|
|
||||||
valid_options=fakes.FAKE_SUPP_OPTIONS)
|
valid_options=fakes.FAKE_SUPP_OPTIONS)
|
||||||
|
|
||||||
updates = {'invalid_option': 'new_fake_value'}
|
updates = {'invalid_option': 'new_fake_value'}
|
||||||
|
@ -200,15 +154,15 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
self.assertRaises(exc.TripleOYumConfigInvalidOption,
|
self.assertRaises(exc.TripleOYumConfigInvalidOption,
|
||||||
yum_config.update_section,
|
yum_config.update_section,
|
||||||
fakes.FAKE_SECTION1,
|
fakes.FAKE_SECTION1,
|
||||||
updates)
|
updates,
|
||||||
|
file_path=fakes.FAKE_FILE_PATH)
|
||||||
|
|
||||||
def test_update_section_file_not_found(self):
|
def test_update_section_file_not_found(self):
|
||||||
yum_config = self._create_yum_config_obj(
|
yum_config = self._create_yum_config_obj(
|
||||||
file_path=fakes.FAKE_FILE_PATH,
|
|
||||||
valid_options=fakes.FAKE_SUPP_OPTIONS)
|
valid_options=fakes.FAKE_SUPP_OPTIONS)
|
||||||
mock_read_config = self.mock_object(
|
mock_get_configs = self.mock_object(
|
||||||
yum_config, '_read_config_file',
|
yum_config, '_get_config_files',
|
||||||
mock.Mock(return_value=(mock.Mock(), None)))
|
mock.Mock(return_value=[]))
|
||||||
|
|
||||||
updates = {fakes.FAKE_OPTION1: 'new_fake_value'}
|
updates = {fakes.FAKE_OPTION1: 'new_fake_value'}
|
||||||
|
|
||||||
|
@ -216,8 +170,7 @@ class TestTripleOYumConfig(test_main.TestTripleoYumConfigBase):
|
||||||
yum_config.update_section,
|
yum_config.update_section,
|
||||||
fakes.FAKE_SECTION1,
|
fakes.FAKE_SECTION1,
|
||||||
updates)
|
updates)
|
||||||
|
mock_get_configs.assert_called_once_with(fakes.FAKE_SECTION1)
|
||||||
mock_read_config.assert_called_once_with(fakes.FAKE_SECTION1)
|
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
|
@ -229,8 +182,7 @@ class TestTripleOYumRepoConfig(test_main.TestTripleoYumConfigBase):
|
||||||
self.mock_object(os.path, 'isfile')
|
self.mock_object(os.path, 'isfile')
|
||||||
self.mock_object(os, 'access')
|
self.mock_object(os, 'access')
|
||||||
self.mock_object(os.path, 'isdir')
|
self.mock_object(os.path, 'isdir')
|
||||||
cfg_obj = yum_cfg.TripleOYumRepoConfig(
|
cfg_obj = yum_cfg.TripleOYumRepoConfig()
|
||||||
file_path=fakes.FAKE_FILE_PATH)
|
|
||||||
|
|
||||||
mock_update = self.mock_object(yum_cfg.TripleOYumConfig,
|
mock_update = self.mock_object(yum_cfg.TripleOYumConfig,
|
||||||
'update_section')
|
'update_section')
|
||||||
|
@ -240,10 +192,12 @@ class TestTripleOYumRepoConfig(test_main.TestTripleoYumConfigBase):
|
||||||
if enable is not None:
|
if enable is not None:
|
||||||
expected_updates['enabled'] = '1' if enable else '0'
|
expected_updates['enabled'] = '1' if enable else '0'
|
||||||
|
|
||||||
cfg_obj.update_section(fakes.FAKE_SECTION1, updates, enable=enable)
|
cfg_obj.update_section(fakes.FAKE_SECTION1, set_dict=updates,
|
||||||
|
file_path=fakes.FAKE_FILE_PATH, enabled=enable)
|
||||||
|
|
||||||
mock_update.assert_called_once_with(fakes.FAKE_SECTION1,
|
mock_update.assert_called_once_with(fakes.FAKE_SECTION1,
|
||||||
expected_updates)
|
expected_updates,
|
||||||
|
file_path=fakes.FAKE_FILE_PATH)
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
|
|
Loading…
Reference in New Issue