Explorar o código

add get registry endpoint

Alexander Belanger %!s(int64=4) %!d(string=hai) anos
pai
achega
dd37b00718

+ 64 - 0
api/server/authz/registry.go

@@ -0,0 +1,64 @@
+package authz
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz/policy"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
+)
+
+type RegistryScopedFactory struct {
+	config *shared.Config
+}
+
+func NewRegistryScopedFactory(
+	config *shared.Config,
+) *RegistryScopedFactory {
+	return &RegistryScopedFactory{config}
+}
+
+func (p *RegistryScopedFactory) Middleware(next http.Handler) http.Handler {
+	return &RegistryScopedMiddleware{next, p.config}
+}
+
+type RegistryScopedMiddleware struct {
+	next   http.Handler
+	config *shared.Config
+}
+
+func (p *RegistryScopedMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// read the project to check scopes
+	proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+
+	// get the registry id from the URL param context
+	reqScopes, _ := r.Context().Value(RequestScopeCtxKey).(map[types.PermissionScope]*policy.RequestAction)
+	registryID := reqScopes[types.RegistryScope].Resource.UInt
+
+	registry, err := p.config.Repo.Registry().ReadRegistry(proj.ID, registryID)
+
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			apierrors.HandleAPIError(w, p.config.Logger, apierrors.NewErrForbidden(
+				fmt.Errorf("registry with id %d not found in project %d", registryID, proj.ID),
+			))
+		} else {
+			apierrors.HandleAPIError(w, p.config.Logger, apierrors.NewErrInternal(err))
+		}
+
+		return
+	}
+
+	ctx := NewRegistryContext(r.Context(), registry)
+	r = r.WithContext(ctx)
+	p.next.ServeHTTP(w, r)
+}
+
+func NewRegistryContext(ctx context.Context, registry *models.Registry) context.Context {
+	return context.WithValue(ctx, types.RegistryScope, registry)
+}

+ 32 - 0
api/server/handlers/registry/get.go

@@ -0,0 +1,32 @@
+package registry
+
+import (
+	"net/http"
+
+	"github.com/porter-dev/porter/api/server/authz"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type RegistryGetHandler struct {
+	handlers.PorterHandlerWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewRegistryGetHandler(
+	config *shared.Config,
+	writer shared.ResultWriter,
+) *RegistryGetHandler {
+	return &RegistryGetHandler{
+		PorterHandlerWriter:   handlers.NewDefaultPorterHandler(config, nil, writer),
+		KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *RegistryGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	registry, _ := r.Context().Value(types.RegistryScope).(*models.Registry)
+
+	c.WriteResult(w, registry.ToRegistryType())
+}

+ 83 - 0
api/server/router/registry.go

@@ -0,0 +1,83 @@
+package router
+
+import (
+	"github.com/go-chi/chi"
+	"github.com/porter-dev/porter/api/server/handlers/registry"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/types"
+)
+
+func NewRegistryScopedRegisterer(children ...*Registerer) *Registerer {
+	return &Registerer{
+		GetRoutes: GetRegistryScopedRoutes,
+		Children:  children,
+	}
+}
+
+func GetRegistryScopedRoutes(
+	r chi.Router,
+	config *shared.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+	children ...*Registerer,
+) []*Route {
+	routes, projPath := getRegistryRoutes(r, config, basePath, factory)
+
+	if len(children) > 0 {
+		r.Route(projPath.RelativePath, func(r chi.Router) {
+			for _, child := range children {
+				childRoutes := child.GetRoutes(r, config, basePath, factory, child.Children...)
+
+				routes = append(routes, childRoutes...)
+			}
+		})
+	}
+
+	return routes
+}
+
+func getRegistryRoutes(
+	r chi.Router,
+	config *shared.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+) ([]*Route, *types.Path) {
+	relPath := "/registries/{registry_id}"
+
+	newPath := &types.Path{
+		Parent:       basePath,
+		RelativePath: relPath,
+	}
+
+	routes := make([]*Route, 0)
+
+	// GET /api/projects/{project_id}/registries/{registry_id} -> registry.NewRegistryGetHandler
+	getEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath,
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.RegistryScope,
+			},
+		},
+	)
+
+	getHandler := registry.NewRegistryGetHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getEndpoint,
+		Handler:  getHandler,
+		Router:   r,
+	})
+
+	return routes, newPath
+}

+ 6 - 0
api/server/router/router.go

