github.com/cloudfoundry-incubator/stembuild@v0.0.0-20211223202937-5b61d62226c6/iaas_cli/iaas_clients/vcenter_client.go (about) 1 package iaas_clients 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "net/url" 8 "os" 9 "regexp" 10 "strings" 11 12 "github.com/cloudfoundry-incubator/stembuild/iaas_cli" 13 ) 14 15 type VcenterClient struct { 16 Url string 17 credentialUrl string 18 redactedUrl string 19 caCertFile string 20 Runner iaas_cli.CliRunner 21 } 22 23 func NewVcenterClient(username string, password string, u string, caCertFile string, runner iaas_cli.CliRunner) *VcenterClient { 24 25 encodedUser := url.QueryEscape(username) 26 encodedPassword := url.QueryEscape(password) 27 urlWithCredentials := fmt.Sprintf("%s:%s@%s", encodedUser, encodedPassword, u) 28 urlWithRedactedPassword := fmt.Sprintf("%s:REDACTED@%s", encodedUser, u) 29 return &VcenterClient{Url: u, credentialUrl: urlWithCredentials, redactedUrl: urlWithRedactedPassword, caCertFile: caCertFile, Runner: runner} 30 } 31 32 func (c *VcenterClient) ValidateUrl() error { 33 args := []string{"about", "-u", c.Url} 34 errMsg := fmt.Sprintf("vcenter_client - unable to validate url: %s", c.Url) 35 if c.caCertFile != "" { 36 args = append(args, fmt.Sprintf("-tls-ca-certs=%s", c.caCertFile)) 37 errMsg = fmt.Sprintf("vcenter_client - invalid ca certs or url: %s", c.Url) 38 } 39 errCode := c.Runner.Run(args) 40 if errCode != 0 { 41 return errors.New(errMsg) 42 } 43 return nil 44 45 } 46 47 func (c *VcenterClient) ValidateCredentials() error { 48 args := c.buildGovcCommand("about") 49 errCode := c.Runner.Run(args) 50 if errCode != 0 { 51 return errors.New(fmt.Sprintf("vcenter_client - invalid credentials for: %s", c.redactedUrl)) 52 } 53 54 return nil 55 } 56 57 func (c *VcenterClient) FindVM(vmInventoryPath string) error { 58 args := c.buildGovcCommand("find", "-maxdepth=0", vmInventoryPath) 59 errCode := c.Runner.Run(args) 60 if errCode != 0 { 61 return errors.New(fmt.Sprintf("vcenter_client - unable to find VM: %s. Ensure your inventory path is formatted properly and includes \"vm\" in its path, example: /my-datacenter/vm/my-folder/my-vm-name", vmInventoryPath)) 62 } 63 64 return nil 65 } 66 67 func (c *VcenterClient) ListDevices(vmInventoryPath string) ([]string, error) { 68 args := c.buildGovcCommand("device.ls", "-vm", vmInventoryPath) 69 o, exitCode, err := c.Runner.RunWithOutput(args) 70 71 if exitCode != 0 { 72 return []string{}, fmt.Errorf("vcenter_client - failed to list devices in vCenter, govc exit code %d", exitCode) 73 } 74 75 if err != nil { 76 return []string{}, fmt.Errorf("vcenter_client - failed to parse list of devices. Err: %s", err) 77 } 78 79 entries := strings.Split(o, "\n") 80 devices := []string{} 81 r, _ := regexp.Compile(`\S+`) 82 for _, entry := range entries { 83 if entry != "" { 84 devices = append(devices, r.FindString(entry)) 85 } 86 } 87 return devices, nil 88 } 89 func (c *VcenterClient) RemoveDevice(vmInventoryPath string, deviceName string) error { 90 args := c.buildGovcCommand("device.remove", "-vm", vmInventoryPath, deviceName) 91 errCode := c.Runner.Run(args) 92 if errCode != 0 { 93 return fmt.Errorf("vcenter_client - %s could not be removed", deviceName) 94 } 95 return nil 96 } 97 98 func (c *VcenterClient) EjectCDRom(vmInventoryPath string, deviceName string) error { 99 100 args := c.buildGovcCommand("device.cdrom.eject", "-vm", vmInventoryPath, "-device", deviceName) 101 errCode := c.Runner.Run(args) 102 if errCode != 0 { 103 return fmt.Errorf("vcenter_client - %s could not be ejected", deviceName) 104 } 105 return nil 106 } 107 108 func (c *VcenterClient) ExportVM(vmInventoryPath string, destination string) error { 109 _, err := os.Stat(destination) 110 if err != nil { 111 return errors.New(fmt.Sprintf("vcenter_client - provided destination directory: %s does not exist", destination)) 112 } 113 args := c.buildGovcCommand("export.ovf", "-sha", "1", "-vm", vmInventoryPath, destination) 114 errCode := c.Runner.Run(args) 115 if errCode != 0 { 116 return errors.New(fmt.Sprintf("vcenter_client - %s could not be exported", vmInventoryPath)) 117 } 118 return nil 119 } 120 121 func (c *VcenterClient) UploadArtifact(vmInventoryPath, artifact, destination, username, password string) error { 122 vmCredentials := fmt.Sprintf("%s:%s", username, password) 123 args := c.buildGovcCommand("guest.upload", "-f", "-l", vmCredentials, "-vm", vmInventoryPath, artifact, destination) 124 errCode := c.Runner.Run(args) 125 if errCode != 0 { 126 return fmt.Errorf("vcenter_client - %s could not be uploaded", artifact) 127 } 128 return nil 129 } 130 131 func (c *VcenterClient) MakeDirectory(vmInventoryPath, path, username, password string) error { 132 vmCredentials := fmt.Sprintf("%s:%s", username, password) 133 134 args := c.buildGovcCommand("guest.mkdir", "-l", vmCredentials, "-vm", vmInventoryPath, "-p", path) 135 errCode := c.Runner.Run(args) 136 if errCode != 0 { 137 return fmt.Errorf("vcenter_client - directory `%s` could not be created", path) 138 } 139 return nil 140 } 141 142 func (c *VcenterClient) Start(vmInventoryPath, username, password, command string, args ...string) (string, error) { 143 vmCredentials := fmt.Sprintf("%s:%s", username, password) 144 145 cmdArgs := c.buildGovcCommand(append([]string{"guest.start", "-l", vmCredentials, "-vm", vmInventoryPath, command}, args...)...) 146 pid, exitCode, err := c.Runner.RunWithOutput(cmdArgs) 147 if err != nil { 148 return "", fmt.Errorf("vcenter_client - failed to run '%s': %s", command, err) 149 } 150 if exitCode != 0 { 151 return "", fmt.Errorf("vcenter_client - '%s' returned exit code: %d", command, exitCode) 152 } 153 // We trim this suffix since govc outputs the pid with an '\n' in the output 154 return strings.TrimSuffix(pid, "\n"), nil 155 } 156 157 type govcPS struct { 158 ProcessInfo []struct { 159 Name string 160 Pid int 161 Owner string 162 CmdLine string 163 StartTime string 164 EndTime string 165 ExitCode int 166 } 167 } 168 169 func (c *VcenterClient) WaitForExit(vmInventoryPath, username, password, pid string) (int, error) { 170 vmCredentials := fmt.Sprintf("%s:%s", username, password) 171 args := c.buildGovcCommand("guest.ps", "-l", vmCredentials, "-vm", vmInventoryPath, "-p", pid, "-X", "-json") 172 output, exitCode, err := c.Runner.RunWithOutput(args) 173 if err != nil { 174 return 0, fmt.Errorf("vcenter_client - failed to fetch exit code for PID %s: %s", pid, err) 175 } 176 if exitCode != 0 { 177 return 0, fmt.Errorf("vcenter_client - fetching PID %s returned with exit code: %d", pid, exitCode) 178 } 179 180 ps := govcPS{} 181 err = json.Unmarshal([]byte(output), &ps) 182 if err != nil { 183 return 0, fmt.Errorf("vcenter_client - received bad JSON output for PID %s: %s", pid, output) 184 } 185 if len(ps.ProcessInfo) != 1 { 186 return 0, fmt.Errorf("vcenter_client - couldn't get exit code for PID %s", pid) 187 } 188 189 return ps.ProcessInfo[0].ExitCode, nil 190 } 191 192 func (c *VcenterClient) buildGovcCommand(args ...string) []string { 193 commonArgs := []string{"-u", c.credentialUrl} 194 if c.caCertFile != "" { 195 commonArgs = append(commonArgs, fmt.Sprintf("-tls-ca-certs=%s", c.caCertFile)) 196 } 197 args = append(args[:1], append(commonArgs, args[1:]...)...) 198 return args 199 } 200 201 func (c *VcenterClient) IsPoweredOff(vmInventoryPath string) (bool, error) { 202 args := c.buildGovcCommand("vm.info", vmInventoryPath) 203 out, exitCode, err := c.Runner.RunWithOutput(args) 204 if exitCode != 0 { 205 return false, fmt.Errorf("vcenter_client - failed to get vm info, govc exit code: %d", exitCode) 206 } 207 if err != nil { 208 return false, fmt.Errorf("vcenter_client - failed to determine vm power state: %s", err) 209 } 210 211 if strings.Contains(out, "poweredOff") { 212 return true, nil 213 } 214 215 return false, nil 216 }