diff --git a/agent/go.sum b/agent/go.sum index 8a910acd8..0a0b937f6 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -115,6 +115,7 @@ github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -378,6 +379,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/cli/apiserver/provider.go b/cli/apiserver/provider.go index 5c0bda4d6..7a94d0659 100644 --- a/cli/apiserver/provider.go +++ b/cli/apiserver/provider.go @@ -36,6 +36,16 @@ func NewProvider(url string, retries int, timeout time.Duration) *Provider { } } +func NewProviderWithoutRetries(url string, timeout time.Duration) *Provider { + return &Provider{ + url: url, + retries: 1, + client: &http.Client{ + Timeout: timeout, + }, + } +} + func (provider *Provider) TestConnection() error { retriesLeft := provider.retries for retriesLeft > 0 { diff --git a/cli/cmd/common.go b/cli/cmd/common.go index b99b1011d..cba18688b 100644 --- a/cli/cmd/common.go +++ b/cli/cmd/common.go @@ -6,18 +6,18 @@ import ( "errors" "fmt" "github.com/up9inc/mizu/cli/apiserver" + "github.com/up9inc/mizu/cli/config/configStructs" + "github.com/up9inc/mizu/cli/errormessage" "github.com/up9inc/mizu/cli/mizu" "github.com/up9inc/mizu/cli/mizu/fsUtils" "github.com/up9inc/mizu/cli/resources" "github.com/up9inc/mizu/cli/telemetry" + "github.com/up9inc/mizu/cli/uiUtils" "github.com/up9inc/mizu/shared" "path" "time" "github.com/up9inc/mizu/cli/config" - "github.com/up9inc/mizu/cli/config/configStructs" - "github.com/up9inc/mizu/cli/errormessage" - "github.com/up9inc/mizu/cli/uiUtils" "github.com/up9inc/mizu/shared/kubernetes" "github.com/up9inc/mizu/shared/logger" ) @@ -27,14 +27,35 @@ func GetApiServerUrl() string { } func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) { - err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, config.Config.Tap.GuiPort, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName) + httpServer, err := kubernetes.StartProxy(kubernetesProvider, config.Config.Tap.ProxyHost, config.Config.Tap.GuiPort, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName, cancel) if err != nil { logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running k8s proxy %v\n"+ "Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName)) cancel() + return } - logger.Log.Debugf("proxy ended") + apiProvider = apiserver.NewProviderWithoutRetries(GetApiServerUrl(), time.Second) // short check for proxy + if err := apiProvider.TestConnection(); err != nil { + logger.Log.Debugf("Couldn't connect using proxy, stopping proxy and trying to create port-forward") + if err := httpServer.Shutdown(context.Background()); err != nil { + logger.Log.Debugf("Error occurred while stopping proxy %v", errormessage.FormatError(err)) + } + + if err := kubernetes.NewPortForward(kubernetesProvider, config.Config.MizuResourcesNamespace, kubernetes.ApiServerPodName, config.Config.Tap.GuiPort, cancel); err != nil { + logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error occured while running port forward %v\n"+ + "Try setting different port by using --%s", errormessage.FormatError(err), configStructs.GuiPortTapName)) + cancel() + return + } + + apiProvider = apiserver.NewProvider(GetApiServerUrl(), apiserver.DefaultRetries, apiserver.DefaultTimeout) // long check for port-forward + if err := apiProvider.TestConnection(); err != nil { + logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", fsUtils.GetLogFilePath())) + cancel() + return + } + } } func getKubernetesProviderForCli() (*kubernetes.Provider, error) { diff --git a/cli/cmd/installRunner.go b/cli/cmd/installRunner.go index 2802bc011..b3551e6cc 100644 --- a/cli/cmd/installRunner.go +++ b/cli/cmd/installRunner.go @@ -46,7 +46,7 @@ func runMizuInstall() { if err = resources.CreateInstallMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), - config.Config.MizuResourcesNamespace, config.Config.AgentImage, + config.Config.MizuResourcesNamespace, config.Config.AgentImage, config.Config.BasenineImage, nil, defaultMaxEntriesDBSizeBytes, defaultResources, config.Config.ImagePullPolicy(), config.Config.LogLevel(), false); err != nil { var statusError *k8serrors.StatusError diff --git a/cli/cmd/tapRunner.go b/cli/cmd/tapRunner.go index fec222338..b39c79ae9 100644 --- a/cli/cmd/tapRunner.go +++ b/cli/cmd/tapRunner.go @@ -23,7 +23,6 @@ import ( "github.com/up9inc/mizu/cli/config" "github.com/up9inc/mizu/cli/config/configStructs" "github.com/up9inc/mizu/cli/errormessage" - "github.com/up9inc/mizu/cli/mizu/fsUtils" "github.com/up9inc/mizu/cli/uiUtils" "github.com/up9inc/mizu/shared" "github.com/up9inc/mizu/shared/kubernetes" @@ -124,7 +123,7 @@ func RunMizuTap() { } logger.Log.Infof("Waiting for Mizu Agent to start...") - if state.mizuServiceAccountExists, err = resources.CreateTapMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel()); err != nil { + if state.mizuServiceAccountExists, err = resources.CreateTapMizuResources(ctx, kubernetesProvider, serializedValidationRules, serializedContract, serializedMizuConfig, config.Config.IsNsRestrictedMode(), config.Config.MizuResourcesNamespace, config.Config.AgentImage, config.Config.BasenineImage, getSyncEntriesConfig(), config.Config.Tap.MaxEntriesDBSizeBytes(), config.Config.Tap.ApiServerResources, config.Config.ImagePullPolicy(), config.Config.LogLevel()); err != nil { var statusError *k8serrors.StatusError if errors.As(err, &statusError) { if statusError.ErrStatus.Reason == metav1.StatusReasonAlreadyExists { @@ -416,20 +415,15 @@ func watchApiServerEvents(ctx context.Context, kubernetesProvider *kubernetes.Pr } func postApiServerStarted(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc, err error) { - go startProxyReportErrorIfAny(kubernetesProvider, cancel) + startProxyReportErrorIfAny(kubernetesProvider, cancel) - url := GetApiServerUrl() - if err := apiProvider.TestConnection(); err != nil { - logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Couldn't connect to API server, for more info check logs at %s", fsUtils.GetLogFilePath())) - cancel() - return - } options, _ := getMizuApiFilteringOptions() if err = startTapperSyncer(ctx, cancel, kubernetesProvider, state.targetNamespaces, *options, state.startTime); err != nil { logger.Log.Errorf(uiUtils.Error, fmt.Sprintf("Error starting mizu tapper syncer: %v", err)) cancel() } + url := GetApiServerUrl() logger.Log.Infof("Mizu is available at %s", url) if !config.Config.HeadlessMode { uiUtils.OpenBrowser(url) diff --git a/cli/cmd/viewRunner.go b/cli/cmd/viewRunner.go index 2cba89126..9e416dcdd 100644 --- a/cli/cmd/viewRunner.go +++ b/cli/cmd/viewRunner.go @@ -47,8 +47,7 @@ func runMizuView() { return } logger.Log.Infof("Establishing connection to k8s cluster...") - go startProxyReportErrorIfAny(kubernetesProvider, cancel) - + startProxyReportErrorIfAny(kubernetesProvider, cancel) } apiServerProvider := apiserver.NewProvider(url, apiserver.DefaultRetries, apiserver.DefaultTimeout) diff --git a/cli/config/configStruct.go b/cli/config/configStruct.go index 6fad6180d..854adf56f 100644 --- a/cli/config/configStruct.go +++ b/cli/config/configStruct.go @@ -5,6 +5,7 @@ import ( "github.com/op/go-logging" "github.com/up9inc/mizu/cli/config/configStructs" "github.com/up9inc/mizu/cli/mizu" + "github.com/up9inc/mizu/shared" v1 "k8s.io/api/core/v1" "k8s.io/client-go/util/homedir" "os" @@ -26,6 +27,7 @@ type ConfigStruct struct { Auth configStructs.AuthConfig `yaml:"auth"` Config configStructs.ConfigConfig `yaml:"config,omitempty"` AgentImage string `yaml:"agent-image,omitempty" readonly:""` + BasenineImage string `yaml:"basenine-image,omitempty" readonly:""` ImagePullPolicyStr string `yaml:"image-pull-policy" default:"Always"` MizuResourcesNamespace string `yaml:"mizu-resources-namespace" default:"mizu"` Telemetry bool `yaml:"telemetry" default:"true"` @@ -47,6 +49,7 @@ func (config *ConfigStruct) validate() error { } func (config *ConfigStruct) SetDefaults() { + config.BasenineImage = fmt.Sprintf("%s:%s", shared.BasenineImageRepo, shared.BasenineImageTag) config.AgentImage = fmt.Sprintf("gcr.io/up9-docker-hub/mizu/%s:%s", mizu.Branch, mizu.SemVer) config.ConfigFilePath = path.Join(mizu.GetMizuFolderPath(), "config.yaml") } diff --git a/cli/go.sum b/cli/go.sum index cafe918a9..336f9ce44 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -100,6 +100,7 @@ github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -336,6 +337,7 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/cli/resources/createResources.go b/cli/resources/createResources.go index 0e3b7c2dd..9553be6ed 100644 --- a/cli/resources/createResources.go +++ b/cli/resources/createResources.go @@ -15,7 +15,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level) (bool, error) { +func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, basenineImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level) (bool, error) { if !isNsRestrictedMode { if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil { return false, err @@ -42,6 +42,7 @@ func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes. Namespace: mizuResourcesNamespace, PodName: kubernetes.ApiServerPodName, PodImage: agentImage, + BasenineImage: basenineImage, ServiceAccountName: serviceAccountName, IsNamespaceRestricted: isNsRestrictedMode, SyncEntriesConfig: syncEntriesConfig, @@ -65,7 +66,7 @@ func CreateTapMizuResources(ctx context.Context, kubernetesProvider *kubernetes. return mizuServiceAccountExists, nil } -func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) error { +func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kubernetes.Provider, serializedValidationRules string, serializedContract string, serializedMizuConfig string, isNsRestrictedMode bool, mizuResourcesNamespace string, agentImage string, basenineImage string, syncEntriesConfig *shared.SyncEntriesConfig, maxEntriesDBSizeBytes int64, apiServerResources shared.Resources, imagePullPolicy core.PullPolicy, logLevel logging.Level, noPersistentVolumeClaim bool) error { if err := createMizuNamespace(ctx, kubernetesProvider, mizuResourcesNamespace); err != nil { return err } @@ -95,6 +96,7 @@ func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kuberne Namespace: mizuResourcesNamespace, PodName: kubernetes.ApiServerPodName, PodImage: agentImage, + BasenineImage: basenineImage, ServiceAccountName: serviceAccountName, IsNamespaceRestricted: isNsRestrictedMode, SyncEntriesConfig: syncEntriesConfig, @@ -113,7 +115,7 @@ func CreateInstallMizuResources(ctx context.Context, kubernetesProvider *kuberne if err != nil { return err } - logger.Log.Infof("service/%v created", kubernetes.ApiServerPodName) + logger.Log.Infof("service/%v created", kubernetes.ApiServerPodName) return nil } diff --git a/shared/kubernetes/provider.go b/shared/kubernetes/provider.go index 1d5615b6c..5c21ccde7 100644 --- a/shared/kubernetes/provider.go +++ b/shared/kubernetes/provider.go @@ -177,6 +177,7 @@ type ApiServerOptions struct { Namespace string PodName string PodImage string + BasenineImage string ServiceAccountName string IsNamespaceRestricted bool SyncEntriesConfig *shared.SyncEntriesConfig @@ -280,7 +281,7 @@ func (provider *Provider) GetMizuApiServerPodObject(opts *ApiServerOptions, moun }, { Name: "basenine", - Image: fmt.Sprintf("%s:%s", shared.BasenineImageRepo, shared.BasenineImageTag), + Image: opts.BasenineImage, ImagePullPolicy: opts.ImagePullPolicy, VolumeMounts: volumeMounts, ReadinessProbe: &core.Probe{ diff --git a/shared/kubernetes/proxy.go b/shared/kubernetes/proxy.go index 4cb425e96..44d627451 100644 --- a/shared/kubernetes/proxy.go +++ b/shared/kubernetes/proxy.go @@ -1,9 +1,16 @@ package kubernetes import ( + "bytes" + "context" "fmt" + "github.com/up9inc/mizu/shared" + "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" "net" "net/http" + "net/url" "strings" "time" @@ -14,8 +21,8 @@ import ( const k8sProxyApiPrefix = "/" const mizuServicePort = 80 -func StartProxy(kubernetesProvider *Provider, proxyHost string, mizuPort uint16, mizuNamespace string, mizuServiceName string) error { - logger.Log.Debugf("Starting proxy. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort) +func StartProxy(kubernetesProvider *Provider, proxyHost string, mizuPort uint16, mizuNamespace string, mizuServiceName string, cancel context.CancelFunc) (*http.Server, error) { + logger.Log.Debugf("Starting proxy using proxy method. namespace: [%v], service name: [%s], port: [%v]", mizuNamespace, mizuServiceName, mizuPort) filter := &proxy.FilterServer{ AcceptPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathAcceptRE), RejectPaths: proxy.MakeRegexpArrayOrDie(proxy.DefaultPathRejectRE), @@ -25,7 +32,7 @@ func StartProxy(kubernetesProvider *Provider, proxyHost string, mizuPort uint16, proxyHandler, err := proxy.NewProxyHandler(k8sProxyApiPrefix, filter, &kubernetesProvider.clientConfig, time.Second*2) if err != nil { - return err + return nil, err } mux := http.NewServeMux() mux.Handle(k8sProxyApiPrefix, getRerouteHttpHandlerMizuAPI(proxyHandler, mizuNamespace, mizuServiceName)) @@ -33,14 +40,21 @@ func StartProxy(kubernetesProvider *Provider, proxyHost string, mizuPort uint16, l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", proxyHost, int(mizuPort))) if err != nil { - return err + return nil, err } - server := http.Server{ + server := &http.Server{ Handler: mux, } - return server.Serve(l) + go func() { + if err := server.Serve(l); err != nil && err != http.ErrServerClosed { + logger.Log.Errorf("Error creating proxy, %v", err) + cancel() + } + }() + + return server, nil } func getMizuApiServerProxiedHostAndPath(mizuNamespace string, mizuServiceName string) string { @@ -69,3 +83,42 @@ func getRerouteHttpHandlerMizuStatic(proxyHandler http.Handler, mizuNamespace st proxyHandler.ServeHTTP(w, r) }) } + +func NewPortForward(kubernetesProvider *Provider, namespace string, podName string, localPort uint16, cancel context.CancelFunc) error { + logger.Log.Debugf("Starting proxy using port-forward method. namespace: [%v], service name: [%s], port: [%v]", namespace, podName, localPort) + + dialer, err := getHttpDialer(kubernetesProvider, namespace, podName) + if err != nil { + return err + } + + stopChan, readyChan := make(chan struct{}, 1), make(chan struct{}, 1) + out, errOut := new(bytes.Buffer), new(bytes.Buffer) + + forwarder, err := portforward.New(dialer, []string{fmt.Sprintf("%d:%d", localPort, shared.DefaultApiServerPort)}, stopChan, readyChan, out, errOut) + if err != nil { + return err + } + + go func() { + if err = forwarder.ForwardPorts(); err != nil { + logger.Log.Errorf("kubernetes port-forwarding error: %v", err) + cancel() + } + }() + + return nil +} + +func getHttpDialer(kubernetesProvider *Provider, namespace string, podName string) (httpstream.Dialer, error) { + roundTripper, upgrader, err := spdy.RoundTripperFor(&kubernetesProvider.clientConfig) + if err != nil { + logger.Log.Errorf("Error creating http dialer") + return nil, err + } + path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", namespace, podName) + hostIP := strings.TrimLeft(kubernetesProvider.clientConfig.Host, "htps:/") // no need specify "t" twice + serverURL := url.URL{Scheme: "https", Path: path, Host: hostIP} + + return spdy.NewDialer(upgrader, &http.Client{Transport: roundTripper}, http.MethodPost, &serverURL), nil +}