Skip to content

Offering Configuration

An Offering represents a service or product that can be ordered through the Waldur marketplace. This document describes the configuration options available for offerings.

Overview

Offerings are created by service providers and define:

  • What service is being offered (type, description, terms)
  • How users can customize their orders (options)
  • How provisioned resources can be modified (resource_options)
  • Behavioral rules and constraints (plugin_options)
  • Pricing structure (plans and components)

Data Flow: Options to Resource

Understanding how user input flows through the system:

flowchart LR
    subgraph Offering["Offering (schema)"]
        OO["options"]
        RO["resource_options"]
    end

    subgraph Order["Order"]
        OA["attributes"]
    end

    subgraph Resource["Resource"]
        RA["attributes"]
        ROPT["options"]
    end

    OO -->|defines form| OA
    OA -->|all values| RA
    OA -->|filtered by| RO
    RO -->|matching keys| ROPT

    style RA fill:#e1f5fe
    style ROPT fill:#c8e6c9
Step What happens
1 offering.options defines the order form schema
2 User fills out the form, values become order.attributes
3 All attributes are copied to resource.attributes (immutable)
4 Only attributes matching keys in offering.resource_options are copied to resource.options
5 resource.options can be modified after provisioning (triggers UPDATE orders)

Key Configuration Fields

options

Defines the input fields users fill out when creating an order. These values are stored in order.attributes and resource.attributes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "options": {
    "order": ["storage_data_type", "permissions", "hard_quota_space"],
    "options": {
      "storage_data_type": {
        "type": "select_string",
        "label": "Storage Type",
        "required": true,
        "choices": ["Store", "Archive", "Scratch"]
      },
      "permissions": {
        "type": "select_string",
        "label": "Permissions",
        "required": true,
        "choices": ["2770", "2775", "2777"]
      },
      "hard_quota_space": {
        "type": "integer",
        "label": "Space (TB)",
        "required": true
      }
    }
  }
}

Supported field types:

Type Description
string Free text input
text Multi-line text input
integer Whole number
money Decimal number for currency
boolean True/false checkbox
select_string Dropdown with string choices
select_string_multi Multi-select dropdown
date Date picker
time Time picker
html_text Rich text editor
component_multiplier Links to component for billing

resource_options

Defines which attributes can be modified after resource creation. When an order is created, attribute values matching keys defined here are copied to resource.options.

Important: The keys in resource_options.options act as a filter. Only attributes with matching keys are copied to resource.options and become modifiable.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "resource_options": {
    "order": ["soft_quota_space", "hard_quota_space", "permissions"],
    "options": {
      "soft_quota_space": {
        "type": "integer",
        "label": "Soft Quota (TB)",
        "required": false
      },
      "hard_quota_space": {
        "type": "integer",
        "label": "Hard Quota (TB)",
        "required": false
      },
      "permissions": {
        "type": "select_string",
        "label": "Permissions",
        "required": false,
        "choices": ["2770", "2775", "2777"]
      }
    }
  }
}

Example flow:

  1. User orders with: storage_data_type=Store, permissions=2770, hard_quota_space=10
  2. resource.attributes = {storage_data_type: "Store", permissions: "2770", hard_quota_space: 10}
  3. resource.options = {permissions: "2770", hard_quota_space: 10} (only keys from resource_options)
  4. storage_data_type is NOT in resource.options because it's not in resource_options.options
  5. User can later modify permissions and hard_quota_space, but NOT storage_data_type

plugin_options

Defines behavioral rules, constraints, and provider-specific settings. This is where most operational configuration lives.

backend_id_rules

Defines per-offering validation rules for the backend_id field on resources. Supports format validation via regex and configurable uniqueness scopes. Default is {} (no validation, backward compatible). Empty backend_id values always bypass validation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "backend_id_rules": {
    "format": {
      "regex": "^[A-Z]{2}-\\d{6}$",
      "description": "Must be 2 uppercase letters, dash, 6 digits"
    },
    "uniqueness": {
      "scope": "offering",
      "include_terminated": false
    }
  }
}

Both format and uniqueness are optional top-level keys.

Format validation:

