111 lines
5.4 KiB
Go
111 lines
5.4 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"slices"
|
|
)
|
|
|
|
// A provider's consumers' credentials, in a file of the provider's own (novox/hq ADR 0373, issue 679).
|
|
//
|
|
// A provider in the tool runner may not ask the vault (ADR 0349 clause 11, ADR 0370 clause 2), so its template names
|
|
// `${consumers:<provision>}` and its machine's provisioner client fills it: one entry per consumer bound to the
|
|
// provider for that provision, each carrying the credential the vault keeps for the pair. Only the client fills it,
|
|
// so the placeholder is a provider's alone, for a provision it provides, in a file only the provider reads (mode 0600
|
|
// or 0400, owned at runtime, so a change on disk is written back); and a module not handed to the client on a machine
|
|
// is refused there, never written the placeholder's text, nor an empty list the provider would read as no consumers.
|
|
|
|
// ConsumersNamespace is a provider's consumers' credentials' placeholder namespace, `${consumers:<provision>}`.
|
|
const ConsumersNamespace = "consumers"
|
|
|
|
// ofConsumers is where a provider places its consumers' credentials. Loose inside the braces, so a misspelt name is
|
|
// refused rather than passed over.
|
|
var ofConsumers = regexp.MustCompile(`\$\{consumers:([^}]*)\}`)
|
|
|
|
// consumersPlaceholderProblems is what is wrong with one resource's ${consumers:…}: placed only in a file's content,
|
|
// for a provision the module provides, in a file only the provider reads and the client writes back.
|
|
func consumersPlaceholderProblems(m Manifest, r map[string]any) []string {
|
|
var problems []string
|
|
for _, field := range sortedKeys(r) {
|
|
v, ok := r[field].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, f := range ofConsumers.FindAllStringSubmatch(v, -1) {
|
|
switch {
|
|
case field != "content" || fmt.Sprint(r["type"]) != "file":
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v names %s in its %s; a provider's consumers "+
|
|
"are placed only in a file's content (novox/hq ADR 0373)", m.Module, r["id"], f[0], field))
|
|
continue
|
|
case !slices.ContainsFunc(m.Provides, func(o Offer) bool { return o.Name == f[1] }):
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v names %s, and %s does not provide %q: only a "+
|
|
"provider's template holds its consumers' credentials (novox/hq ADR 0373)", m.Module, r["id"], f[0],
|
|
m.Module, f[1]))
|
|
}
|
|
if mode := fmt.Sprint(r["mode"]); mode != "0600" && mode != "0400" {
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v holds its consumers' credentials (%s) with "+
|
|
"mode %q: the provider alone reads it, 0600 or 0400 (novox/hq ADR 0373)", m.Module, r["id"], f[0],
|
|
r["mode"]))
|
|
}
|
|
if owner := ownershipOf(r); owner != RuntimeOwned {
|
|
problems = append(problems, fmt.Sprintf("%s's resource %v holds its consumers' credentials (%s) and is "+
|
|
"%s at runtime: it is owned, written back whenever it differs (novox/hq ADR 0373)", m.Module,
|
|
r["id"], f[0], owner))
|
|
}
|
|
}
|
|
}
|
|
return problems
|
|
}
|
|
|
|
// consumersHandedOnly says what a file naming ${consumers:…} is where it would be written the old way: the provisioner's
|
|
// client alone fills it, so on a machine that does not hand the module it is **left out** of the declaration, never
|
|
// written as that text or as an empty list the provider would read as no consumers, and never the reason the module is
|
|
// refused: a provider not handed there keeps working as it did, from its grant files (novox/hq ADR 0373, issue 780).
|
|
// A moved module judged from its repository's tree (WithTemplatesFrom: `module check`), on a machine
|
|
// that would hand it, has no templates archive to hand and is judged as it composes; there the placeholder is judged
|
|
// and taken out of the text, since what is composed so is never sent (judged answers whether the file is so judged).
|
|
func consumersHandedOnly(m Manifest, r map[string]any, with Rendering) (judged, leftOut bool) {
|
|
content, _ := r["content"].(string)
|
|
switch {
|
|
case ofConsumers.FindString(content) == "":
|
|
return false, false
|
|
case m.Moved && m.Handed == nil && with.Hands && with.Provisioner != "":
|
|
return true, false
|
|
}
|
|
return false, true
|
|
}
|
|
|
|
// consumersLeftOutLine is the plan's one line for a file left out because the module is not handed.
|
|
func consumersLeftOutLine(m Manifest, r map[string]any) string {
|
|
return fmt.Sprintf("%s's %v names ${consumers:…}, which the provisioner's client alone fills: left out, handed only "+
|
|
"(novox/hq ADR 0373)", m.Module, r["id"])
|
|
}
|
|
|
|
// consumersNotHanded is consumersHandedOnly as a refusal, for a caller that has no way to leave the file out.
|
|
func consumersNotHanded(m Manifest, r map[string]any, with Rendering) (judged bool, err error) {
|
|
judged, left := consumersHandedOnly(m, r, with)
|
|
if left {
|
|
return false, fmt.Errorf("%s's resource %v names %s, which the provisioner's client alone fills, and %s is not "+
|
|
"handed to it on this machine: it would be written as that text, or empty, and the provider would read no "+
|
|
"consumers (novox/hq ADR 0373)", m.Module, r["id"], ofConsumers.FindString(fmt.Sprint(r["content"])), m.Module)
|
|
}
|
|
return judged, nil
|
|
}
|
|
|
|
// consumersOfTemplate is what is wrong with a handed file's template naming ${consumers:…}, judged on the file's
|
|
// resource as if its text were inline.
|
|
func consumersOfTemplate(m Manifest, id, text string) []string {
|
|
for _, r := range m.Resources {
|
|
if fmt.Sprint(r["id"]) != id {
|
|
continue
|
|
}
|
|
withText := map[string]any{}
|
|
for k, v := range r {
|
|
withText[k] = v
|
|
}
|
|
withText["content"] = text
|
|
return consumersPlaceholderProblems(m, withText)
|
|
}
|
|
return nil
|
|
}
|