Browse Source

ecr image list

Alexander Belanger 5 năm trước cách đây
mục cha
commit
8b76c25035

+ 34 - 0
cli/cmd/api/registry.go

@@ -136,3 +136,37 @@ func (c *Client) ListRegistryRepositories(
 
 	return *bodyResp, nil
 }
+
+// ListImagesResponse is the list of images in a repository
+type ListImagesResponse []registry.Image
+
+// ListImages lists the images (repository+tag) in a repository
+func (c *Client) ListImages(
+	ctx context.Context,
+	projectID uint,
+	registryID uint,
+	repoName string,
+) (ListImagesResponse, error) {
+	req, err := http.NewRequest(
+		"GET",
+		fmt.Sprintf("%s/projects/%d/registries/%d/repositories/%s", c.BaseURL, projectID, registryID, repoName),
+		nil,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	req = req.WithContext(ctx)
+	bodyResp := &ListImagesResponse{}
+
+	if httpErr, err := c.sendRequest(req, bodyResp, true); httpErr != nil || err != nil {
+		if httpErr != nil {
+			return nil, fmt.Errorf("code %d, errors %v", httpErr.Code, httpErr.Errors)
+		}
+
+		return nil, err
+	}
+
+	return *bodyResp, nil
+}

+ 45 - 0
cli/cmd/registry.go

@@ -29,6 +29,18 @@ var registryCmdListRepos = &cobra.Command{
 	},
 }
 
+var registryCmdListImages = &cobra.Command{
+	Use:   "images list [REPO_NAME]",
+	Short: "Lists the images in an image repository",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, listImages)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
 func init() {
 	rootCmd.AddCommand(registryCmd)
 
@@ -40,6 +52,8 @@ func init() {
 	)
 
 	registryCmd.AddCommand(registryCmdListRepos)
+
+	registryCmd.AddCommand(registryCmdListImages)
 }
 
 func listRepos(user *api.AuthCheckResponse, client *api.Client, args []string) error {
@@ -70,3 +84,34 @@ func listRepos(user *api.AuthCheckResponse, client *api.Client, args []string) e
 
 	return nil
 }