Field Type Description
format.regex string Python regex pattern validated with re.fullmatch. Max 200 characters. Patterns with nested/adjacent quantifiers are rejected (ReDoS protection)
format.description string Human-readable description shown in validation errors. Falls back to displaying the regex pattern

Uniqueness configuration:

Field Type Default Description
uniqueness.scope string — Scope for uniqueness check (see table below)
uniqueness.include_terminated boolean true Whether terminated resources are included in the uniqueness check

Uniqueness scopes:

Scope Description
offering Unique across resources of this offering
offering_group Unique across all offerings that share the same offering.backend_id (e.g. offerings attached to the same vcluster). Falls back to offering scope if the offering has no backend_id
service_provider Unique across all offerings of the same customer/service provider
service_provider_category Unique across all offerings of the same provider in the same category

API endpoints:

Endpoint Method Description
/api/marketplace-provider-offerings/{uuid}/update_backend_id_rules/ POST Configure rules. Requires UPDATE_OFFERING_OPTIONS permission
/api/marketplace-provider-offerings/{uuid}/check_unique_backend_id/ POST Check a backend ID. Set use_offering_rules: true to validate format and uniqueness per configured rules

Enforcement points:

  • set_backend_id action (manual backend ID assignment)
  • import_resource action (resource import from external systems)
  • Not applied when backend systems automatically set backend_id via processors

Visibility: backend_id_rules is exposed on the provider offering serializer but excluded from the public offering serializer.

Plugin Options Reference

Approval and Auto-Processing

Option Type Default Description
auto_approve_remote_orders boolean false Skip provider approval for orders from external customers
auto_approve_in_service_provider_projects boolean false Skip consumer approval when ordering within the same organization
disable_autoapprove boolean false Force manual consumer approval for all provisioning orders, overriding every other consumer-side auto-approve setting (including role-based and project-level auto-approval rules); termination orders, staff and provider approval are exempt

Example:

1
2
3
4
5
6
{
  "plugin_options": {
    "auto_approve_remote_orders": true,
    "auto_approve_in_service_provider_projects": true
  }
}

Resource Constraints

Option Type Default Description
maximal_resource_count_per_project integer none Maximum number of resources from this offering per project
unique_resource_per_attribute string none Attribute name to enforce uniqueness. Only one non-terminated resource per attribute value per project
minimal_team_count_for_provisioning integer none Minimum number of team members required in project
required_team_role_for_provisioning string none Required role name (e.g., "PI") for user to provision

Example - Storage offering with one resource per storage type:

1
2
3
4
5
6
{
  "plugin_options": {
    "unique_resource_per_attribute": "storage_data_type",
    "maximal_resource_count_per_project": 4
  }
}

With this configuration:

  • A project can have one "Store", one "Archive", one "Users", and one "Scratch" resource
  • A project cannot have two "Store" resources (blocked by unique_resource_per_attribute)
  • Total resources capped at 4 (defense in depth via maximal_resource_count_per_project)

Resource Lifecycle

Option Type Default Description
is_resource_termination_date_required boolean false Require end date when ordering
default_resource_termination_offset_in_days integer none Default days until termination from order date
max_resource_termination_offset_in_days integer none Maximum days until termination allowed
latest_date_for_resource_termination date none Hard deadline for all resource terminations
resource_expiration_threshold integer 30 Days before expiration to start warning users
can_restore_resource boolean false Allow restoring terminated resources
supports_downscaling boolean false Allow reducing resource limits
supports_pausing boolean false Allow pausing/resuming resources
restrict_deletion_with_active_resources boolean false Prevent offering deletion while it has non-terminated resources (applies to all users including staff)
enable_resource_end_date_change_requests boolean false Let users without the end date permission request an end date change; not available on prepaid offerings
enable_resource_limit_change_requests boolean false Let users who cannot change limits directly request a limit change; approval submits an update order

A child offering follows its parent's value for both request switches, and an offering imported from another Waldur keeps its local value when it is synchronized.

Example:

1
2
3
4
5
6
7
8
{
  "plugin_options": {
    "is_resource_termination_date_required": true,
    "default_resource_termination_offset_in_days": 90,
    "max_resource_termination_offset_in_days": 365,
    "restrict_deletion_with_active_resources": true
  }
}

Order Processing

