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:
@@ -14,6 +14,8 @@ import (
|
||||
type manifest struct {
|
||||
Module string `json:"module"`
|
||||
Version string `json:"version"`
|
||||
Moved bool `json:"moved"`
|
||||
Items []item `json:"items"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Requires []string `json:"requires"`
|
||||
Claims []claim `json:"claims"`
|
||||
@@ -44,6 +46,53 @@ type shellCode struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
// An item of a collection (novox/hq ADR 0349 clause 13): its text is the template file beside the manifest.
|
||||
type item struct {
|
||||
Collection string `json:"collection"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
Template string `json:"template"`
|
||||
}
|
||||
|
||||
// shell is the code the module adds to a shell's or the session's start, read from the templates of its items of
|
||||
// the collections shell:<file>:<slot>, in the order they are declared.
|
||||
func (m manifest) shell(t *testing.T) []shellCode {
|
||||
t.Helper()
|
||||
var out []shellCode
|
||||
for _, it := range m.Items {
|
||||
parts := strings.Split(it.Collection, ":")
|
||||
if len(parts) != 3 || parts[0] != "shell" {
|
||||
continue
|
||||
}
|
||||
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out = append(out, shellCode{For: parts[1], Slot: parts[2], Code: string(text)})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// environmentOf is the variables the module sets, read from the templates of its items of the environment.
|
||||
func (m manifest) environmentOf(t *testing.T) *environment {
|
||||
t.Helper()
|
||||
var env *environment
|
||||
for _, it := range m.Items {
|
||||
if it.Collection != "environment" || it.Attributes["kind"] != "variable" {
|
||||
continue
|
||||
}
|
||||
text, err := os.ReadFile(filepath.Join("..", "..", it.Template))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if env == nil {
|
||||
env = &environment{Variables: map[string]string{}}
|
||||
}
|
||||
env.Variables[it.Attributes["name"]] = strings.TrimSuffix(string(text), "\n")
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func readManifest(t *testing.T) manifest {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
|
||||
@@ -56,6 +105,7 @@ func readManifest(t *testing.T) manifest {
|
||||
if err := dec.Decode(&m); err != nil {
|
||||
t.Fatalf("module.json: %v", err)
|
||||
}
|
||||
m.Environment = m.environmentOf(t)
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -83,26 +133,66 @@ func (m manifest) packages() (present, absent []string) {
|
||||
return present, absent
|
||||
}
|
||||
|
||||
// sameAsSource checks that a file resource's content is byte for byte the module's source file, so
|
||||
// the readable file in the repository is what the machine gets.
|
||||
func (m manifest) sameAsSource(t *testing.T, id, source string) {
|
||||
// fromTemplate checks that a file resource takes its text from its template file in the module's
|
||||
// repository (novox/hq ADR 0349 §2): it names the template, carries no text of its own beside it, and the
|
||||
// template is there. The readable file in the repository is the one place the machine's text is written.
|
||||
func (m manifest) fromTemplate(t *testing.T, id, template string) {
|
||||
t.Helper()
|
||||
want, err := os.ReadFile(filepath.Join("..", "..", source))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := m.resource(t, id)
|
||||
if r["type"] != "file" {
|
||||
t.Fatalf("%s is a %v, not a file", id, r["type"])
|
||||
}
|
||||
if got, _ := r["content"].(string); got != string(want) {
|
||||
t.Fatalf("resource %s's content is not %s: edit the source and copy it into module.json", id, source)
|
||||
if r["template"] != template {
|
||||
t.Fatalf("resource %s does not take its text from %s: template %v", id, template, r["template"])
|
||||
}
|
||||
if _, inline := r["content"]; inline {
|
||||
t.Fatalf("resource %s keeps its text inline beside its template: a file's text is in one place", id)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join("..", "..", template)); err != nil {
|
||||
t.Fatalf("resource %s names %s: %v", id, template, err)
|
||||
}
|
||||
if r["owner"] != "${machine:account}" && !strings.HasPrefix(r["path"].(string), "/etc/") {
|
||||
t.Fatalf("%s under the home is the account's", id)
|
||||
}
|
||||
}
|
||||
|
||||
// everyFileIsATemplate checks that no file resource carries its text inline, and that the build packs
|
||||
// files/ into the one archive the controller reads the templates back from (novox/hq ADR 0349 §2).
|
||||
func (m manifest) everyFileIsATemplate(t *testing.T) {
|
||||
t.Helper()
|
||||
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"])
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// text is the text a file resource places on the machine: its template file's.
|
||||
func (m manifest) text(t *testing.T, id string) string {
|
||||
t.Helper()
|
||||
name, _ := m.resource(t, id)["template"].(string)
|
||||
if name == "" {
|
||||
t.Fatalf("resource %s names no template", id)
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(raw)
|
||||
}
|
||||
|
||||
// checkTheToolsAgree checks that the manifest lists the module's own tools exactly, that the bundle
|
||||
// serves each seat verb the claims promise as <seat>.<verb>, and that the Go bundle is declared.
|
||||
func checkTheToolsAgree(t *testing.T, m manifest) {
|
||||
|
||||
@@ -29,13 +29,13 @@ func TestThePAMLinesAreBlocksWrittenIntoTheStacks(t *testing.T) {
|
||||
"pam-login": {"/etc/pam.d/login", "files/pam/login"},
|
||||
"pam-passwd": {"/etc/pam.d/passwd", "files/pam/passwd"},
|
||||
} {
|
||||
m.sameAsSource(t, id, want.source)
|
||||
m.fromTemplate(t, id, want.source)
|
||||
r := m.resource(t, id)
|
||||
if r["path"] != want.path || r["into"] != "block" || r["at"] != "end" || r["owner"] != nil {
|
||||
t.Errorf("%s: %v", id, r)
|
||||
}
|
||||
}
|
||||
login := m.resource(t, "pam-login")["content"].(string)
|
||||
login := m.text(t, "pam-login")
|
||||
if !strings.Contains(login, "\nauth optional pam_gnome_keyring.so\n") ||
|
||||
!strings.Contains(login, "\nsession optional pam_gnome_keyring.so auto_start\n") {
|
||||
t.Fatalf("%s", login)
|
||||
@@ -44,10 +44,10 @@ func TestThePAMLinesAreBlocksWrittenIntoTheStacks(t *testing.T) {
|
||||
|
||||
func TestItStartsNoDaemonAndOnlyNamesTheAgentsSocket(t *testing.T) {
|
||||
m := readManifest(t)
|
||||
if len(m.Shell) != 1 || m.Shell[0].For != "xinitrc" || m.Shell[0].Slot != "first" {
|
||||
t.Fatalf("%+v", m.Shell)
|
||||
if len(m.shell(t)) != 1 || m.shell(t)[0].For != "xinitrc" || m.shell(t)[0].Slot != "first" {
|
||||
t.Fatalf("%+v", m.shell(t))
|
||||
}
|
||||
code := m.Shell[0].Code
|
||||
code := m.shell(t)[0].Code
|
||||
if strings.Contains(code, "gnome-keyring-daemon") || strings.Contains(code, "--unlock") ||
|
||||
!strings.Contains(code, `SSH_AUTH_SOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gcr/ssh"`) {
|
||||
t.Fatalf("%q", code)
|
||||
@@ -62,3 +62,8 @@ func TestTheToolsAgreeWithTheManifest(t *testing.T) {
|
||||
checkTheToolsAgree(t, m)
|
||||
checkNoSecretsOrInstallationNames(t)
|
||||
}
|
||||
|
||||
// No file resource carries its text inline (novox/hq ADR 0349 §2).
|
||||
func TestEveryFileTakesItsTextFromATemplate(t *testing.T) {
|
||||
readManifest(t).everyFileIsATemplate(t)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# The ssh agent (module gnome-keyring, novox/hq ADR 0208): gcr's, which the account's service manager
|
||||
# starts on first use from its socket. Named here, for the session and every terminal it starts, until
|
||||
# the account's environment can say a path under the runtime directory (see the module's README).
|
||||
SSH_AUTH_SOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gcr/ssh"
|
||||
export SSH_AUTH_SOCK
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"module": "gnome-keyring",
|
||||
"version": "1",
|
||||
"moved": true,
|
||||
"capabilities": [
|
||||
"package-manager"
|
||||
],
|
||||
@@ -16,11 +17,11 @@
|
||||
"gnome_keyring_collections",
|
||||
"gnome_keyring_ssh_keys"
|
||||
],
|
||||
"shell": [
|
||||
"items": [
|
||||
{
|
||||
"for": "xinitrc",
|
||||
"slot": "first",
|
||||
"code": "# The ssh agent (module gnome-keyring, novox/hq ADR 0208): gcr's, which the account's service manager\n# starts on first use from its socket. Named here, for the session and every terminal it starts, until\n# the account's environment can say a path under the runtime directory (see the module's README).\nSSH_AUTH_SOCK=\"${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/gcr/ssh\"\nexport SSH_AUTH_SOCK\n"
|
||||
"collection": "shell:xinitrc:first",
|
||||
"name": "xinitrc-first",
|
||||
"template": "files/xinitrc-first"
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
@@ -46,7 +47,7 @@
|
||||
"mode": "0644",
|
||||
"into": "block",
|
||||
"at": "end",
|
||||
"content": "# The login keyring (module gnome-keyring, novox/hq ADR 0208, ADR 0102): the password typed at the\n# login screen unlocks the keyring, and the login session starts the keyring daemon with it.\nauth optional pam_gnome_keyring.so\nsession optional pam_gnome_keyring.so auto_start\n"
|
||||
"template": "files/pam/login"
|
||||
},
|
||||
{
|
||||
"id": "pam-passwd",
|
||||
@@ -55,7 +56,7 @@
|
||||
"mode": "0644",
|
||||
"into": "block",
|
||||
"at": "end",
|
||||
"content": "# The login keyring (module gnome-keyring, novox/hq ADR 0208, ADR 0102): changing the account's\n# password changes the login keyring's with it, so the next login still unlocks it.\npassword optional pam_gnome_keyring.so\n"
|
||||
"template": "files/pam/passwd"
|
||||
}
|
||||
],
|
||||
"build": {
|
||||
@@ -70,6 +71,11 @@
|
||||
"loads": [
|
||||
"gnome-keyring-tools"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"kind": "archive",
|
||||
"from": "files"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user