This document describes how to interact directly with OpenShift and MaaS APIs to implement the functionality provided by maas-toolbox. This enables customers to implement their own version of the toolbox by understanding the underlying API flows.
- Prerequisites
- Authentication
- Tier Management via ConfigMap
- Group Management via OpenShift API
- User and Group Queries
- LLMInferenceService Management
- TokenRateLimitPolicy Management
- RateLimitPolicy Management
- Complete Workflow Examples
# OpenShift API Server
export OPENSHIFT_API="https://api.your-cluster.example.com:6443"
# Authentication token (see Authentication section)
export TOKEN="your-bearer-token-here"
# ConfigMap settings for tier management
export TIER_NAMESPACE="maas-api"
export TIER_CONFIGMAP="tier-to-group-mapping"
# Rate limit policy settings
export RATELIMIT_NAMESPACE="openshift-ingress"
export RATELIMIT_POLICY_NAME="gateway-rate-limits"
export TOKEN_RATELIMIT_POLICY_NAME="gateway-token-rate-limits"OpenShift uses bearer token authentication. You can obtain a token in several ways:
# Login to OpenShift
oc login https://api.your-cluster.example.com:6443
# Extract the token
export TOKEN=$(oc whoami -t)# Create a service account
oc create serviceaccount maas-admin -n maas-api
# Grant necessary permissions
oc adm policy add-cluster-role-to-user cluster-admin -z maas-admin -n maas-api
# Get the service account token
export TOKEN=$(oc create token maas-admin -n maas-api)curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces"Expected Response: A JSON list of namespaces if authentication is successful.
Tiers are stored in a Kubernetes ConfigMap in the maas-api namespace. The ConfigMap contains a YAML list of tier definitions.
apiVersion: v1
kind: ConfigMap
metadata:
name: tier-to-group-mapping
namespace: maas-api
data:
tiers: |
- name: free
description: Free tier for basic users
level: 1
groups:
- system:authenticated
- name: premium
description: Premium tier
level: 10
groups:
- premium-users# Create the ConfigMap with initial tier configuration
curl -k -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps" \
-d '{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "tier-to-group-mapping",
"namespace": "maas-api",
"labels": {
"app": "tier-to-group-admin"
}
},
"data": {
"tiers": "- name: free\n description: Free tier for basic users\n level: 1\n groups:\n - system:authenticated\n"
}
}'Expected Response (201 Created): The created ConfigMap object in JSON format.
# Get the ConfigMap
curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}"Expected Response (200 OK): ConfigMap object with the tier data.
Extract and parse the tiers:
# Get ConfigMap and extract tiers field
curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers'Output: The YAML content of the tiers list.
This requires:
- Get the current ConfigMap
- Parse the existing tiers
- Add the new tier to the list
- Update the ConfigMap
# Step 1: Get current ConfigMap
CURRENT_CM=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}")
# Step 2: Extract current tiers YAML
CURRENT_TIERS=$(echo "$CURRENT_CM" | jq -r '.data.tiers')
# Step 3: Append new tier to YAML (manual or programmatic)
# For this example, we'll construct new YAML with the additional tier
NEW_TIERS="${CURRENT_TIERS}
- name: premium
description: Premium tier
level: 10
groups:
- premium-users
"
# Step 4: Update the ConfigMap with PATCH
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"Expected Response (200 OK): Updated ConfigMap object.
Note: In practice, you would use a YAML parser (like yq) to properly manipulate the YAML structure:
# Using yq to add a tier
CURRENT_TIERS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Add new tier using yq
NEW_TIERS=$(echo "$CURRENT_TIERS" | yq eval '. += [{"name": "premium", "description": "Premium tier", "level": 10, "groups": ["premium-users"]}]' -)
# Update ConfigMap
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"# Get current tiers
CURRENT_TIERS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Update tier using yq (example: update "free" tier's level to 2)
NEW_TIERS=$(echo "$CURRENT_TIERS" | yq eval '(.[] | select(.name == "free") | .level) = 2' -)
# Update ConfigMap
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"# Get current tiers
CURRENT_TIERS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Remove tier using yq (example: remove "free" tier)
NEW_TIERS=$(echo "$CURRENT_TIERS" | yq eval 'del(.[] | select(.name == "free"))' -)
# Update ConfigMap
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"# Get current tiers
CURRENT_TIERS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Add group to tier using yq (example: add "trial-users" to "free" tier)
NEW_TIERS=$(echo "$CURRENT_TIERS" | yq eval '(.[] | select(.name == "free") | .groups) += ["trial-users"]' -)
# Update ConfigMap
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"# Get current tiers
CURRENT_TIERS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Remove group from tier using yq (example: remove "trial-users" from "free" tier)
NEW_TIERS=$(echo "$CURRENT_TIERS" | yq eval '(.[] | select(.name == "free") | .groups) -= ["trial-users"]' -)
# Update ConfigMap
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"OpenShift Groups are cluster-scoped resources in the user.openshift.io/v1 API.
curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups"Expected Response (200 OK): JSON list of all groups with their members.
Parse to get group names and users:
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups" \
| jq -r '.items[] | "\(.metadata.name): \(.users | join(", "))"'GROUP_NAME="premium-users"
curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups/${GROUP_NAME}"Expected Response:
- 200 OK: Group exists (returns group object)
- 404 Not Found: Group does not exist
USERNAME="bryonbaker"
# List all groups and filter for the user
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups" \
| jq -r --arg user "$USERNAME" '.items[] | select(.users and (.users[] == $user)) | .metadata.name'Output: List of group names that the user belongs to.
Note: The special group system:authenticated is always implicitly assigned to authenticated users but won't appear in the API response.
curl -k -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups" \
-d '{
"apiVersion": "user.openshift.io/v1",
"kind": "Group",
"metadata": {
"name": "premium-users"
},
"users": [
"bryonbaker",
"johndoe"
]
}'Expected Response (201 Created): The created Group object.
This requires getting the group, modifying the users list, and updating:
GROUP_NAME="premium-users"
NEW_USER="janedoe"
# Step 1: Get current group
CURRENT_GROUP=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups/${GROUP_NAME}")
# Step 2: Add user to the users array
UPDATED_GROUP=$(echo "$CURRENT_GROUP" | jq --arg user "$NEW_USER" '.users += [$user] | .users |= unique')
# Step 3: Update the group
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups/${GROUP_NAME}" \
-d "$UPDATED_GROUP"Expected Response (200 OK): Updated group object.
This combines getting user groups with tier configuration:
USERNAME="bryonbaker"
# Step 1: Get all groups the user belongs to
USER_GROUPS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups" \
| jq -r --arg user "$USERNAME" '.items[] | select(.users and (.users[] == $user)) | .metadata.name')
# Add system:authenticated (implicit for all authenticated users)
USER_GROUPS="${USER_GROUPS}
system:authenticated"
# Step 2: Get tier configuration
TIER_CONFIG=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Step 3: Filter tiers where user's groups intersect with tier's groups
# (This requires scripting logic - example with yq and bash)
echo "$TIER_CONFIG" | yq eval -o=json '.' | jq --arg groups "$USER_GROUPS" '
.[] | select(
.groups as $tier_groups |
($groups | split("\n")) as $user_groups |
($tier_groups | map(. as $g | $user_groups | index($g)) | any)
)'Output: JSON array of tiers the user can access.
GROUP_NAME="premium-users"
# Get tier configuration
TIER_CONFIG=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
# Filter tiers that contain the group
echo "$TIER_CONFIG" | yq eval -o=json '.' | jq --arg group "$GROUP_NAME" '
.[] | select(.groups | index($group))'Output: JSON array of tiers containing the specified group.
LLMInferenceService resources are managed via the KServe API (serving.kserve.io/v1alpha1).
curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/llminferenceservices"Expected Response (200 OK): JSON list of all LLMInferenceService resources.
NAMESPACE="acme-inc-models"
SERVICE_NAME="acme-dev-model"
curl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}"Expected Response (200 OK): The LLMInferenceService object.
This involves adding/updating the serving.kserve.io/tiers annotation:
NAMESPACE="acme-inc-models"
SERVICE_NAME="acme-dev-model"
TIER_NAME="free"
# Step 1: Get current LLMInferenceService
CURRENT_SERVICE=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}")
# Step 2: Extract current tiers annotation (if exists)
CURRENT_TIERS=$(echo "$CURRENT_SERVICE" | jq -r '.metadata.annotations["serving.kserve.io/tiers"] // "[]"')
# Step 3: Add new tier to the list (avoiding duplicates)
NEW_TIERS=$(echo "$CURRENT_TIERS" | jq --arg tier "$TIER_NAME" '. + [$tier] | unique')
# Step 4: Update the annotation using PATCH
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/merge-patch+json" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}" \
-d "{
\"metadata\": {
\"annotations\": {
\"serving.kserve.io/tiers\": $(echo "$NEW_TIERS" | jq -c .)
}
}
}"Expected Response (200 OK): Updated LLMInferenceService object.
NAMESPACE="acme-inc-models"
SERVICE_NAME="acme-dev-model"
TIER_NAME="free"
# Step 1: Get current service
CURRENT_SERVICE=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}")
# Step 2: Extract and remove tier from annotation
CURRENT_TIERS=$(echo "$CURRENT_SERVICE" | jq -r '.metadata.annotations["serving.kserve.io/tiers"] // "[]"')
NEW_TIERS=$(echo "$CURRENT_TIERS" | jq --arg tier "$TIER_NAME" 'del(.[] | select(. == $tier))')
# Step 3: Update annotation
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/merge-patch+json" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}" \
-d "{
\"metadata\": {
\"annotations\": {
\"serving.kserve.io/tiers\": $(echo "$NEW_TIERS" | jq -c .)
}
}
}"TIER_NAME="free"
# Get all LLMInferenceServices
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/llminferenceservices" \
| jq --arg tier "$TIER_NAME" '.items[] | select(
.metadata.annotations["serving.kserve.io/tiers"] and
(.metadata.annotations["serving.kserve.io/tiers"] | fromjson | index($tier))
) | {name: .metadata.name, namespace: .metadata.namespace, tiers: (.metadata.annotations["serving.kserve.io/tiers"] | fromjson)}'Output: JSON array of LLMInferenceServices that have the specified tier.
TokenRateLimitPolicy is a Kuadrant CRD (kuadrant.io/v1alpha1) that manages token consumption rate limits.
apiVersion: kuadrant.io/v1alpha1
kind: TokenRateLimitPolicy
metadata:
name: gateway-token-rate-limits
namespace: openshift-ingress
spec:
limits:
serverless-user-tokens:
counters:
- expression: auth.identity.userid
rates:
- limit: 100
window: 5m
when:
- predicate: |
auth.identity.tier == "serverless" && !request.path.endsWith("/v1/models")
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: maas-default-gatewaycurl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}"Expected Response (200 OK): TokenRateLimitPolicy object.
# Step 1: Get current policy
CURRENT_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}")
# Step 2: Add new limit to spec.limits map
LIMIT_NAME="free-user-tokens"
TIER_NAME="free"
LIMIT_VALUE=100
WINDOW="1m"
UPDATED_POLICY=$(echo "$CURRENT_POLICY" | jq --arg name "$LIMIT_NAME" \
--arg tier "$TIER_NAME" \
--argjson limit $LIMIT_VALUE \
--arg window "$WINDOW" \
'.spec.limits[$name] = {
"counters": [{"expression": "auth.identity.userid"}],
"rates": [{"limit": $limit, "window": $window}],
"when": [{"predicate": "auth.identity.tier == \"" + $tier + "\" && !request.path.endsWith(\"/v1/models\")"}]
}')
# Step 3: Update the policy
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_POLICY"Expected Response (200 OK): Updated TokenRateLimitPolicy object.
LIMIT_NAME="free-user-tokens"
NEW_LIMIT_VALUE=200
NEW_WINDOW="2m"
# Step 1: Get current policy
CURRENT_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}")
# Step 2: Update the limit values (keep tier from predicate)
UPDATED_POLICY=$(echo "$CURRENT_POLICY" | jq --arg name "$LIMIT_NAME" \
--argjson limit $NEW_LIMIT_VALUE \
--arg window "$NEW_WINDOW" \
'.spec.limits[$name].rates[0].limit = $limit |
.spec.limits[$name].rates[0].window = $window')
# Step 3: Update the policy
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_POLICY"LIMIT_NAME="free-user-tokens"
# Step 1: Get current policy
CURRENT_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}")
# Step 2: Remove the limit from spec.limits map
UPDATED_POLICY=$(echo "$CURRENT_POLICY" | jq --arg name "$LIMIT_NAME" 'del(.spec.limits[$name])')
# Step 3: Update the policy
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_POLICY"curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}" \
| jq '.spec.limits | to_entries[] | {
name: .key,
limit: .value.rates[0].limit,
window: .value.rates[0].window,
tier: (.value.when[0].predicate | capture("tier == \"(?<tier>[^\"]+)\"").tier)
}'Output: JSON array of all token rate limits with their settings.
RateLimitPolicy is a Kuadrant CRD (kuadrant.io/v1) that manages request rate limits.
apiVersion: kuadrant.io/v1
kind: RateLimitPolicy
metadata:
name: gateway-rate-limits
namespace: openshift-ingress
spec:
targetRef:
group: gateway.networking.k8s.io
kind: Gateway
name: maas-default-gateway
limits:
serverless:
rates:
- limit: 5
window: 2m
when:
- predicate: |
auth.identity.tier == "serverless"
counters:
- expression: auth.identity.useridcurl -k -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}"Expected Response (200 OK): RateLimitPolicy object.
# Step 1: Get current policy
CURRENT_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}")
# Step 2: Add new limit to spec.limits map
LIMIT_NAME="free"
TIER_NAME="free"
LIMIT_VALUE=5
WINDOW="2m"
UPDATED_POLICY=$(echo "$CURRENT_POLICY" | jq --arg name "$LIMIT_NAME" \
--arg tier "$TIER_NAME" \
--argjson limit $LIMIT_VALUE \
--arg window "$WINDOW" \
'.spec.limits[$name] = {
"counters": [{"expression": "auth.identity.userid"}],
"rates": [{"limit": $limit, "window": $window}],
"when": [{"predicate": "auth.identity.tier == \"" + $tier + "\""}]
}')
# Step 3: Update the policy
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_POLICY"Expected Response (200 OK): Updated RateLimitPolicy object.
LIMIT_NAME="free"
NEW_LIMIT_VALUE=10
NEW_WINDOW="3m"
# Step 1: Get current policy
CURRENT_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}")
# Step 2: Update the limit values
UPDATED_POLICY=$(echo "$CURRENT_POLICY" | jq --arg name "$LIMIT_NAME" \
--argjson limit $NEW_LIMIT_VALUE \
--arg window "$NEW_WINDOW" \
'.spec.limits[$name].rates[0].limit = $limit |
.spec.limits[$name].rates[0].window = $window')
# Step 3: Update the policy
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_POLICY"LIMIT_NAME="free"
# Step 1: Get current policy
CURRENT_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}")
# Step 2: Remove the limit from spec.limits map
UPDATED_POLICY=$(echo "$CURRENT_POLICY" | jq --arg name "$LIMIT_NAME" 'del(.spec.limits[$name])')
# Step 3: Update the policy
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_POLICY"curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
| jq '.spec.limits | to_entries[] | {
name: .key,
limit: .value.rates[0].limit,
window: .value.rates[0].window,
tier: (.value.when[0].predicate | capture("tier == \"(?<tier>[^\"]+)\"").tier)
}'Output: JSON array of all request rate limits with their settings.
This workflow demonstrates creating a complete tier configuration for a new customer:
# Variables
CUSTOMER_NAME="acme-inc"
CUSTOMER_TIER="${CUSTOMER_NAME}-dedicated"
CUSTOMER_GROUP="${CUSTOMER_NAME}-users"
CUSTOMER_USERS=("alice" "bob" "charlie")
# Step 1: Create OpenShift Group for the customer
echo "Creating group: ${CUSTOMER_GROUP}"
curl -k -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups" \
-d "{
\"apiVersion\": \"user.openshift.io/v1\",
\"kind\": \"Group\",
\"metadata\": {
\"name\": \"${CUSTOMER_GROUP}\"
},
\"users\": $(printf '%s\n' "${CUSTOMER_USERS[@]}" | jq -R . | jq -s .)
}"
echo "Group created successfully."
# Step 2: Add tier to ConfigMap
echo "Adding tier: ${CUSTOMER_TIER}"
CURRENT_TIERS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
NEW_TIERS=$(echo "$CURRENT_TIERS" | yq eval ". += [{
\"name\": \"${CUSTOMER_TIER}\",
\"description\": \"Tier for ${CUSTOMER_NAME}'s dedicated models\",
\"level\": 50,
\"groups\": [\"${CUSTOMER_GROUP}\"]
}]" -)
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/strategic-merge-patch+json" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
-d "{
\"data\": {
\"tiers\": $(echo "$NEW_TIERS" | jq -Rs .)
}
}"
echo "Tier added to ConfigMap."
# Step 3: Create TokenRateLimitPolicy entry
echo "Creating token rate limit for ${CUSTOMER_TIER}"
CURRENT_TOKEN_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}")
UPDATED_TOKEN_POLICY=$(echo "$CURRENT_TOKEN_POLICY" | jq \
--arg name "${CUSTOMER_TIER}-tokens" \
--arg tier "${CUSTOMER_TIER}" \
'.spec.limits[$name] = {
"counters": [{"expression": "auth.identity.userid"}],
"rates": [{"limit": 10000, "window": "1h"}],
"when": [{"predicate": "auth.identity.tier == \"" + $tier + "\" && !request.path.endsWith(\"/v1/models\")"}]
}')
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_TOKEN_POLICY"
echo "Token rate limit created."
# Step 4: Create RateLimitPolicy entry
echo "Creating request rate limit for ${CUSTOMER_TIER}"
CURRENT_RATE_POLICY=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}")
UPDATED_RATE_POLICY=$(echo "$CURRENT_RATE_POLICY" | jq \
--arg name "${CUSTOMER_TIER}" \
--arg tier "${CUSTOMER_TIER}" \
'.spec.limits[$name] = {
"counters": [{"expression": "auth.identity.userid"}],
"rates": [{"limit": 100, "window": "1m"}],
"when": [{"predicate": "auth.identity.tier == \"" + $tier + "\""}]
}')
curl -k -X PUT \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
-d "$UPDATED_RATE_POLICY"
echo "Request rate limit created."
echo "Customer onboarding complete for ${CUSTOMER_NAME}!"Output: A complete tier configuration with group, tier mapping, and rate limits.
TIER_NAME="premium"
SERVICES=(
"namespace1:service1"
"namespace1:service2"
"namespace2:service3"
)
echo "Annotating services with tier: ${TIER_NAME}"
for SERVICE_SPEC in "${SERVICES[@]}"; do
IFS=':' read -r NAMESPACE SERVICE_NAME <<< "$SERVICE_SPEC"
echo "Processing ${NAMESPACE}/${SERVICE_NAME}..."
# Get current service
CURRENT_SERVICE=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}")
# Extract current tiers
CURRENT_TIERS=$(echo "$CURRENT_SERVICE" | jq -r '.metadata.annotations["serving.kserve.io/tiers"] // "[]"')
# Add tier
NEW_TIERS=$(echo "$CURRENT_TIERS" | jq --arg tier "$TIER_NAME" '. + [$tier] | unique')
# Update annotation
curl -k -X PATCH \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/merge-patch+json" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/namespaces/${NAMESPACE}/llminferenceservices/${SERVICE_NAME}" \
-d "{
\"metadata\": {
\"annotations\": {
\"serving.kserve.io/tiers\": $(echo "$NEW_TIERS" | jq -c .)
}
}
}"
echo "✓ ${NAMESPACE}/${SERVICE_NAME} annotated"
done
echo "All services annotated with tier ${TIER_NAME}!"This workflow demonstrates getting all relevant information for a user:
USERNAME="alice"
echo "=== User Access Report for ${USERNAME} ==="
# Step 1: Get user's groups
echo -e "\n1. User Groups:"
USER_GROUPS=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/user.openshift.io/v1/groups" \
| jq -r --arg user "$USERNAME" '.items[] | select(.users and (.users[] == $user)) | .metadata.name')
echo "$USER_GROUPS"
echo "system:authenticated (implicit)"
# Combine groups
ALL_USER_GROUPS="${USER_GROUPS}
system:authenticated"
# Step 2: Get tiers user has access to
echo -e "\n2. Accessible Tiers:"
TIER_CONFIG=$(curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/api/v1/namespaces/${TIER_NAMESPACE}/configmaps/${TIER_CONFIGMAP}" \
| jq -r '.data.tiers')
USER_TIERS=$(echo "$TIER_CONFIG" | yq eval -o=json '.' | jq --arg groups "$ALL_USER_GROUPS" '
.[] | select(
.groups as $tier_groups |
($groups | split("\n")) as $user_groups |
($tier_groups | map(. as $g | $user_groups | index($g)) | any)
)')
echo "$USER_TIERS" | jq -r '"\(.name) (level \(.level)): \(.description)"'
# Step 3: Get rate limits for user's tiers
echo -e "\n3. Rate Limits:"
echo "$USER_TIERS" | jq -r '.name' | while read -r tier; do
echo "Tier: $tier"
# Token rate limits
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1alpha1/namespaces/${RATELIMIT_NAMESPACE}/tokenratelimitpolicies/${TOKEN_RATELIMIT_POLICY_NAME}" \
| jq --arg tier "$tier" '.spec.limits | to_entries[] | select(.value.when[0].predicate | contains($tier)) |
" Token Limit: \(.value.rates[0].limit) tokens per \(.value.rates[0].window)"' -r
# Request rate limits
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
| jq --arg tier "$tier" '.spec.limits | to_entries[] | select(.value.when[0].predicate | contains($tier)) |
" Request Limit: \(.value.rates[0].limit) requests per \(.value.rates[0].window)"' -r
done
# Step 4: Get accessible LLMInferenceServices
echo -e "\n4. Accessible LLMInferenceServices:"
echo "$USER_TIERS" | jq -r '.name' | while read -r tier; do
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/serving.kserve.io/v1alpha1/llminferenceservices" \
| jq --arg tier "$tier" '.items[] | select(
.metadata.annotations["serving.kserve.io/tiers"] and
(.metadata.annotations["serving.kserve.io/tiers"] | fromjson | index($tier))
) | " - \(.metadata.namespace)/\(.metadata.name) (tiers: \(.metadata.annotations["serving.kserve.io/tiers"]))"' -r
done
echo -e "\n=== End of Report ==="When using group names with special characters (like system:authenticated), you may need URL encoding in some contexts:
- Colon (
:) →%3A - Space (
) →%20
- ConfigMap tiers data is stored as YAML string
- Most OpenShift API operations use JSON
- Tools like
yqandjqare essential for conversions
Always check HTTP response codes:
200 OK: Success201 Created: Resource created404 Not Found: Resource doesn't exist409 Conflict: Resource already exists401 Unauthorized: Authentication failure403 Forbidden: Authorization failure
After modifying RateLimitPolicy or TokenRateLimitPolicy CRDs, the policies may need time to propagate through the system. Monitor the policy status:
curl -k -s -H "Authorization: Bearer ${TOKEN}" \
"${OPENSHIFT_API}/apis/kuadrant.io/v1/namespaces/${RATELIMIT_NAMESPACE}/ratelimitpolicies/${RATELIMIT_POLICY_NAME}" \
| jq '.status'- Idempotency: Always check if a resource exists before creating
- Atomic Updates: Use GET-MODIFY-PUT pattern for updates
- Error Recovery: Implement retry logic for transient failures
- Validation: Validate data before sending to API
- Logging: Log all API operations for audit trail
curl: HTTP clientjq: JSON processoryq: YAML processor (github.com/mikefarah/yq)ocorkubectl: For obtaining authentication tokens
- OpenShift REST API Documentation
- Kubernetes API Conventions
- KServe LLMInferenceService API
- Kuadrant Rate Limiting
- ODH MaaS Documentation
Last Updated: February 2026
Version: 1.0
Author: Bryon Baker