tripleo-repos/plugins/module_utils/tripleo_repos/yum_config/compose_repos.py

207 lines
8.1 KiB
Python

# 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
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,
environment_file=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,
environment_file=environment_file)
def _get_compose_info(self):
"""Retrieve compose info for a provided compose-id url."""
# NOTE(dviroel): works for both centos 8 and 9
import urllib.request
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. "
"Trying to update it...", var)
self.update_section(var.lower(),
set_dict=add_dict,
file_path=file_path)
# 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)