Option Type Default Description
create_orders_on_resource_option_change boolean false Create UPDATE orders when resource_options change
enable_purchase_order_upload boolean false Allow users to attach purchase orders
require_purchase_order_upload boolean false Require purchase order attachment
enable_provider_consumer_messaging boolean false Allow providers and consumers to exchange messages with attachments on pending orders
notify_about_provider_consumer_messages boolean false Send email notifications when providers or consumers exchange messages on pending orders. Requires enable_provider_consumer_messaging

Resource Naming

Option Type Default Description
resource_name_pattern string none Python format string for generating suggested resource names

When set, the suggest_name endpoint uses this pattern instead of the default {customer_slug}-{project_slug}-{offering_slug}[-counter] format.

Available variables:

Variable Description
{customer_name} Customer organization name
{customer_slug} Customer slug
{project_name} Project name
{project_slug} Project slug
{offering_name} Offering name
{offering_slug} Offering slug
{plan_name} Selected plan name (empty if no plan provided)
{counter} Incremental counter (empty for first resource, 2 for second, etc.)
{attributes[KEY]} Any order form attribute value (empty if the key is missing)

Examples:

1
2
3
4
5
{
  "plugin_options": {
    "resource_name_pattern": "{project_slug}-{offering_slug}-{counter}"
  }
}

With attributes from the order form:

1
2
3
4
5
{
  "plugin_options": {
    "resource_name_pattern": "{project_slug}-{attributes[environment]}-{counter}"
  }
}

Non-alphanumeric characters (except -, _, .) are replaced with hyphens; duplicate hyphens are collapsed; leading/trailing hyphens are stripped. If the pattern is malformed, the endpoint falls back to the default naming behavior.

The related resource_slug_template option (e.g. {project_slug}-{counter}) generates a unique resource slug, which the Waldur Site Agent uses as the backend ID (e.g. the SLURM account name).

Do not combine resource_slug_template with the project_slug account name policy

For site-agent-managed offerings, the account_name_generation_policy plugin option (under Offering Users) is an alternative way to make backend IDs unique. When it is set to project_slug, the agent ignores the resource slug and instead derives the backend ID from the project slug plus its own incrementing counter — producing IDs like prefix-test-prj-01-2-31 even when the slug is already unique. If you configure a resource_slug_template, leave account_name_generation_policy unset so the unique slug is used directly.

Display and UI

Option Type Default Description
conceal_billing_data boolean false Hide pricing/billing information from users
highlight_backend_id_display boolean false Emphasize backend ID in resource display
backend_id_display_label string none Custom label for backend ID field

Offering Users (Identity Management)

Option Type Default Description
service_provider_can_create_offering_user boolean false Allow provider to create offering-specific user accounts
uses_robot_accounts boolean false (unset) Identity model for this offering is per-resource robot accounts, for integrators choosing robot-account provisioning instead of offering users. Cannot be combined with service_provider_can_create_offering_user. Does not gate robot account creation: offerings that use offering users can still have robot accounts (for example Lexis/HEAppE) without this flag
username_generation_policy string "service_provider" How usernames are generated: waldur_username, anonymized, service_provider, full_name, freeipa, eduteams. Inherits from the provider's account_username_generation_policy when unset
account_name_generation_policy string none Site-agent backend ID (e.g. SLURM account name) generation. Unset = use the resource slug as-is; project_slug = derive from the project slug with an incrementing counter. Do not combine with resource_slug_template (see Resource Naming)
initial_uidnumber integer 5000 Starting UID for generated users
initial_primarygroup_number integer 5000 Starting GID for primary groups
initial_usergroup_number integer 6000 Starting GID for user groups
homedir_prefix string "/home/" Prefix for home directory paths. Inherits from the provider's account_homedir_prefix when unset
login_shell string "/bin/bash" Login shell assigned to new accounts. Inherits from the provider's account_login_shell when unset
username_anonymized_prefix string "waldur_" Prefix for anonymized usernames; the name is the prefix followed by the account's POSIX UID (a per-offering counter when no UID resolves). Inherits from the provider's account_username_anonymized_prefix when unset
account_scope string "offering" offering keeps one account per offering; provider shares one account per user across the provider's offerings. Inherits from the provider's account_scope when unset

Inheriting account settings from the service provider

