mirror of
https://github.com/ansible/awx.git
synced 2026-02-08 04:54:45 -03:30
Compare commits
113 Commits
serve-ansi
...
constructe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d88cee6bf | ||
|
|
103b4567fe | ||
|
|
8ba2b1b50c | ||
|
|
dc049af0eb | ||
|
|
8ea8558605 | ||
|
|
3335afcd3a | ||
|
|
1f2a5cf7e4 | ||
|
|
7f2933c43c | ||
|
|
d55af032f7 | ||
|
|
05af2972bf | ||
|
|
60458bebfd | ||
|
|
3ff65db2e6 | ||
|
|
e25c767a47 | ||
|
|
0866bfc549 | ||
|
|
2d9f2d36a1 | ||
|
|
c60ba5cec9 | ||
|
|
c98f86a355 | ||
|
|
3f0d28dd7f | ||
|
|
49e5d76062 | ||
|
|
46ea031566 | ||
|
|
0d7bbb4389 | ||
|
|
1dda373aaf | ||
|
|
ecd788312e | ||
|
|
e1e27a028c | ||
|
|
33c1968210 | ||
|
|
049a158638 | ||
|
|
0961ca06c9 | ||
|
|
a3d7c02802 | ||
|
|
280ceae267 | ||
|
|
ea719e053e | ||
|
|
f275c2a9c5 | ||
|
|
32f7295f44 | ||
|
|
6772fb876b | ||
|
|
3242dbcbe6 | ||
|
|
51112b95bc | ||
|
|
6c1d4a5cfd | ||
|
|
2e9106d8ea | ||
|
|
84822784e8 | ||
|
|
0f3adb52b1 | ||
|
|
59da9a29df | ||
|
|
a949ee048a | ||
|
|
341f8e385c | ||
|
|
b959bc278f | ||
|
|
052644eb9d | ||
|
|
659853dcea | ||
|
|
80c15e286f | ||
|
|
c22d8f1d7e | ||
|
|
4e18827909 | ||
|
|
27a97017dd | ||
|
|
59ce8c4148 | ||
|
|
3b9c04bf1e | ||
|
|
c72dca3ea5 | ||
|
|
ddb3cde872 | ||
|
|
f28203913f | ||
|
|
9b2725e5fe | ||
|
|
1af955d28c | ||
|
|
0815f935ca | ||
|
|
6997876da6 | ||
|
|
93d84fe2c9 | ||
|
|
f5785976be | ||
|
|
c2ec8396cd | ||
|
|
de115ed1c8 | ||
|
|
61c7d4e4ca | ||
|
|
a2f528e6e5 | ||
|
|
058ae132cf | ||
|
|
6483575437 | ||
|
|
a15a23c1d3 | ||
|
|
ffdcb9f4dd | ||
|
|
2d9da11443 | ||
|
|
5ce6c14f74 | ||
|
|
61748c072d | ||
|
|
89dae3865d | ||
|
|
87918bd275 | ||
|
|
808ab9803e | ||
|
|
d64b6d4dfe | ||
|
|
c9d931ceee | ||
|
|
7598e117d4 | ||
|
|
8fb831d3de | ||
|
|
64865af3bb | ||
|
|
9f63c99bee | ||
|
|
d7025a919c | ||
|
|
dab7d91cff | ||
|
|
700055801a | ||
|
|
6a79d19668 | ||
|
|
47176cb31b | ||
|
|
1c6a48ffb6 | ||
|
|
fab83715e9 | ||
|
|
b0a4173545 | ||
|
|
0ebe57cbf4 | ||
|
|
d4840b240b | ||
|
|
bb387f939b | ||
|
|
bda806fd03 | ||
|
|
9777ce7fb8 | ||
|
|
8538d37702 | ||
|
|
5550086b3b | ||
|
|
d8e7c59fe8 | ||
|
|
4470b80059 | ||
|
|
980bfc4b6f | ||
|
|
6351e8bbc9 | ||
|
|
325e566a3d | ||
|
|
d7f87ed27c | ||
|
|
a5baee1b3a | ||
|
|
dd8c9f87a9 | ||
|
|
7f6f57bfee | ||
|
|
ae92f8292f | ||
|
|
51e244e183 | ||
|
|
ad4e257fdb | ||
|
|
fcf56950b3 | ||
|
|
27ea239c00 | ||
|
|
128a130b84 | ||
|
|
d75f12c001 | ||
|
|
2034eac620 | ||
|
|
1d87e6e04c |
62
.github/workflows/ci.yml
vendored
62
.github/workflows/ci.yml
vendored
@@ -1,8 +1,10 @@
|
||||
---
|
||||
name: CI
|
||||
env:
|
||||
BRANCH: ${{ github.base_ref || 'devel' }}
|
||||
LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting
|
||||
CI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DEV_DOCKER_TAG_BASE: ghcr.io/${{ github.repository_owner }}
|
||||
COMPOSE_TAG: ${{ github.base_ref || 'devel' }}
|
||||
on:
|
||||
pull_request:
|
||||
jobs:
|
||||
@@ -18,85 +20,33 @@ jobs:
|
||||
tests:
|
||||
- name: api-test
|
||||
command: /start_tests.sh
|
||||
label: Run API Tests
|
||||
- name: api-lint
|
||||
command: /var/lib/awx/venv/awx/bin/tox -e linters
|
||||
label: Run API Linters
|
||||
- name: api-swagger
|
||||
command: /start_tests.sh swagger
|
||||
label: Generate API Reference
|
||||
- name: awx-collection
|
||||
command: /start_tests.sh test_collection_all
|
||||
label: Run Collection Tests
|
||||
- name: api-schema
|
||||
label: Check API Schema
|
||||
command: /start_tests.sh detect-schema-change SCHEMA_DIFF_BASE_BRANCH=${{ github.event.pull_request.base.ref }}
|
||||
- name: ui-lint
|
||||
label: Run UI Linters
|
||||
command: make ui-lint
|
||||
- name: ui-test-screens
|
||||
label: Run UI Screens Tests
|
||||
command: make ui-test-screens
|
||||
- name: ui-test-general
|
||||
label: Run UI General Tests
|
||||
command: make ui-test-general
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Get python version from Makefile
|
||||
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||
- name: Run check ${{ matrix.tests.name }}
|
||||
run: AWX_DOCKER_CMD='${{ matrix.tests.command }}' make github_ci_runner
|
||||
|
||||
- name: Install python ${{ env.py_version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ env.py_version }}
|
||||
|
||||
- name: Log in to registry
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Pre-pull image to warm build cache
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ env.BRANCH }} || :
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ env.BRANCH }} make docker-compose-build
|
||||
|
||||
- name: ${{ matrix.texts.label }}
|
||||
run: |
|
||||
docker run -u $(id -u) --rm -v ${{ github.workspace}}:/awx_devel/:Z \
|
||||
--workdir=/awx_devel ghcr.io/${{ github.repository_owner }}/awx_devel:${{ env.BRANCH }} ${{ matrix.tests.command }}
|
||||
dev-env:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Get python version from Makefile
|
||||
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||
|
||||
- name: Install python ${{ env.py_version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ env.py_version }}
|
||||
|
||||
- name: Log in to registry
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Pre-pull image to warm build cache
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ env.BRANCH }} || :
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} COMPOSE_TAG=${{ env.BRANCH }} make docker-compose-build
|
||||
|
||||
- name: Run smoke test
|
||||
run: |
|
||||
export DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }}
|
||||
export COMPOSE_TAG=${{ env.BRANCH }}
|
||||
ansible-playbook tools/docker-compose/ansible/smoke-test.yml -e repo_dir=$(pwd) -v
|
||||
run: make github_ci_setup && ansible-playbook tools/docker-compose/ansible/smoke-test.yml -v
|
||||
|
||||
awx-operator:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
17
Makefile
17
Makefile
@@ -65,7 +65,7 @@ I18N_FLAG_FILE = .i18n_built
|
||||
sdist \
|
||||
ui-release ui-devel \
|
||||
VERSION PYTHON_VERSION docker-compose-sources \
|
||||
.git/hooks/pre-commit
|
||||
.git/hooks/pre-commit github_ci_setup github_ci_runner
|
||||
|
||||
clean-tmp:
|
||||
rm -rf tmp/
|
||||
@@ -301,6 +301,21 @@ test:
|
||||
cd awxkit && $(VENV_BASE)/awx/bin/tox -re py3
|
||||
awx-manage check_migrations --dry-run --check -n 'missing_migration_file'
|
||||
|
||||
## Login to Github container image registry, pull image, then build image.
|
||||
github_ci_setup:
|
||||
# GITHUB_ACTOR is automatic github actions env var
|
||||
# CI_GITHUB_TOKEN is defined in .github files
|
||||
echo $(CI_GITHUB_TOKEN) | docker login ghcr.io -u $(GITHUB_ACTOR) --password-stdin
|
||||
docker pull $(DEVEL_IMAGE_NAME) || : # Pre-pull image to warm build cache
|
||||
make docker-compose-build
|
||||
|
||||
## Runs AWX_DOCKER_CMD inside a new docker container.
|
||||
docker-runner:
|
||||
docker run -u $(shell id -u) --rm -v $(shell pwd):/awx_devel/:Z --workdir=/awx_devel $(DEVEL_IMAGE_NAME) $(AWX_DOCKER_CMD)
|
||||
|
||||
## Builds image and runs AWX_DOCKER_CMD in it, mainly for .github checks.
|
||||
github_ci_runner: github_ci_setup docker-runner
|
||||
|
||||
test_collection:
|
||||
rm -f $(shell ls -d $(VENV_BASE)/awx/lib/python* | head -n 1)/no-global-site-packages.txt
|
||||
if [ "$(VENV_BASE)" ]; then \
|
||||
|
||||
@@ -67,7 +67,6 @@ else:
|
||||
from django.db import connection
|
||||
|
||||
if HAS_DJANGO is True:
|
||||
|
||||
# See upgrade blocker note in requirements/README.md
|
||||
try:
|
||||
names_digest('foo', 'bar', 'baz', length=8)
|
||||
|
||||
@@ -80,7 +80,6 @@ class VerbatimField(serializers.Field):
|
||||
|
||||
|
||||
class OAuth2ProviderField(fields.DictField):
|
||||
|
||||
default_error_messages = {'invalid_key_names': _('Invalid key names: {invalid_key_names}')}
|
||||
valid_key_names = {'ACCESS_TOKEN_EXPIRE_SECONDS', 'AUTHORIZATION_CODE_EXPIRE_SECONDS', 'REFRESH_TOKEN_EXPIRE_SECONDS'}
|
||||
child = fields.IntegerField(min_value=1)
|
||||
|
||||
@@ -160,7 +160,6 @@ class FieldLookupBackend(BaseFilterBackend):
|
||||
NO_DUPLICATES_ALLOW_LIST = (CharField, IntegerField, BooleanField, TextField)
|
||||
|
||||
def get_fields_from_lookup(self, model, lookup):
|
||||
|
||||
if '__' in lookup and lookup.rsplit('__', 1)[-1] in self.SUPPORTED_LOOKUPS:
|
||||
path, suffix = lookup.rsplit('__', 1)
|
||||
else:
|
||||
|
||||
@@ -135,7 +135,6 @@ def get_default_schema():
|
||||
|
||||
|
||||
class APIView(views.APIView):
|
||||
|
||||
schema = get_default_schema()
|
||||
versioning_class = URLPathVersioning
|
||||
|
||||
@@ -800,7 +799,6 @@ class RetrieveUpdateDestroyAPIView(RetrieveUpdateAPIView, DestroyAPIView):
|
||||
|
||||
|
||||
class ResourceAccessList(ParentMixin, ListAPIView):
|
||||
|
||||
serializer_class = ResourceAccessListElementSerializer
|
||||
ordering = ('username',)
|
||||
|
||||
@@ -823,7 +821,6 @@ def trigger_delayed_deep_copy(*args, **kwargs):
|
||||
|
||||
|
||||
class CopyAPIView(GenericAPIView):
|
||||
|
||||
serializer_class = CopySerializer
|
||||
permission_classes = (AllowAny,)
|
||||
copy_return_serializer_class = None
|
||||
|
||||
@@ -128,7 +128,7 @@ class Metadata(metadata.SimpleMetadata):
|
||||
# Special handling of notification configuration where the required properties
|
||||
# are conditional on the type selected.
|
||||
if field.field_name == 'notification_configuration':
|
||||
for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:
|
||||
for notification_type_name, notification_tr_name, notification_type_class in NotificationTemplate.NOTIFICATION_TYPES:
|
||||
field_info[notification_type_name] = notification_type_class.init_parameters
|
||||
|
||||
# Special handling of notification messages where the required properties
|
||||
@@ -138,7 +138,7 @@ class Metadata(metadata.SimpleMetadata):
|
||||
except (AttributeError, KeyError):
|
||||
view_model = None
|
||||
if view_model == NotificationTemplate and field.field_name == 'messages':
|
||||
for (notification_type_name, notification_tr_name, notification_type_class) in NotificationTemplate.NOTIFICATION_TYPES:
|
||||
for notification_type_name, notification_tr_name, notification_type_class in NotificationTemplate.NOTIFICATION_TYPES:
|
||||
field_info[notification_type_name] = notification_type_class.default_messages
|
||||
|
||||
# Update type of fields returned...
|
||||
|
||||
@@ -24,7 +24,6 @@ class DisabledPaginator(DjangoPaginator):
|
||||
|
||||
|
||||
class Pagination(pagination.PageNumberPagination):
|
||||
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = settings.MAX_PAGE_SIZE
|
||||
count_disabled = False
|
||||
|
||||
@@ -22,7 +22,6 @@ class SurrogateEncoder(encoders.JSONEncoder):
|
||||
|
||||
|
||||
class DefaultJSONRenderer(renderers.JSONRenderer):
|
||||
|
||||
encoder_class = SurrogateEncoder
|
||||
|
||||
|
||||
@@ -95,7 +94,6 @@ class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer):
|
||||
|
||||
|
||||
class PlainTextRenderer(renderers.BaseRenderer):
|
||||
|
||||
media_type = 'text/plain'
|
||||
format = 'txt'
|
||||
|
||||
@@ -106,18 +104,15 @@ class PlainTextRenderer(renderers.BaseRenderer):
|
||||
|
||||
|
||||
class DownloadTextRenderer(PlainTextRenderer):
|
||||
|
||||
format = "txt_download"
|
||||
|
||||
|
||||
class AnsiTextRenderer(PlainTextRenderer):
|
||||
|
||||
media_type = 'text/plain'
|
||||
format = 'ansi'
|
||||
|
||||
|
||||
class AnsiDownloadRenderer(PlainTextRenderer):
|
||||
|
||||
format = "ansi_download"
|
||||
|
||||
|
||||
|
||||
@@ -200,7 +200,6 @@ def reverse_gfk(content_object, request):
|
||||
|
||||
|
||||
class CopySerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField()
|
||||
|
||||
def validate(self, attrs):
|
||||
@@ -432,7 +431,6 @@ class BaseSerializer(serializers.ModelSerializer, metaclass=BaseSerializerMetacl
|
||||
continue
|
||||
summary_fields[fk] = OrderedDict()
|
||||
for field in related_fields:
|
||||
|
||||
fval = getattr(fkval, field, None)
|
||||
|
||||
if fval is None and field == 'type':
|
||||
@@ -930,7 +928,6 @@ class UnifiedJobListSerializer(UnifiedJobSerializer):
|
||||
|
||||
|
||||
class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
|
||||
|
||||
result_stdout = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -944,7 +941,6 @@ class UnifiedJobStdoutSerializer(UnifiedJobSerializer):
|
||||
|
||||
|
||||
class UserSerializer(BaseSerializer):
|
||||
|
||||
password = serializers.CharField(required=False, default='', write_only=True, help_text=_('Write-only field used to change the password.'))
|
||||
ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True)
|
||||
external_account = serializers.SerializerMethodField(help_text=_('Set if the account is managed by an external service'))
|
||||
@@ -1104,7 +1100,6 @@ class UserActivityStreamSerializer(UserSerializer):
|
||||
|
||||
|
||||
class BaseOAuth2TokenSerializer(BaseSerializer):
|
||||
|
||||
refresh_token = serializers.SerializerMethodField()
|
||||
token = serializers.SerializerMethodField()
|
||||
ALLOWED_SCOPES = ['read', 'write']
|
||||
@@ -1222,7 +1217,6 @@ class UserPersonalTokenSerializer(BaseOAuth2TokenSerializer):
|
||||
|
||||
|
||||
class OAuth2ApplicationSerializer(BaseSerializer):
|
||||
|
||||
show_capabilities = ['edit', 'delete']
|
||||
|
||||
class Meta:
|
||||
@@ -1457,7 +1451,6 @@ class ExecutionEnvironmentSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
|
||||
|
||||
status = serializers.ChoiceField(choices=Project.PROJECT_STATUS_CHOICES, read_only=True)
|
||||
last_update_failed = serializers.BooleanField(read_only=True)
|
||||
last_updated = serializers.DateTimeField(read_only=True)
|
||||
@@ -1548,7 +1541,6 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
|
||||
|
||||
|
||||
class ProjectPlaybooksSerializer(ProjectSerializer):
|
||||
|
||||
playbooks = serializers.SerializerMethodField(help_text=_('Array of playbooks available within this project.'))
|
||||
|
||||
class Meta:
|
||||
@@ -1566,7 +1558,6 @@ class ProjectPlaybooksSerializer(ProjectSerializer):
|
||||
|
||||
|
||||
class ProjectInventoriesSerializer(ProjectSerializer):
|
||||
|
||||
inventory_files = serializers.ReadOnlyField(help_text=_('Array of inventory files and directories available within this project, ' 'not comprehensive.'))
|
||||
|
||||
class Meta:
|
||||
@@ -1581,7 +1572,6 @@ class ProjectInventoriesSerializer(ProjectSerializer):
|
||||
|
||||
|
||||
class ProjectUpdateViewSerializer(ProjectSerializer):
|
||||
|
||||
can_update = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -1611,7 +1601,6 @@ class ProjectUpdateSerializer(UnifiedJobSerializer, ProjectOptionsSerializer):
|
||||
|
||||
|
||||
class ProjectUpdateDetailSerializer(ProjectUpdateSerializer):
|
||||
|
||||
playbook_counts = serializers.SerializerMethodField(help_text=_('A count of all plays and tasks for the job run.'))
|
||||
|
||||
class Meta:
|
||||
@@ -1634,7 +1623,6 @@ class ProjectUpdateListSerializer(ProjectUpdateSerializer, UnifiedJobListSeriali
|
||||
|
||||
|
||||
class ProjectUpdateCancelSerializer(ProjectUpdateSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -1692,13 +1680,8 @@ class InventorySerializer(LabelsListMixin, BaseSerializerWithVariables):
|
||||
res.update(
|
||||
dict(
|
||||
hosts=self.reverse('api:inventory_hosts_list', kwargs={'pk': obj.pk}),
|
||||
groups=self.reverse('api:inventory_groups_list', kwargs={'pk': obj.pk}),
|
||||
root_groups=self.reverse('api:inventory_root_groups_list', kwargs={'pk': obj.pk}),
|
||||
variable_data=self.reverse('api:inventory_variable_data', kwargs={'pk': obj.pk}),
|
||||
script=self.reverse('api:inventory_script_view', kwargs={'pk': obj.pk}),
|
||||
tree=self.reverse('api:inventory_tree_view', kwargs={'pk': obj.pk}),
|
||||
inventory_sources=self.reverse('api:inventory_inventory_sources_list', kwargs={'pk': obj.pk}),
|
||||
update_inventory_sources=self.reverse('api:inventory_inventory_sources_update', kwargs={'pk': obj.pk}),
|
||||
activity_stream=self.reverse('api:inventory_activity_stream_list', kwargs={'pk': obj.pk}),
|
||||
job_templates=self.reverse('api:inventory_job_template_list', kwargs={'pk': obj.pk}),
|
||||
ad_hoc_commands=self.reverse('api:inventory_ad_hoc_commands_list', kwargs={'pk': obj.pk}),
|
||||
@@ -1709,8 +1692,18 @@ class InventorySerializer(LabelsListMixin, BaseSerializerWithVariables):
|
||||
labels=self.reverse('api:inventory_label_list', kwargs={'pk': obj.pk}),
|
||||
)
|
||||
)
|
||||
if obj.kind in ('', 'constructed'):
|
||||
# links not relevant for the "old" smart inventory
|
||||
res['groups'] = self.reverse('api:inventory_groups_list', kwargs={'pk': obj.pk})
|
||||
res['root_groups'] = self.reverse('api:inventory_root_groups_list', kwargs={'pk': obj.pk})
|
||||
res['update_inventory_sources'] = self.reverse('api:inventory_inventory_sources_update', kwargs={'pk': obj.pk})
|
||||
res['inventory_sources'] = self.reverse('api:inventory_inventory_sources_list', kwargs={'pk': obj.pk})
|
||||
res['tree'] = self.reverse('api:inventory_tree_view', kwargs={'pk': obj.pk})
|
||||
if obj.organization:
|
||||
res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk})
|
||||
if obj.kind == 'constructed':
|
||||
res['input_inventories'] = self.reverse('api:inventory_input_inventories', kwargs={'pk': obj.pk})
|
||||
res['constructed_url'] = self.reverse('api:constructed_inventory_detail', kwargs={'pk': obj.pk})
|
||||
return res
|
||||
|
||||
def to_representation(self, obj):
|
||||
@@ -1752,6 +1745,91 @@ class InventorySerializer(LabelsListMixin, BaseSerializerWithVariables):
|
||||
return super(InventorySerializer, self).validate(attrs)
|
||||
|
||||
|
||||
class ConstructedFieldMixin(serializers.Field):
|
||||
def get_attribute(self, instance):
|
||||
if not hasattr(instance, '_constructed_inv_src'):
|
||||
instance._constructed_inv_src = instance.inventory_sources.first()
|
||||
inv_src = instance._constructed_inv_src
|
||||
return super().get_attribute(inv_src) # yoink
|
||||
|
||||
|
||||
class ConstructedCharField(ConstructedFieldMixin, serializers.CharField):
|
||||
pass
|
||||
|
||||
|
||||
class ConstructedIntegerField(ConstructedFieldMixin, serializers.IntegerField):
|
||||
pass
|
||||
|
||||
|
||||
class ConstructedInventorySerializer(InventorySerializer):
|
||||
source_vars = ConstructedCharField(
|
||||
required=False,
|
||||
default=None,
|
||||
allow_blank=True,
|
||||
help_text=_('The source_vars for the related auto-created inventory source, special to constructed inventory.'),
|
||||
)
|
||||
update_cache_timeout = ConstructedIntegerField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
min_value=0,
|
||||
default=None,
|
||||
help_text=_('The cache timeout for the related auto-created inventory source, special to constructed inventory'),
|
||||
)
|
||||
limit = ConstructedCharField(
|
||||
required=False,
|
||||
default=None,
|
||||
allow_blank=True,
|
||||
help_text=_('The limit to restrict the returned hosts for the related auto-created inventory source, special to constructed inventory.'),
|
||||
)
|
||||
verbosity = ConstructedIntegerField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
min_value=0,
|
||||
max_value=2,
|
||||
default=None,
|
||||
help_text=_('The verbosity level for the related auto-created inventory source, special to constructed inventory'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Inventory
|
||||
fields = ('*', '-host_filter', 'source_vars', 'update_cache_timeout', 'limit', 'verbosity')
|
||||
read_only_fields = ('*', 'kind')
|
||||
|
||||
def pop_inv_src_data(self, data):
|
||||
inv_src_data = {}
|
||||
for field in ('source_vars', 'update_cache_timeout', 'limit', 'verbosity'):
|
||||
if field in data:
|
||||
# values always need to be removed, as they are not valid for Inventory model
|
||||
value = data.pop(field)
|
||||
# null is not valid for any of those fields, taken as not-provided
|
||||
if value is not None:
|
||||
inv_src_data[field] = value
|
||||
return inv_src_data
|
||||
|
||||
def apply_inv_src_data(self, inventory, inv_src_data):
|
||||
if inv_src_data:
|
||||
update_fields = []
|
||||
inv_src = inventory.inventory_sources.first()
|
||||
for field, value in inv_src_data.items():
|
||||
setattr(inv_src, field, value)
|
||||
update_fields.append(field)
|
||||
if update_fields:
|
||||
inv_src.save(update_fields=update_fields)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['kind'] = 'constructed'
|
||||
inv_src_data = self.pop_inv_src_data(validated_data)
|
||||
inventory = super().create(validated_data)
|
||||
self.apply_inv_src_data(inventory, inv_src_data)
|
||||
return inventory
|
||||
|
||||
def update(self, obj, validated_data):
|
||||
inv_src_data = self.pop_inv_src_data(validated_data)
|
||||
obj = super().update(obj, validated_data)
|
||||
self.apply_inv_src_data(obj, inv_src_data)
|
||||
return obj
|
||||
|
||||
|
||||
class InventoryScriptSerializer(InventorySerializer):
|
||||
class Meta:
|
||||
fields = ()
|
||||
@@ -1805,6 +1883,8 @@ class HostSerializer(BaseSerializerWithVariables):
|
||||
ansible_facts=self.reverse('api:host_ansible_facts_detail', kwargs={'pk': obj.pk}),
|
||||
)
|
||||
)
|
||||
if obj.inventory.kind == 'constructed':
|
||||
res['original_host'] = self.reverse('api:host_detail', kwargs={'pk': obj.instance_id})
|
||||
if obj.inventory:
|
||||
res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk})
|
||||
if obj.last_job:
|
||||
@@ -1861,8 +1941,8 @@ class HostSerializer(BaseSerializerWithVariables):
|
||||
return value
|
||||
|
||||
def validate_inventory(self, value):
|
||||
if value.kind == 'smart':
|
||||
raise serializers.ValidationError({"detail": _("Cannot create Host for Smart Inventory")})
|
||||
if value.kind in ('constructed', 'smart'):
|
||||
raise serializers.ValidationError({"detail": _("Cannot create Host for Smart or Constructed Inventories")})
|
||||
return value
|
||||
|
||||
def validate_variables(self, value):
|
||||
@@ -1960,8 +2040,8 @@ class GroupSerializer(BaseSerializerWithVariables):
|
||||
return value
|
||||
|
||||
def validate_inventory(self, value):
|
||||
if value.kind == 'smart':
|
||||
raise serializers.ValidationError({"detail": _("Cannot create Group for Smart Inventory")})
|
||||
if value.kind in ('constructed', 'smart'):
|
||||
raise serializers.ValidationError({"detail": _("Cannot create Group for Smart or Constructed Inventories")})
|
||||
return value
|
||||
|
||||
def to_representation(self, obj):
|
||||
@@ -1972,7 +2052,6 @@ class GroupSerializer(BaseSerializerWithVariables):
|
||||
|
||||
|
||||
class GroupTreeSerializer(GroupSerializer):
|
||||
|
||||
children = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -2036,6 +2115,7 @@ class InventorySourceOptionsSerializer(BaseSerializer):
|
||||
'custom_virtualenv',
|
||||
'timeout',
|
||||
'verbosity',
|
||||
'limit',
|
||||
)
|
||||
read_only_fields = ('*', 'custom_virtualenv')
|
||||
|
||||
@@ -2070,7 +2150,6 @@ class InventorySourceOptionsSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOptionsSerializer):
|
||||
|
||||
status = serializers.ChoiceField(choices=InventorySource.INVENTORY_SOURCE_STATUS_CHOICES, read_only=True)
|
||||
last_update_failed = serializers.BooleanField(read_only=True)
|
||||
last_updated = serializers.DateTimeField(read_only=True)
|
||||
@@ -2143,8 +2222,8 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
||||
return value
|
||||
|
||||
def validate_inventory(self, value):
|
||||
if value and value.kind == 'smart':
|
||||
raise serializers.ValidationError({"detail": _("Cannot create Inventory Source for Smart Inventory")})
|
||||
if value and value.kind in ('constructed', 'smart'):
|
||||
raise serializers.ValidationError({"detail": _("Cannot create Inventory Source for Smart or Constructed Inventories")})
|
||||
return value
|
||||
|
||||
# TODO: remove when old 'credential' fields are removed
|
||||
@@ -2165,6 +2244,8 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
||||
obj = super(InventorySourceSerializer, self).update(obj, validated_data)
|
||||
if deprecated_fields:
|
||||
self._update_deprecated_fields(deprecated_fields, obj)
|
||||
if obj.source == 'constructed':
|
||||
raise serializers.ValidationError({'error': _("Cannot edit source of type constructed.")})
|
||||
return obj
|
||||
|
||||
# TODO: remove when old 'credential' fields are removed
|
||||
@@ -2191,6 +2272,8 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
||||
if get_field_from_model_or_attrs('source') == 'scm':
|
||||
if ('source' in attrs or 'source_project' in attrs) and get_field_from_model_or_attrs('source_project') is None:
|
||||
raise serializers.ValidationError({"source_project": _("Project required for scm type sources.")})
|
||||
elif (get_field_from_model_or_attrs('source') == 'constructed') and (self.instance and self.instance.source != 'constructed'):
|
||||
raise serializers.ValidationError({"Error": _('constructed not a valid source for inventory')})
|
||||
else:
|
||||
redundant_scm_fields = list(filter(lambda x: attrs.get(x, None), ['source_project', 'source_path']))
|
||||
if redundant_scm_fields:
|
||||
@@ -2215,7 +2298,6 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
||||
|
||||
|
||||
class InventorySourceUpdateSerializer(InventorySourceSerializer):
|
||||
|
||||
can_update = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -2232,7 +2314,6 @@ class InventorySourceUpdateSerializer(InventorySourceSerializer):
|
||||
|
||||
|
||||
class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSerializer):
|
||||
|
||||
custom_virtualenv = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
@@ -2273,7 +2354,6 @@ class InventoryUpdateSerializer(UnifiedJobSerializer, InventorySourceOptionsSeri
|
||||
|
||||
|
||||
class InventoryUpdateDetailSerializer(InventoryUpdateSerializer):
|
||||
|
||||
source_project = serializers.SerializerMethodField(help_text=_('The project used for this job.'), method_name='get_source_project_id')
|
||||
|
||||
class Meta:
|
||||
@@ -2324,7 +2404,6 @@ class InventoryUpdateListSerializer(InventoryUpdateSerializer, UnifiedJobListSer
|
||||
|
||||
|
||||
class InventoryUpdateCancelSerializer(InventoryUpdateSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -2682,7 +2761,6 @@ class CredentialSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class CredentialSerializerCreate(CredentialSerializer):
|
||||
|
||||
user = serializers.PrimaryKeyRelatedField(
|
||||
queryset=User.objects.all(),
|
||||
required=False,
|
||||
@@ -3037,7 +3115,6 @@ class JobTemplateWithSpecSerializer(JobTemplateSerializer):
|
||||
|
||||
|
||||
class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
|
||||
passwords_needed_to_start = serializers.ReadOnlyField()
|
||||
artifacts = serializers.SerializerMethodField()
|
||||
|
||||
@@ -3120,7 +3197,6 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
||||
|
||||
|
||||
class JobDetailSerializer(JobSerializer):
|
||||
|
||||
playbook_counts = serializers.SerializerMethodField(help_text=_('A count of all plays and tasks for the job run.'))
|
||||
custom_virtualenv = serializers.ReadOnlyField()
|
||||
|
||||
@@ -3138,7 +3214,6 @@ class JobDetailSerializer(JobSerializer):
|
||||
|
||||
|
||||
class JobCancelSerializer(BaseSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -3147,7 +3222,6 @@ class JobCancelSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class JobRelaunchSerializer(BaseSerializer):
|
||||
|
||||
passwords_needed_to_start = serializers.SerializerMethodField()
|
||||
retry_counts = serializers.SerializerMethodField()
|
||||
hosts = serializers.ChoiceField(
|
||||
@@ -3207,7 +3281,6 @@ class JobRelaunchSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class JobCreateScheduleSerializer(LabelsListMixin, BaseSerializer):
|
||||
|
||||
can_schedule = serializers.SerializerMethodField()
|
||||
prompts = serializers.SerializerMethodField()
|
||||
|
||||
@@ -3333,7 +3406,6 @@ class AdHocCommandDetailSerializer(AdHocCommandSerializer):
|
||||
|
||||
|
||||
class AdHocCommandCancelSerializer(AdHocCommandSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -3372,7 +3444,6 @@ class SystemJobTemplateSerializer(UnifiedJobTemplateSerializer):
|
||||
|
||||
|
||||
class SystemJobSerializer(UnifiedJobSerializer):
|
||||
|
||||
result_stdout = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
@@ -3399,7 +3470,6 @@ class SystemJobSerializer(UnifiedJobSerializer):
|
||||
|
||||
|
||||
class SystemJobCancelSerializer(SystemJobSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -3564,7 +3634,6 @@ class WorkflowJobListSerializer(WorkflowJobSerializer, UnifiedJobListSerializer)
|
||||
|
||||
|
||||
class WorkflowJobCancelSerializer(WorkflowJobSerializer):
|
||||
|
||||
can_cancel = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -3578,7 +3647,6 @@ class WorkflowApprovalViewSerializer(UnifiedJobSerializer):
|
||||
|
||||
|
||||
class WorkflowApprovalSerializer(UnifiedJobSerializer):
|
||||
|
||||
can_approve_or_deny = serializers.SerializerMethodField()
|
||||
approval_expiration = serializers.SerializerMethodField()
|
||||
timed_out = serializers.ReadOnlyField()
|
||||
@@ -3973,7 +4041,6 @@ class JobHostSummarySerializer(BaseSerializer):
|
||||
|
||||
|
||||
class JobEventSerializer(BaseSerializer):
|
||||
|
||||
event_display = serializers.CharField(source='get_event_display2', read_only=True)
|
||||
event_level = serializers.IntegerField(read_only=True)
|
||||
|
||||
@@ -4069,7 +4136,6 @@ class ProjectUpdateEventSerializer(JobEventSerializer):
|
||||
|
||||
|
||||
class AdHocCommandEventSerializer(BaseSerializer):
|
||||
|
||||
event_display = serializers.CharField(source='get_event_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
@@ -4351,7 +4417,6 @@ class JobLaunchSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class WorkflowJobLaunchSerializer(BaseSerializer):
|
||||
|
||||
can_start_without_user_input = serializers.BooleanField(read_only=True)
|
||||
defaults = serializers.SerializerMethodField()
|
||||
variables_needed_to_start = serializers.ReadOnlyField()
|
||||
@@ -4408,7 +4473,6 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
|
||||
return False
|
||||
|
||||
def get_defaults(self, obj):
|
||||
|
||||
defaults_dict = {}
|
||||
for field_name in WorkflowJobTemplate.get_ask_mapping().keys():
|
||||
if field_name == 'inventory':
|
||||
@@ -4425,7 +4489,6 @@ class WorkflowJobLaunchSerializer(BaseSerializer):
|
||||
return dict(name=obj.name, id=obj.id, description=obj.description)
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
template = self.instance
|
||||
|
||||
accepted, rejected, errors = template._accept_or_ignore_job_kwargs(**attrs)
|
||||
@@ -4666,7 +4729,6 @@ class NotificationTemplateSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class NotificationSerializer(BaseSerializer):
|
||||
|
||||
body = serializers.SerializerMethodField(help_text=_('Notification body'))
|
||||
|
||||
class Meta:
|
||||
@@ -5038,7 +5100,6 @@ class InstanceHealthCheckSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class InstanceGroupSerializer(BaseSerializer):
|
||||
|
||||
show_capabilities = ['edit', 'delete']
|
||||
capacity = serializers.SerializerMethodField()
|
||||
consumed_capacity = serializers.SerializerMethodField()
|
||||
@@ -5225,7 +5286,6 @@ class InstanceGroupSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class ActivityStreamSerializer(BaseSerializer):
|
||||
|
||||
changes = serializers.SerializerMethodField()
|
||||
object_association = serializers.SerializerMethodField(help_text=_("When present, shows the field name of the role or relationship that changed."))
|
||||
object_type = serializers.SerializerMethodField(help_text=_("When present, shows the model on which the role or relationship was defined."))
|
||||
|
||||
@@ -6,7 +6,10 @@ from django.urls import re_path
|
||||
from awx.api.views.inventory import (
|
||||
InventoryList,
|
||||
InventoryDetail,
|
||||
ConstructedInventoryDetail,
|
||||
ConstructedInventoryList,
|
||||
InventoryActivityStreamList,
|
||||
InventoryInputInventoriesList,
|
||||
InventoryJobTemplateList,
|
||||
InventoryAccessList,
|
||||
InventoryObjectRolesList,
|
||||
@@ -37,6 +40,7 @@ urls = [
|
||||
re_path(r'^(?P<pk>[0-9]+)/script/$', InventoryScriptView.as_view(), name='inventory_script_view'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/tree/$', InventoryTreeView.as_view(), name='inventory_tree_view'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/inventory_sources/$', InventoryInventorySourcesList.as_view(), name='inventory_inventory_sources_list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/input_inventories/$', InventoryInputInventoriesList.as_view(), name='inventory_input_inventories'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/update_inventory_sources/$', InventoryInventorySourcesUpdate.as_view(), name='inventory_inventory_sources_update'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/activity_stream/$', InventoryActivityStreamList.as_view(), name='inventory_activity_stream_list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/job_templates/$', InventoryJobTemplateList.as_view(), name='inventory_job_template_list'),
|
||||
@@ -48,4 +52,10 @@ urls = [
|
||||
re_path(r'^(?P<pk>[0-9]+)/copy/$', InventoryCopy.as_view(), name='inventory_copy'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
# Constructed inventory special views
|
||||
constructed_inventory_urls = [
|
||||
re_path(r'^$', ConstructedInventoryList.as_view(), name='constructed_inventory_list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/$', ConstructedInventoryDetail.as_view(), name='constructed_inventory_detail'),
|
||||
]
|
||||
|
||||
__all__ = ['urls', 'constructed_inventory_urls']
|
||||
|
||||
@@ -39,7 +39,7 @@ from .organization import urls as organization_urls
|
||||
from .user import urls as user_urls
|
||||
from .project import urls as project_urls
|
||||
from .project_update import urls as project_update_urls
|
||||
from .inventory import urls as inventory_urls
|
||||
from .inventory import urls as inventory_urls, constructed_inventory_urls
|
||||
from .execution_environments import urls as execution_environment_urls
|
||||
from .team import urls as team_urls
|
||||
from .host import urls as host_urls
|
||||
@@ -110,6 +110,7 @@ v2_urls = [
|
||||
re_path(r'^project_updates/', include(project_update_urls)),
|
||||
re_path(r'^teams/', include(team_urls)),
|
||||
re_path(r'^inventories/', include(inventory_urls)),
|
||||
re_path(r'^constructed_inventories/', include(constructed_inventory_urls)),
|
||||
re_path(r'^hosts/', include(host_urls)),
|
||||
re_path(r'^groups/', include(group_urls)),
|
||||
re_path(r'^inventory_sources/', include(inventory_source_urls)),
|
||||
|
||||
@@ -33,7 +33,6 @@ class HostnameRegexValidator(RegexValidator):
|
||||
return f"regex={self.regex}, message={self.message}, code={self.code}, inverse_match={self.inverse_match}, flags={self.flags}"
|
||||
|
||||
def __validate(self, value):
|
||||
|
||||
if ' ' in value:
|
||||
return False, ValidationError("whitespaces in hostnames are illegal")
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@ from rest_framework import status
|
||||
# Red Hat has an OID namespace (RHANANA). Receptor has its own designation under that.
|
||||
RECEPTOR_OID = "1.3.6.1.4.1.2312.19.1"
|
||||
|
||||
|
||||
# generate install bundle for the instance
|
||||
# install bundle directory structure
|
||||
# ├── install_receptor.yml (playbook)
|
||||
@@ -40,7 +41,6 @@ RECEPTOR_OID = "1.3.6.1.4.1.2312.19.1"
|
||||
# │ └── work-public-key.pem
|
||||
# └── requirements.yml
|
||||
class InstanceInstallBundle(GenericAPIView):
|
||||
|
||||
name = _('Install Bundle')
|
||||
model = models.Instance
|
||||
serializer_class = serializers.InstanceSerializer
|
||||
|
||||
@@ -14,6 +14,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework import serializers
|
||||
|
||||
# AWX
|
||||
from awx.main.models import ActivityStream, Inventory, JobTemplate, Role, User, InstanceGroup, InventoryUpdateEvent, InventoryUpdate
|
||||
@@ -31,6 +32,7 @@ from awx.api.views.labels import LabelSubListCreateAttachDetachView
|
||||
|
||||
from awx.api.serializers import (
|
||||
InventorySerializer,
|
||||
ConstructedInventorySerializer,
|
||||
ActivityStreamSerializer,
|
||||
RoleSerializer,
|
||||
InstanceGroupSerializer,
|
||||
@@ -46,7 +48,6 @@ logger = logging.getLogger('awx.api.views.organization')
|
||||
|
||||
|
||||
class InventoryUpdateEventsList(SubListAPIView):
|
||||
|
||||
model = InventoryUpdateEvent
|
||||
serializer_class = InventoryUpdateEventSerializer
|
||||
parent_model = InventoryUpdate
|
||||
@@ -66,13 +67,11 @@ class InventoryUpdateEventsList(SubListAPIView):
|
||||
|
||||
|
||||
class InventoryList(ListCreateAPIView):
|
||||
|
||||
model = Inventory
|
||||
serializer_class = InventorySerializer
|
||||
|
||||
|
||||
class InventoryDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = Inventory
|
||||
serializer_class = InventorySerializer
|
||||
|
||||
@@ -82,7 +81,9 @@ class InventoryDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIVie
|
||||
|
||||
# Do not allow changes to an Inventory kind.
|
||||
if kind is not None and obj.kind != kind:
|
||||
return Response(dict(error=_('You cannot turn a regular inventory into a "smart" inventory.')), status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
return Response(
|
||||
dict(error=_('You cannot turn a regular inventory into a "smart" or "constructed" inventory.')), status=status.HTTP_405_METHOD_NOT_ALLOWED
|
||||
)
|
||||
return super(InventoryDetail, self).update(request, *args, **kwargs)
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
@@ -97,8 +98,30 @@ class InventoryDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIVie
|
||||
return Response(dict(error=_("{0}".format(e))), status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class InventoryActivityStreamList(SubListAPIView):
|
||||
class ConstructedInventoryDetail(InventoryDetail):
|
||||
serializer_class = ConstructedInventorySerializer
|
||||
|
||||
|
||||
class ConstructedInventoryList(InventoryList):
|
||||
serializer_class = ConstructedInventorySerializer
|
||||
|
||||
def get_queryset(self):
|
||||
r = super().get_queryset()
|
||||
return r.filter(kind='constructed')
|
||||
|
||||
|
||||
class InventoryInputInventoriesList(SubListAttachDetachAPIView):
|
||||
model = Inventory
|
||||
serializer_class = InventorySerializer
|
||||
parent_model = Inventory
|
||||
relationship = 'input_inventories'
|
||||
|
||||
def is_valid_relation(self, parent, sub, created=False):
|
||||
if sub.kind == 'constructed':
|
||||
raise serializers.ValidationError({'error': 'You cannot add a constructed inventory to another constructed inventory.'})
|
||||
|
||||
|
||||
class InventoryActivityStreamList(SubListAPIView):
|
||||
model = ActivityStream
|
||||
serializer_class = ActivityStreamSerializer
|
||||
parent_model = Inventory
|
||||
@@ -113,7 +136,6 @@ class InventoryActivityStreamList(SubListAPIView):
|
||||
|
||||
|
||||
class InventoryInstanceGroupsList(SubListAttachDetachAPIView):
|
||||
|
||||
model = InstanceGroup
|
||||
serializer_class = InstanceGroupSerializer
|
||||
parent_model = Inventory
|
||||
@@ -121,13 +143,11 @@ class InventoryInstanceGroupsList(SubListAttachDetachAPIView):
|
||||
|
||||
|
||||
class InventoryAccessList(ResourceAccessList):
|
||||
|
||||
model = User # needs to be User for AccessLists's
|
||||
parent_model = Inventory
|
||||
|
||||
|
||||
class InventoryObjectRolesList(SubListAPIView):
|
||||
|
||||
model = Role
|
||||
serializer_class = RoleSerializer
|
||||
parent_model = Inventory
|
||||
@@ -140,7 +160,6 @@ class InventoryObjectRolesList(SubListAPIView):
|
||||
|
||||
|
||||
class InventoryJobTemplateList(SubListAPIView):
|
||||
|
||||
model = JobTemplate
|
||||
serializer_class = JobTemplateSerializer
|
||||
parent_model = Inventory
|
||||
@@ -154,11 +173,9 @@ class InventoryJobTemplateList(SubListAPIView):
|
||||
|
||||
|
||||
class InventoryLabelList(LabelSubListCreateAttachDetachView):
|
||||
|
||||
parent_model = Inventory
|
||||
|
||||
|
||||
class InventoryCopy(CopyAPIView):
|
||||
|
||||
model = Inventory
|
||||
copy_return_serializer_class = InventorySerializer
|
||||
|
||||
@@ -59,13 +59,11 @@ class LabelSubListCreateAttachDetachView(SubListCreateAttachDetachAPIView):
|
||||
|
||||
|
||||
class LabelDetail(RetrieveUpdateAPIView):
|
||||
|
||||
model = Label
|
||||
serializer_class = LabelSerializer
|
||||
|
||||
|
||||
class LabelList(ListCreateAPIView):
|
||||
|
||||
name = _("Labels")
|
||||
model = Label
|
||||
serializer_class = LabelSerializer
|
||||
|
||||
@@ -10,13 +10,11 @@ from awx.main.models import InstanceLink, Instance
|
||||
|
||||
|
||||
class MeshVisualizer(APIView):
|
||||
|
||||
name = _("Mesh Visualizer")
|
||||
permission_classes = (IsSystemAdminOrAuditor,)
|
||||
swagger_topic = "System Configuration"
|
||||
|
||||
def get(self, request, format=None):
|
||||
|
||||
data = {
|
||||
'nodes': InstanceNodeSerializer(Instance.objects.all(), many=True).data,
|
||||
'links': InstanceLinkSerializer(InstanceLink.objects.select_related('target', 'source'), many=True).data,
|
||||
|
||||
@@ -27,7 +27,6 @@ logger = logging.getLogger('awx.analytics')
|
||||
|
||||
|
||||
class MetricsView(APIView):
|
||||
|
||||
name = _('Metrics')
|
||||
swagger_topic = 'Metrics'
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ logger = logging.getLogger('awx.api.views.organization')
|
||||
|
||||
|
||||
class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
|
||||
|
||||
model = Organization
|
||||
serializer_class = OrganizationSerializer
|
||||
|
||||
@@ -70,7 +69,6 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
|
||||
|
||||
|
||||
class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = Organization
|
||||
serializer_class = OrganizationSerializer
|
||||
|
||||
@@ -106,7 +104,6 @@ class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPI
|
||||
|
||||
|
||||
class OrganizationInventoriesList(SubListAPIView):
|
||||
|
||||
model = Inventory
|
||||
serializer_class = InventorySerializer
|
||||
parent_model = Organization
|
||||
@@ -114,7 +111,6 @@ class OrganizationInventoriesList(SubListAPIView):
|
||||
|
||||
|
||||
class OrganizationUsersList(BaseUsersList):
|
||||
|
||||
model = User
|
||||
serializer_class = UserSerializer
|
||||
parent_model = Organization
|
||||
@@ -123,7 +119,6 @@ class OrganizationUsersList(BaseUsersList):
|
||||
|
||||
|
||||
class OrganizationAdminsList(BaseUsersList):
|
||||
|
||||
model = User
|
||||
serializer_class = UserSerializer
|
||||
parent_model = Organization
|
||||
@@ -132,7 +127,6 @@ class OrganizationAdminsList(BaseUsersList):
|
||||
|
||||
|
||||
class OrganizationProjectsList(SubListCreateAPIView):
|
||||
|
||||
model = Project
|
||||
serializer_class = ProjectSerializer
|
||||
parent_model = Organization
|
||||
@@ -140,7 +134,6 @@ class OrganizationProjectsList(SubListCreateAPIView):
|
||||
|
||||
|
||||
class OrganizationExecutionEnvironmentsList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = ExecutionEnvironment
|
||||
serializer_class = ExecutionEnvironmentSerializer
|
||||
parent_model = Organization
|
||||
@@ -150,7 +143,6 @@ class OrganizationExecutionEnvironmentsList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
|
||||
class OrganizationJobTemplatesList(SubListCreateAPIView):
|
||||
|
||||
model = JobTemplate
|
||||
serializer_class = JobTemplateSerializer
|
||||
parent_model = Organization
|
||||
@@ -158,7 +150,6 @@ class OrganizationJobTemplatesList(SubListCreateAPIView):
|
||||
|
||||
|
||||
class OrganizationWorkflowJobTemplatesList(SubListCreateAPIView):
|
||||
|
||||
model = WorkflowJobTemplate
|
||||
serializer_class = WorkflowJobTemplateSerializer
|
||||
parent_model = Organization
|
||||
@@ -166,7 +157,6 @@ class OrganizationWorkflowJobTemplatesList(SubListCreateAPIView):
|
||||
|
||||
|
||||
class OrganizationTeamsList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = Team
|
||||
serializer_class = TeamSerializer
|
||||
parent_model = Organization
|
||||
@@ -175,7 +165,6 @@ class OrganizationTeamsList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
|
||||
class OrganizationActivityStreamList(SubListAPIView):
|
||||
|
||||
model = ActivityStream
|
||||
serializer_class = ActivityStreamSerializer
|
||||
parent_model = Organization
|
||||
@@ -184,7 +173,6 @@ class OrganizationActivityStreamList(SubListAPIView):
|
||||
|
||||
|
||||
class OrganizationNotificationTemplatesList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = NotificationTemplate
|
||||
serializer_class = NotificationTemplateSerializer
|
||||
parent_model = Organization
|
||||
@@ -193,34 +181,28 @@ class OrganizationNotificationTemplatesList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
|
||||
class OrganizationNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
|
||||
|
||||
model = NotificationTemplate
|
||||
serializer_class = NotificationTemplateSerializer
|
||||
parent_model = Organization
|
||||
|
||||
|
||||
class OrganizationNotificationTemplatesStartedList(OrganizationNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_started'
|
||||
|
||||
|
||||
class OrganizationNotificationTemplatesErrorList(OrganizationNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_error'
|
||||
|
||||
|
||||
class OrganizationNotificationTemplatesSuccessList(OrganizationNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_success'
|
||||
|
||||
|
||||
class OrganizationNotificationTemplatesApprovalList(OrganizationNotificationTemplatesAnyList):
|
||||
|
||||
relationship = 'notification_templates_approvals'
|
||||
|
||||
|
||||
class OrganizationInstanceGroupsList(SubListAttachDetachAPIView):
|
||||
|
||||
model = InstanceGroup
|
||||
serializer_class = InstanceGroupSerializer
|
||||
parent_model = Organization
|
||||
@@ -228,7 +210,6 @@ class OrganizationInstanceGroupsList(SubListAttachDetachAPIView):
|
||||
|
||||
|
||||
class OrganizationGalaxyCredentialsList(SubListAttachDetachAPIView):
|
||||
|
||||
model = Credential
|
||||
serializer_class = CredentialSerializer
|
||||
parent_model = Organization
|
||||
@@ -240,13 +221,11 @@ class OrganizationGalaxyCredentialsList(SubListAttachDetachAPIView):
|
||||
|
||||
|
||||
class OrganizationAccessList(ResourceAccessList):
|
||||
|
||||
model = User # needs to be User for AccessLists's
|
||||
parent_model = Organization
|
||||
|
||||
|
||||
class OrganizationObjectRolesList(SubListAPIView):
|
||||
|
||||
model = Role
|
||||
serializer_class = RoleSerializer
|
||||
parent_model = Organization
|
||||
|
||||
@@ -36,7 +36,6 @@ logger = logging.getLogger('awx.api.views.root')
|
||||
|
||||
|
||||
class ApiRootView(APIView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
name = _('REST API')
|
||||
versioning_class = None
|
||||
@@ -59,7 +58,6 @@ class ApiRootView(APIView):
|
||||
|
||||
|
||||
class ApiOAuthAuthorizationRootView(APIView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
name = _("API OAuth 2 Authorization Root")
|
||||
versioning_class = None
|
||||
@@ -74,7 +72,6 @@ class ApiOAuthAuthorizationRootView(APIView):
|
||||
|
||||
|
||||
class ApiVersionRootView(APIView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
swagger_topic = 'Versioning'
|
||||
|
||||
@@ -101,6 +98,7 @@ class ApiVersionRootView(APIView):
|
||||
data['tokens'] = reverse('api:o_auth2_token_list', request=request)
|
||||
data['metrics'] = reverse('api:metrics_view', request=request)
|
||||
data['inventory'] = reverse('api:inventory_list', request=request)
|
||||
data['constructed_inventory'] = reverse('api:constructed_inventory_list', request=request)
|
||||
data['inventory_sources'] = reverse('api:inventory_source_list', request=request)
|
||||
data['inventory_updates'] = reverse('api:inventory_update_list', request=request)
|
||||
data['groups'] = reverse('api:group_list', request=request)
|
||||
@@ -172,7 +170,6 @@ class ApiV2PingView(APIView):
|
||||
|
||||
|
||||
class ApiV2SubscriptionView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
name = _('Subscriptions')
|
||||
swagger_topic = 'System Configuration'
|
||||
@@ -212,7 +209,6 @@ class ApiV2SubscriptionView(APIView):
|
||||
|
||||
|
||||
class ApiV2AttachView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
name = _('Attach Subscription')
|
||||
swagger_topic = 'System Configuration'
|
||||
@@ -230,7 +226,6 @@ class ApiV2AttachView(APIView):
|
||||
user = getattr(settings, 'SUBSCRIPTIONS_USERNAME', None)
|
||||
pw = getattr(settings, 'SUBSCRIPTIONS_PASSWORD', None)
|
||||
if pool_id and user and pw:
|
||||
|
||||
data = request.data.copy()
|
||||
try:
|
||||
with set_environ(**settings.AWX_TASK_ENV):
|
||||
@@ -258,7 +253,6 @@ class ApiV2AttachView(APIView):
|
||||
|
||||
|
||||
class ApiV2ConfigView(APIView):
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
name = _('Configuration')
|
||||
swagger_topic = 'System Configuration'
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class ConfConfig(AppConfig):
|
||||
|
||||
name = 'awx.conf'
|
||||
verbose_name = _('Configuration')
|
||||
|
||||
@@ -16,7 +15,6 @@ class ConfConfig(AppConfig):
|
||||
self.module.autodiscover()
|
||||
|
||||
if not set(sys.argv) & {'migrate', 'check_migrations'}:
|
||||
|
||||
from .settings import SettingsWrapper
|
||||
|
||||
SettingsWrapper.initialize()
|
||||
|
||||
@@ -47,7 +47,6 @@ class IntegerField(IntegerField):
|
||||
|
||||
|
||||
class StringListField(ListField):
|
||||
|
||||
child = CharField()
|
||||
|
||||
def to_representation(self, value):
|
||||
@@ -57,7 +56,6 @@ class StringListField(ListField):
|
||||
|
||||
|
||||
class StringListBooleanField(ListField):
|
||||
|
||||
default_error_messages = {'type_error': _('Expected None, True, False, a string or list of strings but got {input_type} instead.')}
|
||||
child = CharField()
|
||||
|
||||
@@ -96,7 +94,6 @@ class StringListBooleanField(ListField):
|
||||
|
||||
|
||||
class StringListPathField(StringListField):
|
||||
|
||||
default_error_messages = {'type_error': _('Expected list of strings but got {input_type} instead.'), 'path_error': _('{path} is not a valid path choice.')}
|
||||
|
||||
def to_internal_value(self, paths):
|
||||
@@ -126,7 +123,6 @@ class StringListIsolatedPathField(StringListField):
|
||||
}
|
||||
|
||||
def to_internal_value(self, paths):
|
||||
|
||||
if isinstance(paths, (list, tuple)):
|
||||
for p in paths:
|
||||
if not isinstance(p, str):
|
||||
|
||||
@@ -8,7 +8,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -48,7 +48,6 @@ def revert_tower_settings(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0001_initial'), ('main', '0004_squashed_v310_release')]
|
||||
|
||||
run_before = [('main', '0005_squashed_v310_v313_updates')]
|
||||
|
||||
@@ -7,7 +7,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0002_v310_copy_tower_settings')]
|
||||
|
||||
operations = [migrations.AlterField(model_name='setting', name='value', field=awx.main.fields.JSONBlob(null=True))]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0003_v310_JSONField_changes')]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -15,7 +15,6 @@ def reverse_copy_session_settings(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0004_v320_reencrypt')]
|
||||
|
||||
operations = [migrations.RunPython(copy_session_settings, reverse_copy_session_settings)]
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0005_v330_rename_two_session_settings')]
|
||||
|
||||
operations = [migrations.RunPython(fill_ldap_group_type_params)]
|
||||
|
||||
@@ -9,7 +9,6 @@ def copy_allowed_ips(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0006_v331_ldap_group_type')]
|
||||
|
||||
operations = [migrations.RunPython(copy_allowed_ips)]
|
||||
|
||||
@@ -14,7 +14,6 @@ def _noop(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0007_v380_rename_more_settings')]
|
||||
|
||||
operations = [migrations.RunPython(clear_old_license, _noop), migrations.RunPython(prefill_rh_credentials, _noop)]
|
||||
|
||||
@@ -10,7 +10,6 @@ def rename_proot_settings(apps, schema_editor):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [('conf', '0008_subscriptions')]
|
||||
|
||||
operations = [migrations.RunPython(rename_proot_settings)]
|
||||
|
||||
@@ -10,7 +10,6 @@ __all__ = ['rename_setting']
|
||||
|
||||
|
||||
def rename_setting(apps, schema_editor, old_key, new_key):
|
||||
|
||||
old_setting = None
|
||||
Setting = apps.get_model('conf', 'Setting')
|
||||
if Setting.objects.filter(key=new_key).exists() or hasattr(settings, new_key):
|
||||
|
||||
@@ -17,7 +17,6 @@ __all__ = ['Setting']
|
||||
|
||||
|
||||
class Setting(CreatedModifiedModel):
|
||||
|
||||
key = models.CharField(max_length=255)
|
||||
value = JSONBlob(null=True)
|
||||
user = prevent_search(models.ForeignKey('auth.User', related_name='settings', default=None, null=True, editable=False, on_delete=models.CASCADE))
|
||||
|
||||
@@ -104,7 +104,6 @@ def filter_sensitive(registry, key, value):
|
||||
|
||||
|
||||
class TransientSetting(object):
|
||||
|
||||
__slots__ = ('pk', 'value')
|
||||
|
||||
def __init__(self, pk, value):
|
||||
|
||||
@@ -5,7 +5,6 @@ from awx.conf.fields import StringListBooleanField, StringListPathField, ListTup
|
||||
|
||||
|
||||
class TestStringListBooleanField:
|
||||
|
||||
FIELD_VALUES = [
|
||||
("hello", "hello"),
|
||||
(("a", "b"), ["a", "b"]),
|
||||
@@ -53,7 +52,6 @@ class TestStringListBooleanField:
|
||||
|
||||
|
||||
class TestListTuplesField:
|
||||
|
||||
FIELD_VALUES = [([('a', 'b'), ('abc', '123')], [("a", "b"), ("abc", "123")])]
|
||||
|
||||
FIELD_VALUES_INVALID = [("abc", type("abc")), ([('a', 'b', 'c'), ('abc', '123', '456')], type(('a',))), (['a', 'b'], type('a')), (123, type(123))]
|
||||
@@ -73,7 +71,6 @@ class TestListTuplesField:
|
||||
|
||||
|
||||
class TestStringListPathField:
|
||||
|
||||
FIELD_VALUES = [
|
||||
((".", "..", "/"), [".", "..", "/"]),
|
||||
(("/home",), ["/home"]),
|
||||
|
||||
@@ -36,7 +36,6 @@ SettingCategory = collections.namedtuple('SettingCategory', ('url', 'slug', 'nam
|
||||
|
||||
|
||||
class SettingCategoryList(ListAPIView):
|
||||
|
||||
model = Setting # Not exactly, but needed for the view.
|
||||
serializer_class = SettingCategorySerializer
|
||||
filter_backends = []
|
||||
@@ -58,7 +57,6 @@ class SettingCategoryList(ListAPIView):
|
||||
|
||||
|
||||
class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = Setting # Not exactly, but needed for the view.
|
||||
serializer_class = SettingSingletonSerializer
|
||||
filter_backends = []
|
||||
@@ -146,7 +144,6 @@ class SettingSingletonDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
|
||||
class SettingLoggingTest(GenericAPIView):
|
||||
|
||||
name = _('Logging Connectivity Test')
|
||||
model = Setting
|
||||
serializer_class = SettingSingletonSerializer
|
||||
|
||||
@@ -561,7 +561,6 @@ class NotificationAttachMixin(BaseAccess):
|
||||
|
||||
|
||||
class InstanceAccess(BaseAccess):
|
||||
|
||||
model = Instance
|
||||
prefetch_related = ('rampart_groups',)
|
||||
|
||||
@@ -579,7 +578,6 @@ class InstanceAccess(BaseAccess):
|
||||
return super(InstanceAccess, self).can_unattach(obj, sub_obj, relationship, relationship, data=data)
|
||||
|
||||
def can_add(self, data):
|
||||
|
||||
return self.user.is_superuser
|
||||
|
||||
def can_change(self, obj, data):
|
||||
@@ -590,7 +588,6 @@ class InstanceAccess(BaseAccess):
|
||||
|
||||
|
||||
class InstanceGroupAccess(BaseAccess):
|
||||
|
||||
model = InstanceGroup
|
||||
prefetch_related = ('instances',)
|
||||
|
||||
@@ -1030,7 +1027,9 @@ class GroupAccess(BaseAccess):
|
||||
return Group.objects.filter(inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
|
||||
|
||||
def can_add(self, data):
|
||||
if not data or 'inventory' not in data:
|
||||
if not data: # So the browseable API will work
|
||||
return Inventory.accessible_objects(self.user, 'admin_role').exists()
|
||||
if 'inventory' not in data:
|
||||
return False
|
||||
# Checks for admin or change permission on inventory.
|
||||
return self.check_related('inventory', Inventory, data)
|
||||
@@ -2352,7 +2351,6 @@ class JobEventAccess(BaseAccess):
|
||||
|
||||
|
||||
class UnpartitionedJobEventAccess(JobEventAccess):
|
||||
|
||||
model = UnpartitionedJobEvent
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,5 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class MainConfig(AppConfig):
|
||||
|
||||
name = 'awx.main'
|
||||
verbose_name = _('Main')
|
||||
|
||||
@@ -68,7 +68,11 @@ def conjur_backend(**kwargs):
|
||||
with CertFiles(cacert) as cert:
|
||||
# https://www.conjur.org/api.html#authentication-authenticate-post
|
||||
auth_kwargs['verify'] = cert
|
||||
resp = requests.post(urljoin(url, '/'.join(['api', 'authn', account, username, 'authenticate'])), **auth_kwargs)
|
||||
try:
|
||||
resp = requests.post(urljoin(url, '/'.join(['authn', account, username, 'authenticate'])), **auth_kwargs)
|
||||
resp.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
resp = requests.post(urljoin(url, '/'.join(['api', 'authn', account, username, 'authenticate'])), **auth_kwargs)
|
||||
raise_for_status(resp)
|
||||
token = resp.content.decode('utf-8')
|
||||
|
||||
@@ -78,14 +82,20 @@ def conjur_backend(**kwargs):
|
||||
}
|
||||
|
||||
# https://www.conjur.org/api.html#secrets-retrieve-a-secret-get
|
||||
path = urljoin(url, '/'.join(['api', 'secrets', account, 'variable', secret_path]))
|
||||
path = urljoin(url, '/'.join(['secrets', account, 'variable', secret_path]))
|
||||
path_conjurcloud = urljoin(url, '/'.join(['api', 'secrets', account, 'variable', secret_path]))
|
||||
if version:
|
||||
ver = "version={}".format(version)
|
||||
path = '?'.join([path, ver])
|
||||
path_conjurcloud = '?'.join([path_conjurcloud, ver])
|
||||
|
||||
with CertFiles(cacert) as cert:
|
||||
lookup_kwargs['verify'] = cert
|
||||
resp = requests.get(path, timeout=30, **lookup_kwargs)
|
||||
try:
|
||||
resp = requests.get(path, timeout=30, **lookup_kwargs)
|
||||
resp.raise_for_status()
|
||||
except requests.exceptions.HTTPError:
|
||||
resp = requests.get(path_conjurcloud, timeout=30, **lookup_kwargs)
|
||||
raise_for_status(resp)
|
||||
return resp.text
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ logger = logging.getLogger('awx.main.dispatch')
|
||||
|
||||
|
||||
class Control(object):
|
||||
|
||||
services = ('dispatcher', 'callback_receiver')
|
||||
result = None
|
||||
|
||||
|
||||
@@ -192,7 +192,6 @@ class PoolWorker(object):
|
||||
|
||||
|
||||
class StatefulPoolWorker(PoolWorker):
|
||||
|
||||
track_managed_tasks = True
|
||||
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ class task:
|
||||
bind_kwargs = self.bind_kwargs
|
||||
|
||||
class PublisherMixin(object):
|
||||
|
||||
queue = None
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -40,7 +40,6 @@ class WorkerSignalHandler:
|
||||
|
||||
|
||||
class AWXConsumerBase(object):
|
||||
|
||||
last_stats = time.time()
|
||||
|
||||
def __init__(self, name, worker, queues=[], pool=None):
|
||||
|
||||
@@ -154,6 +154,8 @@ class CallbackBrokerWorker(BaseWorker):
|
||||
metrics_events_missing_created = 0
|
||||
metrics_total_job_event_processing_seconds = datetime.timedelta(seconds=0)
|
||||
for cls, events in self.buff.items():
|
||||
if not events:
|
||||
continue
|
||||
logger.debug(f'{cls.__name__}.objects.bulk_create({len(events)})')
|
||||
for e in events:
|
||||
e.modified = now # this can be set before created because now is set above on line 149
|
||||
|
||||
@@ -232,7 +232,6 @@ class ImplicitRoleField(models.ForeignKey):
|
||||
field_names = [field_names]
|
||||
|
||||
for field_name in field_names:
|
||||
|
||||
if field_name.startswith('singleton:'):
|
||||
continue
|
||||
|
||||
@@ -244,7 +243,6 @@ class ImplicitRoleField(models.ForeignKey):
|
||||
field = getattr(cls, field_name, None)
|
||||
|
||||
if field and type(field) is ReverseManyToOneDescriptor or type(field) is ManyToManyDescriptor:
|
||||
|
||||
if '.' in field_attr:
|
||||
raise Exception('Referencing deep roles through ManyToMany fields is unsupported.')
|
||||
|
||||
@@ -629,7 +627,6 @@ class CredentialInputField(JSONSchemaField):
|
||||
# `ssh_key_unlock` requirements are very specific and can't be
|
||||
# represented without complicated JSON schema
|
||||
if model_instance.credential_type.managed is True and 'ssh_key_unlock' in defined_fields:
|
||||
|
||||
# in order to properly test the necessity of `ssh_key_unlock`, we
|
||||
# need to know the real value of `ssh_key_data`; for a payload like:
|
||||
# {
|
||||
@@ -791,7 +788,8 @@ class CredentialTypeInjectorField(JSONSchemaField):
|
||||
'type': 'object',
|
||||
'patternProperties': {
|
||||
# http://docs.ansible.com/ansible/playbooks_variables.html#what-makes-a-valid-variable-name
|
||||
'^[a-zA-Z_]+[a-zA-Z0-9_]*$': {'type': 'string'},
|
||||
# plus, add ability to template
|
||||
r'^[a-zA-Z_\{\}]+[a-zA-Z0-9_\{\}]*$': {"anyOf": [{'type': 'string'}, {'type': 'array'}, {'$ref': '#/properties/extra_vars'}]}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
@@ -858,27 +856,44 @@ class CredentialTypeInjectorField(JSONSchemaField):
|
||||
template_name = template_name.split('.')[1]
|
||||
setattr(valid_namespace['tower'].filename, template_name, 'EXAMPLE_FILENAME')
|
||||
|
||||
def validate_template_string(type_, key, tmpl):
|
||||
try:
|
||||
sandbox.ImmutableSandboxedEnvironment(undefined=StrictUndefined).from_string(tmpl).render(valid_namespace)
|
||||
except UndefinedError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('{sub_key} uses an undefined field ({error_msg})').format(sub_key=key, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
except SecurityError as e:
|
||||
raise django_exceptions.ValidationError(_('Encountered unsafe code execution: {}').format(e))
|
||||
except TemplateSyntaxError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format(sub_key=key, type=type_, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
def validate_extra_vars(key, node):
|
||||
if isinstance(node, dict):
|
||||
for k, v in node.items():
|
||||
validate_template_string("extra_vars", 'a key' if key is None else key, k)
|
||||
validate_extra_vars(k if key is None else "{key}.{k}".format(key=key, k=k), v)
|
||||
elif isinstance(node, list):
|
||||
for i, x in enumerate(node):
|
||||
validate_extra_vars("{key}[{i}]".format(key=key, i=i), x)
|
||||
else:
|
||||
validate_template_string("extra_vars", key, node)
|
||||
|
||||
for type_, injector in value.items():
|
||||
if type_ == 'env':
|
||||
for key in injector.keys():
|
||||
self.validate_env_var_allowed(key)
|
||||
for key, tmpl in injector.items():
|
||||
try:
|
||||
sandbox.ImmutableSandboxedEnvironment(undefined=StrictUndefined).from_string(tmpl).render(valid_namespace)
|
||||
except UndefinedError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('{sub_key} uses an undefined field ({error_msg})').format(sub_key=key, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
except SecurityError as e:
|
||||
raise django_exceptions.ValidationError(_('Encountered unsafe code execution: {}').format(e))
|
||||
except TemplateSyntaxError as e:
|
||||
raise django_exceptions.ValidationError(
|
||||
_('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format(sub_key=key, type=type_, error_msg=e),
|
||||
code='invalid',
|
||||
params={'value': value},
|
||||
)
|
||||
if type_ == 'extra_vars':
|
||||
validate_extra_vars(None, injector)
|
||||
else:
|
||||
for key, tmpl in injector.items():
|
||||
validate_template_string(type_, key, tmpl)
|
||||
|
||||
|
||||
class AskForField(models.BooleanField):
|
||||
|
||||
@@ -9,7 +9,6 @@ class Command(BaseCommand):
|
||||
"""Checks connection to the database, and prints out connection info if not connected"""
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT version()")
|
||||
version = str(cursor.fetchone()[0])
|
||||
|
||||
@@ -82,7 +82,6 @@ class DeleteMeta:
|
||||
part_drop = {}
|
||||
|
||||
for pk, status, created in self.jobs_qs:
|
||||
|
||||
part_key = partition_table_name(self.job_class, created)
|
||||
if status in ['pending', 'waiting', 'running']:
|
||||
part_drop[part_key] = False
|
||||
|
||||
@@ -17,7 +17,6 @@ class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not options['user']:
|
||||
|
||||
raise CommandError('Username not supplied. Usage: awx-manage create_oauth2_token --user=username.')
|
||||
try:
|
||||
user = User.objects.get(username=options['user'])
|
||||
|
||||
35
awx/main/management/commands/enable_local_authentication.py
Normal file
35
awx/main/management/commands/enable_local_authentication.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""enable or disable authentication system"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""
|
||||
This adds the --enable --disable functionalities to the command using mutally_exclusive to avoid situations in which users pass both flags
|
||||
"""
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--enable', dest='enable', action='store_true', help='Pass --enable to enable local authentication')
|
||||
group.add_argument('--disable', dest='disable', action='store_true', help='Pass --disable to disable local authentication')
|
||||
|
||||
def _enable_disable_auth(self, enable, disable):
|
||||
"""
|
||||
this method allows the disabling or enabling of local authenication based on the argument passed into the parser
|
||||
if no arguments throw a command error, if --enable set the DISABLE_LOCAL_AUTH to False
|
||||
if --disable it's set to True. Realizing that the flag is counterintuitive to what is expected.
|
||||
"""
|
||||
|
||||
if enable:
|
||||
settings.DISABLE_LOCAL_AUTH = False
|
||||
print("Setting has changed to {} allowing local authentication".format(settings.DISABLE_LOCAL_AUTH))
|
||||
|
||||
elif disable:
|
||||
settings.DISABLE_LOCAL_AUTH = True
|
||||
print("Setting has changed to {} disallowing local authentication".format(settings.DISABLE_LOCAL_AUTH))
|
||||
|
||||
else:
|
||||
raise CommandError('Please pass --enable flag to allow local auth or --disable flag to disable local auth')
|
||||
|
||||
def handle(self, **options):
|
||||
self._enable_disable_auth(options.get('enable'), options.get('disable'))
|
||||
@@ -10,7 +10,6 @@ from django.utils.text import slugify
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'Export custom inventory scripts into a tarfile.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
@@ -21,7 +20,6 @@ class Command(BaseCommand):
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
with tarfile.open(tar_filename, "w") as tar:
|
||||
|
||||
for cis in CustomInventoryScript.objects.all():
|
||||
# naming convention similar to project paths
|
||||
slug_name = slugify(str(cis.name)).replace(u'-', u'_')
|
||||
|
||||
@@ -6,7 +6,6 @@ import json
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'This is for offline licensing usage'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
|
||||
@@ -934,7 +934,6 @@ class Command(BaseCommand):
|
||||
# (even though inventory_import.Command.handle -- which calls
|
||||
# perform_update -- has its own lock, inventory_ID_import)
|
||||
with advisory_lock('inventory_{}_perform_update'.format(self.inventory.id)):
|
||||
|
||||
try:
|
||||
self.check_license()
|
||||
except PermissionDenied as e:
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Ungrouped(object):
|
||||
|
||||
name = 'ungrouped'
|
||||
policy_instance_percentage = None
|
||||
policy_instance_minimum = None
|
||||
|
||||
@@ -7,7 +7,6 @@ from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = (
|
||||
"Remove an instance (specified by --hostname) from the specified queue (instance group).\n"
|
||||
"In order remove the queue, use the `unregister_queue` command."
|
||||
|
||||
@@ -28,7 +28,6 @@ class JobStatusLifeCycle:
|
||||
|
||||
|
||||
class ReplayJobEvents(JobStatusLifeCycle):
|
||||
|
||||
recording_start = None
|
||||
replay_start = None
|
||||
|
||||
@@ -190,7 +189,6 @@ class ReplayJobEvents(JobStatusLifeCycle):
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'Replay job events over websockets ordered by created on date.'
|
||||
|
||||
def _parse_slice_range(self, slice_arg):
|
||||
|
||||
@@ -7,7 +7,6 @@ from awx.main.models import CredentialType
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = 'Load default managed credential types.'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
@@ -10,7 +10,6 @@ from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
help = (
|
||||
"Remove specified queue (instance group) from database.\n"
|
||||
"Instances inside of queue will continue to exist, \n"
|
||||
|
||||
@@ -38,7 +38,6 @@ class SettingsCacheMiddleware(MiddlewareMixin):
|
||||
|
||||
|
||||
class TimingMiddleware(threading.local, MiddlewareMixin):
|
||||
|
||||
dest = '/var/log/tower/profile'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -14,7 +14,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
|
||||
@@ -12,7 +12,6 @@ from ._squashed_30 import SQUASHED_30
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0003_squashed_v300_v303_updates'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ from ._squashed_31 import SQUASHED_31
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0004_squashed_v310_release'),
|
||||
]
|
||||
|
||||
@@ -26,7 +26,6 @@ def replaces():
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0005_squashed_v310_v313_updates'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ from awx.main.migrations import ActivityStreamDisabledMigration
|
||||
|
||||
|
||||
class Migration(ActivityStreamDisabledMigration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0006_v320_release'),
|
||||
]
|
||||
|
||||
@@ -11,7 +11,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0007_v320_data_migrations'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0008_v320_drop_v1_credential_fields'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0009_v322_add_setting_field_for_activity_stream'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from awx.main.migrations import ActivityStreamDisabledMigration
|
||||
|
||||
|
||||
class Migration(ActivityStreamDisabledMigration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0010_v322_add_ovirt4_tower_inventory'),
|
||||
]
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0011_v322_encrypt_survey_passwords'),
|
||||
]
|
||||
|
||||
@@ -9,7 +9,6 @@ from awx.main.migrations._multi_cred import migrate_to_multi_cred, migrate_back_
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0012_v322_update_cred_types'),
|
||||
]
|
||||
|
||||
@@ -11,7 +11,6 @@ from awx.main.migrations._scan_jobs import remove_scan_type_nodes
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0013_v330_multi_credential'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0014_v330_saved_launchtime_configs'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0015_v330_blank_start_args'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0016_v330_non_blank_workflow'),
|
||||
]
|
||||
|
||||
@@ -9,7 +9,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0017_v330_move_deprecated_stdout'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0018_v330_add_additional_stdout_events'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0019_v330_custom_virtualenv'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0020_v330_instancegroup_policies'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ from awx.main.migrations import _migration_utils as migration_utils
|
||||
|
||||
|
||||
class Migration(ActivityStreamDisabledMigration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0021_v330_declare_new_rbac_roles'),
|
||||
]
|
||||
|
||||
@@ -9,7 +9,6 @@ from awx.main.migrations._multi_cred import migrate_inventory_source_cred, migra
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0022_v330_create_new_rbac_roles'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sessions', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
|
||||
@@ -10,7 +10,6 @@ import re
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0024_v330_create_user_session_membership'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0025_v330_add_oauth_activity_stream_registrar'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0026_v330_delete_authtoken'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0027_v330_emitted_events'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0028_v330_add_tower_verify'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import oauth2_provider.generators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0030_v330_modify_application'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0031_v330_encrypt_oauth2_secret'),
|
||||
]
|
||||
|
||||
@@ -12,7 +12,6 @@ import oauth2_provider.generators
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0032_v330_polymorphic_delete'),
|
||||
]
|
||||
|
||||
@@ -10,7 +10,6 @@ from awx.main.migrations import _migration_utils as migration_utils
|
||||
|
||||
|
||||
class Migration(ActivityStreamDisabledMigration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0033_v330_oauth_help_text'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0034_v330_delete_user_role'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0035_v330_more_oauth2_help_text'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0036_v330_credtype_remove_become_methods'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import awx.main.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0037_v330_remove_legacy_fact_cleanup'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0038_v330_add_deleted_activitystream_actor'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0039_v330_custom_venv_help_text'),
|
||||
]
|
||||
|
||||
@@ -8,7 +8,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL),
|
||||
('main', '0040_v330_unifiedjob_controller_node'),
|
||||
|
||||
@@ -9,7 +9,6 @@ from awx.main.migrations._rbac import rebuild_role_hierarchy
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0041_v330_update_oauth_refreshtoken'),
|
||||
]
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0042_v330_org_member_role_deparent'),
|
||||
]
|
||||
|
||||
@@ -7,7 +7,6 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('main', '0043_v330_oauth2accesstoken_modified'),
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user