package handler import ( "context" "testing" "github.com/goodrain/rainbond/db" dbdao "github.com/goodrain/rainbond/db/dao" dbmodel "github.com/goodrain/rainbond/db/model" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" dynamic "k8s.io/client-go/dynamic" dynamicfake "k8s.io/client-go/dynamic/fake" ) type testManager struct { db.Manager tenantDao dbdao.TenantDao } func (m testManager) TenantDao() dbdao.TenantDao { return m.tenantDao } type testTenantDao struct { dbdao.TenantDao tenant *dbmodel.Tenants err error requestedFor string } func (d *testTenantDao) GetTenantIDByName(name string) (*dbmodel.Tenants, error) { d.requestedFor = name return d.tenant, d.err } // capability_id: rainbond.ns-resource.handler-singleton func TestGetNsResourceHandlerSingleton(t *testing.T) { h1 := GetNsResourceHandler() h2 := GetNsResourceHandler() assert.NotNil(t, h1) assert.Equal(t, h1, h2) } // capability_id: rainbond.ns-resource.mark-source func TestInjectSourceLabelYaml(t *testing.T) { labels := map[string]string{} injectSourceLabel(labels, "yaml") assert.Equal(t, "yaml", labels["rainbond.io/source"]) } // capability_id: rainbond.ns-resource.mark-source func TestInjectSourceLabelManual(t *testing.T) { labels := map[string]string{} injectSourceLabel(labels, "manual") assert.Equal(t, "manual", labels["rainbond.io/source"]) } // capability_id: rainbond.ns-resource.detect-source func TestDetectResourceSource(t *testing.T) { tests := []struct { labels map[string]string expected string }{ {map[string]string{"app.kubernetes.io/managed-by": "Helm"}, "helm"}, {map[string]string{"rainbond.io/source": "yaml"}, "yaml"}, {map[string]string{"rainbond.io/source": "manual"}, "manual"}, {map[string]string{}, "external"}, {nil, "external"}, } for _, tt := range tests { result := detectResourceSource(tt.labels) assert.Equal(t, tt.expected, result) } } // capability_id: rainbond.ns-resource.resolve-tenant-namespace func TestGetTenantNamespaceUsesNamespaceField(t *testing.T) { tenantDao := &testTenantDao{ tenant: &dbmodel.Tenants{ Name: "demo-team", UUID: "tenant-uuid", Namespace: "tenant-namespace", }, } db.SetTestManager(testManager{Manager: workloadTestManager{database: namespaceAdmissionDB(t)}, tenantDao: tenantDao}) defer db.SetTestManager(nil) ns, err := GetNsResourceHandler().getTenantNamespace("demo-team") assert.NoError(t, err) assert.Equal(t, "demo-team", tenantDao.requestedFor) assert.Equal(t, "tenant-namespace", ns) } // capability_id: rainbond.ns-resource.resolve-tenant-namespace func TestGetTenantNamespaceFallsBackToUUIDWhenNamespaceEmpty(t *testing.T) { tenantDao := &testTenantDao{ tenant: &dbmodel.Tenants{ Name: "demo-team", UUID: "tenant-uuid", }, } db.SetTestManager(testManager{Manager: workloadTestManager{database: namespaceAdmissionDB(t)}, tenantDao: tenantDao}) defer db.SetTestManager(nil) ns, err := GetNsResourceHandler().getTenantNamespace("demo-team") assert.NoError(t, err) assert.Equal(t, "demo-team", tenantDao.requestedFor) assert.Equal(t, "tenant-uuid", ns) } // capability_id: rainbond.ns-resource.batch-create func TestCreateNsResourceBatchAggregatesPartialSuccess(t *testing.T) { tenantDao := &testTenantDao{ tenant: &dbmodel.Tenants{ Name: "demo-team", UUID: "tenant-uuid", Namespace: "team-namespace", }, } db.SetTestManager(testManager{Manager: workloadTestManager{database: namespaceAdmissionDB(t)}, tenantDao: tenantDao}) defer db.SetTestManager(nil) scheme := runtime.NewScheme() assert.NoError(t, appsv1.AddToScheme(scheme)) assert.NoError(t, corev1.AddToScheme(scheme)) assert.NoError(t, rbacv1.AddToScheme(scheme)) client := dynamicfake.NewSimpleDynamicClient(scheme, &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "demo-service", Namespace: "team-namespace", }, }) mapper := newTestNsResourceMapper( appsv1.SchemeGroupVersion.WithKind("Deployment"), meta.RESTScopeNamespace, corev1.SchemeGroupVersion.WithKind("Service"), meta.RESTScopeNamespace, rbacv1.SchemeGroupVersion.WithKind("ClusterRoleBinding"), meta.RESTScopeRoot, ) originalMapper := nsResourceRESTMapper originalDynamicClient := nsResourceDynamicClient nsResourceRESTMapper = func() meta.RESTMapper { return mapper } nsResourceDynamicClient = func() dynamic.Interface { return client } defer func() { nsResourceRESTMapper = originalMapper nsResourceDynamicClient = originalDynamicClient }() result, statusCode, err := GetNsResourceHandler().CreateNsResource("demo-team", "yaml", []byte(` apiVersion: apps/v1 kind: Deployment metadata: name: demo-deploy spec: selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - name: demo image: nginx --- apiVersion: v1 kind: Service metadata: name: demo-service spec: selector: app: demo ports: - port: 80 --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: demo-binding subjects: [] roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: view `)) assert.NoError(t, err) assert.Equal(t, 207, statusCode) assert.Equal(t, 3, result.Summary.Total) assert.Equal(t, 2, result.Summary.SuccessCount) assert.Equal(t, 1, result.Summary.FailureCount) assert.True(t, result.Summary.PartialSuccess) assert.Len(t, result.Results, 3) assert.Equal(t, "Deployment", result.Results[0].Kind) assert.Equal(t, "team-namespace", result.Results[0].Namespace) assert.True(t, result.Results[0].Success) assert.Equal(t, "Service", result.Results[1].Kind) assert.False(t, result.Results[1].Success) assert.Contains(t, result.Results[1].Message, "already exists") assert.Equal(t, "cluster", result.Results[2].ResourceScope) assert.True(t, result.Results[2].Success) deployment, err := client.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}). Namespace("team-namespace"). Get(context.Background(), "demo-deploy", metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, "yaml", deployment.GetLabels()["rainbond.io/source"]) clusterRoleBinding, err := client.Resource(schema.GroupVersionResource{ Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrolebindings", }).Get(context.Background(), "demo-binding", metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, "yaml", clusterRoleBinding.GetLabels()["rainbond.io/source"]) assert.Equal(t, "", clusterRoleBinding.GetNamespace()) } // capability_id: rainbond.ns-resource.batch-create func TestCreateNsResourceBatchOverridesExplicitNamespace(t *testing.T) { tenantDao := &testTenantDao{ tenant: &dbmodel.Tenants{ Name: "demo-team", UUID: "tenant-uuid", Namespace: "team-namespace", }, } db.SetTestManager(testManager{Manager: workloadTestManager{database: namespaceAdmissionDB(t)}, tenantDao: tenantDao}) defer db.SetTestManager(nil) scheme := runtime.NewScheme() assert.NoError(t, corev1.AddToScheme(scheme)) client := dynamicfake.NewSimpleDynamicClient(scheme) mapper := newTestNsResourceMapper(corev1.SchemeGroupVersion.WithKind("ConfigMap"), meta.RESTScopeNamespace) originalMapper := nsResourceRESTMapper originalDynamicClient := nsResourceDynamicClient nsResourceRESTMapper = func() meta.RESTMapper { return mapper } nsResourceDynamicClient = func() dynamic.Interface { return client } defer func() { nsResourceRESTMapper = originalMapper nsResourceDynamicClient = originalDynamicClient }() result, statusCode, err := GetNsResourceHandler().CreateNsResource("demo-team", "manual", []byte(` apiVersion: v1 kind: ConfigMap metadata: name: explicit-config namespace: custom-namespace data: demo: "1" `)) assert.NoError(t, err) assert.Equal(t, 200, statusCode) assert.Equal(t, "team-namespace", result.Results[0].Namespace) configMap, err := client.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}). Namespace("team-namespace"). Get(context.Background(), "explicit-config", metav1.GetOptions{}) if !assert.NoError(t, err) { return } assert.Equal(t, "team-namespace", configMap.GetNamespace()) assert.Equal(t, "manual", configMap.GetLabels()["rainbond.io/source"]) } // capability_id: rainbond.ns-resource.ingress-summary func TestToNsResourceInfoIncludesIngressSummary(t *testing.T) { ingress := unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "networking.k8s.io/v1", "kind": "Ingress", "metadata": map[string]interface{}{ "name": "demo-ingress", }, "spec": map[string]interface{}{ "ingressClassName": "nginx", "defaultBackend": map[string]interface{}{ "service": map[string]interface{}{ "name": "default-svc", }, }, "rules": []interface{}{ map[string]interface{}{ "host": "api.example.com", "http": map[string]interface{}{ "paths": []interface{}{ map[string]interface{}{ "backend": map[string]interface{}{ "service": map[string]interface{}{ "name": "api-svc", }, }, }, }, }, }, map[string]interface{}{ "host": "web.example.com", "http": map[string]interface{}{ "paths": []interface{}{ map[string]interface{}{ "backend": map[string]interface{}{ "service": map[string]interface{}{ "name": "web-svc", }, }, }, }, }, }, }, "tls": []interface{}{ map[string]interface{}{ "hosts": []interface{}{"api.example.com"}, }, }, }, }, } info := toNsResourceInfo(ingress) assert.Equal(t, "demo-ingress", info.Name) assert.Equal(t, "Ingress", info.Kind) assert.Equal(t, "nginx", info.IngressClass) assert.ElementsMatch(t, []string{"api.example.com", "web.example.com"}, info.Hosts) assert.ElementsMatch(t, []string{"api.example.com"}, info.TLSHosts) assert.ElementsMatch(t, []string{"default-svc", "api-svc", "web-svc"}, info.BackendServices) } func newTestNsResourceMapper(entries ...interface{}) meta.RESTMapper { groupVersions := make([]schema.GroupVersion, 0) for i := 0; i < len(entries); i += 2 { groupVersions = append(groupVersions, entries[i].(schema.GroupVersionKind).GroupVersion()) } mapper := meta.NewDefaultRESTMapper(groupVersions) for i := 0; i < len(entries); i += 2 { mapper.Add(entries[i].(schema.GroupVersionKind), entries[i+1].(meta.RESTScope)) } return mapper }