CloudConvert

Use the CloudConvert integration to convert your files to the desired format.

Utilities · Cloud Convert

Details

IDCloudConvert
ProviderLunaweb GmbH
CategoryUtilities
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Use the CloudConvert integration to convert your files to the required format.
This integration was integrated and tested with version v2 of CloudConvert.

Configure CloudConvert in Cortex

Parameter Description Required
apikey API Key True
insecure Trust any certificate (not secure) False
proxy Use system proxy settings False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

cloudconvert-upload


Uploads a file for conversion.

Base Command

cloudconvert-upload

Input

Argument Name Description Required
url The file’s URL. If an entry ID is supplied, do not use this argument. Optional
entry_id The file’s War Room entry ID. If a URL is supplied, do not use this argument. Optional

Context Output

Path Type Description
CloudConvert.Task.id String Unique task ID of the scanned file.
CloudConvert.Task.status String Status of the current task.
CloudConvert.Task.message String Response message from the API.
CloudConvert.Task.operation String The operation that was performed.
CloudConvert.Task.result.files.filename String The name of the uploaded file.
CloudConvert.Task.result.files.size Number The size of the uploaded file.
CloudConvert.Task.result.files.url String The URL of the uploaded file.
CloudConvert.Task.created_at Date Time the task was created.
CloudConvert.Task.started_at Date Start time of the task.
CloudConvert.Task.ended_at Date End time of the task.
CloudConvert.Task.host_name String Name of the host used for the task.
CloudConvert.Task.storage String Storage server used for the task.
CloudConvert.Task.links String API link for the task.

Command Example

cloudconvert-upload entry_id=@123

Human Readable Output

cloudconvert-convert


Converts an uploaded file to the required format.

Base Command

cloudconvert-convert

Input

Argument Name Description Required
task_id ID of the task that uploaded the file. Required
output_format The required output format for the given file. Required

Context Output

Path Type Description
CloudConvert.Task.id String Unique task ID of the scanned file.
CloudConvert.Task.status String Status of the current task.
CloudConvert.Task.message String Response message from the API.
CloudConvert.Task.operation String The task that was performed.
CloudConvert.Task.result.files.filename String The name of the converted file.
CloudConvert.Task.result.files.size Number The size of the converted file.
CloudConvert.Task.result.files.url String The URL of the converted file.
CloudConvert.Task.created_at Date Time the task was created.
CloudConvert.Task.started_at Date Start time of the task.
CloudConvert.Task.ended_at Date End time of the task.
CloudConvert.Task.host_name String Name of the host used for the task.
CloudConvert.Task.storage String Storage server used for the task.
CloudConvert.Task.depends_on_task_ids String The ID of the previous task that was conducted on this file.
CloudConvert.Task.links String API link for the task.

Command Example

cloudconvert-convert task_id=1 output_format=pdf

Human Readable Output

cloudconvert-check-status


Checks the status of an operation. Use the ‘create_war_room_entry’ argument to also create a war room entry of the file when checking on a download operation.

Base Command

cloudconvert-check-status

Input

Argument Name Description Required
task_id ID of the task used to convert the file. Required
create_war_room_entry Whether to create an entry in the War Room if the task is finished. Possible values are: “True” and “False”. Use this argument to be able to check on a download operation in the War Room. Optional

Context Output

Path Type Description
CloudConvert.Task.id String Unique task ID of the scanned file.
CloudConvert.Task.status String Status of the current task.
CloudConvert.Task.message String Response message from the API.
CloudConvert.Task.operation String The task that was performed.
CloudConvert.Task.result.files.filename String The name of the converted file.
CloudConvert.Task.result.files.size Number The size of the converted file.
CloudConvert.Task.result.files.url String The URL of the converted file.
CloudConvert.Task.created_at Date The time the task was created.
CloudConvert.Task.started_at Date Start time of the task.
CloudConvert.Task.ended_at Date End time of the task.
CloudConvert.Task.host_name String Name of the host used for the task.
CloudConvert.Task.storage String Storage server used for the task.
CloudConvert.Task.depends_on_task_ids String The ID of the previous task conducted on this file.
CloudConvert.Task.links String API link for the task.

