Procházet zdrojové kódy

add webhook endpoints

Alexander Belanger před 4 roky
rodič
revize
7b635f8ea1

+ 77 - 0
api/server/handlers/release/create_webhook.go

@@ -0,0 +1,77 @@
+package release
+
+import (
+	"fmt"
+	"net/http"
+
+	"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/repository"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+type CreateWebhookHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewCreateWebhookHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *CreateWebhookHandler {
+	return &CreateWebhookHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+	}
+}
+
+func (c *CreateWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
+	namespace := r.Context().Value(types.NamespaceScope).(string)
+
+	token, err := repository.GenerateRandomBytes(16)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	// create release with webhook token in db
+	image, ok := helmRelease.Config["image"].(map[string]interface{})
+
+	if !ok {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("Could not find field image in config")))
+		return
+	}
+
+	repository := image["repository"]
+	repoStr, ok := repository.(string)
+
+	if !ok {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("Could not find field repository in config")))
+		return
+	}
+
+	release := &models.Release{
+		ClusterID:    cluster.ID,
+		ProjectID:    cluster.ProjectID,
+		Namespace:    namespace,
+		Name:         name,
+		WebhookToken: token,
+		ImageRepoURI: repoStr,
+	}
+
+	release, err = c.Repo().Release().CreateRelease(release)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, release.ToReleaseType())
+}

+ 7 - 0
api/server/handlers/release/get.go

@@ -11,6 +11,7 @@ import (
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/templater/parser"
+	"gorm.io/gorm"
 	"helm.sh/helm/v3/pkg/release"
 )
 
@@ -41,12 +42,17 @@ func (c *ReleaseGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	release, err := c.Repo().Release().ReadRelease(cluster.ID, helmRelease.Name, helmRelease.Namespace)
 
 	if err == nil {
+		res.PorterRelease = release.ToReleaseType()
+
 		res.ID = release.ID
 		res.WebhookToken = release.WebhookToken
 
 		if release.GitActionConfig != nil {
 			res.GitActionConfig = release.GitActionConfig.ToGitActionConfigType()
 		}
+	} else if err != gorm.ErrRecordNotFound {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
 	}
 
 	// look for the form using the dynamic client
@@ -54,6 +60,7 @@ func (c *ReleaseGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
 	if err != nil {
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
 	}
 
 	parserDef := &parser.ClientConfigDefault{

+ 47 - 0
api/server/handlers/release/get_webhook.go

@@ -0,0 +1,47 @@
+package release
+
+import (
+	"net/http"
+
+	"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"
+	"gorm.io/gorm"
+)
+
+type GetWebhookHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewGetWebhookHandler(
+	config *config.Config,
+	writer shared.ResultWriter,
+) *GetWebhookHandler {
+	return &GetWebhookHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, nil, writer),
+	}
+}
+
+func (c *GetWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	name, _ := requestutils.GetURLParamString(r, types.URLParamReleaseName)
+	namespace := r.Context().Value(types.NamespaceScope).(string)
+
+	release, err := c.Repo().Release().ReadRelease(cluster.ID, name, namespace)
+
+	if err != nil {
+		if err == gorm.ErrRecordNotFound {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
+
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	c.WriteResult(w, r, release.ToReleaseType())
+}

+ 59 - 0
api/server/router/release.go

@@ -261,5 +261,64 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/webhook -> release.NewGetWebhookHandler
+	getWebhookEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: "/releases/{name}/webhook",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+			},
+		},
+	)
+
+	getWebhookHandler := release.NewGetWebhookHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: getWebhookEndpoint,
+		Handler:  getWebhookHandler,
+		Router:   r,
+	})
+
+	// POST /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/webhook -> release.NewCreateWebhookHandler
+	createWebhookEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: "/releases/{name}/webhook",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+				types.ReleaseScope,
+			},
+		},
+	)
+
+	createWebhookHandler := release.NewCreateWebhookHandler(
+		config,
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: createWebhookEndpoint,
+		Handler:  createWebhookHandler,
+		Router:   r,
+	})
+
 	return routes, newPath
 }

+ 5 - 1
api/types/release.go

@@ -5,11 +5,15 @@ import "helm.sh/helm/v3/pkg/release"
 // Release is a helm release with a form attached
 type Release struct {
 	*release.Release
+	*PorterRelease
 
+	Form *FormYAML `json:"form,omitempty"`
+}
+
+type PorterRelease struct {
 	ID              uint             `json:"id"`
 	WebhookToken    string           `json:"webhook_token"`
 	GitActionConfig *GitActionConfig `json:"git_action_config,omitempty"`
-	Form            *FormYAML        `json:"form,omitempty"`
 }
 
 type GetReleaseResponse Release

+ 5 - 4
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -67,11 +67,13 @@ const SettingsSection: React.FC<PropsType> = ({
       .getReleaseToken(
         "<token>",
         {
+        },
+        { 
+          id: currentProject.id, 
+          name: currentChart?.name,
           namespace: currentChart?.namespace,
           cluster_id: currentCluster.id,
-          storage: StorageType.Secret,
-        },
-        { id: currentProject.id, name: currentChart?.name }
+        }
       )
       .then((res) => {
         if (!isSubscribed) {
@@ -155,7 +157,6 @@ const SettingsSection: React.FC<PropsType> = ({
           chart_name,
           namespace,
           cluster_id,
-          storage: StorageType.Secret,
         }
       );
       setCreateWebhookButtonStatus("successful");

+ 6 - 8
dashboard/src/shared/api.tsx

@@ -671,13 +671,12 @@ const getRegistryIntegrations = baseApi("GET", "/api/integrations/registry");
 
 const getReleaseToken = baseApi<
   {
-    namespace: string;
-    cluster_id: number;
-    storage: StorageType;
   },
-  { name: string; id: number }
+  { name: string; id: number, namespace: string; cluster_id: number; }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/releases/${pathParams.name}/webhook_token`;
+  let { id, cluster_id, namespace, name } = pathParams
+
+  return `/api/projects/${id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${name}/webhook`;
 });
 
 const destroyEKS = baseApi<
@@ -1037,12 +1036,11 @@ const createWebhookToken = baseApi<
     chart_name: string;
     namespace: string;
     cluster_id: number;
-    storage: StorageType;
   }
 >(
   "POST",
-  ({ project_id, chart_name, namespace, cluster_id, storage }) =>
-    `/api/projects/${project_id}/releases/${chart_name}/webhook_token?namespace=${namespace}&cluster_id=${cluster_id}&storage=${storage}`
+  ({ project_id, chart_name, namespace, cluster_id }) =>
+    `/api/projects/${project_id}/clusters/${cluster_id}/namespaces/${namespace}/releases/${chart_name}/webhook`
 );
 
 // Bundle export to allow default api import (api.<method> is more readable)

+ 14 - 0
internal/models/release.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"github.com/porter-dev/porter/api/types"
 	"gorm.io/gorm"
 )
 
@@ -40,3 +41,16 @@ func (r *Release) Externalize() *ReleaseExternal {
 		GitActionConfig: r.GitActionConfig.Externalize(),
 	}
 }
+
+func (r *Release) ToReleaseType() *types.PorterRelease {
+	res := &types.PorterRelease{
+		ID:           r.ID,
+		WebhookToken: r.WebhookToken,
+	}
+
+	if r.GitActionConfig != nil {
+		res.GitActionConfig = r.GitActionConfig.ToGitActionConfigType()
+	}
+
+	return res
+}