The mesh owns the operator's ~/.ssh: account fact + home-scoped resources (to-be 29)
A node carries its operator account (name + home; migration 0036, Node.Account,
SetAccount, 'node account' CLI). The account and its home are offered as
machine facts ${machine:account} / ${machine:account-home}, and machineInto
now resolves placeholders in a resource's path and owner (not just content), so
a module writes into a person's home naming what it cannot know. A RosterFile
gains Home: the file is placed under the account's home and chowned to it, its
template sees each node's Account, and a machine with no account gets none —
this is how the ssh Host blocks for every node reach a person's ~/.ssh. Roster
carries per-node accounts (Rendering.Accounts). Tested, including ssh-client
composed end-to-end. Not deployed.
This commit is contained in:
@@ -14,7 +14,7 @@ func TestAModuleIsGivenTheFileItAskedFor(t *testing.T) {
|
||||
m := Manifest{Module: "resolver", Facts: map[string]RosterFile{
|
||||
"zones": {Path: "/etc/mesh/zones.conf", Template: "{{range .Machines}}address=/{{.FQDN}}/{{.Address}}\n{{end}}"},
|
||||
}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, threeMachines, threeMachines, "")
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, threeMachines, threeMachines, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -43,7 +43,7 @@ func TestASharedFactIsWrittenIntoARegion(t *testing.T) {
|
||||
"node-names": {Path: "/etc/hosts", Template: "{{range .Names}}{{.FQDN}}\n{{end}}", Shared: true},
|
||||
"node-zones": {Path: "/etc/zones", Template: "{{range .Machines}}{{.FQDN}}\n{{end}}"},
|
||||
}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, roster, roster, "")
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, roster, roster, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -68,11 +68,11 @@ func TestTheFormatIsTheModulesOwn(t *testing.T) {
|
||||
sshish := Manifest{Module: "b", Facts: map[string]RosterFile{
|
||||
"f": {Path: "/f", Template: "{{range .Names}}Host {{.Name}}\n HostName {{.FQDN}}\n{{end}}"}}}
|
||||
|
||||
h, err := FactsInto(hostsish, Resolution{Node: "homer"}, roster, roster, "")
|
||||
h, err := FactsInto(hostsish, Resolution{Node: "homer"}, roster, roster, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := FactsInto(sshish, Resolution{Node: "homer"}, roster, roster, "")
|
||||
s, err := FactsInto(sshish, Resolution{Node: "homer"}, roster, roster, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func TestTheFormatIsTheModulesOwn(t *testing.T) {
|
||||
func TestABrokenTemplateIsRefusedHere(t *testing.T) {
|
||||
m := Manifest{Module: "resolver", Facts: map[string]RosterFile{
|
||||
"zones": {Path: "/etc/zones", Template: "{{range .Machines}}oops"}}}
|
||||
_, err := FactsInto(m, Resolution{}, nil, nil, "")
|
||||
_, err := FactsInto(m, Resolution{}, nil, nil, nil, "")
|
||||
if err == nil {
|
||||
t.Fatal("a template that does not parse was accepted, so the machine gets an empty file")
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func TestABrokenTemplateIsRefusedHere(t *testing.T) {
|
||||
func TestATemplateReadingWhatTheMeshDoesNotHaveIsRefused(t *testing.T) {
|
||||
m := Manifest{Module: "resolver", Facts: map[string]RosterFile{
|
||||
"zones": {Path: "/etc/zones", Template: "{{.Weather}}"}}}
|
||||
if _, err := FactsInto(m, Resolution{}, nil, nil, ""); err == nil {
|
||||
if _, err := FactsInto(m, Resolution{}, nil, nil, nil, ""); err == nil {
|
||||
t.Fatal("a template read a field nobody computes and rendered anyway, silently")
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func TestATemplateReadingWhatTheMeshDoesNotHaveIsRefused(t *testing.T) {
|
||||
func TestAFactMustBeAskedForAtAnAbsolutePath(t *testing.T) {
|
||||
m := Manifest{Module: "resolver", Facts: map[string]RosterFile{
|
||||
"hosts": {Path: "etc/hosts", Template: "x"}}}
|
||||
if _, err := FactsInto(m, Resolution{}, nil, nil, ""); err == nil {
|
||||
if _, err := FactsInto(m, Resolution{}, nil, nil, nil, ""); err == nil {
|
||||
t.Fatal("a relative path was accepted")
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func TestAFactMustBeAskedForAtAnAbsolutePath(t *testing.T) {
|
||||
func TestAMachineWithNoAddressIsNotInTheRoster(t *testing.T) {
|
||||
m := Manifest{Module: "a", Facts: map[string]RosterFile{
|
||||
"f": {Path: "/f", Template: "{{range .Machines}}{{.Name}}\n{{end}}"}}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, threeMachines, threeMachines, "")
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, threeMachines, threeMachines, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -142,11 +142,11 @@ func TestNamesAreNotSuffixedTwice(t *testing.T) {
|
||||
bare := map[string]string{"homer": "10.42.0.1"}
|
||||
tmpl := RosterFile{Path: "/f", Template: "{{range .Machines}}{{.FQDN}} {{.Name}}\n{{end}}"}
|
||||
|
||||
fromInternal, err := FactsInto(Manifest{Module: "a", Facts: map[string]RosterFile{"f": tmpl}}, Resolution{Node: "homer"}, internal, internal, "")
|
||||
fromInternal, err := FactsInto(Manifest{Module: "a", Facts: map[string]RosterFile{"f": tmpl}}, Resolution{Node: "homer"}, internal, internal, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fromBare, err := FactsInto(Manifest{Module: "a", Facts: map[string]RosterFile{"f": tmpl}}, Resolution{Node: "homer"}, bare, bare, "")
|
||||
fromBare, err := FactsInto(Manifest{Module: "a", Facts: map[string]RosterFile{"f": tmpl}}, Resolution{Node: "homer"}, bare, bare, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func TestTheSuffixIsCarriedAsComposed(t *testing.T) {
|
||||
names := map[string]string{"homer.lan": "10.42.0.1"}
|
||||
m := Manifest{Module: "a", Facts: map[string]RosterFile{
|
||||
"f": {Path: "/f", Template: "local=/{{.Suffix}}/\n{{range .Machines}}{{.FQDN}}\n{{end}}"}}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, names, names, "lan")
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, names, names, nil, "lan")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func TestATemplateChoosesMachinesOrEveryName(t *testing.T) {
|
||||
"zones": {Path: "/etc/zones", Template: "{{range .Machines}}{{.FQDN}}\n{{end}}"},
|
||||
"hosts": {Path: "/etc/hosts", Template: "{{range .Names}}{{.FQDN}}\n{{end}}"},
|
||||
}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, every, machines, "")
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, every, machines, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -219,3 +219,42 @@ func TestATemplateChoosesMachinesOrEveryName(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A home fact is placed under the operator account's home and chowned to it, and its template sees
|
||||
// each node's account (novox/hq to-be 29) — the ssh-client config is the case.
|
||||
func TestAHomeFactIsPlacedUnderTheAccountsHomeAndOwnedByIt(t *testing.T) {
|
||||
names := map[string]string{"homer.internal": "10.42.0.1", "marge.internal": "10.42.0.2"}
|
||||
accounts := map[string]string{"homer": "jo", "marge": "jo"}
|
||||
m := Manifest{Module: "ssh-client", Facts: map[string]RosterFile{
|
||||
"ssh-config": {Path: ".ssh/config.d/mesh", Home: true,
|
||||
Template: "{{range .Names}}Host {{.Name}}\n HostName {{.FQDN}}\n User {{.Account}}\n{{end}}"}}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer", Account: "jo"}, names, names, accounts, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := given[0]
|
||||
if f["path"] != "/home/jo/.ssh/config.d/mesh" {
|
||||
t.Fatalf("the home fact was not placed under the account's home: %v", f["path"])
|
||||
}
|
||||
if f["owner"] != "jo" {
|
||||
t.Fatalf("the home fact is not owned by the account: %v", f["owner"])
|
||||
}
|
||||
if !strings.Contains(f["content"].(string), "Host marge\n HostName marge.internal\n User jo") {
|
||||
t.Fatalf("the config does not name the peer's account:\n%s", f["content"])
|
||||
}
|
||||
}
|
||||
|
||||
// A machine with no operator account gets no home fact — it cannot be placed, so it is left out
|
||||
// rather than written to nowhere.
|
||||
func TestAHomeFactIsSkippedWhereThereIsNoAccount(t *testing.T) {
|
||||
names := map[string]string{"homer.internal": "10.42.0.1"}
|
||||
m := Manifest{Module: "ssh-client", Facts: map[string]RosterFile{
|
||||
"ssh-config": {Path: ".ssh/config", Home: true, Template: "x"}}}
|
||||
given, err := FactsInto(m, Resolution{Node: "homer"}, names, names, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(given) != 0 {
|
||||
t.Fatalf("a home fact was placed on a machine with no operator account: %v", given)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user