Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@

# Infrastructure
#BLOCKCHAIN_IMAGE=

# Third-party
#POSTGRES_IMAGE=
#OPENBAO_IMAGE=
#REDIS_IMAGE=
#DYNAMODB_LOCAL_IMAGE=
#SMTP4DEV_IMAGE=
#MINIO_IMAGE=
125 changes: 125 additions & 0 deletions images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package smelt_test

import (
"os"
"path/filepath"
"strings"
"testing"

"gopkg.in/yaml.v3"
)

// Third-party images read a <NAME>_IMAGE variable whose default is the tag
// the stack used before the variable existed, so an unset variable renders
// the same compose config and a set one repins exactly these services.
var thirdPartyImages = map[string]map[string]string{
"systems/ingot/compose.yml": {
"ingot-openbao": "${OPENBAO_IMAGE:-openbao/openbao:2.6}",
"ingot-openbao-init": "${OPENBAO_IMAGE:-openbao/openbao:2.6}",
"ingot-postgres": "${POSTGRES_IMAGE:-postgres:16-alpine}",
},
"systems/upload/compose.yml": {
"postgres": "${POSTGRES_IMAGE:-postgres:16-alpine}",
},
"systems/hilt/compose.yml": {
"hilt-vault": "${OPENBAO_IMAGE:-openbao/openbao:2.6}",
"hilt-postgres": "${POSTGRES_IMAGE:-postgres:16-alpine}",
},
"systems/swarf/compose.yml": {
"swarf-postgres": "${POSTGRES_IMAGE:-postgres:16-alpine}",
},
"systems/plc/compose.yml": {
"plc-postgres": "${POSTGRES_IMAGE:-postgres:16-alpine}",
},
"systems/common/compose.yml": {
"dynamodb-local": "${DYNAMODB_LOCAL_IMAGE:-amazon/dynamodb-local:latest}",
"email": "${SMTP4DEV_IMAGE:-rnwood/smtp4dev:v3}",
},
"systems/indexing/indexer/compose.yml": {
"redis": "${REDIS_IMAGE:-redis:7-alpine}",
},
}

type composeImages struct {
Services map[string]struct {
Image string `yaml:"image"`
} `yaml:"services"`
}

func readComposeImages(t *testing.T, path string) composeImages {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var c composeImages
if err := yaml.Unmarshal(data, &c); err != nil {
t.Fatalf("%s: %v", path, err)
}
return c
}

func TestThirdPartyImagesReadOverrideVariables(t *testing.T) {
for path, want := range thirdPartyImages {
c := readComposeImages(t, path)
for svc, image := range want {
got, ok := c.Services[svc]
if !ok {
t.Errorf("%s: service %q not found", path, svc)
continue
}
if got.Image != image {
t.Errorf("%s: %s image = %q, want %q", path, svc, got.Image, image)
}
}
}
}

// Every image the root compose file runs is pinnable from the environment.
// The telemetry system is not part of the root stack and keeps its literals.
func TestIncludedComposeImagesAreInterpolated(t *testing.T) {
root := readIncludes(t, "compose.yml")
checked := 0
for _, path := range root {
if strings.HasPrefix(path, "generated/") {
continue // covered by the generator's own tests
}
for _, p := range append([]string{path}, readIncludes(t, path)...) {
for svc, s := range readComposeImages(t, p).Services {
if s.Image == "" {
continue // built locally
}
checked++
if !strings.HasPrefix(s.Image, "${") {
t.Errorf("%s: %s image %q has no override variable", p, svc, s.Image)
}
}
}
}
if checked == 0 {
t.Fatal("no images checked")
}
}

