Sfoglia il codice sorgente

Merge branch 'pr/meehawk/2336' into dev

jnfrati 3 anni fa
parent
commit
e0b1cad4f9

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

@@ -727,7 +727,7 @@ func getClusterRoutes(
 	// GET /api/projects/{project_id}/clusters/{cluster_id}/kubeconfig -> cluster.NewGetTemporaryKubeconfigHandler
 	getTemporaryKubeconfigEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
-			Verb:   types.APIVerbGet,
+			Verb:   types.APIVerbUpdate, // we do not want users with no-write access to be able to use this
 			Method: types.HTTPVerbGet,
 			Path: &types.Path{
 				Parent:       basePath,

+ 1 - 1
api/server/shared/config/env/envconfs.go

@@ -100,7 +100,7 @@ type ServerConf struct {
 	ProvisionerTest bool `env:"PROVISIONER_TEST,default=false"`
 
 	// Disable filtering for project creation
-	DisableAllowlist bool `env:"DISABLE_ALLOWLIST,default=false"`
+	DisableAllowlist bool `env:"DISABLE_ALLOWLIST,default=true"`
 
 	// Enable gitlab integration
 	EnableGitlab bool `env:"ENABLE_GITLAB,default=false"`

+ 60 - 0
cli/cmd/helm.go

@@ -0,0 +1,60 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/spf13/cobra"
+)
+
+var helmCmd = &cobra.Command{
+	Use:   "helm",
+	Short: "Use helm to interact with a Porter cluster",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, runHelm)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(helmCmd)
+}
+
+func runHelm(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
+	_, err := exec.LookPath("helm")
+
+	if err != nil {
+		return fmt.Errorf("error finding helm: %w", err)
+	}
+
+	tmpFile, err := downloadTempKubeconfig(client)
+
+	if err != nil {
+		return err
+	}
+
+	defer func() {
+		os.Remove(tmpFile)
+	}()
+
+	os.Setenv("KUBECONFIG", tmpFile)
+
+	cmd := exec.Command("helm", args...)
+
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+
+	err = cmd.Run()
+
+	if err != nil {
+		return fmt.Errorf("error running helm: %w", err)
+	}
+
+	return nil
+}

+ 85 - 0
cli/cmd/kubectl.go

@@ -0,0 +1,85 @@
+package cmd
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/spf13/cobra"
+)
+
+var kubectlCmd = &cobra.Command{
+	Use:   "kubectl",
+	Short: "Use kubectl to interact with a Porter cluster",
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, runKubectl)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(kubectlCmd)
+}
+
+func runKubectl(_ *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
+	_, err := exec.LookPath("kubectl")
+
+	if err != nil {
+		return fmt.Errorf("error finding kubectl: %w", err)
+	}
+
+	tmpFile, err := downloadTempKubeconfig(client)
+
+	if err != nil {
+		return err
+	}
+
+	defer func() {
+		os.Remove(tmpFile)
+	}()
+
+	os.Setenv("KUBECONFIG", tmpFile)
+
+	cmd := exec.Command("kubectl", args...)
+
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+
+	err = cmd.Run()
+
+	if err != nil {
+		return fmt.Errorf("error running helm: %w", err)
+	}
+
+	return nil
+}
+
+func downloadTempKubeconfig(client *api.Client) (string, error) {
+	tmpFile, err := os.CreateTemp("", "porter_kubeconfig_*.yaml")
+
+	if err != nil {
+		return "", fmt.Errorf("error creating temp file for kubeconfig: %w", err)
+	}
+
+	defer tmpFile.Close()
+
+	resp, err := client.GetKubeconfig(context.Background(), cliConf.Project, cliConf.Cluster, cliConf.Kubeconfig)
+
+	if err != nil {
+		return "", fmt.Errorf("error fetching kubeconfig for cluster: %w", err)
+	}
+
+	_, err = tmpFile.Write(resp.Kubeconfig)
+
+	if err != nil {
+		return "", fmt.Errorf("error writing kubeconfig to temp file: %w", err)
+	}
+
+	return tmpFile.Name(), nil
+}

