Parcourir la source

add helm repo get endpoint + middleware

Alexander Belanger il y a 4 ans
Parent
commit
5688a4774e

+ 64 - 0
api/server/authz/helm_repo.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 HelmRepoScopedFactory struct {
+	config *shared.Config
+}
+
+func NewHelmRepoScopedFactory(
+	config *shared.Config,
+) *HelmRepoScopedFactory {
+	return &HelmRepoScopedFactory{config}
+}
+
+func (p *HelmRepoScopedFactory) Middleware(next http.Handler) http.Handler {
+	return &HelmRepoScopedMiddleware{next, p.config}
+}
+
+type HelmRepoScopedMiddleware struct {
+	next   http.Handler
+	config *shared.Config
+}
+
+func (p *HelmRepoScopedMiddleware) 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)
+	helmRepoID := reqScopes[types.HelmRepoScope].Resource.UInt
+
+	helmRepo, err := p.config.Repo.HelmRepo().ReadHelmRepo(proj.ID, helmRepoID)
+
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			apierrors.HandleAPIError(w, p.config.Logger, apierrors.NewErrForbidden(
+				fmt.Errorf("helm repo with id %d not found in project %d", helmRepoID, proj.ID),
+			))
+		} else {
+			apierrors.HandleAPIError(w, p.config.Logger, apierrors.NewErrInternal(err))
+		}
+
+		return
+	}
+
+	ctx := NewHelmRepoContext(r.Context(), helmRepo)
+	r = r.WithContext(ctx)
+	p.next.ServeHTTP(w, r)
+}
+
+func NewHelmRepoContext(ctx context.Context, helmRepo *models.HelmRepo) context.Context {
+	return context.WithValue(ctx, types.HelmRepoScope, helmRepo)
+}

+ 4 - 0
api/server/authz/policy.go

@@ -98,6 +98,10 @@ func getRequestActionForEndpoint(
 			resource.UInt, reqErr = GetURLParamUint(r, string(types.URLParamProjectID))
 		case types.ClusterScope:
 			resource.UInt, reqErr = GetURLParamUint(r, string(types.URLParamClusterID))
+		case types.RegistryScope:
+			resource.UInt, reqErr = GetURLParamUint(r, string(types.URLParamRegistryID))
+		case types.HelmRepoScope:
+			resource.UInt, reqErr = GetURLParamUint(r, string(types.URLParamHelmRepoID))
 		case types.NamespaceScope:
 			resource.Name, reqErr = GetURLParamString(r, string(types.URLParamNamespace))
 		case types.ReleaseScope:

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

@@ -0,0 +1,32 @@
+package helmrepo
+
+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 HelmRepoGetHandler struct {
+	handlers.PorterHandlerWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewHelmRepoGetHandler(
+	config *shared.Config,
+	writer shared.ResultWriter,
+) *HelmRepoGetHandler {
+	return &HelmRepoGetHandler{
+		PorterHandlerWriter:   handlers.NewDefaultPorterHandler(config, nil, writer),
+		KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *HelmRepoGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	helmRepo, _ := r.Context().Value(types.HelmRepoScope).(*models.HelmRepo)
+
+	c.WriteResult(w, helmRepo.ToHelmRepoType())
+}

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

@@ -0,0 +1,83 @@
+package router
+
+import (
+	"github.com/go-chi/chi"
+	"github.com/porter-dev/porter/api/server/handlers/helmrepo"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/types"
+)
+
+func NewHelmRepoScopedRegisterer(children ...*Registerer) *Registerer {
+	return &Registerer{
+		GetRoutes: GetHelmRepoScopedRoutes,
+		Children:  children,
+	}
+}
+
+func GetHelmRepoScopedRoutes(
+	r chi.Router,
+	config *shared.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+	children ...*Registerer,
+) []*Route {
+	routes, projPath := getHelmRepoRoutes(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 getHelmRepoRoutes(
+	r chi.Router,
+	config *shared.Config,
+	basePath *types.Path,
+	factory shared.APIEndpointFactory,
+) ([]*Route, *types.Path) {
+	relPath := "/helmrepos/{helm_repo_id}"
+
+	newPath := &types.Path{
+		Parent:       basePath,
+		RelativePath: relPath,
+	}
+
+	routes := make([]*Route, 0)
+
+	// GET /api/projects/{project_id}/helmrepos/{helm_repo_id} -> registry.NewHelmRepoGetHandler
+	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.HelmRepoScope,
+			},
+		},
+	)
+
+	getHandler := helmrepo.NewHelmRepoGetHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getEndpoint,
+		Handler:  getHandler,
+		Router:   r,
+	})
+
+	return routes, newPath
+}

