diff --git a/awx/main/analytics/broadcast_websocket.py b/awx/main/analytics/broadcast_websocket.py index 449726f3fe..5cfda529eb 100644 --- a/awx/main/analytics/broadcast_websocket.py +++ b/awx/main/analytics/broadcast_websocket.py @@ -44,8 +44,8 @@ class FixedSlidingWindow(): def cleanup(self, now_bucket=None): now_bucket = now_bucket or now_seconds() - if self.start_time + 60 <= now_bucket: - self.start_time = now_bucket + 60 + 1 + if self.start_time + 60 < now_bucket: + self.start_time = now_bucket - 60 # Delete old entries for k in list(self.buckets.keys()): @@ -53,16 +53,15 @@ class FixedSlidingWindow(): del self.buckets[k] def record(self, ts=None): - ts = ts or datetime.datetime.now() - now_bucket = int((ts - datetime.datetime(1970,1,1)).total_seconds()) + now_bucket = ts or dt_to_seconds(datetime.datetime.now()) val = self.buckets.get(now_bucket, 0) self.buckets[now_bucket] = val + 1 self.cleanup(now_bucket) - def render(self): - self.cleanup() + def render(self, ts=None): + self.cleanup(now_bucket=ts) return sum(self.buckets.values()) or 0 diff --git a/awx/main/managers.py b/awx/main/managers.py index 9f1537fd6f..2076e7f0b0 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -121,6 +121,17 @@ class InstanceManager(models.Manager): if not hostname: hostname = settings.CLUSTER_HOST_ID with advisory_lock('instance_registration_%s' % hostname): + if settings.AWX_AUTO_DEPROVISION_INSTANCES: + # detect any instances with the same IP address. + # if one exists, set it to None + inst_conflicting_ip = self.filter(ip_address=ip_address).exclude(hostname=hostname) + if inst_conflicting_ip.exists(): + for other_inst in inst_conflicting_ip: + other_hostname = other_inst.hostname + other_inst.ip_address = None + other_inst.save(update_fields=['ip_address']) + logger.warning("IP address {0} conflict detected, ip address unset for host {1}.".format(ip_address, other_hostname)) + instance = self.filter(hostname=hostname) if instance.exists(): instance = instance.get() diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 7c596547e7..c8a54354cf 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -2577,6 +2577,12 @@ class satellite6(PluginFileInjector): def inventory_as_dict(self, inventory_update, private_data_dir): ret = super(satellite6, self).inventory_as_dict(inventory_update, private_data_dir) + want_ansible_ssh_host = False + foreman_opts = inventory_update.source_vars_dict.copy() + for k, v in foreman_opts.items(): + if k == 'satellite6_want_ansible_ssh_host' and isinstance(v, bool): + want_ansible_ssh_host = v + # Compatibility content group_by_hostvar = { "environment": {"prefix": "foreman_environment_", @@ -2603,6 +2609,9 @@ class satellite6(PluginFileInjector): ret['want_facts'] = True ret['want_params'] = True + if want_ansible_ssh_host: + ret['compose'] = {'ansible_ssh_host': "foreman['ip6'] | default(foreman['ip'], true)"} + return ret diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 989aa69832..0fe0e9035f 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -2074,29 +2074,34 @@ class RunProjectUpdate(BaseTask): if settings.GALAXY_IGNORE_CERTS: env['ANSIBLE_GALAXY_IGNORE'] = True # Set up the public Galaxy server, if enabled + galaxy_configured = False if settings.PUBLIC_GALAXY_ENABLED: - galaxy_servers = [settings.PUBLIC_GALAXY_SERVER] + galaxy_servers = [settings.PUBLIC_GALAXY_SERVER] # static setting else: + galaxy_configured = True galaxy_servers = [] # Set up fallback Galaxy servers, if configured if settings.FALLBACK_GALAXY_SERVERS: + galaxy_configured = True galaxy_servers = settings.FALLBACK_GALAXY_SERVERS + galaxy_servers # Set up the primary Galaxy server, if configured if settings.PRIMARY_GALAXY_URL: + galaxy_configured = True galaxy_servers = [{'id': 'primary_galaxy'}] + galaxy_servers for key in GALAXY_SERVER_FIELDS: value = getattr(settings, 'PRIMARY_GALAXY_{}'.format(key.upper())) if value: galaxy_servers[0][key] = value - for server in galaxy_servers: - for key in GALAXY_SERVER_FIELDS: - if not server.get(key): - continue - env_key = ('ANSIBLE_GALAXY_SERVER_{}_{}'.format(server.get('id', 'unnamed'), key)).upper() - env[env_key] = server[key] - if galaxy_servers: - # now set the precedence of galaxy servers - env['ANSIBLE_GALAXY_SERVER_LIST'] = ','.join([server.get('id', 'unnamed') for server in galaxy_servers]) + if galaxy_configured: + for server in galaxy_servers: + for key in GALAXY_SERVER_FIELDS: + if not server.get(key): + continue + env_key = ('ANSIBLE_GALAXY_SERVER_{}_{}'.format(server.get('id', 'unnamed'), key)).upper() + env[env_key] = server[key] + if galaxy_servers: + # now set the precedence of galaxy servers + env['ANSIBLE_GALAXY_SERVER_LIST'] = ','.join([server.get('id', 'unnamed') for server in galaxy_servers]) return env def _build_scm_url_extra_vars(self, project_update): diff --git a/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml b/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml index 20d868137a..6d4faee619 100644 --- a/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml +++ b/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml @@ -1,3 +1,5 @@ +compose: + ansible_ssh_host: foreman['ip6'] | default(foreman['ip'], true) keyed_groups: - key: foreman['environment_name'] | lower | regex_replace(' ', '') | regex_replace('[^A-Za-z0-9\_]', '_') | regex_replace('none', '') prefix: foreman_environment_ diff --git a/awx/main/tests/unit/analytics/test_broadcast_websocket.py b/awx/main/tests/unit/analytics/test_broadcast_websocket.py new file mode 100644 index 0000000000..6edfe51b92 --- /dev/null +++ b/awx/main/tests/unit/analytics/test_broadcast_websocket.py @@ -0,0 +1,69 @@ +import datetime + +from awx.main.analytics.broadcast_websocket import FixedSlidingWindow +from awx.main.analytics.broadcast_websocket import dt_to_seconds + + +class TestFixedSlidingWindow(): + + def ts(self, **kwargs): + e = { + 'year': 1985, + 'month': 1, + 'day': 1, + 'hour': 1, + } + return dt_to_seconds(datetime.datetime(**kwargs, **e)) + + def test_record_same_minute(self): + """ + Legend: + - = record() + ^ = render() + |---| = 1 minute, 60 seconds + + .................... + |------------------------------------------------------------| + ^^^^^^^^^^^^^^^^^^^^ + """ + + fsw = FixedSlidingWindow(self.ts(minute=0, second=0, microsecond=0)) + for i in range(20): + fsw.record(self.ts(minute=0, second=i, microsecond=0)) + assert (i + 1) == fsw.render(self.ts(minute=0, second=i, microsecond=0)) + + + def test_record_same_minute_render_diff_minute(self): + """ + Legend: + - = record() + ^ = render() + |---| = 1 minute, 60 seconds + + .................... + |------------------------------------------------------------| + ^^ ^ + AB C + |------------------------------------------------------------| + ^^^^^^^^^^^^^^^^^^^^^ + DEEEEEEEEEEEEEEEEEEEF + """ + + fsw = FixedSlidingWindow(self.ts(minute=0, second=0, microsecond=0)) + for i in range(20): + fsw.record(self.ts(minute=0, second=i, microsecond=0)) + + assert 20 == fsw.render(self.ts(minute=0, second=19, microsecond=0)), \ + "A. The second of the last record() call" + assert 20 == fsw.render(self.ts(minute=0, second=20, microsecond=0)), \ + "B. The second after the last record() call" + assert 20 == fsw.render(self.ts(minute=0, second=59, microsecond=0)), \ + "C. Last second in the same minute that all record() called in" + assert 20 == fsw.render(self.ts(minute=1, second=0, microsecond=0)), \ + "D. First second of the minute following the minute that all record() calls in" + for i in range(20): + assert 20 - i == fsw.render(self.ts(minute=1, second=i, microsecond=0)), \ + "E. Sliding window where 1 record() should drop from the results each time" + + assert 0 == fsw.render(self.ts(minute=1, second=20, microsecond=0)), \ + "F. First second one minute after all record() calls" diff --git a/awx/main/tests/unit/api/test_logger.py b/awx/main/tests/unit/api/test_logger.py index a28c5d0153..2a0bb9856d 100644 --- a/awx/main/tests/unit/api/test_logger.py +++ b/awx/main/tests/unit/api/test_logger.py @@ -44,7 +44,7 @@ data_loggly = { 'https', '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="logs-01.loggly.com" serverport="80" usehttps="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/")', # noqa + 'action(type="omhttp" server="logs-01.loggly.com" serverport="80" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/")', # noqa ]) ), ( @@ -77,7 +77,7 @@ data_loggly = { None, '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="yoursplunk" serverport="443" usehttps="on" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + 'action(type="omhttp" server="yoursplunk" serverport="443" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa ]) ), ( @@ -88,7 +88,7 @@ data_loggly = { None, '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="yoursplunk" serverport="80" usehttps="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + 'action(type="omhttp" server="yoursplunk" serverport="80" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa ]) ), ( @@ -99,7 +99,7 @@ data_loggly = { None, '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa ]) ), ( @@ -110,7 +110,7 @@ data_loggly = { None, '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + 'action(type="omhttp" server="yoursplunk" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa ]) ), ( @@ -121,7 +121,7 @@ data_loggly = { 'https', '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="on" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="on" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa ]) ), ( @@ -132,7 +132,7 @@ data_loggly = { None, '\n'.join([ 'template(name="awx" type="string" string="%rawmsg-after-pri%")\nmodule(load="omhttp")', - 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + 'action(type="omhttp" server="yoursplunk.org" serverport="8088" usehttps="off" allowunsignedcerts="off" skipverifyhost="off" action.resumeRetryCount="-1" template="awx" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa ]) ), ] diff --git a/awx/main/utils/external_logging.py b/awx/main/utils/external_logging.py index 4b3fcda7fb..8444b1cfbb 100644 --- a/awx/main/utils/external_logging.py +++ b/awx/main/utils/external_logging.py @@ -60,6 +60,7 @@ def construct_rsyslog_conf_template(settings=settings): # https://github.com/rsyslog/rsyslog-doc/blob/master/source/configuration/modules/omhttp.rst ssl = 'on' if parsed.scheme == 'https' else 'off' skip_verify = 'off' if settings.LOG_AGGREGATOR_VERIFY_CERT else 'on' + allow_unsigned = 'off' if settings.LOG_AGGREGATOR_VERIFY_CERT else 'on' if not port: port = 443 if parsed.scheme == 'https' else 80 @@ -68,6 +69,7 @@ def construct_rsyslog_conf_template(settings=settings): f'server="{host}"', f'serverport="{port}"', f'usehttps="{ssl}"', + f'allowunsignedcerts="{allow_unsigned}"', f'skipverifyhost="{skip_verify}"', 'action.resumeRetryCount="-1"', 'template="awx"', diff --git a/awx/playbooks/project_update.yml b/awx/playbooks/project_update.yml index 9c61c81093..fc791069a9 100644 --- a/awx/playbooks/project_update.yml +++ b/awx/playbooks/project_update.yml @@ -136,9 +136,9 @@ register: doesRequirementsExist - name: fetch galaxy roles from requirements.yml - command: ansible-galaxy install -r requirements.yml -p {{roles_destination|quote}}{{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} + command: ansible-galaxy install -r roles/requirements.yml -p {{roles_destination|quote}}{{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} args: - chdir: "{{project_path|quote}}/roles" + chdir: "{{project_path|quote}}" register: galaxy_result when: doesRequirementsExist.stat.exists changed_when: "'was installed successfully' in galaxy_result.stdout" @@ -157,9 +157,9 @@ register: doesCollectionRequirementsExist - name: fetch galaxy collections from collections/requirements.yml - command: ansible-galaxy collection install -r requirements.yml -p {{collections_destination|quote}}{{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} + command: ansible-galaxy collection install -r collections/requirements.yml -p {{collections_destination|quote}}{{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} args: - chdir: "{{project_path|quote}}/collections" + chdir: "{{project_path|quote}}" register: galaxy_collection_result when: doesCollectionRequirementsExist.stat.exists changed_when: "'Installing ' in galaxy_collection_result.stdout" diff --git a/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html b/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html index fa3f21844a..5c61d06673 100644 --- a/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html +++ b/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html @@ -80,7 +80,7 @@ collection="vm.approvals" dataset="vm.dataset" iterator="template" - base-path="unified_job_templates" + base-path="workflow_approvals" query-set="vm.queryset" hide-view-per-page="true"> diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html index 098d1c2fbf..9a4a9a80f7 100644 --- a/awx/ui/client/src/license/license.partial.html +++ b/awx/ui/client/src/license/license.partial.html @@ -116,7 +116,7 @@ -