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 | |
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 | |
Example flow:
- User orders with:
storage_data_type=Store,permissions=2770,hard_quota_space=10 resource.attributes={storage_data_type: "Store", permissions: "2770", hard_quota_space: 10}resource.options={permissions: "2770", hard_quota_space: 10}(only keys fromresource_options)storage_data_typeis NOT inresource.optionsbecause it's not inresource_options.options- User can later modify
permissionsandhard_quota_space, but NOTstorage_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 | |
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_idaction (manual backend ID assignment)import_resourceaction (resource import from external systems)- Not applied when backend systems automatically set
backend_idvia 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 | |
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 | |
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 | |
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 | |
With attributes from the order form:
1 2 3 4 5 | |
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:
- the offering's own plugin option, when set;
- otherwise the service provider's
account_optionsvalue of the same key; - 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 | |
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 | |
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 | |
Validation Behavior
Order Creation Validation
When an order is created, the following plugin_options are validated:
maximal_resource_count_per_project: Counts non-terminated resources for the project+offeringunique_resource_per_attribute: Checks if a non-terminated resource with the same attribute value existsminimal_team_count_for_provisioning: Validates project team sizerequired_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:
- If
backend_idis empty, all validation is skipped - Format check: If
format.regexis set, the value must match usingre.fullmatch - Uniqueness check: If
uniqueness.scopeis 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:
- If the offering sets
require_purchase_order_uploadand no purchase order is attached, manual approval is required — this applies to staff too. Termination orders are exempt - Staff users skip consumer approval. This includes the system robot that drives scheduled sweeps and backend-triggered flows
- If
disable_autoapproveistrue, 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 generalORDER.APPROVEpermission that owners and managers hold by default. Termination orders are exempt: they reduce spend, so they follow the normal termination rules instead - Otherwise the private-offering, same-organization (
auto_approve_in_service_provider_projects), termination, role-based (auto_approve_for_roles) andORDER.APPROVErules 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