Skip to content

Custom scripts

Custom scripts is a type of plugin that allows defining custom scripts that are executed at different lifecycle events of the resource. The scripts are executed in one time containers. Depending on the deployment type, it can be either a docker container for docker-compose-based, or Kubernetes Jobs for Helm-based deployments.

The following lifecycle events are supported:

  • Creation;
  • Update - change of plans or limits;
  • Termination;
  • Regular updates - executed once per hour, aka pull script.

Script output format

It is possible to control certain aspects of resource management with outputs of the custom scripts. Below we list currently supported conventions and their impact.

Creation script

You can set the the backend_id of the created resource by passing a single string as the last line of the output.

1
2
3
4
5
# for python-based scripts
import uuid

UUID = uuid.uuid4()
print(UUID)

If you want to save additional metadata, then last line of output should consist of 2 strings:

  • ID of the created resource that will be saved as backend_id;
  • Base64 encoded metadata object.
1
2
3
4
5
6
7
# for python-based scripts
import base64
import uuid

UUID = uuid.uuid4()
metadata = {"backend_metadata": {"cpu": 1}}
print(UUID + ' ' + base64.b64encode(metadata))

Regular updates script

The script for regular updates allows to update usage information as well as provide updates of reporting. In all cases the last line should include a base64-encoded string containing a dictionary with keywords:

  • "usages" for usage reporting;
  • "report" for updating resource report.

Examples of Python-based scripts are:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# for python-based scripts
import base64

info = {
  "usages": [
    {
      "type": "cpu",
      "amount": 10
    },
  ]
}

info_json = json.dumps(info)

info_json_encoded = info_json.encode("utf-8")

print(base64.b64encode(info_json_encoded).decode("utf-8"))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# for python-based scripts
import base64

info = {
  "report": [
    {
      "header": "header",
      "body": "body"
    },
  ]
}

info_json = json.dumps(info)

info_json_encoded = info_json.encode("utf-8")

print(base64.b64encode(info_json_encoded).decode("utf-8"))

Example scripts

Each of the scripts below require access to remote Waldur instance. Credentials for this passed as environment variables to the scripts with keys:

  • WALDUR_API_URL - URL of remote Waldur API including /api/ path, example: http://localhost/api/
  • WALDUR_API_TOKEN - token for a remote user with permissions of service provider owner

Script for resource creation

In the remote Waldur site, customer and offering should be pre-created for successful resource creation. Please, add the necessary variables to the local offering's environment:

  • REMOTE_CUSTOMER_NAME - name of the pre-created customer in the remote Waldur
  • REMOTE_OFFERING_UUID - UUID of the remote offering for creation of the remote resource
  • PROJECT_NAME - name of the remote project to be created
  • PI_EMAILS - optional comma-separated list of emails receiving invitations to the project after creation of the remote resource
  • REMOTE_PROJECT_CREDIT_AMOUNT - optional amount of credit applied to the remote project
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
from waldur_client import WaldurClient
from os import environ
from time import sleep
import uuid
import json

waldur_client = WaldurClient(environ["WALDUR_API_URL"], environ["WALDUR_API_TOKEN"])

CUSTOMER_NAME = environ["REMOTE_CUSTOMER_NAME"]
OFFERING_UUID = environ["REMOTE_OFFERING_UUID"]
PROJECT_NAME = environ["REMOTE_PROJECT_NAME"]
PI_EMAILS = environ.get("PI_EMAILS")
RESOURCE_LIMITS = environ["LIMITS"]
PROJECT_CREDIT_AMOUNT = environ.get("REMOTE_PROJECT_CREDIT_AMOUNT")


def get_or_create_project():
    print(f"Listing customers with name_exact: {CUSTOMER_NAME}")
    existing_customers = waldur_client.list_customers({"name_exact": CUSTOMER_NAME})
    if len(existing_customers) == 0:
        print(f"Customer with name {CUSTOMER_NAME} not found")
        exit(1)
    else:
        print(f"Customer with name {CUSTOMER_NAME} exists")
        customer = existing_customers[0]

    customer_uuid = customer["uuid"]

    print(f"Listing projects with name_exact: {PROJECT_NAME}")
    existing_projects = waldur_client.list_projects({"name_exact": PROJECT_NAME})
    if len(existing_projects) == 0:
        print(f"Project with name {PROJECT_NAME} not found, creating it")
        return waldur_client.create_project(customer_uuid, PROJECT_NAME)
    else:
        print(f"Project with name {PROJECT_NAME} exists")
        return existing_projects[0]


def get_or_create_project_credits():
    print(f"Listing project credits for project_uuid: {project_uuid}")
    project_credits = waldur_client.list_project_credits({"project_uuid": project_uuid})
    if len(project_credits) == 0:
        print(
            f"Project credit for project_uuid {project_uuid} not found, creating it with amount {PROJECT_CREDIT_AMOUNT}"
        )
        return waldur_client.create_project_credit(
            project_uuid, PROJECT_CREDIT_AMOUNT
        )
    else:
        print(f"Project credit for project_uuid {project_uuid} exists")
        return project_credits[0]


