Example #1
0
        def _run_script():
            """
            Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with
            the change_logging context manager (which is bypassed if commit == False).
            """
            try:
                with transaction.atomic():
                    script.output = script.run(data=data, commit=commit)
                    job_result.set_status(
                        JobResultStatusChoices.STATUS_COMPLETED)

                    if not commit:
                        raise AbortTransaction()

            except AbortTransaction:
                script.log_info(
                    "Database changes have been reverted automatically.")

            except Exception as e:
                stacktrace = traceback.format_exc()
                script.log_failure(
                    f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
                )
                script.log_info(
                    "Database changes have been reverted due to error.")
                logger.error(f"Exception raised during script execution: {e}")
                job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)

            finally:
                job_result.data = ScriptOutputSerializer(script).data
                job_result.save()

            logger.info(f"Script completed in {job_result.duration}")
Example #2
0
def run_script(script, data, request, commit=True):
    """
    A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
    exists outside of the Script class to ensure it cannot be overridden by a script author.
    """
    output = None
    start_time = None
    end_time = None

    script_name = script.__class__.__name__
    logger = logging.getLogger(
        f"netbox.scripts.{script.module()}.{script_name}")
    logger.info(f"Running script (commit={commit})")

    # Add files to form data
    files = request.FILES
    for field_name, fileobj in files.items():
        data[field_name] = fileobj

    # Add the current request as a property of the script
    script.request = request

    # Determine whether the script accepts a 'commit' argument (this was introduced in v2.7.8)
    kwargs = {'data': data}
    if 'commit' in inspect.signature(script.run).parameters:
        kwargs['commit'] = commit

    try:
        with transaction.atomic():
            start_time = time.time()
            output = script.run(**kwargs)
            end_time = time.time()
            if not commit:
                raise AbortTransaction()
    except AbortTransaction:
        pass
    except Exception as e:
        stacktrace = traceback.format_exc()
        script.log_failure(
            "An exception occurred: `{}: {}`\n```\n{}\n```".format(
                type(e).__name__, e, stacktrace))
        logger.error(f"Exception raised during script execution: {e}")
        commit = False
    finally:
        if not commit:
            # Delete all pending changelog entries
            purge_changelog.send(Script)
            script.log_info(
                "Database changes have been reverted automatically.")

    # Calculate execution time
    if end_time is not None:
        execution_time = end_time - start_time
        logger.info(f"Script completed in {execution_time:.4f} seconds")
    else:
        execution_time = None

    return output, execution_time
Example #3
0
def run_script(script, data, request, commit=True):
    """
    A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
    exists outside of the Script class to ensure it cannot be overridden by a script author.
    """
    output = None
    start_time = None
    end_time = None

    # Add files to form data
    files = request.FILES
    for field_name, fileobj in files.items():
        data[field_name] = fileobj

    # Add the current request as a property of the script
    script.request = request

    try:
        with transaction.atomic():
            start_time = time.time()
            output = script.run(data)
            end_time = time.time()
            if not commit:
                raise AbortTransaction()
    except AbortTransaction:
        pass
    except Exception as e:
        stacktrace = traceback.format_exc()
        script.log_failure(
            "An exception occurred: `{}: {}`\n```\n{}\n```".format(
                type(e).__name__, e, stacktrace))
        commit = False
    finally:
        if not commit:
            # Delete all pending changelog entries
            purge_changelog.send(Script)
            script.log_info(
                "Database changes have been reverted automatically.")

    # Calculate execution time
    if end_time is not None:
        execution_time = end_time - start_time
    else:
        execution_time = None

    return output, execution_time
