Write into a file the machine shares instead of over it, and reload a service that re-reads its configuration instead of restarting it (hq ADR 0102)
This commit is contained in:
@@ -154,6 +154,13 @@ type File struct {
|
||||
// (ADR 0030), and it does not overwrite that either.
|
||||
CreateOnce bool `json:"create-once,omitempty"`
|
||||
|
||||
// Into says the file is shared with software the mesh did not install, and the content is
|
||||
// the mesh's part of it: written into what is there, never over it (novox/hq ADR 0102). Only
|
||||
// "json" is spoken — the content is a JSON object whose keys the host sets in the file's
|
||||
// object, keeping every other key as it found it and recording what each of its keys held
|
||||
// before, so undeclaring the file gives those back.
|
||||
Into string `json:"into,omitempty"`
|
||||
|
||||
// Sealed is content encrypted to this node's sealing key, for a file the mesh must deliver
|
||||
// without being able to read.
|
||||
//
|
||||
@@ -224,6 +231,24 @@ func (f *File) validate(where string, _ bool) []string {
|
||||
if f.Path == "" {
|
||||
problems = append(problems, where+": a file needs a path")
|
||||
}
|
||||
switch f.Into {
|
||||
case "":
|
||||
case IntoJSON:
|
||||
var object map[string]json.RawMessage
|
||||
if err := json.Unmarshal([]byte(f.Content), &object); err != nil || object == nil {
|
||||
problems = append(problems, where+
|
||||
": a file written into JSON carries a JSON object of the keys it sets")
|
||||
}
|
||||
if f.Sealed != "" || f.Bytes != "" || len(f.Secrets) > 0 || f.CreateOnce {
|
||||
problems = append(problems, where+
|
||||
": a file written into says only its keys, in content — not sealed, bytes, "+
|
||||
"secrets or create-once")
|
||||
}
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: into %q; a file is written into \"json\", or omits it to be written whole",
|
||||
where, f.Into))
|
||||
}
|
||||
var said []string
|
||||
for name, value := range map[string]string{
|
||||
"content": f.Content, "sealed": f.Sealed, "bytes": f.Bytes,
|
||||
@@ -586,6 +611,12 @@ type Service struct {
|
||||
// would be an action, and the link may not carry one (novox/hq ADR 0005) — so this is not a
|
||||
// way around that rule, it is the shape the rule leaves.
|
||||
RestartOn []string `json:"restart-on,omitempty"`
|
||||
|
||||
// ReloadOn names resources whose change means this service must be reloaded — for a service
|
||||
// that re-reads its configuration when told to, where a restart would stop what it runs: the
|
||||
// container runtime, whose restart stops every container on the machine (novox/hq ADR 0102).
|
||||
// A change that is also in RestartOn restarts it, which covers a reload.
|
||||
ReloadOn []string `json:"reload-on,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Service) Identity() string { return s.ID }
|
||||
@@ -609,6 +640,9 @@ func (s *Service) validate(where string, _ bool) []string {
|
||||
return problems
|
||||
}
|
||||
|
||||
// IntoJSON is the one structured format a file is written into.
|
||||
const IntoJSON = "json"
|
||||
|
||||
// Opening is a port reachable on an adopted node, from where, and on which path.
|
||||
//
|
||||
// **From** is everywhere or mesh — the private network, by its interface. **Path** is incoming,
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package declaration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Defends novox/hq ADR 0102: a file written into carries only a JSON object of its keys, in a
|
||||
// format the host speaks, and a service may name what it is reloaded on.
|
||||
|
||||
func TestAFileWrittenIntoIsRefusedUnlessItIsAnObjectOfKeys(t *testing.T) {
|
||||
for name, c := range map[string]struct{ resource, refusal string }{
|
||||
"another format": {`{"id":"f","type":"file","path":"/etc/x","into":"toml","content":"a = 1"}`, `into "toml"`},
|
||||
"not an object": {`{"id":"f","type":"file","path":"/etc/x","into":"json","content":"[1,2]"}`, "JSON object"},
|
||||
"with create-once": {`{"id":"f","type":"file","path":"/etc/x","into":"json","content":"{}","create-once":true}`, "create-once"},
|
||||
} {
|
||||
_, err := Parse([]byte(`{"declaration":1,"resources":[` + c.resource + `]}`))
|
||||
if err == nil || !strings.Contains(err.Error(), c.refusal) {
|
||||
t.Errorf("%s: want a refusal naming %q, got %v", name, c.refusal, err)
|
||||
}
|
||||
}
|
||||
d, err := Parse([]byte(`{"declaration":1,"resources":[
|
||||
{"id":"f","type":"file","path":"/etc/x","into":"json","content":"{\"k\":1}"},
|
||||
{"id":"s","type":"service","unit":"docker.service","state":"running","reload-on":["f"]}
|
||||
]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f := d.Resources[0].(*File); f.Into != IntoJSON {
|
||||
t.Errorf("into was read as %q", f.Into)
|
||||
}
|
||||
if s := d.Resources[1].(*Service); len(s.ReloadOn) != 1 || s.ReloadOn[0] != "f" {
|
||||
t.Errorf("reload-on was read as %v", s.ReloadOn)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user