فهرست منبع

Merge branch 'master' into nafees/hotfixes

Mohammed Nafees 4 سال پیش
والد
کامیت
96d754fecd
4فایلهای تغییر یافته به همراه402 افزوده شده و 66 حذف شده
  1. 288 0
      cli/cmd/portforward.go
  2. 112 66
      dashboard/src/main/home/cluster-dashboard/expanded-chart/BuildSettingsTab.tsx
  3. 1 0
      go.mod
  4. 1 0
      go.sum

+ 288 - 0
cli/cmd/portforward.go

@@ -0,0 +1,288 @@
+package cmd
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"net/url"
+	"os"
+	"os/signal"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/briandowns/spinner"
+	api "github.com/porter-dev/porter/api/client"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+	"github.com/spf13/cobra"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/client-go/tools/portforward"
+	"k8s.io/client-go/transport/spdy"
+	"k8s.io/kubectl/pkg/util"
+)
+
+var address []string
+
+var portForwardCmd = &cobra.Command{
+	Use:   "port-forward [release] [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N]",
+	Short: "Forward one or more local ports to a pod of a release",
+	Args:  cobra.MinimumNArgs(2),
+	Run: func(cmd *cobra.Command, args []string) {
+		err := checkLoginAndRun(args, portForward)
+
+		if err != nil {
+			os.Exit(1)
+		}
+	},
+}
+
+func init() {
+	portForwardCmd.PersistentFlags().StringVar(
+		&namespace,
+		"namespace",
+		"default",
+		"namespace of the release whose pod you want to port-forward to",
+	)
+
+	portForwardCmd.Flags().StringSliceVar(
+		&address,
+		"address",
+		[]string{"localhost"},
+		"Addresses to listen on (comma separated). Only accepts IP addresses or localhost as a value. "+
+			"When localhost is supplied, kubectl will try  to bind on both 127.0.0.1 and ::1 and will fail "+
+			"if neither of these addresses are available to bind.")
+
+	rootCmd.AddCommand(portForwardCmd)
+}
+
+func forwardPorts(
+	method string,
+	url *url.URL,
+	kubeConfig *rest.Config,
+	address, ports []string,
+	stopChan <-chan struct{},
+	readyChan chan struct{},
+) error {
+	transport, upgrader, err := spdy.RoundTripperFor(kubeConfig)
+
+	if err != nil {
+		return err
+	}
+
+	dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
+	fw, err := portforward.NewOnAddresses(
+		dialer, address, ports, stopChan, readyChan, os.Stdout, os.Stderr)
+
+	if err != nil {
+		return err
+	}
+
+	return fw.ForwardPorts()
+}
+
+// splitPort splits port string which is in form of [LOCAL PORT]:REMOTE PORT
+// and returns local and remote ports separately
+func splitPort(port string) (local, remote string) {
+	parts := strings.Split(port, ":")
+	if len(parts) == 2 {
+		return parts[0], parts[1]
+	}
+
+	return parts[0], parts[0]
+}
+
+func portForward(user *types.GetAuthenticatedUserResponse, client *api.Client, args []string) error {
+	var err error
+	var pod corev1.Pod
+
+	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+	s.Color("cyan")
+	s.Suffix = fmt.Sprintf(" Loading list of pods for %s", args[0])
+	s.Start()
+
+	podsResp, err := client.GetK8sAllPods(context.Background(), cliConf.Project, cliConf.Cluster, namespace, args[0])
+
+	s.Stop()
+
+	if err != nil {
+		return err
+	}
+
+	pods := *podsResp
+
+	if len(pods) > 1 {
+		selectedPod, err := utils.PromptSelect("Select a pod to port-forward", func() []string {
+			var names []string
+
+			for i, pod := range pods {
+				names = append(names, fmt.Sprintf("%d - %s", (i+1), pod.Name))
+			}
+
+			return names
+		}())
+
+		if err != nil {
+			return err
+		}
+
+		podIdxStr := strings.Split(selectedPod, " - ")[0]
+
+		podIdx, err := strconv.Atoi(podIdxStr)
+
+		if err != nil {
+			return err
+		}
+
+		pod = pods[podIdx]
+	} else {
+		pod = pods[0]
+	}
+
+	kubeResp, err := client.GetKubeconfig(context.Background(), cliConf.Project, cliConf.Cluster)
+
+	if err != nil {
+		return err
+	}
+
+	kubeBytes := kubeResp.Kubeconfig
+
+	cmdConf, err := clientcmd.NewClientConfigFromBytes(kubeBytes)
+
+	if err != nil {
+		return err
+	}
+
+	restConf, err := cmdConf.ClientConfig()
+
+	if err != nil {
+		return err
+	}
+
+	restConf.GroupVersion = &schema.GroupVersion{
+		Group:   "api",
+		Version: "v1",
+	}
+
+	restConf.NegotiatedSerializer = runtime.NewSimpleNegotiatedSerializer(runtime.SerializerInfo{})
+
+	restClient, err := rest.RESTClientFor(restConf)
+
+	if err != nil {
+		return err
+	}
+
+	err = checkUDPPortInPod(args[1:], &pod)
+
+	if err != nil {
+		return err
+	}
+
+	ports, err := convertPodNamedPortToNumber(args[1:], pod)
+
+	if err != nil {
+		return err
+	}
+
+	stopChannel := make(chan struct{}, 1)
+	readyChannel := make(chan struct{})
+
+	signals := make(chan os.Signal, 1)
+	signal.Notify(signals, os.Interrupt)
+	defer signal.Stop(signals)
+
+	go func() {
+		<-signals
+		if stopChannel != nil {
+			close(stopChannel)
+		}
+	}()
+
+	req := restClient.Post().
+		Resource("pods").
+		Namespace(namespace).
+		Name(pod.Name).
+		SubResource("portforward")
+
+	return forwardPorts("POST", req.URL(), restConf, address, ports, stopChannel, readyChannel)
+}
+
+func checkUDPPortInPod(ports []string, pod *corev1.Pod) error {
+	udpPorts := sets.NewInt()
+	tcpPorts := sets.NewInt()
+	for _, ct := range pod.Spec.Containers {
+		for _, ctPort := range ct.Ports {
+			portNum := int(ctPort.ContainerPort)
+			switch ctPort.Protocol {
+			case corev1.ProtocolUDP:
+				udpPorts.Insert(portNum)
+			case corev1.ProtocolTCP:
+				tcpPorts.Insert(portNum)
+			}
+		}
+	}
+	return checkUDPPorts(udpPorts.Difference(tcpPorts), ports, pod)
+}
+
+func checkUDPPorts(udpOnlyPorts sets.Int, ports []string, obj metav1.Object) error {
+	for _, port := range ports {
+		_, remotePort := splitPort(port)
+		portNum, err := strconv.Atoi(remotePort)
+		if err != nil {
+			switch v := obj.(type) {
+			case *corev1.Service:
+				svcPort, err := util.LookupServicePortNumberByName(*v, remotePort)
+				if err != nil {
+					return err
+				}
+				portNum = int(svcPort)
+
+			case *corev1.Pod:
+				ctPort, err := util.LookupContainerPortNumberByName(*v, remotePort)
+				if err != nil {
+					return err
+				}
+				portNum = int(ctPort)
+
+			default:
+				return fmt.Errorf("unknown object: %v", obj)
+			}
+		}
+		if udpOnlyPorts.Has(portNum) {
+			return fmt.Errorf("UDP protocol is not supported for %s", remotePort)
+		}
+	}
+	return nil
+}
+
+func convertPodNamedPortToNumber(ports []string, pod corev1.Pod) ([]string, error) {
+	var converted []string
+	for _, port := range ports {
+		localPort, remotePort := splitPort(port)
+
+		containerPortStr := remotePort
+		_, err := strconv.Atoi(remotePort)
+		if err != nil {
+			containerPort, err := util.LookupContainerPortNumberByName(pod, remotePort)
+			if err != nil {
+				return nil, err
+			}
+
+			containerPortStr = strconv.Itoa(int(containerPort))
+		}
+
+		if localPort != remotePort {
+			converted = append(converted, fmt.Sprintf("%s:%s", localPort, containerPortStr))
+		} else {
+			converted = append(converted, containerPortStr)
+		}
+	}
+
+	return converted, nil
+}

