func (self *NodeNameHandler) Prepare(config *util.Config) { if !config.HasCIConnection() { return } if foundNode, err := self.verifyNodeName(config); err == nil { if !foundNode { if config.CreateClientIfMissing { if err := self.createNode(config); err == nil { util.GOut("naming", "Created node '%s' in Jenkins.", config.ClientName) } else { util.GOut("naming", "ERROR: Failed to create node '%s' in Jenkins. Cause: %v", config.ClientName, err) } foundNode, _ = self.verifyNodeName(config) } else { util.GOut("naming", "Will not attempt to auto generate node '%s' in Jenkins. Enable this with '-create' or within the configuration.", config.ClientName) } } if foundNode { util.GOut("naming", "Found client node name in Jenkins, using '%v'.", config.ClientName) } else { util.GOut("naming", "WARN: Client node name '%v' was %s in Jenkins. Likely the next operations will fail.", config.ClientName, "NOT FOUND") } } else { util.GOut("nameing", "ERROR: Failed to verify the client node name in Jenkins. Cause: %v", err) } }
func (self *JenkinsNodeMonitor) IsConfigAcceptable(config *util.Config) bool { if config.ClientMonitorStateOnServer && !config.HasCIConnection() { util.GOut("monitor", "No Jenkins URI defined. Cannot monitor this node within Jenkins.") return false } return true }
func (self *NodeNameHandler) verifyNodeName(config *util.Config) (bool, error) { clientName := config.ClientName if clientName == "" { if name, err := util.Hostname(); err == nil { clientName = name config.ClientName = name } } if nodes, err := GetAllRegisteredNodesInJenkins(config); err == nil { clientName = strings.ToLower(clientName) match, bestMatch := "", "" for _, computerName := range nodes.Names { name := strings.ToLower(computerName) if name == clientName { bestMatch, match = computerName, computerName } else if len(clientName) > 0 && strings.Index(name, clientName+".") == 0 { match = computerName } } if bestMatch != "" { config.ClientName = bestMatch } else if match != "" { config.ClientName = match } return bestMatch != "" || match != "", nil } else { return false, err } }
func (self *FullGCInvoker) IsConfigAcceptable(config *util.Config) bool { if config.ForceFullGC && !config.HasCIConnection() { util.GOut("gc", "WARN: No Jenkins URI defined. System.GC() cannot be called inside the Jenkins client.") return false } return true }
func (self *JenkinsClientDownloader) downloadJar(config *util.Config) error { util.GOut("DOWNLOAD", "Getting latest Jenkins client %v", (config.CIHostURI + "/" + ClientJarURL)) // Create the HTTP request. request, err := config.CIRequest("GET", ClientJarURL, nil) if err != nil { return err } if fi, err := os.Stat(ClientJarName); err == nil { request.Header.Add("If-Modified-Since", fi.ModTime().Format(http.TimeFormat)) } // Perform the HTTP request. var source io.ReadCloser sourceTime := time.Now() if response, err := config.CIClient().Do(request); err == nil { defer response.Body.Close() source = response.Body if response.StatusCode == 304 { util.GOut("DOWNLOAD", "Jenkins client is up-to-date, no need to download.") return nil } else if response.StatusCode != 200 { return fmt.Errorf("Failed downloading jenkins client. Cause: HTTP-%v %v", response.StatusCode, response.Status) } if value := response.Header.Get("Last-Modified"); value != "" { if time, err := http.ParseTime(value); err == nil { sourceTime = time } } } else { return fmt.Errorf("Failed downloading jenkins client. Connect failed. Cause: %v", err) } target, err := os.Create(ClientJarDownloadName) defer target.Close() if err != nil { return fmt.Errorf("Failed downloading jenkins client. Cannot create local file. Cause: %v", err) } if _, err = io.Copy(target, source); err == nil { target.Close() if err = os.Remove(ClientJarName); err == nil || os.IsNotExist(err) { if err = os.Rename(ClientJarDownloadName, ClientJarName); err == nil { os.Chtimes(ClientJarName, sourceTime, sourceTime) } } return err } else { return fmt.Errorf("Failed downloading jenkins client. Transfer failed. Cause: %v", err) } }
func (self *SSHTunnelEstablisher) IsConfigAcceptable(config *util.Config) bool { if config.CITunnelSSHEnabled && config.CITunnelSSHAddress == "" { util.GOut("ssh-tunnel", "WARN: SSH tunnel is enabled but SSH server address is empty.") return false } if config.CITunnelSSHAddress != "" && !config.HasCIConnection() { util.GOut("ssh-tunnel", "WARN: No Jenkins URI defined. SSH tunnel settings are not enough to connect to Jenkins.") return false } return true }
// Returns the names of all nodes that are registered in Jenkins. func GetAllRegisteredNodesInJenkins(config *util.Config) (*AllComputerNames, error) { response, err := config.CIGet(ComputersURI) if err == nil && response.StatusCode == 200 { defer response.Body.Close() names := new(AllComputerNames) err = xml.NewDecoder(response.Body).Decode(names) return names, err } else { if err == nil && response != nil { err = fmt.Errorf(response.Status) } return nil, err } }
// Returns the current offline and idle status of this Jenkins node from the Jenkins server. func GetJenkinsNodeStatus(config *util.Config) (*JenkinsNodeStatus, error) { if response, err := config.CIGet(fmt.Sprintf(NodeMonitoringURI, config.ClientName)); err == nil { defer response.Body.Close() if response.StatusCode == 200 { status := &JenkinsNodeStatus{} err = xml.NewDecoder(response.Body).Decode(status) return status, err } else { return nil, fmt.Errorf(response.Status) } } else { return nil, err } }
// Returns the current configuration of this Jenkins node from the Jenkins server. func GetJenkinsNodeConfig(config *util.Config) (*JenkinsNodeConfig, error) { if response, err := config.CIGet(fmt.Sprintf("/computer/%s/config.xml", config.ClientName)); err == nil { defer response.Body.Close() if response.StatusCode == 200 { config := &JenkinsNodeConfig{} err = xml.NewDecoder(response.Body).Decode(config) return config, err } else { return nil, fmt.Errorf(response.Status) } } else { return nil, err } }
func (self *ClientMode) IsConfigAcceptable(config *util.Config) bool { if !config.HasCIConnection() { util.GOut(self.Name(), "ERROR: No Jenkins URI defined. Cannot connect to the CI server.") return false } if config.SecretKey == "" && !self.isAuthCredentialsPassedViaCommandline(config) { if config.SecretKey = self.getSecretFromJenkins(config); config.SecretKey == "" { util.GOut(self.Name(), "ERROR: No secret key set for node %v and the attempt to fetch it from Jenkins failed.", config.ClientName) return false } } return true }
func (self *FullGCInvoker) invokeSystemGC(config *util.Config) { // curl -d "script=System.gc()" -X POST http://user:password@jenkins-host/ci/computer/%s/scriptText postBody := strings.NewReader(fmt.Sprintf(FullGCPostBody, url.QueryEscape(FullGCScript))) request, err := config.CIRequest("POST", fmt.Sprintf(FullGCURL, config.ClientName), postBody) if err == nil { if response, err := config.CIClient().Do(request); err == nil { response.Body.Close() if response.StatusCode != 200 { util.GOut("gc", "ERROR: Failed invoking full GC as node request in Jenkins failed with %s", response.Status) } } else { util.GOut("gc", "ERROR: Failed invoking full GC as Jenkins cannot be contacted. Cause: %v", err) } } }
func (self *JenkinsClientDownloader) Prepare(config *util.Config) { util.ClientJar, _ = filepath.Abs(ClientJarName) modes.RegisterModeListener(func(mode modes.ExecutableMode, nextStatus int32, config *util.Config) { if mode.Name() == "client" && nextStatus == modes.ModeStarting && config.HasCIConnection() { if err := self.downloadJar(config); err != nil { jar, e := os.Open(ClientJarName) defer jar.Close() if os.IsNotExist(e) { panic(fmt.Sprintf("No jenkins client: %s", err)) } else { util.GOut("DOWNLOAD", "%s", err) } } } }) }
// Gets the port that is used by the JNLP client to communicate with Jenkins. // Returns the port number as string and an error if fetching the port failed for any reason. func (self *SSHTunnelEstablisher) getJNLPListenerPort(config *util.Config) (port string, err error) { var response *http.Response port = "" if response, err = config.CIGet("/tcpSlaveAgentListener/"); err == nil { response.Body.Close() if response.StatusCode == 200 { if port = response.Header.Get("X-Jenkins-JNLP-Port"); port == "" { port = response.Header.Get("X-Hudson-JNLP-Port") } } if port == "" { err = fmt.Errorf("Jenkins did not provide the JNLP-Port, the reply was %v.", response.Status) } } return }
func (self *ClientMode) getSecretFromJenkins(config *util.Config) string { response, err := config.CIGet(fmt.Sprintf("computer/%s/", config.ClientName)) if err == nil { defer response.Body.Close() if response.StatusCode == 200 { var content []byte if content, err = ioutil.ReadAll(response.Body); err == nil { return self.extractSecret(content) } } else { util.GOut("client", "ERROR: Failed fetching secret key from Jenkins. Cause: %v", response.Status) } } if err != nil { util.GOut("client", "ERROR: Failed fetching secret key from Jenkins. Cause: %v", err) } return "" }
func (self *ClientMode) getCustomizedAgentJnlp(config *util.Config) []byte { response, err := config.CIGet(fmt.Sprintf("computer/%s/slave-agent.jnlp", config.ClientName)) if err == nil { defer response.Body.Close() if response.StatusCode == 200 { var content []byte if content, err = ioutil.ReadAll(response.Body); err == nil { return self.applyCustomJnlpArgs(config, content) } } else { util.GOut("client", "ERROR: Failed JNLP config from Jenkins. Cause: %v", response.Status) } } if err != nil { util.GOut("client", "ERROR: Failed JNLP config from Jenkins. Cause: %v", err) } return nil }
func (self *NodeNameHandler) createNode(config *util.Config) error { toJson := func(v interface{}) string { if content, err := json.Marshal(v); err == nil { return string(content) } else { return fmt.Sprintf("{\"error\": \"%v\"}", err) } } cwd, _ := os.Getwd() mode := "EXCLUSIVE" // NORMAL or EXCLUSIVE (tied jobs only) retention := "hudson.slaves.RetentionStrategy$Always" // Always on params := make(url.Values) params.Set("name", config.ClientName) params.Set("type", ExpectedNodeType) params.Set("json", toJson(map[string]interface{}{ "name": config.ClientName, "nodeDescription": fmt.Sprintf("JSL auto generated node '%s'.", config.ClientName), "numExecutors": 1, "remoteFS": cwd, "labelString": fmt.Sprintf("JSL %s %s", runtime.GOOS, runtime.GOARCH), "mode": mode, "type": ExpectedNodeType, "retentionStrategy": map[string]interface{}{"stapler-class": retention}, "nodeProperties": map[string]interface{}{"stapler-class-bag": true}, "launcher": map[string]interface{}{"stapler-class": ExpectedNodeLauncher}, })) if response, err := config.CIGet(CreateNodeURI + "?" + params.Encode()); err == nil { response.Body.Close() if response.StatusCode == 200 { return nil } else { return fmt.Errorf("Create node failed. Jenkins returned %v", response.Status) } } else { return err } }
// Closes a previously opened SSL connection. func (self *SSHTunnelEstablisher) tearDownSSHTunnel(config *util.Config) { if self.ciHostURL == nil { return } self.tunnelConnected.Set(false) self.resetAliveStateMonitoring(config) config.CIHostURI = self.ciHostURL.String() delete(util.JnlpArgs, "-url") delete(util.JnlpArgs, "-tunnel") if self.closables != nil && len(self.closables) > 0 { for i := len(self.closables) - 1; i >= 0; i-- { self.closables[i].Close() } self.closables = self.closables[0:0] } }
// Opens a new SSH connection, local server ports (JNLP, HTTP) and forwards it to the corresponding ports on Jenkins. func (self *SSHTunnelEstablisher) setupSSHTunnel(config *util.Config) { if self.ciHostURL == nil { return } if !config.PassCIAuth && config.SecretKey != "" { util.GOut("ssh-tunnel", "WARN: Secret key is not supported in combination with SSH tunnel. Implicitly setting %v to %v", "client>passAuth", "true") config.PassCIAuth = true } // Ensure no other SSL connections are still open. self.tearDownSSHTunnel(config) // Configuring the SSH client clientConfig := &ssh.ClientConfig{ User: config.CITunnelSSHUsername, Auth: []ssh.AuthMethod{ ssh.Password(config.CITunnelSSHPassword), }, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { expected, actual := config.CITunnelSSHFingerprint, self.formatHostFingerprint(key) if actual != expected && expected != "-" { if expected == "" { return fmt.Errorf("The host fingerprint of '%v' is '%v'. Please add this to the configuration in order to connect.", hostname, actual) } else { return fmt.Errorf("The host fingerprint of '%v' is '%v' while '%v' was expected. Connection aborted.", hostname, actual, expected) } } return nil }, } // Connecting to the SSH host if config.CITunnelSSHPort == 0 { config.CITunnelSSHPort = 22 } sshAddress := fmt.Sprintf("%v:%v", config.CITunnelSSHAddress, config.CITunnelSSHPort) sshClient, err := ssh.Dial("tcp", sshAddress, clientConfig) if err == nil { self.closables = append(self.closables, sshClient) util.GOut("ssh-tunnel", "Successfully connected with '%v'.", sshAddress) } else { util.GOut("ssh-tunnel", "ERROR: Failed connecting with %v. Cause: %v", sshAddress, err) return } // Fetching target ports jnlpTargetAddress, err := self.formatJNLPHostAndPort(config) if err != nil { util.GOut("ssh-tunnel", "ERROR: Failed fetching JNLP port from '%v'. Cause: %v.", config.CIHostURI, err) return } httpTargetAddress := self.formatHttpHostAndPort() // Creating a local server listeners to use for port forwarding. httpListener, err1 := self.newLocalServerListener() jnlpListener, err2 := self.newLocalServerListener() if err1 != nil || err2 != nil { self.tearDownSSHTunnel(config) return } // Forward local connections to the HTTP(S)/JNLP ports. go self.forwardLocalConnectionsTo(config, sshClient, httpListener, httpTargetAddress) go self.forwardLocalConnectionsTo(config, sshClient, jnlpListener, jnlpTargetAddress) // Apply the tunnel configuration localCiURL, _ := url.Parse(self.ciHostURL.String()) localCiURL.Host = httpListener.Addr().String() config.CIHostURI = localCiURL.String() util.JnlpArgs["-url"] = localCiURL.String() util.JnlpArgs["-tunnel"] = jnlpListener.Addr().String() // Mark tunnel as connected when we passed this line. self.tunnelConnected.Set(true) }