Let the node's own composition carry its files' filled paths, and refuse a composition that would have a provisioner's file written empty (issue 1945)
mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/delivery-group group fix/r6-node-composition held: a member is held: novox/mesh-sdk@10b6e202320f (merged without a passing check: only a person decides wh…
mesh/check checking 10b6e202320f on da7f0455759a
mesh/delivery held for a person: merged without a passing check: only a person decides what follows
mesh/delivery-group group fix/r6-node-composition held: a member is held: novox/mesh-sdk@10b6e202320f (merged without a passing check: only a person decides wh…
mesh/check checking 10b6e202320f on da7f0455759a
This commit is contained in:
@@ -86,8 +86,10 @@ func (c Composed) check() error {
|
|||||||
if c.FP != "" {
|
if c.FP != "" {
|
||||||
return fmt.Errorf("delivery: the node's own composition names no fingerprint, and this one names %s", c.FP)
|
return fmt.Errorf("delivery: the node's own composition names no fingerprint, and this one names %s", c.FP)
|
||||||
}
|
}
|
||||||
if c.Data || len(c.Filled) > 0 {
|
// It carries the filled paths of its own files, those of a module with no fingerprint of the new form moved
|
||||||
return fmt.Errorf("delivery: the node's own composition holds no module's data or filled files")
|
// into it (issue 1945), and never a module's data.
|
||||||
|
if c.Data {
|
||||||
|
return fmt.Errorf("delivery: the node's own composition holds no module's data")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := ComposedKey(c.Node, c.Module, c.FP); err != nil {
|
if _, err := ComposedKey(c.Node, c.Module, c.FP); err != nil {
|
||||||
@@ -120,6 +122,20 @@ func (c Composed) check() error {
|
|||||||
files[f.Path] = append(files[f.Path], f)
|
files[f.Path] = append(files[f.Path], f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A file the provisioner fills says no content, so a composition that does not say it is filled would have the
|
||||||
|
// node-engine write it empty (issue 1945, live on 2026-10-25): refused, naming it.
|
||||||
|
filledHere := make(map[string]bool, len(c.Filled))
|
||||||
|
for _, path := range c.Filled {
|
||||||
|
filledHere[path] = true
|
||||||
|
}
|
||||||
|
for path, fs := range files {
|
||||||
|
for _, f := range fs {
|
||||||
|
if f.OwnerAtRuntime == declaration.RuntimeOwned && !filledHere[path] && !saysContent(f) {
|
||||||
|
return fmt.Errorf("delivery: the file %q says no content and the composition of %q on %s does not say "+
|
||||||
|
"the provisioner fills %s: the node-engine would write it empty", f.ID, c.Module, c.Node, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, path := range c.Filled {
|
for _, path := range c.Filled {
|
||||||
if len(files[path]) == 0 {
|
if len(files[path]) == 0 {
|
||||||
return fmt.Errorf("delivery: the composition of %q fills %q, and declares no file there", c.Module, path)
|
return fmt.Errorf("delivery: the composition of %q fills %q, and declares no file there", c.Module, path)
|
||||||
@@ -138,6 +154,12 @@ func (c Composed) check() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saysContent says a file names what to write: content, bytes, a digest or a sealed value. A file the provisioner fills
|
||||||
|
// names none of them.
|
||||||
|
func saysContent(f *declaration.File) bool {
|
||||||
|
return f.Content != "" || f.Bytes != "" || f.Digest != "" || f.Sealed != "" || f.VaultSealed != nil
|
||||||
|
}
|
||||||
|
|
||||||
// wire is c checked and made canonical, ready to marshal.
|
// wire is c checked and made canonical, ready to marshal.
|
||||||
func (c Composed) wire() (composedWire, error) {
|
func (c Composed) wire() (composedWire, error) {
|
||||||
if err := c.check(); err != nil {
|
if err := c.check(); err != nil {
|
||||||
|
|||||||
@@ -320,3 +320,45 @@ func TestADottedModuleKeysEscaped(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The node's own composition carries the filled paths of the files moved into it, the files of a module with no
|
||||||
|
// fingerprint of the new form: the provisioner fills them all the same (issue 1945, live on 2026-10-25: the paths were
|
||||||
|
// dropped and the node-engine wrote those files empty). It still holds no module's data.
|
||||||
|
func TestTheNodesOwnCompositionCarriesTheFilledPathsOfItsFiles(t *testing.T) {
|
||||||
|
filled := json.RawMessage(`{"id":"power.unit","type":"file","path":"/etc/systemd/system/p.service","mode":"0644","owner-at-runtime":"owned"}`)
|
||||||
|
own := Composed{Node: "ace", Resources: []json.RawMessage{filled}, Filled: []string{"/etc/systemd/system/p.service"},
|
||||||
|
Own: NodeOwn{Provisioner: "mesh-provisioner-client"}}
|
||||||
|
raw, err := EncodeComposed(own)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("the node's own composition with a filled file: %v", err)
|
||||||
|
}
|
||||||
|
back, err := DecodeComposed(raw)
|
||||||
|
if err != nil || len(back.Filled) != 1 {
|
||||||
|
t.Fatalf("read back: %v %v", back.Filled, err)
|
||||||
|
}
|
||||||
|
if _, err := EncodeComposed(Composed{Node: "ace", Data: true, Resources: []json.RawMessage{filled},
|
||||||
|
Filled: own.Filled}); err == nil {
|
||||||
|
t.Error("the node's own composition was let hold a module's data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A file the provisioner fills says no content: in a composition that does not say it is filled, the node-engine would
|
||||||
|
// write it empty. Such a composition is refused, in the node's own as in a module's (issue 1945).
|
||||||
|
func TestAFileWithNoContentThatIsNotFilledIsRefused(t *testing.T) {
|
||||||
|
fp := fp2of('a')
|
||||||
|
for _, c := range []Composed{
|
||||||
|
{Node: "ace", Resources: []json.RawMessage{json.RawMessage(
|
||||||
|
`{"id":"power.unit","type":"file","path":"/etc/p","mode":"0644","owner-at-runtime":"owned"}`)}},
|
||||||
|
{Node: "ace", Module: "m", FP: fp, Resources: []json.RawMessage{json.RawMessage(
|
||||||
|
`{"id":"m.f","type":"file","module":"m","path":"/etc/m","mode":"0644","owner-at-runtime":"owned"}`)}},
|
||||||
|
} {
|
||||||
|
if _, err := EncodeComposed(c); err == nil || !strings.Contains(err.Error(), "empty") {
|
||||||
|
t.Errorf("%s/%s: %v, want a refusal saying it would be written empty", c.Node, c.Module, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A file that says its content, even an empty one, is written as it says.
|
||||||
|
if _, err := EncodeComposed(Composed{Node: "ace", Resources: []json.RawMessage{json.RawMessage(
|
||||||
|
`{"id":"x.f","type":"file","path":"/etc/x","mode":"0644","content":""}`)}}); err != nil {
|
||||||
|
t.Errorf("a file that says it is empty: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user