Google Resource Manager

Google Cloud Platform Resource Manager.

IT Services · Google Resource Manager

Details

IDGoogle Resource Manager
ProviderGoogle
CategoryIT Services
From Version5.0.0
Docker Imagedemisto/googleapi-python3:1.0.0.115338
Supported ModulesAgentix XSIAM

README

Configure Google Resource Manager on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Google Resource Manager.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Project ID
    • Private Key ID
    • Private Key
    • Client Email
    • Client ID
    • Client X509 Cert URL
    • Use system proxy settings
  4. Click Test to validate the URLs, token, and connection.

Commands

You can execute these commands from the Cortex XSOAR 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.

  1. Create a new project: grm-create-project
  2. Get information for a project: grm-get-project
  3. Get a list of all projects: grm-list-projects
  4. Update a project: grm-update-project
  5. Search organization resources: grm-search-organizations
  6. Get information for an organization: grm-get-organization
  7. Delete a project: grm-delete-project
  8. Restore a project: grm-undelete-project

1. Create a new project


Creates a new Project resource with the user-specified values passed as command arguments.

Base Command

grm-create-project

Input
Argument Name Description Required
project_id The unique, user-assigned ID of the Project. It must be 6 to 30 lowercase letters, digits, or hyphens. It must start with a letter. Trailing hyphens are prohibited. Example: tokyo-rain-123 Required
parent_id The ID of the parent resource. Organizations' Name output is in the format "organizations/12345" where the numbers after the forward slash are the ID of the organization. To find Organization IDs available to assign as a parent resource try running the grm-search-organization command. Required
label_keys The label keys associated with this Project. Label keys must be between 1 and 63 characters long and conform to the following regular expression: [a-z]([-a-z0-9]*[a-z0-9])?. You can associate a maximum of 256 labels with a given resource. Optional
name The user-assigned display name of the Project. It must be 4 to 30 characters. Supported characters: lowercase and uppercase letters, numbers, hyphens, single-quotes, double-quotes, spaces, and exclamation points. Example: My Project Optional
parent_type Represents the resource type the parent_id is for. Valid resource types: "organization" and "folder". Required
label_values The label values associated with this Project. Label values must be between 0 and 63 characters long and conform to the following regular expression [a-z]([-a-z0-9]*[a-z0-9])?. A label value can be empty. You can associate a maximum of 256 labels with a given resource. Optional

 

Context Output
Path Type Description
GRM.Project.Name String The user-assigned display name of the Project
GRM.Project.ID String The unique, user-assigned ID of the Project
GRM.Project.Number String The number uniquely identifying the Project
GRM.Project.State String The Project lifecycle state
GRM.Project.CreateTime Date createTime - The time the resource was created
GRM.Project.Label Unknown The labels associated with this Project
GRM.Project.Parent.ID String ID of the parent resource
GRM.Project.Parent.Type String Type of the parent resource

 

Command Example

grm-create-project project_id=faramir-111 parent_id=690006273490 parent_type=organization name="Faramir"

Context Example
{
    "GRM.Project": {
        "Name": "Faramir",
        "Parent": {
            "Type": "organization",
            "ID": "690006273490"
        },
        "Number": "110336878499",
        "Label": null,
        "State": "ACTIVE",
        "ID": "faramir-111",
        "CreateTime": "2019-01-15T12:30:21.267Z"
    }
}
Human Readable Output

Google Cloud Project Successfully Created

Name Parent Number ID State Label CreateTime
Faramir Type: organization
ID: 690006273490
110336878499 faramir-111 ACTIVE   2019-01-15T12:30:21.267Z

 

2. Getting information for a project


Retrieves the Project by the specified project_id, e.g., my-project-123.

Base Command

grm-get-project

Input
Argument Name Description Required
project_id The unique ID of the Project to fetch, e.g., tokyo-rain-123. Required

 

Context Output
Path Type Description
GRM.Project.Number String The number uniquely identifying the Project
GRM.Project.ID String The unique, user-assigned ID of the Project
GRM.Project.State String The Project lifecycle state
GRM.Project.Name String The user-assigned display name of the Project
GRM.Project.CreateTime Date createTime - The time the resource was created
GRM.Project.Label Unknown The labels associated with this Project
GRM.Project.Parent.ID String ID of the parent resource
GRM.Project.Parent.Type String Type of the parent resource

 

Command Example

grm-get-project project_id=faramir-111

