registry_handler.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. package api
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/porter-dev/porter/internal/oauth"
  10. "github.com/porter-dev/porter/internal/registry"
  11. "github.com/go-chi/chi"
  12. "github.com/porter-dev/porter/internal/forms"
  13. "github.com/porter-dev/porter/internal/models"
  14. "github.com/aws/aws-sdk-go/service/ecr"
  15. )
  16. // HandleCreateRegistry creates a new registry
  17. func (app *App) HandleCreateRegistry(w http.ResponseWriter, r *http.Request) {
  18. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  19. if err != nil || projID == 0 {
  20. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  21. return
  22. }
  23. form := &forms.CreateRegistry{
  24. ProjectID: uint(projID),
  25. }
  26. // decode from JSON to form value
  27. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  28. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  29. return
  30. }
  31. // validate the form
  32. if err := app.validator.Struct(form); err != nil {
  33. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  34. return
  35. }
  36. // convert the form to a registry
  37. registry, err := form.ToRegistry(app.Repo)
  38. if err != nil {
  39. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  40. return
  41. }
  42. // handle write to the database
  43. registry, err = app.Repo.Registry().CreateRegistry(registry)
  44. if err != nil {
  45. app.handleErrorDataWrite(err, w)
  46. return
  47. }
  48. app.Logger.Info().Msgf("New registry created: %d", registry.ID)
  49. w.WriteHeader(http.StatusCreated)
  50. regExt := registry.Externalize()
  51. if err := json.NewEncoder(w).Encode(regExt); err != nil {
  52. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  53. return
  54. }
  55. }
  56. // HandleListProjectRegistries returns a list of registries for a project
  57. func (app *App) HandleListProjectRegistries(w http.ResponseWriter, r *http.Request) {
  58. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  59. if err != nil || projID == 0 {
  60. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  61. return
  62. }
  63. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  64. if err != nil {
  65. app.handleErrorRead(err, ErrProjectDataRead, w)
  66. return
  67. }
  68. extRegs := make([]*models.RegistryExternal, 0)
  69. for _, reg := range regs {
  70. extRegs = append(extRegs, reg.Externalize())
  71. }
  72. w.WriteHeader(http.StatusOK)
  73. if err := json.NewEncoder(w).Encode(extRegs); err != nil {
  74. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  75. return
  76. }
  77. }
  78. // temp -- token response
  79. type RegTokenResponse struct {
  80. Token string `json:"token"`
  81. ExpiresAt *time.Time `json:"expires_at"`
  82. }
  83. // HandleGetProjectRegistryECRToken gets an ECR token for a registry
  84. func (app *App) HandleGetProjectRegistryECRToken(w http.ResponseWriter, r *http.Request) {
  85. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  86. if err != nil || projID == 0 {
  87. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  88. return
  89. }
  90. region := chi.URLParam(r, "region")
  91. if region == "" {
  92. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  93. return
  94. }
  95. // list registries and find one that matches the region
  96. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  97. var token string
  98. var expiresAt *time.Time
  99. for _, reg := range regs {
  100. if reg.AWSIntegrationID != 0 {
  101. awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(reg.AWSIntegrationID)
  102. if err != nil {
  103. app.handleErrorDataRead(err, w)
  104. return
  105. }
  106. if awsInt.AWSRegion == region {
  107. // get the aws integration and session
  108. sess, err := awsInt.GetSession()
  109. if err != nil {
  110. app.handleErrorDataRead(err, w)
  111. return
  112. }
  113. ecrSvc := ecr.New(sess)
  114. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  115. if err != nil {
  116. app.handleErrorDataRead(err, w)
  117. return
  118. }
  119. token = *output.AuthorizationData[0].AuthorizationToken
  120. expiresAt = output.AuthorizationData[0].ExpiresAt
  121. }
  122. }
  123. }
  124. resp := &RegTokenResponse{
  125. Token: token,
  126. ExpiresAt: expiresAt,
  127. }
  128. w.WriteHeader(http.StatusOK)
  129. if err := json.NewEncoder(w).Encode(resp); err != nil {
  130. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  131. return
  132. }
  133. }
  134. // HandleGetProjectRegistryDockerhubToken gets a Dockerhub token for a registry
  135. func (app *App) HandleGetProjectRegistryDockerhubToken(w http.ResponseWriter, r *http.Request) {
  136. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  137. if err != nil || projID == 0 {
  138. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  139. return
  140. }
  141. // list registries and find one that matches the region
  142. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  143. var token string
  144. var expiresAt *time.Time
  145. for _, reg := range regs {
  146. if reg.BasicIntegrationID != 0 && strings.Contains(reg.URL, "index.docker.io") {
  147. basic, err := app.Repo.BasicIntegration().ReadBasicIntegration(reg.BasicIntegrationID)
  148. if err != nil {
  149. app.handleErrorDataRead(err, w)
  150. return
  151. }
  152. token = base64.StdEncoding.EncodeToString([]byte(string(basic.Username) + ":" + string(basic.Password)))
  153. // we'll just set an arbitrary 30-day expiry time (this is not enforced)
  154. timeExpires := time.Now().Add(30 * 24 * 3600 * time.Second)
  155. expiresAt = &timeExpires
  156. }
  157. }
  158. resp := &RegTokenResponse{
  159. Token: token,
  160. ExpiresAt: expiresAt,
  161. }
  162. w.WriteHeader(http.StatusOK)
  163. if err := json.NewEncoder(w).Encode(resp); err != nil {
  164. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  165. return
  166. }
  167. }
  168. type GCRTokenRequestBody struct {
  169. ServerURL string `json:"server_url"`
  170. }
  171. // HandleGetProjectRegistryGCRToken gets a GCR token for a registry
  172. func (app *App) HandleGetProjectRegistryGCRToken(w http.ResponseWriter, r *http.Request) {
  173. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  174. if err != nil || projID == 0 {
  175. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  176. return
  177. }
  178. reqBody := &GCRTokenRequestBody{}
  179. // decode from JSON to form value
  180. if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
  181. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  182. return
  183. }
  184. // list registries and find one that matches the region
  185. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  186. var token string
  187. var expiresAt *time.Time
  188. for _, reg := range regs {
  189. if reg.GCPIntegrationID != 0 && strings.Contains(reg.URL, reqBody.ServerURL) {
  190. _reg := registry.Registry(*reg)
  191. tokenCache, err := _reg.GetGCRToken(app.Repo)
  192. if err != nil {
  193. app.handleErrorDataRead(err, w)
  194. return
  195. }
  196. token = string(tokenCache.Token)
  197. expiresAt = &tokenCache.Expiry
  198. break
  199. }
  200. }
  201. resp := &RegTokenResponse{
  202. Token: token,
  203. ExpiresAt: expiresAt,
  204. }
  205. w.WriteHeader(http.StatusOK)
  206. if err := json.NewEncoder(w).Encode(resp); err != nil {
  207. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  208. return
  209. }
  210. }
  211. // HandleGetProjectRegistryDOCRToken gets a DOCR token for a registry
  212. func (app *App) HandleGetProjectRegistryDOCRToken(w http.ResponseWriter, r *http.Request) {
  213. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  214. if err != nil || projID == 0 {
  215. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  216. return
  217. }
  218. reqBody := &GCRTokenRequestBody{}
  219. // decode from JSON to form value
  220. if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
  221. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  222. return
  223. }
  224. // list registries and find one that matches the region
  225. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  226. var token string
  227. var expiresAt *time.Time
  228. for _, reg := range regs {
  229. if reg.DOIntegrationID != 0 && strings.Contains(reg.URL, reqBody.ServerURL) {
  230. oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(reg.DOIntegrationID)
  231. if err != nil {
  232. app.handleErrorDataRead(err, w)
  233. return
  234. }
  235. tok, expiry, err := oauth.GetAccessToken(oauthInt, app.DOConf, app.Repo)
  236. if err != nil {
  237. app.handleErrorDataRead(err, w)
  238. return
  239. }
  240. token = tok
  241. expiresAt = expiry
  242. break
  243. }
  244. }
  245. resp := &RegTokenResponse{
  246. Token: token,
  247. ExpiresAt: expiresAt,
  248. }
  249. w.WriteHeader(http.StatusOK)
  250. if err := json.NewEncoder(w).Encode(resp); err != nil {
  251. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  252. return
  253. }
  254. }
  255. // HandleUpdateProjectRegistry updates a registry
  256. func (app *App) HandleUpdateProjectRegistry(w http.ResponseWriter, r *http.Request) {
  257. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  258. if err != nil || projID == 0 {
  259. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  260. return
  261. }
  262. registryID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  263. if err != nil || registryID == 0 {
  264. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  265. return
  266. }
  267. form := &forms.UpdateRegistryForm{
  268. ID: uint(registryID),
  269. }
  270. // decode from JSON to form value
  271. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  272. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  273. return
  274. }
  275. // validate the form
  276. if err := app.validator.Struct(form); err != nil {
  277. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  278. return
  279. }
  280. // convert the form to a registry
  281. registry, err := form.ToRegistry(app.Repo.Registry())
  282. if err != nil {
  283. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  284. return
  285. }
  286. // handle write to the database
  287. registry, err = app.Repo.Registry().UpdateRegistry(registry)
  288. if err != nil {
  289. app.handleErrorDataWrite(err, w)
  290. return
  291. }
  292. w.WriteHeader(http.StatusOK)
  293. regExt := registry.Externalize()
  294. if err := json.NewEncoder(w).Encode(regExt); err != nil {
  295. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  296. return
  297. }
  298. }
  299. // HandleDeleteProjectRegistry handles the deletion of a Registry via the registry ID
  300. func (app *App) HandleDeleteProjectRegistry(w http.ResponseWriter, r *http.Request) {
  301. id, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  302. if err != nil || id == 0 {
  303. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  304. return
  305. }
  306. reg, err := app.Repo.Registry().ReadRegistry(uint(id))
  307. if err != nil {
  308. app.handleErrorRead(err, ErrProjectDataRead, w)
  309. return
  310. }
  311. err = app.Repo.Registry().DeleteRegistry(reg)
  312. if err != nil {
  313. app.handleErrorRead(err, ErrProjectDataRead, w)
  314. return
  315. }
  316. w.WriteHeader(http.StatusOK)
  317. }
  318. // HandleListRepositories returns a list of repositories for a given registry
  319. func (app *App) HandleListRepositories(w http.ResponseWriter, r *http.Request) {
  320. regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  321. if err != nil || regID == 0 {
  322. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  323. return
  324. }
  325. reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
  326. if err != nil {
  327. app.handleErrorRead(err, ErrProjectDataRead, w)
  328. return
  329. }
  330. // cast to a registry from registry package
  331. _reg := registry.Registry(*reg)
  332. regAPI := &_reg
  333. repos, err := regAPI.ListRepositories(app.Repo, app.DOConf)
  334. if err != nil {
  335. app.handleErrorRead(err, ErrProjectDataRead, w)
  336. return
  337. }
  338. w.WriteHeader(http.StatusOK)
  339. if err := json.NewEncoder(w).Encode(repos); err != nil {
  340. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  341. return
  342. }
  343. }
  344. // HandleListImages retrieves a list of repo names
  345. func (app *App) HandleListImages(w http.ResponseWriter, r *http.Request) {
  346. regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  347. if err != nil || regID == 0 {
  348. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  349. return
  350. }
  351. repoName := chi.URLParam(r, "*")
  352. reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
  353. if err != nil {
  354. app.handleErrorRead(err, ErrProjectDataRead, w)
  355. return
  356. }
  357. // cast to a registry from registry package
  358. _reg := registry.Registry(*reg)
  359. regAPI := &_reg
  360. imgs, err := regAPI.ListImages(repoName, app.Repo, app.DOConf)
  361. if err != nil {
  362. app.handleErrorRead(err, ErrProjectDataRead, w)
  363. return
  364. }
  365. w.WriteHeader(http.StatusOK)
  366. if err := json.NewEncoder(w).Encode(imgs); err != nil {
  367. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  368. return
  369. }
  370. }