diff --git a/internal/apply/apply.go b/internal/apply/apply.go index a1ff719a..f6efe674 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -785,7 +785,7 @@ func applyScoped( // change one apply late, and never misses it. // And one patience with the artifact store for the whole apply (novox/hq issue 291): a fetch it // does not answer during a maintenance window waits for the window instead of failing. - in := inputs{declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log), + in := inputs{node: d.For, declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log), groups: wanted, handed: handed} // Where nothing is placed or mounted from, judged before anything is applied (novox/hq issue 339), and // which declared directories were already here: one this apply makes is the mesh's, one found may not be. @@ -1625,7 +1625,7 @@ func applyOne(ctx context.Context, sys system.System, r declaration.Resource, ru if err != nil { return begin(r), err } - return applyProcess(ctx, res, run, changed, previous, held, in.away) + return applyProcess(ctx, withNode(res, in.node), run, changed, previous, held, in.away) case *declaration.Action: return applyAction(ctx, res, run) case *declaration.Network: @@ -2816,6 +2816,9 @@ type inputs struct { // fetched is every archive's and process's bytes this apply fetched before anything was replaced (fetchFirst), each // taken by its applier once (artifact). fetched map[string]fetchedArtifact + // node is the node's own name, the declaration's For: every process runs with it as MESH_NODE beside its own env + // (design 47 G A-342). Empty where the declaration names none (the first node's carried bundle). + node string } // fileDigest is what a file the container reads holds, by digest. diff --git a/internal/apply/filled.go b/internal/apply/filled.go index 6bd9b7a5..75cf15e6 100644 --- a/internal/apply/filled.go +++ b/internal/apply/filled.go @@ -95,7 +95,7 @@ func WriteFilledWith( "yet taken (%s), so nothing was written", path, held.Why)} } - in := inputs{declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log), + in := inputs{node: d.For, declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log), groups: groupsWanted(d.Resources), handed: Handed{}} in.refused = refusedPlaces(d.Resources) in.agentHomes = rootNeverHomes(d) diff --git a/internal/apply/process.go b/internal/apply/process.go index 7eb2a4d9..4d434ab0 100644 --- a/internal/apply/process.go +++ b/internal/apply/process.go @@ -274,6 +274,25 @@ func applyProcess(ctx context.Context, r *declaration.Process, run Runner, return out, nil } +// NodeEnv is the variable every process the node-engine runs reads its node's name from (design 47 G A-342), the one +// mesh-sdk reads (MESH_NODE). +const NodeEnv = "MESH_NODE" + +// withNode is the process as it runs on node: its own env with NodeEnv beside it (design 47 G A-342). The validator +// refuses a resource that sets it, so the mesh's value is the only one; with no node named the process is as declared. +func withNode(r *declaration.Process, node string) *declaration.Process { + if node == "" { + return r + } + p := *r + p.Env = make(map[string]string, len(r.Env)+1) + for k, v := range r.Env { + p.Env[k] = v + } + p.Env[NodeEnv] = node + return &p +} + // unitFor is the unit the mesh writes for a daemon. // // **Generated whole and never edited in place**, the same rule as every other managed file: an diff --git a/internal/apply/process_test.go b/internal/apply/process_test.go index 146ce1b4..1cab4259 100644 --- a/internal/apply/process_test.go +++ b/internal/apply/process_test.go @@ -319,11 +319,17 @@ func TestAProcessAppliedAgainIsUnchangedAndNotRestarted(t *testing.T) { } d := declare(t, `{"id":"node-tools.runtime","type":"process","name":"node-tools","source":"`+serving(t, body)+ `","digest":"`+digest+`","run":["node","main.js"],"env":{"MESH_TOOL_MODULES":"a=/x/index.js"}}`) + d.For = "anchor" first, state, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginDeclared, run, nil, nil) if err != nil { t.Fatal(err) } + // Design 47 G A-342: the process runs with its node's name as MESH_NODE beside its own env. + if b, err := os.ReadFile(filepath.Join(units, "node-tools.service")); err != nil || !strings.Contains(string(b), `Environment="MESH_NODE=anchor"`) || + !strings.Contains(string(b), `Environment="MESH_TOOL_MODULES=`) { + t.Errorf("the unit does not carry the node's name beside its env (%v):\n%s", err, b) + } if o := outcomeOf(first, "node-tools.runtime"); o.Action != "created" { t.Fatalf("first apply: %+v, want created", o) } @@ -360,6 +366,7 @@ func TestAProcessAppliedAgainIsUnchangedAndNotRestarted(t *testing.T) { // An input of its unit that differs restarts it, and the line says which (issue 1966). changed := declare(t, `{"id":"node-tools.runtime","type":"process","name":"node-tools","source":"`+serving(t, body)+ `","digest":"`+digest+`","run":["node","main.js"],"env":{"MESH_TOOL_MODULES":"a=/x/index.js,b=/y/index.js"}}`) + changed.For = "anchor" fourth, _, err := Apply(context.Background(), archHost(t), changed, state, store.OriginDeclared, run, nil, nil) if err != nil { t.Fatal(err)