+
+func listImages(user *api.AuthCheckResponse, client *api.Client, args []string) error {
+	pID := getProjectID()
+	rID := getRegistryID()
+	repoName := args[1]
+
+	// get the list of namespaces
+	imgs, err := client.ListImages(
+		context.Background(),
+		pID,
+		rID,
+		repoName,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	w := new(tabwriter.Writer)
+	w.Init(os.Stdout, 3, 8, 0, '\t', tabwriter.AlignRight)
+
+	fmt.Fprintf(w, "%s\t%s\n", "IMAGE", "DIGEST")
+
+	for _, img := range imgs {
+		fmt.Fprintf(w, "%s\t%s\n", repoName+":"+img.Tag, img.Digest)
+	}
+
+	w.Flush()
+
+	return nil
+}

+ 1 - 0
cmd/app/main.go

@@ -49,6 +49,7 @@ func main() {
 		&ints.GCPIntegration{},
 		&ints.AWSIntegration{},
 		&ints.TokenCache{},
+		&ints.RegTokenCache{},
 	)
 
 	if err != nil {

+ 1 - 0
cmd/migrate/main.go

@@ -40,6 +40,7 @@ func main() {
 		&ints.GCPIntegration{},
 		&ints.AWSIntegration{},
 		&ints.TokenCache{},
+		&ints.RegTokenCache{},
 	)
 
 	if err != nil {

+ 30 - 0
internal/models/integrations/token_cache.go

@@ -36,3 +36,33 @@ type TokenCache struct {
 func (t *TokenCache) IsExpired() bool {
 	return time.Now().After(t.Expiry)
 }
+
+// GetRegTokenCacheFunc is a function that retrieves the token and expiry
+// time from the db
+type GetRegTokenCacheFunc func() (tok *TokenCache, err error)
+
+// SetRegTokenCacheFunc is a function that updates the token cache
+// with a new token and expiry time
+type SetRegTokenCacheFunc func(token string, expiry time.Time) error
+
+// RegTokenCache stores a token and an expiration for the JWT token for a
+// Docker registry. This will never be shared over REST, so no need
+// to externalize.
+type RegTokenCache struct {
+	gorm.Model
+
+	RegistryID uint `json:"registry_id"`
+
+	Expiry time.Time `json:"expiry,omitempty"`
+
+	// ------------------------------------------------------------------
+	// All fields below this line are encrypted before storage
+	// ------------------------------------------------------------------
+
+	Token []byte `json:"access_token"`
+}
+
+// IsExpired returns true if a token is expired, false otherwise
+func (r *RegTokenCache) IsExpired() bool {
+	return time.Now().After(r.Expiry)
+}

+ 5 - 2
internal/models/registry.go

@@ -23,8 +23,11 @@ type Registry struct {
 	GCPIntegrationID uint
 	AWSIntegrationID uint
 
-	// A token cache that can be used by an auth mechanism, if desired
-	TokenCache integrations.TokenCache `json:"token_cache"`
+	// A token cache that can be used by an auth mechanism (integration), if desired
+	IntTokenCache integrations.TokenCache
+
+	// A token cache that can be used by a Docker registry for JWT tokens, if necessary
+	DockerTokenCache integrations.RegTokenCache
 }
 
 // RegistryExternal is an external Registry to be shared over REST

+ 110 - 42
internal/registry/registry.go

@@ -48,20 +48,12 @@ func (r *Registry) ListRepositories(repo repository.Repository) ([]*Repository,
 	}
 
 	if r.GCPIntegrationID != 0 {
-		return r.listGCPRepositories(repo)
+		return r.listGCRRepositories(repo)
 	}
 
 	return nil, fmt.Errorf("error listing repositories")
 }
 
-// ListImages lists the images for an image repository
-func (r *Registry) ListImages(
-	repo repository.Repository,
-	regName string,
-) ([]*Image, error) {
-	return nil, nil
-}
-
 type gcrJWT struct {
 	AccessToken  string `json:"token"`
 	ExpiresInSec int    `json:"expires_in"`
@@ -71,58 +63,83 @@ type gcrRepositoryResp struct {
 	Repositories []string `json:"repositories"`
 }
 
-// TODO -- use a token cache for the JWT token as well
-func (r *Registry) listGCPRepositories(
+func (r *Registry) listGCRRepositories(
 	repo repository.Repository,
 ) ([]*Repository, error) {
-	gcp, err := repo.GCPIntegration.ReadGCPIntegration(
-		r.GCPIntegrationID,
-	)
+	jwtTok := string(r.DockerTokenCache.Token)
 
-	if err != nil {
-		return nil, err
-	}
+	// if a jwt token does not exist or is expired, refresh it
+	if r.DockerTokenCache.IsExpired() || len(jwtTok) == 0 {
+		gcp, err := repo.GCPIntegration.ReadGCPIntegration(
+			r.GCPIntegrationID,
+		)
 
-	// get oauth2 access token
-	oauthTok, err := gcp.GetBearerToken(r.getTokenCache, r.setTokenCacheFunc(repo))
+		if err != nil {
+			return nil, err
+		}
 
-	if err != nil {
-		return nil, err
-	}
+		// get oauth2 access token
+		oauthTok, err := gcp.GetBearerToken(r.getTokenCache, r.setTokenCacheFunc(repo))
 
-	// get jwt token
-	client := &http.Client{}
+		if err != nil {
+			return nil, err
+		}
 
-	req, err := http.NewRequest(
-		"GET",
-		"https://gcr.io/v2/token?service=gcr.io&scope=registry:catalog:*",
-		nil,
-	)
+		// get jwt token
+		client := &http.Client{}
 
-	req.SetBasicAuth("_token", oauthTok)
+		req, err := http.NewRequest(
+			"GET",
+			"https://gcr.io/v2/token?service=gcr.io&scope=registry:catalog:*",
+			nil,
+		)
 
-	resp, err := client.Do(req)
+		req.SetBasicAuth("_token", oauthTok)
 
-	if err != nil {
-		return nil, err
-	}
+		resp, err := client.Do(req)
+
+		if err != nil {
+			return nil, err
+		}
 
-	jwtSource := gcrJWT{}
+		jwtSource := gcrJWT{}
+
+		if err := json.NewDecoder(resp.Body).Decode(&jwtSource); err != nil {
+			return nil, fmt.Errorf("Invalid token JSON from metadata: %v", err)
+		}
+
+		_, err = repo.Registry.UpdateRegistryDockerTokenCache(
+			&ints.RegTokenCache{
+				RegistryID: r.ID,
+				Token:      []byte(jwtSource.AccessToken),
+				// subtract some time from expiry for buffer
+				Expiry: time.Now().Add(time.Second*time.Duration(jwtSource.ExpiresInSec) - 5*time.Second),
+			},
+		)
 
-	if err := json.NewDecoder(resp.Body).Decode(&jwtSource); err != nil {
-		return nil, fmt.Errorf("Invalid token JSON from metadata: %v", err)
+		if err != nil {
+			return nil, err
+		}
+
+		jwtTok = jwtSource.AccessToken
 	}
 
 	// use JWT token to request catalog
-	req, err = http.NewRequest(
+	client := &http.Client{}
+
+	req, err := http.NewRequest(
 		"GET",
 		"https://gcr.io/v2/_catalog",
 		nil,
 	)
 
-	req.Header.Add("Authorization", "Bearer "+jwtSource.AccessToken)
+	if err != nil {
+		return nil, err
+	}
 
-	resp, err = client.Do(req)
+	req.Header.Add("Authorization", "Bearer "+jwtTok)
+
+	resp, err := client.Do(req)
 
 	if err != nil {
 		return nil, err
@@ -181,14 +198,14 @@ func (r *Registry) listECRRepositories(repo repository.Repository) ([]*Repositor
 }
 
 func (r *Registry) getTokenCache() (tok *ints.TokenCache, err error) {
-	return &r.TokenCache, nil
+	return &r.IntTokenCache, nil
 }
 
 func (r *Registry) setTokenCacheFunc(
 	repo repository.Repository,
 ) ints.SetTokenCacheFunc {
 	return func(token string, expiry time.Time) error {
-		_, err := repo.Registry.UpdateRegistryTokenCache(
+		_, err := repo.Registry.UpdateRegistryIntTokenCache(
 			&ints.TokenCache{
 				RegistryID: r.ID,
 				Token:      []byte(token),
@@ -199,3 +216,54 @@ func (r *Registry) setTokenCacheFunc(
 		return err
 	}
 }
+
+// ListImages lists the images for an image repository
+func (r *Registry) ListImages(
+	repoName string,
+	repo repository.Repository,
+) ([]*Image, error) {
+	// switch on the auth mechanism to get a token
+	if r.AWSIntegrationID != 0 {
+		return r.listECRImages(repoName, repo)
+	}
+
+	return nil, fmt.Errorf("error listing images")
+}
+
+func (r *Registry) listECRImages(repoName string, repo repository.Repository) ([]*Image, error) {
+	aws, err := repo.AWSIntegration.ReadAWSIntegration(
+		r.AWSIntegrationID,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	sess, err := aws.GetSession()
+
+	if err != nil {
+		return nil, err
+	}
+
+	svc := ecr.New(sess)
+
+	resp, err := svc.ListImages(&ecr.ListImagesInput{
+		RepositoryName: &repoName,
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	res := make([]*Image, 0)
+
+	for _, img := range resp.ImageIds {
+		res = append(res, &Image{
+			Digest:         *img.ImageDigest,
+			Tag:            *img.ImageTag,
+			RepositoryName: repoName,
+		})
+	}
+
+	return res, nil
+}

+ 1 - 0
internal/repository/gorm/helpers_test.go

@@ -58,6 +58,7 @@ func setupTestEnv(tester *tester, t *testing.T) {
 		&ints.GCPIntegration{},
 		&ints.AWSIntegration{},
 		&ints.TokenCache{},
+		&ints.RegTokenCache{},
 	)
 
 	if err != nil {

+ 72 - 12
internal/repository/gorm/registry.go

@@ -44,13 +44,23 @@ func (repo *RegistryRepository) CreateRegistry(reg *models.Registry) (*models.Re
 	}
 
 	// create a token cache by default
-	assoc = repo.db.Model(reg).Association("TokenCache")
+	assoc = repo.db.Model(reg).Association("IntTokenCache")
 
 	if assoc.Error != nil {
 		return nil, assoc.Error
 	}
 
-	if err := assoc.Append(&reg.TokenCache); err != nil {
+	if err := assoc.Append(&reg.IntTokenCache); err != nil {
+		return nil, err
+	}
+
+	assoc = repo.db.Model(reg).Association("DockerTokenCache")
+
+	if assoc.Error != nil {
+		return nil, assoc.Error
+	}
+
+	if err := assoc.Append(&reg.DockerTokenCache); err != nil {
 		return nil, err
 	}
 
@@ -67,7 +77,7 @@ func (repo *RegistryRepository) CreateRegistry(reg *models.Registry) (*models.Re
 func (repo *RegistryRepository) ReadRegistry(id uint) (*models.Registry, error) {
 	reg := &models.Registry{}
 
-	if err := repo.db.Preload("TokenCache").Where("id = ?", id).First(&reg).Error; err != nil {
+	if err := repo.db.Preload("IntTokenCache").Preload("DockerTokenCache").Where("id = ?", id).First(&reg).Error; err != nil {
 		return nil, err
 	}
 
@@ -83,7 +93,7 @@ func (repo *RegistryRepository) ListRegistriesByProjectID(
 ) ([]*models.Registry, error) {
 	regs := []*models.Registry{}
 
-	if err := repo.db.Preload("TokenCache").Where("project_id = ?", projectID).Find(&regs).Error; err != nil {
+	if err := repo.db.Preload("IntTokenCache").Preload("DockerTokenCache").Where("project_id = ?", projectID).Find(&regs).Error; err != nil {
 		return nil, err
 	}
 
@@ -94,8 +104,8 @@ func (repo *RegistryRepository) ListRegistriesByProjectID(
 	return regs, nil
 }
 
-// UpdateRegistryTokenCache updates the token cache for a registry
-func (repo *RegistryRepository) UpdateRegistryTokenCache(
+// UpdateRegistryIntTokenCache updates the token cache for a registry
+func (repo *RegistryRepository) UpdateRegistryIntTokenCache(
 	tokenCache *ints.TokenCache,
 ) (*models.Registry, error) {
 	if tok := tokenCache.Token; len(tok) > 0 {
@@ -114,8 +124,38 @@ func (repo *RegistryRepository) UpdateRegistryTokenCache(
 		return nil, err
 	}
 
-	registry.TokenCache.Token = tokenCache.Token
-	registry.TokenCache.Expiry = tokenCache.Expiry
+	registry.IntTokenCache.Token = tokenCache.Token
+	registry.IntTokenCache.Expiry = tokenCache.Expiry
+
+	if err := repo.db.Save(registry).Error; err != nil {
+		return nil, err
+	}
+
+	return registry, nil
+}
+
+// UpdateRegistryDockerTokenCache updates the token cache for a registry
+func (repo *RegistryRepository) UpdateRegistryDockerTokenCache(
+	tokenCache *ints.RegTokenCache,
+) (*models.Registry, error) {
+	if tok := tokenCache.Token; len(tok) > 0 {
+		cipherData, err := repository.Encrypt(tok, repo.key)
+
+		if err != nil {
+			return nil, err
+		}
+
+		tokenCache.Token = cipherData
+	}
+
+	registry := &models.Registry{}
+
+	if err := repo.db.Where("id = ?", tokenCache.RegistryID).First(&registry).Error; err != nil {
+		return nil, err
+	}
+
+	registry.DockerTokenCache.Token = tokenCache.Token
+	registry.DockerTokenCache.Expiry = tokenCache.Expiry
 
 	if err := repo.db.Save(registry).Error; err != nil {
 		return nil, err
@@ -130,14 +170,24 @@ func (repo *RegistryRepository) EncryptRegistryData(
 	registry *models.Registry,
 	key *[32]byte,
 ) error {
-	if tok := registry.TokenCache.Token; len(tok) > 0 {
+	if tok := registry.IntTokenCache.Token; len(tok) > 0 {
+		cipherData, err := repository.Encrypt(tok, key)
+
+		if err != nil {
+			return err
+		}
+
+		registry.IntTokenCache.Token = cipherData
+	}
+
+	if tok := registry.DockerTokenCache.Token; len(tok) > 0 {
 		cipherData, err := repository.Encrypt(tok, key)
 
 		if err != nil {
 			return err
 		}
 
-		registry.TokenCache.Token = cipherData
+		registry.DockerTokenCache.Token = cipherData
 	}
 
 	return nil
@@ -149,14 +199,24 @@ func (repo *RegistryRepository) DecryptRegistryData(
 	registry *models.Registry,
 	key *[32]byte,
 ) error {
-	if tok := registry.TokenCache.Token; len(tok) > 0 {
+	if tok := registry.IntTokenCache.Token; len(tok) > 0 {
+		plaintext, err := repository.Decrypt(tok, key)
+
+		if err != nil {
+			return err
+		}
+
+		registry.IntTokenCache.Token = plaintext
+	}
+
+	if tok := registry.DockerTokenCache.Token; len(tok) > 0 {
 		plaintext, err := repository.Decrypt(tok, key)
 
 		if err != nil {
 			return err
 		}
 
-		registry.TokenCache.Token = plaintext
+		registry.DockerTokenCache.Token = plaintext
 	}
 
 	return nil

+ 62 - 14
internal/repository/gorm/registry_test.go

@@ -97,10 +97,14 @@ func TestUpdateRegistryToken(t *testing.T) {
 	reg := &models.Registry{
 		Name:      "registry-test",
 		ProjectID: tester.initProjects[0].Model.ID,
-		TokenCache: ints.TokenCache{
+		IntTokenCache: ints.TokenCache{
 			Token:  []byte("token-1"),
 			Expiry: time.Now().Add(-1 * time.Hour),
 		},
+		DockerTokenCache: ints.RegTokenCache{
+			Token:  []byte("docker-token-1"),
+			Expiry: time.Now().Add(-1 * time.Hour),
+		},
 	}
 
 	reg, err := tester.repo.Registry.CreateRegistry(reg)
@@ -116,23 +120,67 @@ func TestUpdateRegistryToken(t *testing.T) {
 	}
 
 	// make sure registry id of token is 1
-	if reg.TokenCache.RegistryID != 1 {
-		t.Fatalf("incorrect registry id in token cache: expected %d, got %d\n", 1, reg.TokenCache.RegistryID)
+	if reg.IntTokenCache.RegistryID != 1 {
+		t.Fatalf("incorrect registry id in token cache: expected %d, got %d\n", 1, reg.IntTokenCache.RegistryID)
+	}
+
+	// make sure old token is expired
+	if isExpired := reg.IntTokenCache.IsExpired(); !isExpired {
+		t.Fatalf("token was not expired\n")
+	}
+
+	if string(reg.IntTokenCache.Token) != "token-1" {
+		t.Errorf("incorrect token in cache: expected %s, got %s\n", "token-1", reg.IntTokenCache.Token)
+	}
+
+	reg.IntTokenCache.Token = []byte("token-2")
+	reg.IntTokenCache.Expiry = time.Now().Add(24 * time.Hour)
+
+	reg, err = tester.repo.Registry.UpdateRegistryIntTokenCache(&reg.IntTokenCache)
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+	reg, err = tester.repo.Registry.ReadRegistry(reg.Model.ID)
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	// make sure id is 1
+	if reg.Model.ID != 1 {
+		t.Errorf("incorrect registry ID: expected %d, got %d\n", 1, reg.Model.ID)
+	}
+
+	// make sure new token is correct and not expired
+	if reg.IntTokenCache.RegistryID != 1 {
+		t.Fatalf("incorrect registry ID in token cache: expected %d, got %d\n", 1, reg.IntTokenCache.RegistryID)
+	}
+
+	if isExpired := reg.IntTokenCache.IsExpired(); isExpired {
+		t.Fatalf("token was expired\n")
+	}
+
+	if string(reg.IntTokenCache.Token) != "token-2" {
+		t.Errorf("incorrect token in cache: expected %s, got %s\n", "token-2", reg.IntTokenCache.Token)
+	}
+
+	// make sure registry id of docker token is 1
+	if reg.DockerTokenCache.RegistryID != 1 {
+		t.Fatalf("incorrect registry id in token cache: expected %d, got %d\n", 1, reg.DockerTokenCache.RegistryID)
 	}
 
 	// make sure old token is expired
-	if isExpired := reg.TokenCache.IsExpired(); !isExpired {
+	if isExpired := reg.DockerTokenCache.IsExpired(); !isExpired {
 		t.Fatalf("token was not expired\n")
 	}
 
-	if string(reg.TokenCache.Token) != "token-1" {
-		t.Errorf("incorrect token in cache: expected %s, got %s\n", "token-2", reg.TokenCache.Token)
+	if string(reg.DockerTokenCache.Token) != "docker-token-1" {
+		t.Errorf("incorrect token in cache: expected %s, got %s\n", "docker-token-1", reg.DockerTokenCache.Token)
 	}
 
-	reg.TokenCache.Token = []byte("token-2")
-	reg.TokenCache.Expiry = time.Now().Add(24 * time.Hour)
+	reg.DockerTokenCache.Token = []byte("docker-token-2")
+	reg.DockerTokenCache.Expiry = time.Now().Add(24 * time.Hour)
 
-	reg, err = tester.repo.Registry.UpdateRegistryTokenCache(&reg.TokenCache)
+	reg, err = tester.repo.Registry.UpdateRegistryDockerTokenCache(&reg.DockerTokenCache)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -147,15 +195,15 @@ func TestUpdateRegistryToken(t *testing.T) {
 	}
 
 	// make sure new token is correct and not expired
-	if reg.TokenCache.RegistryID != 1 {
-		t.Fatalf("incorrect registry ID in token cache: expected %d, got %d\n", 1, reg.TokenCache.RegistryID)
+	if reg.DockerTokenCache.RegistryID != 1 {
+		t.Fatalf("incorrect registry ID in token cache: expected %d, got %d\n", 1, reg.DockerTokenCache.RegistryID)
 	}
 
-	if isExpired := reg.TokenCache.IsExpired(); isExpired {
+	if isExpired := reg.DockerTokenCache.IsExpired(); isExpired {
 		t.Fatalf("token was expired\n")
 	}
 
-	if string(reg.TokenCache.Token) != "token-2" {
-		t.Errorf("incorrect token in cache: expected %s, got %s\n", "token-2", reg.TokenCache.Token)
+	if string(reg.DockerTokenCache.Token) != "docker-token-2" {
+		t.Errorf("incorrect token in cache: expected %s, got %s\n", "docker-token-2", reg.DockerTokenCache.Token)
 	}
 }

+ 2 - 1
internal/repository/registry.go

@@ -10,5 +10,6 @@ type RegistryRepository interface {
 	CreateRegistry(reg *models.Registry) (*models.Registry, error)
 	ReadRegistry(id uint) (*models.Registry, error)
 	ListRegistriesByProjectID(projectID uint) ([]*models.Registry, error)
-	UpdateRegistryTokenCache(tokenCache *ints.TokenCache) (*models.Registry, error)
+	UpdateRegistryIntTokenCache(tokenCache *ints.TokenCache) (*models.Registry, error)
+	UpdateRegistryDockerTokenCache(tokenCache *ints.RegTokenCache) (*models.Registry, error)
 }

+ 19 - 4
internal/repository/test/registry.go

@@ -73,8 +73,8 @@ func (repo *RegistryRepository) ListRegistriesByProjectID(
 	return res, nil
 }
 
-// UpdateRegistryTokenCache updates the token cache for a registry
-func (repo *RegistryRepository) UpdateRegistryTokenCache(
+// UpdateRegistryIntTokenCache updates the token cache for a registry
+func (repo *RegistryRepository) UpdateRegistryIntTokenCache(
 	tokenCache *ints.TokenCache,
 ) (*models.Registry, error) {
 	if !repo.canQuery {
@@ -82,8 +82,23 @@ func (repo *RegistryRepository) UpdateRegistryTokenCache(
 	}
 
 	index := int(tokenCache.RegistryID - 1)
-	repo.registries[index].TokenCache.Token = tokenCache.Token
-	repo.registries[index].TokenCache.Expiry = tokenCache.Expiry
+	repo.registries[index].IntTokenCache.Token = tokenCache.Token
+	repo.registries[index].IntTokenCache.Expiry = tokenCache.Expiry
+
+	return repo.registries[index], nil
+}
+
+// UpdateRegistryDockerTokenCache updates the token cache for a registry
+func (repo *RegistryRepository) UpdateRegistryDockerTokenCache(
+	tokenCache *ints.RegTokenCache,
+) (*models.Registry, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot write database")
+	}
+
+	index := int(tokenCache.RegistryID - 1)
+	repo.registries[index].DockerTokenCache.Token = tokenCache.Token
+	repo.registries[index].DockerTokenCache.Expiry = tokenCache.Expiry
 
 	return repo.registries[index], nil
 }

+ 48 - 19
server/api/registry_handler.go

@@ -2,7 +2,6 @@ package api
 
 import (
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"strconv"
 
@@ -11,10 +10,6 @@ import (
 	"github.com/go-chi/chi"
 	"github.com/porter-dev/porter/internal/forms"
 	"github.com/porter-dev/porter/internal/models"
-
-	"github.com/google/go-containerregistry/pkg/authn"
-	"github.com/google/go-containerregistry/pkg/name"
-	"github.com/google/go-containerregistry/pkg/v1/remote"
 )
 
 // HandleCreateRegistry creates a new registry
@@ -137,31 +132,65 @@ func (app *App) HandleListRepositories(w http.ResponseWriter, r *http.Request) {
 
 // HandleListImages retrieves a list of repo names
 func (app *App) HandleListImages(w http.ResponseWriter, r *http.Request) {
-	ref, err := name.ParseReference("gcr.io/google-containers/pause")
-	if err != nil {
-		fmt.Println(err)
+	regID, err := strconv.ParseUint(chi.URLParam(r, "registry_id"), 0, 64)
+
+	if err != nil || regID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
 		return
 	}
 
-	img, err := remote.Image(ref)
+	repoName := chi.URLParam(r, "repo_name")
+
+	reg, err := app.repo.Registry.ReadRegistry(uint(regID))
+
 	if err != nil {
-		fmt.Println(err)
+		app.handleErrorRead(err, ErrProjectDataRead, w)
 		return
 	}
-	fmt.Println(img.Size())
 
-	ctx := r.Context()
-	reg, err := name.NewRegistry("index.docker.io")
+	// cast to a registry from registry package
+	_reg := registry.Registry(*reg)
+	regAPI := &_reg
+
+	imgs, err := regAPI.ListImages(repoName, *app.repo)
+
 	if err != nil {
-		fmt.Println("fuk")
-		fmt.Println(err)
+		app.handleErrorRead(err, ErrProjectDataRead, w)
 		return
 	}
 
-	stuff, err := remote.Catalog(ctx, reg, remote.WithAuthFromKeychain(authn.DefaultKeychain))
-	if err != nil {
-		fmt.Println(err)
+	w.WriteHeader(http.StatusOK)
+
+	if err := json.NewEncoder(w).Encode(imgs); err != nil {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
 		return
 	}
-	fmt.Println(stuff[0])
+
+	// ref, err := name.ParseReference("gcr.io/google-containers/pause")
+	// if err != nil {
+	// 	fmt.Println(err)
+	// 	return
+	// }
+
+	// img, err := remote.Image(ref)
+	// if err != nil {
+	// 	fmt.Println(err)
+	// 	return
+	// }
+	// fmt.Println(img.Size())
+
+	// ctx := r.Context()
+	// reg, err := name.NewRegistry("index.docker.io")
+	// if err != nil {
+	// 	fmt.Println("fuk")
+	// 	fmt.Println(err)
+	// 	return
+	// }
+
+	// stuff, err := remote.Catalog(ctx, reg, remote.WithAuthFromKeychain(authn.DefaultKeychain))
+	// if err != nil {
+	// 	fmt.Println(err)
+	// 	return
+	// }
+	// fmt.Println(stuff[0])
 }

+ 14 - 7
server/router/router.go

@@ -215,6 +215,20 @@ func New(
 			),
 		)
 
+		r.Method(
+			"GET",
+			"/projects/{project_id}/registries/{registry_id}/repositories/{repo_name}",
+			auth.DoesUserHaveProjectAccess(
+				auth.DoesUserHaveRegistryAccess(
+					requestlog.NewHandler(a.HandleListImages, l),
+					mw.URLParam,
+					mw.URLParam,
+				),
+				mw.URLParam,
+				mw.WriteAccess,
+			),
+		)
+
 		// /api/projects/{project_id}/releases routes
 		r.Method(
 			"GET",
@@ -346,13 +360,6 @@ func New(
 		// )
 
 		// /api/projects/{project_id}/images routes
-		// TODO: add back project access check
-		r.Method(
-			"GET",
-			"/projects/{project_id}/images",
-			auth.BasicAuthenticate(requestlog.NewHandler(a.HandleListImages, l)),
-		)
-
 		r.Method(
 			"POST",
 			"/projects/{project_id}/deploy",