feat: drop the subscription licence and the file that gated it - #1005
Merged
Merged
Conversation
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
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.
blaipr
force-pushed
the
feat/drop-subscription
branch
from
September 15, 2026 19:26
52ea7ad to
2a077e8
Compare
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.
…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.
It answered with four constants. valid_key and subscription_name meant nothing, license_type was always open, and product_name was a second brand string. eula was already an empty string nothing read. Both are gone from /api/v2/config/, and nothing replaces them. product_name needed no new home. The brand already has a canonical source, BRAND_NAME in default.strings.json, which is the file a white-labelled install edits and which useBrandName() feeds to the About modal, the login page, the settings list and six other places. Only AppContainer read the licence blob instead, so its masthead labelled the logo Ascender while everything else said Ascender Automation. It uses the hook now, and takes its readiness from version rather than from a licence being present. Every remaining license_type comparison was dead. The two Max Hosts blocks on the organisation screens were wrapped in != 'open', so neither has rendered for as long as this fork has existed; max_hosts stays on the model, in the serializer, and in the form's payload so an edit round-trips it rather than clearing it, and stays enforced on bulk host create. The Pendo field was disabled under an open licence, which the backend already refuses to change. issuePendoIdentity wrote three values into license_info that nothing read, and fed Pendo four fields that only ever existed inside license_info, so they have been sending undefined since AWX. With nothing left calling it, licensing.py is a constant and server_product_name(), which is all the X-API-Product-Name header needed. The browsable API's own page for this endpoint documented license_info, the eula, a POST that installs a licence and a DELETE that removes one. It now describes what the endpoint actually returns.
asgi.py and wsgi.py opened /var/lib/ascender/.tower_version in production and refused to start if it was present and did not match the running version. That is the same file get_licenser used to pick the subscription product, and it is written by nothing in the Ascender repositories, so on a normal install the check was a FileNotFoundError and on an install carried over from Tower it could refuse to boot, citing a setup playbook that does not exist here. Verified by planting a stale file: the application starts now. make version_file wrote /var/lib/ascender/.ascender_version on every dev container start and on make refresh. Nothing in this repository or in any of the other ten reads it, and with the .tower_version readers gone the whole mechanism was write-only, so the target and its two callers go too. The module docstrings said WSGI and ASGI config for AWX project, which the rename missed. The copyright headers stay: they are attribution for inherited code rather than branding.
Four places still named one: - RADIUSBackend and SAMLAuth carried "Custom backend to verify license status". Neither has ever verified a licence; each answers only when its own settings are configured, which is what the docstrings say now. - The AUTHENTICATION_BACKENDS help text, which is shown in Settings, said the list follows "license features". There are none. It follows whether each backend has the settings it requires. - The inventory import logged every PermissionDenied as a "License error". The licence checks that raised it are gone, so the label was wrong for whatever is left. - docs/insights.md described the Insights credential type, the Insights project type and the proxied Insights API views, all removed in ctrliq#931, ctrliq#932 and ctrliq#933. Nothing it documents exists and nothing links to it.
The host filter lookup offered "Insights system ID", keyed on insights_system_id. That field is not on the Host model, not in any serializer and has no column in the database: it went with the Insights removal in ctrliq#931, ctrliq#932 and ctrliq#933, and the filter entry did not. Choosing it sent the API a field it rejects. Broken on main today rather than by this branch, and included here because it is the same Insights debris the rest of this pull request clears.
Four msgids leave with the screens that carried them: Max Hosts and its two help texts from the organisation screens, and Insights system ID from the host filter. No id changed, so no translation is orphaned.
The Insights credential type, the Insights project type and the Insights integration went in ctrliq#931, ctrliq#932 and ctrliq#933. The user guide still described all three as things to select, with screenshots of forms that no longer exist: scm_type choices are '', git, svn and archive, and there is no Insights credential type to pick. Gone with them: the two screenshots nothing referenced any more, and the notification parameter list, which named hg and insights as scm_type choices and omitted archive, so it was wrong three ways rather than one. awx-manage.rst documented gather_analytics --ship, a command this pull request removes. The section keeps its other half, host_metric, which stays, and is retitled for what it now covers.
MiscSystemDetail special-cased AUTOMATION_ANALYTICS_LAST_ENTRIES, rendering it in a CodeDetail because the value was a JSON blob. That setting is unregistered now, so the branch could never be taken and CodeDetail was its only use here.
blaipr
force-pushed
the
feat/drop-subscription
branch
from
September 15, 2026 21:32
4d69b57 to
065dbf1
Compare
The AUTHENTICATION_BACKENDS help text is part of the schema, so changing it drifted api.generated.ts. ui-api-types-drift caught it, which is what that job is for. typed.py still declared LICENSE: dict after the setting was unregistered, so it named a key that resolves nowhere.
cigamit
approved these changes
Sep 15, 2026
blaipr
added a commit
to blaipr/ascender
that referenced
this pull request
Sep 16, 2026
license_capacity and license_consumed were the compliance layer: capacity was the entitlement and consumed the running total against it. ctrliq#1005 already left capacity always zero, since its only source was the licence's instance_count, and consumed measures a headroom that no longer exists. Both columns go, with migration 0217. What stays is the part that was never about a subscription: hosts_added and hosts_deleted, the per-month record of which hosts an install first automated and which it deleted, and HostMetric itself with its automation counts. That removes the running total from the summary task, so _update_summary just records the two monthly figures and _get_license_consumed_before goes with it. The three help texts that named a licence now describe what the columns hold. Verified by rolling the migration back on a database holding license_consumed=42 and license_capacity=99, then forward again: the columns drop and the row keeps its hosts_added and hosts_deleted.
blaipr
added a commit
to blaipr/ascender
that referenced
this pull request
Sep 16, 2026
BulkHostCreateSerializer subtracted existing names belonging to organization 1 whatever organization the request was for, sitting next to an org_active_count that scopes correctly. On any other organization a host that already existed counted as new, so the limit could refuse a request that was inside it. The test covers a non-1 organization and fails without the fix. Raised by Copilot on this pull request, along with two others: the comment above the imports in inventory_import named a DRF error class the file no longer imports, and the organization host limit page described Max Hosts as a control to set, when ctrliq#1005 removed that field from the web interface and left max_hosts writable only through the API.
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.
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. It returns the open licence, always.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 licence
Licenserclass, its certificate parsing and expiry arithmetic:licensing.pyis 51 lines where it was 288.POSTandDELETE /api/v2/config/answer400saying there is no licence, rather than vanishing into a bare405.server_product_name, and the inventory source that choseredhat.satelliteovertheforeman.foreman.The pages, which were already unreachable
The settings page redirects to
/settingswhen the licence type is open, and the usage route is deleted unlessSUBSCRIPTION_USAGE_MODELisunique_managed_hosts, which defaults to empty. That is the point @cigamit made on the forum.Gone: the Subscription settings screen and its manifest wizard, the Subscription Usage screen and its API model, the settings entry, route and breadcrumbs, and the gate that sent every route to the wizard when no licence was valid.
The enforcement
check_licenseandcheck_org_host_limitboth opened by returning early on an open licence, so neither has done anything on a normal install. They are gone, and with them thevalidate_licenseparameter on seventeencan_startandcan_addsignatures.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. An argument meaning "no data" was reading as "no licence check".inventory_importheld its own copies, including aremote_tower_license_compareonly the tests called.license_errorandorg_host_limit_errorstay on the model, since they are API surface, and are simply never set now.The rest were questions 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, and the User-Agent naming the licence type. The last two keep the exact string they produced, so nothing on the wire moves.
The Red Hat analytics upload
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, thegather_analyticstask and the collectors that only filled that payload go with them.config,counts,instance_info,job_countsandjob_instance_countsstill feed/api/v2/metrics.Ten settings are unregistered,
LICENSE,INSIGHTS_TRACKING_STATEand the fourAUTOMATION_ANALYTICS_*among them, and conf migration0014deletes their rows so a Red Hat username and password do not sit in an upgraded database with no screen left to clear them from.The config endpoint
license_infoanswered with four constants:valid_keyandsubscription_namemeant nothing,license_typewas alwaysopen, andproduct_namewas a second brand string.eulawas already an empty string nothing read. Both are gone from/api/v2/config/, and nothing replaces them.product_nameneeded no new home. The brand already has a canonical source,BRAND_NAMEindefault.strings.json, which is the file a white-labelled install edits and whichuseBrandName()feeds to the About modal, the login page, the settings list and six other places. OnlyAppContainerread the licence blob instead, so its masthead labelled the logoAscenderwhile everything else saidAscender Automation. It uses the hook now.With nothing left calling it,
licensing.pyis a constant andserver_product_name(), which is all theX-API-Product-Nameheader ever needed.Licenser,OpenLicense,get_licenserandMAX_INSTANCESare gone.Every remaining
license_typecomparison in the UI was dead, so three go with it:license_type !== 'open', so neither has rendered for as long as this fork has existed, and removing them changes nothing a user can see.max_hostsstays on the model, in the serializer, in the form's payload so an edit round-trips it rather than clearing it, and enforced on bulk host create. Whether it should return as a visible control is a separate question.issuePendoIdentitywrote three values intolicense_infothat nothing read, and fed PendoplanLevel,planPrice,creationDateandtrialfrom top-level fields that only ever existed insidelicense_info, so they have been sendingundefinedsince AWX.AppContainerused!!config.license_infoto decide the config had loaded, which made the app wait on a licence to render. It readsversionnow.Host metrics is deliberately untouched: opening that up for everyone is a decision rather than a removal.
Follow-ups
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
Full Python suite green,
ruff check,ruff format --checkandyamllintclean, no missing migrations. Migration0014run against a database with every removed key planted deletes exactly those and leaves the rest. UI eslint, prettier,tsc, the catalogue check and both UI suites pass.