@@ -84,6 +84,10 @@ func registerRoutes(config *shared.Config, routes []*Route) {
 	// after authorization. Each subsequent http.Handler can lookup the cluster in context.
 	clusterFactory := authz.NewClusterScopedFactory(config)
 
+	// Create a new "registry-scoped" factory which will create a new registry-scoped request
+	// after authorization. Each subsequent http.Handler can lookup the registry in context.
+	registryFactory := authz.NewRegistryScopedFactory(config)
+
 	// Create a new "release-scoped" factory which will create a new release-scoped request
 	// after authorization. Each subsequent http.Handler can lookup the release in context.
 	releaseFactory := authz.NewReleaseScopedFactory(config)
@@ -110,6 +114,8 @@ func registerRoutes(config *shared.Config, routes []*Route) {
 				atomicGroup.Use(projFactory.Middleware)
 			case types.ClusterScope:
 				atomicGroup.Use(clusterFactory.Middleware)
+			case types.RegistryScope:
+				atomicGroup.Use(registryFactory.Middleware)
 			case types.ReleaseScope:
 				atomicGroup.Use(releaseFactory.Middleware)
 			}

+ 4 - 8
api/types/policy.go

@@ -6,6 +6,7 @@ const (
 	UserScope      PermissionScope = "user"
 	ProjectScope   PermissionScope = "project"
 	ClusterScope   PermissionScope = "cluster"
+	RegistryScope  PermissionScope = "registry"
 	NamespaceScope PermissionScope = "namespace"
 	SettingsScope  PermissionScope = "settings"
 	ReleaseScope   PermissionScope = "release"
@@ -25,14 +26,8 @@ type PolicyDocument struct {
 
 type ScopeTree map[PermissionScope]ScopeTree
 
-/* ScopeHeirarchy describes the scope tree:
-			Project
-		   /	   \
-		Cluster   Settings
-		/
-	Namespace
-       |
-	 Release
+/* ScopeHeirarchy describes the tree of scopes, i.e. Cluster, Registry, and Settings
+are children of Project, Namespace is a child of Cluster, etc.
 */
 var ScopeHeirarchy = ScopeTree{
 	ProjectScope: {
@@ -41,6 +36,7 @@ var ScopeHeirarchy = ScopeTree{
 				ReleaseScope: {},
 			},
 		},
+		RegistryScope: {},
 		SettingsScope: {},
 	},
 }

+ 22 - 0
api/types/registry.go

@@ -0,0 +1,22 @@
+package types
+
+import "github.com/porter-dev/porter/internal/models/integrations"
+
+type Registry struct {
+	ID uint `json:"id"`
+
+	// The project that this integration belongs to
+	ProjectID uint `json:"project_id"`
+
+	// Name of the registry
+	Name string `json:"name"`
+
+	// URL of the registry
+	URL string `json:"url"`
+
+	// The integration service for this registry
+	Service integrations.IntegrationService `json:"service"`
+
+	// The infra id, if registry was provisioned with Porter
+	InfraID uint `json:"infra_id"`
+}

+ 1 - 0
api/types/request.go

@@ -35,6 +35,7 @@ type URLParam string
 const (
 	URLParamProjectID      URLParam = "project_id"
 	URLParamClusterID      URLParam = "cluster_id"
+	URLParamRegistryID     URLParam = "registry_id"
 	URLParamNamespace      URLParam = "namespace"
 	URLParamReleaseName    URLParam = "name"
 	URLParamReleaseVersion URLParam = "version"

+ 31 - 0
internal/models/registry.go

@@ -3,6 +3,7 @@ package models
 import (
 	"strings"
 
+	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models/integrations"
 	"gorm.io/gorm"
 )
@@ -87,3 +88,33 @@ func (r *Registry) Externalize() *RegistryExternal {
 		InfraID:   r.InfraID,
 	}
 }
+
+func (r *Registry) ToRegistryType() *types.Registry {
+	var serv integrations.IntegrationService
+
+	if r.AWSIntegrationID != 0 {
+		serv = integrations.ECR
+	} else if r.GCPIntegrationID != 0 {
+		serv = integrations.GCR
+	} else if r.DOIntegrationID != 0 {
+		serv = integrations.DOCR
+	} else if strings.Contains(r.URL, "index.docker.io") {
+		serv = integrations.DockerHub
+	}
+
+	uri := r.URL
+
+	// remove the protocol
+	if splStr := strings.Split(uri, "://"); len(splStr) > 1 {
+		uri = splStr[1]
+	}
+
+	return &types.Registry{
+		ID:        r.ID,
+		ProjectID: r.ProjectID,
+		Name:      r.Name,
+		URL:       uri,
+		Service:   serv,
+		InfraID:   r.InfraID,
+	}
+}

+ 1 - 1
internal/registry/registry.go

@@ -358,7 +358,7 @@ func (r *Registry) getTokenCacheFunc(
 	repo repository.Repository,
 ) ints.GetTokenCacheFunc {
 	return func() (tok *ints.TokenCache, err error) {
-		reg, err := repo.Registry().ReadRegistry(r.ID)
+		reg, err := repo.Registry().ReadRegistry(r.ProjectID, r.ID)
 
 		if err != nil {
 			return nil, err

+ 2 - 2
internal/repository/gorm/git_action_config_test.go

@@ -49,7 +49,7 @@ func TestCreateGitActionConfig(t *testing.T) {
 	// reset fields for reflect.DeepEqual
 	ga.Model = orm.Model{}
 
-	if diff := deep.Equal(expGA, *ga); diff != nil {
+	if diff := deep.Equal(&expGA, ga); diff != nil {
 		t.Errorf("incorrect git action config")
 		t.Error(diff)
 	}
@@ -64,7 +64,7 @@ func TestCreateGitActionConfig(t *testing.T) {
 	gotReleaseGA := release.GitActionConfig
 	gotReleaseGA.Model = orm.Model{}
 
-	if diff := deep.Equal(expGA, gotReleaseGA); diff != nil {
+	if diff := deep.Equal(&expGA, gotReleaseGA); diff != nil {
 		t.Errorf("incorrect git action config")
 		t.Error(diff)
 	}

+ 2 - 2
internal/repository/gorm/registry.go

@@ -64,10 +64,10 @@ func (repo *RegistryRepository) CreateRegistry(reg *models.Registry) (*models.Re
 }
 
 // ReadRegistry gets a registry specified by a unique id
-func (repo *RegistryRepository) ReadRegistry(id uint) (*models.Registry, error) {
+func (repo *RegistryRepository) ReadRegistry(projectID, regID 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("TokenCache").Where("project_id = ? AND id = ?", projectID, regID).First(&reg).Error; err != nil {
 		return nil, err
 	}
 

+ 6 - 6
internal/repository/gorm/registry_test.go

@@ -31,7 +31,7 @@ func TestCreateRegistry(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	reg, err = tester.repo.Registry().ReadRegistry(reg.Model.ID)
+	reg, err = tester.repo.Registry().ReadRegistry(tester.initProjects[0].Model.ID, reg.Model.ID)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -108,7 +108,7 @@ func TestUpdateRegistry(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	reg, err = tester.repo.Registry().ReadRegistry(tester.initRegs[0].ID)
+	reg, err = tester.repo.Registry().ReadRegistry(tester.initProjects[0].Model.ID, tester.initRegs[0].ID)
 
 	// make sure data is correct
 	expRegistry := models.Registry{
@@ -151,7 +151,7 @@ func TestUpdateRegistryToken(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	reg, err = tester.repo.Registry().ReadRegistry(reg.Model.ID)
+	reg, err = tester.repo.Registry().ReadRegistry(tester.initProjects[0].Model.ID, reg.Model.ID)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -178,7 +178,7 @@ func TestUpdateRegistryToken(t *testing.T) {
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
-	reg, err = tester.repo.Registry().ReadRegistry(reg.Model.ID)
+	reg, err = tester.repo.Registry().ReadRegistry(tester.initProjects[0].Model.ID, reg.Model.ID)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
@@ -212,7 +212,7 @@ func TestDeleteRegistry(t *testing.T) {
 	initRegistry(tester, t)
 	defer cleanup(tester, t)
 
-	reg, err := tester.repo.Registry().ReadRegistry(tester.initRegs[0].Model.ID)
+	reg, err := tester.repo.Registry().ReadRegistry(tester.initProjects[0].Model.ID, tester.initRegs[0].Model.ID)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -224,7 +224,7 @@ func TestDeleteRegistry(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	_, err = tester.repo.Registry().ReadRegistry(tester.initRegs[0].Model.ID)
+	_, err = tester.repo.Registry().ReadRegistry(tester.initProjects[0].Model.ID, tester.initRegs[0].Model.ID)
 
 	if err != orm.ErrRecordNotFound {
 		t.Fatalf("incorrect error: expected %v, got %v\n", orm.ErrRecordNotFound, err)

+ 1 - 1
internal/repository/registry.go

@@ -8,7 +8,7 @@ import (
 // RegistryRepository represents the set of queries on the Registry model
 type RegistryRepository interface {
 	CreateRegistry(reg *models.Registry) (*models.Registry, error)
-	ReadRegistry(id uint) (*models.Registry, error)
+	ReadRegistry(projectID, regID uint) (*models.Registry, error)
 	ListRegistriesByProjectID(projectID uint) ([]*models.Registry, error)
 	UpdateRegistry(reg *models.Registry) (*models.Registry, error)
 	UpdateRegistryTokenCache(tokenCache *ints.RegTokenCache) (*models.Registry, error)

+ 3 - 3
internal/repository/test/registry.go

@@ -39,17 +39,17 @@ func (repo *RegistryRepository) CreateRegistry(
 
 // ReadRegistry finds a registry by id
 func (repo *RegistryRepository) ReadRegistry(
-	id uint,
+	projectID, regID uint,
 ) (*models.Registry, error) {
 	if !repo.canQuery {
 		return nil, errors.New("Cannot read from database")
 	}
 
-	if int(id-1) >= len(repo.registries) || repo.registries[id-1] == nil {
+	if int(regID-1) >= len(repo.registries) || repo.registries[regID-1] == nil {
 		return nil, gorm.ErrRecordNotFound
 	}
 
-	index := int(id - 1)
+	index := int(regID - 1)
 	return repo.registries[index], nil
 }