Context Example
{
    "GRM.Project": {
        "Name": "Faramir",
        "Parent": {
            "Type": "organization",
            "ID": "690006273490"
        },
        "Number": "110336878499",
        "Label": null,
        "State": "ACTIVE",
        "ID": "faramir-111",
        "CreateTime": "2019-01-15T12:30:21.267Z"
    }
}
Human Readable Output

Details of Fetched Google Cloud Project

Name Parent Number ID State Label CreateTime
Faramir Type: organization
ID: 690006273490
110336878499 faramir-111 ACTIVE   2019-01-15T12:30:21.267Z

 

3. Get a list of all projects


Lists projects that are visible to the user and satisfies the specified filter. Projects are returned in an unspecified order.

Base Command

grm-list-projects

Input
Argument Name Description Required
filter An expression for filtering the results of the request. Filter rules are case insensitive. The fields eligible for filtering are:
  • name
  • id
  • labels.key (where key is the name of a label)

Examples:

  • name:how*: The project's name starts with "how".
  • name:Howl: The project's name is Howl or howl.
  • name:HOWL: Equivalent to above.
  • NAME:howl: Equivalent to above.
  • labels.color:*: The project has the label color.
  • labels.color:red: The project's label color has the value red.
Optional 

 

Context Output
Path Type Description
GRM.Project.Name String The user-assigned display name of the Project
GRM.Project.ID String The unique, user-assigned ID of the Project
GRM.Project.Number String The number uniquely identifying the Project
GRM.Project.State String The Project lifecycle state.
GRM.Project.CreateTime Date The time the resource was created
GRM.Project.Label Unknown The labels associated with this Project
GRM.Project.Parent.ID String ID of the parent resource
GRM.Project.Parent.Type String Type of the parent resource

 

Command Example

grm-list-projects filter="id:faramir*"

Context Example
{
    "GRM.Project": [
        {
            "Name": "Faramir 2",
            "Parent": {
                "Type": "organization",
                "ID": "690006273490"
            },
            "Number": "877118493152",
            "Label": null,
            "State": "ACTIVE",
            "ID": "faramir-222",
            "CreateTime": "2019-01-20T07:42:58.155Z"
        },
        {
            "Name": "Faramir",
            "Parent": {
                "Type": "organization",
                "ID": "690006273490"
            },
            "Number": "110336878499",
            "Label": null,
            "State": "ACTIVE",
            "ID": "faramir-111",
            "CreateTime": "2019-01-15T12:30:21.267Z"
        }
    ]
}
Human Readable Output

Projects Filtered by 'id:faramir*'

Name Parent Number ID State Label CreateTime
Faramir 2 Type: organization
ID: 690006273490
877118493152 faramir-222 ACTIVE   2019-01-20T07:42:58.155Z
Faramir Type: organization
ID: 690006273490
110336878499 faramir-111 ACTIVE   2019-01-15T12:30:21.267Z

 

4. Update a project


Updates the attributes of the Project identified by the specified project_id. Currently the only fields that can be updated are the project name and labels.

Base Command

grm-update-project

Input
Argument Name Description Required
project_id The unique ID of the Project to update.
Example: tokyo-rain-123
Required
name The string to update the Project name with. It must be 4 to 30 characters. Supported characters are: lowercase and uppercase letters, numbers, hyphens, single-quotes, double-quotes, spaces, and exclamation points.
Example: My Project
Optional
parent_id The ID of the parent resource. Organizations' Name output is in the format organizations/12345 where the numbers after the forward slash are the ID of the organization. To find Organization IDs available to assign as a parent resource try running the grm-search-organization command. Required
parent_type The resource type the parent_id is for. Valid resource types: "organization" and "folder". Required
label_keys The label keys to associate with this Project. Label keys must be between 1 and 63 characters long and conform to the following regular expression: [a-z]([-a-z0-9]*[a-z0-9])? You can associate a maximum of 256 labels with a given resource. Optional
label_values The label values to associate with this Project. Label values must be between 0 and 63 characters long and conform to the following regular expression [a-z]([-a-z0-9]*[a-z0-9])?. A label value can be empty. You can associate a maximum of 256 labels with a given resource. Optional

 

Context Output
Path Type Description
GRM.Project.Name String The user-assigned display name of the Project
GRM.Project.ID String The unique, user-assigned ID of the Project
GRM.Project.Number String The number uniquely identifying the Project
GRM.Project.State String The Project lifecycle state.
GRM.Project.CreateTime Date The time the resource was created
GRM.Project.Label Unknown The labels associated with this Project
GRM.Project.Parent.ID String ID of the parent resource
GRM.Project.Parent.Type String Type of the parent resource

 

Command Example

