Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,19 @@ and this project adheres to the versioning of
Testing a lookup credential, or a lookup credential type, performs the lookup
and reports what came back, with `--inputs` and `--metadata` to try values that
are not saved yet. Both take JSON or YAML, or `@` a file holding either.
- Every connection setting now answers to an `ASCENDER_` environment variable:
`ASCENDER_HOST`, `ASCENDER_OAUTH_TOKEN`, `ASCENDER_TOKEN`, `ASCENDER_USERNAME`,
`ASCENDER_PASSWORD`, `ASCENDER_VERIFY_SSL`, `ASCENDER_VERBOSE`,
`ASCENDER_FORMAT` and `ASCENDER_COLOR`. The `CONTROLLER_` and `TOWER_` prefixes
still name the same settings, so nothing written against them breaks. Where more
than one is set, `ASCENDER_` wins, then `CONTROLLER_`, then `TOWER_`.

### Changed

- `ascender login -f human` prints `export ASCENDER_OAUTH_TOKEN=` rather than
`export CONTROLLER_OAUTH_TOKEN=`. Both variables are read, so a shell that
already sourced the old line keeps working.

- TLS certificates are now verified by default. Set `ASCENDERKIT_ASSUME_UNTRUSTED`
to restore the previous behaviour when talking to an Ascender that presents a
self-signed certificate. The CLI is unaffected: it already verified unless given
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ imported back, that monitored jobs exit 0, 1 and 2 for success, failure and
cancellation, and that token authentication works.

```bash
export CONTROLLER_HOST=https://ascender.example.org
export CONTROLLER_USERNAME=admin CONTROLLER_PASSWORD=...
export CONTROLLER_VERIFY_SSL=false # self-signed development servers
export ASCENDER_HOST=https://ascender.example.org
export ASCENDER_USERNAME=admin ASCENDER_PASSWORD=...
export ASCENDER_VERIFY_SSL=false # self-signed development servers
pytest tests/integration -v
```

Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ pip install -e ascender-kit
Point the client at your server and confirm it can authenticate:

```bash
export CONTROLLER_HOST=https://ascender.example.org
export CONTROLLER_USERNAME=alice
export CONTROLLER_PASSWORD=secret
export ASCENDER_HOST=https://ascender.example.org
export ASCENDER_USERNAME=alice
export ASCENDER_PASSWORD=secret

ascender config
```
Expand Down Expand Up @@ -69,9 +69,11 @@ Connection settings resolve from highest to lowest precedence:
| Precedence | Source |
| ---------- | ------ |
| 1 | Command line flags, such as `--conf.host` and `--conf.token` |
| 2 | Environment variables: `CONTROLLER_HOST`, `CONTROLLER_USERNAME`, `CONTROLLER_PASSWORD` |
| 2 | Environment variables: `ASCENDER_HOST`, `ASCENDER_USERNAME`, `ASCENDER_PASSWORD` |
| 3 | The config file written by `ascender login` and `ascender config` |

The `CONTROLLER_` and `TOWER_` prefixes still name the same variables, so a script written against either keeps working.

For repeated use, generate a token instead of passing credentials each time:

