registry.go 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463
  1. package registry
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
  13. "github.com/aws/aws-sdk-go/aws/awserr"
  14. "github.com/aws/aws-sdk-go/service/ecr"
  15. "github.com/porter-dev/porter/internal/models"
  16. "github.com/porter-dev/porter/internal/oauth"
  17. "github.com/porter-dev/porter/internal/repository"
  18. "golang.org/x/oauth2"
  19. ints "github.com/porter-dev/porter/internal/models/integrations"
  20. ptypes "github.com/porter-dev/porter/api/types"
  21. "github.com/digitalocean/godo"
  22. "github.com/docker/cli/cli/config/configfile"
  23. "github.com/docker/cli/cli/config/types"
  24. "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry"
  25. "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
  26. )
  27. // Registry wraps the gorm Registry model
  28. type Registry models.Registry
  29. func GetECRRegistryURL(awsIntRepo repository.AWSIntegrationRepository, projectID, awsIntID uint) (string, error) {
  30. awsInt, err := awsIntRepo.ReadAWSIntegration(projectID, awsIntID)
  31. if err != nil {
  32. return "", err
  33. }
  34. sess, err := awsInt.GetSession()
  35. if err != nil {
  36. return "", err
  37. }
  38. ecrSvc := ecr.New(sess)
  39. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  40. if err != nil {
  41. return "", err
  42. }
  43. return *output.AuthorizationData[0].ProxyEndpoint, nil
  44. }
  45. // ListRepositories lists the repositories for a registry
  46. func (r *Registry) ListRepositories(
  47. repo repository.Repository,
  48. doAuth *oauth2.Config, // only required if using DOCR
  49. ) ([]*ptypes.RegistryRepository, error) {
  50. // switch on the auth mechanism to get a token
  51. if r.AWSIntegrationID != 0 {
  52. return r.listECRRepositories(repo)
  53. }
  54. if r.GCPIntegrationID != 0 {
  55. return r.listGCRRepositories(repo)
  56. }
  57. if r.DOIntegrationID != 0 {
  58. return r.listDOCRRepositories(repo, doAuth)
  59. }
  60. if r.AzureIntegrationID != 0 {
  61. return r.listACRRepositories(repo)
  62. }
  63. if r.BasicIntegrationID != 0 {
  64. return r.listPrivateRegistryRepositories(repo)
  65. }
  66. return nil, fmt.Errorf("error listing repositories")
  67. }
  68. type gcrJWT struct {
  69. AccessToken string `json:"token"`
  70. ExpiresInSec int `json:"expires_in"`
  71. }
  72. type gcrErr struct {
  73. Code string `json:"code"`
  74. Message string `json:"message"`
  75. }
  76. type gcrRepositoryResp struct {
  77. Repositories []string `json:"repositories"`
  78. Errors []gcrErr `json:"errors"`
  79. }
  80. func (r *Registry) GetGCRToken(repo repository.Repository) (*oauth2.Token, error) {
  81. getTokenCache := r.getTokenCacheFunc(repo)
  82. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  83. r.ProjectID,
  84. r.GCPIntegrationID,
  85. )
  86. if err != nil {
  87. return nil, err
  88. }
  89. // get oauth2 access token
  90. return gcp.GetBearerToken(
  91. getTokenCache,
  92. r.setTokenCacheFunc(repo),
  93. "https://www.googleapis.com/auth/devstorage.read_write",
  94. )
  95. }
  96. func (r *Registry) listGCRRepositories(
  97. repo repository.Repository,
  98. ) ([]*ptypes.RegistryRepository, error) {
  99. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  100. r.ProjectID,
  101. r.GCPIntegrationID,
  102. )
  103. if err != nil {
  104. return nil, err
  105. }
  106. // Just use service account key to authenticate, since scopes may not be in place
  107. // for oauth. This also prevents us from making more requests.
  108. client := &http.Client{}
  109. regURL := r.URL
  110. if !strings.HasPrefix(regURL, "http") {
  111. regURL = fmt.Sprintf("https://%s", regURL)
  112. }
  113. regURLParsed, err := url.Parse(regURL)
  114. regHostname := "gcr.io"
  115. if err == nil {
  116. regHostname = regURLParsed.Host
  117. }
  118. req, err := http.NewRequest(
  119. "GET",
  120. fmt.Sprintf("https://%s/v2/_catalog", regHostname),
  121. nil,
  122. )
  123. if err != nil {
  124. return nil, err
  125. }
  126. req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))
  127. resp, err := client.Do(req)
  128. if err != nil {
  129. return nil, err
  130. }
  131. gcrResp := gcrRepositoryResp{}
  132. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  133. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  134. }
  135. if len(gcrResp.Errors) > 0 {
  136. errMsg := ""
  137. for _, gcrErr := range gcrResp.Errors {
  138. errMsg += fmt.Sprintf(": Code %s, message %s", gcrErr.Code, gcrErr.Message)
  139. }
  140. return nil, fmt.Errorf(errMsg)
  141. }
  142. res := make([]*ptypes.RegistryRepository, 0)
  143. parsedURL, err := url.Parse("https://" + r.URL)
  144. if err != nil {
  145. return nil, err
  146. }
  147. for _, repo := range gcrResp.Repositories {
  148. res = append(res, &ptypes.RegistryRepository{
  149. Name: repo,
  150. URI: parsedURL.Host + "/" + repo,
  151. })
  152. }
  153. return res, nil
  154. }
  155. func (r *Registry) listECRRepositories(repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
  156. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  157. r.ProjectID,
  158. r.AWSIntegrationID,
  159. )
  160. if err != nil {
  161. return nil, err
  162. }
  163. sess, err := aws.GetSession()
  164. if err != nil {
  165. return nil, err
  166. }
  167. svc := ecr.New(sess)
  168. resp, err := svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{})
  169. if err != nil {
  170. return nil, err
  171. }
  172. res := make([]*ptypes.RegistryRepository, 0)
  173. for _, repo := range resp.Repositories {
  174. res = append(res, &ptypes.RegistryRepository{
  175. Name: *repo.RepositoryName,
  176. CreatedAt: *repo.CreatedAt,
  177. URI: *repo.RepositoryUri,
  178. })
  179. }
  180. return res, nil
  181. }
  182. func (r *Registry) listACRRepositories(repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
  183. az, err := repo.AzureIntegration().ReadAzureIntegration(
  184. r.ProjectID,
  185. r.AzureIntegrationID,
  186. )
  187. if err != nil {
  188. return nil, err
  189. }
  190. client := &http.Client{}
  191. req, err := http.NewRequest(
  192. "GET",
  193. fmt.Sprintf("%s/v2/_catalog", r.URL),
  194. nil,
  195. )
  196. if err != nil {
  197. return nil, err
  198. }
  199. req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))
  200. resp, err := client.Do(req)
  201. if err != nil {
  202. return nil, err
  203. }
  204. gcrResp := gcrRepositoryResp{}
  205. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  206. return nil, fmt.Errorf("Could not read Azure registry repositories: %v", err)
  207. }
  208. res := make([]*ptypes.RegistryRepository, 0)
  209. if err != nil {
  210. return nil, err
  211. }
  212. for _, repo := range gcrResp.Repositories {
  213. res = append(res, &ptypes.RegistryRepository{
  214. Name: repo,
  215. URI: strings.TrimPrefix(r.URL, "https://") + "/" + repo,
  216. })
  217. }
  218. return res, nil
  219. }
  220. // Returns the username/password pair for the registry
  221. func (r *Registry) GetACRCredentials(repo repository.Repository) (string, string, error) {
  222. az, err := repo.AzureIntegration().ReadAzureIntegration(
  223. r.ProjectID,
  224. r.AzureIntegrationID,
  225. )
  226. if err != nil {
  227. return "", "", err
  228. }
  229. // if the passwords and name aren't set, generate them
  230. if az.ACRTokenName == "" || len(az.ACRPassword1) == 0 {
  231. az.ACRTokenName = "porter-acr-token"
  232. // create an acr repo token
  233. cred, err := azidentity.NewClientSecretCredential(az.AzureTenantID, az.AzureClientID, string(az.ServicePrincipalSecret), nil)
  234. if err != nil {
  235. return "", "", err
  236. }
  237. scopeMapsClient, err := armcontainerregistry.NewScopeMapsClient(az.AzureSubscriptionID, cred, nil)
  238. if err != nil {
  239. return "", "", err
  240. }
  241. smRes, err := scopeMapsClient.Get(
  242. context.Background(),
  243. az.ACRResourceGroupName,
  244. az.ACRName,
  245. "_repositories_admin",
  246. nil,
  247. )
  248. if err != nil {
  249. return "", "", err
  250. }
  251. tokensClient, err := armcontainerregistry.NewTokensClient(az.AzureSubscriptionID, cred, nil)
  252. if err != nil {
  253. return "", "", err
  254. }
  255. pollerResp, err := tokensClient.BeginCreate(
  256. context.Background(),
  257. az.ACRResourceGroupName,
  258. az.ACRName,
  259. "porter-acr-token",
  260. armcontainerregistry.Token{
  261. Properties: &armcontainerregistry.TokenProperties{
  262. ScopeMapID: smRes.ID,
  263. Status: to.Ptr(armcontainerregistry.TokenStatusEnabled),
  264. },
  265. },
  266. nil,
  267. )
  268. if err != nil {
  269. return "", "", err
  270. }
  271. tokResp, err := pollerResp.PollUntilDone(context.Background(), 2*time.Second)
  272. if err != nil {
  273. return "", "", err
  274. }
  275. registriesClient, err := armcontainerregistry.NewRegistriesClient(az.AzureSubscriptionID, cred, nil)
  276. if err != nil {
  277. return "", "", err
  278. }
  279. poller, err := registriesClient.BeginGenerateCredentials(
  280. context.Background(),
  281. az.ACRResourceGroupName,
  282. az.ACRName,
  283. armcontainerregistry.GenerateCredentialsParameters{
  284. TokenID: tokResp.ID,
  285. },
  286. &armcontainerregistry.RegistriesClientBeginGenerateCredentialsOptions{ResumeToken: ""})
  287. if err != nil {
  288. return "", "", err
  289. }
  290. genCredentialsResp, err := poller.PollUntilDone(context.Background(), 2*time.Second)
  291. if err != nil {
  292. return "", "", err
  293. }
  294. for i, tokPassword := range genCredentialsResp.Passwords {
  295. if i == 0 {
  296. az.ACRPassword1 = []byte(*tokPassword.Value)
  297. } else if i == 1 {
  298. az.ACRPassword2 = []byte(*tokPassword.Value)
  299. }
  300. }
  301. // update the az integration
  302. az, err = repo.AzureIntegration().OverwriteAzureIntegration(
  303. az,
  304. )
  305. if err != nil {
  306. return "", "", err
  307. }
  308. }
  309. return az.ACRTokenName, string(az.ACRPassword1), nil
  310. }
  311. func (r *Registry) listDOCRRepositories(
  312. repo repository.Repository,
  313. doAuth *oauth2.Config,
  314. ) ([]*ptypes.RegistryRepository, error) {
  315. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  316. r.ProjectID,
  317. r.DOIntegrationID,
  318. )
  319. if err != nil {
  320. return nil, err
  321. }
  322. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  323. if err != nil {
  324. return nil, err
  325. }
  326. client := godo.NewFromToken(tok)
  327. urlArr := strings.Split(r.URL, "/")
  328. if len(urlArr) != 2 {
  329. return nil, fmt.Errorf("invalid digital ocean registry url")
  330. }
  331. name := urlArr[1]
  332. repos, _, err := client.Registry.ListRepositories(context.TODO(), name, &godo.ListOptions{})
  333. if err != nil {
  334. return nil, err
  335. }
  336. res := make([]*ptypes.RegistryRepository, 0)
  337. for _, repo := range repos {
  338. res = append(res, &ptypes.RegistryRepository{
  339. Name: repo.Name,
  340. URI: r.URL + "/" + repo.Name,
  341. })
  342. }
  343. return res, nil
  344. }
  345. func (r *Registry) listPrivateRegistryRepositories(
  346. repo repository.Repository,
  347. ) ([]*ptypes.RegistryRepository, error) {
  348. // handle dockerhub different, as it doesn't implement the docker registry http api
  349. if strings.Contains(r.URL, "docker.io") {
  350. // in this case, we just return the single dockerhub repository that's linked
  351. res := make([]*ptypes.RegistryRepository, 0)
  352. res = append(res, &ptypes.RegistryRepository{
  353. Name: strings.Split(r.URL, "docker.io/")[1],
  354. URI: r.URL,
  355. })
  356. return res, nil
  357. }
  358. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  359. r.ProjectID,
  360. r.BasicIntegrationID,
  361. )
  362. if err != nil {
  363. return nil, err
  364. }
  365. // Just use service account key to authenticate, since scopes may not be in place
  366. // for oauth. This also prevents us from making more requests.
  367. client := &http.Client{}
  368. // get the host and scheme to make the request
  369. parsedURL, err := url.Parse(r.URL)
  370. req, err := http.NewRequest(
  371. "GET",
  372. fmt.Sprintf("%s://%s/v2/_catalog", parsedURL.Scheme, parsedURL.Host),
  373. nil,
  374. )
  375. if err != nil {
  376. return nil, err
  377. }
  378. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  379. resp, err := client.Do(req)
  380. if err != nil {
  381. return nil, err
  382. }
  383. // if the status code is 404, fallback to the Docker Hub implementation
  384. if resp.StatusCode == 404 {
  385. req, err := http.NewRequest(
  386. "GET",
  387. fmt.Sprintf("%s/", r.URL),
  388. nil,
  389. )
  390. if err != nil {
  391. return nil, err
  392. }
  393. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  394. resp, err = client.Do(req)
  395. if err != nil {
  396. return nil, err
  397. }
  398. }
  399. gcrResp := gcrRepositoryResp{}
  400. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  401. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  402. }
  403. res := make([]*ptypes.RegistryRepository, 0)
  404. if err != nil {
  405. return nil, err
  406. }
  407. for _, repo := range gcrResp.Repositories {
  408. res = append(res, &ptypes.RegistryRepository{
  409. Name: repo,
  410. URI: parsedURL.Host + "/" + repo,
  411. })
  412. }
  413. return res, nil
  414. }
  415. func (r *Registry) getTokenCacheFunc(
  416. repo repository.Repository,
  417. ) ints.GetTokenCacheFunc {
  418. return func() (tok *ints.TokenCache, err error) {
  419. reg, err := repo.Registry().ReadRegistry(r.ProjectID, r.ID)
  420. if err != nil {
  421. return nil, err
  422. }
  423. return &reg.TokenCache.TokenCache, nil
  424. }
  425. }
  426. func (r *Registry) setTokenCacheFunc(
  427. repo repository.Repository,
  428. ) ints.SetTokenCacheFunc {
  429. return func(token string, expiry time.Time) error {
  430. _, err := repo.Registry().UpdateRegistryTokenCache(
  431. &ints.RegTokenCache{
  432. TokenCache: ints.TokenCache{
  433. Token: []byte(token),
  434. Expiry: expiry,
  435. },
  436. RegistryID: r.ID,
  437. },
  438. )
  439. return err
  440. }
  441. }
  442. // CreateRepository creates a repository for a registry, if needed
  443. // (currently only required for ECR)
  444. func (r *Registry) CreateRepository(
  445. repo repository.Repository,
  446. name string,
  447. ) error {
  448. // if aws, create repository
  449. if r.AWSIntegrationID != 0 {
  450. return r.createECRRepository(repo, name)
  451. }
  452. // otherwise, no-op
  453. return nil
  454. }
  455. func (r *Registry) createECRRepository(
  456. repo repository.Repository,
  457. name string,
  458. ) error {
  459. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  460. r.ProjectID,
  461. r.AWSIntegrationID,
  462. )
  463. if err != nil {
  464. return err
  465. }
  466. sess, err := aws.GetSession()
  467. if err != nil {
  468. return err
  469. }
  470. svc := ecr.New(sess)
  471. // determine if repository already exists
  472. _, err = svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{
  473. RepositoryNames: []*string{&name},
  474. })
  475. // if the repository was not found, create it
  476. if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryNotFoundException {
  477. _, err = svc.CreateRepository(&ecr.CreateRepositoryInput{
  478. RepositoryName: &name,
  479. })
  480. return err
  481. } else if err != nil {
  482. return err
  483. }
  484. return nil
  485. }
  486. // ListImages lists the images for an image repository
  487. func (r *Registry) ListImages(
  488. repoName string,
  489. repo repository.Repository,
  490. doAuth *oauth2.Config, // only required if using DOCR
  491. ) ([]*ptypes.Image, error) {
  492. // switch on the auth mechanism to get a token
  493. if r.AWSIntegrationID != 0 {
  494. return r.listECRImages(repoName, repo)
  495. }
  496. if r.AzureIntegrationID != 0 {
  497. return r.listACRImages(repoName, repo)
  498. }
  499. if r.GCPIntegrationID != 0 {
  500. return r.listGCRImages(repoName, repo)
  501. }
  502. if r.DOIntegrationID != 0 {
  503. return r.listDOCRImages(repoName, repo, doAuth)
  504. }
  505. if r.BasicIntegrationID != 0 {
  506. return r.listPrivateRegistryImages(repoName, repo)
  507. }
  508. return nil, fmt.Errorf("error listing images")
  509. }
  510. func (r *Registry) GetECRPaginatedImages(
  511. repoName string,
  512. repo repository.Repository,
  513. maxResults int64,
  514. nextToken *string,
  515. ) ([]*ptypes.Image, *string, error) {
  516. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  517. r.ProjectID,
  518. r.AWSIntegrationID,
  519. )
  520. if err != nil {
  521. return nil, nil, err
  522. }
  523. sess, err := aws.GetSession()
  524. if err != nil {
  525. return nil, nil, err
  526. }
  527. svc := ecr.New(sess)
  528. resp, err := svc.ListImages(&ecr.ListImagesInput{
  529. RepositoryName: &repoName,
  530. MaxResults: &maxResults,
  531. NextToken: nextToken,
  532. })
  533. if err != nil {
  534. return nil, nil, err
  535. }
  536. if len(resp.ImageIds) == 0 {
  537. return []*ptypes.Image{}, nil, nil
  538. }
  539. imageIDLen := len(resp.ImageIds)
  540. imageDetails := make([]*ecr.ImageDetail, 0)
  541. imageIDMap := make(map[string]bool)
  542. for _, id := range resp.ImageIds {
  543. if id != nil && id.ImageTag != nil {
  544. imageIDMap[*id.ImageTag] = true
  545. }
  546. }
  547. var wg sync.WaitGroup
  548. var mu sync.Mutex
  549. // AWS API expects the length of imageIDs to be at max 100 at a time
  550. for start := 0; start < imageIDLen; start += 100 {
  551. end := start + 100
  552. if end > imageIDLen {
  553. end = imageIDLen
  554. }
  555. wg.Add(1)
  556. go func(start, end int) {
  557. defer wg.Done()
  558. describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
  559. RepositoryName: &repoName,
  560. ImageIds: resp.ImageIds[start:end],
  561. })
  562. if err != nil {
  563. return
  564. }
  565. mu.Lock()
  566. imageDetails = append(imageDetails, describeResp.ImageDetails...)
  567. mu.Unlock()
  568. }(start, end)
  569. }
  570. wg.Wait()
  571. res := make([]*ptypes.Image, 0)
  572. imageInfoMap := make(map[string]*ptypes.Image)
  573. for _, img := range imageDetails {
  574. for _, tag := range img.ImageTags {
  575. newImage := &ptypes.Image{
  576. Digest: *img.ImageDigest,
  577. Tag: *tag,
  578. RepositoryName: repoName,
  579. PushedAt: img.ImagePushedAt,
  580. }
  581. if _, ok := imageIDMap[*tag]; ok {
  582. if _, ok := imageInfoMap[*tag]; !ok {
  583. imageInfoMap[*tag] = newImage
  584. }
  585. }
  586. if len(imageInfoMap) == int(maxResults) {
  587. break
  588. }
  589. }
  590. if len(imageInfoMap) == int(maxResults) {
  591. break
  592. }
  593. }
  594. for _, v := range imageInfoMap {
  595. res = append(res, v)
  596. }
  597. return res, resp.NextToken, nil
  598. }
  599. func (r *Registry) listECRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  600. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  601. r.ProjectID,
  602. r.AWSIntegrationID,
  603. )
  604. if err != nil {
  605. return nil, err
  606. }
  607. sess, err := aws.GetSession()
  608. if err != nil {
  609. return nil, err
  610. }
  611. svc := ecr.New(sess)
  612. maxResults := int64(1000)
  613. var imageIDs []*ecr.ImageIdentifier
  614. resp, err := svc.ListImages(&ecr.ListImagesInput{
  615. RepositoryName: &repoName,
  616. MaxResults: &maxResults,
  617. })
  618. if err != nil {
  619. return nil, err
  620. }
  621. if len(resp.ImageIds) == 0 {
  622. return []*ptypes.Image{}, nil
  623. }
  624. imageIDs = append(imageIDs, resp.ImageIds...)
  625. nextToken := resp.NextToken
  626. for nextToken != nil {
  627. resp, err := svc.ListImages(&ecr.ListImagesInput{
  628. RepositoryName: &repoName,
  629. MaxResults: &maxResults,
  630. NextToken: nextToken,
  631. })
  632. if err != nil {
  633. return nil, err
  634. }
  635. imageIDs = append(imageIDs, resp.ImageIds...)
  636. nextToken = resp.NextToken
  637. }
  638. imageIDLen := len(imageIDs)
  639. imageDetails := make([]*ecr.ImageDetail, 0)
  640. var wg sync.WaitGroup
  641. var mu sync.Mutex
  642. // AWS API expects the length of imageIDs to be at max 100 at a time
  643. for start := 0; start < imageIDLen; start += 100 {
  644. end := start + 100
  645. if end > imageIDLen {
  646. end = imageIDLen
  647. }
  648. wg.Add(1)
  649. go func(start, end int) {
  650. defer wg.Done()
  651. describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
  652. RepositoryName: &repoName,
  653. ImageIds: imageIDs[start:end],
  654. })
  655. if err != nil {
  656. return
  657. }
  658. mu.Lock()
  659. imageDetails = append(imageDetails, describeResp.ImageDetails...)
  660. mu.Unlock()
  661. }(start, end)
  662. }
  663. wg.Wait()
  664. res := make([]*ptypes.Image, 0)
  665. imageInfoMap := make(map[string]*ptypes.Image)
  666. for _, img := range imageDetails {
  667. for _, tag := range img.ImageTags {
  668. newImage := &ptypes.Image{
  669. Digest: *img.ImageDigest,
  670. Tag: *tag,
  671. RepositoryName: repoName,
  672. PushedAt: img.ImagePushedAt,
  673. }
  674. if _, ok := imageInfoMap[*tag]; !ok {
  675. imageInfoMap[*tag] = newImage
  676. }
  677. }
  678. }
  679. for _, v := range imageInfoMap {
  680. res = append(res, v)
  681. }
  682. return res, nil
  683. }
  684. func (r *Registry) listACRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  685. az, err := repo.AzureIntegration().ReadAzureIntegration(
  686. r.ProjectID,
  687. r.AzureIntegrationID,
  688. )
  689. if err != nil {
  690. return nil, err
  691. }
  692. // use JWT token to request catalog
  693. client := &http.Client{}
  694. req, err := http.NewRequest(
  695. "GET",
  696. fmt.Sprintf("%s/v2/%s/tags/list", r.URL, repoName),
  697. nil,
  698. )
  699. if err != nil {
  700. return nil, err
  701. }
  702. req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))
  703. resp, err := client.Do(req)
  704. if err != nil {
  705. return nil, err
  706. }
  707. gcrResp := gcrImageResp{}
  708. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  709. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  710. }
  711. res := make([]*ptypes.Image, 0)
  712. for _, tag := range gcrResp.Tags {
  713. res = append(res, &ptypes.Image{
  714. RepositoryName: strings.TrimPrefix(repoName, "https://"),
  715. Tag: tag,
  716. })
  717. }
  718. return res, nil
  719. }
  720. type gcrImageResp struct {
  721. Tags []string `json:"tags"`
  722. }
  723. func (r *Registry) listGCRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  724. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  725. r.ProjectID,
  726. r.GCPIntegrationID,
  727. )
  728. if err != nil {
  729. return nil, err
  730. }
  731. // use JWT token to request catalog
  732. client := &http.Client{}
  733. parsedURL, err := url.Parse("https://" + r.URL)
  734. if err != nil {
  735. return nil, err
  736. }
  737. trimmedPath := strings.Trim(parsedURL.Path, "/")
  738. req, err := http.NewRequest(
  739. "GET",
  740. fmt.Sprintf("https://%s/v2/%s/%s/tags/list", parsedURL.Host, trimmedPath, repoName),
  741. nil,
  742. )
  743. if err != nil {
  744. return nil, err
  745. }
  746. req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))
  747. resp, err := client.Do(req)
  748. if err != nil {
  749. return nil, err
  750. }
  751. gcrResp := gcrImageResp{}
  752. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  753. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  754. }
  755. res := make([]*ptypes.Image, 0)
  756. for _, tag := range gcrResp.Tags {
  757. res = append(res, &ptypes.Image{
  758. RepositoryName: repoName,
  759. Tag: tag,
  760. })
  761. }
  762. return res, nil
  763. }
  764. func (r *Registry) listDOCRImages(
  765. repoName string,
  766. repo repository.Repository,
  767. doAuth *oauth2.Config,
  768. ) ([]*ptypes.Image, error) {
  769. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  770. r.ProjectID,
  771. r.DOIntegrationID,
  772. )
  773. if err != nil {
  774. return nil, err
  775. }
  776. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  777. if err != nil {
  778. return nil, err
  779. }
  780. client := godo.NewFromToken(tok)
  781. urlArr := strings.Split(r.URL, "/")
  782. if len(urlArr) != 2 {
  783. return nil, fmt.Errorf("invalid digital ocean registry url")
  784. }
  785. name := urlArr[1]
  786. var tags []*godo.RepositoryTag
  787. opt := &godo.ListOptions{
  788. PerPage: 200,
  789. }
  790. for {
  791. nextTags, resp, err := client.Registry.ListRepositoryTags(context.TODO(), name, repoName, opt)
  792. if err != nil {
  793. return nil, err
  794. }
  795. tags = append(tags, nextTags...)
  796. if resp.Links == nil || resp.Links.IsLastPage() {
  797. break
  798. }
  799. page, err := resp.Links.CurrentPage()
  800. if err != nil {
  801. return nil, err
  802. }
  803. opt.Page = page + 1
  804. }
  805. res := make([]*ptypes.Image, 0)
  806. for _, tag := range tags {
  807. res = append(res, &ptypes.Image{
  808. RepositoryName: repoName,
  809. Tag: tag.Tag,
  810. })
  811. }
  812. return res, nil
  813. }
  814. func (r *Registry) listPrivateRegistryImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  815. // handle dockerhub different, as it doesn't implement the docker registry http api
  816. if strings.Contains(r.URL, "docker.io") {
  817. return r.listDockerHubImages(repoName, repo)
  818. }
  819. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  820. r.ProjectID,
  821. r.BasicIntegrationID,
  822. )
  823. if err != nil {
  824. return nil, err
  825. }
  826. // Just use service account key to authenticate, since scopes may not be in place
  827. // for oauth. This also prevents us from making more requests.
  828. client := &http.Client{}
  829. // get the host and scheme to make the request
  830. parsedURL, err := url.Parse(r.URL)
  831. req, err := http.NewRequest(
  832. "GET",
  833. fmt.Sprintf("%s://%s/v2/%s/tags/list", parsedURL.Scheme, parsedURL.Host, repoName),
  834. nil,
  835. )
  836. if err != nil {
  837. return nil, err
  838. }
  839. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  840. resp, err := client.Do(req)
  841. if err != nil {
  842. return nil, err
  843. }
  844. gcrResp := gcrImageResp{}
  845. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  846. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  847. }
  848. res := make([]*ptypes.Image, 0)
  849. for _, tag := range gcrResp.Tags {
  850. res = append(res, &ptypes.Image{
  851. RepositoryName: repoName,
  852. Tag: tag,
  853. })
  854. }
  855. return res, nil
  856. }
  857. type dockerHubImageResult struct {
  858. Name string `json:"name"`
  859. }
  860. type dockerHubImageResp struct {
  861. Results []dockerHubImageResult `json:"results"`
  862. }
  863. type dockerHubLoginReq struct {
  864. Username string `json:"username"`
  865. Password string `json:"password"`
  866. }
  867. type dockerHubLoginResp struct {
  868. Token string `json:"token"`
  869. }
  870. func (r *Registry) listDockerHubImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  871. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  872. r.ProjectID,
  873. r.BasicIntegrationID,
  874. )
  875. if err != nil {
  876. return nil, err
  877. }
  878. client := &http.Client{}
  879. // first, make a request for the access token
  880. data, err := json.Marshal(&dockerHubLoginReq{
  881. Username: string(basic.Username),
  882. Password: string(basic.Password),
  883. })
  884. if err != nil {
  885. return nil, err
  886. }
  887. req, err := http.NewRequest(
  888. "POST",
  889. "https://hub.docker.com/v2/users/login",
  890. strings.NewReader(string(data)),
  891. )
  892. if err != nil {
  893. return nil, err
  894. }
  895. req.Header.Add("Content-Type", "application/json")
  896. resp, err := client.Do(req)
  897. if err != nil {
  898. return nil, err
  899. }
  900. tokenObj := dockerHubLoginResp{}
  901. if err := json.NewDecoder(resp.Body).Decode(&tokenObj); err != nil {
  902. return nil, fmt.Errorf("Could not decode Dockerhub token from response: %v", err)
  903. }
  904. req, err = http.NewRequest(
  905. "GET",
  906. fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags", strings.Split(r.URL, "docker.io/")[1]),
  907. nil,
  908. )
  909. if err != nil {
  910. return nil, err
  911. }
  912. req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokenObj.Token))
  913. resp, err = client.Do(req)
  914. if err != nil {
  915. return nil, err
  916. }
  917. imageResp := dockerHubImageResp{}
  918. if err := json.NewDecoder(resp.Body).Decode(&imageResp); err != nil {
  919. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  920. }
  921. res := make([]*ptypes.Image, 0)
  922. for _, result := range imageResp.Results {
  923. res = append(res, &ptypes.Image{
  924. RepositoryName: repoName,
  925. Tag: result.Name,
  926. })
  927. }
  928. return res, nil
  929. }
  930. // GetDockerConfigJSON returns a dockerconfigjson file contents with "auths"
  931. // populated.
  932. func (r *Registry) GetDockerConfigJSON(
  933. repo repository.Repository,
  934. doAuth *oauth2.Config, // only required if using DOCR
  935. ) ([]byte, error) {
  936. var conf *configfile.ConfigFile
  937. var err error
  938. // switch on the auth mechanism to get a token
  939. if r.AWSIntegrationID != 0 {
  940. conf, err = r.getECRDockerConfigFile(repo)
  941. }
  942. if r.GCPIntegrationID != 0 {
  943. conf, err = r.getGCRDockerConfigFile(repo)
  944. }
  945. if r.DOIntegrationID != 0 {
  946. conf, err = r.getDOCRDockerConfigFile(repo, doAuth)
  947. }
  948. if r.BasicIntegrationID != 0 {
  949. conf, err = r.getPrivateRegistryDockerConfigFile(repo)
  950. }
  951. if r.AzureIntegrationID != 0 {
  952. conf, err = r.getACRDockerConfigFile(repo)
  953. }
  954. if err != nil {
  955. return nil, err
  956. }
  957. return json.Marshal(conf)
  958. }
  959. func (r *Registry) getECRDockerConfigFile(
  960. repo repository.Repository,
  961. ) (*configfile.ConfigFile, error) {
  962. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  963. r.ProjectID,
  964. r.AWSIntegrationID,
  965. )
  966. if err != nil {
  967. return nil, err
  968. }
  969. sess, err := aws.GetSession()
  970. if err != nil {
  971. return nil, err
  972. }
  973. ecrSvc := ecr.New(sess)
  974. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  975. if err != nil {
  976. return nil, err
  977. }
  978. token := *output.AuthorizationData[0].AuthorizationToken
  979. decodedToken, err := base64.StdEncoding.DecodeString(token)
  980. if err != nil {
  981. return nil, err
  982. }
  983. parts := strings.SplitN(string(decodedToken), ":", 2)
  984. if len(parts) < 2 {
  985. return nil, err
  986. }
  987. key := r.URL
  988. if !strings.Contains(key, "http") {
  989. key = "https://" + key
  990. }
  991. return &configfile.ConfigFile{
  992. AuthConfigs: map[string]types.AuthConfig{
  993. key: {
  994. Username: parts[0],
  995. Password: parts[1],
  996. Auth: token,
  997. },
  998. },
  999. }, nil
  1000. }
  1001. func (r *Registry) getGCRDockerConfigFile(
  1002. repo repository.Repository,
  1003. ) (*configfile.ConfigFile, error) {
  1004. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  1005. r.ProjectID,
  1006. r.GCPIntegrationID,
  1007. )
  1008. if err != nil {
  1009. return nil, err
  1010. }
  1011. key := r.URL
  1012. if !strings.Contains(key, "http") {
  1013. key = "https://" + key
  1014. }
  1015. parsedURL, _ := url.Parse(key)
  1016. return &configfile.ConfigFile{
  1017. AuthConfigs: map[string]types.AuthConfig{
  1018. parsedURL.Host: {
  1019. Username: "_json_key",
  1020. Password: string(gcp.GCPKeyData),
  1021. Auth: generateAuthToken("_json_key", string(gcp.GCPKeyData)),
  1022. },
  1023. },
  1024. }, nil
  1025. }
  1026. func (r *Registry) getDOCRDockerConfigFile(
  1027. repo repository.Repository,
  1028. doAuth *oauth2.Config,
  1029. ) (*configfile.ConfigFile, error) {
  1030. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  1031. r.ProjectID,
  1032. r.DOIntegrationID,
  1033. )
  1034. if err != nil {
  1035. return nil, err
  1036. }
  1037. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  1038. if err != nil {
  1039. return nil, err
  1040. }
  1041. key := r.URL
  1042. if !strings.Contains(key, "http") {
  1043. key = "https://" + key
  1044. }
  1045. parsedURL, _ := url.Parse(key)
  1046. return &configfile.ConfigFile{
  1047. AuthConfigs: map[string]types.AuthConfig{
  1048. parsedURL.Host: {
  1049. Username: tok,
  1050. Password: tok,
  1051. Auth: generateAuthToken(tok, tok),
  1052. },
  1053. },
  1054. }, nil
  1055. }
  1056. func (r *Registry) getPrivateRegistryDockerConfigFile(
  1057. repo repository.Repository,
  1058. ) (*configfile.ConfigFile, error) {
  1059. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  1060. r.ProjectID,
  1061. r.BasicIntegrationID,
  1062. )
  1063. if err != nil {
  1064. return nil, err
  1065. }
  1066. key := r.URL
  1067. if !strings.Contains(key, "http") {
  1068. key = "https://" + key
  1069. }
  1070. parsedURL, _ := url.Parse(key)
  1071. authConfigKey := parsedURL.Host
  1072. if strings.Contains(r.URL, "index.docker.io") {
  1073. authConfigKey = "https://index.docker.io/v1/"
  1074. }
  1075. return &configfile.ConfigFile{
  1076. AuthConfigs: map[string]types.AuthConfig{
  1077. authConfigKey: {
  1078. Username: string(basic.Username),
  1079. Password: string(basic.Password),
  1080. Auth: generateAuthToken(string(basic.Username), string(basic.Password)),
  1081. },
  1082. },
  1083. }, nil
  1084. }
  1085. func (r *Registry) getACRDockerConfigFile(
  1086. repo repository.Repository,
  1087. ) (*configfile.ConfigFile, error) {
  1088. username, pw, err := r.GetACRCredentials(repo)
  1089. if err != nil {
  1090. return nil, err
  1091. }
  1092. key := r.URL
  1093. if !strings.Contains(key, "http") {
  1094. key = "https://" + key
  1095. }
  1096. parsedURL, _ := url.Parse(key)
  1097. return &configfile.ConfigFile{
  1098. AuthConfigs: map[string]types.AuthConfig{
  1099. parsedURL.Host: {
  1100. Username: string(username),
  1101. Password: string(pw),
  1102. Auth: generateAuthToken(string(username), string(pw)),
  1103. },
  1104. },
  1105. }, nil
  1106. }
  1107. func generateAuthToken(username, password string) string {
  1108. return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
  1109. }