registry_handler.go 14 KB

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