commit ae3778c57cdc9205c57aaeb98693c8008bbff4cb Author: Artem Goncharov Date: Mon Sep 12 13:56:21 2022 +0200 initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b333a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.pyc +__pycache__ +.tox +build_artifact +ansible_collections +galaxy.yml +FILES.json +MANIFEST.json +importer_result.json +**.swp + +*.tar.gz +doc/build +tmp + +#idea +.idea/** +*/.idea/** + diff --git a/bindep.txt b/bindep.txt new file mode 100644 index 0000000..8acc524 --- /dev/null +++ b/bindep.txt @@ -0,0 +1,8 @@ +# This is a cross-platform list tracking distribution packages needed by tests; +# see https://docs.openstack.org/infra/bindep/ for additional information. + +gcc-c++ [doc test platform:rpm] +libyaml-devel [test platform:rpm] +libyaml-dev [test platform:dpkg] +python3-devel [test platform:rpm] +python3 [test platform:rpm] diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..69f764e --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,32 @@ +--- +namespace: opentelekomcloud +name: backend +version: 0.1.0 +readme: README.rst +authors: + - Artem Goncharov (@gtema) + +description: Roles and modules for managing Open Telekom Cloud Backend +license: + - GPL-2.0-or-later +tags: + - opentelekomcloud +dependencies: {} +repository: https://gitea.eco.tsi-dev.otc-service.com/infra/ansible-collection-backend +documentation: http://docs.otc-service.com +homepage: https://open-telekom-cloud.com +issues: https://gitea.eco.tsi-dev.otc-service.com/infra/ansible-collection-backend/issues +build_ignore: + - '*.tar.gz' + - build_artifact/ + - ci/ + - galaxy.yml.in + - setup.cfg + - setup.py + - tools + - tox.ini + - .gitignore + - .gitreview + - .zuul.yaml + - .pytest_cache + - importer_result.json diff --git a/plugins/doc_fragments/otc.py b/plugins/doc_fragments/otc.py new file mode 100644 index 0000000..a8eee28 --- /dev/null +++ b/plugins/doc_fragments/otc.py @@ -0,0 +1,106 @@ +# 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. + + +class ModuleDocFragment(object): + + # Standard otc documentation fragment + DOCUMENTATION = r''' +options: + cloud: + description: + - Named cloud or cloud config to operate against. + If I(cloud) is a string, it references a named cloud config as defined + in an OpenStack clouds.yaml file. Provides default values for I(auth) + and I(auth_type). This parameter is not needed if I(auth) is provided + or if OpenStack OS_* environment variables are present. + If I(cloud) is a dict, it contains a complete cloud configuration like + would be in a section of clouds.yaml. + type: raw + auth: + description: + - Dictionary containing auth information as needed by the cloud's auth + plugin strategy. For the default I(password) plugin, this would contain + I(auth_url), I(username), I(password), I(project_name) and any + information about domains (for example, I(os_user_domain_name) or I(os_project_domain_name)) if the cloud supports them. + For other plugins, + this param will need to contain whatever parameters that auth plugin + requires. This parameter is not needed if a named cloud is provided or + OpenStack OS_* environment variables are present. + type: dict + auth_type: + description: + - Name of the auth plugin to use. If the cloud uses something other than + password authentication, the name of the plugin should be indicated here + and the contents of the I(auth) parameter should be updated accordingly. + type: str + region_name: + description: + - Name of the region. + type: str + wait: + description: + - Should ansible wait until the requested resource is complete. + type: bool + default: yes + timeout: + description: + - How long should ansible wait for the requested resource. + type: int + default: 180 + api_timeout: + description: + - How long should the socket layer wait before timing out for API calls. + If this is omitted, nothing will be passed to the requests library. + type: int + validate_certs: + description: + - Whether or not SSL API requests should be verified. + - Before Ansible 2.3 this defaulted to C(yes). + type: bool + aliases: [ verify ] + ca_cert: + description: + - A path to a CA Cert bundle that can be used as part of verifying + SSL API requests. + type: str + aliases: [ cacert ] + client_cert: + description: + - A path to a client certificate to use as part of the SSL transaction. + type: str + aliases: [ cert ] + client_key: + description: + - A path to a client key to use as part of the SSL transaction. + type: str + aliases: [ key ] + interface: + description: + - Endpoint URL type to fetch from the service catalog. + type: str + choices: [ admin, internal, public ] + default: public + aliases: [ endpoint_type ] +requirements: + - python >= 3.6 + - openstacksdk >= 0.36.0 +notes: + - The standard OpenStack environment variables, such as C(OS_USERNAME) + may be used instead of providing explicit values. + - Auth information is driven by openstacksdk, which means that values + can come from a yaml config file in /etc/ansible/openstack.yaml, + /etc/openstack/clouds.yaml or ~/.config/openstack/clouds.yaml, then from + standard environment variables, then finally by explicit parameters in + plays. More information can be found at + U(https://docs.openstack.org/openstacksdk/) +''' diff --git a/plugins/module_utils/otc.py b/plugins/module_utils/otc.py new file mode 100644 index 0000000..1496f4f --- /dev/null +++ b/plugins/module_utils/otc.py @@ -0,0 +1,206 @@ +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import abc + +try: + import openstack as sdk + import otcextensions + from otcextensions import sdk as otc_sdk + from pkg_resources import parse_version as V + HAS_LIBRARIES = True +except ImportError: + HAS_LIBRARIES = False + +from ansible.module_utils.basic import AnsibleModule + + +def openstack_full_argument_spec(**kwargs): + spec = dict( + cloud=dict(default=None, type='raw'), + auth_type=dict(default=None), + auth=dict(default=None, type='dict', no_log=True), + region_name=dict(default=None), + validate_certs=dict(default=None, type='bool', aliases=['verify']), + ca_cert=dict(default=None, aliases=['cacert']), + client_cert=dict(default=None, aliases=['cert']), + client_key=dict(default=None, no_log=True, aliases=['key']), + wait=dict(default=True, type='bool'), + timeout=dict(default=180, type='int'), + api_timeout=dict(default=None, type='int'), + interface=dict( + default='public', choices=['public', 'internal', 'admin'], + aliases=['endpoint_type']), + ) + spec.update(kwargs) + return spec + + +class OTCModule: + """Openstack Module is a base class for all Openstack Module classes. + + The class has `run` function that should be overriden in child classes, + the provided methods include: + + Methods: + params: Dictionary of Ansible module parameters. + module_name: Module name (i.e. server_action) + sdk_version: Version of used OpenstackSDK. + results: Dictionary for return of Ansible module, + must include `changed` keyword. + exit, exit_json: Exit module and return data inside, must include + changed` keyword in a data. + fail, fail_json: Exit module with failure, has `msg` keyword to + specify a reason of failure. + conn: Connection to SDK object. + log: Print message to system log. + debug: Print debug message to system log, prints if Ansible Debug is + enabled or verbosity is more than 2. + check_deprecated_names: Function that checks if module was called with + a deprecated name and prints the correct name with deprecation + warning. + check_versioned: helper function to check that all arguments are known + in the current SDK version. + run: method that executes and shall be overriden in inherited classes. + + Args: + deprecated_names: Should specify deprecated modules names for current + module. + argument_spec: Used for construction of Openstack common arguments. + module_kwargs: Additional arguments for Ansible Module. + """ + + argument_spec = {} + module_kwargs = {} + otce_min_version = None + + def __init__(self): + + self.ansible = AnsibleModule( + openstack_full_argument_spec(**self.argument_spec), + **self.module_kwargs) + self.params = self.ansible.params + self.module_name = self.ansible._name + self.sdk_version = None + self.results = {'changed': False} + self.errors = [] + self.exit = self.exit_json = self.ansible.exit_json + self.fail = self.fail_json = self.ansible.fail_json + self.sdk, self.conn = self.openstack_cloud_from_module() + + def log(self, msg): + """Prints log message to system log. + + Arguments: + msg {str} -- Log message + """ + self.ansible.log(msg) + + def debug(self, msg): + """Prints debug message to system log + + Arguments: + msg {str} -- Debug message. + """ + if self.ansible._debug or self.ansible._verbosity > 2: + self.ansible.log( + " ".join(['[DEBUG]', msg])) + + def openstack_cloud_from_module(self, min_version='0.6.9'): + if self.otce_min_version: + min_version = self.otce_min_version + + if not HAS_LIBRARIES: + self.fail_json(msg='openstacksdk and otcextensions are required for this self') + + if min_version: + min_version = max(V('0.6.9'), V(min_version)) + else: + min_version = V('0.6.9') + + if V(otcextensions.__version__) < min_version: + self.fail_json( + msg="To utilize this self, the installed version of " + "the otcextensions library MUST be >={min_version}".format( + min_version=min_version)) + + cloud_config = self.params.pop('cloud', None) + try: + if isinstance(cloud_config, dict): + fail_message = ( + "A cloud config dict was provided to the cloud parameter" + " but also a value was provided for {param}. If a cloud" + " config dict is provided, {param} should be" + " excluded.") + for param in ( + 'auth', 'region_name', 'validate_certs', + 'ca_cert', 'client_key', 'api_timeout', 'auth_type'): + if self.params[param] is not None: + self.fail_json(msg=fail_message.format(param=param)) + # For 'interface' parameter, fail if we receive a non-default value + if self.params['interface'] != 'public': + self.fail_json(msg=fail_message.format(param='interface')) + conn = sdk.connect(**cloud_config) + otc_sdk.load(conn) + return sdk, conn + else: + conn = sdk.connect( + cloud=cloud_config, + auth_type=self.params['auth_type'], + auth=self.params['auth'], + region_name=self.params['region_name'], + verify=self.params['validate_certs'], + cacert=self.params['ca_cert'], + key=self.params['client_key'], + api_timeout=self.params['api_timeout'], + interface=self.params['interface'], + ) + otc_sdk.load(conn) + return sdk, conn + except sdk.exceptions.SDKException as e: + # Probably a cloud configuration/login error + self.fail_json(msg=str(e)) + + # Filter out all arguments that are not from current SDK version + def check_versioned(self, **kwargs): + """Check that provided arguments are supported by current SDK version + + Returns: + versioned_result {dict} dictionary of only arguments that are + supported by current SDK version. All others + are dropped. + """ + versioned_result = {} + for var_name in kwargs: + if ('min_ver' in self.argument_spec[var_name] + and V(self.sdk_version) < self.argument_spec[var_name]['min_ver']): + continue + if ('max_ver' in self.argument_spec[var_name] + and V(self.sdk_version) > self.argument_spec[var_name]['max_ver']): + continue + versioned_result.update({var_name: kwargs[var_name]}) + return versioned_result + + @abc.abstractmethod + def run(self): + pass + + def __call__(self): + """Execute `run` function when calling the class. + """ + + try: + results = self.run() + if results and isinstance(results, dict): + self.ansible.exit_json(**results) + + except self.sdk.exceptions.OpenStackCloudException as e: + params = { + 'msg': str(e), + 'extra_data': { + 'data': getattr(e, 'extra_data', 'None'), + 'details': getattr(e, 'details', 'None'), + 'response': getattr(getattr(e, 'response', ''), + 'text', 'None') + } + } + self.ansible.fail_json(**params) diff --git a/plugins/modules/service_catalog.py b/plugins/modules/service_catalog.py new file mode 100644 index 0000000..386dba6 --- /dev/null +++ b/plugins/modules/service_catalog.py @@ -0,0 +1,345 @@ +#!/usr/bin/python +# +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' +module: service_catalog +extends_documentation_fragment: opentelekomcloud.backend.otc +short_description: Manage Open Telekom Cloud service catalog +version_added: "0.1.0" +author: "Artem Goncharov (@gtema)" +description: + - Manages service catalog +options: + services: + description: + - Service Configuration data + type: dict + required: true + environment: + description: + - Environment name + type: str + required: true + limit_services: + description: + - optionally limit services + type: list + elements: str +''' + +RETURN = ''' +''' + + +EXAMPLES = ''' +''' + + +from urllib.parse import urlparse + +from ansible_collections.opentelekomcloud.backend.plugins.module_utils.otc import ( + OTCModule +) + + +def _is_srv_update_necessary(current, name, description, is_enabled): + if current.name != name: + return True + if current.description != description: + return True + if current.is_enabled != is_enabled: + return True + return False + + +def _is_ep_update_necessary(current, url, is_enabled=True): + if current.url != url: + return True + if current.is_enabled != is_enabled: + return True + return False + + +class ServiceCatalogModule(OTCModule): + argument_spec = dict( + services=dict(type='dict', required=True), + environment=dict(type='str', required=True), + limit_services=dict(type='list', elements='str'), + ) + module_kwargs = dict( + supports_check_mode=True + ) + + def _connect(self, cloud): + identity_url = self.conn.identity.get_endpoint_data().url + parsed_domain = urlparse(identity_url) + self.identity_ext_base = '%s://%s/v3.0' % ( + parsed_domain.scheme, parsed_domain.netloc) + + def _update_service(self, existing, name, description, is_enabled): + _url = self.sdk.utils.urljoin( + self.identity_ext_base, + 'OS-CATALOG', 'services' + ) + return self.conn.identity._update( + self.sdk.identity.v3.service.Service, + existing, + base_path=_url, + name=name, description=description, is_enabled=is_enabled + ) + + def _create_service(self, srv_type, name, description, is_enabled): + _url = self.sdk.utils.urljoin(self.identity_ext_base, 'OS-CATALOG', 'services') + return self.conn.identity._create( + self.sdk.identity.v3.service.Service, + base_path=_url, + type=srv_type, name=name, description=description, + is_enabled=is_enabled + ) + + def _delete_service(self, current): + _url = self.sdk.utils.urljoin( + self.identity_ext_base, + 'OS-CATALOG', 'services', current.id + ) + self.conn.identity.delete(_url) + + def _update_endpoint(self, current, url, is_enabled=True): + _url = self.sdk.utils.urljoin( + self.identity_ext_base, + 'OS-CATALOG' + ) + return self.conn.identity._update( + self.sdk.identity.v3.endpoint.Endpoint, + current, + base_path=_url, + url=url, is_enabled=is_enabled + ) + + def _create_endpoint( + self, + service_id, interface, url, region_id=None, is_enabled=True + ): + _url = self.sdk.utils.urljoin( + self.identity_ext_base, + 'OS-CATALOG', 'endpoints', + ) + return self.conn.identity._create( + self.sdk.identity.v3.endpoint.Endpoint, + base_path=_url, + service_id=service_id, interface=interface, region_id=region_id, + url=url, is_enabled=is_enabled + ) + + def _delete_endpoint(self, current): + _url = self.sdk.utils.urljoin( + self.identity_ext_base, + 'OS-CATALOG', current.id + ) + self.conn.identity.delete(_url) + + def process( + self, + target_data, + target_env='production', + dry_run=True, + skip_delete=True, + limit_services=None + ): + self._connect('dummy') + changed = False + + existing_services = list(self.conn.identity.services()) + existing_service_per_type = {k.type: k for k in existing_services} + _used_services = set() + _used_eps = set() + results = dict(services=[], endpoints=[]) + for srv_type, target_srv in target_data.get( + 'services', {}).items(): + if limit_services is not None and srv_type not in limit_services: + # logging.debug(f'Skipping service {srv_type} as requested') + continue + # logging.debug(f'Processing service {srv_type}') + + target_envs = target_srv.get('environments', {}) + if not ( + target_env in target_envs + and target_envs.get(target_env) is not None + ): + # logging.debug(f'Skipping service {srv_type} for {target_env}') + continue + current_srv = None + current_eps = [] + target_name = target_srv.get('name') + target_enabled = target_srv.get('enabled', True) + target_description = target_srv.get('description') + if srv_type and srv_type in existing_service_per_type: + current_srv = existing_service_per_type.get(srv_type) + if _is_srv_update_necessary( + current_srv, target_name, + target_description, target_enabled): + changed = True + + if not dry_run: + current_srv = self._update_service( + current_srv, target_name, target_description, + target_enabled) + results['services'].append({ + 'type': srv_type, + 'operation': 'update', + 'id': current_srv.id, + 'current_name': current_srv.name, + 'new_name': target_name, + 'current_enabled': current_srv.is_enabled, + 'new_enabled': target_enabled, + 'current_description': current_srv.description, + 'new_description': target_description, + }) + else: + changed = True + if not dry_run: + current_srv = self._create_service( + srv_type, target_name, target_description, + target_enabled) + results['services'].append({ + 'type': srv_type, + 'operation': 'create', + 'new_name': target_name, + 'new_enabled': target_enabled, + 'new_description': target_description, + }) + + if hasattr(current_srv, 'id'): + # make dry_run easier + _used_services.add(current_srv.id) + current_eps = list(self.conn.identity.endpoints( + service_id=current_srv.id)) + target_ep = target_envs.get(target_env, {}) + for region_id, target_eps in target_ep.get( + 'endpoints', {}).items(): + for tep in target_eps: + # logging.debug(f'Checking ep {tep}') + target_interface = tep.get('interface', 'public') + target_url = tep['url'] # done explicitly to force + # presence + target_enabled = tep.get('enabled', True) + _ep_found = False + ep = None + + for cep in current_eps: + # Dirty hack to compare region = None + cep_region = cep.region_id if cep.region_id \ + is not None else "" + + if ( + region_id == cep_region + and target_interface == cep.interface + ): + # Found matching EP + _ep_found = True + if _is_ep_update_necessary( + cep, target_url, target_enabled + ): + changed = True + if not dry_run: + ep = self._update_endpoint( + cep, target_url, + is_enabled=target_enabled) + else: + ep = cep + results['endpoints'].append({ + 'service_type': srv_type, + 'operation': 'update', + 'id': cep.id, + 'interface': target_interface, + 'current_url': cep.url, + 'new_url': target_url, + 'current_enabled': cep.is_enabled, + 'new_enabled': target_enabled, + }) + + else: + ep = cep + break # no need to continue + if not _ep_found: + changed = True + if not dry_run: + ep = self._create_endpoint( + current_srv.id, target_interface, + target_url, region_id, target_enabled) + results['endpoints'].append({ + 'service_type': srv_type, + 'operation': 'create', + 'region_id': region_id, + 'interface': target_interface, + 'new_url': target_url, + 'new_enabled': target_enabled, + }) + + if hasattr(ep, 'id'): + # Friendly dry_run + _used_eps.add(ep.id) + + if not limit_services: + # NOTE(gtema): Can't do cleanup of unused when filter requested + # Cleanup useless endpoints + for existing_ep in self.conn.identity.endpoints(): + if existing_ep.id not in _used_eps: + changed = True + if not skip_delete and not dry_run: + self._delete_endpoint(existing_ep) + results['endpoints'].append({ + 'operation': 'delete', + 'id': existing_ep.id, + 'current_url': existing_ep.url, + 'current_enabled': existing_ep.is_enabled + }) + + # Cleanup of unused or duplicated entries + for srv in existing_services: + if srv.id not in _used_services: + changed = True + if not skip_delete and not dry_run: + self._delete_service(srv) + results['services'].append({ + 'type': srv.type, + 'operation': 'delete', + 'id': srv.id, + 'current_name': srv.name, + 'current_description': srv.description, + 'current_enabled': srv.is_enabled + }) + + return (results, changed) + + def run(self): + changed = False + (results, changed) = self.process( + target_data=self.params['services'], + target_env=['self.args.environment'], + dry_run=self.ansible.check_mode, + limit_services=self.params['limit_services'] + ) + if len(self.errors) == 0: + self.exit_json( + changed=changed, + results=results + ) + else: + self.fail_json( + msg='Failures occured', + errors=self.errors, + ) + + +def main(): + module = ServiceCatalogModule() + module() + + +if __name__ == "__main__": + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..423b488 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +otcextensions diff --git a/runtime.yml b/runtime.yml new file mode 100644 index 0000000..d9e1a25 --- /dev/null +++ b/runtime.yml @@ -0,0 +1,2 @@ +--- +{} diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..555b007 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,32 @@ +[metadata] +name = ansible-collection-backend +summary = Ansible collections for managing OTC Backend +description-file = + README.md + +author = opentelekomcloud +home-page = https://open.telekom.cloud +classifier = + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + Intended Audience :: System Administrators + Intended Audience :: Information Technology + Topic :: System :: Systems Administration + Topic :: Utilities + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[files] +data_files = + share/ansible/collections/ansible_collections/opentelekomcloud/backend/ = README.md + share/ansible/collections/ansible_collections/opentelekomcloud/backend/plugins = plugins/* + +[wheel] +universal = 1 + +[pbr] +skip_authors = True +skip_changelog = True diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..61d59db --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +# 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 setuptools + +setuptools.setup( + setup_requires=['pbr'], + pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..826c7f3 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,10 @@ +ansible-core +ansible-lint==5.4.0 +pycodestyle==2.6.0 +flake8==3.8.4 +pylint +voluptuous +yamllint +rstcheck +ruamel.yaml +requests diff --git a/tests/integration/integration_config.yml b/tests/integration/integration_config.yml new file mode 100644 index 0000000..d18d574 --- /dev/null +++ b/tests/integration/integration_config.yml @@ -0,0 +1 @@ +cloud: "otc" diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt new file mode 100644 index 0000000..74b54db --- /dev/null +++ b/tests/integration/requirements.txt @@ -0,0 +1,2 @@ +otcextensions +openstacksdk diff --git a/tests/integration/targets/service_catalog/tasks/main.yaml b/tests/integration/targets/service_catalog/tasks/main.yaml new file mode 100644 index 0000000..17b67a3 --- /dev/null +++ b/tests/integration/targets/service_catalog/tasks/main.yaml @@ -0,0 +1,19 @@ +--- +- ansible.builtin.set_fact: + services: + compute: + name: "nova" + environments: + production: + endpoints: + eu-de: + - url: "https://ecs.eu-de.otc.t-systems.com/v2.1/$(tenant_id)s" + eu-nl: + - url: "https://ecs.eu-nl.otc.t-systems.com/v2.1/$(tenant_id)s" + +- name: Check run + opentelekomcloud.backend.service_catalog: + cloud: "{{ cloud }}" + environment: "production" + services: "{{ services }}" + check_mode: true diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..1805656 --- /dev/null +++ b/tox.ini @@ -0,0 +1,93 @@ +[tox] +minversion = 3.1 +envlist = pep8 +skipsdist = True +ignore_basepython_conflict = True + +[testenv] +skip_install = True +install_command = python -m pip install {opts} {packages} +basepython = python3 +passenv = + OS_* + pip: PIP_INSTALL +setenv = + VIRTUAL_ENV={envdir} + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C + OS_LOG_CAPTURE={env:OS_LOG_CAPTURE:true} + OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:true} + OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:true} + pip: PIP_INSTALL={env:PIP_INSTALL:true} +deps = + -r{toxinidir}/test-requirements.txt + pip: {toxinidir} + +commands = stestr run {posargs} + stestr slowest + +[testenv:pep8] +commands = + flake8 + +[testenv:build] +deps = + pbr + ruamel.yaml + ansible-base + +commands = + ansible --version + ansible-galaxy collection build --force {toxinidir} --output-path {toxinidir}/build_artifact + +[testenv:linters] +passenv = * +deps = + {[testenv]deps} +commands = + ansible-lint -vvv + +[testenv:linters-2.9] +passenv = {[testenv:linters]passenv} +commands = {[testenv:linters]commands} +deps = + -r{toxinidir}/test-requirements-2.9.txt + +[testenv:venv] +deps = + -r{toxinidir}/test-requirements.txt +commands = {posargs} + +[flake8] +# W503 Is supposed to be off by default but in the latest pycodestyle isn't. +# Also, both openstacksdk and Donald Knuth disagree with the rule. Line +# breaks should occur before the binary operator for readability. +# H4 are rules for docstrings. Maybe we should clean them? +# E501,E402,H301 are ignored so we can import the existing +# modules unchanged and then clean them in subsequent patches. +ignore = W503,H4,E501,E402,H301 +show-source = True +exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,ansible_collections + +[testenv:ansible] +# Need to pass some env vars for the Ansible playbooks +passenv = HOME USER ANSIBLE_* +deps = + {[testenv]deps} +commands = + /bin/bash {toxinidir}/ci/run-ansible-tests-collection.sh -e {envdir} {posargs} + +# PIP job runs with Ansible-2.9 +[testenv:ansible-pip] +deps = + -r{toxinidir}/test-requirements-2.9.txt + {toxinidir} +passenv = {[testenv:ansible]passenv} +commands = {[testenv:ansible]commands} + +[testenv:ansible-2.9] +deps = + -r{toxinidir}/test-requirements-2.9.txt +passenv = {[testenv:ansible]passenv} +commands = {[testenv:ansible]commands} diff --git a/zuul.yaml b/zuul.yaml new file mode 100644 index 0000000..0de92fc --- /dev/null +++ b/zuul.yaml @@ -0,0 +1,63 @@ +# yamllint disable +--- +- job: + name: gitcontrol-test-integration + parent: ansible-collection-test-integration + description: | + Execute ansible-test integration for the collection + vars: + ansible_test_test_command: "integration" + ansible_test_no_temp_unicode: true + files: + plugins/module_utils/git.py + plugins/module_utils/github.py + plugins/modules/github + pre-run: tests/playbooks/pre.yaml + secrets: + - secret: gitcontrol-secret + name: gitcontrol + +- secret: + name: gitcontrol-secret + data: + token: !encrypted/pkcs1-oaep + - QduY2mJaxznljZMLGoGE/LcHwb0Fk4QRcqX8DY+a8i320Rl0GdRE9yfFwkxfdFQ245UXT + ZgcnHsuofTCbRQOWkt5eEN/+OqKqgvtDyP2QmOfOksbTUhnpWR1jV9/wDgqXIKTH7USgf + EQbnaSBYCgUTIi2BZVhJBPEtb9AvO8h1NcrhCxR4R8Cw8PJ/bOzPAgfvZ/D9067S9smgz + cKZyOuloBhwVRskdbj5tbvViya44cGDs13j6Dpvv1C2tgdVctM/6iHwvXyOL2PtTu5LCP + 3OVxgZCWx3XPzgfUl+QwL5KZSaDmsU6b7v5lr89HcTDrVmu02El6sEkMbUPQ/dDB6/7LK + fzMxCXz17bSWehIt1ASC+e+1jkfZKXHC5ss/6ebzRESdEeYDMb/6ueq3FPNslRbKwoGTK + 54D8fSOIagjg+F+AYJjXaozslsj5S1Uv7HgGj6Ecgv75Hcx8/jJiUsqyHazx7EZ4CDBfG + Lh28IR72f6JqALD01ontiYLWb7CdlxIa0zzNmmiA2oC/Zcvnef6hpSdvoFwmamY3HNUsM + VsvUiEJiT0uRropSKEr4q3/uKbwsqqbQ8TkWj/CaKbZ+MeIR4N4WO54r81K8W7p/3W6pt + qUFW1s/mMQWoYeQRh1gPHetpEgGfVP8d636Dk0hzK33vuvaJIfQdZxST7hkmuw= + +- project: + merge-mode: squash-merge + vars: + ansible_collection_namespace: "opentelekomcloud" + ansible_collection_name: "gitcontrol" + check: + jobs: + - otc-tox-pep8 + - otc-tox-linters + - ansible-collection-test-sanity + - ansible-collection-build + - ansible-collection-docs + check-post: + jobs: + - gitcontrol-test-integration + gate: + jobs: + - otc-tox-pep8 + - otc-tox-linters + - ansible-collection-test-sanity + - ansible-collection-build + - ansible-collection-docs + - gitcontrol-test-integration + release: + jobs: + - release-ansible-collection + promote: + jobs: + - promote-ansible-collection-docs