mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 18:40:01 -03:30
Implement API side for custom inventory script support with endpoints
and unit tests
This commit is contained in:
parent
c402d13a73
commit
06c75aeecf
@ -929,11 +929,16 @@ class GroupVariableDataSerializer(BaseVariableDataSerializer):
|
||||
class Meta:
|
||||
model = Group
|
||||
|
||||
class CustomInventoryScriptSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = CustomInventoryScript
|
||||
fields = ('*', "script")
|
||||
|
||||
class InventorySourceOptionsSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
fields = ('*', 'source', 'source_path', 'source_vars', 'credential',
|
||||
fields = ('*', 'source', 'source_path', 'source_script', 'source_vars', 'credential',
|
||||
'source_regions', 'overwrite', 'overwrite_vars')
|
||||
|
||||
def get_related(self, obj):
|
||||
|
||||
@ -116,6 +116,11 @@ inventory_update_urls = patterns('awx.api.views',
|
||||
url(r'^(?P<pk>[0-9]+)/stdout/$', 'inventory_update_stdout'),
|
||||
)
|
||||
|
||||
inventory_script_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'inventory_script_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', 'inventory_script_detail'),
|
||||
)
|
||||
|
||||
credential_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'credential_list'),
|
||||
url(r'^(?P<pk>[0-9]+)/activity_stream/$', 'credential_activity_stream_list'),
|
||||
@ -193,6 +198,7 @@ v1_urls = patterns('awx.api.views',
|
||||
url(r'^groups/', include(group_urls)),
|
||||
url(r'^inventory_sources/', include(inventory_source_urls)),
|
||||
url(r'^inventory_updates/', include(inventory_update_urls)),
|
||||
url(r'^inventory_scripts/', include(inventory_script_urls)),
|
||||
url(r'^credentials/', include(credential_urls)),
|
||||
url(r'^permissions/', include(permission_urls)),
|
||||
url(r'^job_templates/', include(job_template_urls)),
|
||||
|
||||
@ -102,6 +102,7 @@ class ApiV1RootView(APIView):
|
||||
data['teams'] = reverse('api:team_list')
|
||||
data['credentials'] = reverse('api:credential_list')
|
||||
data['inventory'] = reverse('api:inventory_list')
|
||||
data['inventory_scripts'] = reverse('api:inventory_script_list')
|
||||
data['inventory_sources'] = reverse('api:inventory_source_list')
|
||||
data['groups'] = reverse('api:group_list')
|
||||
data['hosts'] = reverse('api:host_list')
|
||||
@ -806,6 +807,16 @@ class PermissionDetail(RetrieveUpdateDestroyAPIView):
|
||||
model = Permission
|
||||
serializer_class = PermissionSerializer
|
||||
|
||||
class InventoryScriptList(ListCreateAPIView):
|
||||
|
||||
model = CustomInventoryScript
|
||||
serializer_class = CustomInventoryScriptSerializer
|
||||
|
||||
class InventoryScriptDetail(RetrieveUpdateDestroyAPIView):
|
||||
|
||||
model = CustomInventoryScript
|
||||
serializer_class = CustomInventoryScriptSerializer
|
||||
|
||||
class InventoryList(ListCreateAPIView):
|
||||
|
||||
model = Inventory
|
||||
|
||||
@ -1323,6 +1323,32 @@ class ActivityStreamAccess(BaseAccess):
|
||||
def can_delete(self, obj):
|
||||
return False
|
||||
|
||||
class CustomInventoryScriptAccess(BaseAccess):
|
||||
|
||||
model = CustomInventoryScript
|
||||
|
||||
def get_queryset(self):
|
||||
qs = self.model.objects.filter(active=True).distinct()
|
||||
return qs
|
||||
|
||||
def can_read(self, obj):
|
||||
return True
|
||||
|
||||
def can_add(self, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_change(self, obj, data):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_delete(self, obj):
|
||||
if self.user.is_superuser:
|
||||
return True
|
||||
return False
|
||||
|
||||
register_access(User, UserAccess)
|
||||
register_access(Organization, OrganizationAccess)
|
||||
register_access(Inventory, InventoryAccess)
|
||||
@ -1343,3 +1369,4 @@ register_access(Schedule, ScheduleAccess)
|
||||
register_access(UnifiedJobTemplate, UnifiedJobTemplateAccess)
|
||||
register_access(UnifiedJob, UnifiedJobAccess)
|
||||
register_access(ActivityStream, ActivityStreamAccess)
|
||||
register_access(CustomInventoryScript, CustomInventoryScriptAccess)
|
||||
|
||||
@ -61,7 +61,7 @@ PERMISSION_TYPE_CHOICES = [
|
||||
(PERM_INVENTORY_CHECK, _('Deploy To Inventory (Dry Run)')),
|
||||
]
|
||||
|
||||
CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure']
|
||||
CLOUD_INVENTORY_SOURCES = ['ec2', 'rax', 'vmware', 'gce', 'azure', 'custom']
|
||||
|
||||
|
||||
class VarsDictProperty(object):
|
||||
|
||||
@ -39,7 +39,7 @@ from awx.main.models.jobs import Job
|
||||
from awx.main.models.unified_jobs import *
|
||||
from awx.main.utils import encrypt_field, ignore_inventory_computed_fields, _inventory_updates
|
||||
|
||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate']
|
||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'CustomInventoryScript']
|
||||
|
||||
logger = logging.getLogger('awx.main.models.inventory')
|
||||
|
||||
@ -744,6 +744,7 @@ class InventorySourceOptions(BaseModel):
|
||||
('gce', _('Google Compute Engine')),
|
||||
('azure', _('Microsoft Azure')),
|
||||
('vmware', _('VMware vCenter')),
|
||||
('custom', _('Custom Script')),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
@ -761,6 +762,13 @@ class InventorySourceOptions(BaseModel):
|
||||
default='',
|
||||
editable=False,
|
||||
)
|
||||
source_script = models.ForeignKey(
|
||||
'CustomInventoryScript',
|
||||
null=True,
|
||||
default=None,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
source_vars = models.TextField(
|
||||
blank=True,
|
||||
default='',
|
||||
@ -930,7 +938,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
|
||||
|
||||
@classmethod
|
||||
def _get_unified_job_field_names(cls):
|
||||
return ['name', 'description', 'source', 'source_path', 'source_vars',
|
||||
return ['name', 'description', 'source', 'source_path', 'source_script', 'source_vars',
|
||||
'credential', 'source_regions', 'overwrite', 'overwrite_vars']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@ -1067,3 +1075,19 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions):
|
||||
@property
|
||||
def task_impact(self):
|
||||
return 50
|
||||
|
||||
class CustomInventoryScript(CommonModel):
|
||||
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
ordering = ('name',)
|
||||
|
||||
script = models.TextField(
|
||||
blank=True,
|
||||
default='',
|
||||
help_text=_('Inventory script contents'),
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('api:inventory_script_detail', args=(self.pk,))
|
||||
|
||||
|
||||
@ -1110,7 +1110,19 @@ class RunInventoryUpdate(BaseTask):
|
||||
|
||||
elif inventory_update.source == 'file':
|
||||
args.append(inventory_update.source_path)
|
||||
|
||||
elif inventory_update.source == 'custom':
|
||||
runpath = tempfile.mkdtemp(prefix='ansible_tower_launch_')
|
||||
handle, path = tempfile.mkstemp(dir=runpath)
|
||||
f = os.fdopen(handle, 'w')
|
||||
# Check that source_script is not none and exists and that .script is not an empty string
|
||||
f.write(inventory_update.source_script.script)
|
||||
f.close()
|
||||
os.chmod(path, stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR)
|
||||
args.append(runpath)
|
||||
try:
|
||||
shutil.rmtree(runpath, True)
|
||||
except OSError:
|
||||
pass
|
||||
verbosity = getattr(settings, 'INVENTORY_UPDATE_VERBOSITY', 1)
|
||||
args.append('-v%d' % verbosity)
|
||||
if settings.DEBUG:
|
||||
|
||||
@ -21,6 +21,8 @@ from awx.main.tests.base import BaseTest, BaseTransactionTest
|
||||
|
||||
__all__ = ['InventoryTest', 'InventoryUpdatesTest']
|
||||
|
||||
TEST_SIMPLE_INVENTORY_SCRIPT = "#!/usr/bin/env python\nimport json\nprint json.dumps({'hosts': ['ahost-01', 'ahost-02', 'ahost-03', 'ahost-04']})"
|
||||
|
||||
class InventoryTest(BaseTest):
|
||||
|
||||
def setUp(self):
|
||||
@ -267,6 +269,17 @@ class InventoryTest(BaseTest):
|
||||
self.delete(permission_detail, expect=204, auth=self.get_super_credentials())
|
||||
self.get(inventory_detail, expect=403, auth=self.get_other_credentials())
|
||||
|
||||
def test_create_inventory_script(self):
|
||||
inventory_scripts = reverse('api:inventory_script_list')
|
||||
new_script = dict(name="Test", description="Test Script", script=TEST_SIMPLE_INVENTORY_SCRIPT)
|
||||
script_data = self.post(inventory_scripts, data=new_script, expect=201, auth=self.get_super_credentials())
|
||||
|
||||
got = self.get(inventory_scripts, expect=200, auth=self.get_super_credentials())
|
||||
self.assertEquals(got['count'], 1)
|
||||
|
||||
new_failed_script = dict(name="Shouldfail", description="This test should fail", script=TEST_SIMPLE_INVENTORY_SCRIPT)
|
||||
self.post(inventory_scripts, data=new_failed_script, expect=403, auth=self.get_normal_credentials())
|
||||
|
||||
def test_main_line(self):
|
||||
|
||||
# some basic URLs...
|
||||
@ -1546,3 +1559,26 @@ class InventoryUpdatesTest(BaseTransactionTest):
|
||||
# its own child).
|
||||
self.assertTrue(self.group in self.inventory.root_groups)
|
||||
|
||||
def test_update_from_custom_script(self):
|
||||
# Create the inventory script
|
||||
inventory_scripts = reverse('api:inventory_script_list')
|
||||
new_script = dict(name="Test", description="Test Script", script=TEST_SIMPLE_INVENTORY_SCRIPT)
|
||||
script_data = self.post(inventory_scripts, data=new_script, expect=201, auth=self.get_super_credentials())
|
||||
|
||||
custom_inv = self.organization.inventories.create(name='Custom Script Inventory')
|
||||
custom_group = custom_inv.groups.create(name="Custom Script Group")
|
||||
custom_inv_src = reverse('api:inventory_source_detail',
|
||||
args=(custom_group.inventory_source.pk,))
|
||||
custom_inv_update = reverse('api:inventory_source_update_view',
|
||||
args=(custom_group.inventory_source.pk,))
|
||||
inv_src_opts = {'source': 'custom',
|
||||
'source_script': script_data["id"]}
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.put(custom_inv_src, inv_src_opts, expect=200)
|
||||
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.get(custom_inv_update, expect=200)
|
||||
self.assertTrue(response['can_update'])
|
||||
with self.current_user(self.super_django_user):
|
||||
response = self.post(custom_inv_update, {}, expect=202)
|
||||
self.assertTrue(custom_group.hosts.all().count() == 4)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user