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
}
func TestGetNsResourceHandlerSingleton(t *testing.T) {
h1 := GetNsResourceHandler()
h2 := GetNsResourceHandler()
assert.NotNil(t, h1)
assert.Equal(t, h1, h2)
}
func TestInjectSourceLabelYaml(t *testing.T) {
labels := map[string]string{}
injectSourceLabel(labels, "yaml")
assert.Equal(t, "yaml", labels["rainbond.io/source"])
}
func TestInjectSourceLabelManual(t *testing.T) {
labels := map[string]string{}
injectSourceLabel(labels, "manual")
assert.Equal(t, "manual", labels["rainbond.io/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)
}
}
func TestGetTenantNamespaceUsesNamespaceField(t *testing.T) {
tenantDao := &testTenantDao{
tenant: &dbmodel.Tenants{
Name: "demo-team",
UUID: "tenant-uuid",
Namespace: "tenant-namespace",
},
}
db.SetTestManager(testManager{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)
}
func TestGetTenantNamespaceFallsBackToUUIDWhenNamespaceEmpty(t *testing.T) {
tenantDao := &testTenantDao{
tenant: &dbmodel.Tenants{
Name: "demo-team",
UUID: "tenant-uuid",
},
}
db.SetTestManager(testManager{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)
}
func TestCreateNsResourceBatchAggregatesPartialSuccess(t *testing.T) {
tenantDao := &testTenantDao{
tenant: &dbmodel.Tenants{
Name: "demo-team",
UUID: "tenant-uuid",
Namespace: "team-namespace",
},
}
db.SetTestManager(testManager{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())
}
func TestCreateNsResourceBatchOverridesExplicitNamespace(t *testing.T) {
tenantDao := &testTenantDao{
tenant: &dbmodel.Tenants{
Name: "demo-team",
UUID: "tenant-uuid",
Namespace: "team-namespace",
},
}
db.SetTestManager(testManager{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"])
}
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
}