The account settings account_scope, username_generation_policy, username_anonymized_prefix, homedir_prefix and login_shell can be set on an offering, in its plugin options, and on its service provider, in the provider's account_options. Both use the same keys and the same validation. They resolve most specific first:

  1. the offering's own plugin option, when set;
  2. otherwise the service provider's account_options value of the same key;
  3. otherwise the built-in default shown in the table above.

Leave an option out of the offering's plugin options to inherit it. Saving an offering does not fill in the defaults, so a provider-level value applies to every offering that does not set its own.

Both are updated key by key: omitting a key keeps its current value, and an empty string removes the setting so it is inherited again. For example, to set a login shell for all of a provider's offerings and clear its home directory prefix:

1
2
PATCH /api/marketplace-service-providers/{uuid}/
{"account_options": {"login_shell": "/bin/zsh", "homedir_prefix": ""}}

Changing the username generation policy or the anonymized prefix, on the offering or on the provider, regenerates the usernames of the offering users it affects. A provider change reaches only the offerings that inherit the setting. Accounts held at the provider (account_scope: provider) are not regenerated this way; their usernames belong to the provider account.

Moving accounts into provider scope is refused while a person holds different usernames on the offerings that would share them. This applies to the provider's account_scope and to a single offering's account_scope alike. The provider's username_conflicts action lists the people concerned, and adopt_provider_accounts resolves them. A clean switch backs the existing accounts with provider accounts straight away.

The provider-offering and public-offering APIs expose the effective values as the read-only account_settings field, and resources expose them for their offering as offering_account_settings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "account_scope": {
    "value": "offering", "source": "default",
    "inherited": {"value": "offering", "source": "default"}
  },
  "username_generation_policy": {
    "value": "anonymized", "source": "provider",
    "inherited": {"value": "anonymized", "source": "provider"}
  },
  "login_shell": {
    "value": "/bin/sh", "source": "offering",
    "inherited": {"value": "/bin/zsh", "source": "provider"}
  }
}

source is offering, provider or default. inherited is what the setting resolves to without the offering's own value: what removing the offering's override leads to.

Offerings saved before this behaviour stored the defaults (service_provider, waldur_, /home/, /bin/bash) in their plugin options, which hides any provider value. A migration removes such a stored default when the offering's service provider sets that setting. Values that differ from a default are kept, and so is a stored username setting whose removal would rename existing accounts. The migration logs those offerings; send the key as an empty string to apply the provider setting and regenerate the usernames.

The anonymized policy names an account <prefix><posix uid> -- for example hpc_9001 for a prefix of hpc_ and uid 9001. The uid is the one the account holds (or is allocated) from the POSIX ID pool that resolves for the offering, or the user's uid_number when uid_source is user_attribute. Because a pool allocates one uid per person across every offering that resolves to it, the same person gets the same username on every offering sharing that pool, and regenerating the name (refresh_offering_usernames, a policy change) is a no-op. When no uid resolves -- no pool covers the offering, or POSIX accounts are disabled -- the name falls back to a per-offering counter (<prefix>00000, <prefix>00001, ...) and a warning is logged. The prefix, like username_generation_policy, homedir_prefix and login_shell, is resolved most-specific-first: the offering's plugin option, else the provider's account_* field, else the default.

Plugin-Specific Options

OpenStack

Option Type Description
default_internal_network_mtu integer (68-9000) MTU for tenant internal networks
max_instances integer Default instance limit per tenant
max_volumes integer Default volume limit per tenant
max_security_groups integer Default security group limit
storage_mode "fixed" or "dynamic" How storage quota is calculated
snapshot_size_limit_gb integer Snapshot size limit in GB

HEAppE (HPC)

Option Type Description
heappe_url URL HEAppE server endpoint. Required
heappe_username string Service account username. Required
heappe_cluster_id string ID of the target cluster in HEAppE. Required
heappe_local_base_path string Root directory on the cluster under which project directories are created. Required
heappe_identifier string Human-readable identifier of the HEAppE instance this offering targets, e.g. it4i-heappe-prod; disambiguates between multiple HEAppE deployments a provider may run. Distinct from heappe_cluster_id
project_permanent_directory string Persistent project directory path
scratch_project_directory string Temporary scratch directory path

