diff --git a/deploy/templates/nstemplatetiers/openclaw/cluster.yaml b/deploy/templates/nstemplatetiers/openclaw/cluster.yaml new file mode 100644 index 000000000..f8f076711 --- /dev/null +++ b/deploy/templates/nstemplatetiers/openclaw/cluster.yaml @@ -0,0 +1,152 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: openclaw-cluster-resources +objects: +# ClusterResourceQuotas aggregate limits across both -dev and -openclaw namespaces. +# Limits are base1ns values + modest headroom for the -openclaw namespace. +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-deployments + spec: + quota: + hard: + count/deployments.apps: "33" + count/deploymentconfigs.apps: "30" + count/pods: "55" + count/virtualmachines.kubevirt.io: "2" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-replicas + spec: + quota: + hard: + count/replicasets.apps: "33" + count/replicationcontrollers: "30" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-routes + spec: + quota: + hard: + count/ingresses.extensions: "31" + count/routes.route.openshift.io: "31" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-jobs + spec: + quota: + hard: + count/jobs.batch: "30" + count/daemonsets.apps: "30" + count/cronjobs.batch: "30" + count/statefulsets.apps: "30" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-services + spec: + quota: + hard: + count/services: "33" + services.loadbalancers: '0' + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-bc + spec: + quota: + hard: + count/buildconfigs.build.openshift.io: "30" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-secrets + spec: + quota: + hard: + count/secrets: "110" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: quota.openshift.io/v1 + kind: ClusterResourceQuota + metadata: + name: for-${SPACE_NAME}-cm + spec: + quota: + hard: + count/configmaps: "110" + selector: + annotations: null + labels: + matchLabels: + toolchain.dev.openshift.com/space: ${SPACE_NAME} +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: osl-access-${SPACE_NAME} + annotations: + toolchain.dev.openshift.com/feature: openshift-lightspeed + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: lightspeed-operator-query-access + subjects: + - kind: User + apiGroup: rbac.authorization.k8s.io + name: ${SPACE_NAME} +- apiVersion: toolchain.dev.openshift.com/v1alpha1 + kind: Idler + metadata: + name: ${SPACE_NAME}-dev + spec: + timeoutSeconds: ${{IDLER_TIMEOUT_SECONDS}} +- apiVersion: toolchain.dev.openshift.com/v1alpha1 + kind: Idler + metadata: + name: ${SPACE_NAME}-openclaw + spec: + timeoutSeconds: ${{IDLER_TIMEOUT_SECONDS}} +parameters: +- name: SPACE_NAME + required: true +- name: IDLER_TIMEOUT_SECONDS + # 12 hours + value: "43200" diff --git a/deploy/templates/nstemplatetiers/openclaw/ns_dev.yaml b/deploy/templates/nstemplatetiers/openclaw/ns_dev.yaml new file mode 100644 index 000000000..c6b9bb3c2 --- /dev/null +++ b/deploy/templates/nstemplatetiers/openclaw/ns_dev.yaml @@ -0,0 +1,331 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: openclaw-dev +objects: +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + openshift.io/description: ${SPACE_NAME}-dev + openshift.io/display-name: ${SPACE_NAME}-dev + openshift.io/requester: ${SPACE_NAME} + labels: + name: ${SPACE_NAME}-dev + # For RHODS: Allow user namespace to be treated as a DSP to enable Model Serving on this NS + modelmesh-enabled: "true" + opendatahub.io/dashboard: "true" + name: ${SPACE_NAME}-dev + +# Role and RoleBindings for CRT administration (not associated with users) +- apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: exec-pods + namespace: ${SPACE_NAME}-dev + rules: + - apiGroups: + - "" + resources: + - pods/exec + verbs: + - get + - list + - watch + - create + - delete + - update +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: crtadmin-view + namespace: ${SPACE_NAME}-dev + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: crtadmin-users-view +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: crtadmin-pods + namespace: ${SPACE_NAME}-dev + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: exec-pods + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: crtadmin-users-view + +# User admin RBAC for the -dev namespace. +# Embedded here rather than in spacerole_admin because the spacerole applies +# to all namespaces equally, and we need the user to have full admin only in +# -dev while having narrow permissions in -openclaw. +- apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: rbac-edit + namespace: ${SPACE_NAME}-dev + rules: + - apiGroups: + - authorization.openshift.io + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ${SPACE_NAME}-rbac-edit + namespace: ${SPACE_NAME}-dev + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: rbac-edit + subjects: + - kind: User + name: ${SPACE_NAME} +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ${SPACE_NAME}-edit + namespace: ${SPACE_NAME}-dev + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: edit + subjects: + - kind: User + name: ${SPACE_NAME} + +# Quotas and default limits for not-terminating containers (regular long-running containers) +# and terminating (short-lived containers like build) containers +- apiVersion: v1 + kind: ResourceQuota + metadata: + name: compute-deploy + namespace: ${SPACE_NAME}-dev + spec: + scopes: + - NotTerminating + hard: + limits.cpu: 30000m + limits.memory: ${MEMORY_LIMIT} + limits.nvidia.com/gpu: 0 + requests.cpu: 3000m + requests.memory: ${MEMORY_REQUEST} + requests.nvidia.com/gpu: 0 +- apiVersion: v1 + kind: ResourceQuota + metadata: + name: compute-build + namespace: ${SPACE_NAME}-dev + spec: + scopes: + - Terminating + hard: + limits.cpu: 20000m + limits.memory: ${MEMORY_BUILD_LIMIT} + limits.nvidia.com/gpu: 0 + requests.cpu: 3000m + requests.memory: ${MEMORY_BUILD_REQUEST} + requests.nvidia.com/gpu: 0 +- apiVersion: v1 + kind: ResourceQuota + metadata: + name: storage + namespace: ${SPACE_NAME}-dev + spec: + hard: + limits.ephemeral-storage: 15Gi + requests.storage: 80Gi + requests.ephemeral-storage: 15Gi + count/persistentvolumeclaims: "10" +- apiVersion: v1 + kind: LimitRange + metadata: + name: resource-limits + namespace: ${SPACE_NAME}-dev + spec: + limits: + - type: "Container" + default: + cpu: 1000m + memory: 1000Mi + defaultRequest: + cpu: 10m + memory: 64Mi + +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-same-namespace + namespace: ${SPACE_NAME}-dev + spec: + podSelector: {} + ingress: + - from: + - podSelector: {} +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-openshift-ingress + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: ingress + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-openshift-monitoring + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: monitoring + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-codeready-workspaces-operator + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: codeready-workspaces + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-olm-namespaces + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + openshift.io/scc: anyuid + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-console-namespaces + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: console + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-openshift-virtualization-namespaces + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-virtualization-os-images + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-cnv + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-redhat-ods-app-to-mariadb + namespace: ${SPACE_NAME}-dev + spec: + podSelector: + matchLabels: + app: mariadb-dspa + ingress: + - ports: + - protocol: TCP + port: 3306 + from: + - podSelector: + matchLabels: + app.kubernetes.io/name: data-science-pipelines-operator + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: redhat-ods-applications + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-redhat-ods-app-to-mm + namespace: ${SPACE_NAME}-dev + spec: + podSelector: + matchLabels: + modelmesh-service: modelmesh-serving + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: redhat-ods-applications + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-dev-sandbox-managed-ns + namespace: ${SPACE_NAME}-dev + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + dev-sandbox/policy-group: ingress + policyTypes: + - Ingress +parameters: +- name: SPACE_NAME + required: true +- name: MEMORY_LIMIT + value: "30Gi" +- name: MEMORY_REQUEST + value: "30Gi" +- name: MEMORY_BUILD_LIMIT + value: "14Gi" +- name: MEMORY_BUILD_REQUEST + value: "14Gi" diff --git a/deploy/templates/nstemplatetiers/openclaw/ns_openclaw.yaml b/deploy/templates/nstemplatetiers/openclaw/ns_openclaw.yaml new file mode 100644 index 000000000..a38edbff1 --- /dev/null +++ b/deploy/templates/nstemplatetiers/openclaw/ns_openclaw.yaml @@ -0,0 +1,233 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: openclaw-openclaw +objects: +- apiVersion: v1 + kind: Namespace + metadata: + annotations: + openshift.io/description: ${SPACE_NAME}-openclaw + openshift.io/display-name: ${SPACE_NAME}-openclaw + openshift.io/requester: ${SPACE_NAME} + labels: + name: ${SPACE_NAME}-openclaw + name: ${SPACE_NAME}-openclaw + +# Role and RoleBindings for CRT administration (not associated with users) +- apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: exec-pods + namespace: ${SPACE_NAME}-openclaw + rules: + - apiGroups: + - "" + resources: + - pods/exec + verbs: + - get + - list + - watch + - create + - delete + - update +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: crtadmin-view + namespace: ${SPACE_NAME}-openclaw + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: crtadmin-users-view +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: crtadmin-pods + namespace: ${SPACE_NAME}-openclaw + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: exec-pods + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: crtadmin-users-view + +# Tight quotas: the -openclaw namespace has fixed, predictable resource needs +- apiVersion: v1 + kind: ResourceQuota + metadata: + name: compute-deploy + namespace: ${SPACE_NAME}-openclaw + spec: + scopes: + - NotTerminating + hard: + limits.cpu: 2000m + limits.memory: 3Gi + limits.nvidia.com/gpu: 0 + requests.cpu: 500m + requests.memory: 1Gi + requests.nvidia.com/gpu: 0 +- apiVersion: v1 + kind: ResourceQuota + metadata: + name: compute-build + namespace: ${SPACE_NAME}-openclaw + spec: + scopes: + - Terminating + hard: + limits.cpu: 2000m + limits.memory: 3Gi + limits.nvidia.com/gpu: 0 + requests.cpu: 500m + requests.memory: 1Gi + requests.nvidia.com/gpu: 0 +- apiVersion: v1 + kind: ResourceQuota + metadata: + name: storage + namespace: ${SPACE_NAME}-openclaw + spec: + hard: + limits.ephemeral-storage: 7Gi + requests.storage: 15Gi + requests.ephemeral-storage: 7Gi + count/persistentvolumeclaims: "1" +- apiVersion: v1 + kind: LimitRange + metadata: + name: resource-limits + namespace: ${SPACE_NAME}-openclaw + spec: + limits: + - type: "Container" + default: + cpu: 1000m + memory: 1000Mi + defaultRequest: + cpu: 10m + memory: 64Mi + +# Sandbox-standard ingress NetworkPolicies (same as -dev) +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-same-namespace + namespace: ${SPACE_NAME}-openclaw + spec: + podSelector: {} + ingress: + - from: + - podSelector: {} +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-openshift-ingress + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: ingress + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-openshift-monitoring + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: monitoring + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-codeready-workspaces-operator + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: codeready-workspaces + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-olm-namespaces + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + openshift.io/scc: anyuid + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-console-namespaces + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + network.openshift.io/policy-group: console + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-openshift-virtualization-namespaces + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-virtualization-os-images + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-cnv + podSelector: {} + policyTypes: + - Ingress +- apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: allow-from-dev-sandbox-managed-ns + namespace: ${SPACE_NAME}-openclaw + spec: + ingress: + - from: + - namespaceSelector: + matchLabels: + dev-sandbox/policy-group: ingress + policyTypes: + - Ingress +parameters: +- name: SPACE_NAME + required: true diff --git a/deploy/templates/nstemplatetiers/openclaw/spacerole_admin.yaml b/deploy/templates/nstemplatetiers/openclaw/spacerole_admin.yaml new file mode 100644 index 000000000..2ccf9007b --- /dev/null +++ b/deploy/templates/nstemplatetiers/openclaw/spacerole_admin.yaml @@ -0,0 +1,95 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: openclaw-spacerole-admin +objects: + +# Narrow user role applied to all namespaces in the tier. +# In -dev this is redundant with the admin RBAC embedded in ns_dev.yaml. +# In -openclaw this is the user's ONLY RBAC — carefully scoped to protect +# operator-managed Secrets (claw-proxy-ca, claw-gateway-token, llm-keys). +- apiVersion: rbac.authorization.k8s.io/v1 + kind: Role + metadata: + name: openclaw-user + namespace: ${NAMESPACE} + rules: + # Manage the OpenClaw CR lifecycle + - apiGroups: + - claw.sandbox.redhat.com + resources: + - claws + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + # View pods for troubleshooting + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - watch + # View pod logs for troubleshooting + - apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + # View events for troubleshooting + - apiGroups: + - "" + resources: + - events + verbs: + - get + - list + - watch + # View Routes to find the OpenClaw URL + - apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - get + - list + - watch + # Create/manage credential Secrets (user's LLM API keys). + # Deliberately excludes get/list to prevent reading operator-managed + # Secrets like claw-proxy-ca. Users create Secrets via + # "kubectl create secret" (requires only create verb). + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - update + - patch + - delete +- apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + metadata: + name: ${USERNAME}-openclaw-user + namespace: ${NAMESPACE} + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: openclaw-user + subjects: + - kind: User + name: ${USERNAME} + +parameters: +- name: NAMESPACE + required: true +- name: USERNAME + required: true diff --git a/deploy/templates/nstemplatetiers/openclaw/tier.yaml b/deploy/templates/nstemplatetiers/openclaw/tier.yaml new file mode 100644 index 000000000..8410bd42e --- /dev/null +++ b/deploy/templates/nstemplatetiers/openclaw/tier.yaml @@ -0,0 +1,25 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: openclaw-tier +objects: +- kind: NSTemplateTier + apiVersion: toolchain.dev.openshift.com/v1alpha1 + metadata: + name: openclaw + namespace: ${NAMESPACE} + spec: + clusterResources: + templateRef: ${CLUSTER_TEMPL_REF} + namespaces: + - templateRef: ${DEV_TEMPL_REF} + - templateRef: ${OPENCLAW_TEMPL_REF} + spaceRoles: + admin: + templateRef: ${ADMIN_TEMPL_REF} +parameters: +- name: NAMESPACE +- name: CLUSTER_TEMPL_REF +- name: DEV_TEMPL_REF +- name: OPENCLAW_TEMPL_REF +- name: ADMIN_TEMPL_REF diff --git a/deploy/templates/usertiers/openclaw/tier.yaml b/deploy/templates/usertiers/openclaw/tier.yaml new file mode 100644 index 000000000..8eb3d3077 --- /dev/null +++ b/deploy/templates/usertiers/openclaw/tier.yaml @@ -0,0 +1,16 @@ +apiVersion: template.openshift.io/v1 +kind: Template +metadata: + name: openclaw-usertier +objects: + - kind: UserTier + apiVersion: toolchain.dev.openshift.com/v1alpha1 + metadata: + name: openclaw + namespace: ${NAMESPACE} + spec: + deactivationTimeoutDays: ${{DEACTIVATION_TIMEOUT_DAYS}} +parameters: + - name: NAMESPACE + - name: DEACTIVATION_TIMEOUT_DAYS + value: "60" diff --git a/pkg/templates/nstemplatetiers/nstemplatetier_generator_test.go b/pkg/templates/nstemplatetiers/nstemplatetier_generator_test.go index 078bfd7ef..94fc9361f 100644 --- a/pkg/templates/nstemplatetiers/nstemplatetier_generator_test.go +++ b/pkg/templates/nstemplatetiers/nstemplatetier_generator_test.go @@ -31,12 +31,15 @@ var expectedProdTiers = []string{ "base1ns", "base1nsnoidling", "base1ns6didler", + "openclaw", } func nsTypes(tier string) []string { switch tier { case "base": return []string{"dev", "stage"} + case "openclaw": + return []string{"dev", "openclaw"} default: return []string{"dev"} }