Browse Source

<wip> temp commit for debug

Alexander Belanger 4 năm trước cách đây
mục cha
commit
d0f3839359

+ 63 - 0
api/server/handlers/namespace/delete_crd.go

@@ -0,0 +1,63 @@
+package namespace
+
+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/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/templater/dynamic"
+)
+
+type CRDDeleteHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewCRDDeleteHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *CRDDeleteHandler {
+	return &CRDDeleteHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func (c *CRDDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	client, err := c.GetDynamicClient(r, cluster)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	request := &types.DeleteCRDRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	crdWriter := dynamic.NewDynamicTemplateWriter(client, &dynamic.Object{
+		Group:     request.Group,
+		Version:   request.Version,
+		Resource:  request.Resource,
+		Namespace: request.Namespace,
+		Name:      request.Name,
+	}, map[string]interface{}{})
+
+	err = crdWriter.Delete()
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	w.WriteHeader(http.StatusOK)
+}

+ 9 - 3
api/server/handlers/release/get.go

@@ -148,12 +148,18 @@ tabs:
       settings:
         options:
           resource-button:
-            name: "Manual Refresh"
-            description: "This will delete the existing certificate resource."
+            name: "Renew Certificate"
+            description: "This will delete the existing certificate resource, triggering a new certificate request."
             actions:
             - delete:
-                scope: release
+                scope: namespace
                 relative_uri: /crd
+                context:
+                  type: cluster
+                  config:
+                    group: cert-manager.io
+                    version: v1
+                    resource: certificates
       value: |
         .items[] | { 
           metadata: .metadata,

+ 112 - 0
api/server/handlers/release/stream_form.go

@@ -0,0 +1,112 @@
+package release
+
+import (
+	"fmt"
+	"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/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/server/shared/websocket"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/templater/parser"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+type StreamFormHandler struct {
+	handlers.PorterHandlerReadWriter
+	authz.KubernetesAgentGetter
+}
+
+func NewStreamFormHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *StreamFormHandler {
+	return &StreamFormHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+		KubernetesAgentGetter:   authz.NewOutOfClusterAgentGetter(config),
+	}
+}
+
+func onData(val map[string]interface{}) error {
+	fmt.Println("VAL IS", val)
+	return nil
+}
+
+func (c *StreamFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	helmRelease, _ := r.Context().Value(types.ReleaseScope).(*release.Release)
+	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	safeRW := r.Context().Value(types.RequestCtxWebsocketKey).(*websocket.WebsocketSafeReadWriter)
+
+	request := &types.StreamCRDRequest{}
+
+	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	// look for the form using the dynamic client
+	dynClient, err := c.GetDynamicClient(r, cluster)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	parserDef := &parser.ClientConfigDefault{
+		DynamicClient: dynClient,
+		HelmChart:     helmRelease.Chart,
+		HelmRelease:   helmRelease,
+	}
+
+	formData := []byte(certManagerForm)
+
+	// TODO: move this back
+	// var formData []byte
+
+	// for _, file := range helmRelease.Chart.Files {
+	// 	if strings.Contains(file.Name, "form.yaml") {
+	// 		formData = file.Data
+	// 		break
+	// 	}
+	// }
+
+	stopper := make(chan struct{})
+	errorchan := make(chan error)
+	defer close(stopper)
+
+	go func() {
+		// listens for websocket closing handshake
+		for {
+			if _, _, err := safeRW.ReadMessage(); err != nil {
+				errorchan <- nil
+				return
+			}
+		}
+	}()
+
+	err = parser.FormStreamer(parserDef, formData, "", &types.FormContext{
+		Type: "cluster",
+		Config: map[string]string{
+			"group":    request.Group,
+			"resource": request.Resource,
+			"version":  request.Version,
+		},
+	}, onData, stopper)
+
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	for {
+		select {
+		case err := <-errorchan:
+			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+			return
+		}
+	}
+}

+ 29 - 0
api/server/router/namespace.go

@@ -234,6 +234,35 @@ func getNamespaceRoutes(
 		Router:   r,
 	})
 
+	// DELETE /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/crd -> namespace.NewCRDDeleteHandler
+	deleteCRDEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbDelete,
+			Method: types.HTTPVerbDelete,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/crd",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+			},
+		},
+	)
+
+	deleteCRDHandler := namespace.NewCRDDeleteHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: deleteCRDEndpoint,
+		Handler:  deleteCRDHandler,
+		Router:   r,
+	})
+
 	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases -> namespace.NewListReleasesHandler
 	listReleasesEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

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

@@ -82,6 +82,38 @@ func getReleaseRoutes(
 		Router:   r,
 	})
 
