142 lines
4.5 KiB
Go
142 lines
4.5 KiB
Go
package stacks
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPathRejectsTraversal(t *testing.T) {
|
|
s, err := New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, name := range []string{"../x", "/tmp/x", "a/b", ""} {
|
|
if _, err := s.path(name); err == nil {
|
|
t.Fatalf("expected %q to be rejected", name)
|
|
}
|
|
}
|
|
if _, err := s.path("my-stack_1.2"); err != nil {
|
|
t.Fatalf("valid name rejected: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSaveStagesEnvAndSecretsBeforeComposeValidation(t *testing.T) {
|
|
root := t.TempDir()
|
|
bin := t.TempDir()
|
|
docker := filepath.Join(bin, "docker")
|
|
script := `#!/bin/sh
|
|
set -eu
|
|
[ -f .env ]
|
|
[ -f secrets/api_key ]
|
|
[ "$(cat secrets/api_key)" = "supersecret" ]
|
|
exit 0
|
|
`
|
|
if err := os.WriteFile(docker, []byte(script), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
s, err := New(root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
in := SaveInput{Compose: "services:\n app:\n image: nginx:alpine\n env_file: .env\nsecrets:\n api_key:\n file: ./secrets/api_key\n", Env: "A=B\n", Secrets: []SecretFile{{Name: "api_key", Content: "supersecret"}}}
|
|
if err := s.Save(context.Background(), "demo", in); err != nil {
|
|
t.Fatalf("save: %v", err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "demo", "compose.yaml")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if b, err := os.ReadFile(filepath.Join(root, "demo", "secrets", "api_key")); err != nil || string(b) != "supersecret" {
|
|
t.Fatalf("secret not committed: %q %v", string(b), err)
|
|
}
|
|
}
|
|
|
|
func TestSaveReportsDockerExecErrorWhenComposeIsSilent(t *testing.T) {
|
|
root := t.TempDir()
|
|
bin := t.TempDir()
|
|
docker := filepath.Join(bin, "docker")
|
|
if err := os.WriteFile(docker, []byte("#!/bin/sh\nexit 7\n"), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
|
|
s, _ := New(root)
|
|
err := s.Save(context.Background(), "demo", SaveInput{Compose: "services:\n app:\n image: nginx\n"})
|
|
if err == nil || !strings.Contains(err.Error(), "exit status 7") {
|
|
t.Fatalf("expected useful exit error, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDescriptorDigestPrefersDescriptor(t *testing.T) {
|
|
v := map[string]any{"Descriptor": map[string]any{"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}
|
|
got := descriptorDigest(v)
|
|
if got != "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
|
t.Fatalf("got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestDeletePreservesUnmanagedStackDataByDefault(t *testing.T) {
|
|
root := t.TempDir()
|
|
s, err := New(root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := filepath.Join(root, "demo")
|
|
if err := os.MkdirAll(filepath.Join(dir, "data"), 0750); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "compose.yaml"), []byte("services: {}\n"), 0640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "data", "important.db"), []byte("keep"), 0640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Delete(context.Background(), "demo", false, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(dir, "compose.yaml")); !os.IsNotExist(err) {
|
|
t.Fatalf("managed compose file should be removed, got %v", err)
|
|
}
|
|
if b, err := os.ReadFile(filepath.Join(dir, "data", "important.db")); err != nil || string(b) != "keep" {
|
|
t.Fatalf("unmanaged bind-mount data was damaged: %q %v", b, err)
|
|
}
|
|
}
|
|
|
|
func TestSafeDockerPositionalRejectsOptionLikeValues(t *testing.T) {
|
|
for _, v := range []string{"--help", "-f", "bad\nvalue"} {
|
|
if _, err := safeDockerPositional(v, "target"); err == nil {
|
|
t.Fatalf("expected %q to be rejected", v)
|
|
}
|
|
}
|
|
if got, err := safeDockerPositional("sha256:abc", "target"); err != nil || got != "sha256:abc" {
|
|
t.Fatalf("valid target rejected: %q %v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestExecRejectsOptionLikeServiceName(t *testing.T) {
|
|
s, err := New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.Exec(context.Background(), "demo", ExecInput{Service: "--index", Command: "id"}); err == nil {
|
|
t.Fatal("expected invalid service name to be rejected before invoking Docker")
|
|
}
|
|
}
|
|
|
|
func TestStackPathRejectsSymlinkDirectory(t *testing.T) {
|
|
root := t.TempDir()
|
|
outside := t.TempDir()
|
|
if err := os.Symlink(outside, filepath.Join(root, "demo")); err != nil {
|
|
t.Skipf("symlinks unavailable: %v", err)
|
|
}
|
|
s, err := New(root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.path("demo"); err == nil {
|
|
t.Fatal("expected symlink stack directory to be rejected")
|
|
}
|
|
}
|