The service account password heappe_password (required) and heappe_cluster_password are secret options: they go in secret_options, not plugin_options, and are only returned to users who can manage the offering's integration.

GLAuth (LDAP)

Option Type Description
glauth_records_path string Path to GLAuth user records
glauth_users_path string Path to GLAuth users configuration

Rancher (Kubernetes)

See Rancher plugin documentation for detailed Rancher-specific options.

Complete Example

A storage offering with comprehensive configuration:

 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
{
  "name": "HPC Storage",
  "type": "Marketplace.Slurm",
  "options": {
    "order": ["storage_data_type", "hard_quota_space"],
    "options": {
      "storage_data_type": {
        "type": "select_string",
        "label": "Storage Type",
        "required": true,
        "choices": ["Store", "Archive", "Users", "Scratch"]
      },
      "hard_quota_space": {
        "type": "integer",
        "label": "Space (TB)",
        "required": true
      }
    }
  },
  "resource_options": {
    "order": ["soft_quota_space", "hard_quota_space"],
    "options": {
      "soft_quota_space": {
        "type": "integer",
        "label": "Soft Quota (TB)"
      },
      "hard_quota_space": {
        "type": "integer",
        "label": "Hard Quota (TB)"
      }
    }
  },
  "plugin_options": {
    "disable_autoapprove": true,
    "unique_resource_per_attribute": "storage_data_type",
    "maximal_resource_count_per_project": 4,
    "is_resource_termination_date_required": true,
    "default_resource_termination_offset_in_days": 90,
    "max_resource_termination_offset_in_days": 730,
    "create_orders_on_resource_option_change": true,
    "service_provider_can_create_offering_user": true
  }
}

Validation Behavior

Order Creation Validation

When an order is created, the following plugin_options are validated:

  1. maximal_resource_count_per_project: Counts non-terminated resources for the project+offering
  2. unique_resource_per_attribute: Checks if a non-terminated resource with the same attribute value exists
  3. minimal_team_count_for_provisioning: Validates project team size
  4. required_team_role_for_provisioning: Validates user has required role

Backend ID Validation

When backend_id_rules is configured on the offering, the following checks run on set_backend_id and import_resource:

  1. If backend_id is empty, all validation is skipped
  2. Format check: If format.regex is set, the value must match using re.fullmatch
  3. Uniqueness check: If uniqueness.scope is set, a duplicate query runs against the configured scope

The check_unique_backend_id endpoint performs the same checks when use_offering_rules is true, returning is_unique and is_valid_format fields in the response.

Approval Flow

Consumer and provider approval are decided separately, by two independent gates.

Consumer approval is decided by order_should_not_be_reviewed_by_consumer(), which applies these rules in order:

  1. If the offering sets require_purchase_order_upload and no purchase order is attached, manual approval is required — this applies to staff too. Termination orders are exempt
  2. Staff users skip consumer approval. This includes the system robot that drives scheduled sweeps and backend-triggered flows
  3. If disable_autoapprove is true, manual approval is required. It overrides every consumer-side mechanism below it: private-offering permissions, auto_approve_in_service_provider_projects, auto_approve_for_roles, project-level auto-approval rules, and the general ORDER.APPROVE permission that owners and managers hold by default. Termination orders are exempt: they reduce spend, so they follow the normal termination rules instead
  4. Otherwise the private-offering, same-organization (auto_approve_in_service_provider_projects), termination, role-based (auto_approve_for_roles) and ORDER.APPROVE rules apply, in that order

Provider approval is decided by order_should_not_be_reviewed_by_provider() and is not affected by disable_autoapprove. It is governed by the offering type together with auto_approve_remote_orders (skips provider approval for orders from external customers) and auto_approve_marketplace_script.

Both gates only decide whether an order needs approval. Flows that create an order already approved — as a record of a provisioning decision made elsewhere — do not consult them, so disable_autoapprove does not apply to:

  • Proposal allocation. The call review is itself the consumer-side decision: the project is created under the call manager's own organization. The offering's purchase-order requirement is still honoured
  • Autoprovisioning and HPC onboarding rules, which create the resource and its order at user signup. An order left pending would strand the resource in Creating
  • Resource migration (waldur_openstack_replication), which records the order once the migration has already succeeded or failed