diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 7122c14..43a8e93 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -1,5 +1,5 @@ # README FIRST -# 1. replace "NAMESPACE/COLLECTION_NAME" with the correct name, ie "community/zabbix" +# 1. replace "community/okd" with the correct name, ie "community/zabbix" # 2. If you don't have unit tests remove that section # 3. If your collection depends on other collections ensure they are installed, see "Install collection dependencies" # If you need help please ask in #ansible-devel on Freenode IRC @@ -22,9 +22,7 @@ jobs: strategy: matrix: ansible: - # It's important that Sanity is tested against all stable-X.Y branches - # Testing against `devel` may fail as new tests are added. - # - stable-2.9 # Only if your collection supports Ansible 2.9 + - stable-2.9 - stable-2.10 - devel python: @@ -35,14 +33,10 @@ jobs: - python: 3.8 # blocked by ansible/ansible#70155 runs-on: ubuntu-latest steps: - - # ansible-test requires the collection to be in a directory in the form - # .../ansible_collections/NAMESPACE/COLLECTION_NAME/ - - name: Check out code uses: actions/checkout@v2 with: - path: ansible_collections/NAMESPACE/COLLECTION_NAME + path: ansible_collections/community/okd - name: Set up Python ${{ matrix.ansible }} uses: actions/setup-python@v2 @@ -58,122 +52,60 @@ jobs: # Explicity specify the version of Python we want to test - name: Run sanity tests run: ansible-test sanity --docker -v --color --python ${{ matrix.python }} - working-directory: ./ansible_collections/NAMESPACE/COLLECTION_NAME - -### -# Unit tests (OPTIONAL) -# -# https://docs.ansible.com/ansible/latest/dev_guide/testing_units.html - - units: - runs-on: ubuntu-latest - name: Units (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}) - strategy: - # As soon as the first unit test fails, cancel the others to free up the CI queue - fail-fast: true - matrix: - ansible: - # - stable-2.9 # Only if your collection supports Ansible 2.9 - - stable-2.10 - - devel - python: - - 2.7 - - 3.7 - - 3.8 - exclude: - - python: 3.8 # blocked by ansible/ansible#70155 - - steps: - - name: Check out code - uses: actions/checkout@v2 - with: - path: ansible_collections/NAMESPACE/COLLECTION_NAME - - - name: Set up Python ${{ matrix.ansible }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - - name: Install ansible-base (${{ matrix.ansible }}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - # OPTIONAL If your unit test requires Python libraries from other collections - # Install them like this - - name: Install collection dependencies - run: ansible-galaxy collection install ansible.netcommon -p . - - # Run the unit tests - - name: Run unit test - run: ansible-test units -v --color --python ${{ matrix.python }} --docker --coverage - working-directory: ./ansible_collections/NAMESPACE/COLLECTION_NAME - - # ansible-test support producing code coverage date - - name: Generate coverage report - run: ansible-test coverage xml -v --requirements --group-by command --group-by version - working-directory: ./ansible_collections/NAMESPACE/COLLECTION_NAME - - # See the reports at https://codecov.io/gh/ansible_collections/GITHUBORG/REPONAME - - uses: codecov/codecov-action@v1 - with: - fail_ci_if_error: false + working-directory: ./ansible_collections/community/okd ### # Integration tests (RECOMMENDED) # # https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html + # integration: + # runs-on: ubuntu-latest + # name: Integration (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}}) + # strategy: + # fail-fast: false + # matrix: + # ansible: + # - stable-2.9 + # - stable-2.10 + # - devel + # python: + # - 2.7 + # - 3.7 + # - 3.8 + # exclude: + # - python: 3.8 # blocked by ansible/ansible#70155 -# If the application you are testing is available as a docker container and you want to test -# multiple versions see the following for an example: -# https://github.com/ansible-collections/community.zabbix/tree/master/.github/workflows + # steps: + # - name: Check out code + # uses: actions/checkout@v2 + # with: + # path: ansible_collections/community/okd - integration: - runs-on: ubuntu-latest - name: I (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}}) - strategy: - fail-fast: false - matrix: - ansible: - # - stable-2.9 # Only if your collection supports Ansible 2.9 - - stable-2.10 - - devel - python: - - 2.7 - - 3.7 - - 3.8 - exclude: - - python: 3.8 # blocked by ansible/ansible#70155 + # - name: Set up Python ${{ matrix.ansible }} + # uses: actions/setup-python@v2 + # with: + # python-version: ${{ matrix.python }} - steps: - - name: Check out code - uses: actions/checkout@v2 - with: - path: ansible_collections/NAMESPACE/COLLECTION_NAME + # - name: Install ansible-base (${{ matrix.ansible }}) + # run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check - - name: Set up Python ${{ matrix.ansible }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} + # # OPTIONAL If your integration test requires Python libraries or modules from other collections + # # Install them like this + # - name: Install collection dependencies + # run: ansible-galaxy collection install community.kubernetes -p . - - name: Install ansible-base (${{ matrix.ansible }}) - run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check + # # Run the integration tests + # - name: Run integration test + # run: ansible-test integration -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --docker --coverage + # working-directory: ./ansible_collections/community/okd - # OPTIONAL If your integration test requires Python libraries or modules from other collections - # Install them like this - - name: Install collection dependencies - run: ansible-galaxy collection install ansible.netcommon -p . + # # ansible-test support producing code coverage date + # - name: Generate coverage report + # run: ansible-test coverage xml -v --requirements --group-by command --group-by version + # working-directory: ./ansible_collections/community/okd - # Run the integration tests - - name: Run integration test - run: ansible-test integration -v --color --retry-on-error --continue-on-error --diff --python ${{ matrix.python }} --docker --coverage - working-directory: ./ansible_collections/NAMESPACE/COLLECTION_NAME - - # ansible-test support producing code coverage date - - name: Generate coverage report - run: ansible-test coverage xml -v --requirements --group-by command --group-by version - working-directory: ./ansible_collections/NAMESPACE/COLLECTION_NAME - - # See the reports at https://codecov.io/gh/ansible_collections/GITHUBORG/REPONAME - - uses: codecov/codecov-action@v1 - with: - fail_ci_if_error: false + # # See the reports at https://codecov.io/gh/ansible_collections/GITHUBORG/REPONAME + # - uses: codecov/codecov-action@v1 + # with: + # fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 740c811..f97b787 100644 --- a/.gitignore +++ b/.gitignore @@ -1,132 +1,14 @@ -/tests/output/ -/changelogs/.plugin-cache.yaml - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -pip-wheel-metadata/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ - -# Translations -*.mo -*.pot - -# Django stuff: +*.retry +.idea *.log -local_settings.py -db.sqlite3 -db.sqlite3-journal +__pycache__/ -# Flask stuff: -instance/ -.webassets-cache +# Galaxy artifacts. +*.tar.gz -# Scrapy stuff: -.scrapy +# Changelog cache files. +changelogs/.plugin-cache.yaml -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -.python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ +# Temporary test files. +tests/output +tests/integration/cloud-config-* diff --git a/LICENSE b/LICENSE index f288702..e72bfdd 100644 --- a/LICENSE +++ b/LICENSE @@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read -. +. \ No newline at end of file diff --git a/README.md b/README.md index e46a0a1..f6e7d45 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,96 @@ -# collection_template -You can build a new repository for an Ansible Collection using this template by following [Creating a repository from a template](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template). This README.md contains recommended headings for your collection README.md, with comments describing what each section should contain. Once you have created your collection repository, delete this paragraph and the title above it from your README.md. +# OKD/OpenShift Collection for Ansible -# Foo Collection - -[![CI](https://github.com/ansible-collections/REPONAMEHERE/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/REPONAMEHERE/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/REPONAMEHERE)](https://codecov.io/gh/ansible-collections/REPONAMEHERE) +[![CI](https://github.com/ansible-collections/community.okd/workflows/CI/badge.svg?event=push)](https://github.com/ansible-collections/community.okd/actions) [![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.okd)](https://codecov.io/gh/ansible-collections/community.okd) - +This repo hosts the `community.okd` Ansible Collection. -## Tested with Ansible - - - -## External requirements - - - -### Supported connections - +The collection includes a variety of Ansible content to help automate the management of applications in OKD/OpenShift clusters, as well as the provisioning and maintenance of clusters themselves. ## Included content - +Click on the name of a plugin or module to view that content's documentation: -## Using this collection + - **Inventory Source**: + - [openshift](https://docs.ansible.com/ansible/2.10/collections/community/kubernetes/openshift_inventory.html) - +## Installation and Usage -See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. +### Installing the Collection from Ansible Galaxy -## Contributing to this collection +Before using the OKD collection, you need to install it with the Ansible Galaxy CLI: - + ansible-galaxy collection install community.okd +You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml`, using the format: -## Release notes +```yaml +--- +collections: + - name: community.okd + version: 0.1.0 +``` -See the [changelog](https://github.com/ansible-collections/REPONAMEHERE/tree/main/CHANGELOG.rst). +### Installing the OpenShift Python Library -## Roadmap +Content in this collection requires the [OpenShift Python client](https://pypi.org/project/openshift/) to interact with Kubernetes' APIs. You can install it with: - + pip3 install openshift -## More information +### Using modules from the OKD Collection in your playbooks - +It's preferable to use content in this collection using their Fully Qualified Collection Namespace (FQCN), for example `community.okd.openshift`: -- [Ansible Collection overview](https://github.com/ansible-collections/overview) -- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) -- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) -- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) -- [The Bullhorn (the Ansible Contributor newsletter)](https://us19.campaign-archive.com/home/?u=56d874e027110e35dea0e03c1&id=d6635f5420) -- [Changes impacting Contributors](https://github.com/ansible-collections/overview/issues/45) +```yaml +--- +plugin: community.okd.openshift +connections: + - namespaces: + - testing +``` -## Licensing +For documentation on how to use individual plugins included in this collection, please see the links in the 'Included content' section earlier in this README. - +## Testing and Development -GNU General Public License v3.0 or later. +If you want to develop new content for this collection or improve what's already here, the easiest way to work on the collection is to clone it into one of the configured [`COLLECTIONS_PATHS`](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths), and work on it there. -See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. +### Testing with `ansible-test` + +The `tests` directory contains configuration for running sanity and integration tests using [`ansible-test`](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html). + +You can run the collection's test suites with the commands: + + ansible-test sanity --docker -v --color + ansible-test integration --docker -v --color + +## Publishing New Versions + +The current process for publishing new versions of the OKD Collection is manual, and requires a user who has access to the `community.okd` namespace on Ansible Galaxy to publish the build artifact. + + 1. Ensure you're running Ansible from devel, so the [`build_ignore` key](https://github.com/ansible/ansible/issues/67130) in `galaxy.yml` is used. + 1. Run `git clean -x -d -f` in this repository's directory to clean out any extra files which should not be included. + 1. Update `galaxy.yml` and this README's `requirements.yml` example with the new `version` for the collection. + 1. Update the CHANGELOG: + 1. Make sure you have [`antsibull-changelog`](https://pypi.org/project/antsibull-changelog/) installed. + 1. Make sure there are fragments for all known changes in `changelogs/fragments`. + 1. Run `antsibull-changelog release`. + 1. Commit the changes and create a PR with the changes. Wait for tests to pass, then merge it once they have. + 1. Tag the version in Git and push to GitHub. + 1. Run the following commands to build and release the new version on Galaxy: + + ``` + ansible-galaxy collection build + ansible-galaxy collection publish ./community-okd-$VERSION_HERE.tar.gz + ``` + +After the version is published, verify it exists on the [OKD Collection Galaxy page](https://galaxy.ansible.com/community/okd). + +## More Information + +For more information about Ansible's Kubernetes and OpenShift integrations, join the `#ansible-kubernetes` channel on Freenode IRC, and browse the resources in the [Kubernetes Working Group](https://github.com/ansible/community/wiki/Kubernetes) Community wiki page. + +## License + +GNU General Public License v3.0 or later + +See LICENCE to see the full text. diff --git a/changelogs/config.yaml b/changelogs/config.yaml index 08c3ba7..b661a63 100644 --- a/changelogs/config.yaml +++ b/changelogs/config.yaml @@ -1,8 +1,9 @@ +--- changelog_filename_template: ../CHANGELOG.rst changelog_filename_version_depth: 0 changes_file: changelog.yaml changes_format: combined -keep_fragments: false +keep_fragments: true mention_ancestor: true new_plugins_after_name: removed_features notesdir: fragments @@ -25,5 +26,5 @@ sections: - Bugfixes - - known_issues - Known Issues -title: CHANGE THIS IN changelogs/config.yaml! +title: OKD Collection trivial_section_name: trivial diff --git a/changelogs/fragments/.keep b/changelogs/fragments/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..71e957c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +--- +coverage: + precision: 2 + round: down + range: "70...100" + status: + project: + default: false diff --git a/galaxy.yml b/galaxy.yml index 8f4180d..9bf96fe 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,23 +1,29 @@ -# See https://docs.ansible.com/ansible/latest/dev_guide/collections_galaxy_meta.html - -namespace: community -name: FIXME -version: 0.1.0 -readme: README.md +--- authors: - - YOUR NAME (github.com/YOURGITHUB) -description: null -license_file: COPYING + - geerlingguy (https://www.jeffgeerling.com/) + - fabianvf (https://github.com/fabianvf) + - willthames (https://github.com/willthames) + - Akasurde (https://github.com/akasurde) +dependencies: + community.kubernetes: '>=1.0.0' +description: OKD Collection for Ansible. +documentation: '' +homepage: '' +issues: https://github.com/ansible-collections/community.okd/issues +license_file: LICENSE +name: okd +namespace: community +readme: README.md +repository: https://github.com/ansible-collections/community.okd tags: -# tags so people can search for collections https://galaxy.ansible.com/search -# tags are all lower-case, no spaces, no dashes. - - example1 - - example2 -repository: https://github.com/ansible-collections/community.REPO_NAME -#documentation: https://github.com/ansible-collection-migration/community.REPO_NAME/tree/main/docs -homepage: https://github.com/ansible-collections/community.REPO_NAME -issues: https://github.com/ansible-collections/community.REPO_NAME/issues + - kubernetes + - k8s + - cloud + - infrastructure + - openshift + - okd + - cluster +version: 0.1.0 build_ignore: -# https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#ignoring-files-and-folders - - .gitignore - - changelogs/.plugin-cache.yaml + - .DS_Store + - '*.tar.gz' diff --git a/meta/runtime.yml b/meta/runtime.yml index 2ee3c9f..43bbe45 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -1,2 +1,2 @@ --- -requires_ansible: '>=2.9.10' +requires_ansible: '>=2.9' diff --git a/plugins/inventory/openshift.py b/plugins/inventory/openshift.py new file mode 100644 index 0000000..1061003 --- /dev/null +++ b/plugins/inventory/openshift.py @@ -0,0 +1,202 @@ +# Copyright (c) 2018 Ansible Project +# 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 = ''' + name: openshift + plugin_type: inventory + author: + - Chris Houseknecht <@chouseknecht> + + short_description: OpenShift inventory source + + description: + - Fetch containers, services and routes for one or more clusters + - Groups by cluster name, namespace, namespace_services, namespace_pods, namespace_routes, and labels + - Uses openshift.(yml|yaml) YAML configuration file to set parameter values. + + options: + plugin: + description: token that ensures this is a source file for the 'openshift' plugin. + required: True + choices: ['openshift'] + connections: + description: + - Optional list of cluster connection settings. If no connections are provided, the default + I(~/.kube/config) and active context will be used, and objects will be returned for all namespaces + the active user is authorized to access. + suboptions: + name: + description: + - Optional name to assign to the cluster. If not provided, a name is constructed from the server + and port. + kubeconfig: + description: + - Path to an existing Kubernetes config file. If not provided, and no other connection + options are provided, the OpenShift client will attempt to load the default + configuration file from I(~/.kube/config.json). Can also be specified via K8S_AUTH_KUBECONFIG + environment variable. + context: + description: + - The name of a context found in the config file. Can also be specified via K8S_AUTH_CONTEXT environment + variable. + host: + description: + - Provide a URL for accessing the API. Can also be specified via K8S_AUTH_HOST environment variable. + api_key: + description: + - Token used to authenticate with the API. Can also be specified via K8S_AUTH_API_KEY environment + variable. + username: + description: + - Provide a username for authenticating with the API. Can also be specified via K8S_AUTH_USERNAME + environment variable. + password: + description: + - Provide a password for authenticating with the API. Can also be specified via K8S_AUTH_PASSWORD + environment variable. + client_cert: + description: + - Path to a certificate used to authenticate with the API. Can also be specified via K8S_AUTH_CERT_FILE + environment variable. + aliases: [ cert_file ] + client_key: + description: + - Path to a key file used to authenticate with the API. Can also be specified via K8S_AUTH_KEY_FILE + environment variable. + aliases: [ key_file ] + ca_cert: + description: + - Path to a CA certificate used to authenticate with the API. Can also be specified via + K8S_AUTH_SSL_CA_CERT environment variable. + aliases: [ ssl_ca_cert ] + validate_certs: + description: + - "Whether or not to verify the API server's SSL certificates. Can also be specified via + K8S_AUTH_VERIFY_SSL environment variable." + type: bool + aliases: [ verify_ssl ] + namespaces: + description: + - List of namespaces. If not specified, will fetch all containers for all namespaces user is authorized + to access. + + requirements: + - "python >= 2.7" + - "openshift >= 0.6" + - "PyYAML >= 3.11" +''' + +EXAMPLES = ''' +# File must be named openshift.yaml or openshift.yml + +# Authenticate with token, and return all pods and services for all namespaces +plugin: community.okd.openshift +connections: + - host: https://192.168.64.4:8443 + api_key: xxxxxxxxxxxxxxxx + verify_ssl: false + +# Use default config (~/.kube/config) file and active context, and return objects for a specific namespace +plugin: community.okd.openshift +connections: + - namespaces: + - testing + +# Use a custom config file, and a specific context. +plugin: community.okd.openshift +connections: + - kubeconfig: /path/to/config + context: 'awx/192-168-64-4:8443/developer' +''' + +from ansible_collections.community.kubernetes.plugins.inventory.k8s import K8sInventoryException, InventoryModule as K8sInventoryModule, format_dynamic_api_exc + +try: + from openshift.dynamic.exceptions import DynamicApiError +except ImportError: + pass + + +class InventoryModule(K8sInventoryModule): + NAME = 'community.okd.openshift' + + transport = 'oc' + + def fetch_objects(self, connections): + super(InventoryModule, self).fetch_objects(connections) + + if connections: + if not isinstance(connections, list): + raise K8sInventoryException("Expecting connections to be a list.") + + for connection in connections: + client = self.get_api_client(**connection) + name = connection.get('name', self.get_default_host_name(client.configuration.host)) + if connection.get('namespaces'): + namespaces = connection['namespaces'] + else: + namespaces = self.get_available_namespaces(client) + for namespace in namespaces: + self.get_routes_for_namespace(client, name, namespace) + else: + client = self.get_api_client() + name = self.get_default_host_name(client.configuration.host) + namespaces = self.get_available_namespaces(client) + for namespace in namespaces: + self.get_routes_for_namespace(client, name, namespace) + + def get_routes_for_namespace(self, client, name, namespace): + v1_route = client.resources.get(api_version='v1', kind='Route') + try: + obj = v1_route.get(namespace=namespace) + except DynamicApiError as exc: + self.display.debug(exc) + raise K8sInventoryException('Error fetching Routes list: %s' % format_dynamic_api_exc(exc)) + + namespace_group = 'namespace_{0}'.format(namespace) + namespace_routes_group = '{0}_routes'.format(namespace_group) + + self.inventory.add_group(name) + self.inventory.add_group(namespace_group) + self.inventory.add_child(name, namespace_group) + self.inventory.add_group(namespace_routes_group) + self.inventory.add_child(namespace_group, namespace_routes_group) + for route in obj.items: + route_name = route.metadata.name + route_annotations = {} if not route.metadata.annotations else dict(route.metadata.annotations) + + self.inventory.add_host(route_name) + + if route.metadata.labels: + # create a group for each label_value + for key, value in route.metadata.labels: + group_name = 'label_{0}_{1}'.format(key, value) + self.inventory.add_group(group_name) + self.inventory.add_child(group_name, route_name) + route_labels = dict(route.metadata.labels) + else: + route_labels = {} + + self.inventory.add_child(namespace_routes_group, route_name) + + # add hostvars + self.inventory.set_variable(route_name, 'labels', route_labels) + self.inventory.set_variable(route_name, 'annotations', route_annotations) + self.inventory.set_variable(route_name, 'cluster_name', route.metadata.clusterName) + self.inventory.set_variable(route_name, 'object_type', 'route') + self.inventory.set_variable(route_name, 'self_link', route.metadata.selfLink) + self.inventory.set_variable(route_name, 'resource_version', route.metadata.resourceVersion) + self.inventory.set_variable(route_name, 'uid', route.metadata.uid) + + if route.spec.host: + self.inventory.set_variable(route_name, 'host', route.spec.host) + + if route.spec.path: + self.inventory.set_variable(route_name, 'path', route.spec.path) + + if hasattr(route.spec.port, 'targetPort') and route.spec.port.targetPort: + self.inventory.set_variable(route_name, 'port', dict(route.spec.port)) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..29c924b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 160 +ignore = W503,E402 diff --git a/tests/integration/targets/okd/README.md b/tests/integration/targets/okd/README.md new file mode 100644 index 0000000..153dfa5 --- /dev/null +++ b/tests/integration/targets/okd/README.md @@ -0,0 +1,19 @@ +Wait tests +---------- + +wait tests require at least one node, and don't work on the normal k8s +openshift-origin container as provided by ansible-test --docker -v k8s + +minikube, Kubernetes from Docker or any other Kubernetes service will +suffice. + +If kubectl is already using the right config file and context, you can +just do + +``` +cd tests/integration/targets/okd +./runme.sh -vv +``` + +otherwise set one or both of `K8S_AUTH_KUBECONFIG` and `K8S_AUTH_CONTEXT` +and use the same command diff --git a/tests/integration/targets/okd/defaults/main.yml b/tests/integration/targets/okd/defaults/main.yml new file mode 100644 index 0000000..e46ca26 --- /dev/null +++ b/tests/integration/targets/okd/defaults/main.yml @@ -0,0 +1,2 @@ +--- +k8s_openshift: true diff --git a/tests/integration/targets/okd/files/crd-resource.yml b/tests/integration/targets/okd/files/crd-resource.yml new file mode 100644 index 0000000..23d0663 --- /dev/null +++ b/tests/integration/targets/okd/files/crd-resource.yml @@ -0,0 +1,21 @@ +--- +apiVersion: certmanager.k8s.io/v1alpha1 +kind: Certificate +metadata: + name: acme-crt +spec: + secretName: acme-crt-secret + dnsNames: + - foo.example.com + - bar.example.com + acme: + config: + - ingressClass: nginx + domains: + - foo.example.com + - bar.example.com + issuerRef: + name: letsencrypt-prod + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: Issuer diff --git a/tests/integration/targets/okd/files/kuard-extra-property.yml b/tests/integration/targets/okd/files/kuard-extra-property.yml new file mode 100644 index 0000000..bed92bc --- /dev/null +++ b/tests/integration/targets/okd/files/kuard-extra-property.yml @@ -0,0 +1,22 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: kuard + name: kuard + namespace: default +spec: + replicas: 3 + selector: + matchLabels: + app: kuard + unwanted: value + template: + metadata: + labels: + app: kuard + spec: + containers: + - image: gcr.io/kuar-demo/kuard-amd64:1 + name: kuard diff --git a/tests/integration/targets/okd/files/kuard-invalid-type.yml b/tests/integration/targets/okd/files/kuard-invalid-type.yml new file mode 100644 index 0000000..72505f8 --- /dev/null +++ b/tests/integration/targets/okd/files/kuard-invalid-type.yml @@ -0,0 +1,21 @@ +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + app: kuard + name: kuard + namespace: default +spec: + replicas: hello + selector: + matchLabels: + app: kuard + template: + metadata: + labels: + app: kuard + spec: + containers: + - image: gcr.io/kuar-demo/kuard-amd64:1 + name: kuard diff --git a/tests/integration/targets/okd/files/setup-crd.yml b/tests/integration/targets/okd/files/setup-crd.yml new file mode 100644 index 0000000..9c01bc1 --- /dev/null +++ b/tests/integration/targets/okd/files/setup-crd.yml @@ -0,0 +1,15 @@ +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: certificates.certmanager.k8s.io +spec: + group: certmanager.k8s.io + version: v1alpha1 + scope: Namespaced + names: + kind: Certificate + plural: certificates + shortNames: + - cert + - certs diff --git a/tests/integration/targets/okd/handlers/main.yml b/tests/integration/targets/okd/handlers/main.yml new file mode 100644 index 0000000..efb5408 --- /dev/null +++ b/tests/integration/targets/okd/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: delete temporary directory + file: + path: "{{ remote_tmp_dir }}" + state: absent + no_log: yes diff --git a/tests/integration/targets/okd/library/README.md b/tests/integration/targets/okd/library/README.md new file mode 100644 index 0000000..ac31229 --- /dev/null +++ b/tests/integration/targets/okd/library/README.md @@ -0,0 +1,3 @@ +# README + +The `test_tempfile.py` module added here is only used for the `setup_remote_tmp_dir.yml` temporary directory setup task. It is a clone of the `tempfile.py` community-supported Ansible module, and has to be included with the tests here because it is not available in the `ansible-base` distribution against which this collection is tested. diff --git a/tests/integration/targets/okd/library/test_tempfile.py b/tests/integration/targets/okd/library/test_tempfile.py new file mode 100644 index 0000000..c89f5a3 --- /dev/null +++ b/tests/integration/targets/okd/library/test_tempfile.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2016, Krzysztof Magosa +# Copyright: (c) 2017, Ansible Project +# 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: test_tempfile + +short_description: Creates temporary files and directories + +description: + - The C(test_tempfile) module creates temporary files and directories. C(mktemp) command takes different parameters on various systems, this module helps + to avoid troubles related to that. Files/directories created by module are accessible only by creator. In case you need to make them world-accessible + you need to use M(ansible.builtin.file) module. + - For Windows targets, use the M(ansible.builtin.win_tempfile) module instead. + +options: + state: + description: + - Whether to create file or directory. + type: str + choices: [ directory, file ] + default: file + path: + description: + - Location where temporary file or directory should be created. + - If path is not specified, the default system temporary directory will be used. + type: path + prefix: + description: + - Prefix of file/directory name created by module. + type: str + default: ansible. + suffix: + description: + - Suffix of file/directory name created by module. + type: str + default: "" + +seealso: +- module: file +- module: win_tempfile + +author: + - Krzysztof Magosa (@krzysztof-magosa) +''' + +EXAMPLES = """ +- name: create temporary build directory + test_tempfile: + state: directory + suffix: build + +- name: create temporary file + test_tempfile: + state: file + suffix: temp + register: tempfile_1 + +- name: use the registered var and the file module to remove the temporary file + file: + path: "{{ tempfile_1.path }}" + state: absent + when: tempfile_1.path is defined +""" + +RETURN = ''' +path: + description: Path to created file or directory + returned: success + type: str + sample: "/tmp/ansible.bMlvdk" +''' + +from os import close +from tempfile import mkstemp, mkdtemp +from traceback import format_exc + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native + + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='file', choices=['file', 'directory']), + path=dict(type='path'), + prefix=dict(type='str', default='ansible.'), + suffix=dict(type='str', default=''), + ), + ) + + try: + if module.params['state'] == 'file': + handle, path = mkstemp( + prefix=module.params['prefix'], + suffix=module.params['suffix'], + dir=module.params['path'], + ) + close(handle) + elif module.params['state'] == 'directory': + path = mkdtemp( + prefix=module.params['prefix'], + suffix=module.params['suffix'], + dir=module.params['path'], + ) + + module.exit_json(changed=True, path=path) + except Exception as e: + module.fail_json(msg=to_native(e), exception=format_exc()) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/okd/meta/main.yml b/tests/integration/targets/okd/meta/main.yml new file mode 100644 index 0000000..23d65c7 --- /dev/null +++ b/tests/integration/targets/okd/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [] diff --git a/tests/integration/targets/okd/tasks/main.yml b/tests/integration/targets/okd/tasks/main.yml new file mode 100644 index 0000000..1a97171 --- /dev/null +++ b/tests/integration/targets/okd/tasks/main.yml @@ -0,0 +1,50 @@ +--- +- include_tasks: setup_remote_tmp_dir.yml + +- set_fact: + virtualenv: "{{ remote_tmp_dir }}/virtualenv" + virtualenv_command: "{{ ansible_python_interpreter }} -m virtualenv" + +- set_fact: + virtualenv_interpreter: "{{ virtualenv }}/bin/python" + +- pip: + name: virtualenv + +# Test graceful failure for older versions of openshift + +- pip: + name: + - openshift==0.6.0 + - kubernetes==6.0.0 + - coverage + virtualenv: "{{ virtualenv }}" + virtualenv_command: "{{ virtualenv_command }}" + virtualenv_site_packages: no + +- include_tasks: older_openshift_fail.yml + vars: + ansible_python_interpreter: "{{ virtualenv_interpreter }}" + playbook_namespace: ansible-test-k8s-older-openshift + +- file: + path: "{{ virtualenv }}" + state: absent + no_log: yes + +# Test openshift + +- debug: + var: k8s_openshift + +- pip: + name: + - kubernetes-validate==1.12.0 + - openshift>=0.9.2 + - coverage + virtualenv: "{{ virtualenv }}" + virtualenv_command: "{{ virtualenv_command }}" + virtualenv_site_packages: no + +- include: openshift.yml + when: k8s_openshift | bool diff --git a/tests/integration/targets/okd/tasks/older_openshift_fail.yml b/tests/integration/targets/okd/tasks/older_openshift_fail.yml new file mode 100644 index 0000000..aecf978 --- /dev/null +++ b/tests/integration/targets/okd/tasks/older_openshift_fail.yml @@ -0,0 +1,71 @@ +--- +# TODO: Not available in ansible-base +# - python_requirements_info: +# dependencies: +# - openshift==0.6.0 +# - kubernetes==6.0.0 + +# append_hash +- name: use append_hash with ConfigMap + community.kubernetes.k8s: + definition: + metadata: + name: config-map-test + namespace: "{{ playbook_namespace }}" + apiVersion: v1 + kind: ConfigMap + data: + hello: world + append_hash: yes + ignore_errors: yes + register: k8s_append_hash + +- name: assert that append_hash fails gracefully + assert: + that: + - k8s_append_hash is failed + - "'Failed to import the required Python library (openshift >= 0.7.2)' in k8s_append_hash.msg" + - "'. This is required for append_hash.' in k8s_append_hash.msg" + +# validate +- name: attempt to use validate with older openshift + community.kubernetes.k8s: + definition: + metadata: + name: config-map-test + namespace: "{{ playbook_namespace }}" + apiVersion: v1 + kind: ConfigMap + data: + hello: world + validate: + fail_on_error: yes + ignore_errors: yes + register: k8s_validate + +- name: assert that validate fails gracefully + assert: + that: + - k8s_validate is failed + - "k8s_validate.msg == 'openshift >= 0.8.0 is required for validate'" + +# apply +- name: attempt to use apply with older openshift + community.kubernetes.k8s: + definition: + metadata: + name: config-map-test + namespace: "{{ playbook_namespace }}" + apiVersion: v1 + kind: ConfigMap + data: + hello: world + apply: yes + ignore_errors: yes + register: k8s_apply + +- name: assert that apply fails gracefully + assert: + that: + - k8s_apply is failed + - "k8s_apply.msg.startswith('Failed to import the required Python library (openshift >= 0.9.2)')" diff --git a/tests/integration/targets/okd/tasks/openshift.yml b/tests/integration/targets/okd/tasks/openshift.yml new file mode 100644 index 0000000..54949c1 --- /dev/null +++ b/tests/integration/targets/okd/tasks/openshift.yml @@ -0,0 +1,62 @@ +--- +# OpenShift Resources +- name: Create a project + community.kubernetes.k8s: + name: testing + kind: Project + api_version: v1 + apply: no + register: output + +- name: show output + debug: + var: output + +- name: Create deployment config + community.kubernetes.k8s: + state: present + inline: &dc + apiVersion: v1 + kind: DeploymentConfig + metadata: + name: elastic + labels: + app: galaxy + service: elastic + namespace: testing + spec: + template: + metadata: + labels: + app: galaxy + service: elastic + spec: + containers: + - name: elastic + volumeMounts: + - mountPath: /usr/share/elasticsearch/data + name: elastic-volume + command: ['elasticsearch'] + image: 'ansible/galaxy-elasticsearch:2.4.6' + volumes: + - name: elastic-volume + persistentVolumeClaim: + claimName: elastic-volume + replicas: 1 + strategy: + type: Rolling + register: output + +- name: Show output + debug: + var: output + +- name: Create deployment config again + community.kubernetes.k8s: + state: present + inline: *dc + register: output + +- name: DC creation should be idempotent + assert: + that: not output.changed diff --git a/tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml b/tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml new file mode 100644 index 0000000..8acdb49 --- /dev/null +++ b/tests/integration/targets/okd/tasks/setup_remote_tmp_dir.yml @@ -0,0 +1,12 @@ +--- +- name: create temporary directory + test_tempfile: + state: directory + suffix: .test + register: remote_tmp_dir + notify: + - delete temporary directory + +- name: record temporary directory + set_fact: + remote_tmp_dir: "{{ remote_tmp_dir.path }}" diff --git a/tests/integration/targets/okd/tasks/validate_installed.yml b/tests/integration/targets/okd/tasks/validate_installed.yml new file mode 100644 index 0000000..6f5956d --- /dev/null +++ b/tests/integration/targets/okd/tasks/validate_installed.yml @@ -0,0 +1,126 @@ +--- +- block: + - name: Create a namespace + community.kubernetes.k8s: + name: "{{ playbook_namespace }}" + kind: Namespace + + - copy: + src: files + dest: "{{ remote_tmp_dir }}" + + - name: incredibly simple ConfigMap + community.kubernetes.k8s: + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + name: hello + namespace: "{{ playbook_namespace }}" + validate: + fail_on_error: yes + register: k8s_with_validate + + - name: assert that k8s_with_validate succeeds + assert: + that: + - k8s_with_validate is successful + + - name: extra property does not fail without strict + community.kubernetes.k8s: + src: "{{ remote_tmp_dir }}/files/kuard-extra-property.yml" + namespace: "{{ playbook_namespace }}" + validate: + fail_on_error: yes + strict: no + + - name: extra property fails with strict + community.kubernetes.k8s: + src: "{{ remote_tmp_dir }}/files/kuard-extra-property.yml" + namespace: "{{ playbook_namespace }}" + validate: + fail_on_error: yes + strict: yes + ignore_errors: yes + register: extra_property + + - name: check that extra property fails with strict + assert: + that: + - extra_property is failed + + - name: invalid type fails at validation stage + community.kubernetes.k8s: + src: "{{ remote_tmp_dir }}/files/kuard-invalid-type.yml" + namespace: "{{ playbook_namespace }}" + validate: + fail_on_error: yes + strict: no + ignore_errors: yes + register: invalid_type + + - name: check that invalid type fails + assert: + that: + - invalid_type is failed + + - name: invalid type fails with warnings when fail_on_error is False + community.kubernetes.k8s: + src: "{{ remote_tmp_dir }}/files/kuard-invalid-type.yml" + namespace: "{{ playbook_namespace }}" + validate: + fail_on_error: no + strict: no + ignore_errors: yes + register: invalid_type_no_fail + + - name: check that invalid type fails + assert: + that: + - invalid_type_no_fail is failed + + - name: setup custom resource definition + community.kubernetes.k8s: + src: "{{ remote_tmp_dir }}/files/setup-crd.yml" + + - name: wait a few seconds + pause: + seconds: 5 + + - name: add custom resource definition + community.kubernetes.k8s: + src: "{{ remote_tmp_dir }}/files/crd-resource.yml" + namespace: "{{ playbook_namespace }}" + validate: + fail_on_error: yes + strict: yes + register: unknown_kind + + - name: check that unknown kind warns + assert: + that: + - unknown_kind is successful + - "'warnings' in unknown_kind" + + always: + - name: remove custom resource + community.kubernetes.k8s: + definition: "{{ lookup('file', role_path + '/files/crd-resource.yml') }}" + namespace: "{{ playbook_namespace }}" + state: absent + ignore_errors: yes + + - name: remove custom resource definitions + community.kubernetes.k8s: + definition: "{{ lookup('file', role_path + '/files/setup-crd.yml') }}" + state: absent + + - name: Delete namespace + community.kubernetes.k8s: + state: absent + definition: + - kind: Namespace + apiVersion: v1 + metadata: + name: "{{ playbook_namespace }}" + ignore_errors: yes diff --git a/tests/integration/targets/okd/tasks/validate_not_installed.yml b/tests/integration/targets/okd/tasks/validate_not_installed.yml new file mode 100644 index 0000000..91ddf9f --- /dev/null +++ b/tests/integration/targets/okd/tasks/validate_not_installed.yml @@ -0,0 +1,25 @@ +--- +# TODO: Not available in ansible-base +# - python_requirements_info: +# dependencies: +# - openshift +# - kubernetes +# - kubernetes-validate + +- community.kubernetes.k8s: + definition: + apiVersion: v1 + kind: ConfigMap + metadata: + name: hello + namespace: default + validate: + fail_on_error: yes + ignore_errors: yes + register: k8s_no_validate + +- name: assert that k8s_no_validate fails gracefully + assert: + that: + - k8s_no_validate is failed + - "k8s_no_validate.msg == 'kubernetes-validate python library is required to validate resources'"