Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c22a66c470 | ||
|
|
ae936420e0 |
@@ -545,16 +545,23 @@ are open —
|
||||
|
||||
customise web05 how v308
|
||||
|
||||
▸ leave the guest as the template made it
|
||||
this site — fhi-berlin.mpg.de, gateway 10.0.0.1
|
||||
leave the guest as the template made it
|
||||
▸ this site — fhi-berlin.mpg.de, gateway 10.0.0.1
|
||||
linux-static
|
||||
windows-domain
|
||||
|
||||
— and then one line for the address, which says in words what leaving it empty
|
||||
would do:
|
||||
The cursor starts on gvm's own road wherever the site is set up: a domain, a
|
||||
netmask and a gateway written into `~/.gvmrc` are written down so that machines
|
||||
get them, and the answer wanted nearly every time should not be the one that has
|
||||
to be arrowed to. Where the site is not set up it starts at the top instead, on
|
||||
the answer that is never wrong. Nothing is decided by where it sits — the picker
|
||||
picks on Enter, and the confirmation still says in full what will happen.
|
||||
|
||||
address for web05, or empty for DHCP:
|
||||
address for web05, or empty to leave it to linux-static:
|
||||
Then one line for the address, which says in words what leaving it empty would
|
||||
do:
|
||||
|
||||
address for web05, empty for DHCP:
|
||||
address for web05, empty to leave it to linux-static:
|
||||
|
||||
Where that picker does **not** appear, the confirmation says why rather than
|
||||
leaving a step to look broken — neither road is open, because the vCenter holds
|
||||
@@ -571,9 +578,16 @@ when it will not say.
|
||||
gvm new --from ubuntu-tpl --name web05 --ip auto
|
||||
|
||||
Where the site has a tool that hands addresses out, `--ip auto` asks it instead
|
||||
of making you look one up first. In the list the same word does it:
|
||||
of making you look one up first. In the list the same word does it, and it is
|
||||
already standing in the line — a site with a tool hands addresses out from it,
|
||||
so that is the answer Enter gives:
|
||||
|
||||
address for web05, "auto" for one from dns, empty for DHCP:
|
||||
address for web05, "auto" for one from dns, empty for DHCP: auto
|
||||
|
||||
It is offered rather than made the meaning of the empty line, so every answer
|
||||
keeps the meaning it had: erased back to nothing it is DHCP again, or whatever
|
||||
the chosen specification says. And Enter commits nothing — an address fetched
|
||||
here is handed straight back when the deployment is not confirmed.
|
||||
|
||||
It shells out to `dns`, the Infoblox helper — found on the path by that name, or
|
||||
named as `dnstool` in `~/.gvmrc` where it lives somewhere else. Everything it
|
||||
|
||||
+67
-17
@@ -372,7 +372,19 @@ func (b *browser) openPicker(r vmRow, kind pickKind) {
|
||||
// callback runs after the picker has closed, so that what it does — a
|
||||
// confirmation, another question — has the screen to itself.
|
||||
func (b *browser) choose(title string, r vmRow, lines []string, chosen func(int)) {
|
||||
b.pick = &picker{title: title, row: r, lines: lines, chosen: chosen}
|
||||
b.chooseAt(title, r, lines, 0, chosen)
|
||||
}
|
||||
|
||||
// chooseAt is the same with the cursor starting somewhere other than the top,
|
||||
// for a list whose ordinary answer is not its first line. Where the cursor
|
||||
// starts is the only default a picker has — nothing is picked until Enter — so
|
||||
// it belongs on the line somebody nearly always wants, and the others are one
|
||||
// key away.
|
||||
func (b *browser) chooseAt(title string, r vmRow, lines []string, at int, chosen func(int)) {
|
||||
if at < 0 || at >= len(lines) {
|
||||
at = 0
|
||||
}
|
||||
b.pick = &picker{title: title, row: r, lines: lines, chosen: chosen, sel: at}
|
||||
}
|
||||
|
||||
func (b *browser) closePicker() { b.pick = nil }
|
||||
@@ -677,21 +689,9 @@ func (b *browser) deploy(r vmRow) {
|
||||
return
|
||||
}
|
||||
|
||||
lines := []string{"leave the guest as the template made it"}
|
||||
kinds := []string{""} // what each line means: "" none, "-" gvm's own, else a name
|
||||
if own {
|
||||
// What it will do, not where the settings are kept: a picker line that
|
||||
// is mostly an absolute path says nothing about the choice being made,
|
||||
// and `gvm config` is where the file is named.
|
||||
lines = append(lines, SF("this site — %s, gateway %s", b.site.domain, b.site.gateway))
|
||||
kinds = append(kinds, "-")
|
||||
}
|
||||
for _, name := range specs {
|
||||
lines = append(lines, name)
|
||||
kinds = append(kinds, name)
|
||||
}
|
||||
lines, kinds, at := customChoices(b.site, specs, own)
|
||||
|
||||
b.choose(SF("customise %s how", name), r, lines, func(i int) {
|
||||
b.chooseAt(SF("customise %s how", name), r, lines, at, func(i int) {
|
||||
switch kinds[i] {
|
||||
case "":
|
||||
b.deployAsk(r, src, target, name, deployOpts{}, deployStep{})
|
||||
@@ -705,13 +705,50 @@ func (b *browser) deploy(r vmRow) {
|
||||
})
|
||||
}
|
||||
|
||||
// customChoices is what the customisation picker offers, what each line means,
|
||||
// and where its cursor starts.
|
||||
//
|
||||
// kinds is the meaning, carried beside the lines rather than worked out from an
|
||||
// index: "" leaves the guest alone, "-" is the specification gvm writes from
|
||||
// ~/.gvmrc, and anything else is the name of one the vCenter holds.
|
||||
//
|
||||
// The cursor starts on gvm's own road where the site is set up. A site whose
|
||||
// domain, netmask and gateway have been written down is a site whose machines
|
||||
// are meant to be given them — that is what writing them down was for — and the
|
||||
// answer wanted nearly every time should not be the one that has to be arrowed
|
||||
// to. Where there is no such road it starts at the top, on the answer that is
|
||||
// never wrong: leave the guest as the template made it.
|
||||
//
|
||||
// Nothing is decided by the cursor sitting there. The picker chooses on Enter,
|
||||
// the address is asked for after it, and the confirmation still says in full
|
||||
// what will happen.
|
||||
func customChoices(st site, specs []string, own bool) (lines, kinds []string, at int) {
|
||||
lines = []string{"leave the guest as the template made it"}
|
||||
kinds = []string{""}
|
||||
if own {
|
||||
// What it will do, not where the settings are kept: a picker line that
|
||||
// is mostly an absolute path says nothing about the choice being made,
|
||||
// and `gvm config` is where the file is named.
|
||||
lines = append(lines, SF("this site — %s, gateway %s", st.domain, st.gateway))
|
||||
kinds = append(kinds, "-")
|
||||
at = len(lines) - 1
|
||||
}
|
||||
for _, name := range specs {
|
||||
lines = append(lines, name)
|
||||
kinds = append(kinds, name)
|
||||
}
|
||||
return lines, kinds, at
|
||||
}
|
||||
|
||||
// deployAddress asks for the address, which is the other half of what a
|
||||
// specification cannot know: it holds the netmask, the gateway and the domain,
|
||||
// and the machine holds its own number.
|
||||
//
|
||||
// Empty is an answer: it leaves whatever the specification says, which is
|
||||
// usually DHCP, and that is the ordinary case on a network that hands out
|
||||
// addresses.
|
||||
// addresses. Where the site has a tool of its own, the ordinary case is the
|
||||
// other one — so "auto" is in the line when the question appears, and Enter
|
||||
// takes it.
|
||||
func (b *browser) deployAddress(r vmRow, src deploySource, t deployTarget, name string, opts deployOpts) {
|
||||
// Empty is an answer, and it means two different things: with gvm's own
|
||||
// specification it leaves the adapter on DHCP, and with one of the
|
||||
@@ -728,11 +765,24 @@ func (b *browser) deployAddress(r vmRow, src deploySource, t deployTarget, name
|
||||
leave = "empty to leave it to " + opts.spec
|
||||
}
|
||||
ask := SF("address for %s, %s: ", name, leave)
|
||||
|
||||
// And where there is a tool, its word is standing in the line already, so
|
||||
// Enter is the whole answer. That is what the tool is configured for: a
|
||||
// site that has one hands out addresses from it, and typing "auto" every
|
||||
// time is asking somebody to confirm the ordinary case by hand.
|
||||
//
|
||||
// Offered rather than made the meaning of the empty line, so that every
|
||||
// answer keeps the meaning it had: erased back to nothing it is DHCP again,
|
||||
// or whatever the specification says. Nor does pressing Enter commit
|
||||
// anything — an address fetched here is handed straight back where the
|
||||
// deployment is not confirmed (deployStep.giveBack).
|
||||
preset := ""
|
||||
if opts.dns.there() {
|
||||
ask = SF("address for %s, %q for one from dns, %s: ", name, autoIP, leave)
|
||||
preset = autoIP
|
||||
}
|
||||
|
||||
ip, ok := b.input(ask)
|
||||
ip, ok := b.inputWith(ask, preset)
|
||||
if !ok {
|
||||
b.setStatus(colDim, "nothing done")
|
||||
return
|
||||
|
||||
@@ -1300,8 +1300,18 @@ func (e *editor) key(k key) (finished, accepted bool) {
|
||||
// input reads one line in the status area and reports whether it was finished
|
||||
// rather than abandoned. It has its own key loop, so nothing that is typed here
|
||||
// reaches the filter.
|
||||
func (b *browser) input(label string) (string, bool) {
|
||||
b.edit = &editor{label: label}
|
||||
func (b *browser) input(label string) (string, bool) { return b.inputWith(label, "") }
|
||||
|
||||
// inputWith is the same with an answer already standing in the line, for a
|
||||
// question that has one ordinary answer worth offering: Enter takes it, and
|
||||
// Backspace is how it is refused. It is a default one can see — which is the
|
||||
// only kind worth having, since a question whose answer is decided by what was
|
||||
// not typed is one nobody knows they have agreed to.
|
||||
//
|
||||
// The cursor sits at the end of it, so typing carries on from the answer rather
|
||||
// than into the middle of it.
|
||||
func (b *browser) inputWith(label, preset string) (string, bool) {
|
||||
b.edit = &editor{label: label, runes: []rune(preset), cursor: len([]rune(preset))}
|
||||
defer func() { b.edit = nil }()
|
||||
|
||||
for {
|
||||
|
||||
@@ -571,6 +571,43 @@ func TestTheSiteComesOutOfTheConfiguration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The picker opens on the road gvm writes itself where the site has been set
|
||||
// up: a domain, a netmask and a gateway in ~/.gvmrc are there so that machines
|
||||
// get them, and the answer wanted nearly every time must not be the one that
|
||||
// has to be arrowed to. Where there is no such road it opens at the top, on the
|
||||
// answer that is never wrong.
|
||||
func TestTheCustomisationPickerOpensOnTheSiteRoad(t *testing.T) {
|
||||
st := testSite()
|
||||
|
||||
lines, kinds, at := customChoices(st, []string{"linux-static", "windows"}, true)
|
||||
if len(lines) != 4 || len(kinds) != len(lines) {
|
||||
t.Fatalf("the picker offers %d lines and %d meanings: %v", len(lines), len(kinds), lines)
|
||||
}
|
||||
if kinds[at] != "-" {
|
||||
t.Errorf("it opens on %q, which is %q — want gvm's own road", lines[at], kinds[at])
|
||||
}
|
||||
// And that line says what it will do, in the site's own words: a default
|
||||
// nobody can read is one nobody knows they are taking.
|
||||
if !strings.Contains(lines[at], st.domain) || !strings.Contains(lines[at], st.gateway) {
|
||||
t.Errorf("the line it opens on is %q, want the domain and the gateway in it", lines[at])
|
||||
}
|
||||
// The vCenter's own are still there, after it and in order.
|
||||
if lines[2] != "linux-static" || lines[3] != "windows" {
|
||||
t.Errorf("the vCenter's specifications came out as %v", lines[2:])
|
||||
}
|
||||
|
||||
// No site, and the cursor is at the top rather than on somebody else's
|
||||
// specification: leaving the guest as the template made it is the one
|
||||
// answer that cannot be wrong.
|
||||
lines, kinds, at = customChoices(site{}, []string{"linux-static"}, false)
|
||||
if at != 0 || kinds[at] != "" {
|
||||
t.Errorf("with no site it opens on %q (%q)", lines[at], kinds[at])
|
||||
}
|
||||
if len(lines) != 2 {
|
||||
t.Errorf("with no site the picker offers %v", lines)
|
||||
}
|
||||
}
|
||||
|
||||
// The question that asks for an address has to read as a sentence on both
|
||||
// roads. It did not: the interactive half settles which road before it asks,
|
||||
// so at that moment every field is still empty — which read as "no
|
||||
@@ -581,6 +618,8 @@ func TestTheAddressQuestionReadsAsASentence(t *testing.T) {
|
||||
t.Setenv("COLUMNS", "100")
|
||||
t.Setenv("LINES", "24")
|
||||
|
||||
tool, _ := fakeInfoblox(t) // for its there(), not for its answers
|
||||
|
||||
for _, c := range []struct {
|
||||
what string
|
||||
opts deployOpts
|
||||
@@ -589,6 +628,12 @@ func TestTheAddressQuestionReadsAsASentence(t *testing.T) {
|
||||
{"gvm's own specification", deployOpts{how: customSite, st: testSite()}, "empty for DHCP"},
|
||||
{"one the vCenter holds", deployOpts{how: customSpec, spec: "linux-static"},
|
||||
"leave it to linux-static"},
|
||||
// With an address tool, the answer it would give is standing in the
|
||||
// line already: that is what the tool is there for, and the question
|
||||
// still has to say what the other two answers mean.
|
||||
{"a site with an address tool",
|
||||
deployOpts{how: customSite, st: testSite(), dns: tool},
|
||||
`"auto" for one from dns, empty for DHCP: auto`},
|
||||
} {
|
||||
r := templateRow("ubuntu-tpl")
|
||||
b := &browser{rows: []vmRow{r}, view: []int{0}}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.3.8
|
||||
1.3.10
|
||||
|
||||
Reference in New Issue
Block a user