+ 112 - 66
dashboard/src/main/home/cluster-dashboard/expanded-chart/BuildSettingsTab.tsx

@@ -4,8 +4,8 @@ import KeyValueArray from "components/form-components/KeyValueArray";
 import SelectRow from "components/form-components/SelectRow";
 import SelectRow from "components/form-components/SelectRow";
 import Loading from "components/Loading";
 import Loading from "components/Loading";
 import MultiSaveButton from "components/MultiSaveButton";
 import MultiSaveButton from "components/MultiSaveButton";
-import _, { unionBy } from "lodash";
-import React, { useContext, useEffect, useMemo, useState } from "react";
+import _, { differenceBy, unionBy } from "lodash";
+import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
 import api from "shared/api";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { Context } from "shared/Context";
 import {
 import {
@@ -81,10 +81,6 @@ const BuildSettingsTab: React.FC<Props> = ({ chart, isPreviousVersion }) => {
       return;
       return;
     }
     }
 
 
-    if (!config.builder.length || !config.buildpacks.length) {
-      throw new Error("You have to select at least one buildpack");
-    }
-
     try {
     try {
       await api.updateBuildConfig<UpdateBuildconfigResponse>(
       await api.updateBuildConfig<UpdateBuildconfigResponse>(
         "<token>",
         "<token>",
@@ -349,6 +345,47 @@ const BuildpackConfigSection: React.FC<{
     []
     []
   );
   );
 
 
+  const state = useRef<null | {
+    [builder: string]: {
+      stack: string;
+      selectedBuildpacks: Buildpack[];
+      availableBuildpacks: Buildpack[];
+    };
+  }>(null);
+
+  const populateState = (
+    builder: string,
+    stack: string,
+    availableBuildpacks: Buildpack[] = [],
+    selectedBuildpacks: Buildpack[] = []
+  ) => {
+    state.current = {
+      ...state.current,
+      [builder]: {
+        stack: stack,
+        availableBuildpacks: availableBuildpacks,
+        selectedBuildpacks: selectedBuildpacks,
+      },
+    };
+  };
+
+  const populateBuildpacks = (
+    userBuildpacks: string[],
+    detectedBuildpacks: Buildpack[]
+  ) => {
+    const customBuildpackFactory = (name: string): Buildpack => ({
+      name: name,
+      buildpack: name,
+      config: null,
+    });
+
+    return userBuildpacks.map(
+      (ub) =>
+        detectedBuildpacks.find((db) => db.buildpack === ub) ||
+        customBuildpackFactory(ub)
+    );
+  };
+
   useEffect(() => {
   useEffect(() => {
     const currentBuildConfig = currentChart?.build_config;
     const currentBuildConfig = currentChart?.build_config;
 
 
@@ -378,38 +415,26 @@ const BuildpackConfigSection: React.FC<{
           builder.builders.find((stack) => stack === currentBuildConfig.builder)
           builder.builders.find((stack) => stack === currentBuildConfig.builder)
         );
         );
 
 
-        const availableBuildpacks = defaultBuilder.others?.filter(
-          (buildpack) => {
-            if (!currentBuildConfig.buildpacks.includes(buildpack.buildpack)) {
-              return true;
-            }
-            return false;
-          }
-        );
-
-        const userAddedBuildpacks = defaultBuilder.others?.filter(
-          (buildpack) => {
-            if (currentBuildConfig.buildpacks.includes(buildpack.buildpack)) {
-              return true;
-            }
-            return false;
-          }
+        const nonSelectedBuilder = builders.find(
+          (builder) =>
+            !builder.builders.find(
+              (stack) => stack === currentBuildConfig.builder
+            )
         );
         );
 
 
-        const customBuildpacks: any = currentBuildConfig.buildpacks
-          .filter(
-            (buildpack) =>
-              URLRegex.test(buildpack) &&
-              !buildpack.includes("gcr.io/paketo-buildpacks")
-          )
-          .map((b) => ({ buildpack: b, name: b }));
+        const fullDetectedBuildpacks = [
+          ...defaultBuilder.detected,
+          ...defaultBuilder.others,
+        ];
 
 
-        console.log(customBuildpacks);
-        console.log(userAddedBuildpacks);
+        const userSelectedBuildpacks = populateBuildpacks(
+          currentBuildConfig.buildpacks,
+          fullDetectedBuildpacks
+        ).filter((b) => b.buildpack);
 
 
-        const detectedBuildpacks = unionBy(
-          [...userAddedBuildpacks, ...customBuildpacks],
-          defaultBuilder.detected,
+        const availableBuildpacks = differenceBy(
+          fullDetectedBuildpacks,
+          userSelectedBuildpacks,
           "buildpack"
           "buildpack"
         );
         );
 
 
@@ -417,15 +442,29 @@ const BuildpackConfigSection: React.FC<{
           return stack === currentBuildConfig.builder;
           return stack === currentBuildConfig.builder;
         });
         });
 
 
+        populateState(
+          defaultBuilder.name.toLowerCase(),
+          defaultStack,
+          userSelectedBuildpacks,
+          availableBuildpacks
+        );
+
+        populateState(
+          nonSelectedBuilder.name.toLowerCase(),
+          nonSelectedBuilder.builders[0],
+          nonSelectedBuilder.others,
+          nonSelectedBuilder.detected
+        );
+
         setBuilders(builders);
         setBuilders(builders);
         setSelectedBuilder(defaultBuilder.name.toLowerCase());
         setSelectedBuilder(defaultBuilder.name.toLowerCase());
 
 
         setStacks(defaultBuilder.builders);
         setStacks(defaultBuilder.builders);
         setSelectedStack(defaultStack);
         setSelectedStack(defaultStack);
-        if (!Array.isArray(detectedBuildpacks)) {
+        if (!Array.isArray(userSelectedBuildpacks)) {
           setSelectedBuildpacks([]);
           setSelectedBuildpacks([]);
         } else {
         } else {
-          setSelectedBuildpacks(detectedBuildpacks);
+          setSelectedBuildpacks(userSelectedBuildpacks);
         }
         }
         if (!Array.isArray(availableBuildpacks)) {
         if (!Array.isArray(availableBuildpacks)) {
           setAvailableBuildpacks([]);
           setAvailableBuildpacks([]);
@@ -449,6 +488,15 @@ const BuildpackConfigSection: React.FC<{
     onChange(buildConfig);
     onChange(buildConfig);
   }, [selectedBuilder, selectedBuildpacks, selectedStack]);
   }, [selectedBuilder, selectedBuildpacks, selectedStack]);
 
 
+  useEffect(() => {
+    populateState(
+      selectedBuilder,
+      selectedStack,
+      availableBuildpacks,
+      selectedBuildpacks
+    );
+  }, [selectedBuilder, selectedBuildpacks, selectedStack, availableBuildpacks]);
+
   const builderOptions = useMemo(() => {
   const builderOptions = useMemo(() => {
     if (!Array.isArray(builders)) {
     if (!Array.isArray(builders)) {
       return;
       return;
@@ -482,27 +530,18 @@ const BuildpackConfigSection: React.FC<{
     const builder = builders.find(
     const builder = builders.find(
       (b) => b.name.toLowerCase() === builderName.toLowerCase()
       (b) => b.name.toLowerCase() === builderName.toLowerCase()
     );
     );
-    const detectedBuildpacks = builder.detected;
-    const availableBuildpacks = builder.others;
-    const defaultStack = builder.builders.find((stack) => {
-      return stack === DEFAULT_HEROKU_STACK || stack === DEFAULT_PAKETO_STACK;
-    });
-    setSelectedBuilder(builderName);
-    setBuilders(builders);
-    setSelectedBuilder(builderName.toLowerCase());
 
 
+    setBuilders(builders);
     setStacks(builder.builders);
     setStacks(builder.builders);
-    setSelectedStack(defaultStack);
 
 
-    if (!Array.isArray(detectedBuildpacks)) {
-      setSelectedBuildpacks([]);
-    } else {
-      setSelectedBuildpacks(detectedBuildpacks);
-    }
-    if (!Array.isArray(availableBuildpacks)) {
-      setAvailableBuildpacks([]);
-    } else {
-      setAvailableBuildpacks(availableBuildpacks);
+    const currState = state.current;
+    if (currState[builderName]) {
+      const stateBuilder = currState[builderName];
+      setSelectedBuilder(builderName);
+      setSelectedStack(stateBuilder.stack);
+      setAvailableBuildpacks(stateBuilder.availableBuildpacks);
+      setSelectedBuildpacks(stateBuilder.selectedBuildpacks);
+      return;
     }
     }
   };
   };
 
 
@@ -510,7 +549,22 @@ const BuildpackConfigSection: React.FC<{
     buildpacks: Buildpack[],
     buildpacks: Buildpack[],
     action: "remove" | "add"
     action: "remove" | "add"
   ) => {
   ) => {
-    return buildpacks?.map((buildpack) => {
+    if (!buildpacks.length && action === "remove") {
+      return (
+        <StyledCard>Buildpacks will be automatically detected.</StyledCard>
+      );
+    }
+
+    if (!buildpacks.length && action === "add") {
+      return (
+        <StyledCard>
+          No additional buildpacks are available. You can add a custom buildpack
+          below.
+        </StyledCard>
+      );
+    }
+
+    return buildpacks?.map((buildpack, i) => {
       const icon = `devicon-${buildpack?.name?.toLowerCase()}-plain colored`;
       const icon = `devicon-${buildpack?.name?.toLowerCase()}-plain colored`;
 
 
       let disableIcon = false;
       let disableIcon = false;
@@ -522,7 +576,7 @@ const BuildpackConfigSection: React.FC<{
       }
       }
 
 
       return (
       return (
-        <StyledCard>
+        <StyledCard key={i}>
           <ContentContainer>
           <ContentContainer>
             <Icon disableMarginRight={disableIcon} className={icon} />
             <Icon disableMarginRight={disableIcon} className={icon} />
             <EventInformation>
             <EventInformation>
@@ -602,7 +656,6 @@ const BuildpackConfigSection: React.FC<{
           setActiveValue={(option) => handleSelectBuilder(option)}
           setActiveValue={(option) => handleSelectBuilder(option)}
           label="Select a builder"
           label="Select a builder"
         />
         />
-
         <SelectRow
         <SelectRow
           value={selectedStack}
           value={selectedStack}
           width="100%"
           width="100%"
@@ -614,20 +667,13 @@ const BuildpackConfigSection: React.FC<{
           The following buildpacks were automatically detected. You can also
           The following buildpacks were automatically detected. You can also
           manually add/remove buildpacks.
           manually add/remove buildpacks.
         </Helper>
         </Helper>
-
-        {!!selectedBuildpacks?.length &&
-          renderBuildpacksList(selectedBuildpacks, "remove")}
-
+        <>{renderBuildpacksList(selectedBuildpacks, "remove")}</>
         <Helper>Available buildpacks:</Helper>
         <Helper>Available buildpacks:</Helper>
-        {!!availableBuildpacks?.length && (
-          <>{renderBuildpacksList(availableBuildpacks, "add")}</>
-        )}
-
+        <>{renderBuildpacksList(availableBuildpacks, "add")}</>
         <Helper>
         <Helper>
           You may also add buildpacks by directly providing their GitHub links
           You may also add buildpacks by directly providing their GitHub links
           or links to ZIP files that contain the buildpack source code.
           or links to ZIP files that contain the buildpack source code.
         </Helper>
         </Helper>
-
         <AddCustomBuildpackForm onAdd={handleAddCustomBuildpack} />
         <AddCustomBuildpackForm onAdd={handleAddCustomBuildpack} />
       </>
       </>
     </BuildpackConfigurationContainer>
     </BuildpackConfigurationContainer>

+ 1 - 0
go.mod

@@ -118,6 +118,7 @@ require (
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
 	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
+	github.com/fvbommel/sortorder v1.0.1 // indirect
 	github.com/gdamore/encoding v1.0.0 // indirect
 	github.com/gdamore/encoding v1.0.0 // indirect
 	github.com/gdamore/tcell/v2 v2.4.0 // indirect
 	github.com/gdamore/tcell/v2 v2.4.0 // indirect
 	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/ghodss/yaml v1.0.0 // indirect

+ 1 - 0
go.sum

@@ -561,6 +561,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
 github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
+github.com/fvbommel/sortorder v1.0.1 h1:dSnXLt4mJYH25uDDGa3biZNQsozaUWDSWeKJ0qqFfzE=
 github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
 github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
 github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
 github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
 github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=