+ 18 - 8
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -77,15 +77,16 @@ const LogsFC: React.FC<{
     ) {
       return previousLogs?.map((log, i) => {
         return (
-          <Log key={i}>
-            {log.map((ansi, j) => {
+          <Log key={[log.lineNumber, i].join(".")}>
+            <span className="line-number">{log.lineNumber}</span>
+            {log.line.map((ansi, j) => {
               if (ansi.clearLine) {
                 return null;
               }
 
               return (
-                <LogSpan key={i + "." + j} ansi={ansi}>
-                  {ansi.content.replace(/ /g, "\u00a0")}
+                <LogSpan key={[log.lineNumber, i, j].join(".")} ansi={ansi}>
+                  {ansi.content.trim().replace(/ /g, "\u00a0")}
                 </LogSpan>
               );
             })}
@@ -108,15 +109,16 @@ const LogsFC: React.FC<{
 
     return logs?.map((log, i) => {
       return (
-        <Log key={i}>
-          {log.map((ansi, j) => {
+        <Log key={[log.lineNumber, i].join(".")}>
+          <span className="line-number">{log.lineNumber}</span>
+          {log.line.map((ansi, j) => {
             if (ansi.clearLine) {
               return null;
             }
 
             return (
-              <LogSpan key={i + "." + j} ansi={ansi}>
-                {ansi.content.replace(/ /g, "\u00a0")}
+              <LogSpan key={[log.lineNumber, i, j].join(".")} ansi={ansi}>
+                {ansi.content.trim().replace(/ /g, "\u00a0")}
               </LogSpan>
             );
           })}
@@ -351,6 +353,14 @@ const Message = styled.div`
 
 const Log = styled.div`
   font-family: monospace;
+  & > .line-number {
+    display: inline-block;
+    text-align: right;
+    min-width: 35px;
+    margin-right: 8px;
+    opacity: 0.3;
+    font-family: monospace;
+  }
 `;
 
 const LogSpan = styled.span`

+ 37 - 24
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/useLogs.ts

@@ -8,22 +8,26 @@ import { SelectedPodType } from "./types";
 const MAX_LOGS = 5000;
 const LOGS_BUFFER_SIZE = 1000;
 
+interface Log {
+  line: Anser.AnserJsonEntry[];
+  lineNumber: number;
+}
+
 export const useLogs = (
   currentPod: SelectedPodType,
   scroll?: (smooth: boolean) => void
 ) => {
-  let logsBufferRef = useRef<Anser.AnserJsonEntry[][]>([]);
+  let logsBufferRef = useRef<Log[]>([]);
   const currentPodName = useRef<string>();
 
   const { currentCluster, currentProject } = useContext(Context);
   const [containers, setContainers] = useState<string[]>([]);
   const [currentContainer, setCurrentContainer] = useState<string>("");
   const [logs, setLogs] = useState<{
-    [key: string]: Anser.AnserJsonEntry[][];
+    [key: string]: Log[];
   }>({});
-
   const [prevLogs, setPrevLogs] = useState<{
-    [key: string]: Anser.AnserJsonEntry[][];
+    [key: string]: Log[];
   }>({});
 
   const {
@@ -48,14 +52,16 @@ export const useLogs = (
       )
       .then((res) => res.data);
 
-    let processedLogs = [] as Anser.AnserJsonEntry[][];
-
-    events.items.forEach((evt: any) => {
+    const processedLogs: Log[] = events.items.map((evt: any, idx: number) => {
       let ansiEvtType = evt.type == "Warning" ? "\u001b[31m" : "\u001b[32m";
       let ansiLog = Anser.ansiToJson(
         `${ansiEvtType}${evt.type}\u001b[0m \t \u001b[43m\u001b[34m\t${evt.reason} \u001b[0m \t ${evt.message}`
       );
-      processedLogs.push(ansiLog);
+
+      return {
+        line: ansiLog,
+        lineNumber: idx + 1,
+      };
     });
 
     // SET LOGS FOR SYSTEM
@@ -82,12 +88,13 @@ export const useLogs = (
         )
         .then((res) => res.data);
       // Process logs
-      const processedLogs: Anser.AnserJsonEntry[][] = logs.previous_logs.map(
-        (currentLog) => {
-          let ansiLog = Anser.ansiToJson(currentLog);
-          return ansiLog;
-        }
-      );
+      const processedLogs: Log[] = logs.previous_logs.map((currentLog, idx) => {
+        let ansiLog = Anser.ansiToJson(currentLog);
+        return {
+          line: ansiLog,
+          lineNumber: idx + 1,
+        };
+      });
 
       setPrevLogs((pl) => ({
         ...pl,
@@ -101,15 +108,17 @@ export const useLogs = (
    * @param containerName Name of the container
    * @param newLogs New logs to update for
    */
-  const updateContainerLogs = (
-    containerName: string,
-    newLogs: Anser.AnserJsonEntry[][]
-  ) => {
+  const updateContainerLogs = (containerName: string, newLogs: Log[]) => {
     setLogs((logs) => {
-      const tmpLogs = { ...logs };
-      let containerLogs = tmpLogs[containerName] || [];
-
-      containerLogs.push(...newLogs);
+      let containerLogs = logs[containerName] || [];
+      const lastLineNumber = containerLogs?.at(-1)?.lineNumber || 0;
+
+      containerLogs.push(
+        ...newLogs.map((l) => ({
+          ...l,
+          lineNumber: lastLineNumber + l.lineNumber,
+        }))
+      );
       // this is technically not as efficient as things could be
       // if there are performance issues, a deque can be used in place of a list
       // for storing logs
@@ -137,7 +146,7 @@ export const useLogs = (
    */
   const flushLogsBuffer = (containerName?: string) => {
     if (containerName) {
-      updateContainerLogs(containerName, logsBufferRef.current);
+      updateContainerLogs(containerName, [...logsBufferRef.current]);
     }
 
     logsBufferRef.current = [];
@@ -154,7 +163,11 @@ export const useLogs = (
       },
       onmessage: (evt: MessageEvent) => {
         let ansiLog = Anser.ansiToJson(evt.data);
-        logsBufferRef.current.push(ansiLog);
+
+        logsBufferRef.current.push({
+          line: ansiLog,
+          lineNumber: logsBufferRef.current.length + 1,
+        });
 
         // If size of the logs buffer is exceeded, immediately flush the buffer
         if (logsBufferRef.current.length > LOGS_BUFFER_SIZE) {