GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

upstream.go 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package daemon
  2. // This file can be removed from the package once `--connect` is
  3. // removed from fluxd. Until then, it will be imported from here.
  4. import (
  5. "context"
  6. "net/http"
  7. "net/url"
  8. "os"
  9. "time"
  10. "github.com/go-kit/kit/log"
  11. "github.com/go-kit/kit/metrics/prometheus"
  12. "github.com/gorilla/mux"
  13. "github.com/pkg/errors"
  14. stdprometheus "github.com/prometheus/client_golang/prometheus"
  15. "github.com/fluxcd/flux/api"
  16. "github.com/fluxcd/flux/event"
  17. transport "github.com/fluxcd/flux/http"
  18. fluxclient "github.com/fluxcd/flux/http/client"
  19. "github.com/fluxcd/flux/http/websocket"
  20. "github.com/fluxcd/flux/remote/rpc"
  21. )
  22. // Upstream handles communication from the daemon to a service
  23. type Upstream struct {
  24. client *http.Client
  25. ua string
  26. token fluxclient.Token
  27. url *url.URL
  28. endpoint string
  29. apiClient *fluxclient.Client
  30. server api.Server
  31. timeout time.Duration
  32. logger log.Logger
  33. quit chan struct{}
  34. ws websocket.Websocket
  35. }
  36. var (
  37. ErrEndpointDeprecated = errors.New("Your fluxd version is deprecated - please upgrade, see https://github.com/weaveworks/flux/releases")
  38. connectionDuration = prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
  39. Namespace: "flux",
  40. Subsystem: "fluxd",
  41. Name: "connection_duration_seconds",
  42. Help: "Duration in seconds of the current connection to fluxsvc. Zero means unconnected.",
  43. }, []string{"target"})
  44. )
  45. func NewUpstream(client *http.Client, ua string, t fluxclient.Token, router *mux.Router, endpoint string, s api.Server, timeout time.Duration, logger log.Logger) (*Upstream, error) {
  46. httpEndpoint, wsEndpoint, err := inferEndpoints(endpoint)
  47. if err != nil {
  48. return nil, errors.Wrap(err, "inferring WS/HTTP endpoints")
  49. }
  50. u, err := transport.MakeURL(wsEndpoint, router, transport.RegisterDaemonV11)
  51. if err != nil {
  52. return nil, errors.Wrap(err, "constructing URL")
  53. }
  54. a := &Upstream{
  55. client: client,
  56. ua: ua,
  57. token: t,
  58. url: u,
  59. endpoint: wsEndpoint,
  60. apiClient: fluxclient.New(client, router, httpEndpoint, t),
  61. server: s,
  62. timeout: timeout,
  63. logger: logger,
  64. quit: make(chan struct{}),
  65. }
  66. go a.loop()
  67. return a, nil
  68. }
  69. func inferEndpoints(endpoint string) (httpEndpoint, wsEndpoint string, err error) {
  70. endpointURL, err := url.Parse(endpoint)
  71. if err != nil {
  72. return "", "", errors.Wrapf(err, "parsing endpoint %s", endpoint)
  73. }
  74. switch endpointURL.Scheme {
  75. case "ws":
  76. httpURL := *endpointURL
  77. httpURL.Scheme = "http"
  78. return httpURL.String(), endpointURL.String(), nil
  79. case "wss":
  80. httpURL := *endpointURL
  81. httpURL.Scheme = "https"
  82. return httpURL.String(), endpointURL.String(), nil
  83. case "http":
  84. wsURL := *endpointURL
  85. wsURL.Scheme = "ws"
  86. return endpointURL.String(), wsURL.String(), nil
  87. case "https":
  88. wsURL := *endpointURL
  89. wsURL.Scheme = "wss"
  90. return endpointURL.String(), wsURL.String(), nil
  91. default:
  92. return "", "", errors.Errorf("unsupported scheme %s", endpointURL.Scheme)
  93. }
  94. }
  95. func (a *Upstream) loop() {
  96. backoff := 5 * time.Second
  97. errc := make(chan error, 1)
  98. for {
  99. go func() {
  100. errc <- a.connect()
  101. }()
  102. select {
  103. case err := <-errc:
  104. if err != nil {
  105. a.logger.Log("err", err)
  106. if err == ErrEndpointDeprecated {
  107. // We have logged the deprecation error, now crashloop to garner attention
  108. os.Exit(1)
  109. }
  110. }
  111. time.Sleep(backoff)
  112. case <-a.quit:
  113. return
  114. }
  115. }
  116. }
  117. func (a *Upstream) connect() error {
  118. a.setConnectionDuration(0)
  119. a.logger.Log("connecting", true)
  120. ws, err := websocket.Dial(a.client, a.ua, a.token, a.url)
  121. if err != nil {
  122. if err, ok := err.(*websocket.DialErr); ok && err.HTTPResponse != nil && err.HTTPResponse.StatusCode == http.StatusGone {
  123. return ErrEndpointDeprecated
  124. }
  125. return errors.Wrapf(err, "executing websocket %s", a.url)
  126. }
  127. a.ws = ws
  128. defer func() {
  129. a.ws = nil
  130. // TODO: handle this error
  131. a.logger.Log("connection closing", true, "err", ws.Close())
  132. }()
  133. a.logger.Log("connected", true)
  134. // Instrument connection lifespan
  135. connectedAt := time.Now()
  136. disconnected := make(chan struct{})
  137. defer close(disconnected)
  138. go func() {
  139. t := time.NewTicker(1 * time.Second)
  140. for {
  141. select {
  142. case now := <-t.C:
  143. a.setConnectionDuration(now.Sub(connectedAt).Seconds())
  144. case <-disconnected:
  145. t.Stop()
  146. a.setConnectionDuration(0)
  147. return
  148. }
  149. }
  150. }()
  151. // Hook up the rpc server. We are a websocket _client_, but an RPC
  152. // _server_.
  153. rpcserver, err := rpc.NewServer(a.server, a.timeout)
  154. if err != nil {
  155. return errors.Wrap(err, "initializing rpc server")
  156. }
  157. rpcserver.ServeConn(ws)
  158. a.logger.Log("disconnected", true)
  159. return nil
  160. }
  161. func (a *Upstream) setConnectionDuration(duration float64) {
  162. connectionDuration.With("target", a.endpoint).Set(duration)
  163. }
  164. func (a *Upstream) LogEvent(event event.Event) error {
  165. return a.apiClient.LogEvent(context.TODO(), event)
  166. }
  167. // Close closes the connection to the service
  168. func (a *Upstream) Close() error {
  169. close(a.quit)
  170. if a.ws == nil {
  171. return nil
  172. }
  173. return a.ws.Close()
  174. }