registry.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461
  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. imageIDMap[*id.ImageTag] = true
  544. }
  545. var wg sync.WaitGroup
  546. var mu sync.Mutex
  547. // AWS API expects the length of imageIDs to be at max 100 at a time
  548. for start := 0; start < imageIDLen; start += 100 {
  549. end := start + 100
  550. if end > imageIDLen {
  551. end = imageIDLen
  552. }
  553. wg.Add(1)
  554. go func(start, end int) {
  555. defer wg.Done()
  556. describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
  557. RepositoryName: &repoName,
  558. ImageIds: resp.ImageIds[start:end],
  559. })
  560. if err != nil {
  561. return
  562. }
  563. mu.Lock()
  564. imageDetails = append(imageDetails, describeResp.ImageDetails...)
  565. mu.Unlock()
  566. }(start, end)
  567. }
  568. wg.Wait()
  569. res := make([]*ptypes.Image, 0)
  570. imageInfoMap := make(map[string]*ptypes.Image)
  571. for _, img := range imageDetails {
  572. for _, tag := range img.ImageTags {
  573. newImage := &ptypes.Image{
  574. Digest: *img.ImageDigest,
  575. Tag: *tag,
  576. RepositoryName: repoName,
  577. PushedAt: img.ImagePushedAt,
  578. }
  579. if _, ok := imageIDMap[*tag]; ok {
  580. if _, ok := imageInfoMap[*tag]; !ok {
  581. imageInfoMap[*tag] = newImage
  582. }
  583. }
  584. if len(imageInfoMap) == int(maxResults) {
  585. break
  586. }
  587. }
  588. if len(imageInfoMap) == int(maxResults) {
  589. break
  590. }
  591. }
  592. for _, v := range imageInfoMap {
  593. res = append(res, v)
  594. }
  595. return res, resp.NextToken, nil
  596. }
  597. func (r *Registry) listECRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  598. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  599. r.ProjectID,
  600. r.AWSIntegrationID,
  601. )
  602. if err != nil {
  603. return nil, err
  604. }
  605. sess, err := aws.GetSession()
  606. if err != nil {
  607. return nil, err
  608. }
  609. svc := ecr.New(sess)
  610. maxResults := int64(1000)
  611. var imageIDs []*ecr.ImageIdentifier
  612. resp, err := svc.ListImages(&ecr.ListImagesInput{
  613. RepositoryName: &repoName,
  614. MaxResults: &maxResults,
  615. })
  616. if err != nil {
  617. return nil, err
  618. }
  619. if len(resp.ImageIds) == 0 {
  620. return []*ptypes.Image{}, nil
  621. }
  622. imageIDs = append(imageIDs, resp.ImageIds...)
  623. nextToken := resp.NextToken
  624. for nextToken != nil {
  625. resp, err := svc.ListImages(&ecr.ListImagesInput{
  626. RepositoryName: &repoName,
  627. MaxResults: &maxResults,
  628. NextToken: nextToken,
  629. })
  630. if err != nil {
  631. return nil, err
  632. }
  633. imageIDs = append(imageIDs, resp.ImageIds...)
  634. nextToken = resp.NextToken
  635. }
  636. imageIDLen := len(imageIDs)
  637. imageDetails := make([]*ecr.ImageDetail, 0)
  638. var wg sync.WaitGroup
  639. var mu sync.Mutex
  640. // AWS API expects the length of imageIDs to be at max 100 at a time
  641. for start := 0; start < imageIDLen; start += 100 {
  642. end := start + 100
  643. if end > imageIDLen {
  644. end = imageIDLen
  645. }
  646. wg.Add(1)
  647. go func(start, end int) {
  648. defer wg.Done()
  649. describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
  650. RepositoryName: &repoName,
  651. ImageIds: imageIDs[start:end],
  652. })
  653. if err != nil {
  654. return
  655. }
  656. mu.Lock()
  657. imageDetails = append(imageDetails, describeResp.ImageDetails...)
  658. mu.Unlock()
  659. }(start, end)
  660. }
  661. wg.Wait()
  662. res := make([]*ptypes.Image, 0)
  663. imageInfoMap := make(map[string]*ptypes.Image)
  664. for _, img := range imageDetails {
  665. for _, tag := range img.ImageTags {
  666. newImage := &ptypes.Image{
  667. Digest: *img.ImageDigest,
  668. Tag: *tag,
  669. RepositoryName: repoName,
  670. PushedAt: img.ImagePushedAt,
  671. }
  672. if _, ok := imageInfoMap[*tag]; !ok {
  673. imageInfoMap[*tag] = newImage
  674. }
  675. }
  676. }
  677. for _, v := range imageInfoMap {
  678. res = append(res, v)
  679. }
  680. return res, nil
  681. }
  682. func (r *Registry) listACRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  683. az, err := repo.AzureIntegration().ReadAzureIntegration(
  684. r.ProjectID,
  685. r.AzureIntegrationID,
  686. )
  687. if err != nil {
  688. return nil, err
  689. }
  690. // use JWT token to request catalog
  691. client := &http.Client{}
  692. req, err := http.NewRequest(
  693. "GET",
  694. fmt.Sprintf("%s/v2/%s/tags/list", r.URL, repoName),
  695. nil,
  696. )
  697. if err != nil {
  698. return nil, err
  699. }
  700. req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))
  701. resp, err := client.Do(req)
  702. if err != nil {
  703. return nil, err
  704. }
  705. gcrResp := gcrImageResp{}
  706. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  707. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  708. }
  709. res := make([]*ptypes.Image, 0)
  710. for _, tag := range gcrResp.Tags {
  711. res = append(res, &ptypes.Image{
  712. RepositoryName: strings.TrimPrefix(repoName, "https://"),
  713. Tag: tag,
  714. })
  715. }
  716. return res, nil
  717. }
  718. type gcrImageResp struct {
  719. Tags []string `json:"tags"`
  720. }
  721. func (r *Registry) listGCRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  722. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  723. r.ProjectID,
  724. r.GCPIntegrationID,
  725. )
  726. if err != nil {
  727. return nil, err
  728. }
  729. // use JWT token to request catalog
  730. client := &http.Client{}
  731. parsedURL, err := url.Parse("https://" + r.URL)
  732. if err != nil {
  733. return nil, err
  734. }
  735. trimmedPath := strings.Trim(parsedURL.Path, "/")
  736. req, err := http.NewRequest(
  737. "GET",
  738. fmt.Sprintf("https://%s/v2/%s/%s/tags/list", parsedURL.Host, trimmedPath, repoName),
  739. nil,
  740. )
  741. if err != nil {
  742. return nil, err
  743. }
  744. req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))
  745. resp, err := client.Do(req)
  746. if err != nil {
  747. return nil, err
  748. }
  749. gcrResp := gcrImageResp{}
  750. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  751. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  752. }
  753. res := make([]*ptypes.Image, 0)
  754. for _, tag := range gcrResp.Tags {
  755. res = append(res, &ptypes.Image{
  756. RepositoryName: repoName,
  757. Tag: tag,
  758. })
  759. }
  760. return res, nil
  761. }
  762. func (r *Registry) listDOCRImages(
  763. repoName string,
  764. repo repository.Repository,
  765. doAuth *oauth2.Config,
  766. ) ([]*ptypes.Image, error) {
  767. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  768. r.ProjectID,
  769. r.DOIntegrationID,
  770. )
  771. if err != nil {
  772. return nil, err
  773. }
  774. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  775. if err != nil {
  776. return nil, err
  777. }
  778. client := godo.NewFromToken(tok)
  779. urlArr := strings.Split(r.URL, "/")
  780. if len(urlArr) != 2 {
  781. return nil, fmt.Errorf("invalid digital ocean registry url")
  782. }
  783. name := urlArr[1]
  784. var tags []*godo.RepositoryTag
  785. opt := &godo.ListOptions{
  786. PerPage: 200,
  787. }
  788. for {
  789. nextTags, resp, err := client.Registry.ListRepositoryTags(context.TODO(), name, repoName, opt)
  790. if err != nil {
  791. return nil, err
  792. }
  793. tags = append(tags, nextTags...)
  794. if resp.Links == nil || resp.Links.IsLastPage() {
  795. break
  796. }
  797. page, err := resp.Links.CurrentPage()
  798. if err != nil {
  799. return nil, err
  800. }
  801. opt.Page = page + 1
  802. }
  803. res := make([]*ptypes.Image, 0)
  804. for _, tag := range tags {
  805. res = append(res, &ptypes.Image{
  806. RepositoryName: repoName,
  807. Tag: tag.Tag,
  808. })
  809. }
  810. return res, nil
  811. }
  812. func (r *Registry) listPrivateRegistryImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  813. // handle dockerhub different, as it doesn't implement the docker registry http api
  814. if strings.Contains(r.URL, "docker.io") {
  815. return r.listDockerHubImages(repoName, repo)
  816. }
  817. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  818. r.ProjectID,
  819. r.BasicIntegrationID,
  820. )
  821. if err != nil {
  822. return nil, err
  823. }
  824. // Just use service account key to authenticate, since scopes may not be in place
  825. // for oauth. This also prevents us from making more requests.
  826. client := &http.Client{}
  827. // get the host and scheme to make the request
  828. parsedURL, err := url.Parse(r.URL)
  829. req, err := http.NewRequest(
  830. "GET",
  831. fmt.Sprintf("%s://%s/v2/%s/tags/list", parsedURL.Scheme, parsedURL.Host, repoName),
  832. nil,
  833. )
  834. if err != nil {
  835. return nil, err
  836. }
  837. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  838. resp, err := client.Do(req)
  839. if err != nil {
  840. return nil, err
  841. }
  842. gcrResp := gcrImageResp{}
  843. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  844. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  845. }
  846. res := make([]*ptypes.Image, 0)
  847. for _, tag := range gcrResp.Tags {
  848. res = append(res, &ptypes.Image{
  849. RepositoryName: repoName,
  850. Tag: tag,
  851. })
  852. }
  853. return res, nil
  854. }
  855. type dockerHubImageResult struct {
  856. Name string `json:"name"`
  857. }
  858. type dockerHubImageResp struct {
  859. Results []dockerHubImageResult `json:"results"`
  860. }
  861. type dockerHubLoginReq struct {
  862. Username string `json:"username"`
  863. Password string `json:"password"`
  864. }
  865. type dockerHubLoginResp struct {
  866. Token string `json:"token"`
  867. }
  868. func (r *Registry) listDockerHubImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  869. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  870. r.ProjectID,
  871. r.BasicIntegrationID,
  872. )
  873. if err != nil {
  874. return nil, err
  875. }
  876. client := &http.Client{}
  877. // first, make a request for the access token
  878. data, err := json.Marshal(&dockerHubLoginReq{
  879. Username: string(basic.Username),
  880. Password: string(basic.Password),
  881. })
  882. if err != nil {
  883. return nil, err
  884. }
  885. req, err := http.NewRequest(
  886. "POST",
  887. "https://hub.docker.com/v2/users/login",
  888. strings.NewReader(string(data)),
  889. )
  890. if err != nil {
  891. return nil, err
  892. }
  893. req.Header.Add("Content-Type", "application/json")
  894. resp, err := client.Do(req)
  895. if err != nil {
  896. return nil, err
  897. }
  898. tokenObj := dockerHubLoginResp{}
  899. if err := json.NewDecoder(resp.Body).Decode(&tokenObj); err != nil {
  900. return nil, fmt.Errorf("Could not decode Dockerhub token from response: %v", err)
  901. }
  902. req, err = http.NewRequest(
  903. "GET",
  904. fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags", strings.Split(r.URL, "docker.io/")[1]),
  905. nil,
  906. )
  907. if err != nil {
  908. return nil, err
  909. }
  910. req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokenObj.Token))
  911. resp, err = client.Do(req)
  912. if err != nil {
  913. return nil, err
  914. }
  915. imageResp := dockerHubImageResp{}
  916. if err := json.NewDecoder(resp.Body).Decode(&imageResp); err != nil {
  917. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  918. }
  919. res := make([]*ptypes.Image, 0)
  920. for _, result := range imageResp.Results {
  921. res = append(res, &ptypes.Image{
  922. RepositoryName: repoName,
  923. Tag: result.Name,
  924. })
  925. }
  926. return res, nil
  927. }
  928. // GetDockerConfigJSON returns a dockerconfigjson file contents with "auths"
  929. // populated.
  930. func (r *Registry) GetDockerConfigJSON(
  931. repo repository.Repository,
  932. doAuth *oauth2.Config, // only required if using DOCR
  933. ) ([]byte, error) {
  934. var conf *configfile.ConfigFile
  935. var err error
  936. // switch on the auth mechanism to get a token
  937. if r.AWSIntegrationID != 0 {
  938. conf, err = r.getECRDockerConfigFile(repo)
  939. }
  940. if r.GCPIntegrationID != 0 {
  941. conf, err = r.getGCRDockerConfigFile(repo)
  942. }
  943. if r.DOIntegrationID != 0 {
  944. conf, err = r.getDOCRDockerConfigFile(repo, doAuth)
  945. }
  946. if r.BasicIntegrationID != 0 {
  947. conf, err = r.getPrivateRegistryDockerConfigFile(repo)
  948. }
  949. if r.AzureIntegrationID != 0 {
  950. conf, err = r.getACRDockerConfigFile(repo)
  951. }
  952. if err != nil {
  953. return nil, err
  954. }
  955. return json.Marshal(conf)
  956. }
  957. func (r *Registry) getECRDockerConfigFile(
  958. repo repository.Repository,
  959. ) (*configfile.ConfigFile, error) {
  960. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  961. r.ProjectID,
  962. r.AWSIntegrationID,
  963. )
  964. if err != nil {
  965. return nil, err
  966. }
  967. sess, err := aws.GetSession()
  968. if err != nil {
  969. return nil, err
  970. }
  971. ecrSvc := ecr.New(sess)
  972. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  973. if err != nil {
  974. return nil, err
  975. }
  976. token := *output.AuthorizationData[0].AuthorizationToken
  977. decodedToken, err := base64.StdEncoding.DecodeString(token)
  978. if err != nil {
  979. return nil, err
  980. }
  981. parts := strings.SplitN(string(decodedToken), ":", 2)
  982. if len(parts) < 2 {
  983. return nil, err
  984. }
  985. key := r.URL
  986. if !strings.Contains(key, "http") {
  987. key = "https://" + key
  988. }
  989. return &configfile.ConfigFile{
  990. AuthConfigs: map[string]types.AuthConfig{
  991. key: {
  992. Username: parts[0],
  993. Password: parts[1],
  994. Auth: token,
  995. },
  996. },
  997. }, nil
  998. }
  999. func (r *Registry) getGCRDockerConfigFile(
  1000. repo repository.Repository,
  1001. ) (*configfile.ConfigFile, error) {
  1002. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  1003. r.ProjectID,
  1004. r.GCPIntegrationID,
  1005. )
  1006. if err != nil {
  1007. return nil, err
  1008. }
  1009. key := r.URL
  1010. if !strings.Contains(key, "http") {
  1011. key = "https://" + key
  1012. }
  1013. parsedURL, _ := url.Parse(key)
  1014. return &configfile.ConfigFile{
  1015. AuthConfigs: map[string]types.AuthConfig{
  1016. parsedURL.Host: {
  1017. Username: "_json_key",
  1018. Password: string(gcp.GCPKeyData),
  1019. Auth: generateAuthToken("_json_key", string(gcp.GCPKeyData)),
  1020. },
  1021. },
  1022. }, nil
  1023. }
  1024. func (r *Registry) getDOCRDockerConfigFile(
  1025. repo repository.Repository,
  1026. doAuth *oauth2.Config,
  1027. ) (*configfile.ConfigFile, error) {
  1028. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  1029. r.ProjectID,
  1030. r.DOIntegrationID,
  1031. )
  1032. if err != nil {
  1033. return nil, err
  1034. }
  1035. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  1036. if err != nil {
  1037. return nil, err
  1038. }
  1039. key := r.URL
  1040. if !strings.Contains(key, "http") {
  1041. key = "https://" + key
  1042. }
  1043. parsedURL, _ := url.Parse(key)
  1044. return &configfile.ConfigFile{
  1045. AuthConfigs: map[string]types.AuthConfig{
  1046. parsedURL.Host: {
  1047. Username: tok,
  1048. Password: tok,
  1049. Auth: generateAuthToken(tok, tok),
  1050. },
  1051. },
  1052. }, nil
  1053. }
  1054. func (r *Registry) getPrivateRegistryDockerConfigFile(
  1055. repo repository.Repository,
  1056. ) (*configfile.ConfigFile, error) {
  1057. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  1058. r.ProjectID,
  1059. r.BasicIntegrationID,
  1060. )
  1061. if err != nil {
  1062. return nil, err
  1063. }
  1064. key := r.URL
  1065. if !strings.Contains(key, "http") {
  1066. key = "https://" + key
  1067. }
  1068. parsedURL, _ := url.Parse(key)
  1069. authConfigKey := parsedURL.Host
  1070. if strings.Contains(r.URL, "index.docker.io") {
  1071. authConfigKey = "https://index.docker.io/v1/"
  1072. }
  1073. return &configfile.ConfigFile{
  1074. AuthConfigs: map[string]types.AuthConfig{
  1075. authConfigKey: {
  1076. Username: string(basic.Username),
  1077. Password: string(basic.Password),
  1078. Auth: generateAuthToken(string(basic.Username), string(basic.Password)),
  1079. },
  1080. },
  1081. }, nil
  1082. }
  1083. func (r *Registry) getACRDockerConfigFile(
  1084. repo repository.Repository,
  1085. ) (*configfile.ConfigFile, error) {
  1086. username, pw, err := r.GetACRCredentials(repo)
  1087. if err != nil {
  1088. return nil, err
  1089. }
  1090. key := r.URL
  1091. if !strings.Contains(key, "http") {
  1092. key = "https://" + key
  1093. }
  1094. parsedURL, _ := url.Parse(key)
  1095. return &configfile.ConfigFile{
  1096. AuthConfigs: map[string]types.AuthConfig{
  1097. parsedURL.Host: {
  1098. Username: string(username),
  1099. Password: string(pw),
  1100. Auth: generateAuthToken(string(username), string(pw)),
  1101. },
  1102. },
  1103. }, nil
  1104. }
  1105. func generateAuthToken(username, password string) string {
  1106. return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
  1107. }