@ -54,6 +54,7 @@ from itam.viewsets import (
|
||||
device_model as device_model_v2,
|
||||
device_type as device_type_v2,
|
||||
device_software as device_software_v2,
|
||||
inventory,
|
||||
operating_system as operating_system_v2,
|
||||
operating_system_version as operating_system_version_v2,
|
||||
software as software_v2,
|
||||
@ -134,6 +135,7 @@ router.register('itam/device', device_v2.ViewSet, basename='_api_v2_device')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/software', device_software_v2.ViewSet, basename='_api_v2_device_software')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/service', service_device_v2.ViewSet, basename='_api_v2_service_device')
|
||||
router.register('itam/device/(?P<device_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_device_notes')
|
||||
router.register('itam/inventory', inventory.ViewSet, basename='_api_v2_inventory')
|
||||
router.register('itam/operating_system', operating_system_v2.ViewSet, basename='_api_v2_operating_system')
|
||||
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_operating_system_notes')
|
||||
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/version', operating_system_version_v2.ViewSet, basename='_api_v2_operating_system_version')
|
||||
|
@ -33,6 +33,7 @@ class InventoryPermissions(OrganizationPermissionAPI):
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class Collect(OrganizationPermissionAPI, views.APIView):
|
||||
|
||||
queryset = Device.objects.all()
|
||||
|
@ -225,6 +225,15 @@ class ModelViewSet(
|
||||
|
||||
|
||||
|
||||
class ModelCreateViewSet(
|
||||
ModelViewSetBase,
|
||||
viewsets.mixins.CreateModelMixin,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ModelListRetrieveDeleteViewSet(
|
||||
viewsets.mixins.ListModelMixin,
|
||||
viewsets.mixins.RetrieveModelMixin,
|
||||
|
93
app/itam/serializers/inventory.py
Normal file
93
app/itam/serializers/inventory.py
Normal file
@ -0,0 +1,93 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
|
||||
class InventorySerializer(serializers.Serializer):
|
||||
""" Serializer for Inventory Upload """
|
||||
|
||||
|
||||
class DetailsSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text = 'Host name',
|
||||
required = True
|
||||
)
|
||||
|
||||
serial_number = serializers.CharField(
|
||||
default = None,
|
||||
help_text = 'Devices serial number',
|
||||
required = False
|
||||
)
|
||||
|
||||
uuid = serializers.CharField(
|
||||
default = None,
|
||||
help_text = 'Device system UUID',
|
||||
required = False
|
||||
)
|
||||
|
||||
|
||||
def validate(self, data):
|
||||
|
||||
if(
|
||||
data['serial_number'] is None
|
||||
and data['uuid'] is None
|
||||
):
|
||||
|
||||
raise centurion_exceptions.ValidationError(
|
||||
detail = 'Serial Number or UUID is required',
|
||||
code = 'no_serial_or_uuid'
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class OperatingSystemSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text='Name of the operating system installed on the device',
|
||||
required = True,
|
||||
)
|
||||
|
||||
version_major = serializers.IntegerField(
|
||||
help_text='Major semver version number of the OS version',
|
||||
required = True,
|
||||
)
|
||||
|
||||
version = serializers.CharField(
|
||||
help_text='semver version number of the OS',
|
||||
required = True
|
||||
)
|
||||
|
||||
|
||||
class SoftwareSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text='Name of the software',
|
||||
required = True
|
||||
)
|
||||
|
||||
category = serializers.CharField(
|
||||
help_text='Category of the software',
|
||||
default = None,
|
||||
required = False
|
||||
)
|
||||
|
||||
version = serializers.CharField(
|
||||
default = None,
|
||||
help_text='semver version number of the software',
|
||||
required = False
|
||||
)
|
||||
|
||||
|
||||
details = DetailsSerializer()
|
||||
|
||||
os = OperatingSystemSerializer( required = False )
|
||||
|
||||
software = SoftwareSerializer( many = True, required = False )
|
@ -26,6 +26,7 @@ class Index(CommonViewSet):
|
||||
return Response(
|
||||
{
|
||||
"device": reverse('v2:_api_v2_device-list', request=request),
|
||||
"inventory": reverse('v2:_api_v2_inventory-list', request=request),
|
||||
"operating_system": reverse('v2:_api_v2_operating_system-list', request=request),
|
||||
"software": reverse('v2:_api_v2_software-list', request=request)
|
||||
}
|
||||
|
196
app/itam/viewsets/inventory.py
Normal file
196
app/itam/viewsets/inventory.py
Normal file
@ -0,0 +1,196 @@
|
||||
import json
|
||||
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
from api.tasks import process_inventory
|
||||
from api.viewsets.common import ModelCreateViewSet
|
||||
|
||||
from core import exceptions as centurion_exception
|
||||
from core.http.common import Http
|
||||
|
||||
from itam.models.device import Device
|
||||
from itam.serializers.inventory import InventorySerializer
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = "Upload a device's inventory",
|
||||
description = """After inventorying a device, it's inventory file, `.json` is uploaded to this endpoint.
|
||||
If the device does not exist, it will be created. If the device does exist the existing
|
||||
device will be updated with the information within the inventory.
|
||||
|
||||
matching for an existing device is by slug which is the hostname converted to lower case
|
||||
letters. This conversion is automagic.
|
||||
|
||||
**NOTE:** _for device creation, the API user must have user setting 'Default Organization'. Without
|
||||
this setting populated, no device will be created and the endpoint will return HTTP/403_
|
||||
|
||||
## Permissions
|
||||
|
||||
- `itam.add_device` Required to upload inventory
|
||||
""",
|
||||
request = InventorySerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='Inventory upload successful',
|
||||
response = {
|
||||
'OK'
|
||||
}
|
||||
),
|
||||
400: OpenApiResponse(description='Error Occured, see output retured'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
500: OpenApiResponse(description='Exception occured. View server logs for the Stack Trace'),
|
||||
}
|
||||
)
|
||||
)
|
||||
class ViewSet( ModelCreateViewSet ):
|
||||
"""Device Inventory
|
||||
|
||||
Use this endpoint to upload your device inventories.
|
||||
"""
|
||||
|
||||
model = Device
|
||||
|
||||
serializer_class = InventorySerializer
|
||||
|
||||
documentation: str = 'https://nofusscomputing.com/docs/not_model_docs'
|
||||
|
||||
view_name = 'Device Inventory'
|
||||
|
||||
view_description = __doc__
|
||||
|
||||
inventory_action: str = None
|
||||
"""Inventory action, choice. new|update"""
|
||||
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Upload a device inventory
|
||||
|
||||
Raises:
|
||||
centurion_exceptions.PermissionDenied: User is missing the required permissions
|
||||
|
||||
Returns:
|
||||
Response: string denoting what has occured
|
||||
"""
|
||||
|
||||
status = Http.Status.OK
|
||||
response_data = 'OK'
|
||||
|
||||
try:
|
||||
|
||||
data = InventorySerializer(
|
||||
data = request.data
|
||||
)
|
||||
|
||||
device = None
|
||||
|
||||
if not data.is_valid():
|
||||
|
||||
raise centurion_exception.ValidationError(
|
||||
detail = 'Uploaded inventory is not valid',
|
||||
code = 'invalid_inventory'
|
||||
)
|
||||
|
||||
|
||||
self.default_organization = UserSettings.objects.get(user=request.user).default_organization
|
||||
|
||||
if Device.objects.filter(slug=str(data.validated_data['details']['name']).lower()).exists():
|
||||
|
||||
self.obj = Device.objects.get(slug=str(data.validated_data['details']['name']).lower())
|
||||
|
||||
device = self.obj
|
||||
|
||||
task = process_inventory.delay(data.validated_data, self.default_organization.id)
|
||||
|
||||
response_data: dict = {"task_id": f"{task.id}"}
|
||||
|
||||
|
||||
except centurion_exception.PermissionDenied as e:
|
||||
|
||||
status = Http.Status.FORBIDDEN
|
||||
response_data = e.detail
|
||||
|
||||
except centurion_exception.ValidationError as e:
|
||||
|
||||
status = Http.Status.BAD_REQUEST
|
||||
response_data = e.detail
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print(f'An error occured{e}')
|
||||
|
||||
status = Http.Status.SERVER_ERROR
|
||||
response_data = f'Unknown Server Error occured: {e}'
|
||||
|
||||
|
||||
return Response(data=response_data,status=status)
|
||||
|
||||
|
||||
|
||||
def get_dynamic_permissions(self):
|
||||
"""Obtain the permissions required to upload an inventory.
|
||||
|
||||
Returns:
|
||||
list: Permissions required for Inventory Upload
|
||||
"""
|
||||
|
||||
organization = None
|
||||
|
||||
device_search = None
|
||||
|
||||
if 'details' in self.request.data:
|
||||
|
||||
if 'name' in self.request.data['details']:
|
||||
|
||||
device_search = Device.objects.filter(
|
||||
slug = str(self.request.data['details']['name']).lower()
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
centurion_exception.ParseError(
|
||||
detail = {
|
||||
'name': 'Device name is required'
|
||||
},
|
||||
code = 'missing_device_name'
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
centurion_exception.ParseError(
|
||||
detail = {
|
||||
'details': 'Details dict is required'
|
||||
},
|
||||
code = 'missing_details_dict'
|
||||
)
|
||||
|
||||
|
||||
if device_search: # Existing device
|
||||
|
||||
if len(list(device_search)) == 1:
|
||||
|
||||
self.obj = list(device_search)[0]
|
||||
|
||||
self.permission_required = [
|
||||
'itam.change_device'
|
||||
]
|
||||
|
||||
self.inventory_action = 'update'
|
||||
|
||||
else: # New device
|
||||
|
||||
self.permission_required = [
|
||||
'itam.add_device'
|
||||
]
|
||||
|
||||
self.inventory_action = 'new'
|
||||
|
||||
|
||||
return super().get_permission_required()
|
Reference in New Issue
Block a user