Example #4
0
    def post(self, request):
        logger = logging.getLogger('netbox.views.ObjectImportView')
        form = ImportForm(request.POST)

        if form.is_valid():
            logger.debug("Import form validation was successful")

            # Initialize model form
            data = form.cleaned_data['data']
            model_form = self.model_form(data)

            # Assign default values for any fields which were not specified. We have to do this manually because passing
            # 'initial=' to the form on initialization merely sets default values for the widgets. Since widgets are not
            # used for YAML/JSON import, we first bind the imported data normally, then update the form's data with the
            # applicable field defaults as needed prior to form validation.
            for field_name, field in model_form.fields.items():
                if field_name not in data and hasattr(field, 'initial'):
                    model_form.data[field_name] = field.initial

            if model_form.is_valid():

                try:
                    with transaction.atomic():

                        # Save the primary object
                        obj = model_form.save()
                        logger.debug(f"Created {obj} (PK: {obj.pk})")

                        # Iterate through the related object forms (if any), validating and saving each instance.
                        for field_name, related_object_form in self.related_object_forms.items(
                        ):
                            logger.debug(
                                "Processing form for related objects: {related_object_form}"
                            )

                            for i, rel_obj_data in enumerate(
                                    data.get(field_name, list())):

                                f = related_object_form(obj, rel_obj_data)

                                for subfield_name, field in f.fields.items():
                                    if subfield_name not in rel_obj_data and hasattr(
                                            field, 'initial'):
                                        f.data[subfield_name] = field.initial

                                if f.is_valid():
                                    f.save()
                                else:
                                    # Replicate errors on the related object form to the primary form for display
                                    for subfield_name, errors in f.errors.items(
                                    ):
                                        for err in errors:
                                            err_msg = "{}[{}] {}: {}".format(
                                                field_name, i, subfield_name,
                                                err)
                                            model_form.add_error(None, err_msg)
                                    raise AbortTransaction()

                except AbortTransaction:
                    pass

            if not model_form.errors:
                logger.info(f"Import object {obj} (PK: {obj.pk})")
                messages.success(
                    request,
                    mark_safe('Imported object: <a href="{}">{}</a>'.format(
                        obj.get_absolute_url(), obj)))

                if '_addanother' in request.POST:
                    return redirect(request.get_full_path())

                return_url = form.cleaned_data.get('return_url')
                if return_url is not None and is_safe_url(
                        url=return_url, allowed_hosts=request.get_host()):
                    return redirect(return_url)
                else:
                    return redirect(self.get_return_url(request, obj))

            else:
                logger.debug("Model form validation failed")

                # Replicate model form errors for display
                for field, errors in model_form.errors.items():
                    for err in errors:
                        if field == '__all__':
                            form.add_error(None, err)
                        else:
                            form.add_error(None, "{}: {}".format(field, err))

        else:
            logger.debug("Import form validation failed")

        return render(
            request, self.template_name, {
                'form': form,
                'obj_type': self.model._meta.verbose_name,
                'return_url': self.get_return_url(request),
            })
Example #5
0
def run_script(data, request, commit=True, *args, **kwargs):
    """
    A wrapper for calling Script.run(). This performs error handling and provides a hook for committing changes. It
    exists outside of the Script class to ensure it cannot be overridden by a script author.
    """
    job_result = kwargs.pop('job_result')
    module, script_name = job_result.name.split('.', 1)

    script = get_script(module, script_name)()

    job_result.status = JobResultStatusChoices.STATUS_RUNNING
    job_result.save()

    logger = logging.getLogger(f"netbox.scripts.{module}.{script_name}")
    logger.info(f"Running script (commit={commit})")

    # Add files to form data
    files = request.FILES
    for field_name, fileobj in files.items():
        data[field_name] = fileobj

    # Add the current request as a property of the script
    script.request = request

    # TODO: Drop backward-compatibility for absent 'commit' argument in v2.10
    # Determine whether the script accepts a 'commit' argument (this was introduced in v2.7.8)
    kwargs = {'data': data}
    if 'commit' in inspect.signature(script.run).parameters:
        kwargs['commit'] = commit
    else:
        warnings.warn(
            f"The run() method of script {script} should support a 'commit' argument. This will be required beginning "
            f"with NetBox v2.10.")

    with change_logging(request):

        try:
            with transaction.atomic():
                script.output = script.run(**kwargs)
                job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)

                if not commit:
                    raise AbortTransaction()

        except AbortTransaction:
            script.log_info(
                "Database changes have been reverted automatically.")

        except Exception as e:
            stacktrace = traceback.format_exc()
            script.log_failure(
                f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
            )
            script.log_info(
                "Database changes have been reverted due to error.")
            logger.error(f"Exception raised during script execution: {e}")
            job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)

        finally:
            job_result.data = ScriptOutputSerializer(script).data
            job_result.save()

        logger.info(f"Script completed in {job_result.duration}")

    # Delete any previous terminal state results
    JobResult.objects.filter(
        obj_type=job_result.obj_type,
        name=job_result.name,
        status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES).exclude(
            pk=job_result.pk).delete()