grm-update-project project_id=faramir-111 parent_id=690006273490 parent_type=organization name="Faramir-Updated"

Context Example
{
    "GRM.Project": {
        "Name": "Faramir-Updated",
        "Parent": {
            "Type": "organization",
            "ID": "690006273490"
        },
        "Number": "110336878499",
        "Label": null,
        "State": "ACTIVE",
        "ID": "faramir-111",
        "CreateTime": "2019-01-15T12:30:21.267Z"
    }
}
Human Readable Output

Details of Updated Google Cloud Project

Name Parent Number ID State Label CreateTime
Faramir-Updated Type: organization
ID: 690006273490
110336878499 faramir-111 ACTIVE   2019-01-15T12:30:21.267Z

 

5. Search organization resources


Searches Organization resources that are visible to the user and satisfies the specified filter. Organizations are returned in an unspecified order. New Organizations do not necessarily appear at the end of the results.

Base Command

grm-search-organizations

Input
Argument Name Description Required
page_size The maximum number of Organizations to return in the response. Optional
page_token A pagination token returned from a previous call to "organizations.search" that indicates from where the listing should continue Optional
filter An expression for filtering the Organizations returned in the results. Filter rules are case-insensitive. Organizations can be filtered by 'owner.directoryCustomerId' or by 'domain', where the domain is a G Suite domain, e.g., owner.directorycustomerid:123456789 Organizations with owner.directory_customer_id equal to 123456789. domain:google.com Organizations corresponding to the domain google.com. Optional

 

Context Output
Path Type Description
GRM.Organization.Name String The resource name of the organization. This is the organization's relative path in the API. Its format is "organizations/[organization_id]" e.g. "organizations/1234".
GRM.Organization.State String The organization's current lifecycle state
GRM.Organization.CreateTime Date The time the organization resource was created
GRM.Organization.Owner.CustomerID String The G Suite customer ID used in the Directory API

 

Command Example

grm-search-organizations

Context Example
{
    "GRM.Organization": [
        {
            "Owner": {
                "CustomerID": "C02f0zfqw"
            },
            "State": "ACTIVE",
            "CreateTime": "2017-04-25T13:41:05.196Z",
            "Name": "organizations/690006273490"
        }
    ]
}
Human Readable Output

Organizations

Owner State Name CreateTime
CustomerID: C02f0zfqw ACTIVE organizations/690006273490 2017-04-25T13:41:05.196Z

 

6. Get information for an organization


Returns an Organization resource identified by the specified resource name.

Base Command

grm-get-organization

Input
Argument Name Description Required
name The resource name of the Organization to fetch, e.g., "organizations/1234". Required

 

Context Output
Path Type Description
GRM.Organization.Name String The resource name of the organization. This is the organization's relative path in the API. Its format is "organizations/[organization_id]" e.g., "organizations/1234"
GRM.Organization.State String The organization's current lifecycle state
GRM.Organization.CreateTime Date The time the organization resource was created
GRM.Organization.Owner.CustomerID String The G Suite customer ID used in the Directory API

 

Command Example

grm-get-organization name=organizations/690006273490

Context Example
{
    "GRM.Organization": {
        "Owner": {
            "CustomerID": "C02f0zfqw"
        },
        "State": "ACTIVE",
        "CreateTime": "2017-04-25T13:41:05.196Z",
        "Name": "organizations/690006273490"
    }
}
Human Readable Output

Details of Fetched Organization

Owner State Name CreateTime
CustomerID: C02f0zfqw ACTIVE organizations/690006273490 2017-04-25T13:41:05.196Z

 

7. Delete an organization


Marks the Project identified by the specified project_id to be deleted, e.g., my-project-123.

Base Command

grm-delete-project

Input
Argument Name Description Required
project_id The unique ID of the Project to delete. Example: tokyo-rain-123 Required

 

Context Output
Path Type Description
GRM.Project.State String The Project lifecycle state
GRM.Project.Number String The number uniquely identifying the Project
GRM.Project.ID String The unique, user-assigned ID of the Project
GRM.Project.Name String The user-assigned display name of the Project
GRM.Project.CreateTime Date createTime - The time the resource was created
GRM.Project.Label Unknown The labels associated with this Project
GRM.Project.Parent.ID String ID of the parent resource
GRM.Project.Parent.Type String Type of the parent resource

 

Command Example

grm-delete-project project_id=faramir-111

