From 06742d0448ff45d964db457a140544b915f93817 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 13:09:45 +0100 Subject: [PATCH] =?UTF-8?q?A=20contribution's=20unset=20required=20value?= =?UTF-8?q?=20is=20refused=20in=20its=20module's=20own=20form's=20words,?= =?UTF-8?q?=20as=20a=20file's=20is=20(issue=201126,=20seed-8:=20de-spiegel?= =?UTF-8?q?=20in=20the=20config=20form=20was=20told=20${setting:=E2=80=A6}?= =?UTF-8?q?=20and=20settings=20set)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/catalogue/config_test.go | 13 +++++++++++++ go/catalogue/declaration.go | 10 +++++++++- go/catalogue/setting_into.go | 2 ++ go/catalogue/settings.go | 14 +++++++++++--- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/go/catalogue/config_test.go b/go/catalogue/config_test.go index 1168074..852d340 100644 --- a/go/catalogue/config_test.go +++ b/go/catalogue/config_test.go @@ -445,6 +445,19 @@ func TestAnUnsetRequiredValueIsRefusedInTheConfigFormsWords(t *testing.T) { t.Fatalf("not typed as a value nobody gave: %v", err) } + // A contribution's value too (issue 1126, seed-8: de-spiegel's route names were refused with ${setting:…}). + withRoute := inConfigForm() + withRoute["contributes"] = map[string]any{"route": map[string]any{"site": map[string]any{"name": "${config:sender}", "port": 80}}} + routed, err := parseRaw(t, withRoute) + if err != nil { + t.Fatal(err) + } + _, err = Resolution{}.composed(routed, "route", routed.ContributesMany["route"]["site"], nil, "notifier contributing site to route") + if !errors.As(err, &unset) || unset.Module != "notifier" || !strings.Contains(err.Error(), + `notifier has a contribution site to route, in "name", that says ${config:sender}`) || strings.Contains(err.Error(), "${setting:") { + t.Fatalf("a contribution's unset value is not said in the config form's words: %v", err) + } + settingsForm := notifier() settingsForm.Resources[0]["content"] = "from = ${setting:domain}\n" err = unsetInConfigWords(settingInto(settingsForm.Resources[0], nil, "notifier"), settingsForm) diff --git a/go/catalogue/declaration.go b/go/catalogue/declaration.go index efa24a3..66c0660 100644 --- a/go/catalogue/declaration.go +++ b/go/catalogue/declaration.go @@ -10,6 +10,7 @@ package catalogue import ( "encoding/json" + "errors" "fmt" "sort" "strconv" @@ -1832,7 +1833,14 @@ func (r Resolution) composed(m Manifest, to string, raw map[string]any, layers [ // module's files is no part of it (novox/hq 04-ISSUES/173). values, err := overridden(raw, WithDefaults(m, layers), what) if err != nil { - return nil, fmt.Errorf("%s: %w", what, err) + // A required value nothing sets is said in the words of the module's form, as a file's, a unit's and an owner's + // are (issue 1126, seed-8: de-spiegel in the config form was refused with ${setting:…} and `settings set`). + var unset *UnsetSettingError + if errors.As(err, &unset) && unset.Module == "" { + unset.Module = m.Module + unset.where = fmt.Sprintf("a contribution%s, in %q,", strings.TrimPrefix(what, m.Module+" contributing"), unset.field) + } + return nil, unsetInConfigWords(fmt.Errorf("%s: %w", what, err), m) } reaches, err := Reaches(m, layers) if err != nil { diff --git a/go/catalogue/setting_into.go b/go/catalogue/setting_into.go index e16e28d..3ced407 100644 --- a/go/catalogue/setting_into.go +++ b/go/catalogue/setting_into.go @@ -51,6 +51,8 @@ type UnsetSettingError struct { // where says what asks for it — "a file", "a service whose unit", "a directory whose owner" — and // setToday which of the module's keys a layer sets, for saying it again in the config form's words. where, setToday string + // field is the key of a contribution whose value asks for it, where the contribution's words are made by its caller. + field string } func (e *UnsetSettingError) Error() string { return e.said } diff --git a/go/catalogue/settings.go b/go/catalogue/settings.go index 03efee3..4c57f07 100644 --- a/go/catalogue/settings.go +++ b/go/catalogue/settings.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "encoding/json" "encoding/pem" + "errors" "fmt" "regexp" "sort" @@ -129,7 +130,12 @@ func ApplySettings(resource map[string]any, layers []Layer) (map[string]any, err // contract. Until issue 696, a layer replaced any served key it named; influxdb on ace was the one // layer relying on it, and serves `${setting:org}` and `${setting:bucket}` since. func Settle(base map[string]any, layers []Layer) (map[string]any, error) { - return filledFromSettings(deepCopy(base), layers, "what is served") + out, err := filledFromSettings(deepCopy(base), layers, "what is served") + if err != nil { + // Said as it was, untyped: what is served names no module here to type it by. + return nil, errors.New(err.Error()) + } + return out, nil } // ServedSettings is every key a module's served values and its zone's name fill from its settings (`${setting:}` @@ -193,10 +199,12 @@ func filledFromSettings(merged map[string]any, layers []Layer, what string) (map for _, asked := range settingsUsed(s) { v, set := settingValue(layers, asked) if !set { - return nil, fmt.Errorf( + // Typed, its module and where left to the caller that knows them (Resolution.composed), so a module in + // the config form is refused in its own words (unsetInConfigWords; issue 1126, seed-8). + return nil, &UnsetSettingError{Setting: asked, field: key, setToday: orNoSettings(layers), said: fmt.Sprintf( "%s says ${setting:%s} for %q, and nothing sets %q — an operator's value is the "+ "assignment's, never the definition's (novox/hq ADR 0112)%s", - what, asked, key, asked, orNoSettings(layers)) + what, asked, key, asked, orNoSettings(layers))} } s = strings.ReplaceAll(s, "${setting:"+asked+"}", plainly(v)) }