mirror of
https://github.com/ansible/awx.git
synced 2026-05-10 02:47:36 -02:30
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>
This commit is contained in:
@@ -10,7 +10,7 @@ from awx.api.generics import APIView, Response
|
|||||||
from awx.api.permissions import AnalyticsPermission
|
from awx.api.permissions import AnalyticsPermission
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
from awx.main.utils import get_awx_version
|
from awx.main.utils import get_awx_version
|
||||||
from awx.main.utils.analytics_proxy import OIDCClient, DEFAULT_OIDC_TOKEN_ENDPOINT
|
from awx.main.utils.analytics_proxy import OIDCClient
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@@ -205,7 +205,7 @@ class AnalyticsGenericView(APIView):
|
|||||||
try:
|
try:
|
||||||
rh_user = self._get_setting('REDHAT_USERNAME', None, ERROR_MISSING_USER)
|
rh_user = self._get_setting('REDHAT_USERNAME', None, ERROR_MISSING_USER)
|
||||||
rh_password = self._get_setting('REDHAT_PASSWORD', None, ERROR_MISSING_PASSWORD)
|
rh_password = self._get_setting('REDHAT_PASSWORD', None, ERROR_MISSING_PASSWORD)
|
||||||
client = OIDCClient(rh_user, rh_password, DEFAULT_OIDC_TOKEN_ENDPOINT, ['api.console'])
|
client = OIDCClient(rh_user, rh_password)
|
||||||
response = client.make_request(
|
response = client.make_request(
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
@@ -219,8 +219,8 @@ class AnalyticsGenericView(APIView):
|
|||||||
logger.error("Automation Analytics API request failed, trying base auth method")
|
logger.error("Automation Analytics API request failed, trying base auth method")
|
||||||
response = self._base_auth_request(request, method, url, rh_user, rh_password, headers)
|
response = self._base_auth_request(request, method, url, rh_user, rh_password, headers)
|
||||||
except MissingSettings:
|
except MissingSettings:
|
||||||
rh_user = self._get_setting('SUBSCRIPTIONS_USERNAME', None, ERROR_MISSING_USER)
|
rh_user = self._get_setting('SUBSCRIPTIONS_CLIENT_ID', None, ERROR_MISSING_USER)
|
||||||
rh_password = self._get_setting('SUBSCRIPTIONS_PASSWORD', None, ERROR_MISSING_PASSWORD)
|
rh_password = self._get_setting('SUBSCRIPTIONS_CLIENT_SECRET', None, ERROR_MISSING_PASSWORD)
|
||||||
response = self._base_auth_request(request, method, url, rh_user, rh_password, headers)
|
response = self._base_auth_request(request, method, url, rh_user, rh_password, headers)
|
||||||
#
|
#
|
||||||
# Missing or wrong user/pass
|
# Missing or wrong user/pass
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from awx.api.versioning import URLPathVersioning, reverse, drf_reverse
|
|||||||
from awx.main.constants import PRIVILEGE_ESCALATION_METHODS
|
from awx.main.constants import PRIVILEGE_ESCALATION_METHODS
|
||||||
from awx.main.models import Project, Organization, Instance, InstanceGroup, JobTemplate
|
from awx.main.models import Project, Organization, Instance, InstanceGroup, JobTemplate
|
||||||
from awx.main.utils import set_environ
|
from awx.main.utils import set_environ
|
||||||
|
from awx.main.utils.analytics_proxy import TokenError
|
||||||
from awx.main.utils.licensing import get_licenser
|
from awx.main.utils.licensing import get_licenser
|
||||||
|
|
||||||
logger = logging.getLogger('awx.api.views.root')
|
logger = logging.getLogger('awx.api.views.root')
|
||||||
@@ -176,19 +177,21 @@ class ApiV2SubscriptionView(APIView):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = request.data.copy()
|
data = request.data.copy()
|
||||||
if data.get('subscriptions_password') == '$encrypted$':
|
if data.get('subscriptions_client_secret') == '$encrypted$':
|
||||||
data['subscriptions_password'] = settings.SUBSCRIPTIONS_PASSWORD
|
data['subscriptions_client_secret'] = settings.SUBSCRIPTIONS_CLIENT_SECRET
|
||||||
try:
|
try:
|
||||||
user, pw = data.get('subscriptions_username'), data.get('subscriptions_password')
|
user, pw = data.get('subscriptions_client_id'), data.get('subscriptions_client_secret')
|
||||||
with set_environ(**settings.AWX_TASK_ENV):
|
with set_environ(**settings.AWX_TASK_ENV):
|
||||||
validated = get_licenser().validate_rh(user, pw)
|
validated = get_licenser().validate_rh(user, pw)
|
||||||
if user:
|
if user:
|
||||||
settings.SUBSCRIPTIONS_USERNAME = data['subscriptions_username']
|
settings.SUBSCRIPTIONS_CLIENT_ID = data['subscriptions_client_id']
|
||||||
if pw:
|
if pw:
|
||||||
settings.SUBSCRIPTIONS_PASSWORD = data['subscriptions_password']
|
settings.SUBSCRIPTIONS_CLIENT_SECRET = data['subscriptions_client_secret']
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
msg = _("Invalid Subscription")
|
msg = _("Invalid Subscription")
|
||||||
if isinstance(exc, requests.exceptions.HTTPError) and getattr(getattr(exc, 'response', None), 'status_code', None) == 401:
|
if isinstance(exc, TokenError) or (
|
||||||
|
isinstance(exc, requests.exceptions.HTTPError) and getattr(getattr(exc, 'response', None), 'status_code', None) == 401
|
||||||
|
):
|
||||||
msg = _("The provided credentials are invalid (HTTP 401).")
|
msg = _("The provided credentials are invalid (HTTP 401).")
|
||||||
elif isinstance(exc, requests.exceptions.ProxyError):
|
elif isinstance(exc, requests.exceptions.ProxyError):
|
||||||
msg = _("Unable to connect to proxy server.")
|
msg = _("Unable to connect to proxy server.")
|
||||||
@@ -215,12 +218,12 @@ class ApiV2AttachView(APIView):
|
|||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
data = request.data.copy()
|
data = request.data.copy()
|
||||||
pool_id = data.get('pool_id', None)
|
subscription_id = data.get('subscription_id', None)
|
||||||
if not pool_id:
|
if not subscription_id:
|
||||||
return Response({"error": _("No subscription pool ID provided.")}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": _("No subscription ID provided.")}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
user = getattr(settings, 'SUBSCRIPTIONS_USERNAME', None)
|
user = getattr(settings, 'SUBSCRIPTIONS_CLIENT_ID', None)
|
||||||
pw = getattr(settings, 'SUBSCRIPTIONS_PASSWORD', None)
|
pw = getattr(settings, 'SUBSCRIPTIONS_CLIENT_SECRET', None)
|
||||||
if pool_id and user and pw:
|
if subscription_id and user and pw:
|
||||||
data = request.data.copy()
|
data = request.data.copy()
|
||||||
try:
|
try:
|
||||||
with set_environ(**settings.AWX_TASK_ENV):
|
with set_environ(**settings.AWX_TASK_ENV):
|
||||||
@@ -239,7 +242,7 @@ class ApiV2AttachView(APIView):
|
|||||||
logger.exception(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
logger.exception(smart_str(u"Invalid subscription submitted."), extra=dict(actor=request.user.username))
|
||||||
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
for sub in validated:
|
for sub in validated:
|
||||||
if sub['pool_id'] == pool_id:
|
if sub['subscription_id'] == subscription_id:
|
||||||
sub['valid_key'] = True
|
sub['valid_key'] = True
|
||||||
settings.LICENSE = sub
|
settings.LICENSE = sub
|
||||||
return Response(sub)
|
return Response(sub)
|
||||||
|
|||||||
@@ -27,5 +27,5 @@ def _migrate_setting(apps, old_key, new_key, encrypted=False):
|
|||||||
|
|
||||||
|
|
||||||
def prefill_rh_credentials(apps, schema_editor):
|
def prefill_rh_credentials(apps, schema_editor):
|
||||||
_migrate_setting(apps, 'REDHAT_USERNAME', 'SUBSCRIPTIONS_USERNAME', encrypted=False)
|
_migrate_setting(apps, 'REDHAT_USERNAME', 'SUBSCRIPTIONS_CLIENT_ID', encrypted=False)
|
||||||
_migrate_setting(apps, 'REDHAT_PASSWORD', 'SUBSCRIPTIONS_PASSWORD', encrypted=True)
|
_migrate_setting(apps, 'REDHAT_PASSWORD', 'SUBSCRIPTIONS_CLIENT_SECRET', encrypted=True)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from ansible_base.lib.utils.db import advisory_lock
|
|||||||
from awx.main.models import Job
|
from awx.main.models import Job
|
||||||
from awx.main.access import access_registry
|
from awx.main.access import access_registry
|
||||||
from awx.main.utils import get_awx_http_client_headers, set_environ, datetime_hook
|
from awx.main.utils import get_awx_http_client_headers, set_environ, datetime_hook
|
||||||
from awx.main.utils.analytics_proxy import OIDCClient, DEFAULT_OIDC_TOKEN_ENDPOINT
|
from awx.main.utils.analytics_proxy import OIDCClient
|
||||||
|
|
||||||
__all__ = ['register', 'gather', 'ship']
|
__all__ = ['register', 'gather', 'ship']
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ def gather(dest=None, module=None, subset=None, since=None, until=None, collecti
|
|||||||
|
|
||||||
if not (
|
if not (
|
||||||
settings.AUTOMATION_ANALYTICS_URL
|
settings.AUTOMATION_ANALYTICS_URL
|
||||||
and ((settings.REDHAT_USERNAME and settings.REDHAT_PASSWORD) or (settings.SUBSCRIPTIONS_USERNAME and settings.SUBSCRIPTIONS_PASSWORD))
|
and ((settings.REDHAT_USERNAME and settings.REDHAT_PASSWORD) or (settings.SUBSCRIPTIONS_CLIENT_ID and settings.SUBSCRIPTIONS_CLIENT_SECRET))
|
||||||
):
|
):
|
||||||
logger.log(log_level, "Not gathering analytics, configuration is invalid. Use --dry-run to gather locally without sending.")
|
logger.log(log_level, "Not gathering analytics, configuration is invalid. Use --dry-run to gather locally without sending.")
|
||||||
return None
|
return None
|
||||||
@@ -368,8 +368,20 @@ def ship(path):
|
|||||||
logger.error('AUTOMATION_ANALYTICS_URL is not set')
|
logger.error('AUTOMATION_ANALYTICS_URL is not set')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
rh_user = getattr(settings, 'REDHAT_USERNAME', None)
|
rh_id = getattr(settings, 'REDHAT_USERNAME', None)
|
||||||
rh_password = getattr(settings, 'REDHAT_PASSWORD', None)
|
rh_secret = getattr(settings, 'REDHAT_PASSWORD', None)
|
||||||
|
|
||||||
|
if not (rh_id and rh_secret):
|
||||||
|
rh_id = getattr(settings, 'SUBSCRIPTIONS_CLIENT_ID', None)
|
||||||
|
rh_secret = getattr(settings, 'SUBSCRIPTIONS_CLIENT_SECRET', None)
|
||||||
|
|
||||||
|
if not rh_id:
|
||||||
|
logger.error('Neither REDHAT_USERNAME nor SUBSCRIPTIONS_CLIENT_ID are set')
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not rh_secret:
|
||||||
|
logger.error('Neither REDHAT_PASSWORD nor SUBSCRIPTIONS_CLIENT_SECRET are set')
|
||||||
|
return False
|
||||||
|
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
files = {'file': (os.path.basename(path), f, settings.INSIGHTS_AGENT_MIME)}
|
files = {'file': (os.path.basename(path), f, settings.INSIGHTS_AGENT_MIME)}
|
||||||
@@ -377,25 +389,13 @@ def ship(path):
|
|||||||
s.headers = get_awx_http_client_headers()
|
s.headers = get_awx_http_client_headers()
|
||||||
s.headers.pop('Content-Type')
|
s.headers.pop('Content-Type')
|
||||||
with set_environ(**settings.AWX_TASK_ENV):
|
with set_environ(**settings.AWX_TASK_ENV):
|
||||||
if rh_user and rh_password:
|
try:
|
||||||
try:
|
client = OIDCClient(rh_id, rh_secret)
|
||||||
client = OIDCClient(rh_user, rh_password, DEFAULT_OIDC_TOKEN_ENDPOINT, ['api.console'])
|
response = client.make_request("POST", url, headers=s.headers, files=files, verify=settings.INSIGHTS_CERT_PATH, timeout=(31, 31))
|
||||||
response = client.make_request("POST", url, headers=s.headers, files=files, verify=settings.INSIGHTS_CERT_PATH, timeout=(31, 31))
|
except requests.RequestException:
|
||||||
except requests.RequestException:
|
logger.error("Automation Analytics API request failed, trying base auth method")
|
||||||
logger.error("Automation Analytics API request failed, trying base auth method")
|
response = s.post(url, files=files, verify=settings.INSIGHTS_CERT_PATH, auth=(rh_id, rh_secret), headers=s.headers, timeout=(31, 31))
|
||||||
response = s.post(url, files=files, verify=settings.INSIGHTS_CERT_PATH, auth=(rh_user, rh_password), headers=s.headers, timeout=(31, 31))
|
|
||||||
elif not rh_user or not rh_password:
|
|
||||||
logger.info('REDHAT_USERNAME and REDHAT_PASSWORD are not set, using SUBSCRIPTIONS_USERNAME and SUBSCRIPTIONS_PASSWORD')
|
|
||||||
rh_user = getattr(settings, 'SUBSCRIPTIONS_USERNAME', None)
|
|
||||||
rh_password = getattr(settings, 'SUBSCRIPTIONS_PASSWORD', None)
|
|
||||||
if rh_user and rh_password:
|
|
||||||
response = s.post(url, files=files, verify=settings.INSIGHTS_CERT_PATH, auth=(rh_user, rh_password), headers=s.headers, timeout=(31, 31))
|
|
||||||
elif not rh_user:
|
|
||||||
logger.error('REDHAT_USERNAME and SUBSCRIPTIONS_USERNAME are not set')
|
|
||||||
return False
|
|
||||||
elif not rh_password:
|
|
||||||
logger.error('REDHAT_PASSWORD and SUBSCRIPTIONS_USERNAME are not set')
|
|
||||||
return False
|
|
||||||
# Accept 2XX status_codes
|
# Accept 2XX status_codes
|
||||||
if response.status_code >= 300:
|
if response.status_code >= 300:
|
||||||
logger.error('Upload failed with status {}, {}'.format(response.status_code, response.text))
|
logger.error('Upload failed with status {}, {}'.format(response.status_code, response.text))
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ register(
|
|||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
encrypted=False,
|
encrypted=False,
|
||||||
read_only=False,
|
read_only=False,
|
||||||
label=_('Red Hat customer username'),
|
label=_('Red Hat Client ID for Analytics'),
|
||||||
help_text=_('This username is used to send data to Automation Analytics'),
|
help_text=_('Client ID used to send data to Automation Analytics'),
|
||||||
category=_('System'),
|
category=_('System'),
|
||||||
category_slug='system',
|
category_slug='system',
|
||||||
)
|
)
|
||||||
@@ -137,34 +137,34 @@ register(
|
|||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
encrypted=True,
|
encrypted=True,
|
||||||
read_only=False,
|
read_only=False,
|
||||||
label=_('Red Hat customer password'),
|
label=_('Red Hat Client Secret for Analytics'),
|
||||||
help_text=_('This password is used to send data to Automation Analytics'),
|
help_text=_('Client secret used to send data to Automation Analytics'),
|
||||||
category=_('System'),
|
category=_('System'),
|
||||||
category_slug='system',
|
category_slug='system',
|
||||||
)
|
)
|
||||||
|
|
||||||
register(
|
register(
|
||||||
'SUBSCRIPTIONS_USERNAME',
|
'SUBSCRIPTIONS_CLIENT_ID',
|
||||||
field_class=fields.CharField,
|
field_class=fields.CharField,
|
||||||
default='',
|
default='',
|
||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
encrypted=False,
|
encrypted=False,
|
||||||
read_only=False,
|
read_only=False,
|
||||||
label=_('Red Hat or Satellite username'),
|
label=_('Red Hat Client ID for Subscriptions'),
|
||||||
help_text=_('This username is used to retrieve subscription and content information'), # noqa
|
help_text=_('Client ID used to retrieve subscription and content information'), # noqa
|
||||||
category=_('System'),
|
category=_('System'),
|
||||||
category_slug='system',
|
category_slug='system',
|
||||||
)
|
)
|
||||||
|
|
||||||
register(
|
register(
|
||||||
'SUBSCRIPTIONS_PASSWORD',
|
'SUBSCRIPTIONS_CLIENT_SECRET',
|
||||||
field_class=fields.CharField,
|
field_class=fields.CharField,
|
||||||
default='',
|
default='',
|
||||||
allow_blank=True,
|
allow_blank=True,
|
||||||
encrypted=True,
|
encrypted=True,
|
||||||
read_only=False,
|
read_only=False,
|
||||||
label=_('Red Hat or Satellite password'),
|
label=_('Red Hat Client Secret for Subscriptions'),
|
||||||
help_text=_('This password is used to retrieve subscription and content information'), # noqa
|
help_text=_('Client secret used to retrieve subscription and content information'), # noqa
|
||||||
category=_('System'),
|
category=_('System'),
|
||||||
category_slug='system',
|
category_slug='system',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ def mock_analytic_post():
|
|||||||
{
|
{
|
||||||
'REDHAT_USERNAME': 'redhat_user',
|
'REDHAT_USERNAME': 'redhat_user',
|
||||||
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
||||||
'SUBSCRIPTIONS_USERNAME': '',
|
'SUBSCRIPTIONS_CLIENT_ID': '',
|
||||||
'SUBSCRIPTIONS_PASSWORD': '',
|
'SUBSCRIPTIONS_CLIENT_SECRET': '',
|
||||||
},
|
},
|
||||||
True,
|
True,
|
||||||
('redhat_user', 'redhat_pass'),
|
('redhat_user', 'redhat_pass'),
|
||||||
@@ -98,8 +98,8 @@ def mock_analytic_post():
|
|||||||
{
|
{
|
||||||
'REDHAT_USERNAME': None,
|
'REDHAT_USERNAME': None,
|
||||||
'REDHAT_PASSWORD': None,
|
'REDHAT_PASSWORD': None,
|
||||||
'SUBSCRIPTIONS_USERNAME': 'subs_user',
|
'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
|
||||||
'SUBSCRIPTIONS_PASSWORD': 'subs_pass', # NOSONAR
|
'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
|
||||||
},
|
},
|
||||||
True,
|
True,
|
||||||
('subs_user', 'subs_pass'),
|
('subs_user', 'subs_pass'),
|
||||||
@@ -109,8 +109,8 @@ def mock_analytic_post():
|
|||||||
{
|
{
|
||||||
'REDHAT_USERNAME': '',
|
'REDHAT_USERNAME': '',
|
||||||
'REDHAT_PASSWORD': '',
|
'REDHAT_PASSWORD': '',
|
||||||
'SUBSCRIPTIONS_USERNAME': 'subs_user',
|
'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
|
||||||
'SUBSCRIPTIONS_PASSWORD': 'subs_pass', # NOSONAR
|
'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
|
||||||
},
|
},
|
||||||
True,
|
True,
|
||||||
('subs_user', 'subs_pass'),
|
('subs_user', 'subs_pass'),
|
||||||
@@ -120,8 +120,8 @@ def mock_analytic_post():
|
|||||||
{
|
{
|
||||||
'REDHAT_USERNAME': '',
|
'REDHAT_USERNAME': '',
|
||||||
'REDHAT_PASSWORD': '',
|
'REDHAT_PASSWORD': '',
|
||||||
'SUBSCRIPTIONS_USERNAME': '',
|
'SUBSCRIPTIONS_CLIENT_ID': '',
|
||||||
'SUBSCRIPTIONS_PASSWORD': '',
|
'SUBSCRIPTIONS_CLIENT_SECRET': '',
|
||||||
},
|
},
|
||||||
False,
|
False,
|
||||||
None, # No request should be made
|
None, # No request should be made
|
||||||
@@ -131,8 +131,8 @@ def mock_analytic_post():
|
|||||||
{
|
{
|
||||||
'REDHAT_USERNAME': '',
|
'REDHAT_USERNAME': '',
|
||||||
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
||||||
'SUBSCRIPTIONS_USERNAME': 'subs_user',
|
'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
|
||||||
'SUBSCRIPTIONS_PASSWORD': '',
|
'SUBSCRIPTIONS_CLIENT_SECRET': '',
|
||||||
},
|
},
|
||||||
False,
|
False,
|
||||||
None, # Invalid, no request should be made
|
None, # Invalid, no request should be made
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ class TestAnalyticsGenericView:
|
|||||||
'INSIGHTS_TRACKING_STATE': True,
|
'INSIGHTS_TRACKING_STATE': True,
|
||||||
'REDHAT_USERNAME': 'redhat_user',
|
'REDHAT_USERNAME': 'redhat_user',
|
||||||
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
||||||
'SUBSCRIPTIONS_USERNAME': '',
|
'SUBSCRIPTIONS_CLIENT_ID': '',
|
||||||
'SUBSCRIPTIONS_PASSWORD': '',
|
'SUBSCRIPTIONS_CLIENT_SECRET': '',
|
||||||
},
|
},
|
||||||
('redhat_user', 'redhat_pass'),
|
('redhat_user', 'redhat_pass'),
|
||||||
None,
|
None,
|
||||||
@@ -109,8 +109,8 @@ class TestAnalyticsGenericView:
|
|||||||
'INSIGHTS_TRACKING_STATE': True,
|
'INSIGHTS_TRACKING_STATE': True,
|
||||||
'REDHAT_USERNAME': '',
|
'REDHAT_USERNAME': '',
|
||||||
'REDHAT_PASSWORD': '',
|
'REDHAT_PASSWORD': '',
|
||||||
'SUBSCRIPTIONS_USERNAME': 'subs_user',
|
'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
|
||||||
'SUBSCRIPTIONS_PASSWORD': 'subs_pass', # NOSONAR
|
'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
|
||||||
},
|
},
|
||||||
('subs_user', 'subs_pass'),
|
('subs_user', 'subs_pass'),
|
||||||
None,
|
None,
|
||||||
@@ -121,8 +121,8 @@ class TestAnalyticsGenericView:
|
|||||||
'INSIGHTS_TRACKING_STATE': True,
|
'INSIGHTS_TRACKING_STATE': True,
|
||||||
'REDHAT_USERNAME': '',
|
'REDHAT_USERNAME': '',
|
||||||
'REDHAT_PASSWORD': '',
|
'REDHAT_PASSWORD': '',
|
||||||
'SUBSCRIPTIONS_USERNAME': '',
|
'SUBSCRIPTIONS_CLIENT_ID': '',
|
||||||
'SUBSCRIPTIONS_PASSWORD': '',
|
'SUBSCRIPTIONS_CLIENT_SECRET': '',
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
ERROR_MISSING_USER,
|
ERROR_MISSING_USER,
|
||||||
@@ -133,8 +133,8 @@ class TestAnalyticsGenericView:
|
|||||||
'INSIGHTS_TRACKING_STATE': True,
|
'INSIGHTS_TRACKING_STATE': True,
|
||||||
'REDHAT_USERNAME': 'redhat_user',
|
'REDHAT_USERNAME': 'redhat_user',
|
||||||
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
'REDHAT_PASSWORD': 'redhat_pass', # NOSONAR
|
||||||
'SUBSCRIPTIONS_USERNAME': 'subs_user',
|
'SUBSCRIPTIONS_CLIENT_ID': 'subs_user',
|
||||||
'SUBSCRIPTIONS_PASSWORD': 'subs_pass', # NOSONAR
|
'SUBSCRIPTIONS_CLIENT_SECRET': 'subs_pass', # NOSONAR
|
||||||
},
|
},
|
||||||
('redhat_user', 'redhat_pass'),
|
('redhat_user', 'redhat_pass'),
|
||||||
None,
|
None,
|
||||||
@@ -145,8 +145,8 @@ class TestAnalyticsGenericView:
|
|||||||
'INSIGHTS_TRACKING_STATE': True,
|
'INSIGHTS_TRACKING_STATE': True,
|
||||||
'REDHAT_USERNAME': '',
|
'REDHAT_USERNAME': '',
|
||||||
'REDHAT_PASSWORD': '',
|
'REDHAT_PASSWORD': '',
|
||||||
'SUBSCRIPTIONS_USERNAME': 'subs_user', # NOSONAR
|
'SUBSCRIPTIONS_CLIENT_ID': 'subs_user', # NOSONAR
|
||||||
'SUBSCRIPTIONS_PASSWORD': '',
|
'SUBSCRIPTIONS_CLIENT_SECRET': '',
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
ERROR_MISSING_PASSWORD,
|
ERROR_MISSING_PASSWORD,
|
||||||
|
|||||||
37
awx/main/tests/unit/utils/test_licensing.py
Normal file
37
awx/main/tests/unit/utils/test_licensing.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import json
|
||||||
|
from http import HTTPStatus
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from requests import Response
|
||||||
|
|
||||||
|
from awx.main.utils.licensing import Licenser
|
||||||
|
|
||||||
|
|
||||||
|
def test_rhsm_licensing():
|
||||||
|
def mocked_requests_get(*args, **kwargs):
|
||||||
|
assert kwargs['verify'] == True
|
||||||
|
response = Response()
|
||||||
|
subs = json.dumps({'body': []})
|
||||||
|
response.status_code = HTTPStatus.OK
|
||||||
|
response._content = bytes(subs, 'utf-8')
|
||||||
|
return response
|
||||||
|
|
||||||
|
licenser = Licenser()
|
||||||
|
with patch('awx.main.utils.analytics_proxy.OIDCClient.make_request', new=mocked_requests_get):
|
||||||
|
subs = licenser.get_rhsm_subs('localhost', 'admin', 'admin')
|
||||||
|
assert subs == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_satellite_licensing():
|
||||||
|
def mocked_requests_get(*args, **kwargs):
|
||||||
|
assert kwargs['verify'] == True
|
||||||
|
response = Response()
|
||||||
|
subs = json.dumps({'results': []})
|
||||||
|
response.status_code = HTTPStatus.OK
|
||||||
|
response._content = bytes(subs, 'utf-8')
|
||||||
|
return response
|
||||||
|
|
||||||
|
licenser = Licenser()
|
||||||
|
with patch('requests.get', new=mocked_requests_get):
|
||||||
|
subs = licenser.get_satellite_subs('localhost', 'admin', 'admin')
|
||||||
|
assert subs == []
|
||||||
@@ -23,7 +23,7 @@ class TokenError(requests.RequestException):
|
|||||||
try:
|
try:
|
||||||
client = OIDCClient(...)
|
client = OIDCClient(...)
|
||||||
client.make_request(...)
|
client.make_request(...)
|
||||||
except TokenGenerationError as e:
|
except TokenError as e:
|
||||||
print(f"Token generation failed due to {e.__cause__}")
|
print(f"Token generation failed due to {e.__cause__}")
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
print("API request failed)
|
print("API request failed)
|
||||||
@@ -102,13 +102,15 @@ class OIDCClient:
|
|||||||
self,
|
self,
|
||||||
client_id: str,
|
client_id: str,
|
||||||
client_secret: str,
|
client_secret: str,
|
||||||
token_url: str,
|
token_url: str = DEFAULT_OIDC_TOKEN_ENDPOINT,
|
||||||
scopes: list[str],
|
scopes: list[str] = None,
|
||||||
base_url: str = '',
|
base_url: str = '',
|
||||||
) -> None:
|
) -> None:
|
||||||
self.client_id: str = client_id
|
self.client_id: str = client_id
|
||||||
self.client_secret: str = client_secret
|
self.client_secret: str = client_secret
|
||||||
self.token_url: str = token_url
|
self.token_url: str = token_url
|
||||||
|
if scopes is None:
|
||||||
|
scopes = ['api.console']
|
||||||
self.scopes = scopes
|
self.scopes = scopes
|
||||||
self.base_url: str = base_url
|
self.base_url: str = base_url
|
||||||
self.token: Optional[Token] = None
|
self.token: Optional[Token] = None
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from awx_plugins.interfaces._temporary_private_licensing_api import detect_server_product_name
|
from awx_plugins.interfaces._temporary_private_licensing_api import detect_server_product_name
|
||||||
|
|
||||||
from awx.main.constants import SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS
|
from awx.main.constants import SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS
|
||||||
|
from awx.main.utils.analytics_proxy import OIDCClient
|
||||||
|
|
||||||
MAX_INSTANCES = 9999999
|
MAX_INSTANCES = 9999999
|
||||||
|
|
||||||
@@ -228,37 +229,38 @@ class Licenser(object):
|
|||||||
host = getattr(settings, 'REDHAT_CANDLEPIN_HOST', None)
|
host = getattr(settings, 'REDHAT_CANDLEPIN_HOST', None)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
raise ValueError('subscriptions_username is required')
|
raise ValueError('subscriptions_client_id is required')
|
||||||
|
|
||||||
if not pw:
|
if not pw:
|
||||||
raise ValueError('subscriptions_password is required')
|
raise ValueError('subscriptions_client_secret is required')
|
||||||
|
|
||||||
if host and user and pw:
|
if host and user and pw:
|
||||||
if 'subscription.rhsm.redhat.com' in host:
|
if 'subscription.rhsm.redhat.com' in host:
|
||||||
json = self.get_rhsm_subs(host, user, pw)
|
json = self.get_rhsm_subs(settings.SUBSCRIPTIONS_RHSM_URL, user, pw)
|
||||||
else:
|
else:
|
||||||
json = self.get_satellite_subs(host, user, pw)
|
json = self.get_satellite_subs(host, user, pw)
|
||||||
return self.generate_license_options_from_entitlements(json)
|
return self.generate_license_options_from_entitlements(json)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_rhsm_subs(self, host, user, pw):
|
def get_rhsm_subs(self, host, client_id, client_secret):
|
||||||
verify = getattr(settings, 'REDHAT_CANDLEPIN_VERIFY', True)
|
client = OIDCClient(client_id, client_secret)
|
||||||
json = []
|
subs = client.make_request(
|
||||||
try:
|
'GET',
|
||||||
subs = requests.get('/'.join([host, 'subscription/users/{}/owners'.format(user)]), verify=verify, auth=(user, pw))
|
host,
|
||||||
except requests.exceptions.ConnectionError as error:
|
verify=True,
|
||||||
raise error
|
timeout=(31, 31),
|
||||||
except OSError as error:
|
)
|
||||||
raise OSError(
|
|
||||||
'Unable to open certificate bundle {}. Check that the service is running on Red Hat Enterprise Linux.'.format(verify)
|
|
||||||
) from error # noqa
|
|
||||||
subs.raise_for_status()
|
|
||||||
|
|
||||||
for sub in subs.json():
|
subs.raise_for_status()
|
||||||
resp = requests.get('/'.join([host, 'subscription/owners/{}/pools/?match=*tower*'.format(sub['key'])]), verify=verify, auth=(user, pw))
|
subs_formatted = []
|
||||||
resp.raise_for_status()
|
for sku in subs.json()['body']:
|
||||||
json.extend(resp.json())
|
sku_data = {k: v for k, v in sku.items() if k != 'subscriptions'}
|
||||||
return json
|
for sub in sku['subscriptions']:
|
||||||
|
sub_data = sku_data.copy()
|
||||||
|
sub_data['subscriptions'] = sub
|
||||||
|
subs_formatted.append(sub_data)
|
||||||
|
|
||||||
|
return subs_formatted
|
||||||
|
|
||||||
def get_satellite_subs(self, host, user, pw):
|
def get_satellite_subs(self, host, user, pw):
|
||||||
port = None
|
port = None
|
||||||
@@ -267,7 +269,7 @@ class Licenser(object):
|
|||||||
port = str(self.config.get("server", "port"))
|
port = str(self.config.get("server", "port"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception('Unable to read rhsm config to get ca_cert location. {}'.format(str(e)))
|
logger.exception('Unable to read rhsm config to get ca_cert location. {}'.format(str(e)))
|
||||||
verify = getattr(settings, 'REDHAT_CANDLEPIN_VERIFY', True)
|
verify = True
|
||||||
if port:
|
if port:
|
||||||
host = ':'.join([host, port])
|
host = ':'.join([host, port])
|
||||||
json = []
|
json = []
|
||||||
@@ -314,20 +316,11 @@ class Licenser(object):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_appropriate_sub(self, sub):
|
|
||||||
if sub['activeSubscription'] is False:
|
|
||||||
return False
|
|
||||||
# Products that contain Ansible Tower
|
|
||||||
products = sub.get('providedProducts', [])
|
|
||||||
if any(product.get('productId') == '480' for product in products):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def generate_license_options_from_entitlements(self, json):
|
def generate_license_options_from_entitlements(self, json):
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
|
|
||||||
ValidSub = collections.namedtuple(
|
ValidSub = collections.namedtuple(
|
||||||
'ValidSub', 'sku name support_level end_date trial developer_license quantity pool_id satellite subscription_id account_number usage'
|
'ValidSub', 'sku name support_level end_date trial developer_license quantity satellite subscription_id account_number usage'
|
||||||
)
|
)
|
||||||
valid_subs = []
|
valid_subs = []
|
||||||
for sub in json:
|
for sub in json:
|
||||||
@@ -335,10 +328,14 @@ class Licenser(object):
|
|||||||
if satellite:
|
if satellite:
|
||||||
is_valid = self.is_appropriate_sat_sub(sub)
|
is_valid = self.is_appropriate_sat_sub(sub)
|
||||||
else:
|
else:
|
||||||
is_valid = self.is_appropriate_sub(sub)
|
# the list of subs from console.redhat.com are already valid based on the query params we provided
|
||||||
|
is_valid = True
|
||||||
if is_valid:
|
if is_valid:
|
||||||
try:
|
try:
|
||||||
end_date = parse(sub.get('endDate'))
|
if satellite:
|
||||||
|
end_date = parse(sub.get('endDate'))
|
||||||
|
else:
|
||||||
|
end_date = parse(sub['subscriptions']['endDate'])
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
@@ -346,44 +343,50 @@ class Licenser(object):
|
|||||||
if end_date < now:
|
if end_date < now:
|
||||||
# If the sub has a past end date, skip it
|
# If the sub has a past end date, skip it
|
||||||
continue
|
continue
|
||||||
try:
|
|
||||||
quantity = int(sub['quantity'])
|
|
||||||
if quantity == -1:
|
|
||||||
# effectively, unlimited
|
|
||||||
quantity = MAX_INSTANCES
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
sku = sub['productId']
|
|
||||||
trial = sku.startswith('S') # i.e.,, SER/SVC
|
|
||||||
developer_license = False
|
developer_license = False
|
||||||
support_level = ''
|
support_level = ''
|
||||||
usage = ''
|
account_number = ''
|
||||||
pool_id = sub['id']
|
usage = sub.get('usage', '')
|
||||||
subscription_id = sub['subscriptionId']
|
|
||||||
account_number = sub['accountNumber']
|
|
||||||
if satellite:
|
if satellite:
|
||||||
|
try:
|
||||||
|
quantity = int(sub['quantity'])
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
sku = sub['productId']
|
||||||
|
subscription_id = sub['subscriptionId']
|
||||||
|
sub_name = sub['productName']
|
||||||
support_level = sub['support_level']
|
support_level = sub['support_level']
|
||||||
usage = sub['usage']
|
account_number = sub['accountNumber']
|
||||||
else:
|
else:
|
||||||
for attr in sub.get('productAttributes', []):
|
try:
|
||||||
if attr.get('name') == 'support_level':
|
if sub['capacity']['name'] == "Nodes":
|
||||||
support_level = attr.get('value')
|
quantity = int(sub['capacity']['quantity']) * int(sub['subscriptions']['quantity'])
|
||||||
elif attr.get('name') == 'usage':
|
else:
|
||||||
usage = attr.get('value')
|
continue
|
||||||
elif attr.get('name') == 'ph_product_name' and attr.get('value') == 'RHEL Developer':
|
except Exception:
|
||||||
developer_license = True
|
continue
|
||||||
|
sku = sub['sku']
|
||||||
|
sub_name = sub['name']
|
||||||
|
support_level = sub['serviceLevel']
|
||||||
|
subscription_id = sub['subscriptions']['number']
|
||||||
|
if sub.get('name') == 'RHEL Developer':
|
||||||
|
developer_license = True
|
||||||
|
|
||||||
|
if quantity == -1:
|
||||||
|
# effectively, unlimited
|
||||||
|
quantity = MAX_INSTANCES
|
||||||
|
trial = sku.startswith('S') # i.e.,, SER/SVC
|
||||||
|
|
||||||
valid_subs.append(
|
valid_subs.append(
|
||||||
ValidSub(
|
ValidSub(
|
||||||
sku,
|
sku,
|
||||||
sub['productName'],
|
sub_name,
|
||||||
support_level,
|
support_level,
|
||||||
end_date,
|
end_date,
|
||||||
trial,
|
trial,
|
||||||
developer_license,
|
developer_license,
|
||||||
quantity,
|
quantity,
|
||||||
pool_id,
|
|
||||||
satellite,
|
satellite,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
account_number,
|
account_number,
|
||||||
@@ -414,7 +417,6 @@ class Licenser(object):
|
|||||||
license._attrs['satellite'] = satellite
|
license._attrs['satellite'] = satellite
|
||||||
license._attrs['valid_key'] = True
|
license._attrs['valid_key'] = True
|
||||||
license.update(license_date=int(sub.end_date.strftime('%s')))
|
license.update(license_date=int(sub.end_date.strftime('%s')))
|
||||||
license.update(pool_id=sub.pool_id)
|
|
||||||
license.update(subscription_id=sub.subscription_id)
|
license.update(subscription_id=sub.subscription_id)
|
||||||
license.update(account_number=sub.account_number)
|
license.update(account_number=sub.account_number)
|
||||||
licenses.append(license._attrs.copy())
|
licenses.append(license._attrs.copy())
|
||||||
|
|||||||
@@ -964,6 +964,9 @@ CLUSTER_HOST_ID = socket.gethostname()
|
|||||||
# - 'unique_managed_hosts': Compliant = automated - deleted hosts (using /api/v2/host_metrics/)
|
# - 'unique_managed_hosts': Compliant = automated - deleted hosts (using /api/v2/host_metrics/)
|
||||||
SUBSCRIPTION_USAGE_MODEL = ''
|
SUBSCRIPTION_USAGE_MODEL = ''
|
||||||
|
|
||||||
|
# Default URL and query params for obtaining valid AAP subscriptions
|
||||||
|
SUBSCRIPTIONS_RHSM_URL = 'https://console.redhat.com/api/rhsm/v2/products?include=providedProducts&oids=480&status=Active'
|
||||||
|
|
||||||
# Host metrics cleanup - last time of the task/command run
|
# Host metrics cleanup - last time of the task/command run
|
||||||
CLEANUP_HOST_METRICS_LAST_TS = None
|
CLEANUP_HOST_METRICS_LAST_TS = None
|
||||||
# Host metrics cleanup - minimal interval between two cleanups in days
|
# Host metrics cleanup - minimal interval between two cleanups in days
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ options:
|
|||||||
unlicensed or trial licensed. When force=true, the license is always applied.
|
unlicensed or trial licensed. When force=true, the license is always applied.
|
||||||
type: bool
|
type: bool
|
||||||
default: 'False'
|
default: 'False'
|
||||||
pool_id:
|
subscription_id:
|
||||||
description:
|
description:
|
||||||
- Red Hat or Red Hat Satellite pool_id to attach to
|
- Red Hat or Red Hat Satellite subscription_id to attach to
|
||||||
required: False
|
required: False
|
||||||
type: str
|
type: str
|
||||||
state:
|
state:
|
||||||
@@ -57,9 +57,9 @@ EXAMPLES = '''
|
|||||||
username: "my_satellite_username"
|
username: "my_satellite_username"
|
||||||
password: "my_satellite_password"
|
password: "my_satellite_password"
|
||||||
|
|
||||||
- name: Attach to a pool (requires fetching subscriptions at least once before)
|
- name: Attach to a subscription (requires fetching subscriptions at least once before)
|
||||||
license:
|
license:
|
||||||
pool_id: 123456
|
subscription_id: 123456
|
||||||
|
|
||||||
- name: Remove license
|
- name: Remove license
|
||||||
license:
|
license:
|
||||||
@@ -75,14 +75,14 @@ def main():
|
|||||||
module = ControllerAPIModule(
|
module = ControllerAPIModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
manifest=dict(type='str', required=False),
|
manifest=dict(type='str', required=False),
|
||||||
pool_id=dict(type='str', required=False),
|
subscription_id=dict(type='str', required=False),
|
||||||
force=dict(type='bool', default=False),
|
force=dict(type='bool', default=False),
|
||||||
state=dict(choices=['present', 'absent'], default='present'),
|
state=dict(choices=['present', 'absent'], default='present'),
|
||||||
),
|
),
|
||||||
required_if=[
|
required_if=[
|
||||||
['state', 'present', ['manifest', 'pool_id'], True],
|
['state', 'present', ['manifest', 'subscription_id'], True],
|
||||||
],
|
],
|
||||||
mutually_exclusive=[("manifest", "pool_id")],
|
mutually_exclusive=[("manifest", "subscription_id")],
|
||||||
)
|
)
|
||||||
|
|
||||||
json_output = {'changed': False}
|
json_output = {'changed': False}
|
||||||
@@ -124,7 +124,7 @@ def main():
|
|||||||
if module.params.get('manifest', None):
|
if module.params.get('manifest', None):
|
||||||
module.post_endpoint('config', data={'manifest': manifest.decode()})
|
module.post_endpoint('config', data={'manifest': manifest.decode()})
|
||||||
else:
|
else:
|
||||||
module.post_endpoint('config/attach', data={'pool_id': module.params.get('pool_id')})
|
module.post_endpoint('config/attach', data={'subscription_id': module.params.get('subscription_id')})
|
||||||
|
|
||||||
module.exit_json(**json_output)
|
module.exit_json(**json_output)
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ description:
|
|||||||
- Get subscriptions available to Automation Platform Controller. See
|
- Get subscriptions available to Automation Platform Controller. See
|
||||||
U(https://www.ansible.com/tower) for an overview.
|
U(https://www.ansible.com/tower) for an overview.
|
||||||
options:
|
options:
|
||||||
username:
|
client_id:
|
||||||
description:
|
description:
|
||||||
- Red Hat or Red Hat Satellite username to get available subscriptions.
|
- Red Hat service account client ID or Red Hat Satellite username to get available subscriptions.
|
||||||
- The credentials you use will be stored for future use in retrieving renewal or expanded subscriptions
|
- The credentials you use will be stored for future use in retrieving renewal or expanded subscriptions
|
||||||
required: True
|
required: True
|
||||||
type: str
|
type: str
|
||||||
password:
|
client_secret:
|
||||||
description:
|
description:
|
||||||
- Red Hat or Red Hat Satellite password to get available subscriptions.
|
- Red Hat service account client secret or Red Hat Satellite password to get available subscriptions.
|
||||||
- The credentials you use will be stored for future use in retrieving renewal or expanded subscriptions
|
- The credentials you use will be stored for future use in retrieving renewal or expanded subscriptions
|
||||||
required: True
|
required: True
|
||||||
type: str
|
type: str
|
||||||
@@ -53,13 +53,13 @@ subscriptions:
|
|||||||
EXAMPLES = '''
|
EXAMPLES = '''
|
||||||
- name: Get subscriptions
|
- name: Get subscriptions
|
||||||
subscriptions:
|
subscriptions:
|
||||||
username: "my_username"
|
client_id: "c6bd7594-d776-46e5-8156-6d17af147479"
|
||||||
password: "My Password"
|
client_secret: "MO9QUvoOZ5fc5JQKXoTch1AsTLI7nFsZ"
|
||||||
|
|
||||||
- name: Get subscriptions with a filter
|
- name: Get subscriptions with a filter
|
||||||
subscriptions:
|
subscriptions:
|
||||||
username: "my_username"
|
client_id: "c6bd7594-d776-46e5-8156-6d17af147479"
|
||||||
password: "My Password"
|
client_secret: "MO9QUvoOZ5fc5JQKXoTch1AsTLI7nFsZ"
|
||||||
filters:
|
filters:
|
||||||
product_name: "Red Hat Ansible Automation Platform"
|
product_name: "Red Hat Ansible Automation Platform"
|
||||||
support_level: "Self-Support"
|
support_level: "Self-Support"
|
||||||
@@ -72,8 +72,8 @@ def main():
|
|||||||
|
|
||||||
module = ControllerAPIModule(
|
module = ControllerAPIModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
username=dict(type='str', required=True),
|
client_id=dict(type='str', required=True),
|
||||||
password=dict(type='str', no_log=True, required=True),
|
client_secret=dict(type='str', no_log=True, required=True),
|
||||||
filters=dict(type='dict', required=False, default={}),
|
filters=dict(type='dict', required=False, default={}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -82,8 +82,8 @@ def main():
|
|||||||
|
|
||||||
# Check if Tower is already licensed
|
# Check if Tower is already licensed
|
||||||
post_data = {
|
post_data = {
|
||||||
'subscriptions_password': module.params.get('password'),
|
'subscriptions_client_secret': module.params.get('client_secret'),
|
||||||
'subscriptions_username': module.params.get('username'),
|
'subscriptions_client_id': module.params.get('client_id'),
|
||||||
}
|
}
|
||||||
all_subscriptions = module.post_endpoint('config/subscriptions', data=post_data)['json']
|
all_subscriptions = module.post_endpoint('config/subscriptions', data=post_data)['json']
|
||||||
json_output['subscriptions'] = []
|
json_output['subscriptions'] = []
|
||||||
|
|||||||
Reference in New Issue
Block a user