mirror of
https://github.com/ansible/awx.git
synced 2024-10-27 00:55:06 +03:00
Merge pull request #6743 from john-westcott-iv/version_warning
Adding version checking to collection Reviewed-by: Bianca Henderson <beeankha@gmail.com> https://github.com/beeankha
This commit is contained in:
commit
60d2409321
2
Makefile
2
Makefile
@ -393,7 +393,7 @@ symlink_collection:
|
|||||||
ln -s $(shell pwd)/awx_collection $(COLLECTION_INSTALL)
|
ln -s $(shell pwd)/awx_collection $(COLLECTION_INSTALL)
|
||||||
|
|
||||||
build_collection:
|
build_collection:
|
||||||
ansible-playbook -i localhost, awx_collection/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION)
|
ansible-playbook -i localhost, awx_collection/tools/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION) -e '{"awx_template_version":false}'
|
||||||
ansible-galaxy collection build awx_collection --force --output-path=awx_collection
|
ansible-galaxy collection build awx_collection --force --output-path=awx_collection
|
||||||
|
|
||||||
install_collection: build_collection
|
install_collection: build_collection
|
||||||
|
@ -39,6 +39,7 @@ options:
|
|||||||
tower_config_file:
|
tower_config_file:
|
||||||
description:
|
description:
|
||||||
- Path to the Tower or AWX config file.
|
- Path to the Tower or AWX config file.
|
||||||
|
- If provided, the other locations for config files will not be considered.
|
||||||
type: path
|
type: path
|
||||||
|
|
||||||
notes:
|
notes:
|
||||||
|
@ -32,6 +32,15 @@ class ItemNotDefined(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class TowerModule(AnsibleModule):
|
class TowerModule(AnsibleModule):
|
||||||
|
# This gets set by the make process so whatever is in here is irrelevant
|
||||||
|
_COLLECTION_VERSION = "devel"
|
||||||
|
_COLLECTION_TYPE = "awx"
|
||||||
|
# This maps the collections type (awx/tower) to the values returned by the API
|
||||||
|
# Those values can be found in awx/api/generics.py line 204
|
||||||
|
collection_to_version = {
|
||||||
|
'awx': 'AWX',
|
||||||
|
'tower': 'Red Hat Ansible Tower',
|
||||||
|
}
|
||||||
url = None
|
url = None
|
||||||
honorred_settings = ('host', 'username', 'password', 'verify_ssl', 'oauth_token')
|
honorred_settings = ('host', 'username', 'password', 'verify_ssl', 'oauth_token')
|
||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
@ -45,6 +54,7 @@ class TowerModule(AnsibleModule):
|
|||||||
authenticated = False
|
authenticated = False
|
||||||
config_name = 'tower_cli.cfg'
|
config_name = 'tower_cli.cfg'
|
||||||
ENCRYPTED_STRING = "$encrypted$"
|
ENCRYPTED_STRING = "$encrypted$"
|
||||||
|
version_checked = False
|
||||||
|
|
||||||
def __init__(self, argument_spec, **kwargs):
|
def __init__(self, argument_spec, **kwargs):
|
||||||
args = dict(
|
args = dict(
|
||||||
@ -104,14 +114,6 @@ class TowerModule(AnsibleModule):
|
|||||||
local_dir = split(local_dir)[0]
|
local_dir = split(local_dir)[0]
|
||||||
config_files.insert(2, join(local_dir, ".{0}".format(self.config_name)))
|
config_files.insert(2, join(local_dir, ".{0}".format(self.config_name)))
|
||||||
|
|
||||||
for config_file in config_files:
|
|
||||||
if exists(config_file) and not isdir(config_file):
|
|
||||||
# Only throw a formatting error if the file exists and is not a directory
|
|
||||||
try:
|
|
||||||
self.load_config(config_file)
|
|
||||||
except ConfigFileException:
|
|
||||||
self.fail_json('The config file {0} is not properly formatted'.format(config_file))
|
|
||||||
|
|
||||||
# If we have a specified tower config, load it
|
# If we have a specified tower config, load it
|
||||||
if self.params.get('tower_config_file'):
|
if self.params.get('tower_config_file'):
|
||||||
duplicated_params = []
|
duplicated_params = []
|
||||||
@ -129,6 +131,14 @@ class TowerModule(AnsibleModule):
|
|||||||
except ConfigFileException as cfe:
|
except ConfigFileException as cfe:
|
||||||
# Since we were told specifically to load this we want it to fail if we have an error
|
# Since we were told specifically to load this we want it to fail if we have an error
|
||||||
self.fail_json(msg=cfe)
|
self.fail_json(msg=cfe)
|
||||||
|
else:
|
||||||
|
for config_file in config_files:
|
||||||
|
if exists(config_file) and not isdir(config_file):
|
||||||
|
# Only throw a formatting error if the file exists and is not a directory
|
||||||
|
try:
|
||||||
|
self.load_config(config_file)
|
||||||
|
except ConfigFileException:
|
||||||
|
self.fail_json('The config file {0} is not properly formatted'.format(config_file))
|
||||||
|
|
||||||
def load_config(self, config_path):
|
def load_config(self, config_path):
|
||||||
# Validate the config file is an actual file
|
# Validate the config file is an actual file
|
||||||
@ -374,6 +384,26 @@ class TowerModule(AnsibleModule):
|
|||||||
finally:
|
finally:
|
||||||
self.url = self.url._replace(query=None)
|
self.url = self.url._replace(query=None)
|
||||||
|
|
||||||
|
if not self.version_checked:
|
||||||
|
# In PY2 we get back an HTTPResponse object but PY2 is returning an addinfourl
|
||||||
|
# First try to get the headers in PY3 format and then drop down to PY2.
|
||||||
|
try:
|
||||||
|
tower_type = response.getheader('X-API-Product-Name', None)
|
||||||
|
tower_version = response.getheader('X-API-Product-Version', None)
|
||||||
|
except Exception:
|
||||||
|
tower_type = response.info().getheader('X-API-Product-Name', None)
|
||||||
|
tower_version = response.info().getheader('X-API-Product-Version', None)
|
||||||
|
|
||||||
|
if self._COLLECTION_TYPE not in self.collection_to_version or self.collection_to_version[self._COLLECTION_TYPE] != tower_type:
|
||||||
|
self.warn("You are using the {0} version of this collection but connecting to {1}".format(
|
||||||
|
self._COLLECTION_TYPE, tower_type
|
||||||
|
))
|
||||||
|
elif self._COLLECTION_VERSION != tower_version:
|
||||||
|
self.warn("You are running collection version {0} but connecting to tower version {1}".format(
|
||||||
|
self._COLLECTION_VERSION, tower_version
|
||||||
|
))
|
||||||
|
self.version_checked = True
|
||||||
|
|
||||||
response_body = ''
|
response_body = ''
|
||||||
try:
|
try:
|
||||||
response_body = response.read()
|
response_body = response.read()
|
||||||
@ -434,15 +464,6 @@ class TowerModule(AnsibleModule):
|
|||||||
# If we have neither of these, then we can try un-authenticated access
|
# If we have neither of these, then we can try un-authenticated access
|
||||||
self.authenticated = True
|
self.authenticated = True
|
||||||
|
|
||||||
def default_check_mode(self):
|
|
||||||
'''Execute check mode logic for Ansible Tower modules'''
|
|
||||||
if self.check_mode:
|
|
||||||
try:
|
|
||||||
result = self.get_endpoint('ping')
|
|
||||||
self.exit_json(**{'changed': True, 'tower_version': '{0}'.format(result['json']['version'])})
|
|
||||||
except(Exception) as excinfo:
|
|
||||||
self.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo))
|
|
||||||
|
|
||||||
def delete_if_needed(self, existing_item, on_delete=None):
|
def delete_if_needed(self, existing_item, on_delete=None):
|
||||||
# This will exit from the module on its own.
|
# This will exit from the module on its own.
|
||||||
# If the method successfully deletes an item and on_delete param is defined,
|
# If the method successfully deletes an item and on_delete param is defined,
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
- hosts: localhost
|
|
||||||
gather_facts: false
|
|
||||||
connection: local
|
|
||||||
vars:
|
|
||||||
collection_package: awx
|
|
||||||
collection_namespace: awx
|
|
||||||
collection_version: 0.0.1 # not for updating, pass in extra_vars
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Do file content replacements for non-default namespace or package name
|
|
||||||
block:
|
|
||||||
|
|
||||||
- name: Change module doc_fragments to support desired namespace and package names
|
|
||||||
replace:
|
|
||||||
path: "{{ item }}"
|
|
||||||
regexp: '^extends_documentation_fragment: awx.awx.auth$'
|
|
||||||
replace: 'extends_documentation_fragment: {{ collection_namespace }}.{{ collection_package }}.auth'
|
|
||||||
with_fileglob: "{{ playbook_dir }}/plugins/modules/tower_*.py"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item | basename }}"
|
|
||||||
|
|
||||||
- name: Change inventory file to support desired namespace and package names
|
|
||||||
replace:
|
|
||||||
path: "{{ playbook_dir }}/plugins/inventory/tower.py"
|
|
||||||
regexp: "^ NAME = 'awx.awx.tower' # REPLACE$"
|
|
||||||
replace: " NAME = '{{ collection_namespace }}.{{ collection_package }}.tower' # REPLACE"
|
|
||||||
when:
|
|
||||||
- (collection_package != 'awx') or (collection_namespace != 'awx')
|
|
||||||
|
|
||||||
- name: Template the galaxy.yml file
|
|
||||||
template:
|
|
||||||
src: "{{ playbook_dir }}/galaxy.yml.j2"
|
|
||||||
dest: "{{ playbook_dir }}/galaxy.yml"
|
|
||||||
force: true
|
|
@ -108,6 +108,7 @@ def run_module(request, collection_import):
|
|||||||
sanitize_dict(py_data)
|
sanitize_dict(py_data)
|
||||||
resp._content = bytes(json.dumps(django_response.data), encoding='utf8')
|
resp._content = bytes(json.dumps(django_response.data), encoding='utf8')
|
||||||
resp.status_code = django_response.status_code
|
resp.status_code = django_response.status_code
|
||||||
|
resp.headers = {'X-API-Product-Name': 'AWX', 'X-API-Product-Version': 'devel'}
|
||||||
|
|
||||||
if request.config.getoption('verbose') > 0:
|
if request.config.getoption('verbose') > 0:
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -120,7 +121,11 @@ def run_module(request, collection_import):
|
|||||||
|
|
||||||
def new_open(self, method, url, **kwargs):
|
def new_open(self, method, url, **kwargs):
|
||||||
r = new_request(self, method, url, **kwargs)
|
r = new_request(self, method, url, **kwargs)
|
||||||
return mock.MagicMock(read=mock.MagicMock(return_value=r._content), status=r.status_code)
|
m = mock.MagicMock(read=mock.MagicMock(return_value=r._content),
|
||||||
|
status=r.status_code,
|
||||||
|
getheader=mock.MagicMock(side_effect=r.headers.get)
|
||||||
|
)
|
||||||
|
return m
|
||||||
|
|
||||||
stdout_buffer = io.StringIO()
|
stdout_buffer = io.StringIO()
|
||||||
# Requies specific PYTHONPATH, see docs
|
# Requies specific PYTHONPATH, see docs
|
||||||
@ -245,7 +250,7 @@ def silence_deprecation():
|
|||||||
yield this_mock
|
yield this_mock
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture(autouse=True)
|
||||||
def silence_warning():
|
def silence_warning():
|
||||||
"""Warnings use global variable, same as deprecations."""
|
"""Warnings use global variable, same as deprecations."""
|
||||||
with mock.patch('ansible.module_utils.basic.AnsibleModule.warn') as this_mock:
|
with mock.patch('ansible.module_utils.basic.AnsibleModule.warn') as this_mock:
|
||||||
|
@ -152,7 +152,7 @@ def test_make_use_of_custom_credential_type(run_module, organization, admin_user
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_secret_field_write_twice(run_module, organization, admin_user, cred_type, silence_warning):
|
def test_secret_field_write_twice(run_module, organization, admin_user, cred_type):
|
||||||
val1 = '7rEZK38DJl58A7RxA6EC7lLvUHbBQ1'
|
val1 = '7rEZK38DJl58A7RxA6EC7lLvUHbBQ1'
|
||||||
result = run_module('tower_credential', dict(
|
result = run_module('tower_credential', dict(
|
||||||
name='Galaxy Token for Steve',
|
name='Galaxy Token for Steve',
|
||||||
|
@ -163,8 +163,7 @@ def test_job_template_with_survey_encrypted_default(run_module, admin_user, proj
|
|||||||
|
|
||||||
silence_warning.assert_called_once_with(
|
silence_warning.assert_called_once_with(
|
||||||
"The field survey_spec of job_template {0} has encrypted data and "
|
"The field survey_spec of job_template {0} has encrypted data and "
|
||||||
"may inaccurately report task is changed.".format(result['id'])
|
"may inaccurately report task is changed.".format(result['id']))
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@ -1,14 +1,67 @@
|
|||||||
from __future__ import (absolute_import, division, print_function)
|
from __future__ import (absolute_import, division, print_function)
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from requests.models import Response
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import json
|
|
||||||
|
def getheader(self, header_name, default):
|
||||||
|
mock_headers = {'X-API-Product-Name': 'not-junk', 'X-API-Product-Version': '1.2.3'}
|
||||||
|
return mock_headers.get(header_name, default)
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_config(collection_import):
|
def read(self):
|
||||||
|
return json.dumps({})
|
||||||
|
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
return 200
|
||||||
|
|
||||||
|
|
||||||
|
def mock_ping_response(self, method, url, **kwargs):
|
||||||
|
r = Response()
|
||||||
|
r.getheader = getheader.__get__(r)
|
||||||
|
r.read = read.__get__(r)
|
||||||
|
r.status = status.__get__(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_warning(collection_import, silence_warning):
|
||||||
|
TowerModule = collection_import('plugins.module_utils.tower_api').TowerModule
|
||||||
|
cli_data = {'ANSIBLE_MODULE_ARGS': {}}
|
||||||
|
testargs = ['module_file2.py', json.dumps(cli_data)]
|
||||||
|
with mock.patch.object(sys, 'argv', testargs):
|
||||||
|
with mock.patch('ansible.module_utils.urls.Request.open', new=mock_ping_response):
|
||||||
|
my_module = TowerModule(argument_spec=dict())
|
||||||
|
my_module._COLLECTION_VERSION = "1.0.0"
|
||||||
|
my_module._COLLECTION_TYPE = "not-junk"
|
||||||
|
my_module.collection_to_version['not-junk'] = 'not-junk'
|
||||||
|
my_module.get_endpoint('ping')
|
||||||
|
silence_warning.assert_called_once_with(
|
||||||
|
'You are running collection version 1.0.0 but connecting to tower version 1.2.3'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_type_warning(collection_import, silence_warning):
|
||||||
|
TowerModule = collection_import('plugins.module_utils.tower_api').TowerModule
|
||||||
|
cli_data = {'ANSIBLE_MODULE_ARGS': {}}
|
||||||
|
testargs = ['module_file2.py', json.dumps(cli_data)]
|
||||||
|
with mock.patch.object(sys, 'argv', testargs):
|
||||||
|
with mock.patch('ansible.module_utils.urls.Request.open', new=mock_ping_response):
|
||||||
|
my_module = TowerModule(argument_spec={})
|
||||||
|
my_module._COLLECTION_VERSION = "1.2.3"
|
||||||
|
my_module._COLLECTION_TYPE = "junk"
|
||||||
|
my_module.collection_to_version['junk'] = 'junk'
|
||||||
|
my_module.get_endpoint('ping')
|
||||||
|
silence_warning.assert_called_once_with(
|
||||||
|
'You are using the junk version of this collection but connecting to not-junk'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_duplicate_config(collection_import, silence_warning):
|
||||||
# imports done here because of PATH issues unique to this test suite
|
# imports done here because of PATH issues unique to this test suite
|
||||||
TowerModule = collection_import('plugins.module_utils.tower_api').TowerModule
|
TowerModule = collection_import('plugins.module_utils.tower_api').TowerModule
|
||||||
data = {
|
data = {
|
||||||
@ -17,19 +70,42 @@ def test_duplicate_config(collection_import):
|
|||||||
'tower_username': 'bob',
|
'tower_username': 'bob',
|
||||||
'tower_config_file': 'my_config'
|
'tower_config_file': 'my_config'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DuplicateTestTowerModule(TowerModule):
|
||||||
|
def load_config(self, config_path):
|
||||||
|
assert config_path == 'my_config'
|
||||||
|
|
||||||
|
def _load_params(self):
|
||||||
|
self.params = data
|
||||||
|
|
||||||
cli_data = {'ANSIBLE_MODULE_ARGS': data}
|
cli_data = {'ANSIBLE_MODULE_ARGS': data}
|
||||||
testargs = ['module_file.py', json.dumps(cli_data)]
|
testargs = ['module_file.py', json.dumps(cli_data)]
|
||||||
with mock.patch('ansible.module_utils.basic.AnsibleModule.warn') as mock_warn:
|
with mock.patch.object(sys, 'argv', testargs):
|
||||||
with mock.patch.object(sys, 'argv', testargs):
|
argument_spec = dict(
|
||||||
with mock.patch.object(TowerModule, 'load_config') as mock_load:
|
name=dict(required=True),
|
||||||
argument_spec = dict(
|
zig=dict(type='str'),
|
||||||
name=dict(required=True),
|
)
|
||||||
zig=dict(type='str'),
|
DuplicateTestTowerModule(argument_spec=argument_spec)
|
||||||
)
|
silence_warning.assert_called_once_with(
|
||||||
TowerModule(argument_spec=argument_spec)
|
|
||||||
mock_load.mock_calls[-1] == mock.call('my_config')
|
|
||||||
mock_warn.assert_called_once_with(
|
|
||||||
'The parameter(s) tower_username were provided at the same time as '
|
'The parameter(s) tower_username were provided at the same time as '
|
||||||
'tower_config_file. Precedence may be unstable, '
|
'tower_config_file. Precedence may be unstable, '
|
||||||
'we suggest either using config file or params.'
|
'we suggest either using config file or params.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_templated_values(collection_import):
|
||||||
|
"""This test corresponds to replacements done by
|
||||||
|
awx_collection/tools/roles/template_galaxy/tasks/main.yml
|
||||||
|
Those replacements should happen at build time, so they should not be
|
||||||
|
checked into source.
|
||||||
|
"""
|
||||||
|
TowerModule = collection_import('plugins.module_utils.tower_api').TowerModule
|
||||||
|
assert TowerModule._COLLECTION_VERSION == "devel", (
|
||||||
|
'The collection version is templated when the collection is built '
|
||||||
|
'and the code should retain the placeholder of "devel".'
|
||||||
|
)
|
||||||
|
InventoryModule = collection_import('plugins.inventory.tower').InventoryModule
|
||||||
|
assert InventoryModule.NAME == 'awx.awx.tower', (
|
||||||
|
'The inventory plugin FQCN is templated when the collection is built '
|
||||||
|
'and the code should retain the default of awx.awx.'
|
||||||
|
)
|
||||||
|
@ -3,23 +3,23 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from awx.main.models import Project
|
from awx.main.models import Project
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_project(run_module, admin_user, organization):
|
def test_create_project(run_module, admin_user, organization, silence_warning):
|
||||||
with mock.patch('ansible.module_utils.basic.AnsibleModule.warn') as mock_warn:
|
result = run_module('tower_project', dict(
|
||||||
result = run_module('tower_project', dict(
|
name='foo',
|
||||||
name='foo',
|
organization=organization.name,
|
||||||
organization=organization.name,
|
scm_type='git',
|
||||||
scm_type='git',
|
scm_url='https://foo.invalid',
|
||||||
scm_url='https://foo.invalid',
|
wait=False,
|
||||||
wait=False,
|
scm_update_cache_timeout=5
|
||||||
scm_update_cache_timeout=5
|
), admin_user)
|
||||||
), admin_user)
|
silence_warning.assert_called_once_with(
|
||||||
mock_warn.assert_called_once_with('scm_update_cache_timeout will be ignored since scm_update_on_launch was not set to true')
|
'scm_update_cache_timeout will be ignored since scm_update_on_launch '
|
||||||
|
'was not set to true')
|
||||||
|
|
||||||
assert result.pop('changed', None), result
|
assert result.pop('changed', None), result
|
||||||
|
|
||||||
proj = Project.objects.get(name='foo')
|
proj = Project.objects.get(name='foo')
|
||||||
|
@ -17,7 +17,7 @@ from awx.main.models import (
|
|||||||
|
|
||||||
# warns based on password_management param, but not security issue
|
# warns based on password_management param, but not security issue
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_receive_send_jt(run_module, admin_user, mocker, silence_deprecation, silence_warning):
|
def test_receive_send_jt(run_module, admin_user, mocker, silence_deprecation):
|
||||||
org = Organization.objects.create(name='SRtest')
|
org = Organization.objects.create(name='SRtest')
|
||||||
proj = Project.objects.create(
|
proj = Project.objects.create(
|
||||||
name='SRtest',
|
name='SRtest',
|
||||||
|
@ -43,5 +43,4 @@ def test_password_no_op_warning(run_module, admin_user, mock_auth_stuff, silence
|
|||||||
|
|
||||||
silence_warning.assert_called_once_with(
|
silence_warning.assert_called_once_with(
|
||||||
"The field password of user {0} has encrypted data and "
|
"The field password of user {0} has encrypted data and "
|
||||||
"may inaccurately report task is changed.".format(result['id'])
|
"may inaccurately report task is changed.".format(result['id']))
|
||||||
)
|
|
||||||
|
@ -17,68 +17,5 @@
|
|||||||
force_basic_auth: true
|
force_basic_auth: true
|
||||||
url_username: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
url_username: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
||||||
url_password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
url_password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
||||||
|
roles:
|
||||||
tasks:
|
- generate
|
||||||
- name: Get date time data
|
|
||||||
setup:
|
|
||||||
gather_subset: min
|
|
||||||
|
|
||||||
- name: Create module directory
|
|
||||||
file:
|
|
||||||
state: directory
|
|
||||||
name: "modules"
|
|
||||||
|
|
||||||
- name: Load api/v2
|
|
||||||
uri:
|
|
||||||
method: GET
|
|
||||||
url: "{{ api_url }}/api/v2/"
|
|
||||||
register: endpoints
|
|
||||||
|
|
||||||
- name: Load endpoint options
|
|
||||||
uri:
|
|
||||||
method: "OPTIONS"
|
|
||||||
url: "{{ api_url }}{{ item.value }}"
|
|
||||||
loop: "{{ endpoints['json'] | dict2items }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item.key }}"
|
|
||||||
register: end_point_options
|
|
||||||
when: "generate_for is not defined or item.key in generate_for"
|
|
||||||
|
|
||||||
- name: Scan POST options for different things
|
|
||||||
set_fact:
|
|
||||||
all_options: "{{ all_options | default({}) | combine(options[0]) }}"
|
|
||||||
loop: "{{ end_point_options.results }}"
|
|
||||||
vars:
|
|
||||||
options: "{{ item | json_query('json.actions.POST.[*]') }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item['item']['key'] }}"
|
|
||||||
when:
|
|
||||||
- item is not skipped
|
|
||||||
- options is defined
|
|
||||||
|
|
||||||
- name: Process endpoint
|
|
||||||
template:
|
|
||||||
src: "templates/tower_module.j2"
|
|
||||||
dest: "{{ playbook_dir | dirname }}/plugins/modules/{{ file_name }}"
|
|
||||||
loop: "{{ end_point_options['results'] }}"
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item['item']['key'] }}"
|
|
||||||
when: "'json' in item and 'actions' in item['json'] and 'POST' in item['json']['actions']"
|
|
||||||
vars:
|
|
||||||
item_type: "{{ item['item']['key'] }}"
|
|
||||||
human_readable: "{{ item_type | replace('_', ' ') }}"
|
|
||||||
singular_item_type: "{{ item['item']['key'] | regex_replace('ies$', 'y') | regex_replace('s$', '') }}"
|
|
||||||
file_name: "tower_{% if item['item']['key'] in ['settings'] %}{{ item['item']['key'] }}{% else %}{{ singular_item_type }}{% endif %}.py"
|
|
||||||
type_map:
|
|
||||||
bool: 'bool'
|
|
||||||
boolean: 'bool'
|
|
||||||
choice: 'str'
|
|
||||||
datetime: 'str'
|
|
||||||
id: 'str'
|
|
||||||
int: 'int'
|
|
||||||
integer: 'int'
|
|
||||||
json: 'dict'
|
|
||||||
list: 'list'
|
|
||||||
object: 'dict'
|
|
||||||
password: 'str'
|
|
||||||
string: 'str'
|
|
||||||
|
64
awx_collection/tools/roles/generate/tasks/main.yml
Normal file
64
awx_collection/tools/roles/generate/tasks/main.yml
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
- name: Get date time data
|
||||||
|
setup:
|
||||||
|
gather_subset: min
|
||||||
|
|
||||||
|
- name: Create module directory
|
||||||
|
file:
|
||||||
|
state: directory
|
||||||
|
name: "modules"
|
||||||
|
|
||||||
|
- name: Load api/v2
|
||||||
|
uri:
|
||||||
|
method: GET
|
||||||
|
url: "{{ api_url }}/api/v2/"
|
||||||
|
register: endpoints
|
||||||
|
|
||||||
|
- name: Load endpoint options
|
||||||
|
uri:
|
||||||
|
method: "OPTIONS"
|
||||||
|
url: "{{ api_url }}{{ item.value }}"
|
||||||
|
loop: "{{ endpoints['json'] | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}"
|
||||||
|
register: end_point_options
|
||||||
|
when: "generate_for is not defined or item.key in generate_for"
|
||||||
|
|
||||||
|
- name: Scan POST options for different things
|
||||||
|
set_fact:
|
||||||
|
all_options: "{{ all_options | default({}) | combine(options[0]) }}"
|
||||||
|
loop: "{{ end_point_options.results }}"
|
||||||
|
vars:
|
||||||
|
options: "{{ item | json_query('json.actions.POST.[*]') }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item['item']['key'] }}"
|
||||||
|
when:
|
||||||
|
- item is not skipped
|
||||||
|
- options is defined
|
||||||
|
|
||||||
|
- name: Process endpoint
|
||||||
|
template:
|
||||||
|
src: "templates/tower_module.j2"
|
||||||
|
dest: "{{ playbook_dir | dirname }}/plugins/modules/{{ file_name }}"
|
||||||
|
loop: "{{ end_point_options['results'] }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item['item']['key'] }}"
|
||||||
|
when: "'json' in item and 'actions' in item['json'] and 'POST' in item['json']['actions']"
|
||||||
|
vars:
|
||||||
|
item_type: "{{ item['item']['key'] }}"
|
||||||
|
human_readable: "{{ item_type | replace('_', ' ') }}"
|
||||||
|
singular_item_type: "{{ item['item']['key'] | regex_replace('ies$', 'y') | regex_replace('s$', '') }}"
|
||||||
|
file_name: "tower_{% if item['item']['key'] in ['settings'] %}{{ item['item']['key'] }}{% else %}{{ singular_item_type }}{% endif %}.py"
|
||||||
|
type_map:
|
||||||
|
bool: 'bool'
|
||||||
|
boolean: 'bool'
|
||||||
|
choice: 'str'
|
||||||
|
datetime: 'str'
|
||||||
|
id: 'str'
|
||||||
|
int: 'int'
|
||||||
|
integer: 'int'
|
||||||
|
json: 'dict'
|
||||||
|
list: 'list'
|
||||||
|
object: 'dict'
|
||||||
|
password: 'str'
|
||||||
|
string: 'str'
|
40
awx_collection/tools/roles/template_galaxy/tasks/main.yml
Normal file
40
awx_collection/tools/roles/template_galaxy/tasks/main.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
- name: Set the collection version in the tower_api.py file
|
||||||
|
replace:
|
||||||
|
path: "{{ collection_path }}/plugins/module_utils/tower_api.py"
|
||||||
|
regexp: '^ _COLLECTION_VERSION = "devel"'
|
||||||
|
replace: ' _COLLECTION_VERSION = "{{ collection_version }}"'
|
||||||
|
when:
|
||||||
|
- "awx_template_version | default(True)"
|
||||||
|
|
||||||
|
- name: Set the collection type in the tower_api.py file
|
||||||
|
replace:
|
||||||
|
path: "{{ collection_path }}/plugins/module_utils/tower_api.py"
|
||||||
|
regexp: '^ _COLLECTION_TYPE = "awx"'
|
||||||
|
replace: ' _COLLECTION_TYPE = "{{ collection_namespace }}"'
|
||||||
|
|
||||||
|
- name: Do file content replacements for non-default namespace or package name
|
||||||
|
block:
|
||||||
|
|
||||||
|
- name: Change module doc_fragments to support desired namespace and package names
|
||||||
|
replace:
|
||||||
|
path: "{{ item }}"
|
||||||
|
regexp: '^extends_documentation_fragment: awx.awx.auth$'
|
||||||
|
replace: 'extends_documentation_fragment: {{ collection_namespace }}.{{ collection_package }}.auth'
|
||||||
|
with_fileglob: "{{ collection_path }}/plugins/modules/tower_*.py"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item | basename }}"
|
||||||
|
|
||||||
|
- name: Change inventory file to support desired namespace and package names
|
||||||
|
replace:
|
||||||
|
path: "{{ collection_path }}/plugins/inventory/tower.py"
|
||||||
|
regexp: "^ NAME = 'awx.awx.tower' # REPLACE$"
|
||||||
|
replace: " NAME = '{{ collection_namespace }}.{{ collection_package }}.tower' # REPLACE"
|
||||||
|
when:
|
||||||
|
- (collection_package != 'awx') or (collection_namespace != 'awx')
|
||||||
|
|
||||||
|
- name: Template the galaxy.yml file
|
||||||
|
template:
|
||||||
|
src: "{{ collection_path }}/tools/roles/template_galaxy/templates/galaxy.yml.j2"
|
||||||
|
dest: "{{ collection_path }}/galaxy.yml"
|
||||||
|
force: true
|
12
awx_collection/tools/template_galaxy.yml
Normal file
12
awx_collection/tools/template_galaxy.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
- name: Template the collection galaxy.yml
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
connection: local
|
||||||
|
vars:
|
||||||
|
collection_package: awx
|
||||||
|
collection_namespace: awx
|
||||||
|
collection_version: 0.0.1 # not for updating, pass in extra_vars
|
||||||
|
collection_path: "{{ playbook_dir }}/../"
|
||||||
|
roles:
|
||||||
|
- template_galaxy
|
Loading…
Reference in New Issue
Block a user