Add mixer bundle edit command

Adds new command, 'mixer bundle edit', that allows a user to edit local
and upstream bundle definition files. This command will locate the
bundle (looking first in local-bundles, then in upstream-bundles), and launch
an editor to edit it. If the bundle is only found upstream, the bundle file will
first be copied to your local-bundles directory for editing. When the editor
closes, the bundle file is then parsed for validity.

The editor is configured via environment variables. VISUAL takes precedence to
EDITOR. If neither are set, the tool defaults to nano. If nano is not installed,
the tool will skip editing, and act as if '--copy-only' had been passed.

Passing '--copy-only' will suppress launching the editor, and will thus only copy
the bundle file to local-bundles if it is only found upstream. This can be
useful if you want to add a bundle to local-bundles, but wish to edit it at a
later time.

Passing '--add' will also add the bundle(s) to your mix. Please note that
bundles are added after all bunles are edited, and thus will not be added if any
errors are encountered earlier on.

Signed-off-by: Kevin C. Wells <kevin.c.wells@intel.com>
This commit is contained in:
Kevin C. Wells
2018-02-13 14:52:30 -08:00
committed by tmarcu
parent 9dfbf7f65f
commit 424031e5aa
3 changed files with 194 additions and 0 deletions
+138
View File
@@ -16,6 +16,7 @@ package builder
import (
"archive/tar"
"bufio"
"bytes"
"crypto/rsa"
"crypto/x509"
@@ -914,6 +915,143 @@ func (b *Builder) ListBundles(listType listType, tree bool) error {
return nil
}
func getEditorCmd() (string, error) {
cmd := os.Getenv("VISUAL")
if cmd != "" {
return cmd, nil
}
cmd = os.Getenv("EDITOR")
if cmd != "" {
return cmd, nil
}
return exec.LookPath("nano")
}
// editBundleFile launches an editor command to edit the bundle defined by path.
// When the edit process ends, the bundle file is parsed for validity. If a
// parsing error is encountered, the user is asked how to proceed: retry, revert
// and retry, or skip.
func editBundleFile(editorCmd string, bundle string, path string) error {
// Make backup
backup := path + ".orig"
if err := helpers.CopyFileNoOverwrite(backup, path); err != nil && !os.IsExist(err) {
return errors.Wrapf(err, "Could not backup bundle '%s' file for editing", bundle)
}
reader := bufio.NewReader(os.Stdin)
revert := false
editLoop:
for {
if revert {
if err := helpers.CopyFile(path, backup); err != nil {
return errors.Wrapf(err, "Could not restore original from backup for bundle '%s'", bundle)
}
}
// Ignore return from command; parsing below is what will reveal errors
_ = helpers.RunCommandInput(os.Stdin, editorCmd, path)
_, err := parseBundleFile(path)
if err == nil {
// Clean-up backup
if err = os.Remove(backup); err != nil {
return errors.Wrapf(err, "Error cleaning up backup for bundle '%s'", bundle)
}
break editLoop
}
fmt.Printf("Error parsing bundle %s: %s\n", bundle, err)
for {
// Ask the user if they want to retry, revert, or skip
fmt.Print("Would you like to edit as-is, revert and edit, or skip [Edit/Revert/Skip]?: ")
text, err := reader.ReadString('\n')
if err != nil {
return errors.Wrapf(err, "Error reading input")
}
text = strings.ToLower(text)
text = strings.TrimSpace(text)
switch {
case text == "e" || text == "edit":
revert = false
continue editLoop
case text == "r" || text == "revert":
revert = true
continue editLoop
case text == "s" || text == "skip":
fmt.Printf("Skipping bundle '%s' despite errors. Backup retained as '%s'\n", bundle, bundle+".orig")
break editLoop
default:
fmt.Printf("Invalid input: '%s'", text)
}
}
}
return nil
}
// EditBundles copies a list of bundles from upstream-bundles to local-bundles
// (if they are not already there), and launches an editor to edit them. Passing
// true for 'copyOnly' will suppress the launching of the editor (and just do
// the copy, if needed), and 'add' will also add the bundles to the mix.
func (b *Builder) EditBundles(bundles []string, copyOnly bool, add bool, git bool) error {
// Fetch upstream bundle files if needed
if err := b.getUpstreamBundles(b.UpstreamVer, true); err != nil {
return err
}
editorCmd, err := getEditorCmd()
if err != nil {
fmt.Println("Cannot find a valid editor (see usage for configuration). Copying to local-bundles only.")
copyOnly = true
}
for _, bundle := range bundles {
var path string
path, err = b.getBundlePath(bundle)
if err != nil {
return err
}
if !b.isLocalBundle(path) {
localPath := filepath.Join(b.LocalBundleDir, bundle)
if err = helpers.CopyFile(localPath, path); err != nil {
return err
}
path = localPath
}
if copyOnly {
continue
}
if err = editBundleFile(editorCmd, bundle, path); err != nil {
return err
}
}
if add {
if err = b.AddBundles(bundles, false, false, false); err != nil {
return err
}
}
if git {
fmt.Println("Adding git commit")
if err := helpers.Git("add", "."); err != nil {
return err
}
commitMsg := fmt.Sprintf("Edited bundles: %v", bundles)
if err := helpers.Git("commit", "-q", "-m", commitMsg); err != nil {
return err
}
}
return nil
}
// UpdateMixVer automatically bumps the mixversion file +10 to prepare for the next build
// without requiring user intervention. This makes the flow slightly more automatable.
func (b *Builder) UpdateMixVer() error {
+7
View File
@@ -290,9 +290,16 @@ func Git(args ...string) error {
// RunCommand runs the given command with args and prints output
func RunCommand(cmdname string, args ...string) error {
return RunCommandInput(nil, cmdname, args...)
}
// RunCommandInput runs the given command with args and input from an io.Reader,
// and prints output
func RunCommandInput(in io.Reader, cmdname string, args ...string) error {
cmd := exec.Command(cmdname, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = in
err := cmd.Run()
if err != nil {
return errors.Wrapf(err, "failed to execute %s", strings.Join(cmd.Args, " "))
+49
View File
@@ -114,10 +114,55 @@ var bundleListCmd = &cobra.Command{
},
}
// Bundle Edit command ('mixer bundle edit')
type bundleEditCmdFlags struct {
copyOnly bool
add bool
git bool
}
var bundleEditFlags bundleEditCmdFlags
var bundleEditCmd = &cobra.Command{
Use: "edit [bundle(s)]",
Short: "Edit local and upstream bundles",
Long: `Edit local and upstream bundle definition files. This command will locate the
bundle (looking first in local-bundles, then in upstream-bundles), and launch
an editor to edit it. If the bundle is only found upstream, the bundle file will
first be copied to your local-bundles directory for editing. When the editor
closes, the bundle file is then parsed for validity.
The editor is configured via environment variables. VISUAL takes precedence to
EDITOR. If neither are set, the tool defaults to nano. If nano is not installed,
the tool will skip editing, and act as if '--copy-only' had been passed.
Passing '--copy-only' will suppress launching the editor, and will thus only
copy the bundle file to local-bundles (if it is only found upstream). This can
be useful if you want to add a bundle to local-bundles, but wish to edit it at a
later time.
Passing '--add' will also add the bundle(s) to your mix. Please note that
bundles are added after all bundles are edited, and thus will not be added if
any errors are encountered earlier on.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
b, err := builder.NewFromConfig(config)
if err != nil {
fail(err)
}
err = b.EditBundles(args, bundleEditFlags.copyOnly, bundleEditFlags.add, bundleEditFlags.git)
if err != nil {
fail(err)
}
},
}
// List of all bundle commands
var bundlesCmds = []*cobra.Command{
bundleAddCmd,
bundleListCmd,
bundleEditCmd,
}
func init() {
@@ -132,4 +177,8 @@ func init() {
bundleAddCmd.Flags().BoolVar(&bundleAddFlags.git, "git", false, "Automatically apply new git commit")
bundleListCmd.Flags().BoolVar(&bundleListFlags.tree, "tree", false, "Pretty-print the list as a tree.")
bundleEditCmd.Flags().BoolVar(&bundleEditFlags.copyOnly, "copy-only", false, "Suppress launching editor (only copy to local-bundles if upstream)")
bundleEditCmd.Flags().BoolVar(&bundleEditFlags.add, "add", false, "Add the bundle(s) to your mix")
bundleEditCmd.Flags().BoolVar(&bundleEditFlags.git, "git", false, "Automatically apply new git commit")
}