Merge "Revert "Container manage module""

This commit is contained in:
Zuul 2021-06-18 04:41:21 +00:00 committed by Gerrit Code Review
commit 15e3dbf94f
4 changed files with 11 additions and 380 deletions

View File

@ -266,10 +266,8 @@ class ContainerPuppetManager:
default_data = {
# the security_opt can be removed once we properly address:
# https://bugs.launchpad.net/tripleo/+bug/1864501
'security_opt': ['label=disable'],
'security_opt': 'label=disable',
'user': 0,
# container-puppet shouldn't detach
'detach': False,
'entrypoint': CONTAINER_ENTRYPOINT,
'environment': self._get_environment_config()
}
@ -281,10 +279,10 @@ class ContainerPuppetManager:
volumes += ['/etc/puppet/check-mode:'
'/tmp/puppet-check-mode:ro']
if self.net_host:
cdata['net'] = ['host']
cdata['net'] = 'host'
volumes += ['/etc/hosts:/etc/hosts:ro']
else:
cdata['net'] = ['none']
cdata['net'] = 'none'
cdata['environment']['PUPPET_TAGS'] = (
self._get_puppet_tags(config))

View File

@ -1,368 +0,0 @@
#!/usr/bin/python
# Copyright 2020 Red Hat, 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.
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.parsing.convert_bool import boolean
import glob
import os
import yaml
import json
from concurrent.futures import ThreadPoolExecutor
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: tripleo_container_manage
author:
- "Alex Schultz (@mwhahaha)"
version_added: '2.9'
short_description: Create containers from a set of json configurations
notes: []
description:
- Generate puppet containers configs
requirements:
- None
options:
config_id:
description:
- Config id for the label
type: str
required: True
config_dir:
description:
- Path to the json container definitions
type: str
required: True
config_patterns:
description:
- Glob for configuration files
type: str
default: "*.json"
config_overrides:
description:
- Allows to override any container configuration which will take
precedence over the JSON files.
default: {}
required: False
type: dict
log_base_path:
description:
- Log base path directory
type: str
default: '/var/log/containers/stdouts'
concurrency:
description:
- Number of podman actions to run at the same time
type: int
default: 1
debug:
description:
- Enable debug
type: bool
default: False
"""
EXAMPLES = """
- name: Run containers
tripleo_container_manage
config_id: tripleo_step1
config_dir: /var/lib/tripleo-config/container-startup-config/step_1
"""
from ansible_collections.containers.podman.plugins.module_utils.podman.podman_container_lib import PodmanManager, ARGUMENTS_SPEC_CONTAINER # noqa: F402
class ExecFailure(Exception):
def __init__(self, msg, stdout=None, stderr=None):
super().__init__(msg)
self.msg = msg
self.stdout = stdout
self.stderr = stderr
def __str__(self):
return f"ERROR: {self.msg}\nstderr: {self.stderr}"
class TripleoContainerManage:
"""Notes about this module.
It will generate container config that will be consumed by the
tripleo-container-manage role that is using podman_container module.
"""
def __init__(self, module, results):
self.module = module
self.results = results
# parse args
args = self.module.params
# Set parameters
self.concurrency = args.get('concurrency', 4)
self.config_id = args.get('config_id')
self.config_dir = args.get('config_dir')
self.config_patterns = args.get('config_patterns')
self.config_overrides = args['config_overrides']
self.log_base_path = args.get('log_base_path')
self.debug = args.get('debug')
self.run()
self.module.exit_json(**self.results)
# container_config_data.py without overrides
def _get_configs(self):
configs = {}
if not os.path.exists(self.config_dir):
self.module.warn('Configuration directory does not exist '
f'{self.config_dir}')
return configs
matches = glob.glob(os.path.join(self.config_dir,
self.config_patterns))
for match in matches:
name = os.path.splitext(os.path.basename(match))[0]
with open(match, 'r') as data:
config = json.loads(data.read())
if self.debug:
self.module.debug('Config found for {}: {}'.format(name,
config))
configs.update({name: config})
# handle overrides similar to container_config_data
if self.config_overrides:
for k in self.config_overrides.keys():
if k in configs:
for mk, mv in self.config_overrides[k].items():
if self.debug:
self.module.debug(f'Override found for {k}: {mk} '
f'will be set to {mv}')
configs[k][mk] = mv
return configs
def _get_version(self):
rc, out, err = self.module.run_command(['podman', b'--version'])
if rc != 0 or not out or 'version' not in out:
self.module.fail_json(msg='Can not determine podman version')
return out.split('versio')[1].strip()
def _container_opts_defaults(self):
default = {}
opts = ARGUMENTS_SPEC_CONTAINER
for k, v in opts.items():
if 'default' in v:
default[k] = v['default']
else:
default[k] = None
return default
def _container_opts_update(self, container_opts):
opts_dict = self._container_opts_defaults()
aliases = {}
for k, v in ARGUMENTS_SPEC_CONTAINER.items():
if 'aliases' in v:
for alias in v['aliases']:
aliases[alias] = k
for k in list(container_opts):
if k in aliases:
key = aliases[k]
opts_dict[key] = container_opts[k]
container_opts.pop(k)
opts_dict.update(container_opts)
return opts_dict
def _container_opts_types(self, container_opts):
# convert data types since magic ansible option conversion doesn't
# occur here.
for k, v in container_opts.items():
if v is None:
continue
if ARGUMENTS_SPEC_CONTAINER.get(k) is None:
if self.debug:
self.module.debug(f"Container opt '{k}' is unknown")
continue
opt_type = ARGUMENTS_SPEC_CONTAINER.get(k).get('type')
if opt_type in ['raw', 'path']:
continue
if not isinstance(v, eval(opt_type)):
if isinstance(v, str) and opt_type == 'list':
container_opts[k] = [v]
elif isinstance(v, str) and opt_type == 'bool':
container_opts[k] = boolean(v)
elif isinstance(v, str) and opt_type == 'int':
container_opts[k] = int(v)
elif isinstance(v, int) and opt_type == 'str':
container_opts[k] = str(v)
else:
raise TypeError(f"Container {container_opts['name']} "
f"option ({k}, {v}) is not "
f"type {opt_type} is {type(v)}")
return container_opts
def _list_or_dict_arg(self, data, cmd, key, arg):
"""Utility to build a command and its argument with list or dict data.
The key can be a dictionary or a list, the returned arguments will be
a list where each item is the argument name and the item data.
"""
if key not in data:
return
value = data[key]
if isinstance(value, dict):
for k, v in sorted(value.items()):
if v:
cmd.append('%s=%s=%s' % (arg, k, v))
elif k:
cmd.append('%s=%s' % (arg, k))
elif isinstance(value, list):
for v in value:
if v:
cmd.append('%s=%s' % (arg, v))
def exec_container(self, name, config):
cmd = ['podman', 'exec', f"--user={config.get('user', 'root')}"]
if 'privileged' in config:
cmd.append('--privileged=%s' % str(config['privileged']).lower())
self._list_or_dict_arg(config, cmd, 'environment', '--env')
cmd.extend(config['command'])
rc, out, err = self.module.run_command(cmd)
if rc != 0:
msg = "Failure running exec '{}'. rc={}, stdout={}, stderr={}".format(name, rc, out, err)
self.module.warn(msg)
return False
return True
def manage_container(self, name, config):
opts = {
'name': name,
'state': "present",
'label': {
'config_id': self.config_id,
'container_name': name,
'managed_by': 'tripleo_ansible',
'config_data': config
},
'conmon_pidfile': f"/var/run/{name}.pid",
'debug': self.debug,
'log_driver': 'k8s-file',
'log_opt': {"path": f"{self.log_base_path}/{name}.log"},
}
opts.update(config)
# do horible things to convert THT format to ansible module format
if 'volumes' in opts:
opts['volume'] = opts.pop('volumes')
if 'environment' in opts:
opts['env'] = opts.pop('environment')
if 'healthcheck' in opts and isinstance(opts['healthcheck'], dict):
opts['healthcheck'] = opts['healthcheck'].get('test', None)
if 'check_interval' in opts:
opts['healthcheck_interval'] = opts.pop('check_interval')
if 'remove' in opts:
opts['rm'] = opts.pop('remove')
if 'restart' in opts:
# NOTE(mwhahaha): converation from tripleo format to podman as
# systemd handles this restart config
opts['restart'] = False
if 'stop_grace_period' in opts:
opts['stop_timeout'] = opts.pop('stop_grace_period')
success = True
try:
container_opts = self._container_opts_update(opts)
container_opts = self._container_opts_types(container_opts)
PodmanManager(self.module, container_opts).execute()
except ExecFailure as e:
print(e)
self.module.warn(str(e))
success = False
return success
def run_container(self, data):
name, config = data
action = config.get('action', 'create')
success = False
if action == 'exec':
success = self.exec_container(name, config)
else:
success = self.manage_container(name, config)
return (name, success)
def check_failures(self, results):
failed = []
for result in results:
name, res = result
if not res:
failed.append(name)
return failed
def batch_start_order(self, configs):
data = {}
for k in configs:
start_order = configs[k].get('start_order', 0)
if start_order not in data:
data[start_order] = []
data[start_order].append((k, configs.get(k)))
return data
def run(self):
configs = self._get_configs()
# sort configs by start_order
# launch containers?
data = self.batch_start_order(configs)
failed = []
def exe_fail_json(**kwargs):
raise ExecFailure(**kwargs)
# NOTE: fix because PodmanManager calls fail_json directly so we want
# to handle those all at once at the end
orig_fail = self.module.fail_json
self.module.fail_json = exe_fail_json
# loop through keys sorted
for start_order in sorted(data.keys()):
with ThreadPoolExecutor(max_workers=self.concurrency) as exc:
results = exc.map(self.run_container, data[start_order])
failed.extend(self.check_failures(results))
self.module.fail_json = orig_fail
if len(failed) > 0:
self.module.fail_json(
msg='Failed containers: {}'.format(', '.join(failed)))
self.results['changed'] = True
def main():
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
supports_check_mode=True,
)
results = dict(
changed=False
)
TripleoContainerManage(module, results)
if __name__ == '__main__':
main()

View File

@ -229,7 +229,7 @@
assert:
that:
- "'fedora:rawhide' in fedora_infos.containers.0.ImageName"
fail_msg: "fedora container has wrong image {{ fedora_infos.containers }}"
fail_msg: 'fedora container has wrong image'
success_msg: 'fedora container has the right image'
- name: Check if tripleo_fedora systemd service is active
command: systemctl is-active --quiet tripleo_fedora

View File

@ -15,12 +15,13 @@
# under the License.
- name: "Create containers managed by Podman for {{ tripleo_container_manage_config }}"
tripleo_container_manage:
config_id: "{{ tripleo_container_manage_config_id }}"
config_dir: "{{ tripleo_container_manage_config }}"
config_patterns: "{{ tripleo_container_manage_config_patterns }}"
config_overrides: "{{ tripleo_container_manage_config_overrides }}"
concurrency: "{{ tripleo_container_manage_concurrency }}"
when:
- tripleo_container_manage_cli == 'podman'
include_tasks: podman/start_order.yml
vars:
order: "{{ item.key }}"
data: "{{ item.value }}"
loop: "{{ all_containers_hash | subsort(attribute='start_order', null_value=0) | dict2items | list }}"
- name: "Manage container systemd services and cleanup old systemd healthchecks for {{ tripleo_container_manage_config }}"
become: true