mirror of
https://github.com/gluster/glusterd2.git
synced 2026-02-05 12:45:38 +01:00
add logging if there is an error in CLI, with the help of -v flag user should be able to check the failure logs. Fixes #1414 Signed-off-by: Madhu Rajanna <mrajanna@redhat.com>
564 lines
16 KiB
Go
564 lines
16 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gluster/glusterd2/pkg/api"
|
|
|
|
"github.com/olekukonko/tablewriter"
|
|
"github.com/pborman/uuid"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
helpVolumeCmd = "Gluster Volume Management"
|
|
helpVolumeCreateCmd = "Create a Gluster Volume"
|
|
helpVolumeStartCmd = "Start a Gluster Volume"
|
|
helpVolumeStopCmd = "Stop a Gluster Volume"
|
|
helpVolumeDeleteCmd = "Delete a Gluster Volume"
|
|
helpVolumeGetCmd = "Get Gluster Volume Options"
|
|
helpVolumeInfoCmd = "Get Gluster Volume Info"
|
|
helpVolumeListCmd = "List all Gluster Volumes"
|
|
helpVolumeStatusCmd = "Get Gluster Volume Status"
|
|
helpVolumeSizeCmd = "Get Gluster Volume Size Usage"
|
|
helpVolumeExpandCmd = "Expand a Gluster Volume"
|
|
helpVolumeEditCmd = "Edit metadata (key-value pairs) of a volume. Glusterd2 will not interpret these key and value in any way"
|
|
)
|
|
|
|
var (
|
|
// Start Command Flags
|
|
flagStartCmdForce bool
|
|
|
|
// Stop Command Flags
|
|
flagStopCmdForce bool
|
|
|
|
// Expand Command Flags
|
|
flagExpandCmdReplicaCount int
|
|
flagExpandCmdForce bool
|
|
flagExpandCmdDistributeCount int
|
|
flagExpandCmdSize string
|
|
|
|
// Filter Volume Info/List command flags
|
|
flagCmdFilterKey string
|
|
flagCmdFilterValue string
|
|
|
|
//Filter Volume Get command flags
|
|
flagGetAdv bool
|
|
flagGetBsc bool
|
|
flagGetMdf bool
|
|
|
|
// Edit Command Flags
|
|
flagCmdMetadataKey string
|
|
flagCmdMetadataValue string
|
|
flagCmdDeleteMetadata bool
|
|
//volume expand flags
|
|
flagReuseBricks, flagAllowRootDir, flagAllowMountAsBrick, flagCreateBrickDir bool
|
|
)
|
|
|
|
func init() {
|
|
// Volume Start
|
|
volumeStartCmd.Flags().BoolVarP(&flagStartCmdForce, "force", "f", false, "Force")
|
|
volumeCmd.AddCommand(volumeStartCmd)
|
|
|
|
// Volume Stop
|
|
volumeStopCmd.Flags().BoolVarP(&flagStopCmdForce, "force", "f", false, "Force")
|
|
volumeCmd.AddCommand(volumeStopCmd)
|
|
|
|
// Volume Delete
|
|
volumeCmd.AddCommand(volumeDeleteCmd)
|
|
|
|
volumeGetCmd.Flags().BoolVar(&flagGetAdv, "advanced", false, "Get advanced options")
|
|
volumeGetCmd.Flags().BoolVar(&flagGetBsc, "basic", true, "Get basic options")
|
|
volumeGetCmd.Flags().BoolVar(&flagGetMdf, "modified", false, "Get modified options")
|
|
volumeCmd.AddCommand(volumeGetCmd)
|
|
|
|
volumeCmd.AddCommand(volumeSizeCmd)
|
|
|
|
volumeInfoCmd.Flags().StringVar(&flagCmdFilterKey, "key", "", "Filter by metadata key")
|
|
volumeInfoCmd.Flags().StringVar(&flagCmdFilterValue, "value", "", "Filter by metadata value")
|
|
volumeCmd.AddCommand(volumeInfoCmd)
|
|
|
|
volumeCmd.AddCommand(volumeStatusCmd)
|
|
|
|
volumeListCmd.Flags().StringVar(&flagCmdFilterKey, "key", "", "Filter by metadata Key")
|
|
volumeListCmd.Flags().StringVar(&flagCmdFilterValue, "value", "", "Filter by metadata value")
|
|
volumeCmd.AddCommand(volumeListCmd)
|
|
|
|
// Volume Expand
|
|
volumeExpandCmd.Flags().IntVar(&flagExpandCmdReplicaCount, "replica", 0, "Replica Count")
|
|
volumeExpandCmd.Flags().IntVar(&flagExpandCmdDistributeCount, "distribute", 0, "Distribute Count")
|
|
volumeExpandCmd.Flags().StringVar(&flagExpandCmdSize, "size", "", "Size by which volume needs to be expanded.")
|
|
volumeExpandCmd.Flags().BoolVarP(&flagExpandCmdForce, "force", "f", false, "Force")
|
|
volumeExpandCmd.Flags().BoolVar(&flagReuseBricks, "reuse-bricks", false, "Reuse Bricks")
|
|
volumeExpandCmd.Flags().BoolVar(&flagAllowRootDir, "allow-root-dir", false, "Allow Root Directory")
|
|
volumeExpandCmd.Flags().BoolVar(&flagAllowMountAsBrick, "allow-mount-as-brick", false, "Allow Mount as Bricks")
|
|
volumeExpandCmd.Flags().BoolVar(&flagCreateBrickDir, "create-brick-dir", false, "Create brick directory")
|
|
volumeCmd.AddCommand(volumeExpandCmd)
|
|
|
|
// Volume Edit
|
|
volumeEditCmd.Flags().StringVar(&flagCmdMetadataKey, "key", "", "Metadata Key")
|
|
volumeEditCmd.Flags().StringVar(&flagCmdMetadataValue, "value", "", "Metadata Value")
|
|
volumeEditCmd.Flags().BoolVar(&flagCmdDeleteMetadata, "delete", false, "Delete Metadata")
|
|
volumeEditCmd.MarkFlagRequired("key")
|
|
volumeEditCmd.MarkFlagRequired("value")
|
|
volumeCmd.AddCommand(volumeEditCmd)
|
|
}
|
|
|
|
var volumeCmd = &cobra.Command{
|
|
Use: "volume",
|
|
Short: helpVolumeCmd,
|
|
}
|
|
|
|
func bricksAsUUID(bricks []string) ([]api.BrickReq, error) {
|
|
// Validate Brick format
|
|
for _, brick := range bricks {
|
|
hostBrickData := strings.Split(brick, ":")
|
|
if len(hostBrickData) != 2 {
|
|
return nil, errors.New("invalid Brick details, use <host>:<path> or <peerid>:<path>")
|
|
}
|
|
}
|
|
|
|
// validate if <host> in <host>:<path> is already UUID
|
|
validUUIDs := 0
|
|
for _, brick := range bricks {
|
|
host := strings.Split(brick, ":")[0]
|
|
if uuid.Parse(host) == nil {
|
|
break
|
|
}
|
|
validUUIDs++
|
|
}
|
|
|
|
if validUUIDs == len(bricks) {
|
|
// bricks are already of the format <uuid>:<path>
|
|
var bs []api.BrickReq
|
|
for _, b := range bricks {
|
|
bData := strings.Split(b, ":")
|
|
bs = append(bs, api.BrickReq{
|
|
PeerID: bData[0],
|
|
Path: bData[1],
|
|
})
|
|
}
|
|
return bs, nil
|
|
}
|
|
|
|
peers, err := client.Peers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var brickUUIDs []api.BrickReq
|
|
|
|
for _, brick := range bricks {
|
|
host := strings.Split(brick, ":")[0]
|
|
path := strings.Split(brick, ":")[1]
|
|
for _, peer := range peers {
|
|
for _, addr := range peer.PeerAddresses {
|
|
// TODO: Normalize presence/absence of port in peer address
|
|
if strings.Split(addr, ":")[0] == strings.Split(host, ":")[0] {
|
|
brickUUIDs = append(brickUUIDs, api.BrickReq{
|
|
PeerID: peer.ID.String(),
|
|
Path: path,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(brickUUIDs) != len(bricks) {
|
|
return brickUUIDs, errors.New("could not find UUIDs of bricks specified")
|
|
}
|
|
|
|
return brickUUIDs, nil
|
|
}
|
|
|
|
var volumeStartCmd = &cobra.Command{
|
|
Use: "start [flags] <VOLNAME>",
|
|
Short: helpVolumeStartCmd,
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
volname := cmd.Flags().Args()[0]
|
|
err := client.VolumeStart(volname, flagStartCmdForce)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("volume start failed")
|
|
}
|
|
failure("volume start failed", err, 1)
|
|
}
|
|
fmt.Printf("Volume %s started successfully\n", volname)
|
|
},
|
|
}
|
|
|
|
var volumeStopCmd = &cobra.Command{
|
|
Use: "stop [flags] <VOLNAME>",
|
|
Short: helpVolumeStopCmd,
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
volname := cmd.Flags().Args()[0]
|
|
err := client.VolumeStop(volname)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("volume stop failed")
|
|
}
|
|
failure("Volume stop failed", err, 1)
|
|
}
|
|
fmt.Printf("Volume %s stopped successfully\n", volname)
|
|
},
|
|
}
|
|
|
|
var volumeDeleteCmd = &cobra.Command{
|
|
Use: "delete",
|
|
Short: helpVolumeDeleteCmd,
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
volname := cmd.Flags().Args()[0]
|
|
if !GlobalFlag.ScriptMode {
|
|
if ok := PromptConfirm("Are you sure you want to delete volume %s [yes/no]? ", volname); !ok {
|
|
return
|
|
}
|
|
}
|
|
err := client.VolumeDelete(volname)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("volume deletion failed")
|
|
}
|
|
failure("Volume deletion failed", err, 1)
|
|
}
|
|
fmt.Printf("Volume %s deleted successfully\n", volname)
|
|
},
|
|
}
|
|
|
|
var volumeGetCmd = &cobra.Command{
|
|
Use: "get",
|
|
Short: helpVolumeGetCmd,
|
|
Args: cobra.ExactArgs(2),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
|
|
volname := args[0]
|
|
optname := args[1]
|
|
|
|
opts, err := client.VolumeGet(volname, optname)
|
|
if err != nil {
|
|
log.WithError(err).Error("error getting volume options")
|
|
failure("Error getting volume options", err, 1)
|
|
}
|
|
|
|
table := tablewriter.NewWriter(os.Stdout)
|
|
table.SetHeader([]string{"Name", "Modified", "Value", "Default Value", "Option Level"})
|
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
|
|
for _, opt := range opts {
|
|
//if modified flag is set, discard unmodified options
|
|
if flagGetMdf && !opt.Modified {
|
|
continue
|
|
}
|
|
if flagGetBsc && opt.OptionLevel == "Basic" {
|
|
table.Append([]string{opt.OptName, formatBoolYesNo(opt.Modified), opt.Value, opt.DefaultValue, opt.OptionLevel})
|
|
}
|
|
if flagGetAdv && opt.OptionLevel == "Advanced" {
|
|
table.Append([]string{opt.OptName, formatBoolYesNo(opt.Modified), opt.Value, opt.DefaultValue, opt.OptionLevel})
|
|
|
|
}
|
|
}
|
|
table.Render()
|
|
|
|
},
|
|
}
|
|
|
|
func volumeInfoDisplayNumbricks(vol api.VolumeGetResp) {
|
|
|
|
var DistCount = len(vol.Subvols)
|
|
// TODO: Assumption as all subvol types are same
|
|
var RepCount = vol.Subvols[0].ReplicaCount
|
|
var ArbCount = vol.Subvols[0].ArbiterCount
|
|
numBricks := 0
|
|
for _, subvol := range vol.Subvols {
|
|
numBricks += len(subvol.Bricks)
|
|
}
|
|
|
|
if DistCount > 1 && vol.Subvols[0].Type == api.SubvolReplicate {
|
|
if ArbCount == 1 {
|
|
fmt.Printf("Number of Bricks: %d x (%d + %d) = %d\n", DistCount, RepCount-1, ArbCount, numBricks)
|
|
} else {
|
|
fmt.Printf("Number of Bricks: %d x %d = %d\n", DistCount, RepCount, numBricks)
|
|
}
|
|
} else {
|
|
fmt.Println("Number of Bricks:", numBricks)
|
|
}
|
|
}
|
|
|
|
func volumeInfoDisplay(vol api.VolumeGetResp) {
|
|
fmt.Println()
|
|
fmt.Println("Volume Name:", vol.Name)
|
|
fmt.Println("Type:", vol.Type)
|
|
fmt.Println("Volume ID:", vol.ID)
|
|
fmt.Println("State:", vol.State)
|
|
if vol.Capacity != 0 {
|
|
fmt.Println("Capacity:", humanReadable(vol.Capacity))
|
|
}
|
|
fmt.Println("Transport-type:", vol.Transport)
|
|
fmt.Println("Options:")
|
|
for key, value := range vol.Options {
|
|
fmt.Printf(" %s: %s\n", key, value)
|
|
}
|
|
volumeInfoDisplayNumbricks(vol)
|
|
count := 1
|
|
for _, subvol := range vol.Subvols {
|
|
for _, brick := range subvol.Bricks {
|
|
if brick.Type == api.Arbiter {
|
|
fmt.Printf("Brick%d: %s:%s (arbiter)\n", count, brick.Hostname, brick.Path)
|
|
} else {
|
|
fmt.Printf("Brick%d: %s:%s\n", count, brick.Hostname, brick.Path)
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
return
|
|
}
|
|
func volumeInfoHandler2(cmd *cobra.Command, isInfo bool) error {
|
|
var vols api.VolumeListResp
|
|
var err error
|
|
volname := ""
|
|
if len(cmd.Flags().Args()) > 0 {
|
|
volname = cmd.Flags().Args()[0]
|
|
}
|
|
if volname == "" {
|
|
if flagCmdFilterKey == "" && flagCmdFilterValue == "" {
|
|
vols, err = client.Volumes("")
|
|
} else if flagCmdFilterKey != "" && flagCmdFilterValue == "" {
|
|
vols, err = client.Volumes("", map[string]string{"key": flagCmdFilterKey})
|
|
} else if flagCmdFilterKey == "" && flagCmdFilterValue != "" {
|
|
vols, err = client.Volumes("", map[string]string{"value": flagCmdFilterValue})
|
|
} else if flagCmdFilterKey != "" && flagCmdFilterValue != "" {
|
|
vols, err = client.Volumes("", map[string]string{"key": flagCmdFilterKey,
|
|
"value": flagCmdFilterValue,
|
|
})
|
|
}
|
|
} else {
|
|
if flagCmdFilterKey != "" || flagCmdFilterValue != "" {
|
|
return errors.New("invalid command. Cannot give filter arguments when providing volname")
|
|
}
|
|
vols, err = client.Volumes(volname)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(vols) <= 0 {
|
|
fmt.Println("No volumes found")
|
|
return nil
|
|
}
|
|
|
|
if isInfo {
|
|
for _, vol := range vols {
|
|
volumeInfoDisplay(vol)
|
|
}
|
|
} else {
|
|
table := tablewriter.NewWriter(os.Stdout)
|
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
table.SetHeader([]string{"ID", "Name", "Type", "State", "Transport", "Bricks"})
|
|
for _, vol := range vols {
|
|
brickCount := 0
|
|
for _, subvol := range vol.Subvols {
|
|
for range subvol.Bricks {
|
|
brickCount++
|
|
}
|
|
}
|
|
|
|
table.Append([]string{vol.ID.String(), vol.Name, vol.Type.String(),
|
|
vol.State.String(), vol.Transport, strconv.Itoa(brickCount)})
|
|
}
|
|
table.Render()
|
|
}
|
|
return err
|
|
}
|
|
|
|
var volumeInfoCmd = &cobra.Command{
|
|
Use: "info [<volname> |--key <key>|--value <value>|--key <key> --value <value>]",
|
|
Short: helpVolumeInfoCmd,
|
|
Args: cobra.RangeArgs(0, 1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
err := volumeInfoHandler2(cmd, true)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).Error("error getting volumes list")
|
|
}
|
|
failure("Error getting volumes list", err, 1)
|
|
}
|
|
},
|
|
}
|
|
|
|
var volumeListCmd = &cobra.Command{
|
|
Use: "list [--key <key>|--value <value>|--key <key> --value <value>]",
|
|
Short: helpVolumeListCmd,
|
|
Args: cobra.RangeArgs(0, 1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
err := volumeInfoHandler2(cmd, false)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).Error("error getting volumes list")
|
|
}
|
|
failure("Error getting volumes list", err, 1)
|
|
}
|
|
},
|
|
}
|
|
|
|
func volumeStatusDisplay(vol api.BricksStatusResp) {
|
|
table := tablewriter.NewWriter(os.Stdout)
|
|
table.SetHeader([]string{"Brick ID", "Host", "Path", "Online", "Port", "Pid"})
|
|
for _, b := range vol {
|
|
table.Append([]string{b.Info.ID.String(), b.Info.Hostname, b.Info.Path,
|
|
strconv.FormatBool(b.Online), strconv.Itoa(b.Port), strconv.Itoa(b.Pid)})
|
|
}
|
|
table.Render()
|
|
}
|
|
|
|
func volumeStatusHandler(cmd *cobra.Command) error {
|
|
var vol api.BricksStatusResp
|
|
var err error
|
|
volname := ""
|
|
if len(cmd.Flags().Args()) > 0 {
|
|
volname = cmd.Flags().Args()[0]
|
|
}
|
|
if volname == "" {
|
|
var volList api.VolumeListResp
|
|
volList, err = client.Volumes("")
|
|
if len(volList) <= 0 {
|
|
fmt.Println("No volumes found")
|
|
return nil
|
|
}
|
|
for _, volume := range volList {
|
|
vol, err = client.BricksStatus(volume.Name)
|
|
fmt.Println("Volume :", volume.Name)
|
|
if err == nil {
|
|
volumeStatusDisplay(vol)
|
|
} else {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).Error("error getting volume status")
|
|
}
|
|
failure("Error getting volume status", err, 1)
|
|
}
|
|
}
|
|
} else {
|
|
vol, err = client.BricksStatus(volname)
|
|
fmt.Println("Volume :", volname)
|
|
if err == nil {
|
|
volumeStatusDisplay(vol)
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
var volumeStatusCmd = &cobra.Command{
|
|
Use: "status",
|
|
Short: helpVolumeStatusCmd,
|
|
Args: cobra.RangeArgs(0, 1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
err := volumeStatusHandler(cmd)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).Error("error getting volume status")
|
|
}
|
|
failure("Error getting volume status", err, 1)
|
|
}
|
|
},
|
|
}
|
|
|
|
var volumeSizeCmd = &cobra.Command{
|
|
Use: "size",
|
|
Short: helpVolumeSizeCmd,
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
volname := cmd.Flags().Args()[0]
|
|
vol, err := client.VolumeStatus(volname)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("error getting volume size")
|
|
}
|
|
failure("Error getting volume size", err, 1)
|
|
}
|
|
fmt.Println("Volume:", volname)
|
|
fmt.Println("Capacity:", humanReadable(vol.Size.Capacity))
|
|
fmt.Println("Used:", humanReadable(vol.Size.Used))
|
|
fmt.Println("Free:", humanReadable(vol.Size.Free))
|
|
|
|
},
|
|
}
|
|
|
|
var volumeExpandCmd = &cobra.Command{
|
|
Use: "add-brick <volname> [<brick> [<brick>]...|--size <expansion-size>]",
|
|
Short: helpVolumeExpandCmd,
|
|
Args: cobra.MinimumNArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
volname := cmd.Flags().Args()[0]
|
|
bricks, err := bricksAsUUID(cmd.Flags().Args()[1:])
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("error getting brick UUIDs")
|
|
}
|
|
failure("Error getting brick UUIDs", err, 1)
|
|
}
|
|
//set flags
|
|
var size uint64
|
|
if flagExpandCmdSize != "" {
|
|
size, err = sizeToBytes(flagExpandCmdSize)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithFields(log.Fields{
|
|
"volume": volname,
|
|
"size": flagExpandCmdSize}).Error("invalid volume size")
|
|
}
|
|
failure("Invalid Volume Size specified", nil, 1)
|
|
}
|
|
}
|
|
flags := make(map[string]bool)
|
|
flags["reuse-bricks"] = flagReuseBricks
|
|
flags["allow-root-dir"] = flagAllowRootDir
|
|
flags["allow-mount-as-brick"] = flagAllowMountAsBrick
|
|
flags["create-brick-dir"] = flagCreateBrickDir
|
|
vol, err := client.VolumeExpand(volname, api.VolExpandReq{
|
|
ReplicaCount: flagExpandCmdReplicaCount,
|
|
Bricks: bricks, // string of format <UUID>:<path>
|
|
Force: flagExpandCmdForce,
|
|
Flags: flags,
|
|
DistributeCount: flagExpandCmdDistributeCount,
|
|
Size: uint64(size),
|
|
})
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("volume expansion failed")
|
|
}
|
|
failure("Addition of brick failed", err, 1)
|
|
}
|
|
fmt.Printf("%s Volume expanded successfully\n", vol.Name)
|
|
},
|
|
}
|
|
|
|
var volumeEditCmd = &cobra.Command{
|
|
Use: "edit-metadata <volname> --key <key> --value <value> [--delete]",
|
|
Short: helpVolumeEditCmd,
|
|
Args: cobra.ExactArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
volname := args[0]
|
|
metadata := make(map[string]string)
|
|
metadata[flagCmdMetadataKey] = flagCmdMetadataValue
|
|
editMetadataReq := api.VolEditReq{
|
|
Metadata: metadata,
|
|
DeleteMetadata: flagCmdDeleteMetadata,
|
|
}
|
|
_, err := client.EditVolume(volname, editMetadataReq)
|
|
if err != nil {
|
|
if GlobalFlag.Verbose {
|
|
log.WithError(err).WithField("volume", volname).Error("failed to edit metadata")
|
|
}
|
|
failure("Failed to edit metadata", err, 1)
|
|
}
|
|
fmt.Printf("Metadata edit successful\n")
|
|
},
|
|
}
|