Command Example

cloudconvert-check-status task_id=1

Human Readable Output

cloudconvert-download


Downloads a converted file as a URL or a War Room entry.

Base Command

cloudconvert-download

Input

Argument Name Description Required
download_as ‘Whether to download the converted file as a URL or as a War Room entry. Possible values are: “url” and “war_room_entry”. Note that if you download the file as a War Room entry, a URL of the file will also be generated.’ Required
task_id ID of the task that converted the file. Required

Context Output

Path Type Description
CloudConvert.Task.id String Unique task ID of the scanned file.
CloudConvert.Task.status String Status of the current task.
CloudConvert.Task.message String Response message from the API.
CloudConvert.Task.operation String The operation that was performed.
CloudConvert.Task.result.files.filename String The file name of the downloaded file.
CloudConvert.Task.result.files.size Number The size of the downloaded file.
CloudConvert.Task.result.files.url String The URL of the downloaded file.
CloudConvert.Task.created_at Date Time the task was created.
CloudConvert.Task.started_at Date Start time of the task.
CloudConvert.Task.ended_at Date End time of the task.
CloudConvert.Task.host_name String Name of the host used for the task.
CloudConvert.Task.storage String Storage server used for the task.
CloudConvert.Task.depends_on_task_ids String The ID of the previous task conducted on this file.
CloudConvert.Task.links String API link for the task.

Command Example

cloudconvert-download task_id=1 download_as=url

Human Readable Output

Configuration parameters

  • apikey — API Key
  • apikey_creds
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (4)

  • cloudconvert-check-status

    Checks the status of an operation. Use the create_war_room_entry argument to also create a war room entry of the file when checking on a download operation.

  • cloudconvert-convert

    Converts an uploaded file to the required format.

  • cloudconvert-download

    Downloads a converted file to a URL or a War Room entry.

  • cloudconvert-upload

    Uploads a file to convert.

from typing import Any

import demistomock as demisto
import urllib3
from CommonServerPython import *

# Disable insecure warnings
urllib3.disable_warnings()