// readIncludes returns the include paths of a compose file, relative to the
// repository root.
func readIncludes(t *testing.T, path string) []string {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
var c struct {
Include []struct {
Path string `yaml:"path"`
} `yaml:"include"`
}
if err := yaml.Unmarshal(data, &c); err != nil {
t.Fatalf("%s: %v", path, err)
}
var out []string
for _, inc := range c.Include {
out = append(out, filepath.Join(filepath.Dir(path), inc.Path))
}
return out
}
4 changes: 2 additions & 2 deletions pkg/generate/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func buildPiriService(node manifest.ResolvedPiriNode) ComposeService {

func buildPostgresService() ComposeService {
return ComposeService{
Image: "postgres:16-alpine",
Image: "${POSTGRES_IMAGE:-postgres:16-alpine}",
Ports: []string{"${SMELT_PIRI_POSTGRES_PORT:-15074:5432}"},
Environment: []string{
"POSTGRES_USER=piri",
Expand All @@ -164,7 +164,7 @@ func buildPostgresService() ComposeService {

func buildPostgresInitService(databases []string) ComposeService {
return ComposeService{
Image: "postgres:16-alpine",
Image: "${POSTGRES_IMAGE:-postgres:16-alpine}",
Entrypoint: []string{"sh", "/postgres-init.sh"},
Environment: []string{
"PGPASSWORD=piri",
Expand Down
24 changes: 24 additions & 0 deletions pkg/generate/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,30 @@ func TestGeneratePiriComposeMultiNode(t *testing.T) {
}
}

func TestGeneratePiriComposePostgresImageOverride(t *testing.T) {
nodes := []manifest.ResolvedPiriNode{
{Name: "piri-0", Index: 0, Storage: manifest.StorageSpec{DB: "postgres", Blob: "filesystem"}},
}

data, err := GeneratePiriCompose(nodes)
if err != nil {
t.Fatal(err)
}

var compose ComposeFile
if err := yaml.Unmarshal(data, &compose); err != nil {
t.Fatalf("unmarshal generated compose: %v", err)
}

// Unset, POSTGRES_IMAGE falls back to the tag used before it existed.
const want = "${POSTGRES_IMAGE:-postgres:16-alpine}"
for _, name := range []string{"piri-postgres", "piri-postgres-init"} {
if got := compose.Services[name].Image; got != want {
t.Errorf("%s image = %q, want %q", name, got, want)
}
}
}

func TestGeneratePiriComposeSerializedStartup(t *testing.T) {
nodes := []manifest.ResolvedPiriNode{
{Name: "piri-0", Index: 0, Storage: manifest.StorageSpec{DB: "sqlite", Blob: "filesystem"}},
Expand Down
4 changes: 2 additions & 2 deletions systems/common/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

services:
dynamodb-local:
image: amazon/dynamodb-local:latest
image: ${DYNAMODB_LOCAL_IMAGE:-amazon/dynamodb-local:latest}
# Root so docker's auto-created volume dir (root-owned) is writable. The
# upstream image's dynamodblocal uid 1000 can't write into a fresh named
# volume without a chown sidecar.
Expand Down Expand Up @@ -69,7 +69,7 @@ services:
networks:
- forge-network
email:
image: rnwood/smtp4dev:v3
image: ${SMTP4DEV_IMAGE:-rnwood/smtp4dev:v3}
ports:
- "${SMELT_SMTP_PORT:-15080:25}"
- "${SMELT_SMTP_WEB_PORT:-15081:80}" # Web UI / API
Expand Down
4 changes: 2 additions & 2 deletions systems/hilt/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ services:
# restarts but not a hilt-vault container restart. The image's default
# command is `server -dev`; the entrypoint reads the BAO_DEV_* vars below.
hilt-vault:
image: openbao/openbao:2.6
image: ${OPENBAO_IMAGE:-openbao/openbao:2.6}
ports:
- "${SMELT_HILT_VAULT_PORT:-15112:8200}"
environment:
Expand All @@ -129,7 +129,7 @@ services:
# enough — no init sidecar needed. Named hilt-postgres because `postgres`
# is taken by upload's sidecar.
hilt-postgres:
image: postgres:16-alpine
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
ports:
- "${SMELT_HILT_POSTGRES_PORT:-15111:5432}"
environment:
Expand Down
2 changes: 1 addition & 1 deletion systems/indexing/indexer/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
services:
# Redis - Cache backend for indexing service
redis:
image: redis:7-alpine
image: ${REDIS_IMAGE:-redis:7-alpine}
ports:
- "${SMELT_REDIS_PORT:-15020:6379}"
healthcheck:
Expand Down
6 changes: 3 additions & 3 deletions systems/ingot/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ services:
# and a restarted one is sealed until ingot-openbao-init runs again (which
# `make up` does, since one-shot services re-run on every up).
ingot-openbao:
image: openbao/openbao:2.6
image: ${OPENBAO_IMAGE:-openbao/openbao:2.6}
command: ["server"]
ports:
- "${SMELT_INGOT_OPENBAO_PORT:-15132:8200}"
Expand Down Expand Up @@ -107,7 +107,7 @@ services:
# the ingot policy (encrypt/decrypt/rewrap on that key only), and ingot's
# scoped token exist. See openbao/init.sh.
ingot-openbao-init:
image: openbao/openbao:2.6
image: ${OPENBAO_IMAGE:-openbao/openbao:2.6}
user: "0:0" # the image runs as `openbao`; the init volume is root-owned
entrypoint: ["sh", "/init.sh"]
environment:
Expand All @@ -128,7 +128,7 @@ services:
# migrations under the `ingot` schema on startup; the `ingot` database
# created by POSTGRES_DB is enough.
ingot-postgres:
image: postgres:16-alpine
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
ports:
- "${SMELT_INGOT_POSTGRES_PORT:-15131:5432}"
environment:
Expand Down
2 changes: 1 addition & 1 deletion systems/plc/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ services:
# Postgres for the PLC operation log. The server runs kysely migrations on
# startup, so the empty `plc` database created by POSTGRES_DB is enough.
plc-postgres:
image: postgres:16-alpine
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
ports:
- "${SMELT_PLC_POSTGRES_PORT:-15121:5432}"
environment:
Expand Down
2 changes: 1 addition & 1 deletion systems/swarf/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ services:
# Postgres for swarf's revocation store. Swarf runs goose migrations on
# startup, so the `swarf` database created by POSTGRES_DB is enough.
swarf-postgres:
image: postgres:16-alpine
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
ports:
- "${SMELT_SWARF_POSTGRES_PORT:-15141:5432}"
environment:
Expand Down
2 changes: 1 addition & 1 deletion systems/upload/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ services:
# POSTGRES_DB is enough. Separate from `piri-postgres` (which is generated
# from smelt.yml when piri nodes use db: postgres).
postgres:
image: postgres:16-alpine
image: ${POSTGRES_IMAGE:-postgres:16-alpine}
ports:
- "${SMELT_UPLOAD_POSTGRES_PORT:-15061:5432}"
environment:
Expand Down
Loading