mirror of
https://github.com/run-llama/workflows-py.git
synced 2026-08-24 20:01:34 -04:00
363 lines
12 KiB
Go
363 lines
12 KiB
Go
//go:build !integration
|
|
|
|
package controller
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
|
|
llamadeployv1 "llama-agents-operator/api/v1"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// findContainerSecurityContext tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestFindContainerSecurityContext(t *testing.T) {
|
|
sc := &corev1.SecurityContext{
|
|
RunAsNonRoot: ptr(true),
|
|
AllowPrivilegeEscalation: ptr(false),
|
|
}
|
|
containers := []corev1.Container{
|
|
{Name: "app", SecurityContext: sc},
|
|
{Name: "sidecar"},
|
|
}
|
|
|
|
t.Run("found with security context", func(t *testing.T) {
|
|
got := findContainerSecurityContext(containers, "app")
|
|
if got == nil {
|
|
t.Fatal("expected non-nil security context for app container")
|
|
}
|
|
if got != sc {
|
|
t.Error("expected same SecurityContext pointer")
|
|
}
|
|
})
|
|
|
|
t.Run("found without security context", func(t *testing.T) {
|
|
got := findContainerSecurityContext(containers, "sidecar")
|
|
if got != nil {
|
|
t.Error("expected nil for container with no security context")
|
|
}
|
|
})
|
|
|
|
t.Run("not found", func(t *testing.T) {
|
|
got := findContainerSecurityContext(containers, "nonexistent")
|
|
if got != nil {
|
|
t.Error("expected nil for nonexistent container")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Build job security context tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCreateBuildJob_SecurityContext(t *testing.T) {
|
|
r := &LlamaDeploymentReconciler{}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{
|
|
ProjectId: "proj-123",
|
|
RepoUrl: "https://github.com/example/repo",
|
|
GitRef: "main",
|
|
},
|
|
}
|
|
job := r.createBuildJob(ld, "abc123")
|
|
|
|
podSC := job.Spec.Template.Spec.SecurityContext
|
|
if podSC == nil {
|
|
t.Fatal("expected pod security context")
|
|
}
|
|
if podSC.FSGroup == nil || *podSC.FSGroup != AppServerGID {
|
|
t.Errorf("expected FSGroup %d, got %v", AppServerGID, podSC.FSGroup)
|
|
}
|
|
|
|
c := job.Spec.Template.Spec.Containers[0]
|
|
assertFullSecurityContext(t, c.SecurityContext, AppServerUID, AppServerGID)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Deployment security context tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCreateDeployment_SecurityContexts(t *testing.T) {
|
|
r := &LlamaDeploymentReconciler{}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{
|
|
ProjectId: "proj-123",
|
|
RepoUrl: "https://github.com/example/repo",
|
|
GitRef: "main",
|
|
},
|
|
}
|
|
dep := r.createDeploymentForLlama(ld, "build123")
|
|
|
|
podSC := dep.Spec.Template.Spec.SecurityContext
|
|
if podSC == nil {
|
|
t.Fatal("expected pod security context")
|
|
}
|
|
if podSC.FSGroup == nil || *podSC.FSGroup != AppServerGID {
|
|
t.Errorf("expected FSGroup %d, got %v", AppServerGID, podSC.FSGroup)
|
|
}
|
|
|
|
t.Run("bootstrap init container", func(t *testing.T) {
|
|
initContainers := dep.Spec.Template.Spec.InitContainers
|
|
if len(initContainers) == 0 {
|
|
t.Fatal("expected at least one init container")
|
|
}
|
|
assertFullSecurityContext(t, initContainers[0].SecurityContext, AppServerUID, AppServerGID)
|
|
})
|
|
|
|
t.Run("file-server container", func(t *testing.T) {
|
|
c := findContainer(dep.Spec.Template.Spec.Containers, "file-server")
|
|
if c == nil {
|
|
t.Fatal("expected file-server container")
|
|
}
|
|
assertFullSecurityContext(t, c.SecurityContext, NginxUID, NginxGID)
|
|
})
|
|
|
|
t.Run("app container", func(t *testing.T) {
|
|
c := findContainer(dep.Spec.Template.Spec.Containers, ContainerNameApp)
|
|
if c == nil {
|
|
t.Fatal("expected app container")
|
|
}
|
|
sc := c.SecurityContext
|
|
if sc == nil {
|
|
t.Fatal("expected security context on app container")
|
|
}
|
|
if sc.AllowPrivilegeEscalation == nil || *sc.AllowPrivilegeEscalation != false {
|
|
t.Error("expected AllowPrivilegeEscalation=false")
|
|
}
|
|
if sc.Capabilities == nil || len(sc.Capabilities.Drop) == 0 || sc.Capabilities.Drop[0] != "ALL" {
|
|
t.Error("expected capabilities drop ALL")
|
|
}
|
|
// App container intentionally omits RunAsUser/RunAsNonRoot for backward compat
|
|
if sc.RunAsUser != nil {
|
|
t.Error("expected RunAsUser to be nil on app container (backward compat)")
|
|
}
|
|
if sc.RunAsNonRoot != nil {
|
|
t.Error("expected RunAsNonRoot to be nil on app container (backward compat)")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// GIT_CONFIG env vars
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestCreateBuildJob_HasGitSafeDirectoryEnvVars(t *testing.T) {
|
|
r := &LlamaDeploymentReconciler{}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{
|
|
ProjectId: "proj-123",
|
|
RepoUrl: "https://github.com/example/repo",
|
|
GitRef: "main",
|
|
},
|
|
}
|
|
job := r.createBuildJob(ld, "abc123")
|
|
|
|
envs := job.Spec.Template.Spec.Containers[0].Env
|
|
assertEnvVar(t, envs, "GIT_CONFIG_COUNT", "1")
|
|
assertEnvVar(t, envs, "GIT_CONFIG_KEY_0", "safe.directory")
|
|
assertEnvVar(t, envs, "GIT_CONFIG_VALUE_0", "/opt/app")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Overlay security context propagation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestApplyBuildJobTemplateOverlay_PropagatesSecurityContext(t *testing.T) {
|
|
scheme := newTestScheme()
|
|
|
|
overrideSC := &corev1.SecurityContext{
|
|
RunAsNonRoot: ptr(true),
|
|
RunAsUser: ptr(int64(2000)),
|
|
RunAsGroup: ptr(int64(2000)),
|
|
AllowPrivilegeEscalation: ptr(false),
|
|
}
|
|
|
|
t.Run("from build container", func(t *testing.T) {
|
|
tmpl := &llamadeployv1.LlamaDeploymentTemplate{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "sc-build", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentTemplateSpec{
|
|
PodSpec: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{Name: ContainerNameBuild, SecurityContext: overrideSC},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tmpl).Build()
|
|
r := &LlamaDeploymentReconciler{Client: fakeClient, Scheme: scheme}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{TemplateName: "sc-build"},
|
|
}
|
|
|
|
job := r.createBuildJob(ld, "abc123")
|
|
if err := r.applyBuildJobTemplateOverlay(context.Background(), ld, job); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
sc := job.Spec.Template.Spec.Containers[0].SecurityContext
|
|
if sc == nil || sc.RunAsUser == nil || *sc.RunAsUser != 2000 {
|
|
t.Error("expected build container SecurityContext to be replaced by template overlay (RunAsUser=2000)")
|
|
}
|
|
})
|
|
|
|
t.Run("falls back from app container", func(t *testing.T) {
|
|
tmpl := &llamadeployv1.LlamaDeploymentTemplate{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "sc-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentTemplateSpec{
|
|
PodSpec: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{Name: ContainerNameApp, SecurityContext: overrideSC},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tmpl).Build()
|
|
r := &LlamaDeploymentReconciler{Client: fakeClient, Scheme: scheme}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{TemplateName: "sc-app"},
|
|
}
|
|
|
|
job := r.createBuildJob(ld, "abc123")
|
|
if err := r.applyBuildJobTemplateOverlay(context.Background(), ld, job); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
sc := job.Spec.Template.Spec.Containers[0].SecurityContext
|
|
if sc == nil || sc.RunAsUser == nil || *sc.RunAsUser != 2000 {
|
|
t.Error("expected build container SecurityContext to fall back to app container overlay (RunAsUser=2000)")
|
|
}
|
|
})
|
|
|
|
t.Run("pod-level security context override", func(t *testing.T) {
|
|
podSC := &corev1.PodSecurityContext{
|
|
RunAsNonRoot: ptr(true),
|
|
FSGroup: ptr(int64(3000)),
|
|
}
|
|
tmpl := &llamadeployv1.LlamaDeploymentTemplate{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "sc-pod", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentTemplateSpec{
|
|
PodSpec: corev1.PodTemplateSpec{
|
|
Spec: corev1.PodSpec{
|
|
SecurityContext: podSC,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tmpl).Build()
|
|
r := &LlamaDeploymentReconciler{Client: fakeClient, Scheme: scheme}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{TemplateName: "sc-pod"},
|
|
}
|
|
|
|
job := r.createBuildJob(ld, "abc123")
|
|
if err := r.applyBuildJobTemplateOverlay(context.Background(), ld, job); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
got := job.Spec.Template.Spec.SecurityContext
|
|
if got == nil || got.FSGroup == nil || *got.FSGroup != 3000 {
|
|
t.Error("expected pod SecurityContext FSGroup to be overridden to 3000")
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Nginx config tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestGenerateNginxConfig_NonRootDirectives(t *testing.T) {
|
|
r := &LlamaDeploymentReconciler{}
|
|
ld := &llamadeployv1.LlamaDeployment{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "my-app", Namespace: "default"},
|
|
Spec: llamadeployv1.LlamaDeploymentSpec{},
|
|
}
|
|
config := r.generateNginxConfig(ld)
|
|
|
|
required := []string{
|
|
"pid /tmp/nginx.pid",
|
|
"client_body_temp_path /tmp/client_temp",
|
|
"proxy_temp_path /tmp/proxy_temp",
|
|
"fastcgi_temp_path /tmp/fastcgi_temp",
|
|
"uwsgi_temp_path /tmp/uwsgi_temp",
|
|
"scgi_temp_path /tmp/scgi_temp",
|
|
}
|
|
|
|
for _, directive := range required {
|
|
if !strings.Contains(config, directive) {
|
|
t.Errorf("expected nginx config to contain %q", directive)
|
|
}
|
|
}
|
|
|
|
if strings.Contains(config, "user root") {
|
|
t.Error("nginx config should not contain 'user root' directive")
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func assertFullSecurityContext(t *testing.T, sc *corev1.SecurityContext, uid, gid int64) {
|
|
t.Helper()
|
|
if sc == nil {
|
|
t.Fatal("expected non-nil security context")
|
|
}
|
|
if sc.RunAsNonRoot == nil || *sc.RunAsNonRoot != true {
|
|
t.Error("expected RunAsNonRoot=true")
|
|
}
|
|
if sc.RunAsUser == nil || *sc.RunAsUser != uid {
|
|
t.Errorf("expected RunAsUser=%d, got %v", uid, sc.RunAsUser)
|
|
}
|
|
if sc.RunAsGroup == nil || *sc.RunAsGroup != gid {
|
|
t.Errorf("expected RunAsGroup=%d, got %v", gid, sc.RunAsGroup)
|
|
}
|
|
if sc.AllowPrivilegeEscalation == nil || *sc.AllowPrivilegeEscalation != false {
|
|
t.Error("expected AllowPrivilegeEscalation=false")
|
|
}
|
|
if sc.Capabilities == nil || len(sc.Capabilities.Drop) == 0 || sc.Capabilities.Drop[0] != "ALL" {
|
|
t.Error("expected capabilities drop ALL")
|
|
}
|
|
}
|
|
|
|
func assertEnvVar(t *testing.T, envs []corev1.EnvVar, name, value string) {
|
|
t.Helper()
|
|
for _, e := range envs {
|
|
if e.Name == name {
|
|
if e.Value != value {
|
|
t.Errorf("expected %s=%q, got %q", name, value, e.Value)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
t.Errorf("expected env var %s not found", name)
|
|
}
|
|
|
|
func findContainer(containers []corev1.Container, name string) *corev1.Container {
|
|
for i := range containers {
|
|
if containers[i].Name == name {
|
|
return &containers[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|