fix: ensureNamespace uses Get-then-Create to avoid RBAC failures

The deployer was blindly calling Namespaces().Create() which triggered
cluster-scope RBAC checks even when the namespace already existed.
Now checks with Get() first and only creates if NotFound.

Also adds namespace get/create and secrets create/update/patch
permissions to the rdev-api-deployer ClusterRole.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-01-28 21:34:32 -07:00
parent 1adffbd50e
commit 043cc8c63b
2 changed files with 17 additions and 5 deletions

View File

@ -203,14 +203,18 @@ rules:
- apiGroups: ["networking.k8s.io"] - apiGroups: ["networking.k8s.io"]
resources: ["ingresses"] resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# Namespace verification (check exists before creating)
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "create"]
# Pod logs for deployment status # Pod logs for deployment status
- apiGroups: [""] - apiGroups: [""]
resources: ["pods", "pods/log"] resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"] verbs: ["get", "list", "watch"]
# Secrets for TLS certificates (read-only to reference existing) # Secrets for env vars and TLS certificates
- apiGroups: [""] - apiGroups: [""]
resources: ["secrets"] resources: ["secrets"]
verbs: ["get", "list"] verbs: ["get", "list", "create", "update", "patch"]
--- ---
# ClusterRoleBinding for rdev-api deployer # ClusterRoleBinding for rdev-api deployer
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1

View File

@ -15,15 +15,23 @@ import (
"github.com/orchard9/rdev/internal/domain" "github.com/orchard9/rdev/internal/domain"
) )
// ensureNamespace creates the deployment namespace if it doesn't exist. // ensureNamespace verifies the deployment namespace exists, creating it only if needed.
func (d *Deployer) ensureNamespace(ctx context.Context) error { func (d *Deployer) ensureNamespace(ctx context.Context) error {
_, err := d.client.CoreV1().Namespaces().Get(ctx, d.config.Namespace, metav1.GetOptions{})
if err == nil {
return nil // namespace exists
}
if !errors.IsNotFound(err) {
return err
}
// Namespace doesn't exist, try to create it
ns := &corev1.Namespace{ ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: d.config.Namespace, Name: d.config.Namespace,
}, },
} }
_, err = d.client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
_, err := d.client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) { if err != nil && !errors.IsAlreadyExists(err) {
return err return err
} }