diff --git a/awx/main/migrations/0148_rename_inv_sources_inv_updates.py b/awx/main/migrations/0148_rename_inv_sources_inv_updates.py new file mode 100644 index 0000000000..f89ab4e440 --- /dev/null +++ b/awx/main/migrations/0148_rename_inv_sources_inv_updates.py @@ -0,0 +1,108 @@ +# Generated by Django 2.2.16 on 2021-06-17 13:12 +import logging + +from django.db import migrations, models + +from awx.main.models.credential import ManagedCredentialType, CredentialType as ModernCredentialType + + +logger = logging.getLogger(__name__) + + +def forwards(apps, schema_editor): + InventoryUpdate = apps.get_model('main', 'InventoryUpdate') + InventorySource = apps.get_model('main', 'InventorySource') + + r = InventoryUpdate.objects.filter(source='tower').update(source='controller') + if r: + logger.warn(f'Renamed {r} tower inventory updates to controller') + InventorySource.objects.filter(source='tower').update(source='controller') + if r: + logger.warn(f'Renamed {r} tower inventory sources to controller') + + CredentialType = apps.get_model('main', 'CredentialType') + + tower_type = CredentialType.objects.filter(managed_by_tower=True, namespace='tower').first() + if tower_type is not None and not CredentialType.objects.filter(managed_by_tower=True, namespace='controller', kind='cloud').exists(): + registry_type = ManagedCredentialType.registry.get('controller') + if not registry_type: + raise RuntimeError('Excpected to find controller credential, this may need to be edited in the future!') + logger.info('Renaming the Ansible Tower credential type for existing install') + tower_type.name = registry_type.name # sensitive to translations + tower_type.namespace = 'controller' # if not done, will error setup_tower_managed_defaults + tower_type.save(update_fields=['name', 'namespace']) + + ModernCredentialType.setup_tower_managed_defaults() + + +def backwards(apps, schema_editor): + InventoryUpdate = apps.get_model('main', 'InventoryUpdate') + InventorySource = apps.get_model('main', 'InventorySource') + + r = InventoryUpdate.objects.filter(source='controller').update(source='tower') + if r: + logger.warn(f'Renamed {r} controller inventory updates to tower') + r = InventorySource.objects.filter(source='controller').update(source='tower') + if r: + logger.warn(f'Renamed {r} controller inventory sources to tower') + + CredentialType = apps.get_model('main', 'CredentialType') + + tower_type = CredentialType.objects.filter(managed_by_tower=True, namespace='controller', kind='cloud').first() + if tower_type is not None and not CredentialType.objects.filter(managed_by_tower=True, namespace='tower').exists(): + logger.info('Renaming the controller credential type back') + tower_type.namespace = 'tower' + tower_type.name = 'Ansible Tower' + tower_type.save(update_fields=['namespace', 'name']) + + +class Migration(migrations.Migration): + dependencies = [ + ('main', '0147_validate_ee_image_field'), + ] + operations = [ + migrations.RunPython(migrations.RunPython.noop, backwards), + migrations.AlterField( + model_name='inventorysource', + name='source', + field=models.CharField( + choices=[ + ('file', 'File, Directory or Script'), + ('scm', 'Sourced from a Project'), + ('ec2', 'Amazon EC2'), + ('gce', 'Google Compute Engine'), + ('azure_rm', 'Microsoft Azure Resource Manager'), + ('vmware', 'VMware vCenter'), + ('satellite6', 'Red Hat Satellite 6'), + ('openstack', 'OpenStack'), + ('rhv', 'Red Hat Virtualization'), + ('controller', 'Red Hat Ansible Automation Platform'), + ('insights', 'Red Hat Insights'), + ], + default=None, + max_length=32, + ), + ), + migrations.AlterField( + model_name='inventoryupdate', + name='source', + field=models.CharField( + choices=[ + ('file', 'File, Directory or Script'), + ('scm', 'Sourced from a Project'), + ('ec2', 'Amazon EC2'), + ('gce', 'Google Compute Engine'), + ('azure_rm', 'Microsoft Azure Resource Manager'), + ('vmware', 'VMware vCenter'), + ('satellite6', 'Red Hat Satellite 6'), + ('openstack', 'OpenStack'), + ('rhv', 'Red Hat Virtualization'), + ('controller', 'Red Hat Ansible Automation Platform'), + ('insights', 'Red Hat Insights'), + ], + default=None, + max_length=32, + ), + ), + migrations.RunPython(forwards, migrations.RunPython.noop), + ] diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index 0e5cac28f0..82368e6bf3 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -1006,23 +1006,25 @@ ManagedCredentialType( ) ManagedCredentialType( - namespace='tower', + namespace='controller', kind='cloud', - name=ugettext_noop('Ansible Tower'), + name=ugettext_noop('Red Hat Ansible Automation Platform'), managed_by_tower=True, inputs={ 'fields': [ { 'id': 'host', - 'label': ugettext_noop('Ansible Tower Hostname'), + 'label': ugettext_noop('Red Hat Ansible Automation Platform'), 'type': 'string', - 'help_text': ugettext_noop('The Ansible Tower base URL to authenticate with.'), + 'help_text': ugettext_noop('Red Hat Ansible Automation Platform base URL to authenticate with.'), }, { 'id': 'username', 'label': ugettext_noop('Username'), 'type': 'string', - 'help_text': ugettext_noop('The Ansible Tower user to authenticate as.' 'This should not be set if an OAuth token is being used.'), + 'help_text': ugettext_noop( + 'Red Hat Ansible Automation Platform username id to authenticate as.' 'This should not be set if an OAuth token is being used.' + ), }, { 'id': 'password', @@ -1048,6 +1050,11 @@ ManagedCredentialType( 'TOWER_PASSWORD': '{{password}}', 'TOWER_VERIFY_SSL': '{{verify_ssl}}', 'TOWER_OAUTH_TOKEN': '{{oauth_token}}', + 'CONTROLLER_HOST': '{{host}}', + 'CONTROLLER_USERNAME': '{{username}}', + 'CONTROLLER_PASSWORD': '{{password}}', + 'CONTROLLER_VERIFY_SSL': '{{verify_ssl}}', + 'CONTROLLER_OAUTH_TOKEN': '{{oauth_token}}', } }, ) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index d50c286b89..fc4c934685 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -805,7 +805,7 @@ class InventorySourceOptions(BaseModel): ('satellite6', _('Red Hat Satellite 6')), ('openstack', _('OpenStack')), ('rhv', _('Red Hat Virtualization')), - ('tower', _('Ansible Tower')), + ('controller', _('Red Hat Ansible Automation Platform')), ('insights', _('Red Hat Insights')), ] @@ -1529,8 +1529,8 @@ class satellite6(PluginFileInjector): return ret -class tower(PluginFileInjector): - plugin_name = 'tower' +class controller(PluginFileInjector): + plugin_name = 'tower' # TODO: relying on routing for now, update after EEs pick up revised collection base_injector = 'template' namespace = 'awx' collection = 'awx' diff --git a/awx/main/tests/data/inventory/plugins/tower/env.json b/awx/main/tests/data/inventory/plugins/tower/env.json index 9ce3d90f95..cc7a5d1ffa 100644 --- a/awx/main/tests/data/inventory/plugins/tower/env.json +++ b/awx/main/tests/data/inventory/plugins/tower/env.json @@ -4,5 +4,10 @@ "TOWER_PASSWORD": "fooo", "TOWER_USERNAME": "fooo", "TOWER_OAUTH_TOKEN": "", - "TOWER_VERIFY_SSL": "False" + "TOWER_VERIFY_SSL": "False", + "CONTROLLER_HOST": "https://foo.invalid", + "CONTROLLER_PASSWORD": "fooo", + "CONTROLLER_USERNAME": "fooo", + "CONTROLLER_OAUTH_TOKEN": "", + "CONTROLLER_VERIFY_SSL": "False" } \ No newline at end of file diff --git a/awx/main/tests/functional/models/test_activity_stream.py b/awx/main/tests/functional/models/test_activity_stream.py index 9399077940..abc0b12c6b 100644 --- a/awx/main/tests/functional/models/test_activity_stream.py +++ b/awx/main/tests/functional/models/test_activity_stream.py @@ -271,10 +271,10 @@ def test_cluster_node_long_node_name(inventory, project): @pytest.mark.django_db def test_credential_defaults_idempotency(): CredentialType.setup_tower_managed_defaults() - old_inputs = CredentialType.objects.get(name='Ansible Tower', kind='cloud').inputs + old_inputs = CredentialType.objects.get(name='Red Hat Ansible Automation Platform', kind='cloud').inputs prior_count = ActivityStream.objects.count() # this is commonly re-ran in migrations, and no changes should be shown # because inputs and injectors are not actually tracked in the database CredentialType.setup_tower_managed_defaults() - assert CredentialType.objects.get(name='Ansible Tower', kind='cloud').inputs == old_inputs + assert CredentialType.objects.get(name='Red Hat Ansible Automation Platform', kind='cloud').inputs == old_inputs assert ActivityStream.objects.count() == prior_count diff --git a/awx/main/tests/functional/test_credential.py b/awx/main/tests/functional/test_credential.py index b36603275c..09b62e3207 100644 --- a/awx/main/tests/functional/test_credential.py +++ b/awx/main/tests/functional/test_credential.py @@ -101,6 +101,7 @@ def test_default_cred_types(): 'vault', 'vmware', ] + for type_ in CredentialType.defaults.values(): assert type_().managed_by_tower is True diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index fe563e39cf..b226f72ee2 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -1794,14 +1794,14 @@ class TestInventoryUpdateCredentials(TestJobExecution): safe_env = build_safe_env(env) - assert env['TOWER_HOST'] == 'https://tower.example.org' - assert env['TOWER_USERNAME'] == 'bob' - assert env['TOWER_PASSWORD'] == 'secret' + assert env['CONTROLLER_HOST'] == 'https://tower.example.org' + assert env['CONTROLLER_USERNAME'] == 'bob' + assert env['CONTROLLER_PASSWORD'] == 'secret' if verify: - assert env['TOWER_VERIFY_SSL'] == 'True' + assert env['CONTROLLER_VERIFY_SSL'] == 'True' else: - assert env['TOWER_VERIFY_SSL'] == 'False' - assert safe_env['TOWER_PASSWORD'] == tasks.HIDDEN_PASSWORD + assert env['CONTROLLER_VERIFY_SSL'] == 'False' + assert safe_env['CONTROLLER_PASSWORD'] == tasks.HIDDEN_PASSWORD def test_tower_source_ssl_verify_empty(self, inventory_update, private_data_dir, mocker): task = tasks.RunInventoryUpdate() diff --git a/awx_collection/plugins/modules/credential.py b/awx_collection/plugins/modules/credential.py index a913512d3b..9b4bd5c4b1 100644 --- a/awx_collection/plugins/modules/credential.py +++ b/awx_collection/plugins/modules/credential.py @@ -81,7 +81,7 @@ options: - Deprecated, please use credential_type required: False type: str - choices: ["aws", "tower", "gce", "azure_rm", "openstack", "satellite6", "rhv", "vmware", "aim", "conjur", "hashivault_kv", "hashivault_ssh", + choices: ["aws", "controller", "gce", "azure_rm", "openstack", "satellite6", "rhv", "vmware", "aim", "conjur", "hashivault_kv", "hashivault_ssh", "azure_kv", "insights", "kubernetes_bearer_token", "net", "scm", "ssh", "github_token", "gitlab_token", "vault"] host: description: @@ -292,7 +292,7 @@ from ..module_utils.controller_api import ControllerAPIModule KIND_CHOICES = { 'aws': 'Amazon Web Services', - 'tower': 'Ansible Tower', + 'controller': 'Red Hat Ansible Automation Platform', 'gce': 'Google Compute Engine', 'azure_rm': 'Microsoft Azure Resource Manager', 'openstack': 'OpenStack', diff --git a/awx_collection/tools/roles/template_galaxy/templates/README.md.j2 b/awx_collection/tools/roles/template_galaxy/templates/README.md.j2 index 88fd3c4739..9d67a50e7e 100644 --- a/awx_collection/tools/roles/template_galaxy/templates/README.md.j2 +++ b/awx_collection/tools/roles/template_galaxy/templates/README.md.j2 @@ -12,7 +12,7 @@ [comment]: # (* upon build of the collection *) [comment]: # (*******************************************************) -This Ansible collection allows for easy interaction with an {% if collection_package | lower() == 'awx' %}AWX{% else %}Ansible Tower{% endif %} server via Ansible playbooks. +This Ansible collection allows for easy interaction with an {% if collection_package | lower() == 'awx' %}AWX{% else %}Red Hat Ansible Automation Platform{% endif %} server via Ansible playbooks. This source for this collection lives in the `awx_collection` folder inside of the AWX source. diff --git a/awxkit/awxkit/api/pages/credentials.py b/awxkit/awxkit/api/pages/credentials.py index 5f78b63f8b..8b3647c04d 100644 --- a/awxkit/awxkit/api/pages/credentials.py +++ b/awxkit/awxkit/api/pages/credentials.py @@ -79,7 +79,7 @@ credential_type_name_to_config_kind_map = { 'amazon web services': 'aws', 'container registry': 'registry', 'ansible galaxy/automation hub api token': 'galaxy', - 'ansible tower': 'tower', + 'red hat ansible automation platform': 'controller', 'google compute engine': 'gce', 'insights': 'insights', 'openshift or kubernetes api bearer token': 'kubernetes', diff --git a/awxkit/awxkit/cli/docs/README.md b/awxkit/awxkit/cli/docs/README.md index b6f77492e5..e21f937657 100644 --- a/awxkit/awxkit/cli/docs/README.md +++ b/awxkit/awxkit/cli/docs/README.md @@ -2,6 +2,6 @@ Building the Documentation -------------------------- To build the docs, spin up a real AWX server, `pip install sphinx sphinxcontrib-autoprogram`, and run: - ~ TOWER_HOST=https://awx.example.org TOWER_USERNAME=example TOWER_PASSWORD=secret make clean html + ~ CONTROLLER_HOST=https://awx.example.org CONTROLLER_USERNAME=example CONTROLLER_PASSWORD=secret make clean html ~ cd build/html/ && python -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) .. diff --git a/awxkit/awxkit/cli/docs/source/authentication.rst b/awxkit/awxkit/cli/docs/source/authentication.rst index 774366e0be..08dcf2feb1 100644 --- a/awxkit/awxkit/cli/docs/source/authentication.rst +++ b/awxkit/awxkit/cli/docs/source/authentication.rst @@ -12,9 +12,9 @@ The preferred mechanism for authenticating with AWX and |RHAT| is by generating .. code:: bash - TOWER_HOST=https://awx.example.org \ - TOWER_USERNAME=alice \ - TOWER_PASSWORD=secret \ + CONTROLLER_HOST=https://awx.example.org \ + CONTROLLER_USERNAME=alice \ + CONTROLLER_PASSWORD=secret \ awx login As a convenience, the ``awx login -f human`` command prints a shell-formatted token @@ -22,15 +22,15 @@ value: .. code:: bash - export TOWER_OAUTH_TOKEN=6E5SXhld7AMOhpRveZsLJQsfs9VS8U + export CONTROLLER_OAUTH_TOKEN=6E5SXhld7AMOhpRveZsLJQsfs9VS8U By ingesting this token, you can run subsequent CLI commands without having to specify your username and password each time: .. code:: bash - export TOWER_HOST=https://awx.example.org - $(TOWER_USERNAME=alice TOWER_PASSWORD=secret awx login -f human) + export CONTROLLER_HOST=https://awx.example.org + $(CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret awx login -f human) awx config Working with OAuth2.0 Applications @@ -43,7 +43,7 @@ application was created. .. code:: bash - TOWER_USERNAME=alice TOWER_PASSWORD=secret awx login \ + CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret awx login \ --conf.client_id --conf.client_secret @@ -55,7 +55,7 @@ a read-only token, specify ``--scope read``: .. code:: bash - TOWER_USERNAME=alice TOWER_PASSWORD=secret \ + CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret \ awx login --conf.scope read Session Authentication @@ -65,5 +65,5 @@ specify your username and password on every invocation: .. code:: bash - TOWER_USERNAME=alice TOWER_PASSWORD=secret awx jobs list + CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret awx jobs list awx --conf.username alice --conf.password secret jobs list diff --git a/awxkit/awxkit/cli/docs/source/output.rst b/awxkit/awxkit/cli/docs/source/output.rst index 6cba583bdc..251983c63b 100644 --- a/awxkit/awxkit/cli/docs/source/output.rst +++ b/awxkit/awxkit/cli/docs/source/output.rst @@ -54,4 +54,4 @@ Colorized Output By default, |prog| prints colorized output using ANSI color codes. To disable this functionality, specify ``--conf.color f`` or set the environment variable -``TOWER_COLOR=f``. +``CONTROLLER_COLOR=f``. diff --git a/awxkit/awxkit/cli/docs/source/usage.rst b/awxkit/awxkit/cli/docs/source/usage.rst index b22e3c7dbb..c877019bc6 100644 --- a/awxkit/awxkit/cli/docs/source/usage.rst +++ b/awxkit/awxkit/cli/docs/source/usage.rst @@ -77,17 +77,17 @@ A few of the most important ones are: ``-f, --conf.format`` used to specify a custom output format (the default is json) -``--conf.host, TOWER_HOST`` +``--conf.host, CONTROLLER_HOST`` the full URL of the AWX/|RHAT| host (i.e., https://my.awx.example.org) -``-k, --conf.insecure, TOWER_VERIFY_SSL`` +``-k, --conf.insecure, CONTROLLER_VERIFY_SSL`` allows insecure server connections when using SSL -``--conf.username, TOWER_USERNAME`` +``--conf.username, CONTROLLER_USERNAME`` the AWX username to use for authentication -``--conf.password, TOWER_PASSWORD`` +``--conf.password, CONTROLLER_PASSWORD`` the AWX password to use for authentication -``--conf.token, TOWER_OAUTH_TOKEN`` +``--conf.token, CONTROLLER_OAUTH_TOKEN`` an OAuth2.0 token to use for authentication diff --git a/awxkit/awxkit/cli/format.py b/awxkit/awxkit/cli/format.py index adf61337a4..0277e6869a 100644 --- a/awxkit/awxkit/cli/format.py +++ b/awxkit/awxkit/cli/format.py @@ -11,30 +11,30 @@ def add_authentication_arguments(parser, env): auth = parser.add_argument_group('authentication') auth.add_argument( '--conf.host', - default=env.get('TOWER_HOST', 'https://127.0.0.1:443'), + default=env.get('CONTROLLER_HOST', env.get('TOWER_HOST', 'https://127.0.0.1:443')), metavar='https://example.awx.org', ) auth.add_argument( '--conf.token', - default=env.get('TOWER_OAUTH_TOKEN', env.get('TOWER_TOKEN', '')), + default=env.get('CONTROLLER_OAUTH_TOKEN', env.get('CONTROLLER_TOKEN', env.get('TOWER_OAUTH_TOKEN', env.get('TOWER_TOKEN', '')))), help='an OAuth2.0 token (get one by using `awx login`)', metavar='TEXT', ) auth.add_argument( '--conf.username', - default=env.get('TOWER_USERNAME', 'admin'), + default=env.get('CONTROLLER_USERNAME', env.get('TOWER_USERNAME', 'admin')), metavar='TEXT', ) auth.add_argument( '--conf.password', - default=env.get('TOWER_PASSWORD', 'password'), + default=env.get('CONTROLLER_PASSWORD', env.get('TOWER_PASSWORD', 'password')), metavar='TEXT', ) auth.add_argument( '-k', '--conf.insecure', help='Allow insecure server connections when using SSL', - default=not strtobool(env.get('TOWER_VERIFY_SSL', 'True')), + default=not strtobool(env.get('CONTROLLER_VERIFY_SSL', env.get('TOWER_VERIFY_SSL', 'True'))), action='store_true', ) @@ -47,7 +47,7 @@ def add_output_formatting_arguments(parser, env): '--conf.format', dest='conf.format', choices=FORMATTERS.keys(), - default=env.get('TOWER_FORMAT', 'json'), + default=env.get('CONTROLLER_FORMAT', env.get('TOWER_FORMAT', 'json')), help=('specify a format for the input and output'), ) formatting.add_argument( @@ -61,7 +61,7 @@ def add_output_formatting_arguments(parser, env): '--conf.color', metavar='BOOLEAN', help='Display colorized output. Defaults to True', - default=env.get('TOWER_COLOR', 't'), + default=env.get('CONTROLLER_COLOR', env.get('TOWER_COLOR', 't')), type=strtobool, ) formatting.add_argument( @@ -69,7 +69,7 @@ def add_output_formatting_arguments(parser, env): '--verbose', dest='conf.verbose', help='print debug-level logs, including requests made', - default=strtobool(env.get('TOWER_VERBOSE', 'f')), + default=strtobool(env.get('CONTROLLER_VERBOSE', env.get('TOWER_VERBOSE', 'f'))), action="store_true", ) diff --git a/awxkit/awxkit/cli/resource.py b/awxkit/awxkit/cli/resource.py index 0038b96a7d..b4b8a8e2ae 100644 --- a/awxkit/awxkit/cli/resource.py +++ b/awxkit/awxkit/cli/resource.py @@ -99,7 +99,7 @@ class Login(CustomCommand): else: fmt = client.get_config('format') if fmt == 'human': - print('export TOWER_OAUTH_TOKEN={}'.format(token)) + print('export CONTROLLER_OAUTH_TOKEN={}'.format(token)) else: print(to_str(FORMATTERS[fmt]({'token': token}, '.')).strip()) diff --git a/awxkit/awxkit/cli/sphinx.py b/awxkit/awxkit/cli/sphinx.py index dc76c5e7a0..47366898d3 100644 --- a/awxkit/awxkit/cli/sphinx.py +++ b/awxkit/awxkit/cli/sphinx.py @@ -43,7 +43,7 @@ def render(): # The return value of this function is an argparse.ArgumentParser, which # the sphinxcontrib.autoprogram plugin crawls and generates an indexed # Sphinx document from. - for e in ('TOWER_HOST', 'TOWER_USERNAME', 'TOWER_PASSWORD'): + for e in ('CONTROLLER_HOST', 'CONTROLLER_USERNAME', 'CONTROLLER_PASSWORD'): if not os.environ.get(e): raise SystemExit('Please specify a valid {} for a real (running) installation.'.format(e)) # noqa cli = CLI() diff --git a/awxkit/test/cli/test_config.py b/awxkit/test/cli/test_config.py index 61b6b4c54d..6998cb88ab 100644 --- a/awxkit/test/cli/test_config.py +++ b/awxkit/test/cli/test_config.py @@ -7,7 +7,7 @@ from awxkit import config def test_host_from_environment(): cli = CLI() - cli.parse_args(['awx'], env={'TOWER_HOST': 'https://xyz.local'}) + cli.parse_args(['awx'], env={'CONTROLLER_HOST': 'https://xyz.local'}) with pytest.raises(ConnectionError): cli.connect() assert config.base_url == 'https://xyz.local' @@ -23,7 +23,7 @@ def test_host_from_argv(): def test_username_and_password_from_environment(): cli = CLI() - cli.parse_args(['awx'], env={'TOWER_USERNAME': 'mary', 'TOWER_PASSWORD': 'secret'}) + cli.parse_args(['awx'], env={'CONTROLLER_USERNAME': 'mary', 'CONTROLLER_PASSWORD': 'secret'}) with pytest.raises(ConnectionError): cli.connect() @@ -43,7 +43,7 @@ def test_username_and_password_argv(): def test_config_precedence(): cli = CLI() - cli.parse_args(['awx', '--conf.username', 'mary', '--conf.password', 'secret'], env={'TOWER_USERNAME': 'IGNORE', 'TOWER_PASSWORD': 'IGNORE'}) + cli.parse_args(['awx', '--conf.username', 'mary', '--conf.password', 'secret'], env={'CONTROLLER_USERNAME': 'IGNORE', 'CONTROLLER_PASSWORD': 'IGNORE'}) with pytest.raises(ConnectionError): cli.connect()