Explorar el Código

API v1 paginated list images endpoint

Mohammed Nafees hace 3 años
padre
commit
29f376213d

+ 95 - 0
api/server/handlers/v1/registry/list_images.go

@@ -0,0 +1,95 @@
+package registry
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/requestutils"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/registry"
+)
+
+type RegistryListImagesHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewRegistryListImagesHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *RegistryListImagesHandler {
+	return &RegistryListImagesHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (c *RegistryListImagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	reg, _ := r.Context().Value(types.RegistryScope).(*models.Registry)
+
+	repoName, reqErr := requestutils.GetURLParamString(r, types.URLParamWildcard)
+
+	if reqErr != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(reqErr, http.StatusBadRequest))
+		return
+	}
+
+	request := &types.V1ListImageRequest{}
+
+	ok := c.DecodeAndValidate(w, r, request)
+
+	if !ok {
+		return
+	}
+
+	res := &types.V1ListImageResponse{}
+
+	// cast to a registry from registry package
+	_reg := registry.Registry(*reg)
+	regAPI := &_reg
+
+	if regAPI.AWSIntegrationID != 0 {
+		if request.Num == 0 {
+			request.Num = 1000
+		} else if request.Num < 1 || request.Num > 1000 {
+			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
+				fmt.Errorf("num should be between 1 and 1000 for ECR images"), http.StatusBadRequest,
+			))
+			return
+		}
+
+		var nextToken *string
+		if request.Next != "" {
+			nextToken = &request.Next
+		}
+
+		imgs, nextToken, err := regAPI.GetECRPaginatedImages(repoName, c.Repo(), request.Num, nextToken)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		if nextToken != nil {
+			res.Next = url.QueryEscape(*nextToken)
+		}
+
+		res.Images = append(res.Images, imgs...)
+	} else {
+		imgs, err := regAPI.ListImages(repoName, c.Repo(), c.Config().DOConf)
+
+		if err != nil {
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+
+		res.Images = append(res.Images, imgs...)
+	}
+
+	c.WriteResult(w, r, res)
+}

+ 24 - 3
api/server/router/v1/registry.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/go-chi/chi"
 	"github.com/porter-dev/porter/api/server/handlers/registry"
+	v1Registry "github.com/porter-dev/porter/api/server/handlers/v1/registry"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/router"
@@ -384,14 +385,33 @@ func getV1RegistryRoutes(
 	//   - name: registry_id
 	//   - name: repository
 	//     in: path
-	//     description: the image repository name
+	//     description: The image repository name
 	//     type: string
 	//     required: true
+	//   - name: num
+	//     in: query
+	//     description: |
+	//       The number of images to list.
+	//       For ECR images, a maximum of 1000 is allowed.
+	//     type: integer
+	//     required: false
+	//     minimum: 1
+	//   - name: next
+	//     in: query
+	//     description: The next page string used for pagination, from a previous request.
+	//     type: string
+	//   - name: next_page
+	//     in: query
+	//     description: The next page number used for pagination, from a previous request.
+	//     type: integer
+	//     minimum: 2
 	// responses:
 	//   '200':
 	//     description: Successfully listed images
 	//     schema:
-	//       $ref: '#/definitions/ListImagesResponse'
+	//       $ref: '#/definitions/V1ListImageResponse'
+	//   '400':
+	//     description: A malformed or bad request
 	//   '403':
 	//     description: Forbidden
 	listImagesEndpoint := factory.NewAPIEndpoint(
@@ -414,8 +434,9 @@ func getV1RegistryRoutes(
 		},
 	)
 
-	listImagesHandler := registry.NewRegistryListImagesHandler(
+	listImagesHandler := v1Registry.NewRegistryListImagesHandler(
 		config,
+		factory.GetDecoderValidator(),
 		factory.GetResultWriter(),
 	)
 

+ 18 - 0
api/types/registry.go

@@ -191,3 +191,21 @@ type ListRegistryRepositoryResponse []*RegistryRepository
 
 // swagger:model ListImagesResponse
 type ListImageResponse []*Image
+
+type V1ListImageRequest struct {
+	Num      int64  `schema:"num"`
+	Next     string `schema:"next"`
+	NextPage uint   `schema:"next_page"`
+}
+
+// swagger:model V1ListImageResponse
+type V1ListImageResponse struct {
+	// The list of repository images with tags
+	Images []*Image `json:"images" form:"required"`
+
+	// The next page number used for pagination, when applicable
+	NextPage uint `json:"num_page,omitempty"`
+
+	// The next page string used for pagination, when application
+	Next string `json:"next,omitempty"`
+}

+ 64 - 0
internal/registry/registry.go

@@ -664,6 +664,70 @@ func (r *Registry) ListImages(
 	return nil, fmt.Errorf("error listing images")
 }
 
+func (r *Registry) GetECRPaginatedImages(
+	repoName string,
+	repo repository.Repository,
+	maxResults int64,
+	nextToken *string,
+) ([]*ptypes.Image, *string, error) {
+	aws, err := repo.AWSIntegration().ReadAWSIntegration(
+		r.ProjectID,
+		r.AWSIntegrationID,
+	)
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	sess, err := aws.GetSession()
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	svc := ecr.New(sess)
+
+	resp, err := svc.ListImages(&ecr.ListImagesInput{
+		RepositoryName: &repoName,
+		MaxResults:     &maxResults,
+		NextToken:      nextToken,
+	})
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if len(resp.ImageIds) == 0 {
+		return []*ptypes.Image{}, nil, nil
+	}
+
+	describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
+		RepositoryName: &repoName,
+		ImageIds:       resp.ImageIds,
+	})
+
+	if err != nil {
+		return nil, nil, err
+	}
+
+	imageDetails := describeResp.ImageDetails
+
+	res := make([]*ptypes.Image, 0)
+
+	for _, img := range imageDetails {
+		for _, tag := range img.ImageTags {
+			res = append(res, &ptypes.Image{
+				Digest:         *img.ImageDigest,
+				Tag:            *tag,
+				RepositoryName: repoName,
+				PushedAt:       img.ImagePushedAt,
+			})
+		}
+	}
+
+	return res, resp.NextToken, nil
+}
+
 func (r *Registry) listECRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
 	aws, err := repo.AWSIntegration().ReadAWSIntegration(
 		r.ProjectID,