Seth Foster 12dcc10416
[4.6] Update subscription API to use service accounts (#6927)
* Update subscription API to use service accounts

Update code to pull subscriptions from
console.redhat.com instead of
subscription.rhsm.redhat.com

Uses service account client ID and client secret
instead of username/password, which is being
deprecated in July 2025.

Additional changes:

- In awx.awx.subscriptions module, use new service
account params rather than old basic auth params

- Update awx.awx.license module to use subscription_id
instead of pool_id. This is due to using a different API,
which identifies unique subscriptions by subscriptionID
instead of pool ID.

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
Co-authored-by: Chris Meyers <chris.meyers.fsu@gmail.com>
Co-authored-by: Peter Braun <pbraun@redhat.com>

* fix token name

Signed-off-by: Seth Foster <fosterbseth@gmail.com>

* Fix Subscriptions credentials fallback

Ensure service account authentication is being used
when falling back to using SUBSCRIPTIONS_CLIENT_ID.

Additional change:
Subscription data can return two types of capacities:
Sockets and Nodes

For determining overall capacity
if capacity name is Nodes:
  capacity quantity x subscription quantity
if capacity name is Sockets:
  capacity quantity / 2 (minimum of 1) x subscription quantity

Signed-off-by: Seth Foster <fosterbseth@gmail.com>

---------

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
Co-authored-by: Chris Meyers <chris.meyers.fsu@gmail.com>
Co-authored-by: Peter Braun <pbraun@redhat.com>
2025-05-13 15:41:35 -04:00

134 lines
4.0 KiB
Python

#!/usr/bin/python
# coding: utf-8 -*-
# (c) 2019, John Westcott IV <john.westcott.iv@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'}
DOCUMENTATION = '''
---
module: license
author: "John Westcott IV (@john-westcott-iv)"
short_description: Set the license for Automation Platform Controller
description:
- Get or Set Automation Platform Controller license. See
U(https://www.ansible.com/tower) for an overview.
options:
manifest:
description:
- file path to a Red Hat subscription manifest (a .zip file)
required: False
type: str
force:
description:
- By default, the license manifest will only be applied if Tower is currently
unlicensed or trial licensed. When force=true, the license is always applied.
type: bool
default: 'False'
subscription_id:
description:
- Red Hat or Red Hat Satellite subscription_id to attach to
required: False
type: str
state:
description:
- Desired state of the resource.
default: "present"
choices: ["present", "absent"]
type: str
extends_documentation_fragment: awx.awx.auth
'''
RETURN = ''' # '''
EXAMPLES = '''
- name: Set the license using a file
license:
manifest: "/tmp/my_manifest.zip"
- name: Use the subscriptions module to fetch subscriptions from Red Hat or Red Hat Satellite
subscriptions:
username: "my_satellite_username"
password: "my_satellite_password"
- name: Attach to a subscription (requires fetching subscriptions at least once before)
license:
subscription_id: 123456
- name: Remove license
license:
state: absent
'''
import base64
from ..module_utils.controller_api import ControllerAPIModule
def main():
module = ControllerAPIModule(
argument_spec=dict(
manifest=dict(type='str', required=False),
subscription_id=dict(type='str', required=False),
force=dict(type='bool', default=False),
state=dict(choices=['present', 'absent'], default='present'),
),
required_if=[
['state', 'present', ['manifest', 'subscription_id'], True],
],
mutually_exclusive=[("manifest", "subscription_id")],
)
json_output = {'changed': False}
# If the state was absent we can delete the endpoint and exit.
state = module.params.get('state')
if state == 'absent':
module.delete_endpoint('config')
module.exit_json(**json_output)
if module.params.get('manifest', None):
try:
with open(module.params.get('manifest'), 'rb') as fid:
manifest = base64.b64encode(fid.read())
except OSError as e:
module.fail_json(msg=str(e))
# Check if Tower is already licensed
config = module.get_endpoint('config')['json']
already_licensed = (
'license_info' in config
and 'instance_count' in config['license_info']
and config['license_info']['instance_count'] > 0
and 'trial' in config['license_info']
and not config['license_info']['trial']
)
# Determine if we will install the license
perform_install = bool((not already_licensed) or module.params.get('force'))
# Handle check mode
if module.check_mode:
json_output['changed'] = perform_install
module.exit_json(**json_output)
# Do the actual install, if we need to
if perform_install:
json_output['changed'] = True
if module.params.get('manifest', None):
module.post_endpoint('config', data={'manifest': manifest.decode()})
else:
module.post_endpoint('config/attach', data={'subscription_id': module.params.get('subscription_id')})
module.exit_json(**json_output)
if __name__ == '__main__':
main()