class Client(BaseClient):
    @logger
    def __init__(self, headers, verify=False, proxy=False):
        url = "https://api.cloudconvert.com/v2"
        super().__init__(url, headers=headers, verify=verify, proxy=proxy)

    @logger
    def upload_url(self, arguments: dict[str, Any]) -> dict[str, Any]:
        """
        Upload the file given as url to the API's server, for later conversion.
        Note - this operation is called 'import' by the API.
        Args:
            arguments: dict containing the request arguments, should contain the field 'url'

        Returns:
            dict containing the results of the upload action as returned from the API (status, task ID, etc.)
            ``Dict[str, Any]``
        """

        return self._http_request(
            method="POST",
            url_suffix="import/url",
            data=arguments,
            ok_codes=(200, 201, 422),
        )

    @logger
    def upload_entry_id(self, file_path: str, file_name: str) -> dict[str, Any]:
        """
        Upload the file given as a war room entry id to the API's server, for later conversion
        Note - this operation is called 'import' by the API.

        Args:
            file_path: path to given file, derived from the entry id
            file_name: name of file, including format suffix

        Returns:
            dict containing the results of the upload action as returned from the API (status, task ID, etc.)
            ``Dict[str, Any]``
        """

        response_get_form = self._http_request(method="POST", url_suffix="import/upload")
        form = dict_safe_get(response_get_form, ("data", "result", "form"), default_return_value={})

        port_url = form.get("url")
        params = form.get("parameters")

        if port_url is None or params is None:
            raise ValueError("Failed to initiate an upload operation")

        file_dict = {"file": (file_name, open(file_path, "rb"))}
        self._http_request(
            method="POST",
            url_suffix=None,
            full_url=port_url,
            files=file_dict,
            empty_valid_codes=[201, 204],
            return_empty_response=True,
            data=params,
        )

        # As shown, this operation has two requests
        # The data about the operation is within the first request's response,
        # So in order to keep the operation's data, we should return the first request's response,
        # But first we should remove fields that are no longer true, such as ones that indicates that
        # The second request has not been done yet
        if response_get_form.get("data"):
            response_get_form.get("data").pop("message", None)
            response_get_form.get("data").pop("result", None)

        return response_get_form

    @logger
    def convert(self, arguments: dict[str, Any]) -> dict[str, Any]:
        """
        Convert a file to desired format, given the file was priorly uploaded to the API's server
        Args:
            arguments: dict containing the request arguments, should contain the fields 'task_id' and 'output_format'

        Returns:
            dict containing the results of the convert action as returned from the API (status, task ID, etc.)
            ``Dict[str, Any]``
        """

        arguments["input"] = arguments.pop("task_id")
        return self._http_request(
            method="POST",
            url_suffix="convert",
            data=arguments,
            ok_codes=(200, 201, 422),
        )

    def check_status(self, arguments: dict[str, Any]) -> dict[str, Any]:
        """
        Check the status of a request sent to the API's server
        Args:
            arguments: dict containing the request arguments, should contain the field 'task_id'

        Returns:
            dict containing the results of the check status action as returned from the API (status, task ID, etc.)
            ``Dict[str, Any]``
        """

        task_id = arguments.get("task_id")
        return self._http_request(
            method="GET",
            url_suffix=f"/tasks/{task_id}",
            ok_codes=(200, 201, 422),
        )

    @logger
    def download_url(self, arguments: dict[str, Any]) -> dict[str, Any]:
        """
        Download a converted file to a url
        Note - this operation is called 'export' by the API.
        Args:
            arguments:
                dict containing the request arguments, should contain the field 'task_id' of the desired file

        Returns:
            dict containing the results of the download action as returned from the API (status, task ID, etc.)
                    if the action was complete, the result url will be a part of this dict. If the request is pending,
                    one should retrieve the url via the 'check_status' command
            ``Dict[str, Any]``

        """

        arguments["input"] = arguments.pop("task_id")
        return self._http_request(
            method="POST",
            url_suffix="/export/url",
            data=arguments,
            ok_codes=(200, 201, 422),
        )

    @logger
    def get_file_from_url(self, url: str):
        """
        Call a GET http request in order to get the file data given as url
        Args:
            url: url containing a file

        Returns:
             request response, containing the data of the file
        """

        # Saving the headers of this client instance
        # The HTTP request that gets the file data needs to have no headers
        # Passing an empty dictionary to _http_request cause it to use this client's headers by default
        session_headers = self._headers
        self._headers = {}
        try:
            results = self._http_request(
                method="GET",
                url_suffix=None,
                full_url=url,
                headers={},
                resp_type="response",
            )
            return results.content
        finally:
            self._headers = session_headers


@logger
def raise_error_if_no_data(results: dict[str, Any]):
    """
    This function checks if No 'data' field was returned from the request, meaning the input was invalid
    Args:
        results: a dict containing the request's results

    Returns:
        raises error if there is no 'data' field, with the matching error message returned from the server
        if no error message was given from the server, suggests the other optional errors
    """
    if results.get("data") is None:
        if results.get("message"):
            raise ValueError(results.get("message"))
        else:
            raise ValueError(
                "No response from server, the server could be temporary unavailable or it is handling too "
                "many requests. Please try again later."
            )


@logger
def upload_command(client: Client, arguments: dict[str, Any]):
    """
    Upload a file to the API for later conversion
    Args:
        client: CloudConvert client to use
        arguments: All command arguments - either 'url' or 'entry_id'.

    Returns:
        CommandResults object containing the results of the upload action as returned from the API and its
             readable output
    """

    if arguments.get("url"):
        if arguments.get("entry_id"):
            raise ValueError("Both url and entry id were inserted - please insert only one.")
        results = client.upload_url(arguments)

    elif arguments.get("entry_id"):
        demisto.debug("getting the path of the file from its entry id")
        result = demisto.getFilePath(arguments.get("entry_id"))
        if not result:
            raise ValueError("No file was found for given entry id")
        file_path, file_name = result["path"], result["name"]
        results = client.upload_entry_id(file_path, file_name)

    else:
        raise ValueError("No url or entry id specified.")

    raise_error_if_no_data(results)
    format_operation_title(results)
    results_data = results.get("data")

    readable_output = tableToMarkdown(
        "Upload Results",
        remove_empty_elements(results_data),
        headers=("id", "operation", "created_at", "status"),
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="CloudConvert.Task",
        outputs_key_field="id",
        raw_response=results,
        outputs=remove_empty_elements(results_data),
    )