def invite_PIs():
    print("Listing active roles")
    roles = waldur_client.get_roles(params={"is_active": True})
    print('Looking up role with name "PROJECT.MANAGER"')
    project_manager_role = [
        role for role in roles if role["name"] == "PROJECT.MANAGER"
    ][0]
    project_manager_role_uuid = project_manager_role["uuid"]

    print("Inviting PIs")
    for pi_email in PI_EMAILS.split(","):
        if not pi_email:
            continue
        print(f"Creating project invitation for email: {pi_email}")
        waldur_client.create_project_invitation(
            pi_email, project_uuid, project_manager_role_uuid
        )


def create_resource(resource_name):
    print(f"Fetching marketplace provider offerings with UUID: {OFFERING_UUID}")
    offering = waldur_client.get_marketplace_provider_offering(OFFERING_UUID)
    print("Getting first plan UUID from offering")
    plan_uuid = offering["plans"][0]["uuid"]
    resource_attributes = {
        "name": resource_name,
    }
    resource_limits = json.loads(RESOURCE_LIMITS)

    print("Submitting order")
    order_metadata = waldur_client.create_resource_via_marketplace(
        project_uuid, OFFERING_UUID, plan_uuid, resource_attributes, resource_limits
    )
    print("Fetching order")
    create_order_uuid = order_metadata["create_order_uuid"]
    resource_uuid = order_metadata["marketplace_resource_uuid"]
    order = waldur_client.get_order(create_order_uuid)

    print("Approving order")
    waldur_client.marketplace_order_approve_by_provider(order["uuid"])
    order = waldur_client.get_order(create_order_uuid)

    max_retries = 10
    retry_count = 0
    print("Waiting for order to be done")
    while order["state"] != "done" and retry_count < max_retries:
        print(f"Order state: {order['state']}")
        order = waldur_client.get_order(order["uuid"])
        sleep(5)
        retry_count += 1

    if order["state"] != "done":
        print(f"Order execution timed out, state is {order['state']}")
        exit(1)

    print("Order is done")

    print(f"Fetching marketplace provider resource with UUID: {resource_uuid}")
    resource = waldur_client.get_marketplace_provider_resource(resource_uuid)

    print(f'Resource state is {resource["state"]}')
    return resource

unique_id = uuid.uuid4().hex
resource_name = f"portal-test-{unique_id}"

project = get_or_create_project()
project_uuid = project["uuid"]

if PROJECT_CREDIT_AMOUNT is not None:
    get_or_create_project_credits()

resource = create_resource(resource_name)

if PI_EMAILS is not None:
    invite_PIs()

print("Execution finished")

print(resource["uuid"])

Script for usage pull

This script periodically pulls usage data of the remote resource and saves it locally.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from waldur_client import WaldurClient
from os import environ
import datetime
import base64
import json

waldur_client = WaldurClient(environ["WALDUR_API_URL"], environ["WALDUR_API_TOKEN"])

RESOURCE_UUID = environ["RESOURCE_BACKEND_ID"]

current_date = datetime.datetime.now()
month_start = datetime.datetime(day=1, month=current_date.month, year=current_date.year).date()

print(f"Fetching resource usages from {month_start.isoformat()}")
resource_usages = waldur_client.list_component_usages(
    RESOURCE_UUID,
    month_start,
)

usages_data = []

for usage in resource_usages:
    usages_data.append(
        {
            "type": usage["type"],
            "amount": usage["usage"],
        }
    )

output = {
    "usages": usages_data,
}

output_json = json.dumps(output)

output_json_encoded = output_json.encode("utf-8")

print(base64.b64encode(output_json_encoded).decode("utf-8"))

Script for resource termination

This script terminates the remote resource.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from waldur_client import WaldurClient
from os import environ
from time import sleep

waldur_client = WaldurClient(environ["WALDUR_API_URL"], environ["WALDUR_API_TOKEN"])

RESOURCE_UUID = environ["RESOURCE_BACKEND_ID"]

print('Creating resource termination order')
order_uuid = waldur_client.marketplace_provider_resource_terminate_order(RESOURCE_UUID)

print('Approving the order')
waldur_client.marketplace_order_approve_by_provider(order_uuid)

order = waldur_client.get_order(order_uuid)
max_retries = 10
retry_count = 0
print("Waiting for order to be done")
while order["state"] != "done" and retry_count < max_retries:
    print(f"Order state: {order['state']}")
    order = waldur_client.get_order(order["uuid"])
    sleep(5)
    retry_count += 1

if order["state"] != "done":
    print(f"Order execution timed out, state is {order['state']}")
    exit(1)