github.com/ThomasObenaus/nomad@v0.11.1/command/volume_status_csi.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/hashicorp/nomad/api" 9 "github.com/hashicorp/nomad/nomad/structs" 10 ) 11 12 func (c *VolumeStatusCommand) csiBanner() { 13 if !(c.json || len(c.template) > 0) { 14 c.Ui.Output(c.Colorize().Color("[bold]Container Storage Interface[reset]")) 15 } 16 } 17 18 func (c *VolumeStatusCommand) csiStatus(client *api.Client, id string) int { 19 // Invoke list mode if no volume id 20 if id == "" { 21 c.csiBanner() 22 vols, _, err := client.CSIVolumes().List(nil) 23 if err != nil { 24 c.Ui.Error(fmt.Sprintf("Error querying volumes: %s", err)) 25 return 1 26 } 27 28 if len(vols) == 0 { 29 // No output if we have no volumes 30 c.Ui.Error("No CSI volumes") 31 } else { 32 str, err := c.csiFormatVolumes(vols) 33 if err != nil { 34 c.Ui.Error(fmt.Sprintf("Error formatting: %s", err)) 35 return 1 36 } 37 c.Ui.Output(str) 38 } 39 return 0 40 } 41 42 // Try querying the volume 43 vol, _, err := client.CSIVolumes().Info(id, nil) 44 if err != nil { 45 c.Ui.Error(fmt.Sprintf("Error querying volume: %s", err)) 46 return 1 47 } 48 49 str, err := c.formatBasic(vol) 50 if err != nil { 51 c.Ui.Error(fmt.Sprintf("Error formatting volume: %s", err)) 52 return 1 53 } 54 c.Ui.Output(str) 55 56 return 0 57 } 58 59 func (c *VolumeStatusCommand) csiFormatVolumes(vols []*api.CSIVolumeListStub) (string, error) { 60 // Sort the output by volume id 61 sort.Slice(vols, func(i, j int) bool { return vols[i].ID < vols[j].ID }) 62 63 if c.json || len(c.template) > 0 { 64 out, err := Format(c.json, c.template, vols) 65 if err != nil { 66 return "", fmt.Errorf("format error: %v", err) 67 } 68 return out, nil 69 } 70 71 rows := make([]string, len(vols)+1) 72 rows[0] = "ID|Name|Plugin ID|Schedulable|Access Mode" 73 for i, v := range vols { 74 rows[i+1] = fmt.Sprintf("%s|%s|%s|%t|%s", 75 limit(v.ID, c.length), 76 v.Name, 77 v.PluginID, 78 v.Schedulable, 79 v.AccessMode, 80 ) 81 } 82 return formatList(rows), nil 83 } 84 85 func (c *VolumeStatusCommand) formatBasic(vol *api.CSIVolume) (string, error) { 86 if c.json || len(c.template) > 0 { 87 out, err := Format(c.json, c.template, vol) 88 if err != nil { 89 return "", fmt.Errorf("format error: %v", err) 90 } 91 return out, nil 92 } 93 94 output := []string{ 95 fmt.Sprintf("ID|%s", vol.ID), 96 fmt.Sprintf("Name|%s", vol.Name), 97 fmt.Sprintf("External ID|%s", vol.ExternalID), 98 fmt.Sprintf("Plugin ID|%s", vol.PluginID), 99 fmt.Sprintf("Provider|%s", vol.Provider), 100 fmt.Sprintf("Version|%s", vol.ProviderVersion), 101 fmt.Sprintf("Schedulable|%t", vol.Schedulable), 102 fmt.Sprintf("Controllers Healthy|%d", vol.ControllersHealthy), 103 fmt.Sprintf("Controllers Expected|%d", vol.ControllersExpected), 104 fmt.Sprintf("Nodes Healthy|%d", vol.NodesHealthy), 105 fmt.Sprintf("Nodes Expected|%d", vol.NodesExpected), 106 107 fmt.Sprintf("Access Mode|%s", vol.AccessMode), 108 fmt.Sprintf("Attachment Mode|%s", vol.AttachmentMode), 109 fmt.Sprintf("Mount Options|%s", csiVolMountOption(vol.MountOptions, nil)), 110 fmt.Sprintf("Namespace|%s", vol.Namespace), 111 } 112 113 // Exit early 114 if c.short { 115 return formatKV(output), nil 116 } 117 118 // Format the allocs 119 banner := c.Colorize().Color("\n[bold]Allocations[reset]") 120 allocs := formatAllocListStubs(vol.Allocations, c.verbose, c.length) 121 full := []string{formatKV(output), banner, allocs} 122 return strings.Join(full, "\n"), nil 123 } 124 125 func (c *VolumeStatusCommand) formatTopologies(vol *api.CSIVolume) string { 126 var out []string 127 128 // Find the union of all the keys 129 head := map[string]string{} 130 for _, t := range vol.Topologies { 131 for key := range t.Segments { 132 if _, ok := head[key]; !ok { 133 head[key] = "" 134 } 135 } 136 } 137 138 // Append the header 139 var line []string 140 for key := range head { 141 line = append(line, key) 142 } 143 out = append(out, strings.Join(line, " ")) 144 145 // Append each topology 146 for _, t := range vol.Topologies { 147 line = []string{} 148 for key := range head { 149 line = append(line, t.Segments[key]) 150 } 151 out = append(out, strings.Join(line, " ")) 152 } 153 154 return strings.Join(out, "\n") 155 } 156 157 func csiVolMountOption(volume, request *api.CSIMountOptions) string { 158 var req, opts *structs.CSIMountOptions 159 160 if request != nil { 161 req = &structs.CSIMountOptions{ 162 FSType: request.FSType, 163 MountFlags: request.MountFlags, 164 } 165 } 166 167 if volume == nil { 168 opts = req 169 } else { 170 opts = &structs.CSIMountOptions{ 171 FSType: volume.FSType, 172 MountFlags: volume.MountFlags, 173 } 174 opts.Merge(req) 175 } 176 177 if opts == nil { 178 return "<none>" 179 } 180 181 var out string 182 if opts.FSType != "" { 183 out = fmt.Sprintf("fs_type: %s", opts.FSType) 184 } 185 186 if len(opts.MountFlags) > 0 { 187 out = fmt.Sprintf("%s flags: %s", out, strings.Join(opts.MountFlags, ", ")) 188 } 189 190 return out 191 }