@logger
def convert_command(client: Client, arguments: dict[str, Any]):
    """
    Convert a file that was priorly uploaded
    Args:
        client: CloudConvert client to use
        arguments: All command arguments, the fields 'task_id' and 'output_format'

    Returns:
        CommandResults object containing the results of the convert action as returned from the API and its readable output

    """

    results = client.convert(arguments)
    raise_error_if_no_data(results)
    results_data = results.get("data")
    readable_output = tableToMarkdown(
        "Convert Results",
        remove_empty_elements(results_data),
        headers=("id", "operation", "created_at", "status", "depends_on_task_ids"),
        headerTransform=string_to_table_header,
    )

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="CloudConvert.Task",
        outputs_key_field="id",
        raw_response=results,
        outputs=remove_empty_elements(results_data),
    )


@logger
def check_status_command(client: Client, arguments: dict[str, Any]):
    """
    Check status of an existing operation using it's task id
    Args:
        client: CloudConvert client to use
        arguments: All command arguments, the field 'task_id'
            Note: When the checked operation is 'download', the field 'create_war_room_entry' should be set according
             to the chosen download method, true if downloading as war room entry and false if not.
            This way a war room entry containing the file will be created if needed.

    Returns:
            CommandResults object containing the results of the check status action as returned from the API
         and its readable output OR if the argument create_war_room_entry is set to True, then a war room entry is also
         being created.

    """
    results = client.check_status(arguments)
    raise_error_if_no_data(results)
    format_operation_title(results)
    results_data = results.get("data", {})

    # If checking on an download to entry operation, manually change the operation name
    # This is because the 'download as entry' operation is our variation on the export to url operation,
    # hence not distinguished as a different operation by the API
    if argToBoolean(arguments.get("create_war_room_entry", False)) and results_data.get("operation") == "download/url":
        results["data"]["operation"] = "download/entry"

    # Check if an download to war room entry operation is finished
    # If it did - create the entry
    if (
        results_data.get("status") == "finished"
        and argToBoolean(arguments.get("create_war_room_entry", "False"))
        and results_data.get("operation") == "download/entry"
    ):
        modify_results_dict(results_data)
        url = results_data.get("url")
        file_name = results_data.get("file_name")
        file_data = client.get_file_from_url(url)
        war_room_file = fileResult(filename=file_name, data=file_data, file_type=entryTypes["entryInfoFile"])
        readable_output = tableToMarkdown(
            "Check Status Results",
            remove_empty_elements(results_data),
            headers=("id", "operation", "created_at", "status", "depends_on_task_ids", "file_name", "url"),
            headerTransform=string_to_table_header,
        )
        return_results(
            CommandResults(
                outputs_prefix="CloudConvert.Task",
                outputs_key_field="id",
                raw_response=results,
                readable_output=readable_output,
                outputs=remove_empty_elements(results_data),
            )
        )
        return war_room_file

    else:
        modify_results_dict(results_data)

        readable_output = tableToMarkdown(
            "Check Status Results",
            remove_empty_elements(results_data),
            headers=("id", "operation", "created_at", "status", "depends_on_task_ids", "file_name", "url"),
            headerTransform=string_to_table_header,
        )

        return CommandResults(
            readable_output=readable_output,
            outputs_prefix="CloudConvert.Task",
            outputs_key_field="id",
            raw_response=results,
            outputs=remove_empty_elements(results_data),
        )