Context Example
{
    "GRM.Project": {
        "Name": "Faramir-Updated",
        "Parent": {
            "Type": "organization",
            "ID": "690006273490"
        },
        "Number": "110336878499",
        "Label": null,
        "State": "DELETE_REQUESTED",
        "ID": "faramir-111",
        "CreateTime": "2019-01-15T12:30:21.267Z"
    }
}
Human Readable Output

Project State Successfully Set To DELETE_REQUESTED

Name Parent Number ID State Label CreateTime
Faramir-Updated Type: organization
ID: 690006273490
110336878499 faramir-111 DELETE_REQUESTED   2019-01-15T12:30:21.267Z

 

8. Restore a project


Restores the Project identified by the specified project_id, e.g., my-project-123.

Base Command

grm-undelete-project

Input
Argument Name Description Required
project_id The unique ID of the Project to restore. Example: tokyo-rain-123 Required

 

Context Output
Path Type Description
GRM.Project.State String The Project lifecycle state
GRM.Project.Number String The number uniquely identifying the Project
GRM.Project.ID String The unique, user-assigned ID of the Project
GRM.Project.Name String The user-assigned display name of the Project
GRM.Project.CreateTime Date The time the resource was created
GRM.Project.Label Unknown The labels associated with this Project
GRM.Project.Parent.ID String ID of the parent resource
GRM.Project.Parent.Type String Type of the parent resource

 

Command Example

grm-undelete-project project_id=faramir-111

Context Example
{
    "GRM.Project": {
        "Name": "Faramir-Updated",
        "Parent": {
            "Type": "organization",
            "ID": "690006273490"
        },
        "Number": "110336878499",
        "Label": null,
        "State": "ACTIVE",
        "ID": "faramir-111",
        "CreateTime": "2019-01-15T12:30:21.267Z"
    }
}
Human Readable Output

Project State Successfully Set To ACTIVE

Name Parent Number ID State Label CreateTime
Faramir-Updated Type: organization
ID: 690006273490
110336878499 faramir-111 ACTIVE   2019-01-15T12:30:21.267Z

Configuration parameters

  • project_id — Project ID (required)
  • private_key_id — Private Key ID
  • private_key_id_creds
  • private_key — Private Key
  • private_key_creds
  • client_email — Client Email
  • client_id — Client ID
  • client_email_creds — Client Email
  • client_x509_cert_url — Client X509 Cert URL (required)
  • proxy — Use system proxy settings
  • insecure — Trust any certificate (not secure)

Commands (8)

  • grm-create-project

    Request that new Project resource be created with the user-specified values passed as command arguments.

  • grm-delete-project

    Marks the Project identified by the specified project_id to be deleted, e.g., my-project-123.

  • grm-get-organization

    Fetches an Organization resource identified by the specified resource name.

  • grm-get-project

    Retrieves the Project by the specified project_id, e.g., my-project-123.

  • grm-list-projects

    Lists projects that are visible to the user and satisfies the specified filter. Projects are returned in an unspecified order.

  • grm-search-organizations

    Searches Organization resources that are visible to the user and satisfies the specified filter. Organizations are returned in an unspecified order. New Organizations do not necessarily appear at the end of the results.

  • grm-undelete-project

    Restores the Project identified by the specified project_id, e.g., my-project-123.

  • grm-update-project

    Updates the attributes of the Project identified by the specified project_id. Currently the only fields that can be updated are the project name and labels.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401
import json
import time
import urllib.parse
import httplib2

import googleapiclient
from oauth2client import service_account
from googleapiclient import discovery


""" IMPORTS """


""" GLOBALS/PARAMS """

# Params for assembling object of the Service Account Credentials File Contents
PARAMS = demisto.params()
SERVICE_ACT_PROJECT_ID = PARAMS.get("project_id")
PRIVATE_KEY_ID = PARAMS.get("private_key_id_creds", {}).get("password") or PARAMS.get("private_key_id")
PRIVATE_KEY = PARAMS.get("private_key_creds", {}).get("password") or PARAMS.get("private_key")
CLIENT_EMAIL = PARAMS.get("client_email_creds", {}).get("identifier") or PARAMS.get("client_email")
CLIENT_ID = PARAMS.get("client_email_creds", {}).get("password") or PARAMS.get("client_id")
CLIENT_X509_CERT_URL = PARAMS.get("client_x509_cert_url")
PROXY = PARAMS.get("proxy")
DISABLE_SSL = PARAMS.get("insecure")

AUTH_JSON = {
    "type": "service_account",  # guardrails-disable-line
    "project_id": SERVICE_ACT_PROJECT_ID,
    "private_key_id": PRIVATE_KEY_ID,
    "private_key": PRIVATE_KEY.replace("\\n", "\n"),
    "client_email": CLIENT_EMAIL,
    "client_id": CLIENT_ID,
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": CLIENT_X509_CERT_URL,
}

