Feature 57 (config and provisioning): modules declare config and provisions, their files rendered from templates; synced with main (issues of feature 57, 874)
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -28,12 +29,22 @@ type call struct {
|
||||
|
||||
type fake struct {
|
||||
answer func(c call) Ran
|
||||
mu sync.Mutex
|
||||
calls []call
|
||||
}
|
||||
|
||||
// recorded returns a copy of the calls so far; scans call run from parallel goroutines.
|
||||
func (f *fake) recorded() []call {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return append([]call(nil), f.calls...)
|
||||
}
|
||||
|
||||
func (f *fake) run(_ context.Context, env []string, name string, args ...string) Ran {
|
||||
c := call{env, name, args}
|
||||
f.mu.Lock()
|
||||
f.calls = append(f.calls, c)
|
||||
f.mu.Unlock()
|
||||
if f.answer == nil {
|
||||
return Ran{Status: 1}
|
||||
}
|
||||
@@ -152,7 +163,7 @@ func TestKeysAreDescribedByTheirPublicHalfAndAPassphraseIsAskedNotRead(t *testin
|
||||
if k := by["stray"]; !k.PublicMissing || k.OfferedBy != "" || k.Fingerprint != edFP {
|
||||
t.Errorf("stray: %+v", k)
|
||||
}
|
||||
for _, c := range f.calls {
|
||||
for _, c := range f.recorded() {
|
||||
if c.name == "ssh-keygen" && c.args[0] == "-y" && !reflect.DeepEqual(c.args[:3], []string{"-y", "-P", ""}) {
|
||||
t.Errorf("a passphrase check could prompt: %v", c.args)
|
||||
}
|
||||
@@ -263,7 +274,7 @@ func TestKnownHostComparesWhatIsKnownWithWhatIsOffered(t *testing.T) {
|
||||
t.Fatalf("not appended: %s", after)
|
||||
}
|
||||
sawRemove := false
|
||||
for _, c := range f.calls {
|
||||
for _, c := range f.recorded() {
|
||||
if c.name == "ssh-keygen" && reflect.DeepEqual(c.args[:2], []string{"-R", "ace.internal"}) {
|
||||
sawRemove = true
|
||||
}
|
||||
@@ -303,7 +314,7 @@ func TestTestSaysHowItAuthenticatedOrWhyNot(t *testing.T) {
|
||||
if err != nil || got["ok"] != true || got["method"] != "publickey" || got["connected_to"] != "10.0.0.2:22" {
|
||||
t.Fatalf("%v %v", got, err)
|
||||
}
|
||||
args := strings.Join(f.calls[0].args, " ")
|
||||
args := strings.Join(f.recorded()[0].args, " ")
|
||||
if !strings.Contains(args, "BatchMode=yes") || !strings.Contains(args, "StrictHostKeyChecking=yes") || !strings.HasSuffix(args, "ace true") {
|
||||
t.Fatalf("not a batch test: %s", args)
|
||||
}
|
||||
@@ -388,7 +399,7 @@ func TestTheManifestsRegionIsWhatSshReads(t *testing.T) {
|
||||
var region string
|
||||
for _, r := range m.Resources {
|
||||
if r["id"] == "config" {
|
||||
region = r["content"].(string)
|
||||
region = templateText(t, r)
|
||||
if r["into"] != "block" || r["at"] != "start" {
|
||||
t.Fatalf("the region is not written into the start: %v", r)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The module's directory, where its manifest and its files/ are.
|
||||
const moduleDir = "../.."
|
||||
|
||||
// templateText is the text a file resource places on the machine: its template's, in the module's files/
|
||||
// (novox/hq ADR 0349 §2). A resource that names no template, or a template that is not there, fails the test.
|
||||
func templateText(t *testing.T, r map[string]any) string {
|
||||
t.Helper()
|
||||
name, _ := r["template"].(string)
|
||||
if name == "" {
|
||||
t.Fatalf("resource %v names no template", r["id"])
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join(moduleDir, filepath.FromSlash(name)))
|
||||
if err != nil {
|
||||
t.Fatalf("resource %v names %s: %v", r["id"], name, err)
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
// No file resource carries its text inline: each names its template in files/, and the build packs files/
|
||||
// into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
|
||||
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
||||
raw, err := os.ReadFile(filepath.Join(moduleDir, "module.json"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m struct {
|
||||
Resources []map[string]any `json:"resources"`
|
||||
Build struct {
|
||||
Artifacts []map[string]any `json:"artifacts"`
|
||||
} `json:"build"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, r := range m.Resources {
|
||||
if _, inline := r["content"]; inline {
|
||||
t.Errorf("resource %v keeps its text inline: keep it in files/ and name it as the template", r["id"])
|
||||
}
|
||||
if r["type"] == "file" {
|
||||
templateText(t, r)
|
||||
}
|
||||
}
|
||||
packs := 0
|
||||
for _, a := range m.Build.Artifacts {
|
||||
if a["kind"] == "archive" && a["from"] == "files" {
|
||||
packs++
|
||||
if a["name"] != "files" {
|
||||
t.Errorf("the templates archive is named %v, not files", a["name"])
|
||||
}
|
||||
}
|
||||
}
|
||||
if packs != 1 {
|
||||
t.Errorf("the build packs files/ into %d archives, not one", packs)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# The mesh's region (module ssh-client, novox/hq research 027/03). It comes first because ssh
|
||||
# takes the first value it finds for each option. It brings in ~/.ssh/config.d/ in name order:
|
||||
# 00-mesh, the mesh's Host per machine, then each file another module places there. Replaced at
|
||||
# every push. Everything below it is yours, kept as you wrote it, and applies to every host the
|
||||
# files above leave unsettled.
|
||||
Include ~/.ssh/config.d/*
|
||||
@@ -44,7 +44,7 @@
|
||||
"mode": "0600",
|
||||
"into": "block",
|
||||
"at": "start",
|
||||
"content": "# The mesh's region (module ssh-client, novox/hq research 027/03). It comes first because ssh\n# takes the first value it finds for each option. It brings in ~/.ssh/config.d/ in name order:\n# 00-mesh, the mesh's Host per machine, then each file another module places there. Replaced at\n# every push. Everything below it is yours, kept as you wrote it, and applies to every host the\n# files above leave unsettled.\nInclude ~/.ssh/config.d/*\n"
|
||||
"template": "files/config"
|
||||
}
|
||||
],
|
||||
"facts": {
|
||||
@@ -66,6 +66,11 @@
|
||||
"loads": [
|
||||
"ssh-client-tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"kind": "archive",
|
||||
"from": "files"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user