def modify_results_dict(results_data: dict[str, Any]):
    """
    The results of the specific file converted/uploaded/downloaded are sub-values of some keys,
    so parse the results field to the outer scope of the dict
    Args:
        results_data: the dict under the 'data' field in the response's results

    """
    if results_data.get("result"):
        results_info = results_data.get("result", {}).get("files")
        if results_info:
            results_data["file_name"] = results_info[0].get("filename")
            results_data["url"] = results_info[0].get("url")
            results_data["size"] = results_info[0].get("size")


@logger
def download_command(client: Client, arguments: dict[str, Any]):
    """
    Download a converted file back to the user, either as a url or directly as a war room entry
    Note: in order to get the resulted url/entry of the file you need to use a check-status command as well,
        since the response of the download command is usually responded before the file is fully downloaded (hence the
    'status' field is 'waiting', and not 'finished')
    Args:
        client: CloudConvert client to use
        arguments: All command arguments, the fields 'task_id', and 'download_as' (url/war_room_entry)

    Returns:
        CommandResults object containing the results of the download action as returned from the API, and its readable
    """

    # Call download as url request
    # In both url and war room entry we still first get a url
    results = client.download_url(arguments)
    raise_error_if_no_data(results)

    # If downloading as war room entry, manually change the operation name
    # This is because the 'download as entry' operation is our variation on the export to url operation,
    # hence not distinguished as a different operation by the API
    if arguments["download_as"] == "war_room_entry":
        results["data"]["operation"] = "download/entry"
    else:
        format_operation_title(results)

    results_data = results.get("data")

    readable_output = tableToMarkdown(
        "Download Results",
        remove_empty_elements(results_data),
        headers=("id", "operation", "created_at", "status", "depends_on_task_ids"),
        headerTransform=string_to_table_header,
    )
    return CommandResults(
        readable_output=readable_output,
        outputs_prefix="CloudConvert.Task",
        outputs_key_field="id",
        raw_response=results,
        outputs=remove_empty_elements(results_data),
    )


def test_module(client: Client):
    """
    Returning 'ok' indicates that the integration works like it suppose to. Connection to the service is successful.
    Args:
        client: CloudConvert client

    Returns:
        'ok' if test passed, anything else will fail the test
    """

    dummy_url = "https://raw.githubusercontent.com/demisto/content/master/TestData/pdfworking.pdf"
    result = client.upload_url({"url": dummy_url})
    if result.get("data"):
        return "ok"
    elif result.get("message") == "Unauthenticated.":
        return "Authorization Error: make sure API Key is correctly set"
    elif result.get("message"):
        return result.get("message")
    else:
        return (
            "No response from server, the server could be temporary unavailable or it is handling too "
            "many requests. Please try again later."
        )


def format_operation_title(results: dict[str, Any]):
    """
    This function is being used in order to change the titles of the operations that are done by the API and are
    returned in the response to titles that makes more sense for the users actions, and matches the API's use in
    our system.

    Args:
        results: The response from the http request

    """
    title_exchange_dict = {"import/url": "upload/url", "import/upload": "upload/entry", "export/url": "download/url"}

    operation = results["data"]["operation"]

    results["data"]["operation"] = title_exchange_dict.get(operation, operation)


def main() -> None:
    try:
        command = demisto.command()
        params = demisto.params()
        api_key = params.get("apikey_creds", {}).get("password") or params.get("apikey")
        verify = not params.get("insecure", False)
        proxy = params.get("proxy", False)
        headers = {"Authorization": f"Bearer {api_key}"}
        client = Client(headers, verify, proxy)

        if command == "cloudconvert-upload":
            return_results(upload_command(client, demisto.args()))

        elif command == "cloudconvert-convert":
            return_results(convert_command(client, demisto.args()))

        elif command == "cloudconvert-check-status":
            return_results(check_status_command(client, demisto.args()))

        elif command == "cloudconvert-download":
            return_results(download_command(client, demisto.args()))

        elif command == "test-module":
            return_results(test_module(client))

    except Exception as e:
        err_msg = (
            "Task id not found or expired"
            if "No query results for model" in str(e)
            else ("No more conversion minutes for today for this user" if "Payment Required" in str(e) else str(e))
        )
        return_error(f"Failed to execute {command} command. Error: {err_msg}", error=traceback.format_exc())


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()