Conversation
ctrliq#984 renamed the eleven settings the database registers. Seventeen more were left, because they are not registered: thirteen are defined in defaults.py and overridden in a settings file, and four are read from the environment. The thirteen take the same route as ctrliq#984: renamed at the definition and every use, with the old name added to the _FORMER_NAMES table so a conf.d file that writes AWX_NOTIFICATION_REQUEST_TIMEOUT still lands on the current name. The four from the environment could not, and this is the part worth reading. That table runs after the settings modules have loaded, and AWX_SETTINGS_FILE and AWX_SETTINGS_DIR decide which settings file loads at all, so an alias applied afterwards would be too late to matter. They read both names directly instead, through one helper, in the same shape as the ASCENDER_ variables ascender-kit took in its ctrliq#72. Nothing has to move to the new names. AWX_SETTINGS_FILE is what the Dockerfile passes to collectstatic, AWX_LOGGING_MODE is what the three installers write into their compose environments, and AWX_WEB_PROCESS is what supervisor sets. Those keep working for as long as they exist rather than for a release. A test asserts that no settings module reads one of the four with a bare os.environ.get, since that would answer to one name only, which is the failure the helper exists to prevent. Both directions are checked against a real production settings load: the old environment name still selects the settings file, and a file written with the old setting name still carries over.
The package on disk is ascender/ now, and awx/ is the alias. Same machinery as
one module object.
>>> import ascender.main.models, awx.main.models
>>> awx.main.models is ascender.main.models
True
The old name cannot be dropped, and not only for a release. The
awx.credential_plugins entry points live in other people's packages and cannot
be rewritten from here. The dispatcher is the one that cannot wait for anything
to move: task names travel as strings and a worker imports the path it was
sent, so a message queued before an upgrade and read after it names
awx.main.tasks.system.delete_inventory and has to resolve.
What moved: 2,035 import statements, the module paths in INSTALLED_APPS, the
app configs, the settings module, the middleware and filter class paths, the
periodic task paths, the mock targets in the tests, and the packaging, the
Makefile, the Dockerfile, tox, CI and the documentation paths.
What deliberately did not move, because each is a published name rather than an
import path:
- The logger names. Every one of the 155 is identical to main, checked by
extracting them from both trees and comparing. The five loggers that derived
their name from __name__ are now named explicitly, or they would have slipped
out from under the LOGGING keys that configure them.
- LOGGER_BLOCKLIST, a tuple of those logger names. Moving it stopped awx.conf
records being blocked, so the level filter read LOG_AGGREGATOR_LEVEL for each
one and preloaded the settings cache from the database: the suite went from
seventeen seconds to fourteen minutes.
- CREDENTIAL_PLUGIN_GROUPS, where awx.credential_plugins is the group a third
party declares itself under. Moving it left both entries naming the same
group, so nothing outside this repository would have loaded at all.
- The distribution name in pyproject, still awx, so dist/awx.tar.gz and every
installer that names it are untouched.
- The venv at /var/lib/awx/venv, the image layout, and the container names.
Django app labels are unaffected: a label is the last component of the app
config's name, so awx.main and ascender.main are both `main`, and no migration
or content type changes.
The alias already exists. The Dockerfile creates /var/lib/ascender as a symlink onto /var/lib/awx in both image stages, and the platform's own settings have read and written through it for a while: STATIC_ROOT, the projects root and the logging spool all name it already. What lagged behind was the build tooling, which still spelled the old path. Moved: the version file and the collected UI static in the Makefile, the working directory of every supervisor program, the config watcher's hash file, and the directory tree the support bundle collects. Left on /var/lib/awx deliberately: - VENV_BASE and everything under venv/awx. The virtualenv is built by make requirements_awx, which the Dockerfile runs before it creates the symlink, and virtualenv_awx does mkdir $(VENV_BASE) rather than mkdir -p, so pointing it at the alias breaks the image build. - The compose volume mounts, and everything in the Dockerfile that creates, copies or chowns the directory or declares the VOLUME. Those own the real path, and a mount landing on /var/lib/ascender would replace the symlink with a directory. - HOME and the passwd entries written at startup, which match ENV HOME.
The previous commit moved the callers onto /var/lib/ascender while the data still sat in /var/lib/awx. That leaves the retirement of the old name as a move of everything on disk. Pointed the other way round it is one deleted symlink, so the image now builds the directory as /var/lib/ascender and links /var/lib/awx onto it. Nothing has to move for this to be safe, in either direction: - A deployment still mounting a volume at /var/lib/awx keeps working. runc resolves the link inside the container and lands the mount on the directory it names, verified here with both a bind mount and a named volume. Even if a runtime did create a directory over the link instead, the operator's configmap names /var/lib/awx for PROJECTS_ROOT, STATIC_ROOT and JOBOUTPUT_ROOT, so it would read the mount either way. - VENV_BASE can move with it. virtualenv_awx runs before the link exists, but its mkdir has always failed harmlessly on a missing parent and python -m venv creates the tree with makedirs. That is as true of /var/lib/awx today as it is of /var/lib/ascender now, checked by running the sequence in the base image. The CI step that asserts both names reach the same inode is unchanged and still passes, since it tests the link rather than its direction.
The two composite actions were still named for AWX, and so were a handful of things around them: - .github/actions/awx_devel_image is now ascender_devel_image, and run_awx_devel is run_ascender_devel. All five uses: references and both dependabot directories entries move with them, since a composite action is only picked up per directory. - The devel image matrix names ascender_devel, which is what it publishes. - The admin password step calls ascender-manage rather than awx-manage. The alias is in the image either way, so CI may as well exercise the name we ship. - Two comments pointed at awx/settings/defaults.py, which moved. What deliberately stays: the tools_awx_1 container name, the awx-uvicorn supervisor program, and the awx_image and awx_version variables the build playbooks take. None of those are CI's own names, they are the compose service, the supervisor config and the ansible roles, and renaming them from here would leave the Makefile and the docs pointing at something that no longer exists.
Three names the package rename left behind, none of which is a published identifier: nothing outside imports them and nothing refers to them as a string. - AWXBaseEmailBackend, the base every notification backend subclasses. Only ever imported as a class, never named as a settings path, so there is no EMAIL_BACKEND string anywhere to keep working. - get_awx_http_client_headers, which builds the User-Agent the notification backends and analytics send. - get_awx_version, used by the API version response, the analytics collectors and the UI context processor. The rsyslog template moves with them, from template(name="awx") to template(name="ascender"). That one is safe because it is declared and referenced inside the config this file generates, so the name never leaves. The queue file name does not move, and now says why in a comment. It is the spool file on disk, so renaming it would start a new queue at upgrade and strand whatever was still spooled in the old one, losing those records without logging anything to say so.
AWXLogin becomes AscenderLogin, with its props interface and its test export. The component is internal to the Login screen and nothing outside the two files refers to it. Two neighbouring names were checked and deliberately left, each now saying so in place, because both are strings a deployment can be holding: - The awx-* class names the UI renders. CUSTOM_THEME is an administrator's own CSS file, applied to the live UI, and those class names are the only stable hooks a theme has to target. Renaming the 177 of them would stop every theme already uploaded from applying, with no error and nothing in the logs. - AWXModelBackend. AUTHENTICATION_BACKENDS names it by dotted path rather than importing it, and the path is offered as a choice in the authentication settings, so a deployment that has set it has the old string stored. Both want a deprecation rather than a rename, which is a decision rather than a mechanical change.
The compose service, its container and its hostname were awx_1 and tools_awx_1, which is the name a developer types all day and the one every doc page uses. They are ascender_1 and tools_ascender_1 now, along with the init container, and everything that addresses them follows: the Makefile, CI, the six docs pages, the smoke test, the haproxy backends and the l18n script. Two of those had to move together with the hostname or the environment would come up broken rather than merely misnamed: - The receptor node id, its firewall rule and its tcp-peer address, which are the hostname by another name. The hop node's peer address as well. - The peer registration in bootstrap_development.sh, which writes the names itself in a shell loop rather than reading them from the compose file. The volume names stay: tools_awx_db_15 and var_lib_awx hold the development database and the projects directory, and renaming them hands every developer an empty environment instead of a renamed one. Worth knowing on upgrade: the hostname is what provision_instance registers, so an existing environment has an instance called awx_1 that the new container will not answer to. A fresh environment is unaffected, and an existing one wants make docker-compose-container-group-clean or a new instance registration.
The tree a developer edits was mounted at /awx_devel, which is the path in the Dockerfile, the compose file, both supervisor configs, the launch scripts, the Makefile, CI and the docs. It is /ascender_devel now. The old path stays as an alias, because the mount comes from outside the image and this repository does not own every mount that names it: the operator mounts /awx_devel in its kube-dev deployments, and anyone can have a compose override of their own. The dev image creates /ascender_devel and links /awx_devel onto it, so a deployment still naming the old path has its mount resolved onto the real directory. Verified both directions with a real mount. The alias is dev only. Nothing mounts a source tree into the production image, so there is no empty directory shipped there. The provenance comments in requirements.txt move with it. pip-compile writes the path it ran under, so this is what requirements/updater.sh produces on its next run rather than an edit that will drift.
What the directory rename left saying AWX, none of it reaching outside the process: - The private settings attributes and cache keys, _awx_conf_settings, _awx_conf_version, _awx_conf_memoizedcache, _awx_conf_preload_expires and _awx_conf_init_readonly. Two of those are written to the shared settings cache, so an upgrade leaves the old keys behind, but they carry a TTL and the only effect of missing them is one preload. - The make target awx-link, and the six scripts that call it. - The placeholder setting names the conf tests register, AWX_SOME_SETTING and its relatives, which exist only inside those tests. - Ninety one source paths quoted in comments and docstrings, which named awx/api/serializers.py and its neighbours after the files had moved. The paths were rewritten only where they name this tree. A path inside a github.com/ansible/awx URL is upstream's, ~/.awx is the directory an old install left behind, and venv/awx is the virtualenv's own name, which ascender-install addresses by absolute path from another repository.
The comments, docstrings and documentation that describe what this code does still called it AWX. They say Ascender now, along with one user facing string: the label on the default subscription model, which the settings API hands to the UI. The pass was deliberately narrow, because most of what is left is not prose: - A line naming upstream keeps the upstream name. docs/rbac.md distinguishes the original AWX role system from the django-ansible-base one, SEARCH.md is about the AngularJS UI that predates the fork, and the data migration note names AWX 18.0, which is a version that exists. - kind: AWX in the install guide stays, because that is the resource the operator serves. Writing Ascender there documents something that does not exist yet. - The toleration value in the clustering guide stays. It is a Kubernetes value matched against a node taint, not a sentence. Two things had to move with the prose or they would have broken quietly. The markdown anchors follow their headings, so the in page links were rewritten and every one of them checked to resolve. And the subscription label lives in conf.py, so the generated API types were regenerated from it rather than edited, which is what the drift check compares.
The production configs had already dropped it for most of them, so nginx, uvicorn, dispatcher, callback-receiver, wsrelay and ws-heartbeat were bare there while the development config still spelled every one awx-. The four that kept the prefix on both sides, rsyslogd, rsyslog-configurer, cache-clear and autoreload, lose it too, and the development names now match the production ones rather than being a second set. Nothing outside this repository names a program. The operator runs supervisorctl status and counts what is not RUNNING, never naming one, so the config and the code that restarts by name ship in the same image and move together. Both places that name one were moved with it: the rsyslog restart after a logging settings change, and the 4xx recovery script that matches on processname. All four groups are still tower-processes, so the group qualified name that code builds resolves in every config. The autoreload script is renamed on disk to match its program, along with the make target and the three supervisor templates that invoke it. /var/run/awx-receptor keeps its name, unlike the program that writes into it. ascender-install mounts a volume there and passes it as RECEPTORCTL_SOCKET from its own compose file, so the directory is not ours alone to rename.
The directory holding the receptor control socket keeps the same shape as the data directory and the development tree: the real path takes the Ascender name and the old one becomes an alias onto it, so retiring it later is a deleted symlink rather than a coordinated move. The alias is needed rather than tidy. ascender-install mounts a named volume at /var/run/awx-receptor and passes that path as RECEPTORCTL_SOCKET from its own compose file, so the old name has to keep resolving until that repository moves. Checked with a named volume mounted at the old path: the socket written through it appears at the new path, and the symlink survives the mount, so receptorctl on the old name and the platform on the new one are looking at one file.
Every message in the eight Django catalogues and the UI ones carries a #: line naming the file and line it was extracted from, and all 17,573 of them still said awx/. They are not translations and nothing reads them at runtime, but they are what a translator follows to find the string in context, and they now point at files that are not there. Rewritten rather than regenerated, and only on the #: lines. The files moved without their contents changing, so the line numbers still hold and this is exactly what makemessages writes on its next run. Regenerating instead would have reflowed entries and churned the msgids along with them, which is a much larger diff for the same result. Checked with msgfmt. The Django catalogues all parse clean. The UI ones report duplicate definitions, which they did before this change as well: lingui tolerates them where msgfmt does not.
The only translatable strings left naming AWX were the description at the API root, which the browsable API prints at the top of /api/, and the provisioning callback help text on a job template. Both name the product to the person reading them. Changing a msgid costs the translations of that string until the catalogues are regenerated, which is why this is two strings and not a sweep. Nothing asserts either one, and the documentation already called it the Ascender REST API, so the API now agrees with its own reference manual.
A Prometheus metric name is a contract. A dashboard, a recording rule and an alert all key on it, so renaming one breaks them with nothing to say why: the panel goes empty and the alert stops firing. The thirty one metrics at /api/v2/metrics are ascender_ now, and every one of them is served a second time under its former awx_ name, so nothing that scrapes this has to change on the day of the upgrade. The old names can go once the dashboards have moved. The duplication is done on the exposition text, matching the metric name only where it can appear: the start of a HELP line, a TYPE line, or a sample. A label value that happens to contain the prefix is left alone, which a test covers. The internal keys move without a second name, because both ends of each are in this repository and nothing outside reads them: the subsystem metrics hash in Valkey, the dispatcher and callback receiver statistics keys, and the broadcast websocket gauges that run_wsrelay reads back for its status output. An upgrade leaves the old keys behind to expire, and the only effect is one window of subsystem metrics while the new ones fill. test_metrics now asserts what it should: that every family appears under both names, and that the two carry the same value rather than being two readings.
All 1,029 of them, across the stylesheets that define them and the components that carry them. I had left these alone on the assumption that a custom theme would target them, and that was worth checking rather than assuming. None of the four shipped themes references a single one: they scope to html[data-theme="..."] and style PatternFly classes and element ids, which is the shape the CUSTOM_THEME help text asks an administrator to follow. The comment that said otherwise is corrected in place. Checked that the rename kept every class paired with its definition: one class defined and never referenced, thirteen referenced and never defined, and both counts are exactly what they were before the rename. Nothing outside ascender/ui/src referred to one, so there is nothing left dangling. Two things the pattern caught that are not class names, both put back: awx-manage in the generated types header, where the source header file moved with it so the drift check still compares equal, and the execution environment image in the UI fixtures, which had become quay.io/ansible/ascender-ee. That registry path does not exist; the fixtures now name the image the product actually ships, ghcr.io/ctrliq/ascender-ee. The theme whose id is awx keeps it. That id names a look rather than the product, and it is stored per user in their browser, so renaming it would reset the theme of anyone who had chosen it.
A job's private data goes in /tmp/awx_42_xiwm, and that prefix is swept by the cleanup task and read by ascender-kit. It is ascender_ now, and both of those had to move with it rather than after it. The cleanup sweeps both prefixes, one pass each. A single glob cannot spell them: they share only their first letter, and a pattern loose enough to match both matches a great deal else in /tmp, which is not a thing to be casual about in a routine whose job is deleting directories. So the pattern is built from whichever prefix the pass is for, and the callers run it once per prefix, both locally and on each execution node. A folder left by a release that used the old name is still rubbish, and would otherwise sit there forever. The exclusion list carries both names for every active job regardless of which pass is running, so a job that is still going is never swept by the pass looking for the other prefix. ascender-kit asserts the prefix when it reads a job's artifacts, and needs the matching change. Its own comment already points back at this constant.
The last AWX-branded name a user's own browser holds. The two places that fall back to a literal when the setting is missing move with it, and so does the documentation that quotes the cookie in a Set-Cookie example. This one is visible on upgrade and should be in the release notes: a browser holding awx_sessionid presents a name the server no longer reads, so everyone is logged out once. It is a login rather than a loss, and only on the upgrade that carries this change, but nobody should meet it by surprise. A deployment that set SESSION_COOKIE_NAME in its own settings file keeps its value, and a client has never needed to assume either name: the API returns X-API-Session-Cookie-Name on a successful login for exactly this reason, which the settings comment now says out loud.
The recorded job event payloads the UI tests compare against still carried /tmp/awx_<id>_ paths, which is a shape the product no longer produces, and so did the receptor cleanup CLI test and one docstring. Fixtures are evidence of what the server sends, so leaving them on the old name would have them quietly describing a release that no longer exists.
…ames AWXProfiler and AWXProfileBase, which the callback worker and the timing middleware instantiate, and the awx_host variable the keycloak, LDAP, tacacs and vault plumbing playbooks pass to the collection lookups. All internal: nothing outside this repository imports the classes or sets the variable. The dispatcher test's fixture hostnames move with them, and the metrics guide stops using awx_host as the example server name. test_fields.py keeps awx_secret and AWX_SECRET. They look like brand references and are not: that test feeds names to the credential injector schema validation and pairs each with the answer it should get, so renaming them changes what is being tested rather than what it is called. Four of its cases failed on the first attempt, which is what makes the point.
awx_image, awx_image_tag, awx_version, awx_docker_version and awx_official are the variables the build playbook and the compose sources role take, set by the Makefile and by the release and devel image workflows. All of them are this repository's own, read only by tools/ansible and tools/docker-compose, so they move together with nothing outside to keep in step. The job extra vars of the same name are a different thing and stay: awx_version and awx_license_type are passed into the project update playbook, not read by the build. Two image defaults were wrong and are fixed here rather than left. The release build defaulted to ansible/awx, which is upstream's image rather than the one this repository produces, and it is ctrliq/ascender now. The compose default had become ghcr.io/ansible/ascender_devel, which is a registry path that does not exist: my earlier rename of the /awx_devel mount point matched inside the image name. It is ghcr.io/ctrliq/ascender_devel, which is what CI builds and pushes.
awx.example.org, awxhost and https://awx across the OAuth2, credential plugin, credential type, notification parameter and tips pages. They are placeholders a reader substitutes, so they should read as this product rather than the one it came from.
Sixty five files under common/images, configure-awx-*, the splunk and loggly logging examples, the gitlab webhook status shot, the released image verification shot and the hop node topology diagram, along with every page that embeds one. Counted the image references before and after rather than trusting the sed: 546 both times, nought unresolvable both times. The first pass left one broken, instances_awx_task_pods_hopnode, because the file rename and the reference rewrite used different patterns, which is exactly what counting catches.
virtualenv_awx, requirements_awx and requirements_awx_dev, the four awx-kube build targets, the buildx builder names they create, and the kube_devel variable. All of them are this repository's own entry points, called by the Dockerfile, CI and the docs, which move with them. MANAGEMENT_COMMAND now defaults to ascender-manage. The image has always installed both names, so the Makefile may as well drive the one we ship; a caller overriding it keeps whatever they set. One stale path in a comment: the locale directories were described as living under awx/locale. Checked by asking make to resolve each renamed target rather than by reading: all eight, plus the two the Dockerfile calls.
Three places told the outside world it was AWX: the User-Agent on every
notification and analytics request, the X-API-Product-Name response header by
way of server_product_name, and the product_name in the open licence. The
browsable API's error page title as well.
The names are constants now rather than a literal repeated at each site, because
one of those sites was not printing the name but comparing against it:
inventory.py decided whether to use the downstream collection namespace with
server_product_name() != 'AWX'. Changing what the function returns without that
line would not have failed, it would have quietly taken the other branch on
every inventory update. It now asks the question it means, against
OPEN_PRODUCT_NAME.
About.tsx keeps its indexOf('AWX') check, deliberately. BRAND_NAME already ships
as "Ascender Automation" so that branch is already dead in practice, and
changing it would alter what the About box prints rather than what anything is
called. That is product copy and wants deciding rather than renaming.
About.tsx chose its wording with brandName.indexOf('AWX'), which was true when
the brand was AWX and has been false ever since BRAND_NAME became "Ascender
Automation". So the branch meant for the subscription product was the one
running, and the box has been printing "Ascender Automation Controller 25.6.3"
on an install that is not a Controller.
It checks for Ascender now, which puts each product back on its own branch:
Ascender Automation -> Ascender Automation 25.6.3
Red Hat Ansible Automation Platform -> Red Hat Ansible Automation Platform Controller 25.6.3
The brand fixtures across the UI tests move to the name the product actually
ships rather than staying on AWX, and the one assertion paired with a fixture,
the login logo's alt text, moves with its fixture so the test still checks that
the two agree.
Every caller in this repository now runs ascender-manage: the bootstrap script, the Makefile, the supervisor programs, the launch scripts, the migration wait, the smoke test, the minikube bootstrap and the support bundle. The wrapper in tools/docker-compose is renamed to match. What deliberately does not move is the alias itself. Both console scripts stay declared, both /usr/bin symlinks are still created in the production image and both /usr/local/bin ones in the development image, and the builder still calls the old name for collectstatic. The installers and the operator exec awx-manage by absolute path, so it has to keep working until they move; this changes who calls which name, not which names exist. Also here: the SQL profiling cache key, which is set and read in this repository alone, the remaining instance hostnames in the dispatcher fixtures, and prose in the receptor cleanup docstring and the named URL script. test_dispatch keeps its awx.main.tasks.system.delete_inventory assertions. That test exists to prove a task name queued before the rename still resolves after it, so the old prefix is the thing under test.
tools/scripts/awx-python is the wrapper that runs the platform's virtualenv python, installed on PATH and used as the shebang by the management command wrapper, the rsyslog recovery script and firehose. It is ascender-python now, and the three shebangs move with it. Both names are installed in both images. The rename is safe inside this repository because every shebang moved with the file, but a shebang is the kind of line someone copies into a script of their own, so the old name is a symlink onto the new one rather than gone. Rendered both Dockerfiles to check each name is actually installed rather than reading the template: the first attempt renamed the source path and left the destination, which would have left every one of those shebangs pointing at a file that is not there.
The package on disk has been ascender since the rename, but the distribution pip installs was still called awx, which is the name in the metadata, in the egg-info directory, in the sdist filename and in the entry point lookup. Built it and installed it rather than assuming: the sdist comes out as ascender-25.6.3.tar.gz, the metadata reads ascender, and both console scripts are still declared and still land on PATH, which is what keeps awx-manage working. Two things keep the old name so that nothing outside has to move first: - The version lookup asks for ascender and then for awx. An environment installed by an earlier release carries the old metadata, and without the second attempt it would fall through to reading the version out of git, which an installed tree does not have. All three paths are checked, including which name is asked for and in what order. - make sdist still leaves a dist/awx.tar.gz symlink beside dist/ascender.tar.gz. ascender-install pip installs that exact path from its own repository, so the old one stays until that moves.
The Ascender credential type sets the same five values under three names, and the Ascender ones were last, under the CONTROLLER_ block and the TOWER_ block it inherited. Nothing changes at runtime: all three are set either way. What changes is which name reads as the one that matters and which blocks are the ones to delete. The CONTROLLER_ and TOWER_ groups go when nothing reads them any more, and having them below rather than above means removing them leaves the file as it should end up rather than needing a reshuffle at the same time. This is the shape everywhere else already: the task name prefixes, the credential plugin entry point groups, the job folder prefixes, the metric prefixes and the settings environment lookup all put Ascender first with the old name behind it, and the settings alias table gives the Ascender name precedence when both are written down.
The group every platform process belongs to was tower-processes, which is the name the code builds a qualified process name against, the name CI restarts the web process through, and the name the config watcher matches on. Self-contained. The operator runs supervisorctl status and counts what is not RUNNING without naming a group, so the four configs that declare it and the three places that qualify against it all ship in the same image and move together. The recorded job environment fixtures carry SUPERVISOR_GROUP_NAME, so they move with it too.
/etc/tower holds the settings files and /var/log/tower the logs, and they are /etc/ascender and /var/log/ascender now, with the Tower names as aliases onto them. Same shape as the data directory, the development tree and the receptor socket: the real directory takes the Ascender name so retiring the old one is a deleted symlink. The aliases are needed rather than tidy. ascender-install names /etc/tower thirty seven times and the operator thirty eight, mounting individual files at /etc/tower/SECRET_KEY, /etc/tower/settings.py and /etc/tower/conf.d/*.py. A file mounted through a symlinked parent is a different case from the directory mounts the other aliases rely on, so it was checked rather than assumed: both a SECRET_KEY and a conf.d file mounted at the old paths land in /etc/ascender and the symlink survives the mount. production.py keeps naming /etc/tower in its two defaults. Those lines are rewritten by ctrliq#998 and resolve through the alias in the meantime, so they move when that lands rather than conflicting with it now. One thing worth naming, because I did it: the sweep that moved the tooling paths also rewrote the two ln -s lines that create the aliases, leaving symlinks pointing at themselves. Every symlink the image creates is now checked for that.
The classmethod that registers the managed credential types is setup_managed_defaults now. The brand in the middle of it had already stopped matching the field it sets: managed_by_tower became managed some releases ago. Thirteen migrations call it, which is what made this worth checking rather than assuming. A migration that has already run will not run again, but a fresh install runs all of them, so a missed call site is a install that dies at migrate time rather than a test that goes red. Ran every migration against an empty database: all applied, and the thirty one managed credential types those migrations exist to create are there afterwards. Renamed rather than aliased for that reason too. An alias would have hidden a missed call site instead of surfacing it, and the run above is what proves there are none. The migration tables in ascender-install and ascender-pro-install's awx_migrate_ascender READMEs name the old method in prose. They describe what each historical migration does, so they want the same edit when those move.
…file Three more, none of them a name anything outside resolves: - TowerSAMLIdentityProvider, which the SAML backend instantiates directly rather than naming by path, so nothing has the old string stored. - The tower_warnings log handler, which ten loggers route through. Checked that every handler a logger names still exists after the rename, and that Django configures the logging dictionary without complaint. - The test method names in the secret key regeneration tests. The handler's file moves with it, tower.log to ascender.log, and the RBAC migration log alongside it. That one is visible: an administrator tailing /var/log/tower/tower.log, or a log shipper globbing for it, finds nothing after the upgrade. The directory still resolves through its alias but the file inside it has a new name, so this belongs in the release notes with the session cookie. TowerSettings is deliberately untouched. It only appears in a frozen conf migration, resolved through apps.get_model against historical state, and that model has not existed for many releases.
The setting identifying a cluster to an external log aggregator is LOG_AGGREGATOR_ASCENDER_UUID now. It is a registered setting, so the value lives in the database under its key rather than only in a file, and renaming it in the code alone would leave whatever an administrator set behind under a name nothing reads. This follows the path TOWER_URL_BASE took: a conf migration moving the row with _rename_setting, and an entry in the former names table so a deployment that spells the old name in /etc/ascender/conf.d keeps being understood. Checked by planting a value under the old key, running the migration, and reading it back under the new one. A full migration from an empty database also runs clean, which is the thing a fresh install does. The UI fixtures, the generated API types and the logging guide move with it, and the generated settings types are still in step, which its own test asserts.
A custom credential type's injectors are a Jinja template the administrator
writes, and a file injector references the file it wrote as {{tower.filename}}.
That template is stored in the database and cannot be rewritten from here, so
the namespace could not simply be renamed: every credential type written before
this would stop resolving, and a job would fail at injection time with an
undefined variable rather than anything naming the cause.
Both names are bound to the same object instead, at both the render site and the
validator, from one tuple in constants. {{ascender.filename}} is what the
documentation now shows, {{tower.filename}} keeps working, and a field may not
be called either or it would shadow the namespace.
A parametrized test renders a file injector under each name and reads the file
back, which is the behaviour this exists for and nothing covered before.
The reserved-name error message names the field it rejected rather than assuming
which of the two it was.
The scheduler's singleton was named for Tower, and a model name is a table name, so this needed saying in a migration as well as in the class. Without the RenameModel the table stays main_towerschedulestate while Django starts looking for main_ascenderschedulestate, and the scheduler falls over on its next run. Checked the way a rename should be: makemigrations --check reports no drift, so the migration accounts for the whole change; then a full migrate against an empty database applies it, the table in postgres is main_ascenderschedulestate, and get_solo returns the singleton from it. The squashed 0004 migration keeps the old name, because that is what the model was called at that point in the history it describes.
api-lint runs ruff format --check, and ascender is longer than the names it replaced, so five lines that fitted before no longer do. Formatted with the version CI pins, ruff 0.16.6, rather than whatever is to hand: the API root response dict, a setting registration in the conf tests, the execution node generator in the cleanup task, a cleanup kwargs assertion, and a blank line in the licensing module. No behaviour in it, only line breaks. ruff check and yamllint -s . both pass.
The inventory script endpoint takes ?towervars=1 to add the platform's own variables to each host. That is a published query parameter: it appears in someone's script or bookmark and cannot be rewritten from here, so the view reads the Ascender spelling first and falls back to the old one. Everything behind the query string is internal and renamed outright: the keyword argument through get_script_data, the inventory import comments and the callers in the job tasks. A parametrized functional test asks the endpoint under each spelling and checks the variables come back, which is the compatibility this exists for.
tower_broadcast_all and tower_settings_change are the postgres LISTEN/NOTIFY channels the dispatcher and the cache clearer subscribe to. They are ascender_broadcast_all and ascender_settings_change now, named once in constants rather than spelled at each site. A NOTIFY reaches whoever is listening at that moment and is otherwise dropped, which is what shapes this. Both listeners subscribe to the old name as well as the new one, so during a rolling upgrade a node still on the old release publishes to tower_broadcast_all and is still heard. Publishers write to the Ascender name only: the reverse direction, a new node broadcasting to an old one, cannot be covered from here, and the traffic is settings invalidation and receptor config, so the worst of it is a node holding a stale setting until its cache expires. The old names are the ones to delete once no release in the field publishes to them, which is why they are constants beside the current pair rather than literals.
Two things CI caught. prettier reflowed fifteen files, all of it line wrapping: ascender- class names are longer than the awx- ones they replaced, so JSX that fitted on one line no longer does. Run at the version package.json pins. And AppContainer's about modal test still expected "< AWX 222 >" in the speech bubble. It is the second test covering that box, which I missed when fixing the first: its own fixture already says the brand is Ascender Automation, so the assertion was checking against a name nothing in the test provides.
…version The analytics collectors and the metrics payload reported the release under tower_version. That is a field name in data leaving the product, so a consumer keys on it: both names are sent now, Ascender first, and the old one can go when nothing reads it. /var/lib/ascender/.tower_version keeps its name, and now says why in place. Its presence is what tells get_licenser this is the subscription product rather than the open one, and nothing in this ecosystem writes it: an install that has the file got it from the Tower it was migrated from. Renaming the check would move such an install onto the open licence without a word, which is the opposite of what a rebrand should do. Worth noting since it was not deliberate: the build variable sweep a few commits back also renamed the file make version_file writes, from .awx_version to .ascender_version. Nothing reads that one, in this repository or any of the others, so it is write-only either way, and the new name is the right one.
…nto feat/drop-subscription # Conflicts: # ascender/main/notifications/pagerduty_backend.py # ascender/main/notifications/twilio_backend.py # ascender/main/tests/unit/settings/test_environment_names.py # ascender/main/tests/unit/settings/test_logging_mode.py # ascender/settings/environment.py
get_licenser chose between two answers by whether /var/lib/ascender/.tower_version existed: present meant the subscription product, an entitlement manifest to parse and a subscription to keep in date. Nothing in this ecosystem has ever written that file, so the only way to reach that branch was to carry it over from the Tower an install was migrated from, and Ascender has no subscription to check. It returns the open licence, always. What goes with it: - The Licenser class, its entitlement certificate parsing, its signature verification and its expiry arithmetic. licensing.py is 76 lines where it was 288, and the cryptography, zipfile and date parsing imports go with it. - validate_entitlement_manifest, which only that class used. - POST to /api/v2/config/, which took a manifest. The method stays and answers 400 saying there is no licence to install, rather than disappearing and leaving a client with a bare 405. - The EULA on the config endpoint, which was rendered only when the licence was not open, so never. - Two branches that asked which product this is and now cannot vary: server_product_name, and the inventory source that chose redhat.satellite over theforeman.foreman. Every install already took the second one. The API shape does not move. get_licenser().validate() is still what the config view, the access checks, the bulk serializer and the host metrics read, and it still returns license_type open with a valid key. test_tasks patched Licenser.validate to keep the project update off the network; it patches OpenLicense.validate now, which is the class that method is on.
…nd them The previous commit took away the manifest endpoint and made the licence always open. That left the UI for it standing: a Subscription settings page whose wizard posted a manifest to an endpoint that now answers 400, and a Subscription Usage graph reading a usage model nothing sets. Both were already unreachable, which is the point cigamit made on the forum thread. The settings page redirects to /settings when the licence type is open, which it now always is. The usage route and the host metrics route are both deleted from the route config unless SUBSCRIPTION_USAGE_MODEL is unique_managed_hosts, and the default is empty. So this removes pages nobody could open, rather than taking anything away. Gone: the Subscription settings screen and its detail and edit wizard, the Subscription Usage screen and its chart, the SubscriptionUsage API model, the settings list entry, the settings route and its breadcrumbs. The app level gate goes with them. useAuthorizedPath answered false when the licence had no valid key, which sent every route to /subscription_management and the wizard. The open licence is always valid and there is no wizard to send anyone to, so it returns true, and the branch that rendered the wizard is gone. The hook stays rather than being deleted because AppContainer reads it for sidebar visibility. Host metrics is deliberately untouched. cigamit said those are useful and might be opened up for everyone, which is a decision rather than a removal. Checked: type check, eslint and prettier clean, and the UI suite passes whole, 549 files and 3,050 tests.
The nineteen /api/v2/analytics/ routes proxied to console.redhat.com and authenticated with the subscription credentials, which PR 1005 has already taken out. Without them every one of those endpoints answers an error, so they go, along with the gather_analytics task that shipped the payload and the collectors that only existed to fill it. What stays is the part that was never about Red Hat: the config, counts, instance_info, job_counts and job_instance_counts collectors still feed /api/v2/metrics, and config() keeps everything except the twenty licence fields it used to carry. The nine settings that configured the upload are unregistered, and conf migration 0014 deletes their rows, so a Red Hat username and password do not sit in an upgraded database with no screen left to clear them from. test_secret_key_regeneration exercised encryption through REDHAT_PASSWORD; it now uses LOG_AGGREGATOR_PASSWORD, which is the other encrypted setting and tests the same path.
blaipr
added a commit
to blaipr/ascender-collection
that referenced
this pull request
Sep 15, 2026
…ence role The license module posted a Red Hat subscription manifest to /api/v2/config/ and attached a pool through config/attach. The platform now answers that POST with an error, and config/attach has never had a route in ctrliq/ascender at all, so the pool_id path was broken before this and the manifest path is broken as of ctrliq/ascender#1005. subscriptions read what a Red Hat or Satellite account was entitled to through config/subscriptions, another route the platform no longer serves, and the license role existed only to call the two of them. get_stats read awx_license_instance_total and awx_license_instance_free, the two gauges that counted entitlements, and registered a settings request only to print a LICENSE setting. All three are gone from the platform, so the playbook stops asking for them.
This was referenced Sep 15, 2026
…skipped check_license and check_org_host_limit in access.py both opened by returning early when the licence type was open, which it always is, so neither has done anything on an install that was not carried over from Tower. They are gone, and with them the validate_license parameter that seventeen can_start and can_add signatures carried for callers that wanted to skip the check. That parameter had outlived its purpose in a way worth naming: check_related called can_access(type, 'start', resource, None) where the None was meant as the data argument that can_change takes, and instead landed on validate_license. A positional argument meaning "no data" was silently reading as "no licence check". inventory_import held its own copies, including a remote_tower_license_compare that nothing but the test suite ever called, and the try/except around the atomic block existed only to turn a licence PermissionDenied into a flag on the inventory update. license_error and org_host_limit_error stay on the model and in the serializers, since they are API surface, and are simply never set now. The rest are call sites that asked the licenser a question with one answer: the scheduler refusing to spawn a job when the probe raised, the bulk host create counting free instances after it had already enforced the organisation limit, the webhook status context choosing between ansible/awx and ansible/tower, and the User-Agent that named the licence type. The last two keep the string they produced, WEBHOOK_STATUS_CONTEXT and a literal open, so nothing on the wire moves. PENDO_TRACKING_STATE stays read-only, which is what the open licence already made it. The LICENSE setting is unregistered and conf migration 0014 deletes its row, which takes with it the read-only special case in the settings serializer, the key it was skipped under on update, the exclusion on destroy, and its place in the set that decides when the authentication backends default is recomputed. DELETE /api/v2/config/ used to write it back as an empty dict; it now answers the way POST does. The sosreport plugin stops running ascender-manage check_license, the two sections of docs/tasks.md describing tasks that no longer exist go with it, and the Miscellaneous System settings screen drops the code editor it rendered AUTOMATION_ANALYTICS_LAST_ENTRIES in. get_licenser().validate() is unchanged, and the config endpoint still serves license_info, which is what the UI reads.
cigamit
pushed a commit
to ctrliq/ascender-kit
that referenced
this pull request
Sep 15, 2026
Config carried six properties reading fields the config endpoint no longer returns: valid_key, instance_count, trial, features, and a license_type that is only ever open. is_aws_license read metadata a Tower AMI put there. None of them can answer anything but a default now, which is worse than not being there, because a caller reading is_valid_license gets False rather than an error telling it the question no longer applies. The subscriptions page posted to config/subscriptions/ and ConfigAttach posted to config/attach/. Neither route exists in ctrliq/ascender: config/subscriptions went with ctrliq/ascender#1005 and config/attach was never served at all. page.py sniffed response bodies for eleven licence phrases to decide between LicenseInvalid, LicenseExceeded and the ordinary Forbidden or BadRequest. The platform does not emit any of those phrases any more, so every response takes the else branch, and the two exception classes go with the sniffing.
cigamit
pushed a commit
to ctrliq/ascender-collection
that referenced
this pull request
Sep 15, 2026
…ence role (#309) * feat: remove the licence module, the subscriptions module and the licence role The license module posted a Red Hat subscription manifest to /api/v2/config/ and attached a pool through config/attach. The platform now answers that POST with an error, and config/attach has never had a route in ctrliq/ascender at all, so the pool_id path was broken before this and the manifest path is broken as of ctrliq/ascender#1005. subscriptions read what a Red Hat or Satellite account was entitled to through config/subscriptions, another route the platform no longer serves, and the license role existed only to call the two of them. get_stats read awx_license_instance_total and awx_license_instance_free, the two gauges that counted entitlements, and registered a settings request only to print a LICENSE setting. All three are gone from the platform, so the playbook stops asking for them. * ci: name the test database credentials rather than inheriting them The unit job started a PostgreSQL service as awx/awxpass and relied on settings_for_test in the ascender checkout falling back to the same names when no AWX_TEST_DATABASE_* was set. Those fallbacks moved to ascender/ascenderpass with the package rename, so the job has been failing on main with a password authentication error that reads as a broken test rather than a renamed default. Naming them in the step decouples this workflow from what the platform happens to default to.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #997 and #998, so the diff below carries their commits until those land. This change is the last four commits.
get_licenserchose between two answers by whether/var/lib/ascender/.tower_versionexisted: present meant the subscription product, an entitlement manifest to parse and a subscription to keep in date. Nothing in this ecosystem has ever written that file, so the only way to reach that branch was to carry it over from the Tower an install was migrated from, and Ascender has no subscription to check. It returns the open licence, always.This follows #948, which stopped the remote calls to Red Hat. What was left was the local machinery and the pages in front of it.
The server
Licenserclass: entitlement certificate parsing, signature verification, expiry arithmetic.licensing.pyis 51 lines where it was 288, and thecryptography,zipfileand date parsing imports go with it.validate_entitlement_manifest, which only that class used.POST /api/v2/config/, which took a manifest. The method stays and answers400saying there is no licence to install, rather than disappearing and leaving a client with a bare405.server_product_name, and the inventory source that choseredhat.satelliteovertheforeman.foreman. Every install already took the second.The UI
Both pages were already unreachable, which is the point @cigamit made on the forum. The settings page redirects to
/settingswhen the licence type is open, which it now always is. The usage route is deleted from the route config unlessSUBSCRIPTION_USAGE_MODELisunique_managed_hosts, and the default is empty.SubscriptionUsageAPI model.useAuthorizedPathanswered false when the licence had no valid key, which sent every route to/subscription_managementand the wizard. It returns true now, and the branch that rendered the wizard is gone. The hook itself stays becauseAppContainerreads it for sidebar visibility.Host metrics is deliberately untouched. That is the other half of the thread, and opening those up for everyone is a decision rather than a removal.
The enforcement
Two later commits, because removing the licence left more dead code behind it than the pages did.
check_licenseandcheck_org_host_limitinaccess.pyboth opened by returning early when the licence type was open, so neither has done anything on an install that was not carried over from Tower. They are gone, and with them thevalidate_licenseparameter that seventeencan_startandcan_addsignatures carried for callers wanting to skip the check.That parameter had outlived its purpose in a way worth naming.
check_relatedcalledcan_access(type, 'start', resource, None)where theNonewas meant as the data argumentcan_changetakes, and instead landed onvalidate_license. A positional argument meaning "no data" was silently reading as "no licence check".inventory_importheld its own copies, including aremote_tower_license_comparethat only the test suite ever called, and thetry/exceptaround its atomic block existed only to turn a licencePermissionDeniedinto a flag on the inventory update.license_errorandorg_host_limit_errorstay on the model and in the serializers, since they are API surface, and are simply never set now.The rest were call sites asking the licenser a question with one answer: the scheduler refusing to spawn a job when the probe raised, the bulk host create counting free instances after it had already enforced the organisation limit, the webhook status context choosing between
ansible/awxandansible/tower, and the User-Agent naming the licence type. The last two keep the exact string they produced, so nothing on the wire moves.PENDO_TRACKING_STATEstays read-only, which is what the open licence already made it.LICENSEis unregistered, which takes with it the read-only case in the settings serializer, the key it was skipped under on update, the exclusion on destroy and its place in the set deciding when the authentication backends default is recomputed.DELETE /api/v2/config/used to write it back as an empty dict and now answers the wayPOSTdoes.The Red Hat analytics upload
The other half of Insights. Nineteen
/api/v2/analytics/routes proxied toconsole.redhat.comand authenticated with the subscription credentials, so with those gone every one answers an error. The routes go, thegather_analyticstask that shipped the payload goes, and the collectors that only existed to fill it go with them. What stays is the part that was never about Red Hat:config,counts,instance_info,job_countsandjob_instance_countsstill feed/api/v2/metrics, andconfig()keeps everything except the twenty licence fields it carried.Nine settings went with it,
INSIGHTS_TRACKING_STATEand the fourAUTOMATION_ANALYTICS_*among them. Conf migration0014deletes the rows for all ten removed keys, so a Red Hat username and password do not sit in an upgraded database with no screen left to clear them from.What does not move
The API shape.
get_licenser().validate()still returnslicense_type: openwith a valid key, which is what the config view reads and what the UI reads out oflicense_info.Follow-ups in the other repositories
Both call routes this removes: ascender-collection#309 drops the
licenseandsubscriptionsmodules and thelicenserole, and ascender-kit#74 drops the config properties reading fields the endpoint no longer returns.Checked
The Python suite passes whole: 4,192 passing, 6 skipped, across functional, unit, api, conf and sso.
ruff check,ruff format --checkandyamllintclean, andmakemigrations --checkreports no missing migrations. Migration0014was run against a database with all ten keys planted: it deletes exactly those ten and leaves the rest.UI eslint, prettier,
tscand the catalogue check are clean, and both UI suites pass: 181 general files and 368 screen files.api.generated.tsand the nine message catalogues are regenerated, the latter dropping 62 strings that belonged to the removed screens.