# Params for constructing googleapiclient service object
API_VERSION = "v1"
GRM = "cloudresourcemanager"
SCOPE = ["https://www.googleapis.com/auth/cloud-platform"]


""" HELPER FUNCTIONS """


# disable-secrets-detection-start
def get_http_client_with_proxy():
    proxies = handle_proxy()
    if not proxies or not proxies["https"]:
        raise Exception("https proxy value is empty. Check Demisto server configuration")
    https_proxy = proxies["https"]
    if not https_proxy.startswith("https") and not https_proxy.startswith("http"):
        https_proxy = "https://" + https_proxy
    parsed_proxy = urllib.parse.urlparse(https_proxy)
    proxy_info = httplib2.ProxyInfo(
        proxy_type=httplib2.socks.PROXY_TYPE_HTTP,  # disable-secrets-detection
        proxy_host=parsed_proxy.hostname,
        proxy_port=parsed_proxy.port,
        proxy_user=parsed_proxy.username,
        proxy_pass=parsed_proxy.password,
    )
    return httplib2.Http(proxy_info=proxy_info, disable_ssl_certificate_validation=DISABLE_SSL)


# disable-secrets-detection-end


def get_credentials_obj():
    """Gets valid user credentials from storage.

    Returns:
        Credentials, the obtained credential.
    """
    cred = service_account.ServiceAccountCredentials.from_json_keyfile_dict(AUTH_JSON, scopes=SCOPE)  # type: ignore

    return cred.create_delegated(CLIENT_EMAIL)


def build_and_authenticate():
    """
    Return a service object via which can call GRM API.

    Use the service_account credential file generated in the Google Cloud
    Platform to build the Google Resource Manager API Service object.

    returns: service
        Google Resource Manager API Service object via which commands in the
        integration will make API calls
    """
    service_credentials = get_credentials_obj()

    if PROXY or DISABLE_SSL:
        http_client = service_credentials.authorize(get_http_client_with_proxy())
        return discovery.build(GRM, API_VERSION, http=http_client)
    else:
        handle_proxy()

    return discovery.build(GRM, API_VERSION, credentials=service_credentials)


def make_project_body(project_body):
    """
    Create and return the project body argument used when calling the GRM API
    to create or update a project.

    returns: (dict) body
        dict object formatted to be used in the create or update API call
    """
    keys = list(project_body.keys())
    body = {}
    if "project_id" in keys:
        body["projectId"] = project_body["project_id"]
    if "parent_type" in keys and "parent_id" in keys:
        body["parent"] = {"type": project_body["parent_type"], "id": project_body["parent_id"]}
    if "name" in keys:
        body["name"] = project_body["name"]
    if "label_keys" in keys and "label_values" in keys:
        label_keys = argToList(project_body["label_keys"])
        label_values = argToList(project_body["label_values"])
        if len(label_keys) != len(label_values):
            err_msg = (
                "Label attrs array and Label values array do not match"
                " in length.\nThese arrays need to match in length because "
                "each string in label_keys is assigned the value of the "
                "string in the corresponding index in the label_values array."
            )
            return_error(err_msg)
        else:
            body["labels"] = {}
            for lbl_key, lbl_val in zip(label_keys, label_values):
                body["labels"][lbl_key] = lbl_val
    return body


def poll_operation(operation):
    """
    Query status of long running operation and return results if completed.

    parameter: (Operation) operation
        operation object returned from calls to the API that have a long
        execution time

    raises: Exception
        if long executing operation results in an error then raises an
        Exception that the operation was unsuccessful

    returns: (Project) response
        dict object representation of a Project Resource
    """
    name = operation.get("name")
    while not operation.get("done"):
        # delay 1 second and then retry and see if the operation finished
        time.sleep(1)
        service = build_and_authenticate()
        # get the latest state of the long-running operation
        operation = service.operations().get(name=name).execute()
    if not operation.get("error"):
        return operation.get("response")
    else:
        exc = operation.get("error")
        err_code = exc.get("code")
        err_msg = exc.get("message")
        full_err_msg = f"error code: {err_code}\nerror message: {err_msg}"
        return_error(full_err_msg)  # noqa: RET503


""" MAIN FUNCTIONS """


def test_module():
    """If the list_projects_command executes successfully then the test completed and returns 'ok'"""
    build_and_authenticate()
    demisto.results("ok")


