registry_handler.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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. // HandleCreateRepository creates a new image repository in a registry, if the registry
  57. // does not allow for create-on-push behavior
  58. func (app *App) HandleCreateRepository(w http.ResponseWriter, r *http.Request) {
  59. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  60. if err != nil || projID == 0 {
  61. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  62. return
  63. }
  64. regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  65. if err != nil || projID == 0 {
  66. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  67. return
  68. }
  69. form := &forms.CreateRepository{}
  70. // decode from JSON to form value
  71. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  72. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  73. return
  74. }
  75. // validate the form
  76. if err := app.validator.Struct(form); err != nil {
  77. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  78. return
  79. }
  80. // read the registry
  81. reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
  82. if err != nil {
  83. app.handleErrorDataRead(err, w)
  84. return
  85. }
  86. _reg := registry.Registry(*reg)
  87. regAPI := &_reg
  88. // parse the name from the registry
  89. nameSpl := strings.Split(form.ImageRepoURI, "/")
  90. repoName := nameSpl[len(nameSpl)-1]
  91. err = regAPI.CreateRepository(app.Repo, repoName)
  92. if err != nil {
  93. app.handleErrorInternal(err, w)
  94. return
  95. }
  96. w.WriteHeader(http.StatusCreated)
  97. }
  98. // HandleListProjectRegistries returns a list of registries for a project
  99. func (app *App) HandleListProjectRegistries(w http.ResponseWriter, r *http.Request) {
  100. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  101. if err != nil || projID == 0 {
  102. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  103. return
  104. }
  105. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  106. if err != nil {
  107. app.handleErrorRead(err, ErrProjectDataRead, w)
  108. return
  109. }
  110. extRegs := make([]*models.RegistryExternal, 0)
  111. for _, reg := range regs {
  112. extRegs = append(extRegs, reg.Externalize())
  113. }
  114. w.WriteHeader(http.StatusOK)
  115. if err := json.NewEncoder(w).Encode(extRegs); err != nil {
  116. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  117. return
  118. }
  119. }
  120. // temp -- token response
  121. type RegTokenResponse struct {
  122. Token string `json:"token"`
  123. ExpiresAt *time.Time `json:"expires_at"`
  124. }
  125. // HandleGetProjectRegistryECRToken gets an ECR token for a registry
  126. func (app *App) HandleGetProjectRegistryECRToken(w http.ResponseWriter, r *http.Request) {
  127. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  128. if err != nil || projID == 0 {
  129. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  130. return
  131. }
  132. region := chi.URLParam(r, "region")
  133. if region == "" {
  134. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  135. return
  136. }
  137. // list registries and find one that matches the region
  138. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  139. var token string
  140. var expiresAt *time.Time
  141. for _, reg := range regs {
  142. if reg.AWSIntegrationID != 0 {
  143. awsInt, err := app.Repo.AWSIntegration().ReadAWSIntegration(reg.AWSIntegrationID)
  144. if err != nil {
  145. app.handleErrorDataRead(err, w)
  146. return
  147. }
  148. if awsInt.AWSRegion == region {
  149. // get the aws integration and session
  150. sess, err := awsInt.GetSession()
  151. if err != nil {
  152. app.handleErrorDataRead(err, w)
  153. return
  154. }
  155. ecrSvc := ecr.New(sess)
  156. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  157. if err != nil {
  158. app.handleErrorDataRead(err, w)
  159. return
  160. }
  161. token = *output.AuthorizationData[0].AuthorizationToken
  162. expiresAt = output.AuthorizationData[0].ExpiresAt
  163. }
  164. }
  165. }
  166. resp := &RegTokenResponse{
  167. Token: token,
  168. ExpiresAt: expiresAt,
  169. }
  170. w.WriteHeader(http.StatusOK)
  171. if err := json.NewEncoder(w).Encode(resp); err != nil {
  172. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  173. return
  174. }
  175. }
  176. // HandleGetProjectRegistryDockerhubToken gets a Dockerhub token for a registry
  177. func (app *App) HandleGetProjectRegistryDockerhubToken(w http.ResponseWriter, r *http.Request) {
  178. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  179. if err != nil || projID == 0 {
  180. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  181. return
  182. }
  183. // list registries and find one that matches the region
  184. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  185. var token string
  186. var expiresAt *time.Time
  187. for _, reg := range regs {
  188. if reg.BasicIntegrationID != 0 && strings.Contains(reg.URL, "index.docker.io") {
  189. basic, err := app.Repo.BasicIntegration().ReadBasicIntegration(reg.BasicIntegrationID)
  190. if err != nil {
  191. app.handleErrorDataRead(err, w)
  192. return
  193. }
  194. token = base64.StdEncoding.EncodeToString([]byte(string(basic.Username) + ":" + string(basic.Password)))
  195. // we'll just set an arbitrary 30-day expiry time (this is not enforced)
  196. timeExpires := time.Now().Add(30 * 24 * 3600 * time.Second)
  197. expiresAt = &timeExpires
  198. }
  199. }
  200. resp := &RegTokenResponse{
  201. Token: token,
  202. ExpiresAt: expiresAt,
  203. }
  204. w.WriteHeader(http.StatusOK)
  205. if err := json.NewEncoder(w).Encode(resp); err != nil {
  206. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  207. return
  208. }
  209. }
  210. type GCRTokenRequestBody struct {
  211. ServerURL string `json:"server_url"`
  212. }
  213. // HandleGetProjectRegistryGCRToken gets a GCR token for a registry
  214. func (app *App) HandleGetProjectRegistryGCRToken(w http.ResponseWriter, r *http.Request) {
  215. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  216. if err != nil || projID == 0 {
  217. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  218. return
  219. }
  220. reqBody := &GCRTokenRequestBody{}
  221. // decode from JSON to form value
  222. if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
  223. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  224. return
  225. }
  226. // list registries and find one that matches the region
  227. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  228. var token string
  229. var expiresAt *time.Time
  230. for _, reg := range regs {
  231. if reg.GCPIntegrationID != 0 && strings.Contains(reg.URL, reqBody.ServerURL) {
  232. _reg := registry.Registry(*reg)
  233. tokenCache, err := _reg.GetGCRToken(app.Repo)
  234. if err != nil {
  235. app.handleErrorDataRead(err, w)
  236. return
  237. }
  238. token = string(tokenCache.Token)
  239. expiresAt = &tokenCache.Expiry
  240. break
  241. }
  242. }
  243. resp := &RegTokenResponse{
  244. Token: token,
  245. ExpiresAt: expiresAt,
  246. }
  247. w.WriteHeader(http.StatusOK)
  248. if err := json.NewEncoder(w).Encode(resp); err != nil {
  249. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  250. return
  251. }
  252. }
  253. // HandleGetProjectRegistryDOCRToken gets a DOCR token for a registry
  254. func (app *App) HandleGetProjectRegistryDOCRToken(w http.ResponseWriter, r *http.Request) {
  255. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  256. if err != nil || projID == 0 {
  257. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  258. return
  259. }
  260. reqBody := &GCRTokenRequestBody{}
  261. // decode from JSON to form value
  262. if err := json.NewDecoder(r.Body).Decode(reqBody); err != nil {
  263. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  264. return
  265. }
  266. // list registries and find one that matches the region
  267. regs, err := app.Repo.Registry().ListRegistriesByProjectID(uint(projID))
  268. var token string
  269. var expiresAt *time.Time
  270. for _, reg := range regs {
  271. if reg.DOIntegrationID != 0 && strings.Contains(reg.URL, reqBody.ServerURL) {
  272. oauthInt, err := app.Repo.OAuthIntegration().ReadOAuthIntegration(reg.DOIntegrationID)
  273. if err != nil {
  274. app.handleErrorDataRead(err, w)
  275. return
  276. }
  277. tok, expiry, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, app.DOConf, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, app.Repo))
  278. if err != nil {
  279. app.handleErrorDataRead(err, w)
  280. return
  281. }
  282. token = tok
  283. expiresAt = expiry
  284. break
  285. }
  286. }
  287. resp := &RegTokenResponse{
  288. Token: token,
  289. ExpiresAt: expiresAt,
  290. }
  291. w.WriteHeader(http.StatusOK)
  292. if err := json.NewEncoder(w).Encode(resp); err != nil {
  293. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  294. return
  295. }
  296. }
  297. // HandleUpdateProjectRegistry updates a registry
  298. func (app *App) HandleUpdateProjectRegistry(w http.ResponseWriter, r *http.Request) {
  299. projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
  300. if err != nil || projID == 0 {
  301. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  302. return
  303. }
  304. registryID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  305. if err != nil || registryID == 0 {
  306. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  307. return
  308. }
  309. form := &forms.UpdateRegistryForm{
  310. ID: uint(registryID),
  311. }
  312. // decode from JSON to form value
  313. if err := json.NewDecoder(r.Body).Decode(form); err != nil {
  314. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  315. return
  316. }
  317. // validate the form
  318. if err := app.validator.Struct(form); err != nil {
  319. app.handleErrorFormValidation(err, ErrProjectValidateFields, w)
  320. return
  321. }
  322. // convert the form to a registry
  323. registry, err := form.ToRegistry(app.Repo.Registry())
  324. if err != nil {
  325. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  326. return
  327. }
  328. // handle write to the database
  329. registry, err = app.Repo.Registry().UpdateRegistry(registry)
  330. if err != nil {
  331. app.handleErrorDataWrite(err, w)
  332. return
  333. }
  334. w.WriteHeader(http.StatusOK)
  335. regExt := registry.Externalize()
  336. if err := json.NewEncoder(w).Encode(regExt); err != nil {
  337. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  338. return
  339. }
  340. }
  341. // HandleDeleteProjectRegistry handles the deletion of a Registry via the registry ID
  342. func (app *App) HandleDeleteProjectRegistry(w http.ResponseWriter, r *http.Request) {
  343. id, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  344. if err != nil || id == 0 {
  345. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  346. return
  347. }
  348. reg, err := app.Repo.Registry().ReadRegistry(uint(id))
  349. if err != nil {
  350. app.handleErrorRead(err, ErrProjectDataRead, w)
  351. return
  352. }
  353. err = app.Repo.Registry().DeleteRegistry(reg)
  354. if err != nil {
  355. app.handleErrorRead(err, ErrProjectDataRead, w)
  356. return
  357. }
  358. w.WriteHeader(http.StatusOK)
  359. }
  360. // HandleListRepositories returns a list of repositories for a given registry
  361. func (app *App) HandleListRepositories(w http.ResponseWriter, r *http.Request) {
  362. regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  363. if err != nil || regID == 0 {
  364. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  365. return
  366. }
  367. reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
  368. if err != nil {
  369. app.handleErrorRead(err, ErrProjectDataRead, w)
  370. return
  371. }
  372. // cast to a registry from registry package
  373. _reg := registry.Registry(*reg)
  374. regAPI := &_reg
  375. repos, err := regAPI.ListRepositories(app.Repo, app.DOConf)
  376. if err != nil {
  377. app.handleErrorRead(err, ErrProjectDataRead, w)
  378. return
  379. }
  380. w.WriteHeader(http.StatusOK)
  381. if err := json.NewEncoder(w).Encode(repos); err != nil {
  382. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  383. return
  384. }
  385. }
  386. // HandleListImages retrieves a list of repo names
  387. func (app *App) HandleListImages(w http.ResponseWriter, r *http.Request) {
  388. regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
  389. if err != nil || regID == 0 {
  390. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  391. return
  392. }
  393. repoName := chi.URLParam(r, "*")
  394. reg, err := app.Repo.Registry().ReadRegistry(uint(regID))
  395. if err != nil {
  396. app.handleErrorRead(err, ErrProjectDataRead, w)
  397. return
  398. }
  399. // cast to a registry from registry package
  400. _reg := registry.Registry(*reg)
  401. regAPI := &_reg
  402. imgs, err := regAPI.ListImages(repoName, app.Repo, app.DOConf)
  403. if err != nil {
  404. app.handleErrorRead(err, ErrProjectDataRead, w)
  405. return
  406. }
  407. w.WriteHeader(http.StatusOK)
  408. if err := json.NewEncoder(w).Encode(imgs); err != nil {
  409. app.handleErrorFormDecoding(err, ErrProjectDecode, w)
  410. return
  411. }
  412. }