```bash
Expand Down
6 changes: 3 additions & 3 deletions ascenderkit/cli/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ wrong.

To build the reference guide as well, point the same command at a real Ascender:

~ CONTROLLER_HOST=https://ascender.example.org CONTROLLER_USERNAME=example \
CONTROLLER_PASSWORD=secret sphinx-build -b html -W source build/html
~ ASCENDER_HOST=https://ascender.example.org ASCENDER_USERNAME=example \
ASCENDER_PASSWORD=secret sphinx-build -b html -W source build/html

What needs a server, and why
----------------------------
Expand All @@ -28,5 +28,5 @@ version, settings and user access level.

The extension itself imports without a server. `render()` runs only when the
`autoprogram` directive asks for the parser, so that one page is what needs
`CONTROLLER_HOST`, not the Sphinx run. `conf.py` drops the page when no
`ASCENDER_HOST`, not the Sphinx run. `conf.py` drops the page when no
credentials are present, which is what makes the offline build work.
18 changes: 9 additions & 9 deletions ascenderkit/cli/docs/source/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@ The preferred mechanism for authenticating with Ascender is by generating and st

.. code:: bash

CONTROLLER_HOST=https://ascender.example.org \
CONTROLLER_USERNAME=alice \
CONTROLLER_PASSWORD=secret \
ASCENDER_HOST=https://ascender.example.org \
ASCENDER_USERNAME=alice \
ASCENDER_PASSWORD=secret \
ascender login

As a convenience, the ``ascender login -f human`` command prints a shell-formatted token
value:

.. code:: bash

export CONTROLLER_OAUTH_TOKEN=6E5SXhld7AMOhpRveZsLJQsfs9VS8U
export ASCENDER_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 CONTROLLER_HOST=https://ascender.example.org
$(CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret ascender login -f human)
export ASCENDER_HOST=https://ascender.example.org
$(ASCENDER_USERNAME=alice ASCENDER_PASSWORD=secret ascender login -f human)
ascender config

Working with OAuth2.0 Applications
Expand All @@ -43,7 +43,7 @@ application was created.

.. code:: bash

CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret ascender login \
ASCENDER_USERNAME=alice ASCENDER_PASSWORD=secret ascender login \
--conf.client_id <value> --conf.client_secret <value>


Expand All @@ -55,7 +55,7 @@ a read-only token, specify ``--scope read``:

.. code:: bash

CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret \
ASCENDER_USERNAME=alice ASCENDER_PASSWORD=secret \
ascender login --conf.scope read

Session Authentication
Expand All @@ -65,5 +65,5 @@ specify your username and password on every invocation:

.. code:: bash

CONTROLLER_USERNAME=alice CONTROLLER_PASSWORD=secret ascender jobs list
ASCENDER_USERNAME=alice ASCENDER_PASSWORD=secret ascender jobs list
ascender --conf.username alice --conf.password secret jobs list
6 changes: 3 additions & 3 deletions ascenderkit/cli/docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@
# resource against a running Ascender. There is no static command table to fall
# back on: the CLI discovers its own commands the same way. So that one page is
# the only part of these docs that needs a server, and asking for it without one
# exits with a message naming CONTROLLER_HOST.
# exits with a message naming ASCENDER_HOST.
#
# Rather than fail the whole build, drop the page when there are no credentials.
# The six hand-written pages then build offline, which is what CI does, and a
# build with CONTROLLER_HOST set still produces the complete documentation.
# build with ASCENDER_HOST set still produces the complete documentation.
#
# The toctree in index.rst names reference unconditionally. Moving that entry
# behind an `only` directive does not help, because toctree entries resolve
# while the source is read and `only` is evaluated later, so the warning is
# suppressed by name instead.
if not (os.environ.get('CONTROLLER_HOST') or os.environ.get('TOWER_HOST')):
if not any(os.environ.get(prefix + 'HOST') for prefix in ('ASCENDER_', 'CONTROLLER_', 'TOWER_')):
exclude_patterns = ['reference.rst']
suppress_warnings = ['toc.excluded']
2 changes: 1 addition & 1 deletion ascenderkit/cli/docs/source/output.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
``CONTROLLER_COLOR=f``.
``ASCENDER_COLOR=f``.
15 changes: 10 additions & 5 deletions ascenderkit/cli/docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,22 @@ A few of the most important ones are:
``-f, --conf.format``
used to specify a custom output format (the default is json)

``--conf.host, CONTROLLER_HOST``
``--conf.host, ASCENDER_HOST``
the full URL of the Ascender host (i.e., https://my.ascender.example.org)

``-k, --conf.insecure, CONTROLLER_VERIFY_SSL``
``-k, --conf.insecure, ASCENDER_VERIFY_SSL``
allows insecure server connections when using SSL

``--conf.username, CONTROLLER_USERNAME``
``--conf.username, ASCENDER_USERNAME``
the Ascender username to use for authentication

``--conf.password, CONTROLLER_PASSWORD``
``--conf.password, ASCENDER_PASSWORD``
the Ascender password to use for authentication

``--conf.token, CONTROLLER_OAUTH_TOKEN``
``--conf.token, ASCENDER_OAUTH_TOKEN``
an OAuth2.0 token to use for authentication

Each of those variables answers to two older prefixes as well, ``CONTROLLER_``
and ``TOWER_``, so a script written against either keeps working. Where more
than one is set the ``ASCENDER_`` name wins, then ``CONTROLLER_``, then
``TOWER_``.
43 changes: 34 additions & 9 deletions ascenderkit/cli/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,31 @@ def strtobool(val):
raise ValueError(f"invalid truth value {val!r}")


def env_default(env, suffix, default, extra=()):
"""The first of the accepted names that `env` sets, else `default`.

Three prefixes name the same setting, and all three stay readable so a
script written against any of them keeps working: `ASCENDER_` is what the
client documents, `CONTROLLER_` is what it read before the rebrand, and
`TOWER_` is what awxkit read before that. Precedence runs newest first.

`extra` names further suffixes to try under the same prefix before moving
on to the next one, which is how `ASCENDER_OAUTH_TOKEN` and `ASCENDER_TOKEN`
both reach the token argument. The prefix is the outer loop because it says
which release the script was written against, where the suffix is only a
spelling of the same thing.

A name that is set but empty wins over a later one, which is what the
nested `env.get()` calls this replaces did.
"""
for prefix in ('ASCENDER_', 'CONTROLLER_', 'TOWER_'):
for this_suffix in (suffix,) + tuple(extra):
name = prefix + this_suffix
if name in env:
return env[name]
return default


def get_config_credentials():
"""Load username and password from config.credentials.default.

Expand All @@ -41,12 +66,12 @@ def add_authentication_arguments(parser, env):
auth = parser.add_argument_group('authentication')
auth.add_argument(
'--conf.host',
default=env.get('CONTROLLER_HOST', env.get('TOWER_HOST', 'https://127.0.0.1:443')),
default=env_default(env, 'HOST', 'https://127.0.0.1:443'),
metavar='https://example.ascender.org',
)
auth.add_argument(
'--conf.token',
default=env.get('CONTROLLER_OAUTH_TOKEN', env.get('CONTROLLER_TOKEN', env.get('TOWER_OAUTH_TOKEN', env.get('TOWER_TOKEN', '')))),
default=env_default(env, 'OAUTH_TOKEN', '', extra=('TOKEN',)),
help='an OAuth2.0 token (get one by using `ascender login`)',
metavar='TEXT',
)
Expand All @@ -55,20 +80,20 @@ def add_authentication_arguments(parser, env):
# options configured via cli args take higher precedence than those from the config
auth.add_argument(
'--conf.username',
default=env.get('CONTROLLER_USERNAME', env.get('TOWER_USERNAME', config_username)),
default=env_default(env, 'USERNAME', config_username),
metavar='TEXT',
)
auth.add_argument(
'--conf.password',
default=env.get('CONTROLLER_PASSWORD', env.get('TOWER_PASSWORD', config_password)),
default=env_default(env, 'PASSWORD', config_password),
metavar='TEXT',
)

auth.add_argument(
'-k',
'--conf.insecure',
help='Allow insecure server connections when using SSL',
default=not strtobool(env.get('CONTROLLER_VERIFY_SSL', env.get('TOWER_VERIFY_SSL', 'True'))),
default=not strtobool(env_default(env, 'VERIFY_SSL', 'True')),
action='store_true',
)

Expand All @@ -79,7 +104,7 @@ def add_verbose(formatting, env):
'--verbose',
dest='conf.verbose',
help='print debug-level logs, including requests made',
default=strtobool(env.get('CONTROLLER_VERBOSE', env.get('TOWER_VERBOSE', 'f'))),
default=strtobool(env_default(env, 'VERBOSE', 'f')),
action="store_true",
)

Expand All @@ -91,7 +116,7 @@ def add_formatting_import_export(parser, env):
'--conf.format',
dest='conf.format',
choices=['json', 'yaml'],
default=env.get('CONTROLLER_FORMAT', env.get('TOWER_FORMAT', 'json')),
default=env_default(env, 'FORMAT', 'json'),
help=('specify a format for the input and output'),
)
add_verbose(formatting, env)
Expand All @@ -105,7 +130,7 @@ def add_output_formatting_arguments(parser, env):
'--conf.format',
dest='conf.format',
choices=FORMATTERS.keys(),
default=env.get('CONTROLLER_FORMAT', env.get('TOWER_FORMAT', 'json')),
default=env_default(env, 'FORMAT', 'json'),
help=('specify a format for the input and output'),
)
formatting.add_argument(
Expand All @@ -119,7 +144,7 @@ def add_output_formatting_arguments(parser, env):
'--conf.color',
metavar='BOOLEAN',
help='Display colorized output. Defaults to True',
default=env.get('CONTROLLER_COLOR', env.get('TOWER_COLOR', 't')),
default=env_default(env, 'COLOR', 't'),
type=strtobool,
)
add_verbose(formatting, env)
Expand Down
2 changes: 1 addition & 1 deletion ascenderkit/cli/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def handle(self, client, parser):
else:
fmt = client.get_config('format')
if fmt == 'human':
print(f'export CONTROLLER_OAUTH_TOKEN={token}')
print(f'export ASCENDER_OAUTH_TOKEN={token}')
else:
print(to_str(FORMATTERS[fmt]({'token': token}, '.')).strip())

Expand Down
10 changes: 3 additions & 7 deletions ascenderkit/cli/sphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,9 @@ 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 (
('CONTROLLER_HOST', 'TOWER_HOST'),
('CONTROLLER_USERNAME', 'TOWER_USERNAME'),
('CONTROLLER_PASSWORD', 'TOWER_PASSWORD'),
):
if not os.environ.get(e[0]) and not os.environ.get(e[1]):
raise SystemExit('Please specify a valid {} for a real (running) installation.'.format(e[0])) # noqa
for suffix in ('HOST', 'USERNAME', 'PASSWORD'):
if not any(os.environ.get(prefix + suffix) for prefix in ('ASCENDER_', 'CONTROLLER_', 'TOWER_')):
raise SystemExit('Please specify a valid ASCENDER_{} for a real (running) installation.'.format(suffix)) # noqa
cli = CLI()
cli.parse_args(['ascender', '--help'])
cli.connect()
Expand Down
18 changes: 10 additions & 8 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Tests that need a running Ascender.

The whole directory is skipped unless CONTROLLER_HOST is set, so `pytest
The whole directory is skipped unless a host is set, so `pytest
tests/` stays offline by default and CI is unaffected until it is pointed at a
server. To run them:

export CONTROLLER_HOST=https://ascender.example.org
export CONTROLLER_USERNAME=admin CONTROLLER_PASSWORD=...
export CONTROLLER_VERIFY_SSL=false # self-signed development servers
export ASCENDER_HOST=https://ascender.example.org
export ASCENDER_USERNAME=admin ASCENDER_PASSWORD=...
export ASCENDER_VERIFY_SSL=false # self-signed development servers
pytest tests/integration -v
"""

Expand All @@ -15,7 +15,9 @@

import pytest

HOST = os.environ.get('CONTROLLER_HOST')
from ascenderkit.cli.format import env_default

HOST = env_default(os.environ, 'HOST', None)

# Without a server there is nothing here to run, so do not collect it at all.
# A skip marker would still build the fixtures and report a wall of errors.
Expand All @@ -31,7 +33,7 @@ def pytest_collection_modifyitems(items):
@pytest.fixture(scope='session')
def insecure():
"""`-k` when the server presents a certificate we should not verify."""
verify = os.environ.get('CONTROLLER_VERIFY_SSL', 'true').lower()
verify = env_default(os.environ, 'VERIFY_SSL', 'true').lower()
return ['-k'] if verify in ('false', 'f', 'no', 'n', '0', 'off') else []


Expand Down Expand Up @@ -59,10 +61,10 @@ def api():
from ascenderkit.utils import PseudoNamespace

config.base_url = HOST
config.assume_untrusted = os.environ.get('CONTROLLER_VERIFY_SSL', 'true').lower() in ('false', 'f', 'no', 'n', '0', 'off')
config.assume_untrusted = env_default(os.environ, 'VERIFY_SSL', 'true').lower() in ('false', 'f', 'no', 'n', '0', 'off')
config.use_sessions = True
config.credentials = PseudoNamespace(
{'default': {'username': os.environ.get('CONTROLLER_USERNAME', 'admin'), 'password': os.environ.get('CONTROLLER_PASSWORD', '')}}
{'default': {'username': env_default(os.environ, 'USERNAME', 'admin'), 'password': env_default(os.environ, 'PASSWORD', '')}}
)
root = _api.Api()
root.load_session().get()
Expand Down
Loading