// Start the task specified, on the host specified. First runs any necessary // preparation on the remote machine, then kicks off the agent process on the // machine. // Returns an error if any step along the way fails. func (agbh *AgentHostGateway) RunTaskOnHost(settings *evergreen.Settings, taskToRun task.Task, hostObj host.Host) (string, error) { // get the host's SSH options cloudHost, err := providers.GetCloudHost(&hostObj, settings) if err != nil { return "", fmt.Errorf("Failed to get cloud host for %v: %v", hostObj.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return "", fmt.Errorf("Error getting ssh options for host %v: %v", hostObj.Id, err) } // prep the remote host evergreen.Logger.Logf(slogger.INFO, "Prepping remote host %v...", hostObj.Id) agentRevision, err := agbh.prepRemoteHost(hostObj, sshOptions) if err != nil { return "", fmt.Errorf("error prepping remote host %v: %v", hostObj.Id, err) } evergreen.Logger.Logf(slogger.INFO, "Prepping host %v finished successfully", hostObj.Id) // start the agent on the remote machine evergreen.Logger.Logf(slogger.INFO, "Starting agent on host %v for task %v...", hostObj.Id, taskToRun.Id) err = startAgentOnRemote(settings.ApiUrl, &taskToRun, &hostObj, sshOptions) if err != nil { return "", err } evergreen.Logger.Logf(slogger.INFO, "Agent successfully started for task %v", taskToRun.Id) return agentRevision, nil }
// setupHost runs the specified setup script for an individual host. Returns // the output from running the script remotely, as well as any error that // occurs. If the script exits with a non-zero exit code, the error will be non-nil. func (init *HostInit) setupHost(targetHost *host.Host) (string, error) { // fetch the appropriate cloud provider for the host cloudMgr, err := providers.GetCloudManager(targetHost.Provider, init.Settings) if err != nil { return "", fmt.Errorf("failed to get cloud manager for host %v with provider %v: %v", targetHost.Id, targetHost.Provider, err) } // mark the host as initializing if err := targetHost.SetInitializing(); err != nil { if err == mgo.ErrNotFound { return "", ErrHostAlreadyInitializing } else { return "", fmt.Errorf("database error: %v", err) } } /* TESTING ONLY setupDebugSSHTunnel(path_to_ssh_key, targetHost.User, targetHost.Host) */ // run the function scheduled for when the host is up err = cloudMgr.OnUp(targetHost) if err != nil { // if this fails it is probably due to an API hiccup, so we keep going. evergreen.Logger.Logf(slogger.WARN, "OnUp callback failed for host '%v': '%v'", targetHost.Id, err) } cloudHost, err := providers.GetCloudHost(targetHost, init.Settings) if err != nil { return "", fmt.Errorf("failed to get cloud host for %v: %v", targetHost.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return "", fmt.Errorf("error getting ssh options for host %v: %v", targetHost.Id, err) } if targetHost.Distro.Teardown != "" { err = init.copyScript(targetHost, teardownScriptName, targetHost.Distro.Teardown) if err != nil { return "", fmt.Errorf("error copying script %v to host %v: %v", teardownScriptName, targetHost.Id, err) } } if targetHost.Distro.Setup != "" { err = init.copyScript(targetHost, setupScriptName, targetHost.Distro.Setup) if err != nil { return "", fmt.Errorf("error copying script %v to host %v: %v", setupScriptName, targetHost.Id, err) } logs, err := hostutil.RunRemoteScript(targetHost, setupScriptName, sshOptions) if err != nil { return logs, fmt.Errorf("error running setup script over ssh: %v", err) } return logs, nil } return "", nil }
// helper to terminate a single host func terminateHost(host *host.Host, settings *evergreen.Settings) error { // convert the host to a cloud host cloudHost, err := providers.GetCloudHost(host, settings) if err != nil { return fmt.Errorf("error getting cloud host for %v: %v", host.Id, err) } // run teardown script if we have one, sending notifications if things go awry if host.Distro.Teardown != "" && host.Provisioned { evergreen.Logger.Logf(slogger.ERROR, "Running teardown script for host %v", host.Id) if err := runHostTeardown(host, cloudHost); err != nil { evergreen.Logger.Logf(slogger.ERROR, "Error running teardown script for %v: %v", host.Id, err) subj := fmt.Sprintf("%v Error running teardown for host %v", notify.TeardownFailurePreface, host.Id) if err := notify.NotifyAdmins(subj, err.Error(), settings); err != nil { evergreen.Logger.Errorf(slogger.ERROR, "Error sending email: %v", err) } } } // terminate the instance if err := cloudHost.TerminateInstance(); err != nil { return fmt.Errorf("error terminating host %v: %v", host.Id, err) } return nil }
// copyScript writes a given script as file "name" to the target host. This works // by creating a local copy of the script on the runner's machine, scping it over // then removing the local copy. func (init *HostInit) copyScript(target *host.Host, name, script string) error { // parse the hostname into the user, host and port hostInfo, err := util.ParseSSHInfo(target.Host) if err != nil { return err } user := target.Distro.User if hostInfo.User != "" { user = hostInfo.User } // create a temp file for the script file, err := ioutil.TempFile("", name) if err != nil { return fmt.Errorf("error creating temporary script file: %v", err) } defer func() { file.Close() os.Remove(file.Name()) }() expanded, err := init.expandScript(script) if err != nil { return fmt.Errorf("error expanding script for host %v: %v", target.Id, err) } if _, err := io.WriteString(file, expanded); err != nil { return fmt.Errorf("error writing local script: %v", err) } cloudHost, err := providers.GetCloudHost(target, init.Settings) if err != nil { return fmt.Errorf("failed to get cloud host for %v: %v", target.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return fmt.Errorf("error getting ssh options for host %v: %v", target.Id, err) } var scpCmdStderr bytes.Buffer scpCmd := &command.ScpCommand{ Source: file.Name(), Dest: name, Stdout: &scpCmdStderr, Stderr: &scpCmdStderr, RemoteHostName: hostInfo.Hostname, User: user, Options: append([]string{"-P", hostInfo.Port}, sshOptions...), } err = util.RunFunctionWithTimeout(scpCmd.Run, SCPTimeout) if err != nil { if err == util.ErrTimedOut { scpCmd.Stop() return fmt.Errorf("scp-ing script timed out") } return fmt.Errorf("error (%v) copying script to remote machine: %v", err, scpCmdStderr.String()) } return nil }
// IsHostReady returns whether or not the specified host is ready for its setup script // to be run. func (init *HostInit) IsHostReady(host *host.Host) (bool, error) { // fetch the appropriate cloud provider for the host cloudMgr, err := providers.GetCloudManager(host.Distro.Provider, init.Settings) if err != nil { return false, fmt.Errorf("failed to get cloud manager for provider %v: %v", host.Distro.Provider, err) } // ask for the instance's status hostStatus, err := cloudMgr.GetInstanceStatus(host) if err != nil { return false, fmt.Errorf("error checking instance status of host %v: %v", host.Id, err) } // if the host isn't up yet, we can't do anything if hostStatus != cloud.StatusRunning { return false, nil } // set the host's dns name, if it is not set if host.Host == "" { // get the DNS name for the host hostDNS, err := cloudMgr.GetDNSName(host) if err != nil { return false, fmt.Errorf("error checking DNS name for host %v: %v", host.Id, err) } // sanity check for the host DNS name if hostDNS == "" { return false, fmt.Errorf("instance %v is running but not returning a DNS name", host.Id) } // update the host's DNS name if err := host.SetDNSName(hostDNS); err != nil { return false, fmt.Errorf("error setting DNS name for host %v: %v", host.Id, err) } } // check if the host is reachable via SSH cloudHost, err := providers.GetCloudHost(host, init.Settings) if err != nil { return false, fmt.Errorf("failed to get cloud host for %v: %v", host.Id, err) } reachable, err := cloudHost.IsSSHReachable() if err != nil { return false, fmt.Errorf("error checking if host %v is reachable: %v", host.Id, err) } // at this point, we can run the setup if the host is reachable return reachable, nil }
// check reachability for a single host, and take any necessary action func checkHostReachability(host host.Host, settings *evergreen.Settings) error { evergreen.Logger.Logf(slogger.INFO, "Running reachability check for host %v...", host.Id) // get a cloud version of the host cloudHost, err := providers.GetCloudHost(&host, settings) if err != nil { return fmt.Errorf("error getting cloud host for host %v: %v", host.Id, err) } // get the cloud status for the host cloudStatus, err := cloudHost.GetInstanceStatus() if err != nil { return fmt.Errorf("error getting cloud status for host %v: %v", host.Id, err) } // take different action, depending on how the cloud provider reports the host's status switch cloudStatus { case cloud.StatusRunning: // check if the host is reachable via SSH reachable, err := cloudHost.IsSSHReachable() if err != nil { return fmt.Errorf("error checking ssh reachability for host %v: %v", host.Id, err) } // log the status update if the reachability of the host is changing if host.Status == evergreen.HostUnreachable && reachable { evergreen.Logger.Logf(slogger.INFO, "Setting host %v as reachable", host.Id) } else if host.Status != evergreen.HostUnreachable && !reachable { evergreen.Logger.Logf(slogger.INFO, "Setting host %v as unreachable", host.Id) } // mark the host appropriately if err := host.UpdateReachability(reachable); err != nil { return fmt.Errorf("error updating reachability for host %v: %v", host.Id, err) } case cloud.StatusTerminated: evergreen.Logger.Logf(slogger.INFO, "Host %v terminated externally; updating db status to terminated", host.Id) // the instance was terminated from outside our control if err := host.SetTerminated(); err != nil { return fmt.Errorf("error setting host %v terminated: %v", host.Id, err) } } // success return nil }
// helper to terminate a single host func terminateHost(host *host.Host, settings *evergreen.Settings) error { // convert the host to a cloud host cloudHost, err := providers.GetCloudHost(host, settings) if err != nil { return fmt.Errorf("error getting cloud host for %v: %v", host.Id, err) } // terminate the instance if err := cloudHost.TerminateInstance(); err != nil { return fmt.Errorf("error terminating host %v: %v", host.Id, err) } return nil }
func (as *APIServer) modifyHost(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) instanceId := vars["instance_id"] hostAction := r.FormValue("action") host, err := host.FindOne(host.ById(instanceId)) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if host == nil { http.Error(w, "not found", http.StatusNotFound) return } user := GetUser(r) if user == nil || user.Id != host.StartedBy { message := fmt.Sprintf("Only %v is authorized to terminate this host", host.StartedBy) http.Error(w, message, http.StatusUnauthorized) return } switch hostAction { case "terminate": if host.Status == evergreen.HostTerminated { message := fmt.Sprintf("Host %v is already terminated", host.Id) http.Error(w, message, http.StatusBadRequest) return } cloudHost, err := providers.GetCloudHost(host, &as.Settings) if err != nil { as.LoggedError(w, r, http.StatusInternalServerError, err) return } if err = cloudHost.TerminateInstance(); err != nil { as.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Failed to terminate spawn host: %v", err)) return } as.WriteJSON(w, http.StatusOK, spawnResponse{HostInfo: *host}) default: http.Error(w, fmt.Sprintf("Unrecognized action %v", hostAction), http.StatusBadRequest) } }
func (init *HostInit) fetchRemoteTaskData(taskId, cliPath, confPath string, target *host.Host) error { hostSSHInfo, err := util.ParseSSHInfo(target.Host) if err != nil { return fmt.Errorf("error parsing ssh info %v: %v", target.Host, err) } cloudHost, err := providers.GetCloudHost(target, init.Settings) if err != nil { return fmt.Errorf("Failed to get cloud host for %v: %v", target.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return fmt.Errorf("Error getting ssh options for host %v: %v", target.Id, err) } sshOptions = append(sshOptions, "-o", "UserKnownHostsFile=/dev/null") /* TESTING ONLY setupDebugSSHTunnel(path_to_ssh_keys, target.User, hostSSHInfo.Hostname) */ // When testing, use this writer to force a copy of the output to be written to standard out so // that remote command failures also show up in server log output. //cmdOutput := io.MultiWriter(&util.CappedWriter{&bytes.Buffer{}, 1024 * 1024}, os.Stdout) cmdOutput := &util.CappedWriter{&bytes.Buffer{}, 1024 * 1024} makeShellCmd := &command.RemoteCommand{ CmdString: fmt.Sprintf("%s -c '%s' fetch -t %s --source --artifacts --dir='%s'", cliPath, confPath, taskId, target.Distro.WorkDir), Stdout: cmdOutput, Stderr: cmdOutput, RemoteHostName: hostSSHInfo.Hostname, User: target.User, Options: append([]string{"-p", hostSSHInfo.Port}, sshOptions...), } // run the make shell command with a timeout err = util.RunFunctionWithTimeout(makeShellCmd.Run, 15*time.Minute) return err }
// constructPwdUpdateCommand returns a RemoteCommand struct used to // set the RDP password on a remote windows machine. func constructPwdUpdateCommand(settings *evergreen.Settings, hostObj *host.Host, password string) (*command.RemoteCommand, error) { cloudHost, err := providers.GetCloudHost(hostObj, settings) if err != nil { return nil, err } hostInfo, err := util.ParseSSHInfo(hostObj.Host) if err != nil { return nil, err } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return nil, err } outputLineHandler := evergreen.NewInfoLoggingWriter(&evergreen.Logger) errorLineHandler := evergreen.NewErrorLoggingWriter(&evergreen.Logger) updatePwdCmd := fmt.Sprintf("net user %v %v && sc config "+ "sshd obj= '.\\%v' password= \"%v\"", hostObj.User, password, hostObj.User, password) // construct the required termination command remoteCommand := &command.RemoteCommand{ CmdString: updatePwdCmd, Stdout: outputLineHandler, Stderr: errorLineHandler, LoggingDisabled: true, RemoteHostName: hostInfo.Hostname, User: hostObj.User, Options: append([]string{"-p", hostInfo.Port}, sshOptions...), Background: false, } return remoteCommand, nil }
// Start the task specified, on the host specified. First runs any necessary // preparation on the remote machine, then kicks off the agent process on the // machine. // Returns an error if any step along the way fails. func (self *AgentBasedHostGateway) RunTaskOnHost(settings *evergreen.Settings, taskToRun model.Task, hostObj host.Host) (string, error) { // cache mci home evgHome := evergreen.FindEvergreenHome() // get the host's SSH options cloudHost, err := providers.GetCloudHost(&hostObj, settings) if err != nil { return "", fmt.Errorf("Failed to get cloud host for %v: %v", hostObj.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return "", fmt.Errorf("Error getting ssh options for host %v: %v", hostObj.Id, err) } // prep the remote host evergreen.Logger.Logf(slogger.INFO, "Prepping remote host %v...", hostObj.Id) agentRevision, err := self.prepRemoteHost(settings, hostObj, sshOptions, evgHome) if err != nil { return "", fmt.Errorf("error prepping remote host %v: %v", hostObj.Id, err) } evergreen.Logger.Logf(slogger.INFO, "Prepping host finished successfully") // start the agent on the remote machine evergreen.Logger.Logf(slogger.INFO, "Starting agent on host %v for task %v...", hostObj.Id, taskToRun.Id) err = self.startAgentOnRemote(settings, &taskToRun, &hostObj, sshOptions) if err != nil { return "", fmt.Errorf("error starting agent on %v for task %v: %v", hostObj.Id, taskToRun.Id, err) } evergreen.Logger.Logf(slogger.INFO, "Agent successfully started") return agentRevision, nil }
// setupHost runs the specified setup script for an individual host. Returns // the output from running the script remotely, as well as any error that // occurs. If the script exits with a non-zero exit code, the error will be non-nil. func (init *HostInit) setupHost(targetHost *host.Host) ([]byte, error) { // fetch the appropriate cloud provider for the host cloudMgr, err := providers.GetCloudManager(targetHost.Provider, init.Settings) if err != nil { return nil, fmt.Errorf("failed to get cloud manager for host %v with provider %v: %v", targetHost.Id, targetHost.Provider, err) } // mark the host as initializing if err := targetHost.SetInitializing(); err != nil { if err == mgo.ErrNotFound { return nil, ErrHostAlreadyInitializing } else { return nil, fmt.Errorf("database error: %v", err) } } // run the function scheduled for when the host is up err = cloudMgr.OnUp(targetHost) if err != nil { // if this fails it is probably due to an API hiccup, so we keep going. evergreen.Logger.Logf(slogger.WARN, "OnUp callback failed for host '%v': '%v'", targetHost.Id, err) } // run the remote setup script as sudo, if appropriate sudoStr := "" if targetHost.Distro.SetupAsSudo { sudoStr = "sudo " } // parse the hostname into the user, host and port hostInfo, err := util.ParseSSHInfo(targetHost.Host) if err != nil { return nil, err } user := targetHost.Distro.User if hostInfo.User != "" { user = hostInfo.User } // create a temp file for the setup script fileName := "setup.sh" file, err := ioutil.TempFile("", fileName) if err != nil { return nil, fmt.Errorf("error creating setup script: %v", err) } defer func() { file.Close() os.Remove(file.Name()) }() // build the setup script setup, err := init.buildSetupScript(targetHost) if err != nil { return nil, fmt.Errorf("error building setup script for host %v: %v", targetHost.Id, err) } // write the setup script to the file if _, err := file.Write([]byte(setup)); err != nil { return nil, fmt.Errorf("error writing remote setup script: %v", err) } cloudHost, err := providers.GetCloudHost(targetHost, init.Settings) if err != nil { return nil, fmt.Errorf("Failed to get cloud host for %v: %v", targetHost.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return nil, fmt.Errorf("Error getting ssh options for host %v: %v", targetHost.Id, err) } // copy setup script over to the remote machine var scpSetupCmdStderr bytes.Buffer scpSetupCmd := &command.ScpCommand{ Source: file.Name(), Dest: fileName, Stdout: &scpSetupCmdStderr, Stderr: &scpSetupCmdStderr, RemoteHostName: hostInfo.Hostname, User: user, Options: append([]string{"-P", hostInfo.Port}, sshOptions...), } // run the command to scp the setup script with a timeout err = util.RunFunctionWithTimeout( scpSetupCmd.Run, SCPTimeout, ) if err != nil { if err == util.ErrTimedOut { scpSetupCmd.Stop() return nil, fmt.Errorf("scp-ing setup script timed out") } return nil, fmt.Errorf("error (%v) copying setup script to remote "+ "machine: %v", err, scpSetupCmdStderr.String()) } // run command to ssh into remote machine and execute setup script var sshSetupCmdStderr bytes.Buffer runSetupCmd := &command.RemoteCommand{ CmdString: sudoStr + "sh " + fileName, Stdout: &sshSetupCmdStderr, Stderr: &sshSetupCmdStderr, RemoteHostName: hostInfo.Hostname, User: user, Options: []string{"-p", hostInfo.Port}, Background: false, } // only force creation of a tty if sudo if targetHost.Distro.SetupAsSudo { runSetupCmd.Options = []string{"-t", "-t", "-p", hostInfo.Port} } runSetupCmd.Options = append(runSetupCmd.Options, sshOptions...) // run the ssh command with given timeout err = util.RunFunctionWithTimeout( runSetupCmd.Run, time.Duration(SSHTimeoutSeconds)*time.Second, ) return sshSetupCmdStderr.Bytes(), err }
// LoadClient places the evergreen command line client on the host, places a copy of the user's // settings onto the host, and makes the binary appear in the $PATH when the user logs in. // If successful, returns an instance of LoadClientResult which contains the paths where the // binary and config file were written to. func (init *HostInit) LoadClient(target *host.Host) (*LoadClientResult, error) { // Make sure we have the binary we want to upload - if it hasn't been built for the given // architecture, fail early cliBinaryPath, err := LocateCLIBinary(init.Settings, target.Distro.Arch) if err != nil { return nil, fmt.Errorf("couldn't locate CLI binary for upload: %v", err) } if target.ProvisionOptions == nil { return nil, fmt.Errorf("ProvisionOptions is nil") } if target.ProvisionOptions.OwnerId == "" { return nil, fmt.Errorf("OwnerId not set") } // get the information about the owner of the host owner, err := user.FindOne(user.ById(target.ProvisionOptions.OwnerId)) if err != nil { return nil, fmt.Errorf("couldn't fetch owner %v for host: %v", target.ProvisionOptions.OwnerId, err) } // 1. mkdir the destination directory on the host, // and modify ~/.profile so the target binary will be on the $PATH targetDir := "cli_bin" hostSSHInfo, err := util.ParseSSHInfo(target.Host) if err != nil { return nil, fmt.Errorf("error parsing ssh info %v: %v", target.Host, err) } cloudHost, err := providers.GetCloudHost(target, init.Settings) if err != nil { return nil, fmt.Errorf("Failed to get cloud host for %v: %v", target.Id, err) } sshOptions, err := cloudHost.GetSSHOptions() if err != nil { return nil, fmt.Errorf("Error getting ssh options for host %v: %v", target.Id, err) } sshOptions = append(sshOptions, "-o", "UserKnownHostsFile=/dev/null") mkdirOutput := &util.CappedWriter{&bytes.Buffer{}, 1024 * 1024} // Create the directory for the binary to be uploaded into. // Also, make a best effort to add the binary's location to $PATH upon login. If we can't do // this successfully, the command will still succeed, it just means that the user will have to // use an absolute path (or manually set $PATH in their shell) to execute it. makeShellCmd := &command.RemoteCommand{ CmdString: fmt.Sprintf("mkdir -m 777 -p ~/%s && (echo 'PATH=$PATH:~/%s' >> ~/.profile || true; echo 'PATH=$PATH:~/%s' >> ~/.bash_profile || true)", targetDir, targetDir, targetDir), Stdout: mkdirOutput, Stderr: mkdirOutput, RemoteHostName: hostSSHInfo.Hostname, User: target.User, Options: append([]string{"-p", hostSSHInfo.Port}, sshOptions...), } scpOut := &util.CappedWriter{&bytes.Buffer{}, 1024 * 1024} // run the make shell command with a timeout err = util.RunFunctionWithTimeout(makeShellCmd.Run, 30*time.Second) if err != nil { return nil, fmt.Errorf("error running setup command for cli, %v: '%v'", mkdirOutput.Buffer.String(), err) } // place the binary into the directory scpSetupCmd := &command.ScpCommand{ Source: cliBinaryPath, Dest: fmt.Sprintf("~/%s/evergreen", targetDir), Stdout: scpOut, Stderr: scpOut, RemoteHostName: hostSSHInfo.Hostname, User: target.User, Options: append([]string{"-P", hostSSHInfo.Port}, sshOptions...), } // run the command to scp the setup script with a timeout err = util.RunFunctionWithTimeout(scpSetupCmd.Run, 3*time.Minute) if err != nil { return nil, fmt.Errorf("error running SCP command for cli, %v: '%v'", scpOut.Buffer.String(), err) } // 4. Write a settings file for the user that owns the host, and scp it to the directory outputStruct := model.CLISettings{ User: owner.Id, APIKey: owner.APIKey, APIServerHost: init.Settings.ApiUrl + "/api", UIServerHost: init.Settings.Ui.Url, } outputJSON, err := json.Marshal(outputStruct) if err != nil { return nil, err } tempFileName, err := util.WriteTempFile("", outputJSON) if err != nil { return nil, err } defer os.Remove(tempFileName) scpYmlCommand := &command.ScpCommand{ Source: tempFileName, Dest: fmt.Sprintf("~/%s/.evergreen.yml", targetDir), Stdout: scpOut, Stderr: scpOut, RemoteHostName: hostSSHInfo.Hostname, User: target.User, Options: append([]string{"-P", hostSSHInfo.Port}, sshOptions...), } err = util.RunFunctionWithTimeout(scpYmlCommand.Run, 30*time.Second) if err != nil { return nil, fmt.Errorf("error running SCP command for evergreen.yml, %v: '%v'", scpOut.Buffer.String(), err) } return &LoadClientResult{ BinaryPath: fmt.Sprintf("~/%s/evergreen", targetDir), ConfigPath: fmt.Sprintf("~/%s/.evergreen.yml", targetDir), }, nil }
func (uis *UIServer) modifySpawnHost(w http.ResponseWriter, r *http.Request) { _ = MustHaveUser(r) updateParams := struct { Action string `json:"action"` HostId string `json:"host_id"` RDPPwd string `json:"rdp_pwd"` AddHours string `json:"add_hours"` }{} if err := util.ReadJSONInto(r.Body, &updateParams); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } hostId := updateParams.HostId host, err := host.FindOne(host.ById(hostId)) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("error finding host with id %v: %v", hostId, err)) return } if host == nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("No host with id %v found", hostId)) return } // determine what action needs to be taken switch updateParams.Action { case HostTerminate: if host.Status == evergreen.HostTerminated { uis.WriteJSON(w, http.StatusBadRequest, fmt.Sprintf("Host %v is already terminated", host.Id)) return } cloudHost, err := providers.GetCloudHost(host, &uis.Settings) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } if err = cloudHost.TerminateInstance(); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } uis.WriteJSON(w, http.StatusOK, "host terminated") return case HostPasswordUpdate: pwdUpdateCmd, err := constructPwdUpdateCommand(&uis.Settings, host, updateParams.RDPPwd) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error constructing host RDP password: %v", err)) return } // update RDP and sshd password if err = pwdUpdateCmd.Run(); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error updating host RDP password: %v", err)) return } PushFlash(uis.CookieStore, r, w, NewSuccessFlash("Host RDP password successfully updated.")) uis.WriteJSON(w, http.StatusOK, "Successfully updated host password") case HostExpirationExtension: addtHours, err := strconv.Atoi(updateParams.AddHours) if err != nil { http.Error(w, "bad hours param", http.StatusBadRequest) return } // ensure this request is valid addtHourDuration := time.Duration(addtHours) * time.Hour futureExpiration := host.ExpirationTime.Add(addtHourDuration) expirationExtensionDuration := futureExpiration.Sub(time.Now()).Hours() if expirationExtensionDuration > MaxExpirationDurationHours { http.Error(w, fmt.Sprintf("Can not extend %v expiration by %v hours. "+ "Maximum extension is limited to %v hours", hostId, int(expirationExtensionDuration), MaxExpirationDurationHours), http.StatusBadRequest) return } if err = host.SetExpirationTime(futureExpiration); err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error extending host expiration time: %v", err)) return } PushFlash(uis.CookieStore, r, w, NewSuccessFlash(fmt.Sprintf("Host expiration "+ "extension successful; %v will expire on %v", hostId, futureExpiration.Format(time.RFC850)))) uis.WriteJSON(w, http.StatusOK, "Successfully extended host expiration time") return default: http.Error(w, fmt.Sprintf("Unrecognized action: %v", updateParams.Action), http.StatusBadRequest) return } }