// Package fission — чистый адаптер для Fission CRD API. // Содержит только то что специфично для Fission и не зависит от облачной платформы: // - SetupFissionNamespace: создание NS + label для Layer 1 NSWatcher + Fission ServiceAccounts + RoleBindings // - EnsureEnvironment, CleanupEnvironmentIfUnused: управление Fission Environment CRD // - GVR константы (client.go) // // Облачно-специфичные вещи (ResourceQuota, LimitRange, NetworkPolicy, NSManager) — в пакете cloud. package fission import ( "context" "fmt" "log" "os" "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" ) // SetupFissionNamespace создаёт namespace с нужными метками и регистрирует в нём // все Fission ServiceAccounts и RoleBindings. Идемпотентно. // // Метки на Namespace: // - managed-by=fission-console — для фильтрации наших NS в cloud-слое // - fission.io/managed=true — для Layer 1 NSWatcher в executor (auto-discovery) // // ServiceAccounts: // - fission-fetcher, fission-builder — нужны Fission pool pods в user NS // // RoleBindings (cluster-admin в рамках NS): // - Для всех Fission system SA из FISSION_SYSTEM_NAMESPACE (default: fission) // - Почему cluster-admin, а не admin: ClusterRole "admin" не включает fission.io/* CRD-группы, // executor получает "RBAC escalation" при создании Role. cluster-admin в RoleBinding // (не ClusterRoleBinding) безопасен — даёт полный доступ только внутри NS. func SetupFissionNamespace(ctx context.Context, dyn dynamic.Interface, ns string) error { // 1. Namespace labels := map[string]any{ "managed-by": "fission-console", "fission.io/managed": "true", // Layer 1: executor NSWatcher auto-discovers this NS } if strings.HasPrefix(ns, "fission-test-") { labels["fission-console/env"] = "test" } nsObj := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "Namespace", "metadata": map[string]any{ "name": ns, "labels": labels, }, }, } _, err := dyn.Resource(NamespaceGVR).Create(ctx, nsObj, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { return fmt.Errorf("create namespace %s: %w", ns, err) } // 2. ServiceAccounts для Fission pool pods в user namespace. // Fetcher sidecar требует fission-fetcher SA в том же NS где запускается. saGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "serviceaccounts"} for _, saName := range []string{"fission-fetcher", "fission-builder"} { saObj := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": "v1", "kind": "ServiceAccount", "metadata": map[string]any{"name": saName, "namespace": ns}, }} _, saErr := dyn.Resource(saGVR).Namespace(ns).Create(ctx, saObj, metav1.CreateOptions{}) if saErr != nil && !apierrors.IsAlreadyExists(saErr) { log.Printf("fission.SetupFissionNamespace: create SA %s/%s: %v", ns, saName, saErr) } } // 3. RoleBindings для всех Fission system SA. fissionSysNS := os.Getenv("FISSION_SYSTEM_NAMESPACE") if fissionSysNS == "" { fissionSysNS = "fission" } type rbDef struct { name string namespace string binding string } bindings := []rbDef{ {name: "fission-executor", binding: "fission-executor-user-ns"}, {name: "fission-router", binding: "fission-router-user-ns"}, {name: "fission-buildermgr", binding: "fission-buildermgr-user-ns"}, {name: "fission-kubewatcher", binding: "fission-kubewatcher-user-ns"}, {name: "fission-timer", binding: "fission-timer-user-ns"}, {name: "fission-fetcher", binding: "fission-fetcher-system-user-ns"}, {name: "fission-builder", binding: "fission-builder-system-user-ns"}, // fetcher/builder также нужны локально (запускаются в user NS) {name: "fission-fetcher", namespace: ns, binding: "fission-fetcher-local-user-ns"}, {name: "fission-builder", namespace: ns, binding: "fission-builder-local-user-ns"}, } rbGVR := schema.GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebindings"} for _, rb := range bindings { subjectNS := rb.namespace if subjectNS == "" { subjectNS = fissionSysNS } rbObj := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "rbac.authorization.k8s.io/v1", "kind": "RoleBinding", "metadata": map[string]any{"name": rb.binding, "namespace": ns}, "roleRef": map[string]any{ "apiGroup": "rbac.authorization.k8s.io", "kind": "ClusterRole", "name": "cluster-admin", }, "subjects": []any{map[string]any{ "kind": "ServiceAccount", "name": rb.name, "namespace": subjectNS, }}, }, } _, rbErr := dyn.Resource(rbGVR).Namespace(ns).Create(ctx, rbObj, metav1.CreateOptions{}) if rbErr != nil && !apierrors.IsAlreadyExists(rbErr) { log.Printf("fission.SetupFissionNamespace: create rolebinding %s/%s@%s: %v", ns, rb.name, subjectNS, rbErr) } } return nil }