Skip to content

Commit

Permalink
cluster: allow continue editing when new topology has errors (#624)
Browse files Browse the repository at this point in the history
  • Loading branch information
AstroProfundis authored Jul 23, 2020
1 parent 32db7e2 commit 0ec224a
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 33 deletions.
90 changes: 58 additions & 32 deletions components/cluster/command/edit_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package command
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
Expand Down Expand Up @@ -59,7 +60,29 @@ func newEditConfigCmd() *cobra.Command {
return err
}

return editTopo(clusterName, metadata)
// do marshal and unmarshal outside editTopo() to avoid vadation inside
data, err := yaml.Marshal(metadata.Topology)
if err != nil {
return perrs.AddStack(err)
}

newTopo, err := editTopo(clusterName, metadata.Topology, data)
if err != nil {
return err
}
if newTopo == nil {
return nil
}

log.Infof("Apply the change...")
metadata.Topology = newTopo
err = spec.SaveClusterMeta(clusterName, metadata)
if err != nil {
return perrs.Annotate(err, "failed to save")
}

log.Infof("Apply change successfully, please use `%s reload %s [-N <nodes>] [-R <roles>]` to reload config.", cliutil.OsArgs0(), clusterName)
return nil
},
}

Expand All @@ -70,75 +93,78 @@ func newEditConfigCmd() *cobra.Command {
// 2. Open file in editor.
// 3. Check and update Topology.
// 4. Save meta file.
func editTopo(clusterName string, metadata *spec.ClusterMeta) error {
data, err := yaml.Marshal(metadata.Topology)
if err != nil {
return perrs.AddStack(err)
}

func editTopo(clusterName string, origTopo *spec.Specification, data []byte) (*spec.Specification, error) {
file, err := ioutil.TempFile(os.TempDir(), "*")
if err != nil {
return perrs.AddStack(err)
return nil, perrs.AddStack(err)
}

name := file.Name()

_, err = io.Copy(file, bytes.NewReader(data))
if err != nil {
return perrs.AddStack(err)
return nil, perrs.AddStack(err)
}

err = file.Close()
if err != nil {
return perrs.AddStack(err)
return nil, perrs.AddStack(err)
}

err = tiuputils.OpenFileInEditor(name)
if err != nil {
return perrs.AddStack(err)
return nil, perrs.AddStack(err)
}

// Now user finish editing the file.
newData, err := ioutil.ReadFile(name)
if err != nil {
return perrs.AddStack(err)
return nil, perrs.AddStack(err)
}

newTopo := new(spec.Specification)
err = yaml.UnmarshalStrict(newData, newTopo)
if err != nil {
fmt.Print(color.RedString("New topology could not be saved: "))
log.Infof("Failed to parse topology file: %v", err)
return perrs.AddStack(err)
if cliutil.PromptForConfirmReverse("Do you want to continue editing? [Y/n]: ") {
return editTopo(clusterName, origTopo, newData)
}
log.Infof("Nothing changed.")
return nil, nil
}

if bytes.Equal(data, newData) {
log.Infof("The file has nothing changed")
return nil
// report error if immutable field has been changed
if err := tiuputils.ValidateSpecDiff(origTopo, newTopo); err != nil {
fmt.Print(color.RedString("New topology could not be saved: "))
log.Errorf("%s", err)
if cliutil.PromptForConfirmReverse("Do you want to continue editing? [Y/n]: ") {
return editTopo(clusterName, origTopo, newData)
}
log.Infof("Nothing changed.")
return nil, nil

}

// report error if immutable field has been changed
if err := tiuputils.ValidateSpecDiff(metadata.Topology, newTopo); err != nil {
return err
origData, err := yaml.Marshal(origTopo)
if err != nil {
return nil, perrs.AddStack(err)
}
tiuputils.ShowDiff(string(data), string(newData), os.Stdout)

if bytes.Equal(origData, newData) {
log.Infof("The file has nothing changed")
return nil, nil
}

tiuputils.ShowDiff(string(origData), string(newData), os.Stdout)

if !skipConfirm {
if err := cliutil.PromptForConfirmOrAbortError(
color.HiYellowString("Please check change highlight above, do you want to apply the change? [y/N]:"),
); err != nil {
return err
return nil, err
}
}

log.Infof("Apply the change...")

metadata.Topology = newTopo
err = spec.SaveClusterMeta(clusterName, metadata)
if err != nil {
return perrs.Annotate(err, "failed to save")
}

log.Infof("Apply change successfully, please use `%s reload %s [-N <nodes>] [-R <roles>]` to reload config.", cliutil.OsArgs0(), clusterName)

return nil
return newTopo, nil
}
7 changes: 6 additions & 1 deletion pkg/cliutil/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func Prompt(prompt string) string {
return strings.TrimSuffix(input, "\n")
}

// PromptForConfirm accepts yes / no from console by user
// PromptForConfirm accepts yes / no from console by user, default to No
func PromptForConfirm(format string, a ...interface{}) bool {
ans := Prompt(fmt.Sprintf(format, a...))
switch strings.TrimSpace(strings.ToLower(ans)) {
Expand All @@ -79,6 +79,11 @@ func PromptForConfirm(format string, a ...interface{}) bool {
}
}

// PromptForConfirmReverse accepts yes / no from console by user, default to Yes
func PromptForConfirmReverse(format string, a ...interface{}) bool {
return !PromptForConfirm(format, a...)
}

// PromptForConfirmOrAbortError accepts yes / no from console by user, generates AbortError if user does not input yes.
func PromptForConfirmOrAbortError(format string, a ...interface{}) error {
if !PromptForConfirm(format, a...) {
Expand Down

0 comments on commit 0ec224a

Please sign in to comment.