github.com/vmware/govmomi@v0.51.0/cli/volume/ls.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package volume 6 7 import ( 8 "context" 9 "flag" 10 "fmt" 11 "io" 12 "log" 13 "strings" 14 "text/tabwriter" 15 16 "github.com/vmware/govmomi/cli" 17 "github.com/vmware/govmomi/cli/flags" 18 "github.com/vmware/govmomi/cns" 19 "github.com/vmware/govmomi/cns/types" 20 "github.com/vmware/govmomi/units" 21 vim "github.com/vmware/govmomi/vim25/types" 22 ) 23 24 type ls struct { 25 *flags.ClientFlag 26 *flags.DatastoreFlag 27 *flags.StorageProfileFlag 28 *flags.OutputFlag 29 30 types.CnsQueryFilter 31 32 long bool 33 id bool 34 disk bool 35 back bool 36 } 37 38 type keyValue []vim.KeyValue 39 40 func (e *keyValue) String() string { 41 return fmt.Sprintf("%v", *e) 42 } 43 44 func (e *keyValue) Set(v string) error { 45 r := strings.SplitN(v, "=", 2) 46 if len(r) < 2 { 47 return fmt.Errorf("failed to parse: %s", v) 48 } 49 *e = append(*e, vim.KeyValue{Key: r[0], Value: r[1]}) 50 return nil 51 } 52 53 func init() { 54 cli.Register("volume.ls", &ls{}) 55 } 56 57 func (cmd *ls) Register(ctx context.Context, f *flag.FlagSet) { 58 cmd.ClientFlag, ctx = flags.NewClientFlag(ctx) 59 cmd.ClientFlag.Register(ctx, f) 60 61 cmd.DatastoreFlag, ctx = flags.NewDatastoreFlag(ctx) 62 cmd.DatastoreFlag.Register(ctx, f) 63 64 cmd.StorageProfileFlag, ctx = flags.NewStorageProfileFlag(ctx) 65 cmd.StorageProfileFlag.Register(ctx, f) 66 67 cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx) 68 cmd.OutputFlag.Register(ctx, f) 69 70 f.BoolVar(&cmd.long, "l", false, "Long listing format") 71 f.BoolVar(&cmd.id, "i", false, "List volume ID only") 72 f.BoolVar(&cmd.disk, "L", false, "List volume disk or file backing ID only") 73 f.BoolVar(&cmd.back, "b", false, "List file backing path") 74 75 f.Var((*flags.StringList)(&cmd.Names), "n", "List volumes with names") 76 f.Var((*keyValue)(&cmd.Labels), "label", "List volumes with labels") 77 f.StringVar(&cmd.HealthStatus, "H", "", "List volumes with health status") 78 f.Var((*flags.StringList)(&cmd.ContainerClusterIds), "c", "List volumes in clusters") 79 } 80 81 func (cmd *ls) Process(ctx context.Context) error { 82 if err := cmd.ClientFlag.Process(ctx); err != nil { 83 return err 84 } 85 if err := cmd.DatastoreFlag.Process(ctx); err != nil { 86 return err 87 } 88 if err := cmd.StorageProfileFlag.Process(ctx); err != nil { 89 return err 90 } 91 return cmd.OutputFlag.Process(ctx) 92 } 93 94 func (cmd *ls) Usage() string { 95 return "[ID...]" 96 } 97 98 func (cmd *ls) Description() string { 99 return `List CNS volumes. 100 101 Examples: 102 govc volume.ls 103 govc volume.ls -l 104 govc volume.ls -ds vsanDatastore 105 govc volume.ls df86393b-5ae0-4fca-87d0-b692dbc67d45 106 govc volume.ls -json $id | jq -r .volume[].backingObjectDetails.backingDiskPath 107 govc volume.ls -b $id # verify backingDiskPath exists 108 govc disk.ls -l $(govc volume.ls -L pvc-9744a4ff-07f4-43c4-b8ed-48ea7a528734)` 109 } 110 111 type lsWriter struct { 112 Volume []types.CnsVolume `json:"volume"` 113 Info []types.BaseCnsVolumeOperationResult `json:"info,omitempty"` 114 cmd *ls 115 } 116 117 func (r *lsWriter) Dump() any { 118 if len(r.Info) != 0 { 119 return r.Info 120 } 121 return r.Volume 122 } 123 124 func (r *lsWriter) Write(w io.Writer) error { 125 if r.cmd.id { 126 for _, volume := range r.Volume { 127 fmt.Fprintln(r.cmd.Out, volume.VolumeId.Id) 128 } 129 return nil 130 } 131 132 if r.cmd.disk { 133 for _, volume := range r.Volume { 134 var id string 135 switch backing := volume.BackingObjectDetails.(type) { 136 case *types.CnsBlockBackingDetails: 137 id = backing.BackingDiskId 138 case *types.CnsFileBackingDetails: 139 id = backing.BackingFileId 140 case *types.CnsVsanFileShareBackingDetails: 141 id = backing.Name 142 case *types.CnsBackingObjectDetails: 143 id = volume.VolumeId.Id 144 default: 145 log.Printf("%s unknown backing type: %T", volume.VolumeId.Id, backing) 146 } 147 fmt.Fprintln(r.cmd.Out, id) 148 149 } 150 return nil 151 } 152 153 tw := tabwriter.NewWriter(r.cmd.Out, 2, 0, 2, ' ', 0) 154 155 for _, volume := range r.Volume { 156 fmt.Fprintf(tw, "%s\t%s", volume.VolumeId.Id, volume.Name) 157 if r.cmd.back { 158 fmt.Fprintf(tw, "\t%s", r.backing(volume.VolumeId)) 159 } 160 if r.cmd.long { 161 capacity := volume.BackingObjectDetails.GetCnsBackingObjectDetails().CapacityInMb 162 c := volume.Metadata.ContainerCluster 163 fmt.Fprintf(tw, "\t%s\t%s\t%s", units.ByteSize(capacity*1024*1024), c.ClusterType, c.ClusterId) 164 } 165 fmt.Fprintln(tw) 166 } 167 168 return tw.Flush() 169 } 170 171 func (r *lsWriter) backing(id types.CnsVolumeId) string { 172 for _, info := range r.Info { 173 res, ok := info.(*types.CnsQueryVolumeInfoResult) 174 if !ok { 175 continue 176 } 177 178 switch vol := res.VolumeInfo.(type) { 179 case *types.CnsBlockVolumeInfo: 180 if vol.VStorageObject.Config.Id.Id == id.Id { 181 switch backing := vol.VStorageObject.Config.BaseConfigInfo.Backing.(type) { 182 case *vim.BaseConfigInfoDiskFileBackingInfo: 183 return backing.FilePath 184 } 185 } 186 } 187 188 if fault := res.Fault; fault != nil { 189 if f, ok := fault.Fault.(types.CnsFault); ok { 190 if strings.Contains(f.Reason, id.Id) { 191 return f.Reason 192 } 193 } 194 } 195 } 196 return "???" 197 } 198 199 func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error { 200 ds, err := cmd.DatastoreIfSpecified() 201 if err != nil { 202 return err 203 } 204 205 if ds != nil { 206 cmd.Datastores = []vim.ManagedObjectReference{ds.Reference()} 207 } 208 209 cmd.StoragePolicyId, err = cmd.StorageProfile(ctx) 210 if err != nil { 211 return err 212 } 213 214 c, err := cmd.CnsClient() 215 if err != nil { 216 return err 217 } 218 219 for _, arg := range f.Args() { 220 cmd.VolumeIds = append(cmd.VolumeIds, types.CnsVolumeId{Id: arg}) 221 } 222 223 var volumes []types.CnsVolume 224 var info []types.BaseCnsVolumeOperationResult 225 226 for { 227 res, err := c.QueryVolume(ctx, cmd.CnsQueryFilter) 228 if err != nil { 229 return err 230 } 231 232 volumes = append(volumes, res.Volumes...) 233 234 if res.Cursor.Offset == res.Cursor.TotalRecords || len(res.Volumes) == 0 { 235 break 236 } 237 238 cmd.Cursor = &res.Cursor 239 } 240 241 if cmd.back { 242 ids := make([]types.CnsVolumeId, len(volumes)) 243 for i := range volumes { 244 ids[i] = volumes[i].VolumeId 245 } 246 247 task, err := c.QueryVolumeInfo(ctx, ids) 248 if err != nil { 249 return err 250 } 251 252 res, err := cns.GetTaskInfo(ctx, task) 253 if err != nil { 254 return err 255 } 256 257 info, err = cns.GetTaskResultArray(ctx, res) 258 if err != nil { 259 return err 260 } 261 } 262 263 return cmd.WriteResult(&lsWriter{volumes, info, cmd}) 264 }