compose.yaml, docker-compose.yml and their variants tracked by git are
listed under Repos, their services' environment variables as secrets,
named service.VAR.
- environment as a map or a list, and <<: *anchors shared between
services; the secret patterns pick the keys, the rest hidden, with
compose.secretPatterns for compose files' own.
- ${VAR}, $VAR, ${VAR:?…} and variables without a value are marked as
references; docker-compose.example.yml and the like are hidden.
- Jasypt values decrypt with the service's JASYPT_ENCRYPTOR_PASSWORD.
- A blue Docker icon, themeable with composeColor.
141 lines
4.1 KiB
Go
141 lines
4.1 KiB
Go
package repo
|
|
|
|
import (
|
|
"context"
|
|
"maps"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.kebcloud.no/keb/lazysecrets/internal/provider"
|
|
)
|
|
|
|
func TestIsCompose(t *testing.T) {
|
|
for f, want := range map[string]bool{
|
|
"docker-compose.yml": true,
|
|
"deploy/compose.yaml": true,
|
|
"docker-compose.test.yml": true,
|
|
"docker-compose-dev.yaml": true,
|
|
"docker-compose.example.yml": true, // listed hidden
|
|
"applications.yaml": false,
|
|
"composer.json": false,
|
|
"compose.yml.bak": false,
|
|
} {
|
|
if IsCompose(f) != want {
|
|
t.Errorf("%s: %t", f, !want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestComposePlaceholder(t *testing.T) {
|
|
for v, want := range map[string]bool{
|
|
"${DB_PASSWORD}": true, "$DB_PASSWORD": true, "${DB_PASSWORD:?set it}": true, "${DB_PASSWORD?}": true,
|
|
"${DB_PASSWORD:-hunter2}": false, "${DB_PASSWORD-hunter2}": false, // the default is in the file
|
|
"hunter2": false, "pre-${DB_PASSWORD}": false, "$": false,
|
|
} {
|
|
if got := reference(composePlaceholder, v); got != want {
|
|
t.Errorf("%s: reference %t", v, got)
|
|
}
|
|
}
|
|
if name, def, hasDef, _ := composePlaceholder("${PW:-x}"); name != "PW" || def != "x" || !hasDef {
|
|
t.Errorf("default: %s %s %t", name, def, hasDef)
|
|
}
|
|
}
|
|
|
|
func TestParseCompose(t *testing.T) {
|
|
keys, err := parseCompose("", []byte(`x-db: &db
|
|
DB_USER: app
|
|
DB_PASSWORD: shared
|
|
services:
|
|
api:
|
|
image: api
|
|
environment:
|
|
<<: *db
|
|
DB_PASSWORD: own
|
|
API_KEY: ${API_KEY}
|
|
FROM_HOST:
|
|
worker:
|
|
environment:
|
|
- QUEUE_PASSWORD=hunter2
|
|
- TOKEN
|
|
- EMPTY=
|
|
web:
|
|
image: web
|
|
`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := map[string]string{}
|
|
for _, k := range keys {
|
|
ref := ""
|
|
if k.ref {
|
|
ref = " (ref)"
|
|
}
|
|
got[k.name] = k.value + ref
|
|
}
|
|
want := map[string]string{
|
|
"api.DB_USER": "app", "api.DB_PASSWORD": "own", "api.API_KEY": "${API_KEY} (ref)", "api.FROM_HOST": " (ref)",
|
|
"worker.QUEUE_PASSWORD": "hunter2", "worker.TOKEN": " (ref)", "worker.EMPTY": "",
|
|
}
|
|
if !maps.Equal(got, want) {
|
|
t.Errorf("got %v\nwant %v", got, want)
|
|
}
|
|
for _, k := range keys {
|
|
if k.name == "worker.QUEUE_PASSWORD" && (k.line != 14 || k.key != "QUEUE_PASSWORD") {
|
|
t.Errorf("%s: line %d, key %s", k.name, k.line, k.key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestComposeFiles(t *testing.T) {
|
|
root := gitRepo(t, map[string]string{
|
|
"docker-compose.yml": `services:
|
|
api:
|
|
environment:
|
|
PORT: "8080"
|
|
DB_PASSWORD: ${DB_PASSWORD}
|
|
JASYPT_ENCRYPTOR_PASSWORD: test-password
|
|
API_TOKEN: ` + jasyptHunter2 + `
|
|
`,
|
|
"docker-compose.example.yml": "services:\n api:\n environment:\n DB_PASSWORD: changeme\n",
|
|
})
|
|
t.Setenv("JASYPT_ENCRYPTOR_PASSWORD", "")
|
|
ctx := context.Background()
|
|
p := New(Settings{Dir: root})
|
|
srcs, _ := p.Sources(ctx)
|
|
cs, err := p.Containers(ctx, srcs[0])
|
|
if err != nil || len(cs) != 2 {
|
|
t.Fatalf("files: %+v %v", cs, err)
|
|
}
|
|
for _, c := range cs {
|
|
if c.Detail != "compose · yaml" || c.Hidden != (c.Name == "docker-compose.example.yml") {
|
|
t.Errorf("%s: %q hidden %t", c.Name, c.Detail, c.Hidden)
|
|
}
|
|
}
|
|
compose := cs[1] // after docker-compose.example.yml
|
|
|
|
keys, _ := p.Objects(ctx, compose, provider.TypeSecret)
|
|
got := map[string]string{}
|
|
for _, k := range shown(keys) {
|
|
got[k.Name] = k.Detail
|
|
}
|
|
if want := map[string]string{"api.DB_PASSWORD": "reference", "api.JASYPT_ENCRYPTOR_PASSWORD": "", "api.API_TOKEN": "jasypt"}; !maps.Equal(got, want) {
|
|
t.Errorf("keys %v, want %v", got, want)
|
|
}
|
|
// The service's JASYPT_ENCRYPTOR_PASSWORD decrypts its values.
|
|
link := provider.Object{Container: provider.Container{Source: srcs[0], Name: "docker-compose.yml"}, Name: "api.API_TOKEN"}
|
|
if v, err := p.Value(ctx, link); err != nil || v.Reveal() != "hunter2" {
|
|
t.Errorf("jasypt: %q %v", v.Reveal(), err)
|
|
}
|
|
if f, _ := p.File(compose, &link); f.Line != 7 {
|
|
t.Errorf("line %d", f.Line)
|
|
}
|
|
|
|
// compose.secretPatterns over the shared ones.
|
|
os.WriteFile(filepath.Join(root, ".lazysecrets.yaml"), []byte("secretPatterns:\n keys: [\"^nothing$\"]\ncompose:\n secretPatterns:\n keys: [\"^PORT$\"]\n values: []\n"), 0o644)
|
|
keys, _ = p.Objects(ctx, compose, provider.TypeSecret)
|
|
if s := shown(keys); len(s) != 1 || s[0].Name != "api.PORT" {
|
|
t.Errorf("compose's patterns: %v", s)
|
|
}
|
|
}
|