+ 9 - 1
api/server/router/router.go

@@ -22,7 +22,9 @@ func NewAPIRouter(config *shared.Config) *chi.Mux {
 
 	releaseRegisterer := NewReleaseScopedRegisterer()
 	clusterRegisterer := NewClusterScopedRegisterer(releaseRegisterer)
-	projRegisterer := NewProjectScopedRegisterer(clusterRegisterer)
+	registryRegisterer := NewRegistryScopedRegisterer()
+	helmRepoRegisterer := NewHelmRepoScopedRegisterer()
+	projRegisterer := NewProjectScopedRegisterer(clusterRegisterer, registryRegisterer, helmRepoRegisterer)
 	userRegisterer := NewUserScopedRegisterer(projRegisterer)
 
 	r.Route("/api", func(r chi.Router) {
@@ -84,6 +86,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 "helmrepo-scoped" factory which will create a new helmrepo-scoped request
+	// after authorization. Each subsequent http.Handler can lookup the helm repo in context.
+	helmRepoFactory := authz.NewHelmRepoScopedFactory(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)
@@ -114,6 +120,8 @@ func registerRoutes(config *shared.Config, routes []*Route) {
 				atomicGroup.Use(projFactory.Middleware)
 			case types.ClusterScope:
 				atomicGroup.Use(clusterFactory.Middleware)
+			case types.HelmRepoScope:
+				atomicGroup.Use(helmRepoFactory.Middleware)
 			case types.RegistryScope:
 				atomicGroup.Use(registryFactory.Middleware)
 			case types.ReleaseScope:

+ 18 - 0
api/types/helm_repo.go

@@ -0,0 +1,18 @@
+package types
+
+import "github.com/porter-dev/porter/internal/models/integrations"
+
+type HelmRepo struct {
+	ID uint `json:"id"`
+
+	// The project that this integration belongs to
+	ProjectID uint `json:"project_id"`
+
+	// Name of the repo
+	Name string `json:"name"`
+
+	RepoURL string `json:"repo_name"`
+
+	// The integration service for this registry
+	Service integrations.IntegrationService `json:"service"`
+}

+ 1 - 0
api/types/policy.go

@@ -7,6 +7,7 @@ const (
 	ProjectScope   PermissionScope = "project"
 	ClusterScope   PermissionScope = "cluster"
 	RegistryScope  PermissionScope = "registry"
+	HelmRepoScope  PermissionScope = "helm_repo"
 	NamespaceScope PermissionScope = "namespace"
 	SettingsScope  PermissionScope = "settings"
 	ReleaseScope   PermissionScope = "release"

+ 1 - 0
api/types/request.go

@@ -36,6 +36,7 @@ const (
 	URLParamProjectID      URLParam = "project_id"
 	URLParamClusterID      URLParam = "cluster_id"
 	URLParamRegistryID     URLParam = "registry_id"
+	URLParamHelmRepoID     URLParam = "helm_repo_id"
 	URLParamNamespace      URLParam = "namespace"
 	URLParamReleaseName    URLParam = "name"
 	URLParamReleaseVersion URLParam = "version"

+ 22 - 0
internal/models/helm_repo.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models/integrations"
 	"gorm.io/gorm"
 )
@@ -68,3 +69,24 @@ func (hr *HelmRepo) Externalize() *HelmRepoExternal {
 		Service:   serv,
 	}
 }
+
+// ToHelmRepoType generates an external HelmRepo to be shared over REST
+func (hr *HelmRepo) ToHelmRepoType() *types.HelmRepo {
+	var serv integrations.IntegrationService
+
+	if hr.BasicAuthIntegrationID != 0 {
+		serv = integrations.HelmRepo
+	} else if hr.AWSIntegrationID != 0 {
+		serv = integrations.S3
+	} else if hr.GCPIntegrationID != 0 {
+		serv = integrations.GCS
+	}
+
+	return &types.HelmRepo{
+		ID:        hr.ID,
+		ProjectID: hr.ProjectID,
+		Name:      hr.Name,
+		RepoURL:   hr.RepoURL,
+		Service:   serv,
+	}
+}

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

@@ -64,10 +64,10 @@ func (repo *HelmRepoRepository) CreateHelmRepo(hr *models.HelmRepo) (*models.Hel
 }
 
 // ReadHelmRepo gets a helm repo specified by a unique id
-func (repo *HelmRepoRepository) ReadHelmRepo(id uint) (*models.HelmRepo, error) {
+func (repo *HelmRepoRepository) ReadHelmRepo(projectID, hrID uint) (*models.HelmRepo, error) {
 	hr := &models.HelmRepo{}
 
-	if err := repo.db.Preload("TokenCache").Where("id = ?", id).First(&hr).Error; err != nil {
+	if err := repo.db.Preload("TokenCache").Where("project_id = ? AND id = ?", projectID, hrID).First(&hr).Error; err != nil {
 		return nil, err
 	}
 

+ 4 - 4
internal/repository/gorm/helm_repo_test.go

@@ -32,7 +32,7 @@ func TestCreateHelmRepo(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	hr, err = tester.repo.HelmRepo().ReadHelmRepo(hr.Model.ID)
+	hr, err = tester.repo.HelmRepo().ReadHelmRepo(tester.initProjects[0].Model.ID, hr.Model.ID)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -114,7 +114,7 @@ func TestUpdateHelmRepo(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	hr, err = tester.repo.HelmRepo().ReadHelmRepo(tester.initHRs[0].ID)
+	hr, err = tester.repo.HelmRepo().ReadHelmRepo(tester.initProjects[0].Model.ID, tester.initHRs[0].ID)
 
 	// make sure data is correct
 	expHelmRepo := models.HelmRepo{
@@ -159,7 +159,7 @@ func TestUpdateHelmRepoToken(t *testing.T) {
 		t.Fatalf("%v\n", err)
 	}
 
-	hr, err = tester.repo.HelmRepo().ReadHelmRepo(hr.Model.ID)
+	hr, err = tester.repo.HelmRepo().ReadHelmRepo(tester.initProjects[0].Model.ID, hr.Model.ID)
 
 	if err != nil {
 		t.Fatalf("%v\n", err)
@@ -186,7 +186,7 @@ func TestUpdateHelmRepoToken(t *testing.T) {
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}
-	hr, err = tester.repo.HelmRepo().ReadHelmRepo(hr.Model.ID)
+	hr, err = tester.repo.HelmRepo().ReadHelmRepo(tester.initProjects[0].Model.ID, hr.Model.ID)
 	if err != nil {
 		t.Fatalf("%v\n", err)
 	}

+ 1 - 1
internal/repository/helm_repo.go

@@ -8,7 +8,7 @@ import (
 // HelmRepoRepository represents the set of queries on the HelmRepo model
 type HelmRepoRepository interface {
 	CreateHelmRepo(repo *models.HelmRepo) (*models.HelmRepo, error)
-	ReadHelmRepo(id uint) (*models.HelmRepo, error)
+	ReadHelmRepo(projectID, hrID uint) (*models.HelmRepo, error)
 	ListHelmReposByProjectID(projectID uint) ([]*models.HelmRepo, error)
 	UpdateHelmRepo(repo *models.HelmRepo) (*models.HelmRepo, error)
 	UpdateHelmRepoTokenCache(tokenCache *ints.HelmRepoTokenCache) (*models.HelmRepo, error)

+ 1 - 0
internal/repository/test/helm_repo.go

@@ -39,6 +39,7 @@ func (repo *HelmRepoRepository) CreateHelmRepo(
 
 // ReadHelmRepo finds a repoistry by id
 func (repo *HelmRepoRepository) ReadHelmRepo(
+	projectID uint,
 	id uint,
 ) (*models.HelmRepo, error) {
 	if !repo.canQuery {