Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2da48d9b25 |
@@ -528,8 +528,11 @@ are open —
|
||||
linux-static
|
||||
windows-domain
|
||||
|
||||
— and then one line for the address. Empty means DHCP on gvm's own road, and
|
||||
whatever the specification says on the vCenter's.
|
||||
— and then one line for the address, which says in words what leaving it empty
|
||||
would do:
|
||||
|
||||
address for web05, or empty for DHCP:
|
||||
address for web05, or 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
|
||||
|
||||
+12
-8
@@ -690,9 +690,10 @@ func (b *browser) deploy(r vmRow) {
|
||||
case "":
|
||||
b.deployAsk(r, src, target, name, deployOpts{}, "")
|
||||
case "-":
|
||||
b.deployAddress(r, src, target, name, deployOpts{st: b.site})
|
||||
b.deployAddress(r, src, target, name, deployOpts{how: customSite, st: b.site})
|
||||
default:
|
||||
b.deployAddress(r, src, target, name, deployOpts{spec: kinds[i], st: b.site})
|
||||
b.deployAddress(r, src, target, name,
|
||||
deployOpts{how: customSpec, spec: kinds[i], st: b.site})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -705,12 +706,15 @@ func (b *browser) deploy(r vmRow) {
|
||||
// usually DHCP, and that is the ordinary case on a network that hands out
|
||||
// addresses.
|
||||
func (b *browser) deployAddress(r vmRow, src deploySource, t deployTarget, name string, opts deployOpts) {
|
||||
// Empty means two different things and the question says which: with a
|
||||
// specification of the vCenter's it leaves whatever that one says, and with
|
||||
// gvm's own it leaves the adapter on DHCP.
|
||||
ask := SF("address for %s (empty = DHCP): ", name)
|
||||
if !opts.building() {
|
||||
ask = SF("address for %s (empty = as %s says): ", name, opts.spec)
|
||||
// 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
|
||||
// vCenter's it leaves whatever that one says. The question says which, in
|
||||
// words rather than in the shorthand it used to use — "(empty = as
|
||||
// linux-static says)" reads as a sentence with a word missing even when the
|
||||
// name is there.
|
||||
ask := SF("address for %s, or empty for DHCP: ", name)
|
||||
if opts.how == customSpec {
|
||||
ask = SF("address for %s, or empty to leave it to %s: ", name, opts.spec)
|
||||
}
|
||||
ip, ok := b.input(ask)
|
||||
if !ok {
|
||||
|
||||
@@ -51,32 +51,40 @@ type deployOpts struct {
|
||||
powerOn bool
|
||||
|
||||
// What the guest is told about itself, and where that comes from.
|
||||
//
|
||||
// Two ways, and they are the same two everywhere in vSphere. spec names a
|
||||
// customisation specification the vCenter already holds, where the site's
|
||||
// answers live in the vCenter and gvm overrides only this machine's two
|
||||
// facts. Without one, gvm writes the specification itself out of st — the
|
||||
// same site answers, kept in ~/.gvmrc instead. The second is the shorter
|
||||
// road for a site that has no specifications and does not want to keep a
|
||||
// set of them; the first is the only road for Windows, which needs a
|
||||
// Sysprep gvm does not write.
|
||||
spec string
|
||||
how custom
|
||||
spec string // which one, when how is customSpec
|
||||
hostname string // empty means the machine's own name
|
||||
ip string // empty leaves the adapter on DHCP
|
||||
st site // the site's own answers, from the configuration
|
||||
}
|
||||
|
||||
// custom is where a customisation comes from. Two roads, and they are the same
|
||||
// two everywhere in vSphere: a specification the vCenter already holds, where
|
||||
// the site's answers live in the vCenter and gvm overrides only this machine's
|
||||
// two facts; or one gvm writes itself out of ~/.gvmrc, which is the same site
|
||||
// answers kept somewhere else. The second is the shorter road for a site that
|
||||
// has no specifications; the first is the only road for Windows.
|
||||
//
|
||||
// It is a decision carried rather than one worked out from the other fields.
|
||||
// Inferring it was a small mistake with a visible end: the interactive half
|
||||
// settles which road before it asks for an address, so at that moment the
|
||||
// fields are all still empty — which read as "no customisation at all" and
|
||||
// produced the question "address for web05 (empty = as says)", with a hole in
|
||||
// it where the name of a specification that was never chosen would have gone.
|
||||
type custom int
|
||||
|
||||
const (
|
||||
customNone custom = iota // leave the guest as the template made it
|
||||
customSite // gvm writes the specification, from the configuration
|
||||
customSpec // one the vCenter holds, named in spec
|
||||
)
|
||||
|
||||
// customising reports whether anything is to be done to the guest at all.
|
||||
func (o deployOpts) customising() bool {
|
||||
return strings.TrimSpace(o.spec) != "" || strings.TrimSpace(o.ip) != "" ||
|
||||
strings.TrimSpace(o.hostname) != ""
|
||||
}
|
||||
func (o deployOpts) customising() bool { return o.how != customNone }
|
||||
|
||||
// building reports whether gvm is to write the specification itself rather than
|
||||
// take one from the vCenter.
|
||||
func (o deployOpts) building() bool {
|
||||
return strings.TrimSpace(o.spec) == "" && o.customising()
|
||||
}
|
||||
func (o deployOpts) building() bool { return o.how == customSite }
|
||||
|
||||
// deployTarget is where the new machine will go, resolved from the template and
|
||||
// the options before anything is sent — so the question that is asked is the
|
||||
|
||||
@@ -570,3 +570,92 @@ func TestTheSiteComesOutOfTheConfiguration(t *testing.T) {
|
||||
t.Errorf("an empty one describes itself as %q", Config{}.site().describe())
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// customisation at all" and produced "address for web05 (empty = as says)",
|
||||
// a sentence with a hole where the name of a specification nobody had chosen
|
||||
// would have gone.
|
||||
func TestTheAddressQuestionReadsAsASentence(t *testing.T) {
|
||||
t.Setenv("COLUMNS", "100")
|
||||
t.Setenv("LINES", "24")
|
||||
|
||||
for _, c := range []struct {
|
||||
what string
|
||||
opts deployOpts
|
||||
want string
|
||||
}{
|
||||
{"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"},
|
||||
} {
|
||||
r := templateRow("ubuntu-tpl")
|
||||
b := &browser{rows: []vmRow{r}, view: []int{0}}
|
||||
|
||||
pr, pw, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.tty = pw
|
||||
drawn := make(chan string, 1)
|
||||
go func() {
|
||||
buf := make([]byte, 1<<16)
|
||||
n, _ := pr.Read(buf)
|
||||
drawn <- string(buf[:n])
|
||||
}()
|
||||
|
||||
kr, kw, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.keys = newKeyReader(kr)
|
||||
kw.WriteString("\x1b") // esc: the question is what is being read, not the answer
|
||||
|
||||
b.deployAddress(r, deploySource{name: "ubuntu-tpl"},
|
||||
deployTarget{where: "prod-cluster"}, "web05", c.opts)
|
||||
|
||||
frame := stripEscapes(<-drawn)
|
||||
pw.Close()
|
||||
pr.Close()
|
||||
kw.Close()
|
||||
|
||||
if !strings.Contains(frame, c.want) {
|
||||
t.Errorf("%s: the question does not say %q:\n%s", c.what, c.want, lastLine(frame))
|
||||
}
|
||||
// No hole where a name should be, on either road.
|
||||
for _, hole := range []string{"as says", " says", "to : ", "empty = as"} {
|
||||
if strings.Contains(frame, hole) {
|
||||
t.Errorf("%s: the question has a hole in it (%q):\n%s", c.what, hole, lastLine(frame))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The road is carried, not worked out from the other fields — which is what
|
||||
// made that hole. Before an address or a name has been typed, the decision is
|
||||
// already the whole answer.
|
||||
func TestTheRoadIsCarriedNotGuessed(t *testing.T) {
|
||||
// Chosen, with nothing filled in yet: still a customisation, and still
|
||||
// gvm's own.
|
||||
empty := deployOpts{how: customSite, st: testSite()}
|
||||
if !empty.customising() {
|
||||
t.Error("choosing gvm's own specification does not count as customising")
|
||||
}
|
||||
if !empty.building() {
|
||||
t.Error("choosing gvm's own specification is not building one")
|
||||
}
|
||||
|
||||
// A specification of the vCenter's, likewise, before an address is typed.
|
||||
named := deployOpts{how: customSpec, spec: "linux-static"}
|
||||
if !named.customising() || named.building() {
|
||||
t.Errorf("a vCenter specification reads as customising=%v building=%v",
|
||||
named.customising(), named.building())
|
||||
}
|
||||
|
||||
// And nothing chosen is nothing done, whatever else is lying around.
|
||||
none := deployOpts{st: testSite()}
|
||||
if none.customising() || none.building() {
|
||||
t.Error("a deployment nobody asked to customise customises anyway")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,8 +321,18 @@ func run() error {
|
||||
if newFrom == "" || newName == "" {
|
||||
return errf("new needs both: gvm new --from <template> --name <machine>")
|
||||
}
|
||||
// The flags are the decision here, so the road is read off them: --spec
|
||||
// names one the vCenter holds, and either of the other two on their own
|
||||
// means gvm writes it from the configuration.
|
||||
how := customNone
|
||||
switch {
|
||||
case newSpec != "":
|
||||
how = customSpec
|
||||
case newIP != "" || newHostname != "":
|
||||
how = customSite
|
||||
}
|
||||
return deployCLI(vc, newFrom, newName, deployOpts{
|
||||
host: newHost, datastore: newStore, powerOn: newOn,
|
||||
host: newHost, datastore: newStore, powerOn: newOn, how: how,
|
||||
spec: newSpec, ip: newIP, hostname: newHostname, st: cfg.site(),
|
||||
}, yes)
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.3.0
|
||||
1.3.1
|
||||
|
||||
Reference in New Issue
Block a user