The controller plans config, templates and moved providers' provisions, hands bus accounts to the vault and judges the new manifest words, on the feature's SDK and node-engine validator (feature 57, config and provisioning).
135 lines
6.3 KiB
Go
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)
|
|
}
|
|
}
|