def create_project(service, project_body):
    """Build service object and return the result of calling the API 'create' function for the projects resource."""
    body = make_project_body(project_body)
    operation = service.projects().create(body=body).execute()
    # Get back result of long-running operation
    response = poll_operation(operation)
    return response


def create_project_command(service):
    """
    Create a project in the Google Cloud Platform.

    demisto parameter: (string) project_id
        The unique ID of the Project to create

    demisto parameter: (string) name
        The name to give the new Project

    demisto parameter: (string) parent_id
        The id of the parent resource

    demisto parameter: (string) parent_type
        The resource type the parent_id is for

    demisto parameter: (list) label_keys
        The label keys to associate with the new Project

    demisto parameter: (list) label_values
        The label values to associate with the new Project. The values are
        assigned to their corresponding indexed key in label_keys

    returns:
        The new Project resource object
    """
    args = demisto.args()
    response = create_project(service, args)
    # Parse response into context
    context = {
        "Name": response.get("name"),
        "ID": response.get("projectId"),
        "Number": response.get("projectNumber"),
        "State": response.get("lifecycleState"),
        "CreateTime": response.get("createTime"),
        "Parent": {"ID": response.get("parent").get("id"), "Type": response.get("parent").get("type")},
        "Label": response.get("labels"),
    }
    md = tableToMarkdown("Google Cloud Project Successfully Created", context)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "Contents": response,
            "ContentsFormat": formats["json"],
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": md,
            "EntryContext": {"GRM.Project(val.ID && val.ID === obj.ID)": context},
        }
    )


def delete_project(service, project_id):
    """Build service object and return the result of calling the API 'delete' function for the projects resource."""
    operation = service.projects().delete(projectId=project_id).execute()
    return operation


def delete_project_command(service):
    """
    Deletes the specified project.

    Set the lifcycleState attribute of a specified project to DELETE_REQUESTED.

    demisto parameter: (string) project_id
        The unique ID of the Project to restore

    returns:
        Deleted project resource object
    """
    project_id = demisto.args()["project_id"]
    response = delete_project(service, project_id)
    if not response:
        response = get_project(service, project_id)
        # Parse response into context
        context = {
            "Name": response.get("name"),
            "ID": response.get("projectId"),
            "Number": response.get("projectNumber"),
            "State": response.get("lifecycleState"),
            "CreateTime": response.get("createTime"),
            "Parent": {"ID": response.get("parent").get("id"), "Type": response.get("parent").get("type")},
            "Label": response.get("labels"),
        }
        md = tableToMarkdown("Project State Successfully Set To DELETE_REQUESTED", context)
        demisto.results(
            {
                "Type": entryTypes["note"],
                "Contents": response,
                "ContentsFormat": formats["json"],
                "ReadableContentsFormat": formats["markdown"],
                "HumanReadable": md,
                "EntryContext": {"GRM.Project(val.ID && val.ID === obj.ID)": context},
            }
        )
    else:
        return_error(f"Unexpected return object from {demisto.command()} execution. Results uncertain.")


def undelete_project(service, project_id):
    """Build service object and return the result of calling the API 'undelete' function for the projects resource."""
    operation = service.projects().undelete(projectId=project_id).execute()
    return operation


def undelete_project_command(service):
    """
    Restores the specified project.

    Sets the lifcycleState attribute of a specified project back to ACTIVE.

    demisto parameter: (string) project_id
        The unique ID of the Project to restore

    returns:
        Restored project resource object
    """
    project_id = demisto.args()["project_id"]
    response = undelete_project(service, project_id)
    if not response:
        response = get_project(service, project_id)
        # Parse response into context
        context = {
            "Name": response.get("name"),
            "ID": response.get("projectId"),
            "Number": response.get("projectNumber"),
            "State": response.get("lifecycleState"),
            "CreateTime": response.get("createTime"),
            "Parent": {"ID": response.get("parent").get("id"), "Type": response.get("parent").get("type")},
            "Label": response.get("labels"),
        }
        md = tableToMarkdown("Project State Successfully Set To ACTIVE", context)
        demisto.results(
            {
                "Type": entryTypes["note"],
                "Contents": response,
                "ContentsFormat": formats["json"],
                "ReadableContentsFormat": formats["markdown"],
                "HumanReadable": md,
                "EntryContext": {"GRM.Project(val.ID && val.ID === obj.ID)": context},
            }
        )
    else:
        return_error(f"Unexpected return object from {demisto.command()} execution. Results uncertain.")


def get_project(service, project_id):
    """Build service object and return the result of calling the API 'get' function for the projects resource."""
    operation = service.projects().get(projectId=project_id).execute()
    return operation


