mirror of
https://github.com/ansible/awx.git
synced 2026-03-01 08:48:46 -03:30
Merge pull request #7817 from ryanpetrello/galaxy-credentials
Support Organization-scoped Galaxy installs Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -1103,11 +1103,6 @@ class CredentialTypeAccess(BaseAccess):
|
||||
def can_use(self, obj):
|
||||
return True
|
||||
|
||||
def get_method_capability(self, method, obj, parent_obj):
|
||||
if obj.managed_by_tower:
|
||||
return False
|
||||
return super(CredentialTypeAccess, self).get_method_capability(method, obj, parent_obj)
|
||||
|
||||
def filtered_queryset(self):
|
||||
return self.model.objects.all()
|
||||
|
||||
@@ -1182,6 +1177,8 @@ class CredentialAccess(BaseAccess):
|
||||
def get_user_capabilities(self, obj, **kwargs):
|
||||
user_capabilities = super(CredentialAccess, self).get_user_capabilities(obj, **kwargs)
|
||||
user_capabilities['use'] = self.can_use(obj)
|
||||
if getattr(obj, 'managed_by_tower', False) is True:
|
||||
user_capabilities['edit'] = user_capabilities['delete'] = False
|
||||
return user_capabilities
|
||||
|
||||
|
||||
|
||||
162
awx/main/conf.py
162
awx/main/conf.py
@@ -2,7 +2,6 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from distutils.version import LooseVersion as Version
|
||||
|
||||
# Django
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -436,87 +435,6 @@ register(
|
||||
category_slug='jobs',
|
||||
)
|
||||
|
||||
register(
|
||||
'PRIMARY_GALAXY_URL',
|
||||
field_class=fields.URLField,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
label=_('Primary Galaxy Server URL'),
|
||||
help_text=_(
|
||||
'For organizations that run their own Galaxy service, this gives the option to specify a '
|
||||
'host as the primary galaxy server. Requirements will be downloaded from the primary if the '
|
||||
'specific role or collection is available there. If the content is not avilable in the primary, '
|
||||
'or if this field is left blank, it will default to galaxy.ansible.com.'
|
||||
),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs'
|
||||
)
|
||||
|
||||
register(
|
||||
'PRIMARY_GALAXY_USERNAME',
|
||||
field_class=fields.CharField,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
label=_('Primary Galaxy Server Username'),
|
||||
help_text=_('(This setting is deprecated and will be removed in a future release) '
|
||||
'For using a galaxy server at higher precedence than the public Ansible Galaxy. '
|
||||
'The username to use for basic authentication against the Galaxy instance, '
|
||||
'this is mutually exclusive with PRIMARY_GALAXY_TOKEN.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs'
|
||||
)
|
||||
|
||||
register(
|
||||
'PRIMARY_GALAXY_PASSWORD',
|
||||
field_class=fields.CharField,
|
||||
encrypted=True,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
label=_('Primary Galaxy Server Password'),
|
||||
help_text=_('(This setting is deprecated and will be removed in a future release) '
|
||||
'For using a galaxy server at higher precedence than the public Ansible Galaxy. '
|
||||
'The password to use for basic authentication against the Galaxy instance, '
|
||||
'this is mutually exclusive with PRIMARY_GALAXY_TOKEN.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs'
|
||||
)
|
||||
|
||||
register(
|
||||
'PRIMARY_GALAXY_TOKEN',
|
||||
field_class=fields.CharField,
|
||||
encrypted=True,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
label=_('Primary Galaxy Server Token'),
|
||||
help_text=_('For using a galaxy server at higher precedence than the public Ansible Galaxy. '
|
||||
'The token to use for connecting with the Galaxy instance, '
|
||||
'this is mutually exclusive with corresponding username and password settings.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs'
|
||||
)
|
||||
|
||||
register(
|
||||
'PRIMARY_GALAXY_AUTH_URL',
|
||||
field_class=fields.CharField,
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
label=_('Primary Galaxy Authentication URL'),
|
||||
help_text=_('For using a galaxy server at higher precedence than the public Ansible Galaxy. '
|
||||
'The token_endpoint of a Keycloak server.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs'
|
||||
)
|
||||
|
||||
register(
|
||||
'PUBLIC_GALAXY_ENABLED',
|
||||
field_class=fields.BooleanField,
|
||||
default=True,
|
||||
label=_('Allow Access to Public Galaxy'),
|
||||
help_text=_('Allow or deny access to the public Ansible Galaxy during project updates.'),
|
||||
category=_('Jobs'),
|
||||
category_slug='jobs'
|
||||
)
|
||||
|
||||
register(
|
||||
'GALAXY_IGNORE_CERTS',
|
||||
field_class=fields.BooleanField,
|
||||
@@ -856,84 +774,4 @@ def logging_validate(serializer, attrs):
|
||||
return attrs
|
||||
|
||||
|
||||
def galaxy_validate(serializer, attrs):
|
||||
"""Ansible Galaxy config options have mutual exclusivity rules, these rules
|
||||
are enforced here on serializer validation so that users will not be able
|
||||
to save settings which obviously break all project updates.
|
||||
"""
|
||||
prefix = 'PRIMARY_GALAXY_'
|
||||
errors = {}
|
||||
|
||||
def _new_value(setting_name):
|
||||
if setting_name in attrs:
|
||||
return attrs[setting_name]
|
||||
elif not serializer.instance:
|
||||
return ''
|
||||
return getattr(serializer.instance, setting_name, '')
|
||||
|
||||
if not _new_value('PRIMARY_GALAXY_URL'):
|
||||
if _new_value('PUBLIC_GALAXY_ENABLED') is False:
|
||||
msg = _('A URL for Primary Galaxy must be defined before disabling public Galaxy.')
|
||||
# put error in both keys because UI has trouble with errors in toggles
|
||||
for key in ('PRIMARY_GALAXY_URL', 'PUBLIC_GALAXY_ENABLED'):
|
||||
errors.setdefault(key, [])
|
||||
errors[key].append(msg)
|
||||
raise serializers.ValidationError(errors)
|
||||
|
||||
from awx.main.constants import GALAXY_SERVER_FIELDS
|
||||
if not any('{}{}'.format(prefix, subfield.upper()) in attrs for subfield in GALAXY_SERVER_FIELDS):
|
||||
return attrs
|
||||
|
||||
galaxy_data = {}
|
||||
for subfield in GALAXY_SERVER_FIELDS:
|
||||
galaxy_data[subfield] = _new_value('{}{}'.format(prefix, subfield.upper()))
|
||||
if not galaxy_data['url']:
|
||||
for k, v in galaxy_data.items():
|
||||
if v:
|
||||
setting_name = '{}{}'.format(prefix, k.upper())
|
||||
errors.setdefault(setting_name, [])
|
||||
errors[setting_name].append(_(
|
||||
'Cannot provide field if PRIMARY_GALAXY_URL is not set.'
|
||||
))
|
||||
for k in GALAXY_SERVER_FIELDS:
|
||||
if galaxy_data[k]:
|
||||
setting_name = '{}{}'.format(prefix, k.upper())
|
||||
if (not serializer.instance) or (not getattr(serializer.instance, setting_name, '')):
|
||||
# new auth is applied, so check if compatible with version
|
||||
from awx.main.utils import get_ansible_version
|
||||
current_version = get_ansible_version()
|
||||
min_version = '2.9'
|
||||
if Version(current_version) < Version(min_version):
|
||||
errors.setdefault(setting_name, [])
|
||||
errors[setting_name].append(_(
|
||||
'Galaxy server settings are not available until Ansible {min_version}, '
|
||||
'you are running {current_version}.'
|
||||
).format(min_version=min_version, current_version=current_version))
|
||||
if (galaxy_data['password'] or galaxy_data['username']) and (galaxy_data['token'] or galaxy_data['auth_url']):
|
||||
for k in ('password', 'username', 'token', 'auth_url'):
|
||||
setting_name = '{}{}'.format(prefix, k.upper())
|
||||
if setting_name in attrs:
|
||||
errors.setdefault(setting_name, [])
|
||||
errors[setting_name].append(_(
|
||||
'Setting Galaxy token and authentication URL is mutually exclusive with username and password.'
|
||||
))
|
||||
if bool(galaxy_data['username']) != bool(galaxy_data['password']):
|
||||
msg = _('If authenticating via username and password, both must be provided.')
|
||||
for k in ('username', 'password'):
|
||||
setting_name = '{}{}'.format(prefix, k.upper())
|
||||
errors.setdefault(setting_name, [])
|
||||
errors[setting_name].append(msg)
|
||||
if bool(galaxy_data['token']) != bool(galaxy_data['auth_url']):
|
||||
msg = _('If authenticating via token, both token and authentication URL must be provided.')
|
||||
for k in ('token', 'auth_url'):
|
||||
setting_name = '{}{}'.format(prefix, k.upper())
|
||||
errors.setdefault(setting_name, [])
|
||||
errors[setting_name].append(msg)
|
||||
|
||||
if errors:
|
||||
raise serializers.ValidationError(errors)
|
||||
return attrs
|
||||
|
||||
|
||||
register_validate('logging', logging_validate)
|
||||
register_validate('jobs', galaxy_validate)
|
||||
|
||||
@@ -50,7 +50,3 @@ LOGGER_BLOCKLIST = (
|
||||
# loggers that may be called getting logging settings
|
||||
'awx.conf'
|
||||
)
|
||||
|
||||
# these correspond to both AWX and Ansible settings to keep naming consistent
|
||||
# for instance, settings.PRIMARY_GALAXY_AUTH_URL vs env var ANSIBLE_GALAXY_SERVER_FOO_AUTH_URL
|
||||
GALAXY_SERVER_FIELDS = ('url', 'username', 'password', 'token', 'auth_url')
|
||||
|
||||
@@ -42,6 +42,16 @@ class Command(BaseCommand):
|
||||
},
|
||||
created_by=superuser)
|
||||
c.admin_role.members.add(superuser)
|
||||
public_galaxy_credential = Credential(
|
||||
name='Ansible Galaxy',
|
||||
managed_by_tower=True,
|
||||
credential_type=CredentialType.objects.get(kind='galaxy'),
|
||||
inputs = {
|
||||
'url': 'https://galaxy.ansible.com/'
|
||||
}
|
||||
)
|
||||
public_galaxy_credential.save()
|
||||
o.galaxy_credentials.add(public_galaxy_credential)
|
||||
i = Inventory.objects.create(name='Demo Inventory',
|
||||
organization=o,
|
||||
created_by=superuser)
|
||||
|
||||
51
awx/main/migrations/0120_galaxy_credentials.py
Normal file
51
awx/main/migrations/0120_galaxy_credentials.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 2.2.11 on 2020-08-04 15:19
|
||||
|
||||
import logging
|
||||
|
||||
import awx.main.fields
|
||||
from awx.main.utils.encryption import encrypt_field, decrypt_field
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import now
|
||||
import django.db.models.deletion
|
||||
|
||||
from awx.main.migrations import _galaxy as galaxy
|
||||
from awx.main.models import CredentialType as ModernCredentialType
|
||||
from awx.main.utils.common import set_current_apps
|
||||
|
||||
logger = logging.getLogger('awx.main.migrations')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0119_inventory_plugins'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='credentialtype',
|
||||
name='kind',
|
||||
field=models.CharField(choices=[('ssh', 'Machine'), ('vault', 'Vault'), ('net', 'Network'), ('scm', 'Source Control'), ('cloud', 'Cloud'), ('token', 'Personal Access Token'), ('insights', 'Insights'), ('external', 'External'), ('kubernetes', 'Kubernetes'), ('galaxy', 'Galaxy/Automation Hub')], max_length=32),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizationGalaxyCredentialMembership',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('position', models.PositiveIntegerField(db_index=True, default=None, null=True)),
|
||||
('credential', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Credential')),
|
||||
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Organization')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='galaxy_credentials',
|
||||
field=awx.main.fields.OrderedManyToManyField(blank=True, related_name='organization_galaxy_credentials', through='main.OrganizationGalaxyCredentialMembership', to='main.Credential'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='credential',
|
||||
name='managed_by_tower',
|
||||
field=models.BooleanField(default=False, editable=False),
|
||||
),
|
||||
migrations.RunPython(galaxy.migrate_galaxy_settings)
|
||||
]
|
||||
125
awx/main/migrations/_galaxy.py
Normal file
125
awx/main/migrations/_galaxy.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# Generated by Django 2.2.11 on 2020-08-04 15:19
|
||||
|
||||
import logging
|
||||
|
||||
from awx.main.utils.encryption import encrypt_field, decrypt_field
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.timezone import now
|
||||
|
||||
from awx.main.models import CredentialType as ModernCredentialType
|
||||
from awx.main.utils.common import set_current_apps
|
||||
|
||||
logger = logging.getLogger('awx.main.migrations')
|
||||
|
||||
|
||||
def migrate_galaxy_settings(apps, schema_editor):
|
||||
Organization = apps.get_model('main', 'Organization')
|
||||
if Organization.objects.count() == 0:
|
||||
# nothing to migrate
|
||||
return
|
||||
set_current_apps(apps)
|
||||
ModernCredentialType.setup_tower_managed_defaults()
|
||||
CredentialType = apps.get_model('main', 'CredentialType')
|
||||
Credential = apps.get_model('main', 'Credential')
|
||||
Setting = apps.get_model('conf', 'Setting')
|
||||
|
||||
galaxy_type = CredentialType.objects.get(kind='galaxy')
|
||||
private_galaxy_url = Setting.objects.filter(key='PRIMARY_GALAXY_URL').first()
|
||||
|
||||
# by default, prior versions of AWX/Tower automatically pulled content
|
||||
# from galaxy.ansible.com
|
||||
public_galaxy_enabled = True
|
||||
public_galaxy_setting = Setting.objects.filter(key='PUBLIC_GALAXY_ENABLED').first()
|
||||
if public_galaxy_setting and public_galaxy_setting.value is False:
|
||||
# ...UNLESS this behavior was explicitly disabled via this setting
|
||||
public_galaxy_enabled = False
|
||||
|
||||
public_galaxy_credential = Credential(
|
||||
created=now(),
|
||||
modified=now(),
|
||||
name='Ansible Galaxy',
|
||||
managed_by_tower=True,
|
||||
credential_type=galaxy_type,
|
||||
inputs = {
|
||||
'url': 'https://galaxy.ansible.com/'
|
||||
}
|
||||
)
|
||||
public_galaxy_credential.save()
|
||||
|
||||
for org in Organization.objects.all():
|
||||
if private_galaxy_url and private_galaxy_url.value:
|
||||
# If a setting exists for a private Galaxy URL, make a credential for it
|
||||
username = Setting.objects.filter(key='PRIMARY_GALAXY_USERNAME').first()
|
||||
password = Setting.objects.filter(key='PRIMARY_GALAXY_PASSWORD').first()
|
||||
if (username and username.value) or (password and password.value):
|
||||
logger.error(
|
||||
f'Specifying HTTP basic auth for the Ansible Galaxy API '
|
||||
f'({private_galaxy_url.value}) is no longer supported. '
|
||||
'Please provide an API token instead after your upgrade '
|
||||
'has completed',
|
||||
)
|
||||
inputs = {
|
||||
'url': private_galaxy_url.value
|
||||
}
|
||||
token = Setting.objects.filter(key='PRIMARY_GALAXY_TOKEN').first()
|
||||
if token and token.value:
|
||||
inputs['token'] = decrypt_field(token, 'value')
|
||||
auth_url = Setting.objects.filter(key='PRIMARY_GALAXY_AUTH_URL').first()
|
||||
if auth_url and auth_url.value:
|
||||
inputs['auth_url'] = auth_url.value
|
||||
name = f'Private Galaxy ({private_galaxy_url.value})'
|
||||
if 'cloud.redhat.com' in inputs['url']:
|
||||
name = f'Ansible Automation Hub ({private_galaxy_url.value})'
|
||||
cred = Credential(
|
||||
created=now(),
|
||||
modified=now(),
|
||||
name=name,
|
||||
organization=org,
|
||||
credential_type=galaxy_type,
|
||||
inputs=inputs
|
||||
)
|
||||
cred.save()
|
||||
if token and token.value:
|
||||
# encrypt based on the primary key from the prior save
|
||||
cred.inputs['token'] = encrypt_field(cred, 'token')
|
||||
cred.save()
|
||||
org.galaxy_credentials.add(cred)
|
||||
|
||||
fallback_servers = getattr(settings, 'FALLBACK_GALAXY_SERVERS', [])
|
||||
for fallback in fallback_servers:
|
||||
url = fallback.get('url', None)
|
||||
auth_url = fallback.get('auth_url', None)
|
||||
username = fallback.get('username', None)
|
||||
password = fallback.get('password', None)
|
||||
token = fallback.get('token', None)
|
||||
if username or password:
|
||||
logger.error(
|
||||
f'Specifying HTTP basic auth for the Ansible Galaxy API '
|
||||
f'({url}) is no longer supported. '
|
||||
'Please provide an API token instead after your upgrade '
|
||||
'has completed',
|
||||
)
|
||||
inputs = {'url': url}
|
||||
if token:
|
||||
inputs['token'] = token
|
||||
if auth_url:
|
||||
inputs['auth_url'] = auth_url
|
||||
cred = Credential(
|
||||
created=now(),
|
||||
modified=now(),
|
||||
name=f'Ansible Galaxy ({url})',
|
||||
organization=org,
|
||||
credential_type=galaxy_type,
|
||||
inputs=inputs
|
||||
)
|
||||
cred.save()
|
||||
if token:
|
||||
# encrypt based on the primary key from the prior save
|
||||
cred.inputs['token'] = encrypt_field(cred, 'token')
|
||||
cred.save()
|
||||
org.galaxy_credentials.add(cred)
|
||||
|
||||
if public_galaxy_enabled:
|
||||
# If public Galaxy was enabled, associate it to the org
|
||||
org.galaxy_credentials.add(public_galaxy_credential)
|
||||
@@ -96,6 +96,10 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
||||
help_text=_('Specify the type of credential you want to create. Refer '
|
||||
'to the Ansible Tower documentation for details on each type.')
|
||||
)
|
||||
managed_by_tower = models.BooleanField(
|
||||
default=False,
|
||||
editable=False
|
||||
)
|
||||
organization = models.ForeignKey(
|
||||
'Organization',
|
||||
null=True,
|
||||
@@ -331,6 +335,7 @@ class CredentialType(CommonModelNameNotUnique):
|
||||
('insights', _('Insights')),
|
||||
('external', _('External')),
|
||||
('kubernetes', _('Kubernetes')),
|
||||
('galaxy', _('Galaxy/Automation Hub')),
|
||||
)
|
||||
|
||||
kind = models.CharField(
|
||||
@@ -1173,6 +1178,38 @@ ManagedCredentialType(
|
||||
)
|
||||
|
||||
|
||||
ManagedCredentialType(
|
||||
namespace='galaxy_api_token',
|
||||
kind='galaxy',
|
||||
name=ugettext_noop('Ansible Galaxy/Automation Hub API Token'),
|
||||
inputs={
|
||||
'fields': [{
|
||||
'id': 'url',
|
||||
'label': ugettext_noop('Galaxy Server URL'),
|
||||
'type': 'string',
|
||||
'help_text': ugettext_noop('The URL of the Galaxy instance to connect to.')
|
||||
},{
|
||||
'id': 'auth_url',
|
||||
'label': ugettext_noop('Auth Server URL'),
|
||||
'type': 'string',
|
||||
'help_text': ugettext_noop(
|
||||
'The URL of a Keycloak server token_endpoint, if using '
|
||||
'SSO auth.'
|
||||
)
|
||||
},{
|
||||
'id': 'token',
|
||||
'label': ugettext_noop('API Token'),
|
||||
'type': 'string',
|
||||
'secret': True,
|
||||
'help_text': ugettext_noop(
|
||||
'A token to use for authentication against the Galaxy instance.'
|
||||
)
|
||||
}],
|
||||
'required': ['url'],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CredentialInputSource(PrimordialModel):
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -45,6 +45,12 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi
|
||||
blank=True,
|
||||
through='OrganizationInstanceGroupMembership'
|
||||
)
|
||||
galaxy_credentials = OrderedManyToManyField(
|
||||
'Credential',
|
||||
blank=True,
|
||||
through='OrganizationGalaxyCredentialMembership',
|
||||
related_name='%(class)s_galaxy_credentials'
|
||||
)
|
||||
max_hosts = models.PositiveIntegerField(
|
||||
blank=True,
|
||||
default=0,
|
||||
@@ -108,6 +114,23 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi
|
||||
return UnifiedJob.objects.non_polymorphic().filter(organization=self)
|
||||
|
||||
|
||||
class OrganizationGalaxyCredentialMembership(models.Model):
|
||||
|
||||
organization = models.ForeignKey(
|
||||
'Organization',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
credential = models.ForeignKey(
|
||||
'Credential',
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
position = models.PositiveIntegerField(
|
||||
null=True,
|
||||
default=None,
|
||||
db_index=True,
|
||||
)
|
||||
|
||||
|
||||
class Team(CommonModelNameNotUnique, ResourceMixin):
|
||||
'''
|
||||
A team is a group of users that work on common projects.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import re
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
REPLACE_STR = '$encrypted$'
|
||||
|
||||
|
||||
@@ -12,12 +10,6 @@ class UriCleaner(object):
|
||||
|
||||
@staticmethod
|
||||
def remove_sensitive(cleartext):
|
||||
# exclude_list contains the items that will _not_ be redacted
|
||||
exclude_list = [settings.PUBLIC_GALAXY_SERVER['url']]
|
||||
if settings.PRIMARY_GALAXY_URL:
|
||||
exclude_list += [settings.PRIMARY_GALAXY_URL]
|
||||
if settings.FALLBACK_GALAXY_SERVERS:
|
||||
exclude_list += [server['url'] for server in settings.FALLBACK_GALAXY_SERVERS]
|
||||
redactedtext = cleartext
|
||||
text_index = 0
|
||||
while True:
|
||||
@@ -25,10 +17,6 @@ class UriCleaner(object):
|
||||
if not match:
|
||||
break
|
||||
uri_str = match.group(1)
|
||||
# Do not redact items from the exclude list
|
||||
if any(uri_str.startswith(exclude_uri) for exclude_uri in exclude_list):
|
||||
text_index = match.start() + len(uri_str)
|
||||
continue
|
||||
try:
|
||||
# May raise a ValueError if invalid URI for one reason or another
|
||||
o = urlparse.urlsplit(uri_str)
|
||||
|
||||
@@ -51,7 +51,7 @@ import ansible_runner
|
||||
|
||||
# AWX
|
||||
from awx import __version__ as awx_application_version
|
||||
from awx.main.constants import PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_UPDATE_ENV, GALAXY_SERVER_FIELDS
|
||||
from awx.main.constants import PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_UPDATE_ENV
|
||||
from awx.main.access import access_registry
|
||||
from awx.main.redact import UriCleaner
|
||||
from awx.main.models import (
|
||||
@@ -2015,35 +2015,25 @@ class RunProjectUpdate(BaseTask):
|
||||
env['PROJECT_UPDATE_ID'] = str(project_update.pk)
|
||||
if settings.GALAXY_IGNORE_CERTS:
|
||||
env['ANSIBLE_GALAXY_IGNORE'] = True
|
||||
# Set up the public Galaxy server, if enabled
|
||||
galaxy_configured = False
|
||||
if settings.PUBLIC_GALAXY_ENABLED:
|
||||
galaxy_servers = [settings.PUBLIC_GALAXY_SERVER] # static setting
|
||||
else:
|
||||
galaxy_configured = True
|
||||
galaxy_servers = []
|
||||
# Set up fallback Galaxy servers, if configured
|
||||
if settings.FALLBACK_GALAXY_SERVERS:
|
||||
galaxy_configured = True
|
||||
galaxy_servers = settings.FALLBACK_GALAXY_SERVERS + galaxy_servers
|
||||
# Set up the primary Galaxy server, if configured
|
||||
if settings.PRIMARY_GALAXY_URL:
|
||||
galaxy_configured = True
|
||||
galaxy_servers = [{'id': 'primary_galaxy'}] + galaxy_servers
|
||||
for key in GALAXY_SERVER_FIELDS:
|
||||
value = getattr(settings, 'PRIMARY_GALAXY_{}'.format(key.upper()))
|
||||
if value:
|
||||
galaxy_servers[0][key] = value
|
||||
if galaxy_configured:
|
||||
for server in galaxy_servers:
|
||||
for key in GALAXY_SERVER_FIELDS:
|
||||
if not server.get(key):
|
||||
continue
|
||||
env_key = ('ANSIBLE_GALAXY_SERVER_{}_{}'.format(server.get('id', 'unnamed'), key)).upper()
|
||||
env[env_key] = server[key]
|
||||
if galaxy_servers:
|
||||
# now set the precedence of galaxy servers
|
||||
env['ANSIBLE_GALAXY_SERVER_LIST'] = ','.join([server.get('id', 'unnamed') for server in galaxy_servers])
|
||||
|
||||
# build out env vars for Galaxy credentials (in order)
|
||||
galaxy_server_list = []
|
||||
if project_update.project.organization:
|
||||
for i, cred in enumerate(
|
||||
project_update.project.organization.galaxy_credentials.all()
|
||||
):
|
||||
env[f'ANSIBLE_GALAXY_SERVER_SERVER{i}_URL'] = cred.get_input('url')
|
||||
auth_url = cred.get_input('auth_url', default=None)
|
||||
token = cred.get_input('token', default=None)
|
||||
if token:
|
||||
env[f'ANSIBLE_GALAXY_SERVER_SERVER{i}_TOKEN'] = token
|
||||
if auth_url:
|
||||
env[f'ANSIBLE_GALAXY_SERVER_SERVER{i}_AUTH_URL'] = auth_url
|
||||
galaxy_server_list.append(f'server{i}')
|
||||
|
||||
if galaxy_server_list:
|
||||
env['ANSIBLE_GALAXY_SERVER_LIST'] = ','.join(galaxy_server_list)
|
||||
|
||||
return env
|
||||
|
||||
def _build_scm_url_extra_vars(self, project_update):
|
||||
@@ -2116,6 +2106,19 @@ class RunProjectUpdate(BaseTask):
|
||||
raise RuntimeError('Could not determine a revision to run from project.')
|
||||
elif not scm_branch:
|
||||
scm_branch = {'hg': 'tip'}.get(project_update.scm_type, 'HEAD')
|
||||
|
||||
galaxy_creds_are_defined = (
|
||||
project_update.project.organization and
|
||||
project_update.project.organization.galaxy_credentials.exists()
|
||||
)
|
||||
if not galaxy_creds_are_defined and (
|
||||
settings.AWX_ROLES_ENABLED or settings.AWX_COLLECTIONS_ENABLED
|
||||
):
|
||||
logger.debug(
|
||||
'Galaxy role/collection syncing is enabled, but no '
|
||||
f'credentials are configured for {project_update.project.organization}.'
|
||||
)
|
||||
|
||||
extra_vars.update({
|
||||
'projects_root': settings.PROJECTS_ROOT.rstrip('/'),
|
||||
'local_path': os.path.basename(project_update.project.local_path),
|
||||
@@ -2126,8 +2129,8 @@ class RunProjectUpdate(BaseTask):
|
||||
'scm_url': scm_url,
|
||||
'scm_branch': scm_branch,
|
||||
'scm_clean': project_update.scm_clean,
|
||||
'roles_enabled': settings.AWX_ROLES_ENABLED,
|
||||
'collections_enabled': settings.AWX_COLLECTIONS_ENABLED,
|
||||
'roles_enabled': galaxy_creds_are_defined and settings.AWX_ROLES_ENABLED,
|
||||
'collections_enabled': galaxy_creds_are_defined and settings.AWX_COLLECTIONS_ENABLED,
|
||||
})
|
||||
# apply custom refspec from user for PR refs and the like
|
||||
if project_update.scm_refspec:
|
||||
|
||||
@@ -220,7 +220,7 @@ def test_create_valid_kind(kind, get, post, admin):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('kind', ['ssh', 'vault', 'scm', 'insights', 'kubernetes'])
|
||||
@pytest.mark.parametrize('kind', ['ssh', 'vault', 'scm', 'insights', 'kubernetes', 'galaxy'])
|
||||
def test_create_invalid_kind(kind, get, post, admin):
|
||||
response = post(reverse('api:credential_type_list'), {
|
||||
'kind': kind,
|
||||
|
||||
@@ -9,7 +9,7 @@ from django.conf import settings
|
||||
import pytest
|
||||
|
||||
# AWX
|
||||
from awx.main.models import ProjectUpdate
|
||||
from awx.main.models import ProjectUpdate, CredentialType, Credential
|
||||
from awx.api.versioning import reverse
|
||||
|
||||
|
||||
@@ -288,3 +288,90 @@ def test_organization_delete_with_active_jobs(delete, admin, organization, organ
|
||||
|
||||
assert resp.data['error'] == u"Resource is being used by running jobs."
|
||||
assert resp_sorted == expect_sorted
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_galaxy_credential_association_forbidden(alice, organization, post):
|
||||
galaxy = CredentialType.defaults['galaxy_api_token']()
|
||||
galaxy.save()
|
||||
|
||||
cred = Credential.objects.create(
|
||||
credential_type=galaxy,
|
||||
name='Public Galaxy',
|
||||
organization=organization,
|
||||
inputs={
|
||||
'url': 'https://galaxy.ansible.com/'
|
||||
}
|
||||
)
|
||||
url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.id})
|
||||
post(
|
||||
url,
|
||||
{'associate': True, 'id': cred.pk},
|
||||
user=alice,
|
||||
expect=403
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_galaxy_credential_type_enforcement(admin, organization, post):
|
||||
ssh = CredentialType.defaults['ssh']()
|
||||
ssh.save()
|
||||
|
||||
cred = Credential.objects.create(
|
||||
credential_type=ssh,
|
||||
name='SSH Credential',
|
||||
organization=organization,
|
||||
)
|
||||
url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.id})
|
||||
resp = post(
|
||||
url,
|
||||
{'associate': True, 'id': cred.pk},
|
||||
user=admin,
|
||||
expect=400
|
||||
)
|
||||
assert resp.data['msg'] == 'Credential must be a Galaxy credential, not Machine.'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_galaxy_credential_association(alice, admin, organization, post, get):
|
||||
galaxy = CredentialType.defaults['galaxy_api_token']()
|
||||
galaxy.save()
|
||||
|
||||
for i in range(5):
|
||||
cred = Credential.objects.create(
|
||||
credential_type=galaxy,
|
||||
name=f'Public Galaxy {i + 1}',
|
||||
organization=organization,
|
||||
inputs={
|
||||
'url': 'https://galaxy.ansible.com/'
|
||||
}
|
||||
)
|
||||
url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.id})
|
||||
post(
|
||||
url,
|
||||
{'associate': True, 'id': cred.pk},
|
||||
user=admin,
|
||||
expect=204
|
||||
)
|
||||
resp = get(url, user=admin)
|
||||
assert [cred['name'] for cred in resp.data['results']] == [
|
||||
'Public Galaxy 1',
|
||||
'Public Galaxy 2',
|
||||
'Public Galaxy 3',
|
||||
'Public Galaxy 4',
|
||||
'Public Galaxy 5',
|
||||
]
|
||||
|
||||
post(
|
||||
url,
|
||||
{'disassociate': True, 'id': Credential.objects.get(name='Public Galaxy 3').pk},
|
||||
user=admin,
|
||||
expect=204
|
||||
)
|
||||
resp = get(url, user=admin)
|
||||
assert [cred['name'] for cred in resp.data['results']] == [
|
||||
'Public Galaxy 1',
|
||||
'Public Galaxy 2',
|
||||
'Public Galaxy 4',
|
||||
'Public Galaxy 5',
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
from unittest import mock
|
||||
|
||||
from awx.main.models import Project
|
||||
from awx.main.models import Project, Credential, CredentialType
|
||||
from awx.main.models.organization import Organization
|
||||
|
||||
|
||||
@@ -57,3 +57,31 @@ def test_foreign_key_change_changes_modified_by(project, organization):
|
||||
def test_project_related_jobs(project):
|
||||
update = project.create_unified_job()
|
||||
assert update.id in [u.id for u in project._get_related_jobs()]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_galaxy_credentials(project):
|
||||
org = project.organization
|
||||
galaxy = CredentialType.defaults['galaxy_api_token']()
|
||||
galaxy.save()
|
||||
for i in range(5):
|
||||
cred = Credential.objects.create(
|
||||
name=f'Ansible Galaxy {i + 1}',
|
||||
organization=org,
|
||||
credential_type=galaxy,
|
||||
inputs={
|
||||
'url': 'https://galaxy.ansible.com/'
|
||||
}
|
||||
)
|
||||
cred.save()
|
||||
org.galaxy_credentials.add(cred)
|
||||
|
||||
assert [
|
||||
cred.name for cred in org.galaxy_credentials.all()
|
||||
] == [
|
||||
'Ansible Galaxy 1',
|
||||
'Ansible Galaxy 2',
|
||||
'Ansible Galaxy 3',
|
||||
'Ansible Galaxy 4',
|
||||
'Ansible Galaxy 5',
|
||||
]
|
||||
|
||||
@@ -81,6 +81,7 @@ def test_default_cred_types():
|
||||
'azure_rm',
|
||||
'cloudforms',
|
||||
'conjur',
|
||||
'galaxy_api_token',
|
||||
'gce',
|
||||
'github_token',
|
||||
'gitlab_token',
|
||||
|
||||
115
awx/main/tests/functional/test_galaxy_credential_migration.py
Normal file
115
awx/main/tests/functional/test_galaxy_credential_migration.py
Normal file
@@ -0,0 +1,115 @@
|
||||
import importlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
import pytest
|
||||
|
||||
from awx.main.models import Credential, Organization
|
||||
from awx.conf.models import Setting
|
||||
from awx.main.migrations import _galaxy as galaxy
|
||||
|
||||
|
||||
class FakeApps(object):
|
||||
def get_model(self, app, model):
|
||||
if app == 'contenttypes':
|
||||
return ContentType
|
||||
return getattr(importlib.import_module(f'awx.{app}.models'), model)
|
||||
|
||||
|
||||
apps = FakeApps()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_default_public_galaxy():
|
||||
org = Organization.objects.create()
|
||||
assert org.galaxy_credentials.count() == 0
|
||||
galaxy.migrate_galaxy_settings(apps, None)
|
||||
assert org.galaxy_credentials.count() == 1
|
||||
creds = org.galaxy_credentials.all()
|
||||
assert creds[0].name == 'Ansible Galaxy'
|
||||
assert creds[0].inputs['url'] == 'https://galaxy.ansible.com/'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_public_galaxy_disabled():
|
||||
Setting.objects.create(key='PUBLIC_GALAXY_ENABLED', value=False)
|
||||
org = Organization.objects.create()
|
||||
assert org.galaxy_credentials.count() == 0
|
||||
galaxy.migrate_galaxy_settings(apps, None)
|
||||
assert org.galaxy_credentials.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_rh_automation_hub():
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://cloud.redhat.com/api/automation-hub/')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123')
|
||||
org = Organization.objects.create()
|
||||
assert org.galaxy_credentials.count() == 0
|
||||
galaxy.migrate_galaxy_settings(apps, None)
|
||||
assert org.galaxy_credentials.count() == 2
|
||||
assert org.galaxy_credentials.first().name == 'Ansible Automation Hub (https://cloud.redhat.com/api/automation-hub/)' # noqa
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_multiple_galaxies():
|
||||
for i in range(5):
|
||||
Organization.objects.create(name=f'Org {i}')
|
||||
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://example.org/')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_AUTH_URL', value='https://auth.example.org/')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_USERNAME', value='user')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_PASSWORD', value='pass')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123')
|
||||
|
||||
for org in Organization.objects.all():
|
||||
assert org.galaxy_credentials.count() == 0
|
||||
|
||||
galaxy.migrate_galaxy_settings(apps, None)
|
||||
|
||||
for org in Organization.objects.all():
|
||||
assert org.galaxy_credentials.count() == 2
|
||||
creds = org.galaxy_credentials.all()
|
||||
assert creds[0].name == 'Private Galaxy (https://example.org/)'
|
||||
assert creds[0].inputs['url'] == 'https://example.org/'
|
||||
assert creds[0].inputs['auth_url'] == 'https://auth.example.org/'
|
||||
assert creds[0].inputs['token'].startswith('$encrypted$')
|
||||
assert creds[0].get_input('token') == 'secret123'
|
||||
|
||||
assert creds[1].name == 'Ansible Galaxy'
|
||||
assert creds[1].inputs['url'] == 'https://galaxy.ansible.com/'
|
||||
|
||||
public_galaxy_creds = Credential.objects.filter(name='Ansible Galaxy')
|
||||
assert public_galaxy_creds.count() == 1
|
||||
assert public_galaxy_creds.first().managed_by_tower is True
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_fallback_galaxies():
|
||||
org = Organization.objects.create()
|
||||
assert org.galaxy_credentials.count() == 0
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://example.org/')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_AUTH_URL', value='https://auth.example.org/')
|
||||
Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123')
|
||||
try:
|
||||
settings.FALLBACK_GALAXY_SERVERS = [{
|
||||
'id': 'abc123',
|
||||
'url': 'https://some-other-galaxy.example.org/',
|
||||
'auth_url': 'https://some-other-galaxy.sso.example.org/',
|
||||
'username': 'user',
|
||||
'password': 'pass',
|
||||
'token': 'fallback123',
|
||||
}]
|
||||
galaxy.migrate_galaxy_settings(apps, None)
|
||||
finally:
|
||||
settings.FALLBACK_GALAXY_SERVERS = []
|
||||
assert org.galaxy_credentials.count() == 3
|
||||
creds = org.galaxy_credentials.all()
|
||||
assert creds[0].name == 'Private Galaxy (https://example.org/)'
|
||||
assert creds[0].inputs['url'] == 'https://example.org/'
|
||||
assert creds[1].name == 'Ansible Galaxy (https://some-other-galaxy.example.org/)'
|
||||
assert creds[1].inputs['url'] == 'https://some-other-galaxy.example.org/'
|
||||
assert creds[1].inputs['auth_url'] == 'https://some-other-galaxy.sso.example.org/'
|
||||
assert creds[1].inputs['token'].startswith('$encrypted$')
|
||||
assert creds[1].get_input('token') == 'fallback123'
|
||||
assert creds[2].name == 'Ansible Galaxy'
|
||||
assert creds[2].inputs['url'] == 'https://galaxy.ansible.com/'
|
||||
@@ -25,6 +25,7 @@ from awx.main.models import (
|
||||
Job,
|
||||
JobTemplate,
|
||||
Notification,
|
||||
Organization,
|
||||
Project,
|
||||
ProjectUpdate,
|
||||
UnifiedJob,
|
||||
@@ -59,6 +60,19 @@ def patch_Job():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patch_Organization():
|
||||
_credentials = []
|
||||
credentials_mock = mock.Mock(**{
|
||||
'all': lambda: _credentials,
|
||||
'add': _credentials.append,
|
||||
'exists': lambda: len(_credentials) > 0,
|
||||
'spec_set': ['all', 'add', 'exists'],
|
||||
})
|
||||
with mock.patch.object(Organization, 'galaxy_credentials', credentials_mock):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def job():
|
||||
return Job(
|
||||
@@ -131,7 +145,6 @@ def test_send_notifications_list(mock_notifications_filter, mock_job_get, mocker
|
||||
('SECRET_KEY', 'SECRET'),
|
||||
('VMWARE_PASSWORD', 'SECRET'),
|
||||
('API_SECRET', 'SECRET'),
|
||||
('ANSIBLE_GALAXY_SERVER_PRIMARY_GALAXY_PASSWORD', 'SECRET'),
|
||||
('ANSIBLE_GALAXY_SERVER_PRIMARY_GALAXY_TOKEN', 'SECRET'),
|
||||
])
|
||||
def test_safe_env_filtering(key, value):
|
||||
@@ -1780,10 +1793,108 @@ class TestJobCredentials(TestJobExecution):
|
||||
assert env['FOO'] == 'BAR'
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("patch_Organization")
|
||||
class TestProjectUpdateGalaxyCredentials(TestJobExecution):
|
||||
|
||||
@pytest.fixture
|
||||
def project_update(self):
|
||||
org = Organization(pk=1)
|
||||
proj = Project(pk=1, organization=org)
|
||||
project_update = ProjectUpdate(pk=1, project=proj, scm_type='git')
|
||||
project_update.websocket_emit_status = mock.Mock()
|
||||
return project_update
|
||||
|
||||
parametrize = {
|
||||
'test_galaxy_credentials_ignore_certs': [
|
||||
dict(ignore=True),
|
||||
dict(ignore=False),
|
||||
],
|
||||
}
|
||||
|
||||
def test_galaxy_credentials_ignore_certs(self, private_data_dir, project_update, ignore):
|
||||
settings.GALAXY_IGNORE_CERTS = ignore
|
||||
task = tasks.RunProjectUpdate()
|
||||
env = task.build_env(project_update, private_data_dir)
|
||||
if ignore:
|
||||
assert env['ANSIBLE_GALAXY_IGNORE'] is True
|
||||
else:
|
||||
assert 'ANSIBLE_GALAXY_IGNORE' not in env
|
||||
|
||||
def test_galaxy_credentials_empty(self, private_data_dir, project_update):
|
||||
|
||||
class RunProjectUpdate(tasks.RunProjectUpdate):
|
||||
__vars__ = {}
|
||||
|
||||
def _write_extra_vars_file(self, private_data_dir, extra_vars, *kw):
|
||||
self.__vars__ = extra_vars
|
||||
|
||||
task = RunProjectUpdate()
|
||||
env = task.build_env(project_update, private_data_dir)
|
||||
task.build_extra_vars_file(project_update, private_data_dir)
|
||||
assert task.__vars__['roles_enabled'] is False
|
||||
assert task.__vars__['collections_enabled'] is False
|
||||
for k in env:
|
||||
assert not k.startswith('ANSIBLE_GALAXY_SERVER')
|
||||
|
||||
def test_single_public_galaxy(self, private_data_dir, project_update):
|
||||
class RunProjectUpdate(tasks.RunProjectUpdate):
|
||||
__vars__ = {}
|
||||
|
||||
def _write_extra_vars_file(self, private_data_dir, extra_vars, *kw):
|
||||
self.__vars__ = extra_vars
|
||||
|
||||
credential_type = CredentialType.defaults['galaxy_api_token']()
|
||||
public_galaxy = Credential(pk=1, credential_type=credential_type, inputs={
|
||||
'url': 'https://galaxy.ansible.com/',
|
||||
})
|
||||
project_update.project.organization.galaxy_credentials.add(public_galaxy)
|
||||
task = RunProjectUpdate()
|
||||
env = task.build_env(project_update, private_data_dir)
|
||||
task.build_extra_vars_file(project_update, private_data_dir)
|
||||
assert task.__vars__['roles_enabled'] is True
|
||||
assert task.__vars__['collections_enabled'] is True
|
||||
assert sorted([
|
||||
(k, v) for k, v in env.items()
|
||||
if k.startswith('ANSIBLE_GALAXY')
|
||||
]) == [
|
||||
('ANSIBLE_GALAXY_SERVER_LIST', 'server0'),
|
||||
('ANSIBLE_GALAXY_SERVER_SERVER0_URL', 'https://galaxy.ansible.com/'),
|
||||
]
|
||||
|
||||
def test_multiple_galaxy_endpoints(self, private_data_dir, project_update):
|
||||
credential_type = CredentialType.defaults['galaxy_api_token']()
|
||||
public_galaxy = Credential(pk=1, credential_type=credential_type, inputs={
|
||||
'url': 'https://galaxy.ansible.com/',
|
||||
})
|
||||
rh = Credential(pk=2, credential_type=credential_type, inputs={
|
||||
'url': 'https://cloud.redhat.com/api/automation-hub/',
|
||||
'auth_url': 'https://sso.redhat.com/example/openid-connect/token/',
|
||||
'token': 'secret123'
|
||||
})
|
||||
project_update.project.organization.galaxy_credentials.add(public_galaxy)
|
||||
project_update.project.organization.galaxy_credentials.add(rh)
|
||||
task = tasks.RunProjectUpdate()
|
||||
env = task.build_env(project_update, private_data_dir)
|
||||
assert sorted([
|
||||
(k, v) for k, v in env.items()
|
||||
if k.startswith('ANSIBLE_GALAXY')
|
||||
]) == [
|
||||
('ANSIBLE_GALAXY_SERVER_LIST', 'server0,server1'),
|
||||
('ANSIBLE_GALAXY_SERVER_SERVER0_URL', 'https://galaxy.ansible.com/'),
|
||||
('ANSIBLE_GALAXY_SERVER_SERVER1_AUTH_URL', 'https://sso.redhat.com/example/openid-connect/token/'), # noqa
|
||||
('ANSIBLE_GALAXY_SERVER_SERVER1_TOKEN', 'secret123'),
|
||||
('ANSIBLE_GALAXY_SERVER_SERVER1_URL', 'https://cloud.redhat.com/api/automation-hub/'),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("patch_Organization")
|
||||
class TestProjectUpdateCredentials(TestJobExecution):
|
||||
@pytest.fixture
|
||||
def project_update(self):
|
||||
project_update = ProjectUpdate(pk=1, project=Project(pk=1))
|
||||
project_update = ProjectUpdate(
|
||||
pk=1,
|
||||
project=Project(pk=1, organization=Organization(pk=1)),
|
||||
)
|
||||
project_update.websocket_emit_status = mock.Mock()
|
||||
return project_update
|
||||
|
||||
|
||||
Reference in New Issue
Block a user