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.

server.go 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package daemon
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "strings"
  6. "github.com/gorilla/mux"
  7. "github.com/pkg/errors"
  8. stdprometheus "github.com/prometheus/client_golang/prometheus"
  9. "github.com/weaveworks/common/middleware"
  10. "github.com/fluxcd/flux/api"
  11. "github.com/fluxcd/flux/api/v10"
  12. "github.com/fluxcd/flux/api/v11"
  13. "github.com/fluxcd/flux/api/v9"
  14. transport "github.com/fluxcd/flux/http"
  15. "github.com/fluxcd/flux/job"
  16. fluxmetrics "github.com/fluxcd/flux/metrics"
  17. "github.com/fluxcd/flux/resource"
  18. "github.com/fluxcd/flux/update"
  19. )
  20. var (
  21. requestDuration = stdprometheus.NewHistogramVec(stdprometheus.HistogramOpts{
  22. Namespace: "flux",
  23. Name: "request_duration_seconds",
  24. Help: "Time (in seconds) spent serving HTTP requests.",
  25. Buckets: stdprometheus.DefBuckets,
  26. }, []string{fluxmetrics.LabelMethod, fluxmetrics.LabelRoute, "status_code", "ws"})
  27. )
  28. // An API server for the daemon
  29. func NewRouter() *mux.Router {
  30. r := transport.NewAPIRouter()
  31. // All old versions are deprecated in the daemon. Use an up to
  32. // date client!
  33. transport.DeprecateVersions(r, "v1", "v2", "v3", "v4", "v5")
  34. // We assume every request that doesn't match a route is a client
  35. // calling an old or hitherto unsupported API.
  36. r.NewRoute().Name("NotFound").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  37. transport.WriteError(w, r, http.StatusNotFound, transport.MakeAPINotFound(r.URL.Path))
  38. })
  39. return r
  40. }
  41. func NewHandler(s api.Server, r *mux.Router) http.Handler {
  42. handle := HTTPServer{s}
  43. // Erstwhile Upstream(Server) methods, now part of v11
  44. r.Get(transport.Ping).HandlerFunc(handle.Ping)
  45. r.Get(transport.Version).HandlerFunc(handle.Version)
  46. r.Get(transport.Notify).HandlerFunc(handle.Notify)
  47. // v6-v11 handlers
  48. r.Get(transport.ListServices).HandlerFunc(handle.ListServicesWithOptions)
  49. r.Get(transport.ListServicesWithOptions).HandlerFunc(handle.ListServicesWithOptions)
  50. r.Get(transport.ListImages).HandlerFunc(handle.ListImagesWithOptions)
  51. r.Get(transport.ListImagesWithOptions).HandlerFunc(handle.ListImagesWithOptions)
  52. r.Get(transport.UpdateManifests).HandlerFunc(handle.UpdateManifests)
  53. r.Get(transport.JobStatus).HandlerFunc(handle.JobStatus)
  54. r.Get(transport.SyncStatus).HandlerFunc(handle.SyncStatus)
  55. r.Get(transport.Export).HandlerFunc(handle.Export)
  56. r.Get(transport.GitRepoConfig).HandlerFunc(handle.GitRepoConfig)
  57. // These handlers persist to support requests from older fluxctls. In general we
  58. // should avoid adding references to them so that they can eventually be removed.
  59. r.Get(transport.UpdateImages).HandlerFunc(handle.UpdateImages)
  60. r.Get(transport.UpdatePolicies).HandlerFunc(handle.UpdatePolicies)
  61. r.Get(transport.GetPublicSSHKey).HandlerFunc(handle.GetPublicSSHKey)
  62. r.Get(transport.RegeneratePublicSSHKey).HandlerFunc(handle.RegeneratePublicSSHKey)
  63. return middleware.Instrument{
  64. RouteMatcher: r,
  65. Duration: requestDuration,
  66. }.Wrap(r)
  67. }
  68. type HTTPServer struct {
  69. server api.Server
  70. }
  71. func (s HTTPServer) Ping(w http.ResponseWriter, r *http.Request) {
  72. if err := s.server.Ping(r.Context()); err != nil {
  73. transport.ErrorResponse(w, r, err)
  74. return
  75. }
  76. w.WriteHeader(http.StatusNoContent)
  77. return
  78. }
  79. func (s HTTPServer) Version(w http.ResponseWriter, r *http.Request) {
  80. version, err := s.server.Version(r.Context())
  81. if err != nil {
  82. transport.ErrorResponse(w, r, err)
  83. return
  84. }
  85. transport.JSONResponse(w, r, version)
  86. }
  87. func (s HTTPServer) Notify(w http.ResponseWriter, r *http.Request) {
  88. var change v9.Change
  89. defer r.Body.Close()
  90. if err := json.NewDecoder(r.Body).Decode(&change); err != nil {
  91. transport.WriteError(w, r, http.StatusBadRequest, err)
  92. return
  93. }
  94. if err := s.server.NotifyChange(r.Context(), change); err != nil {
  95. transport.ErrorResponse(w, r, err)
  96. return
  97. }
  98. w.WriteHeader(http.StatusAccepted)
  99. }
  100. func (s HTTPServer) JobStatus(w http.ResponseWriter, r *http.Request) {
  101. id := job.ID(mux.Vars(r)["id"])
  102. status, err := s.server.JobStatus(r.Context(), id)
  103. if err != nil {
  104. transport.ErrorResponse(w, r, err)
  105. return
  106. }
  107. transport.JSONResponse(w, r, status)
  108. }
  109. func (s HTTPServer) SyncStatus(w http.ResponseWriter, r *http.Request) {
  110. ref := mux.Vars(r)["ref"]
  111. commits, err := s.server.SyncStatus(r.Context(), ref)
  112. if err != nil {
  113. transport.ErrorResponse(w, r, err)
  114. return
  115. }
  116. transport.JSONResponse(w, r, commits)
  117. }
  118. func (s HTTPServer) ListImagesWithOptions(w http.ResponseWriter, r *http.Request) {
  119. var opts v10.ListImagesOptions
  120. queryValues := r.URL.Query()
  121. // service - Select services to list images for.
  122. service := queryValues.Get("service")
  123. if service == "" {
  124. service = string(update.ResourceSpecAll)
  125. }
  126. spec, err := update.ParseResourceSpec(service)
  127. if err != nil {
  128. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing service spec %q", service))
  129. return
  130. }
  131. opts.Spec = spec
  132. // containerFields - Override which fields to return in the container struct.
  133. containerFields := queryValues.Get("containerFields")
  134. if containerFields != "" {
  135. opts.OverrideContainerFields = strings.Split(containerFields, ",")
  136. }
  137. // namespace - Select namespace to list images for.
  138. namespace := queryValues.Get("namespace")
  139. if namespace != "" {
  140. opts.Namespace = namespace
  141. }
  142. d, err := s.server.ListImagesWithOptions(r.Context(), opts)
  143. if err != nil {
  144. transport.ErrorResponse(w, r, err)
  145. return
  146. }
  147. transport.JSONResponse(w, r, d)
  148. }
  149. func (s HTTPServer) UpdateManifests(w http.ResponseWriter, r *http.Request) {
  150. var spec update.Spec
  151. if err := json.NewDecoder(r.Body).Decode(&spec); err != nil {
  152. transport.WriteError(w, r, http.StatusBadRequest, err)
  153. return
  154. }
  155. jobID, err := s.server.UpdateManifests(r.Context(), spec)
  156. if err != nil {
  157. transport.ErrorResponse(w, r, err)
  158. return
  159. }
  160. transport.JSONResponse(w, r, jobID)
  161. }
  162. func (s HTTPServer) ListServicesWithOptions(w http.ResponseWriter, r *http.Request) {
  163. var opts v11.ListServicesOptions
  164. opts.Namespace = r.URL.Query().Get("namespace")
  165. services := r.URL.Query().Get("services")
  166. if services != "" {
  167. for _, svc := range strings.Split(services, ",") {
  168. id, err := resource.ParseID(svc)
  169. if err != nil {
  170. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing service spec %q", svc))
  171. return
  172. }
  173. opts.Services = append(opts.Services, id)
  174. }
  175. }
  176. res, err := s.server.ListServicesWithOptions(r.Context(), opts)
  177. if err != nil {
  178. transport.ErrorResponse(w, r, err)
  179. return
  180. }
  181. transport.JSONResponse(w, r, res)
  182. }
  183. func (s HTTPServer) Export(w http.ResponseWriter, r *http.Request) {
  184. status, err := s.server.Export(r.Context())
  185. if err != nil {
  186. transport.ErrorResponse(w, r, err)
  187. return
  188. }
  189. transport.JSONResponse(w, r, status)
  190. }
  191. func (s HTTPServer) GitRepoConfig(w http.ResponseWriter, r *http.Request) {
  192. var regenerate bool
  193. if err := json.NewDecoder(r.Body).Decode(&regenerate); err != nil {
  194. transport.WriteError(w, r, http.StatusBadRequest, err)
  195. }
  196. res, err := s.server.GitRepoConfig(r.Context(), regenerate)
  197. if err != nil {
  198. transport.ErrorResponse(w, r, err)
  199. }
  200. transport.JSONResponse(w, r, res)
  201. }
  202. // --- handlers supporting deprecated requests
  203. func (s HTTPServer) UpdateImages(w http.ResponseWriter, r *http.Request) {
  204. var (
  205. vars = mux.Vars(r)
  206. image = vars["image"]
  207. kind = vars["kind"]
  208. )
  209. if err := r.ParseForm(); err != nil {
  210. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing form"))
  211. return
  212. }
  213. var serviceSpecs []update.ResourceSpec
  214. for _, service := range r.Form["service"] {
  215. serviceSpec, err := update.ParseResourceSpec(service)
  216. if err != nil {
  217. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing service spec %q", service))
  218. return
  219. }
  220. serviceSpecs = append(serviceSpecs, serviceSpec)
  221. }
  222. imageSpec, err := update.ParseImageSpec(image)
  223. if err != nil {
  224. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing image spec %q", image))
  225. return
  226. }
  227. releaseKind, err := update.ParseReleaseKind(kind)
  228. if err != nil {
  229. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing release kind %q", kind))
  230. return
  231. }
  232. var excludes []resource.ID
  233. for _, ex := range r.URL.Query()["exclude"] {
  234. s, err := resource.ParseID(ex)
  235. if err != nil {
  236. transport.WriteError(w, r, http.StatusBadRequest, errors.Wrapf(err, "parsing excluded service %q", ex))
  237. return
  238. }
  239. excludes = append(excludes, s)
  240. }
  241. spec := update.ReleaseImageSpec{
  242. ServiceSpecs: serviceSpecs,
  243. ImageSpec: imageSpec,
  244. Kind: releaseKind,
  245. Excludes: excludes,
  246. }
  247. cause := update.Cause{
  248. User: r.FormValue("user"),
  249. Message: r.FormValue("message"),
  250. }
  251. result, err := s.server.UpdateManifests(r.Context(), update.Spec{Type: update.Images, Cause: cause, Spec: spec})
  252. if err != nil {
  253. transport.ErrorResponse(w, r, err)
  254. return
  255. }
  256. transport.JSONResponse(w, r, result)
  257. }
  258. func (s HTTPServer) UpdatePolicies(w http.ResponseWriter, r *http.Request) {
  259. var updates resource.PolicyUpdates
  260. if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
  261. transport.WriteError(w, r, http.StatusBadRequest, err)
  262. return
  263. }
  264. cause := update.Cause{
  265. User: r.FormValue("user"),
  266. Message: r.FormValue("message"),
  267. }
  268. jobID, err := s.server.UpdateManifests(r.Context(), update.Spec{Type: update.Policy, Cause: cause, Spec: updates})
  269. if err != nil {
  270. transport.ErrorResponse(w, r, err)
  271. return
  272. }
  273. transport.JSONResponse(w, r, jobID)
  274. }
  275. func (s HTTPServer) GetPublicSSHKey(w http.ResponseWriter, r *http.Request) {
  276. res, err := s.server.GitRepoConfig(r.Context(), false)
  277. if err != nil {
  278. transport.ErrorResponse(w, r, err)
  279. return
  280. }
  281. transport.JSONResponse(w, r, res.PublicSSHKey)
  282. }
  283. func (s HTTPServer) RegeneratePublicSSHKey(w http.ResponseWriter, r *http.Request) {
  284. _, err := s.server.GitRepoConfig(r.Context(), true)
  285. if err != nil {
  286. transport.ErrorResponse(w, r, err)
  287. return
  288. }
  289. w.WriteHeader(http.StatusNoContent)
  290. return
  291. }