def get_project_command(service):
    """
    Retrieves the Project identified by the specified project_id.

    demisto parameter: (string) project_id
        The unique ID of the Project to fetch

    returns:
        The project resource object specified by the project_id
    """
    project_id = demisto.args().get("project_id")
    response = get_project(service, project_id)
    # Parse response into context
    context = {
        "Name": response.get("name"),
        "ID": response.get("projectId"),
        "Number": response.get("projectNumber"),
        "State": response.get("lifecycleState"),
        "CreateTime": response.get("createTime"),
        "Parent": {"ID": response.get("parent").get("id"), "Type": response.get("parent").get("type")},
        "Label": response.get("labels"),
    }
    md = tableToMarkdown("Details of Fetched Google Cloud Project", context)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "Contents": response,
            "ContentsFormat": formats["json"],
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": md,
            "EntryContext": {"GRM.Project(val.ID && val.ID === obj.ID)": context},
        }
    )


def list_projects(service, filter_list):
    """Build service object and return the result of calling the API 'list' function for the projects resource."""
    operation = service.projects().list(filter=filter_list).execute()
    return operation


def list_projects_command(service):
    """
    Lists Projects that are visible to the user and satisfy the specified
    filter if one is provided.

    demisto parameter: (string) filter -- optional
        An expression for filtering the results of the request

    returns:
        Project resource objects that are visible to the user and satisfy the specified filter
    """
    filter_list = demisto.args().get("filter") if "filter" in demisto.args() else None
    response = list_projects(service, filter_list)
    contexts = []
    for project in response.get("projects", []):
        # Parse project into context
        context = {
            "Name": project.get("name"),
            "ID": project.get("projectId"),
            "Number": project.get("projectNumber"),
            "State": project.get("lifecycleState"),
            "CreateTime": project.get("createTime"),
            "Parent": {"ID": project.get("parent").get("id"), "Type": project.get("parent").get("type")},
            "Label": project.get("labels"),
        }
        contexts.append(context)
    title = f"Projects Filtered by '{filter_list}'" if filter_list else "All Projects"
    md = tableToMarkdown(title, contexts)
    entry = {
        "Type": entryTypes["note"],
        "Contents": response,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": md,
        "EntryContext": {"GRM.Project(val.ID && val.ID === obj.ID)": contexts},
    }
    demisto.results(entry)


def update_project(service, project_id, project_body):
    """Build service object and return the result of calling the API 'update' function for the projects resource."""
    operation = service.projects().update(projectId=project_id, body=project_body).execute()
    return operation


def update_project_command(service):
    """
    Updates the attributes of the Project identified by the specified project_id.

    demisto parameter: (string) project_id
        The unique ID of the Project to update

    demisto parameter: (string) name
        The string to update the Project name with

    demisto parameter: (string) parent_id
        The id of the parent resource

    demisto parameter: (string) parent_type
        The resource type the parent_id is for

    demisto parameter: (list) label_keys
        The label keys to associate with this Project

    demisto parameter: (list) label_values
        The label values to associate with this Project. The values are
        assigned to their corresponding indexed key in label_keys

    returns:
        The updated Project resource object
    """
    project_id = demisto.args().get("project_id")
    project_body = demisto.args()
    project_body = make_project_body(project_body)
    response = update_project(service, project_id, project_body)
    # Parse response into context
    context = {
        "Name": response.get("name"),
        "ID": response.get("projectId"),
        "Number": response.get("projectNumber"),
        "State": response.get("lifecycleState"),
        "CreateTime": response.get("createTime"),
        "Parent": {"ID": response.get("parent").get("id"), "Type": response.get("parent").get("type")},
        "Label": response.get("labels"),
    }
    md = tableToMarkdown("Details of Updated Google Cloud Project", context)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "Contents": response,
            "ContentsFormat": formats["json"],
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": md,
            "EntryContext": {"GRM.Project(val.ID && val.ID === obj.ID)": context},
        }
    )


def search_organizations(service, req_body):
    """
    Build service object and return the result of calling the API 'search' function for the organizations resource.
    """
    operation = service.organizations().search(body=req_body).execute()
    return operation


