// Creates a new tunnel establisher.
func NewSSHTunnelEstablisher(registerInMode bool) *SSHTunnelEstablisher {
	self := new(SSHTunnelEstablisher)
	self.closables = []io.Closer{}
	self.ciHostURL = nil

	self.aliveTicker, self.aliveTickEvaluator = time.NewTicker(nodeSshTunnelAliveMonitoringInterval), time.NewTicker(nodeSshTunnelAliveMonitoringInterval)
	self.expectedAliveTick, self.lastAliveTick = util.NewAtomicInt32(), util.NewAtomicInt32()
	self.tunnelConnected = util.NewAtomicBoolean()

	if registerInMode {
		modes.RegisterModeListener(func(mode modes.ExecutableMode, nextStatus int32, config *util.Config) {
			if !config.CITunnelSSHEnabled || config.CITunnelSSHAddress == "" || mode.Name() != "client" || !config.HasCIConnection() {
				return
			}

			if nextStatus == modes.ModeStarting {
				var err error
				if self.ciHostURL, err = url.Parse(config.CIHostURI); err != nil {
					util.GOut("ssh-tunnel", "ERROR: Failed parsing Jenkins URI. Cannot tunnel connections to Jenkins. Cause: %v", err)
					return
				}

				self.setupSSHTunnel(config)

			} else if nextStatus == modes.ModeStopped {
				self.tearDownSSHTunnel(config)
			}
		})
	}

	return self
}
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)
				}
			}
		}
	})
}