+	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/form_stream -> release.NewStreamFormHandler
+	streamFormEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbGet,
+			Method: types.HTTPVerbGet,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/form_stream",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+				types.ClusterScope,
+				types.NamespaceScope,
+				types.ReleaseScope,
+			},
+			IsWebsocket: true,
+		},
+	)
+
+	streamFormHandler := release.NewStreamFormHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &Route{
+		Endpoint: streamFormEndpoint,
+		Handler:  streamFormHandler,
+		Router:   r,
+	})
+
 	// GET /api/projects/{project_id}/clusters/{cluster_id}/namespaces/{namespace}/releases/{name}/{version}/controllers -> release.NewGetControllersHandler
 	getControllersEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{

+ 16 - 0
api/types/crd.go

@@ -0,0 +1,16 @@
+package types
+
+type DeleteCRDRequest struct {
+	Name      string `schema:"name" form:"required"`
+	Namespace string `schema:"namespace" form:"required"`
+	Group     string `schema:"group" form:"required"`
+	Version   string `schema:"version" form:"required"`
+	Resource  string `schema:"resource" form:"required"`
+}
+
+type StreamCRDRequest struct {
+	Namespace string `json:"namespace"`
+	Group     string `json:"group" form:"required"`
+	Version   string `json:"version" form:"required"`
+	Resource  string `json:"resource" form:"required"`
+}

+ 110 - 47
dashboard/src/components/ExpandableResource.tsx

@@ -1,10 +1,12 @@
-import React, { Component } from "react";
+import React, { Component, useContext, useEffect } from "react";
 import styled from "styled-components";
 import { Context } from "shared/Context";
-
+import { useWebsockets } from "shared/hooks/useWebsockets";
 import ResourceTab from "./ResourceTab";
+import SaveButton from "./SaveButton";
+import { baseApi } from "shared/baseApi";
 
-type PropsType = {
+type Props = {
   resource: any;
   button: any;
   handleClick?: () => void;
@@ -13,50 +15,107 @@ type PropsType = {
   roundAllCorners?: boolean;
 };
 
-type StateType = any;
-
-export default class ExpandableResource extends Component<
-  PropsType,
-  StateType
-> {
-  render() {
-    let { resource, button } = this.props;
-
-    console.log("BUTTON IS", button, resource);
-
-    return (
-      <ResourceTab
-        label={resource.label}
-        name={resource.name}
-        status={{ label: resource.status }}
-      >
-        <ExpandedWrapper>
-          <StatusSection>
-            <StatusHeader>
-              <Status>
-                <Key>Status:</Key> {resource.status}
-              </Status>
-              <Timestamp>Updated {resource.timestamp}</Timestamp>
-            </StatusHeader>
-            {resource.message}
-          </StatusSection>
-          {Object.keys(this.props.resource.data).map(
-            (key: string, i: number) => {
-              return (
-                <Pair key={i}>
-                  <Key>{key}:</Key>
-                  {this.props.resource.data[key]}
-                </Pair>
-              );
-            }
-          )}
-        </ExpandedWrapper>
-      </ResourceTab>
-    );
-  }
-}
-
-ExpandableResource.contextType = Context;
+const ExpandableResource: React.FC<Props> = (props) => {
+  const { resource, button } = props;
+  const { currentCluster, currentProject } = useContext(Context);
+
+  const {
+    newWebsocket,
+    openWebsocket,
+    closeAllWebsockets,
+    closeWebsocket,
+  } = useWebsockets();
+
+  useEffect(() => {
+    let apiEndpoint = `/api/projects/${currentProject.id}/clusters/${currentCluster.id}/namespaces/cert-manager/releases/cert-manager/0/form_stream?`;
+    apiEndpoint += "resource=certificates&group=cert-manager.io&version=v1";
+
+    const wsConfig = {
+      onmessage(evt: MessageEvent) {
+        console.log("EVENT IS", evt);
+      },
+      onerror() {
+        closeWebsocket("testing");
+      },
+    };
+
+    newWebsocket("testing", apiEndpoint, wsConfig);
+    openWebsocket("testing");
+  }, []);
+
+  let onSave = () => {
+    let projID = currentProject.id;
+    let clusterID = currentCluster.id;
+    let config = button.actions[0].delete.context.config;
+
+    // TODO: construct the endpoint scope, right now we're just using release scope
+    let uri = `/api/projects/${projID}/clusters/${clusterID}/namespaces/${resource.metadata.namespace}${button.actions[0].delete.relative_uri}`;
+
+    // compute the endpoint using button and target context
+    baseApi<
+      {
+        name: string;
+        namespace: string;
+        group: string;
+        version: string;
+        resource: string;
+      },
+      {}
+    >("DELETE", uri)(
+      "<token>",
+      {
+        name: resource.metadata.name,
+        namespace: resource.metadata.namespace,
+        group: config.group,
+        version: config.version,
+        resource: config.resource,
+      },
+      {}
+    )
+      .then((res) => {
+        console.log("RES IS", res);
+      })
+      .catch((err) => console.log(err));
+  };
+
+  return (
+    <ResourceTab
+      label={resource.label}
+      name={resource.name}
+      status={{ label: resource.status }}
+    >
+      <ExpandedWrapper>
+        <StatusSection>
+          <StatusHeader>
+            <Status>
+              <Key>Status:</Key> {resource.status}
+            </Status>
+            <Timestamp>Updated {resource.timestamp}</Timestamp>
+          </StatusHeader>
+          {resource.message}
+        </StatusSection>
+        {Object.keys(resource.data).map((key: string, i: number) => {
+          return (
+            <Pair key={i}>
+              <Key>{key}:</Key>
+              {resource.data[key]}
+            </Pair>
+          );
+        })}
+        <StyledSaveButton
+          onClick={onSave}
+          clearPosition={true}
+          text={button.name}
+          helper={button.description}
+          statusPosition={"right"}
+          className="expanded-save-button"
+        />
+      </ExpandedWrapper>
+    </ResourceTab>
+  );
+};
+
+export default ExpandableResource;
 
 const Timestamp = styled.div`
   font-size: 12px;
@@ -101,3 +160,7 @@ const Key = styled.div`
   color: #ffffff;
   margin-right: 8px;
 `;
+
+const StyledSaveButton = styled(SaveButton)`
+  margin-top: 20px;
+`;

+ 7 - 1
internal/templater/dynamic/reader.go

@@ -2,6 +2,7 @@ package dynamic
 
 import (
 	"context"
+	"fmt"
 	"time"
 
 	"github.com/porter-dev/porter/internal/templater/utils"
@@ -118,12 +119,17 @@ func (r *TemplateReader) ReadStream(
 
 			u := obj.(*unstructured.Unstructured)
 
-			data, err := utils.QueryValues(u.Object, r.Queries)
+			queryObj := make(map[string]interface{})
+			queryObj["items"] = []interface{}{u}
+
+			data, err := utils.QueryValues(queryObj, r.Queries)
 
 			if err != nil {
 				return
 			}
 
+			fmt.Println("DATA IS", data)
+
 			pkt["data"] = data
 			on(pkt)
 		},

+ 6 - 2
internal/templater/dynamic/writer.go

@@ -5,7 +5,6 @@ import (
 
 	"github.com/porter-dev/porter/internal/templater/utils"
 
-	"github.com/porter-dev/porter/internal/templater"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
@@ -37,7 +36,7 @@ func NewDynamicTemplateWriter(
 	client dynamic.Interface,
 	obj *Object,
 	base map[string]interface{},
-) templater.TemplateWriter {
+) *TemplateWriter {
 	w := &TemplateWriter{
 		Object: obj,
 		Client: client,
@@ -102,3 +101,8 @@ func (w *TemplateWriter) Update(vals map[string]interface{}) (map[string]interfa
 
 	return update.Object, nil
 }
+
+// Delete deletes a dynamic resource, this must be registered with the API server
+func (w *TemplateWriter) Delete() error {
+	return w.resource.Delete(context.TODO(), w.Object.Name, metav1.DeleteOptions{})
+}

+ 62 - 0
internal/templater/parser/parser.go

@@ -96,6 +96,68 @@ func FormYAMLFromBytes(def *ClientConfigDefault, bytes []byte, stateType string)
 	return form, nil
 }
 
+func FormStreamer(
+	def *ClientConfigDefault,
+	bytes []byte,
+	stateType string,
+	targetContext *types.FormContext,
+	on templater.OnDataStream,
+	stopCh <-chan struct{},
+) error {
+
+	fmt.Println("HERE -2")
+
+	form, err := unqueriedFormYAMLFromBytes(bytes)
+	fmt.Println("HERE -1", form, err)
+
+	if err != nil {
+		return err
+	}
+
+	lookup := formToLookupTable(def, form, stateType)
+	fmt.Println("HERE -0.5", lookup)
+
+	for lookupContext, lookupVal := range lookup {
+		fmt.Println("HERE 0")
+		if lookupVal != nil && areContextsEqual(targetContext, lookupContext) {
+			fmt.Println("HERE 1")
+			err := lookupVal.TemplateReader.ReadStream(on, stopCh)
+
+			fmt.Println("HERE 2", err)
+
+			if err != nil {
+				continue
+			}
+		}
+	}
+
+	return nil
+}
+
+func areContextsEqual(context1, context2 *types.FormContext) bool {
+	if context1.Type != context2.Type {
+		return false
+	}
+
+	subset12 := isSubset(context1.Config, context2.Config)
+	subset21 := isSubset(context2.Config, context1.Config)
+
+	return subset12 && subset21
+}
+
+func isSubset(map1, map2 map[string]string) bool {
+	subset12 := true
+
+	for key1, val1 := range map1 {
+		if val2, exists := map2[key1]; !exists || val2 != val1 {
+			subset12 = false
+			break
+		}
+	}
+
+	return subset12
+}
+
 // unqueriedFormYAMLFromBytes returns a FormYAML without values queries populated
 func unqueriedFormYAMLFromBytes(bytes []byte) (*types.FormYAML, error) {
 	// parse bytes into object