def search_organizations_command(service):
    """
    Searches Organization resources that are visible to the user and satisfy
    the specified filter (if provided).

    demisto parameter: (number) pageSize -- optional
        The maximum number of Organizations to return in the response

    demisto parameter: (string) pageToken -- optional
        A pagination token returned from a previous call to organizations.search()
        that indicates from where listing should continue

    demisto parameter: (string) filter -- optional
        Used to filter the Organizations to return in the response

    returns:
        List of Organization resource objects that are visible to the user and satisfy the specified filter
    """
    args = demisto.args()
    # make request body
    req_body = {
        "pageSize": args.get("page_size") if "page_size" in args else None,
        "pageToken": args.get("page_token") if "page_token" in args else None,
        "filter": args.get("filter") if "filter" in args else None,
    }

    contexts = []
    contents = []
    next_page = True
    # continue calling the API with the appropriate pageToken argument while there are still results to be received
    # from the search organizations API call - this is useful in the case that a pageSize argument was given in this
    # command that was less than the total amount of results returned from the original call to the API
    while next_page:
        response = search_organizations(service, req_body)
        contents.append(response)
        for organization in response.get("organizations", []):
            # Parse organization into context
            context = {
                "Name": organization.get("name"),
                "State": organization.get("lifecycleState"),
                "CreateTime": organization.get("creationTime"),
                "Owner": {"CustomerID": organization.get("owner").get("directoryCustomerId")},
            }
            contexts.append(context)
        if "nextPageToken" not in response:
            next_page = False
        else:
            req_body["pageToken"] = response["nextPageToken"]
    md = tableToMarkdown("Organizations", contexts)
    entry = {
        "Type": entryTypes["note"],
        "Contents": contents,
        "ContentsFormat": formats["json"],
        "ReadableContentsFormat": formats["markdown"],
        "HumanReadable": md,
        "EntryContext": {"GRM.Organization(val.Name && val.Name === obj.Name)": contexts},
    }
    demisto.results(entry)


def get_organization(service, name):
    """
    Build service object and return the result of calling the API 'get' function for the organizations resource.

    parameter: (string) name
        name of the Organization in format <type/number> e.g. 'organizations/1245345444'

    returns: (Organization) operation
        The response from calling the API that takes the form of a Organization object
    """
    operation = service.organizations().get(name=name).execute()
    return operation


def get_organization_command(service):
    """
    Fetches an Organization resource identified by the specified resource name.

    demisto parameter: (string) name
        name of the Organization in format <type/number> e.g. 'organizations/1245345444'

    returns:
        The organization object with its associated fields
    """
    name = demisto.args().get("name")
    response = get_organization(service, name)
    # Parse response into context
    context = {
        "Name": response.get("name"),
        "State": response.get("lifecycleState"),
        "CreateTime": response.get("creationTime"),
        "Owner": {"CustomerID": response.get("owner").get("directoryCustomerId")},
    }
    md = tableToMarkdown("Details of Fetched Organization", context)
    demisto.results(
        {
            "Type": entryTypes["note"],
            "Contents": response,
            "ContentsFormat": formats["json"],
            "ReadableContentsFormat": formats["markdown"],
            "HumanReadable": md,
            "EntryContext": {"GRM.Organization(val.Name && val.Name === obj.Name)": context},
        }
    )


""" COMMANDS MANAGER / SWITCH PANEL """


def main():
    # Command Switch Panel
    commands = {
        "grm-create-project": create_project_command,
        "grm-delete-project": delete_project_command,
        "grm-get-project": get_project_command,
        "grm-list-projects": list_projects_command,
        "grm-update-project": update_project_command,
        "grm-search-organizations": search_organizations_command,
        "grm-get-organization": get_organization_command,
        "grm-undelete-project": undelete_project_command,
    }

    LOG(f"Command being called is {demisto.command()}")

    try:
        if demisto.command() == "test-module":
            # This is the call made when pressing the integration test button.
            test_module()
        elif demisto.command() in list(commands.keys()):
            service = build_and_authenticate()
            commands[demisto.command()](service)

    except Exception as exc:
        # Output HttpError errors from googleapiclient to the warroom nicely
        if isinstance(exc, googleapiclient.errors.HttpError):
            if exc.resp.get("content-type").startswith("application/json"):  # pylint: disable=no-member
                err_json = json.loads(exc.content.decode("utf-8"))  # pylint: disable=no-member
                error_code = dict_safe_get(err_json, ["error", "code"])
                error_msg = dict_safe_get(err_json, ["error", "message"])
                error_reason = dict_safe_get(err_json, ["error", "errors", 0, "reason"])
                error_status = dict_safe_get(err_json, ["error", "status"])
                full_err_msg = f"error code: {error_code}\n{error_msg}\nreason: {error_reason}\nstatus: {error_status}"
                return_error(full_err_msg)
        else:
            return_error(str(exc))


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