Files
mesh-controller/internal/catalogue/handed_consumers_test.go
T

135 lines
6.3 KiB
Go

package catalogue
import (
"os"
"path/filepath"
"strings"
"testing"
)
// A provider's consumers' credentials reach it in a file its machine's provisioner client fills (novox/hq ADR 0373,
// issue 679): `${consumers:<provision>}`, which only a provider's template may hold, for a provision it provides, in a
// file of mode 0600 owned at runtime; filled by the client alone, so a module not handed to it on a machine is
// refused there rather than written the placeholder's text, or nothing.
const (
handedProvider = `{"module":"db","version":"1","moved":true,
"provides":[{"name":"postgres-database","scope":"mesh","identity":{"max":63,"in":"a PostgreSQL role"}}],
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
"resources":[
{"id":"state","type":"directory","mode":"0700","place":"."},
{"id":"consumers","type":"file","path":"${dir:state}/consumers.json","mode":"0600","template":"files/consumers.json"}]}`
consumersText = "${consumers:postgres-database}"
)
func builtProvider(text string) []Built {
return []Built{{Name: "files", Kind: ArtifactArchive,
Reference: ArtifactStoreScheme + "db/files/blobs/sha256:" + strings.Repeat("f", 64),
Digest: "sha256:" + strings.Repeat("f", 64), Texts: map[string]string{"consumers.json": text}}}
}
func TestAHandedProvidersConsumersFileIsHandedToTheClient(t *testing.T) {
r := notifierOn(t, held(t, handedProvider, builtProvider(consumersText)))
c, got := composeWith(t, r, handingTo("store:5000"))
if strings.Contains(got, "${consumers:") {
t.Fatalf("the declaration carries the placeholder: %s", got)
}
plan := c.Plans["db"]
if len(plan.Files) != 1 || plan.Files[0].Template != "consumers.json" || plan.Files[0].OwnerAtRuntime != RuntimeOwned {
t.Fatalf("the plan fills %+v, want the consumers' file owned at runtime", plan.Files)
}
}
func TestWhatAConsumersPlaceholderMayNotBeIsRefused(t *testing.T) {
for why, c := range map[string]struct{ raw, text, want string }{
"a provision the module does not provide": {handedProvider, "${consumers:cache}", "does not provide"},
"a module providing nothing": {strings.Replace(handedProvider, `"provides":[{"name":"postgres-database","scope":"mesh","identity":{"max":63,"in":"a PostgreSQL role"}}],`,
"", 1), consumersText, "does not provide"},
"a file others may read": {strings.Replace(handedProvider, `"mode":"0600","template"`,
`"mode":"0640","template"`, 1), consumersText, "0600"},
"a file the program keeps": {strings.Replace(handedProvider, `"mode":"0600","template"`,
`"mode":"0600","create-once":true,"template"`, 1), consumersText, "owned"},
} {
m, err := ParseManifest([]byte(c.raw))
if err != nil {
t.Fatalf("%s: %v", why, err)
}
resolved, err := m.Resolve(builtProvider(c.text))
if err == nil {
r := notifierOn(t, resolved)
_, err = r.Compose(handingTo("store:5000"))
}
if err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: %v, want a refusal saying %q", why, err, c.want)
}
}
}
// Where the provider is not handed, nothing fills the placeholder, and it is never written as its text or as an empty
// list the provider would read as no consumers: the file is left out, said once, and the module composes exactly as
// it would without the file, so a provider below the handing contract keeps working from its grant files (issue 780).
func TestAProvidersConsumersFileIsLeftOutWhereItIsNotHanded(t *testing.T) {
withFile := notifierOn(t, held(t, handedProvider, builtProvider(consumersText)))
without := strings.Replace(handedProvider, `,
{"id":"consumers","type":"file","path":"${dir:state}/consumers.json","mode":"0600","template":"files/consumers.json"}`, "", 1)
if without == handedProvider {
t.Fatal("the manifest without the file was not made")
}
bare := notifierOn(t, held(t, strings.Replace(strings.Replace(without, `"moved":true,`, "", 1),
`"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},`, "", 1), nil))
for name, with := range map[string]Rendering{
"a node-engine that fills no file": {},
"a node-engine that fills, no client": {Hands: true, ArtifactStore: "store:5000"},
"a client, a node-engine that does not": {Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"},
} {
c, got := composeWith(t, withFile, with)
_, want := composeWith(t, bare, with)
if got != want {
t.Errorf("%s: composes\n%s\nwithout its file, it composed\n%s", name, got, want)
}
if strings.Contains(got, "consumers") {
t.Errorf("%s: the declaration names the consumers' file: %s", name, got)
}
if len(c.HandedOnly) != 1 || !strings.Contains(c.HandedOnly[0], "left out, handed only") {
t.Errorf("%s: said %q, want the one line saying it is left out", name, c.HandedOnly)
}
if len(c.Unplaced) != 0 || len(c.Filled) != 0 || len(c.Plans) != 0 {
t.Errorf("%s: unplaced %v, filled %v, plans %v", name, c.Unplaced, c.Filled, c.Plans)
}
}
// Where it is handed, the file is there and nothing is said.
c, _ := composeWith(t, withFile, handingTo("store:5000"))
if len(c.HandedOnly) != 0 || len(c.Filled) != 1 {
t.Fatalf("handed: said %q, filled %v", c.HandedOnly, c.Filled)
}
}
// What `module check` and the merge gate judge: the provider read from its tree, its text inline. Judged by the same
// rules, a provider naming a provision it does not provide is refused there too, and one that does composes.
func TestAProviderReadFromItsTreeIsJudgedByTheSameRules(t *testing.T) {
read := func(text string) (Manifest, error) {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "files"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "files", "consumers.json"), []byte(text), 0o644); err != nil {
t.Fatal(err)
}
m, err := ParseManifest([]byte(handedProvider))
if err != nil {
t.Fatal(err)
}
return WithTemplatesFrom(m, dir)
}
filled, err := read(consumersText)
if err != nil {
t.Fatalf("a provider read from its tree is refused: %v", err)
}
if _, err := notifierOn(t, filled).Compose(handingTo("store:5000")); err != nil {
t.Fatalf("a provider read from its tree does not compose where it would be handed: %v", err)
}
if _, err := read("${consumers:cache}"); err == nil || !strings.Contains(err.Error(), "does not provide") {
t.Fatalf("a provider read from its tree naming a provision it does not provide: %v", err)
}
}