5d115fc4b7
---ci--- project: orca phase: 2 milestone: v0.12 status: execute ---/ci--- Add ns.ValidateName rejecting .., /, \, leading -, null bytes, control chars, spaces, >128 chars, and reserved 'cluster'. Wire into ns create/delete/inspect/validate/inherit/set-constraint + --parent flag. Fuzz test + 14 traversal regression tests. No namespace dir can escape ORCA_HOME.
662 lines
17 KiB
Go
662 lines
17 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/paths"
|
|
)
|
|
|
|
// resetNSFlags zeroes the ns subcommand flag-bound vars so tests don't
|
|
// leak state.
|
|
func resetNSFlags() {
|
|
nsCreateParent = ""
|
|
nsCreateInheritsEnv = true
|
|
nsCreateInheritsSecret = true
|
|
nsInheritParent = ""
|
|
}
|
|
|
|
func writeDefaultsNS(t *testing.T, root string) {
|
|
t.Helper()
|
|
nsDir := filepath.Join(root, "_defaults")
|
|
if err := os.MkdirAll(nsDir, 0o755); err != nil {
|
|
t.Fatalf("mkdir _defaults: %v", err)
|
|
}
|
|
body := "---\nkind: Namespace\nname: _defaults\ninherits_env: true\ninherits_secrets: true\n---\n# defaults\n"
|
|
if err := os.WriteFile(filepath.Join(nsDir, "ns.md"), []byte(body), 0o644); err != nil {
|
|
t.Fatalf("write _defaults ns.md: %v", err)
|
|
}
|
|
}
|
|
|
|
func writeCustomNS(t *testing.T, root, name, parentsList string) {
|
|
t.Helper()
|
|
nsDir := filepath.Join(root, name)
|
|
if err := os.MkdirAll(nsDir, 0o755); err != nil {
|
|
t.Fatalf("mkdir %s: %v", name, err)
|
|
}
|
|
body := "---\nkind: Namespace\nname: " + name + "\n"
|
|
if parentsList != "" {
|
|
body += "parents: " + parentsList + "\n"
|
|
}
|
|
body += "inherits_env: true\ninherits_secrets: true\n---\n# " + name + "\n"
|
|
if err := os.WriteFile(filepath.Join(nsDir, "ns.md"), []byte(body), 0o644); err != nil {
|
|
t.Fatalf("write %s ns.md: %v", name, err)
|
|
}
|
|
}
|
|
|
|
func TestNSListEmpty(t *testing.T) {
|
|
t.Setenv("ORCA_HOME", t.TempDir())
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
|
|
rootCmd.SetArgs([]string{"ns", "list"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns list: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNSListWithNamespaces(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "list"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns list: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNSCreateHappy(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
|
|
rootCmd.SetArgs([]string{"ns", "create", "prod"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns create: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "prod", "ns.md")); err != nil {
|
|
t.Fatalf("ns.md not created: %v", err)
|
|
}
|
|
for _, sub := range []string{"db", "jobs", "alloc"} {
|
|
if _, err := os.Stat(filepath.Join(root, "prod", sub)); err != nil {
|
|
t.Errorf("subdir %s not created: %v", sub, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNSCreateWithParent(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "base", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "create", "child", "--parent", "base"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns create: %v", err)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(root, "child", "ns.md"))
|
|
if err != nil {
|
|
t.Fatalf("read ns.md: %v", err)
|
|
}
|
|
if !strings.Contains(string(data), "parents: [") || !strings.Contains(string(data), "\"base\"") {
|
|
t.Errorf("ns.md missing parents: %s", string(data))
|
|
}
|
|
}
|
|
|
|
func TestNSCreateExisting(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "create", "prod"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error creating existing namespace, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "already exists") {
|
|
t.Errorf("error = %q, want contains 'already exists'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNSCreateDefaultsRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
|
|
rootCmd.SetArgs([]string{"ns", "create", "_defaults"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error creating _defaults, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSCreateClusterRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
|
|
rootCmd.SetArgs([]string{"ns", "create", "cluster"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error creating cluster, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSDeleteHappy(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "delete", "prod"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns delete: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "prod")); !os.IsNotExist(err) {
|
|
t.Errorf("prod dir still exists after delete")
|
|
}
|
|
}
|
|
|
|
func TestNSDeleteDefaultsRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
|
|
rootCmd.SetArgs([]string{"ns", "delete", "_defaults"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error deleting _defaults, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSDeleteNonEmpty(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
// put a job in jobs/
|
|
if err := os.MkdirAll(filepath.Join(root, "prod", "jobs"), 0o755); err != nil {
|
|
t.Fatalf("mkdir jobs: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "prod", "jobs", "j1.md"), []byte("x"), 0o644); err != nil {
|
|
t.Fatalf("write job: %v", err)
|
|
}
|
|
|
|
rootCmd.SetArgs([]string{"ns", "delete", "prod"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error deleting non-empty namespace, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "refusing to delete") {
|
|
t.Errorf("error = %q, want contains 'refusing to delete'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNSDeleteMissing(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
|
|
rootCmd.SetArgs([]string{"ns", "delete", "ghost"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error deleting missing namespace, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSInspectHappy(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "inspect", "prod"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns inspect: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNSInspectJSON(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetArgs([]string{"ns", "inspect", "prod", "--json"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns inspect --json: %v", err)
|
|
}
|
|
var result map[string]any
|
|
if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &result); err != nil {
|
|
t.Fatalf("unmarshal: %v\n%s", err, buf.String())
|
|
}
|
|
if result["name"] != "prod" {
|
|
t.Errorf("name = %v, want prod", result["name"])
|
|
}
|
|
chain, _ := result["chain"].([]any)
|
|
if len(chain) < 2 {
|
|
t.Errorf("chain too short: %v", chain)
|
|
}
|
|
}
|
|
|
|
func TestNSInspectMissingNamespace(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
|
|
rootCmd.SetArgs([]string{"ns", "inspect", "ghost"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error for missing namespace, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSValidateHappy(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "validate", "prod"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns validate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNSValidateCycle(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
// a -> b, b -> a (cycle)
|
|
writeCustomNS(t, root, "a", "[\"b\"]")
|
|
writeCustomNS(t, root, "b", "[\"a\"]")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "validate", "a"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected cycle error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "cycle") {
|
|
t.Errorf("error = %q, want contains 'cycle'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNSValidateMissingParent(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "x", "[\"ghost\"]")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "validate", "x"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected missing-parent error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "ghost") || !strings.Contains(err.Error(), "not found") {
|
|
t.Errorf("error = %q, want contains ghost + not found", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNSValidateMissingNamespace(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
|
|
rootCmd.SetArgs([]string{"ns", "validate", "ghost"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error for missing namespace, got nil")
|
|
}
|
|
}
|
|
|
|
func TestRenderNSMd(t *testing.T) {
|
|
body := renderNSMd("foo", []string{"_defaults"}, true, false)
|
|
if !strings.Contains(body, "kind: Namespace") {
|
|
t.Errorf("missing kind: %s", body)
|
|
}
|
|
if !strings.Contains(body, "name: foo") {
|
|
t.Errorf("missing name: %s", body)
|
|
}
|
|
if !strings.Contains(body, "inherits_env: true") {
|
|
t.Errorf("missing inherits_env true: %s", body)
|
|
}
|
|
if !strings.Contains(body, "inherits_secrets: false") {
|
|
t.Errorf("missing inherits_secrets false: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestNSRootRegistered(t *testing.T) {
|
|
found := false
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Use == "ns" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("ns command not registered on root")
|
|
}
|
|
// ensure subcommands present
|
|
sub := map[string]bool{}
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Use == "ns" {
|
|
for _, sc := range c.Commands() {
|
|
sub[sc.Use] = true
|
|
}
|
|
}
|
|
}
|
|
for _, want := range []string{"list", "create <name>", "delete <name>", "inspect <name>", "validate <name>", "inherit <name>", "set-constraint <name> <key>=<value>"} {
|
|
if !sub[want] {
|
|
t.Errorf("missing ns subcommand %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNSListNoORCAHOME(t *testing.T) {
|
|
// ORCA_HOME points at a nonexistent dir; list should error.
|
|
t.Setenv("ORCA_HOME", filepath.Join(t.TempDir(), "nope"))
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
|
|
rootCmd.SetArgs([]string{"ns", "list"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error for missing ORCA_HOME, got nil")
|
|
}
|
|
}
|
|
|
|
var _ = paths.DefaultNamespace // keep paths import alive
|
|
|
|
func TestNSInheritSetsParent(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "inherit", "prod", "--parent", "_defaults"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns inherit: %v", err)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(root, "prod", "ns.md"))
|
|
if err != nil {
|
|
t.Fatalf("read ns.md: %v", err)
|
|
}
|
|
if !strings.Contains(string(data), "parents:") || !strings.Contains(string(data), "_defaults") {
|
|
t.Errorf("ns.md missing parents: %s", string(data))
|
|
}
|
|
}
|
|
|
|
func TestNSInheritCycleRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
// a -> b already; now set a's parent to b, then try b -> a.
|
|
writeCustomNS(t, root, "a", `["b"]`)
|
|
writeCustomNS(t, root, "b", "")
|
|
|
|
resetNSFlags()
|
|
rootCmd.SetArgs([]string{"ns", "inherit", "b", "--parent", "a"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected cycle error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "cycle") {
|
|
t.Errorf("error = %q, want contains 'cycle'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNSInheritSelfRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "inherit", "prod", "--parent", "prod"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected self-inherit error, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSInheritDefaultsRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "inherit", "_defaults", "--parent", "prod"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error setting parent on _defaults, got nil")
|
|
}
|
|
}
|
|
|
|
func TestNSSetConstraint(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "set-constraint", "prod", "max-allocs=10"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns set-constraint: %v", err)
|
|
}
|
|
data, err := os.ReadFile(filepath.Join(root, "prod", "ns.md"))
|
|
if err != nil {
|
|
t.Fatalf("read ns.md: %v", err)
|
|
}
|
|
if !strings.Contains(string(data), "constraints:") || !strings.Contains(string(data), "max-allocs=10") {
|
|
t.Errorf("ns.md missing constraints: %s", string(data))
|
|
}
|
|
}
|
|
|
|
func TestNSSetConstraintValidatePasses(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "set-constraint", "prod", "max-allocs=10"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns set-constraint: %v", err)
|
|
}
|
|
resetNSFlags()
|
|
rootCmd.SetArgs([]string{"ns", "validate", "prod"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns validate after set-constraint: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNSSetConstraintInspectShowsConstraint(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "set-constraint", "prod", "max-allocs=10"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns set-constraint: %v", err)
|
|
}
|
|
|
|
resetNSFlags()
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetArgs([]string{"ns", "inspect", "prod", "--json"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns inspect --json: %v", err)
|
|
}
|
|
var result map[string]any
|
|
if err := json.Unmarshal(bytes.TrimSpace(buf.Bytes()), &result); err != nil {
|
|
t.Fatalf("unmarshal: %v\n%s", err, buf.String())
|
|
}
|
|
cons, _ := result["constraints"].([]any)
|
|
found := false
|
|
for _, c := range cons {
|
|
if c == "max-allocs=10" {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("constraint max-allocs=10 not in inspect output: %v", cons)
|
|
}
|
|
}
|
|
|
|
func TestNSSetConstraintInvalidFormat(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "set-constraint", "prod", "noequals"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error for malformed constraint, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "<key>=<value>") {
|
|
t.Errorf("error = %q, want contains '<key>=<value>'", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNSSetConstraintDuplicate(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
|
|
rootCmd.SetArgs([]string{"ns", "set-constraint", "prod", "max-allocs=10"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("ns set-constraint first: %v", err)
|
|
}
|
|
resetNSFlags()
|
|
rootCmd.SetArgs([]string{"ns", "set-constraint", "prod", "max-allocs=10"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected duplicate error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), "already set") {
|
|
t.Errorf("error = %q, want contains 'already set'", err.Error())
|
|
}
|
|
}
|
|
|
|
// --- REQ-120 / F4 path traversal regression tests ---
|
|
|
|
// TestNSCreateTraversalRefused verifies that ns create rejects names
|
|
// that would traverse outside ORCA_HOME via ".." or "/".
|
|
func TestNSCreateTraversalRefused(t *testing.T) {
|
|
bad := []string{
|
|
"..",
|
|
"../etc",
|
|
"foo/../bar",
|
|
"/etc",
|
|
"etc/",
|
|
"foo/bar",
|
|
"-x",
|
|
"--flag",
|
|
"with space",
|
|
"tab\there",
|
|
"newline\nname",
|
|
}
|
|
for _, name := range bad {
|
|
t.Run(name, func(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
rootCmd.SetArgs([]string{"ns", "create", name})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Errorf("ns create %q should fail, got nil", name)
|
|
}
|
|
// Verify no directory was created outside ORCA_HOME.
|
|
// For ".." and "../etc", the danger is a dir was created
|
|
// outside root. Check root's parent has no new orca dirs.
|
|
parent := filepath.Dir(root)
|
|
entries, _ := os.ReadDir(parent)
|
|
for _, e := range entries {
|
|
// The temp dir itself is fine; anything else that looks
|
|
// like an orca namespace (has ns.md) outside root is a
|
|
// leak.
|
|
if e.Name() == filepath.Base(root) {
|
|
continue
|
|
}
|
|
if _, err := os.Stat(filepath.Join(parent, e.Name(), "ns.md")); err == nil {
|
|
t.Errorf("namespace dir leaked outside ORCA_HOME: %s", filepath.Join(parent, e.Name()))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestNSInheritTraversalRefused verifies --parent rejects traversal.
|
|
func TestNSInheritTraversalRefused(t *testing.T) {
|
|
root := t.TempDir()
|
|
t.Setenv("ORCA_HOME", root)
|
|
resetRootFlags(t)
|
|
resetNSFlags()
|
|
writeDefaultsNS(t, root)
|
|
writeCustomNS(t, root, "prod", "")
|
|
rootCmd.SetArgs([]string{"ns", "inherit", "prod", "--parent", "../../etc"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("ns inherit with traversal --parent should fail")
|
|
}
|
|
}
|