diff --git a/cmd/local/connect.go b/cmd/local/connect.go index 0b30a6108..0a5e43237 100644 --- a/cmd/local/connect.go +++ b/cmd/local/connect.go @@ -12,25 +12,54 @@ import ( "syscall" ) -// NewCommandConnect open local port to enable connection between your local computer, to the cluster applications. func NewCommandConnect() *cobra.Command { connectCmd := &cobra.Command{ Use: "connect", - Short: "local connect command enable port forwards", - Long: `local connect command enable local tunnels to enable local connection to the cluster application. Check -logs for details.`, + Short: "connect will open all Kubefirst services port forwards", + Long: "connect opens all Kubefirst service ports for local connection, it makes the services available to" + + "allow local request to the deployed services", RunE: runConnect, } return connectCmd } -// runConnect opens port forwards for the available Kubefirst applications. func runConnect(cmd *cobra.Command, args []string) error { - log.Println("opening Port Forward for console...") - err := k8s.OpenPortForwardForKubeConConsole() + // every port forward has its own closing control. when a channel is closed, the port forward is close. + vaultStopChannel := make(chan struct{}, 1) + argoStopChannel := make(chan struct{}, 1) + argoCDStopChannel := make(chan struct{}, 1) + chartmuseumStopChannel := make(chan struct{}, 1) + minioStopChannel := make(chan struct{}, 1) + minioConsoleStopChannel := make(chan struct{}, 1) + kubefirstConsoleStopChannel := make(chan struct{}, 1) + AtlantisStopChannel := make(chan struct{}, 1) + + // guarantee it will close the port forwards even on a process kill + defer func() { + close(vaultStopChannel) + close(argoStopChannel) + close(argoCDStopChannel) + close(chartmuseumStopChannel) + close(minioStopChannel) + close(minioConsoleStopChannel) + close(kubefirstConsoleStopChannel) + close(AtlantisStopChannel) + log.Println("leaving port-forward command, port forwards are now closed") + }() + + err := k8s.OpenPortForwardForLocal( + vaultStopChannel, + argoStopChannel, + argoCDStopChannel, + chartmuseumStopChannel, + minioStopChannel, + minioConsoleStopChannel, + kubefirstConsoleStopChannel, + AtlantisStopChannel, + ) if err != nil { return err } @@ -38,6 +67,9 @@ func runConnect(cmd *cobra.Command, args []string) error { // style UI with local URLs fmt.Println(reports.StyleMessage(reports.LocalConnectSummary())) + log.Println("Kubefirst port forward done") + log.Println("hanging port forwards until ctrl+c is called") + // managing termination signal from the terminal sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) @@ -45,13 +77,10 @@ func runConnect(cmd *cobra.Command, args []string) error { wg.Add(1) go func() { <-sigs - log.Println("leaving port-forward command, port forwards are now closed") wg.Done() }() - wg.Wait() - log.Println("Kubefirst port forward done") - log.Println("hanging port forwards until ctrl+c is called") + wg.Wait() return nil } diff --git a/cmd/local/local.go b/cmd/local/local.go index f41298712..0c36a5b1a 100644 --- a/cmd/local/local.go +++ b/cmd/local/local.go @@ -4,9 +4,7 @@ import ( "context" "fmt" "log" - "os/exec" "sync" - "syscall" "time" "github.com/go-git/go-git/v5/plumbing" @@ -73,6 +71,9 @@ func NewCommand() *cobra.Command { // on error, doesnt show helper/usage localCmd.SilenceUsage = true + // wire up new commands + localCmd.AddCommand(NewCommandConnect()) + return localCmd } @@ -143,7 +144,6 @@ func runLocal(cmd *cobra.Command, args []string) error { viper.WriteConfig() } - var kPortForwardArgocd *exec.Cmd progressPrinter.SetupProgress(progressPrinter.TotalOfTrackers(), silentMode) executionControl := viper.GetBool("terraform.github.apply.complete") @@ -249,14 +249,18 @@ func runLocal(cmd *cobra.Command, args []string) error { log.Println("already waited for argocd to be ready") } - // establish port-forward - kPortForwardArgocd, err := k8s.PortForward(dryRun, "argocd", "svc/argocd-server", "8080:80") + // ArgoCD port-forward + argoCDStopChannel := make(chan struct{}, 1) defer func() { - err = kPortForwardArgocd.Process.Signal(syscall.SIGTERM) - if err != nil { - log.Println("Error closing kPortForwardArgocd") - } + close(argoCDStopChannel) }() + k8s.OpenPortForwardWrapper( + pkg.ArgoCDPodName, + pkg.ArgoCDNamespace, + pkg.ArgoCDPodPort, + pkg.ArgoCDPodLocalPort, + argoCDStopChannel, + ) pkg.InformUser(fmt.Sprintf("port-forward to argocd is available at %s", viper.GetString("argocd.local.service")), silentMode) // argocd pods are ready, get and set credentials @@ -278,7 +282,7 @@ func runLocal(cmd *cobra.Command, args []string) error { executionControl = viper.GetBool("argocd.registry.applied") if !executionControl { pkg.InformUser("applying the registry application to argocd", silentMode) - err = argocd.ApplyRegistryLocal(dryRun) + err := argocd.ApplyRegistryLocal(dryRun) if err != nil { log.Println("Error applying registry application to argocd") return err @@ -292,28 +296,36 @@ func runLocal(cmd *cobra.Command, args []string) error { if !executionControl { pkg.InformUser("Waiting for vault to be ready", silentMode) vault.WaitVaultToBeRunning(dryRun) - if err != nil { - log.Println("error waiting for vault to become running") - return err - } } - kPortForwardVault, err := k8s.PortForward(dryRun, "vault", "svc/vault", "8200:8200") + + // Vault port-forward + vaultStopChannel := make(chan struct{}, 1) defer func() { - err = kPortForwardVault.Process.Signal(syscall.SIGTERM) - if err != nil { - log.Println("Error closing kPortForwardVault") - } + close(vaultStopChannel) }() + k8s.OpenPortForwardWrapper( + pkg.VaultPodName, + pkg.VaultNamespace, + pkg.VaultPodPort, + pkg.VaultPodLocalPort, + vaultStopChannel, + ) k8s.LoopUntilPodIsReady(dryRun) - kPortForwardMinio, err := k8s.PortForward(dryRun, "minio", "svc/minio", "9000:9000") + + minioStopChannel := make(chan struct{}, 1) defer func() { - err = kPortForwardMinio.Process.Signal(syscall.SIGTERM) - if err != nil { - log.Println("Error closing kPortForwardMinio") - } + close(minioStopChannel) }() + k8s.OpenPortForwardWrapper( + pkg.MinioPodName, + pkg.MinioNamespace, + pkg.MinioPodPort, + pkg.MinioPodLocalPort, + minioStopChannel, + ) + // todo: can I remove it? time.Sleep(20 * time.Second) // configure vault with terraform @@ -370,16 +382,19 @@ func runLocal(cmd *cobra.Command, args []string) error { progressPrinter.IncrementTracker("step-apps", 1) if !viper.GetBool("chartmuseum.host.resolved") { - - //* establish port-forward - var kPortForwardChartMuseum *exec.Cmd - kPortForwardChartMuseum, err = k8s.PortForward(dryRun, "chartmuseum", "svc/chartmuseum", "8181:8080") + // Chartmuseum port-forward + chartmuseumStopChannel := make(chan struct{}, 1) defer func() { - err = kPortForwardChartMuseum.Process.Signal(syscall.SIGTERM) - if err != nil { - log.Println("Error closing kPortForwardChartMuseum") - } + close(chartmuseumStopChannel) }() + k8s.OpenPortForwardWrapper( + pkg.ChartmuseumPodName, + pkg.ChartmuseumNamespace, + pkg.ChartmuseumPodPort, + pkg.ChartmuseumPodLocalPort, + chartmuseumStopChannel, + ) + pkg.AwaitHostNTimes("http://localhost:8181/health", 5, 5) viper.Set("chartmuseum.host.resolved", true) viper.WriteConfig() @@ -388,7 +403,7 @@ func runLocal(cmd *cobra.Command, args []string) error { } pkg.InformUser("Deploying metaphor applications", silentMode) - err = metaphor.DeployMetaphorGithubLocal(dryRun, githubOwner, metaphorBranch, "") + err := metaphor.DeployMetaphorGithubLocal(dryRun, githubOwner, metaphorBranch, "") if err != nil { pkg.InformUser("Error deploy metaphor applications", silentMode) log.Println("Error running deployMetaphorCmd") @@ -425,10 +440,18 @@ func runLocal(cmd *cobra.Command, args []string) error { var wg sync.WaitGroup wg.Add(1) go func() { - err := k8s.OpenAtlantisPortForward() - if err != nil { - log.Println(err) - } + // Atlantis port-forward + atlantisStopChannel := make(chan struct{}, 1) + defer func() { + close(atlantisStopChannel) + }() + k8s.OpenPortForwardWrapper( + pkg.AtlantisPodName, + pkg.AtlantisNamespace, + pkg.AtlantisPodPort, + pkg.AtlantisPodLocalPort, + atlantisStopChannel, + ) gitHubClient := githubWrapper.New() err = gitHubClient.CreatePR(branchName) diff --git a/cmd/local/postrun.go b/cmd/local/postrun.go index 139de1899..ad7b5f5bd 100644 --- a/cmd/local/postrun.go +++ b/cmd/local/postrun.go @@ -1,13 +1,15 @@ package local import ( - "log" - "time" - "github.com/kubefirst/kubefirst/internal/k8s" "github.com/kubefirst/kubefirst/internal/reports" "github.com/kubefirst/kubefirst/pkg" "github.com/spf13/cobra" + "log" + "os" + "os/signal" + "sync" + "syscall" ) func runPostLocal(cmd *cobra.Command, args []string) error { @@ -17,21 +19,50 @@ func runPostLocal(cmd *cobra.Command, args []string) error { return nil } - // open all port forwards, wait console ui be ready, and open console ui in the browser - err := k8s.OpenPortForwardForKubeConConsole() + // every port forward has its own closing control. when a channel is closed, the port forward is close. + vaultStopChannel := make(chan struct{}, 1) + argoStopChannel := make(chan struct{}, 1) + argoCDStopChannel := make(chan struct{}, 1) + chartmuseumStopChannel := make(chan struct{}, 1) + minioStopChannel := make(chan struct{}, 1) + minioConsoleStopChannel := make(chan struct{}, 1) + kubefirstConsoleStopChannel := make(chan struct{}, 1) + AtlantisStopChannel := make(chan struct{}, 1) + + // guarantee it will close the port forwards even on a process kill + defer func() { + close(vaultStopChannel) + close(argoStopChannel) + close(argoCDStopChannel) + close(chartmuseumStopChannel) + close(minioStopChannel) + close(minioConsoleStopChannel) + close(kubefirstConsoleStopChannel) + close(AtlantisStopChannel) + log.Println("leaving port-forward command, port forwards are now closed") + }() + + err := k8s.OpenPortForwardForLocal( + vaultStopChannel, + argoStopChannel, + argoCDStopChannel, + chartmuseumStopChannel, + minioStopChannel, + minioConsoleStopChannel, + kubefirstConsoleStopChannel, + AtlantisStopChannel, + ) if err != nil { - log.Println(err) + return err } - time.Sleep(time.Millisecond * 2000) - log.Println("Starting the presentation of console and api for the handoff screen") - err = pkg.IsConsoleUIAvailable(pkg.ConsoleUILocalURL) + err = pkg.IsConsoleUIAvailable(pkg.KubefirstConsoleLocalURL) if err != nil { log.Println(err) } - err = pkg.OpenBrowser(pkg.ConsoleUILocalURL) + err = pkg.OpenBrowser(pkg.KubefirstConsoleLocalURL) if err != nil { log.Println(err) } @@ -40,5 +71,15 @@ func runPostLocal(cmd *cobra.Command, args []string) error { log.Println("Kubefirst Console available at: http://localhost:9094", silentMode) + // managing termination signal from the terminal + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + var wg sync.WaitGroup + wg.Add(1) + go func() { + <-sigs + wg.Done() + }() + return nil } diff --git a/cmd/postInstall.go b/cmd/postInstall.go index 3ef3c8fb1..f8ea733e4 100644 --- a/cmd/postInstall.go +++ b/cmd/postInstall.go @@ -43,12 +43,12 @@ var postInstallCmd = &cobra.Command{ log.Println(err) } - err = pkg.IsConsoleUIAvailable(pkg.ConsoleUILocalURL) + err = pkg.IsConsoleUIAvailable(pkg.KubefirstConsoleLocalURL) if err != nil { log.Println(err) } - err = pkg.OpenBrowser(pkg.ConsoleUILocalURL) + err = pkg.OpenBrowser(pkg.KubefirstConsoleLocalURL) if err != nil { log.Println(err) } @@ -64,12 +64,12 @@ var postInstallCmd = &cobra.Command{ log.Println(err) } - err = pkg.IsConsoleUIAvailable(pkg.ConsoleUILocalURL) + err = pkg.IsConsoleUIAvailable(pkg.KubefirstConsoleLocalURL) if err != nil { log.Println(err) } - err = pkg.OpenBrowser(pkg.ConsoleUILocalURL) - if pkg.OpenBrowser(pkg.ConsoleUILocalURL) != nil { + err = pkg.OpenBrowser(pkg.KubefirstConsoleLocalURL) + if pkg.OpenBrowser(pkg.KubefirstConsoleLocalURL) != nil { log.Println(err) } } diff --git a/cmd/root.go b/cmd/root.go index cdc88133b..5b1716383 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -43,4 +43,5 @@ func Execute() { func init() { cobra.OnInitialize() rootCmd.AddCommand(local.NewCommand()) + rootCmd.AddCommand(NewDevCommand()) } diff --git a/go.mod b/go.mod index 688f60a10..b5d384d26 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,8 @@ require ( github.com/charmbracelet/bubbletea v0.22.0 github.com/charmbracelet/lipgloss v0.5.0 github.com/cip8/autoname v1.0.0 + github.com/denisbrodbeck/machineid v1.0.1 + github.com/dustin/go-humanize v1.0.0 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git/v5 v5.4.2 github.com/google/go-github/v45 v45.0.0 @@ -33,9 +35,9 @@ require ( golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - k8s.io/api v0.22.1 - k8s.io/apimachinery v0.22.1 - k8s.io/client-go v0.22.1 + k8s.io/api v0.25.3 + k8s.io/apimachinery v0.25.3 + k8s.io/client-go v0.25.3 ) require ( @@ -45,13 +47,13 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.13 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.12 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect - github.com/denisbrodbeck/machineid v1.0.1 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/hashicorp/vault/sdk v0.6.0 // indirect github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect github.com/jpillora/backoff v1.0.0 // indirect + github.com/moby/spdystream v0.2.0 // indirect github.com/segmentio/backo-go v1.0.1 // indirect + github.com/stretchr/objx v0.2.0 // indirect github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect ) @@ -83,7 +85,7 @@ require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/go-billy/v5 v5.3.1 // indirect - github.com/go-logr/logr v1.2.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -141,8 +143,7 @@ require ( github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/spf13/pflag v1.0.5 github.com/subosito/gotenv v1.2.0 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect go.uber.org/atomic v1.9.0 // indirect @@ -162,8 +163,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.60.1 // indirect - k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index 427ef11c9..36aeb010c 100644 --- a/go.sum +++ b/go.sum @@ -177,6 +177,7 @@ github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbj github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= @@ -223,8 +224,9 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -458,6 +460,7 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -992,8 +995,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= +k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ= +k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI= k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= @@ -1001,18 +1005,18 @@ k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= -k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= -k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/k8s/kubernetes.go b/internal/k8s/kubernetes.go index ed93e99d9..5563d522b 100644 --- a/internal/k8s/kubernetes.go +++ b/internal/k8s/kubernetes.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "log" @@ -12,7 +11,6 @@ import ( "os" "os/exec" "strings" - "sync" "time" "github.com/itchyny/gojq" @@ -186,6 +184,7 @@ func GetClientSet(dryRun bool) (*kubernetes.Clientset, error) { return clientset, nil } +// deprecated // PortForward - opens port-forward to services func PortForward(dryRun bool, namespace string, filter string, ports string) (*exec.Cmd, error) { if dryRun { @@ -459,146 +458,3 @@ func SetArgocdCreds(dryRun bool) { viper.Set("argocd.admin.username", "admin") viper.WriteConfig() } - -// todo: this is temporary -func OpenPortForwardForCloudConConsole() error { - var wg sync.WaitGroup - wg.Add(1) - - // Cloud Console UI - go func() { - _, err := PortForward(false, "kubefirst", "svc/kubefirst-console", "9094:80") - if err != nil { - log.Println("error opening Kubefirst-console port forward") - } - wg.Done() - }() - - wg.Wait() - - return nil -} - -func OpenPortForwardForKubeConConsole() error { - - var wg sync.WaitGroup - wg.Add(8) - // argo workflows - go func() { - output, err := PortForward(false, "argo", "svc/argo-server", "2746:2746") - if err != nil { - log.Println("error opening Argo Workflows port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - wg.Done() - }() - // argocd - go func() { - output, err := PortForward(false, "argocd", "svc/argocd-server", "8080:80") - if err != nil { - log.Println("error opening ArgoCD port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - wg.Done() - }() - - // atlantis - go func() { - err := OpenAtlantisPortForward() - if err != nil { - log.Println(err) - } - wg.Done() - }() - - // chartmuseum - go func() { - output, err := PortForward(false, "chartmuseum", "svc/chartmuseum", "8181:8080") - if err != nil { - log.Println("error opening Chartmuseum port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - wg.Done() - }() - - // vault - go func() { - output, err := PortForward(false, "vault", "svc/vault", "8200:8200") - if err != nil { - log.Println("error opening Vault port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - - wg.Done() - }() - - // minio - go func() { - output, err := PortForward(false, "minio", "svc/minio", "9000:9000") - if err != nil { - log.Println("error opening Minio port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - wg.Done() - }() - - // minio console - go func() { - output, err := PortForward(false, "minio", "svc/minio-console", "9001:9001") - if err != nil { - log.Println("error opening Minio-console port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - wg.Done() - }() - - // Kubecon console ui - go func() { - output, err := PortForward(false, "kubefirst", "svc/kubefirst-console", "9094:80") - if err != nil { - log.Println("error opening Kubefirst-console port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - log.Println(stderr) - } - wg.Done() - }() - - wg.Wait() - - return nil -} - -// OpenAtlantisPortForward opens port forward for Atlantis -func OpenAtlantisPortForward() error { - - output, err := PortForward(false, "atlantis", "svc/atlantis", "4141:80") - if err != nil { - return errors.New("error opening Atlantis port forward") - } - stderr := fmt.Sprint(output.Stderr) - if len(stderr) > 0 { - return errors.New(stderr) - } - - return nil -} diff --git a/internal/k8s/portForward.go b/internal/k8s/portForward.go new file mode 100644 index 000000000..872f37106 --- /dev/null +++ b/internal/k8s/portForward.go @@ -0,0 +1,258 @@ +package k8s + +import ( + "context" + "errors" + "fmt" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" + "log" + "net/http" + "net/url" + "os" + "strings" + "sync" +) + +type PortForwardAPodRequest struct { + // RestConfig is the kubernetes config + RestConfig *rest.Config + // Pod is the selected pod for this port forwarding + Pod v1.Pod + // LocalPort is the local port that will be selected to expose the PodPort + LocalPort int + // PodPort is the target port for the pod + PodPort int + + //// Steams configures where to write or read input from + //Streams genericclioptions.IOStreams + + // StopCh is the channel used to manage the port forward lifecycle + StopCh <-chan struct{} + // ReadyCh communicates when the tunnel is ready to receive traffic + ReadyCh chan struct{} +} + +// PortForwardPod receives a PortForwardAPodRequest, and enable port forward for the specified resource. If the provided +// Pod name matches a running Pod, it will try to port forward for that Pod on the specified port. +func PortForwardPod(clientset *kubernetes.Clientset, req PortForwardAPodRequest) error { + + podList, err := clientset.CoreV1().Pods(req.Pod.Namespace).List(context.Background(), metav1.ListOptions{}) + if err != nil || len(podList.Items) == 0 { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + + var runningPod *v1.Pod + for _, pod := range podList.Items { + // pick the first pod found to be running + if pod.Status.Phase == v1.PodRunning && strings.HasPrefix(pod.Name, req.Pod.Name) { + runningPod = &pod + break + } + } + + path := fmt.Sprintf("/api/v1/namespaces/%s/pods/%s/portforward", runningPod.Namespace, runningPod.Name) + hostIP := strings.TrimLeft(req.RestConfig.Host, "htps:/") + + transport, upgrader, err := spdy.RoundTripperFor(req.RestConfig) + if err != nil { + return err + } + + dialer := spdy.NewDialer( + upgrader, + &http.Client{Transport: transport}, + http.MethodPost, + &url.URL{ + Scheme: "https", + Path: path, + Host: hostIP, + }, + ) + + fw, err := portforward.New( + dialer, + []string{fmt.Sprintf( + "%d:%d", + req.LocalPort, + req.PodPort)}, + req.StopCh, + req.ReadyCh, + os.Stdout, + os.Stderr) + if err != nil { + return err + } + + err = fw.ForwardPorts() + if err != nil { + return err + } + + return nil + +} + +// todo: this is temporary +func OpenPortForwardForCloudConConsole() error { + var wg sync.WaitGroup + wg.Add(1) + + // Cloud Console UI + go func() { + _, err := PortForward(false, "kubefirst", "svc/kubefirst-console", "9094:80") + if err != nil { + log.Println("error opening Kubefirst-console port forward") + } + wg.Done() + }() + + wg.Wait() + + return nil +} + +// deprecated +// OpenPortForwardForKubeConConsole was deprecated by OpenPortForwardForLocal, that handles port forwards using k8s +// go client. +func OpenPortForwardForKubeConConsole() error { + + var wg sync.WaitGroup + wg.Add(8) + // argo workflows + go func() { + output, err := PortForward(false, "argo", "svc/argo-server", "2746:2746") + if err != nil { + log.Println("error opening Argo Workflows port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + // argocd + go func() { + output, err := PortForward(false, "argocd", "svc/argocd-server", "8080:80") + if err != nil { + log.Println("error opening ArgoCD port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + + // atlantis + go func() { + err := OpenAtlantisPortForward() + if err != nil { + log.Println(err) + } + wg.Done() + }() + + // chartmuseum + go func() { + output, err := PortForward(false, "chartmuseum", "svc/chartmuseum", "8181:8080") + if err != nil { + log.Println("error opening Chartmuseum port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + + // vault + go func() { + output, err := PortForward(false, "vault", "svc/vault", "8200:8200") + if err != nil { + log.Println("error opening Vault port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + + wg.Done() + }() + + // minio + go func() { + output, err := PortForward(false, "minio", "svc/minio", "9000:9000") + if err != nil { + log.Println("error opening Minio port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + + // minio console + go func() { + output, err := PortForward(false, "minio", "svc/minio", "9000:9000") + if err != nil { + log.Println("error opening Minio port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + + // minio console + go func() { + output, err := PortForward(false, "minio", "svc/minio-console", "9001:9001") + if err != nil { + log.Println("error opening Minio-console port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + + // Kubecon console ui + go func() { + output, err := PortForward(false, "kubefirst", "svc/kubefirst-console", "9094:80") + if err != nil { + log.Println("error opening Kubefirst-console port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + log.Println(stderr) + } + wg.Done() + }() + + wg.Wait() + + return nil +} + +// OpenAtlantisPortForward opens port forward for Atlantis +func OpenAtlantisPortForward() error { + + output, err := PortForward(false, "atlantis", "svc/atlantis", "4141:80") + if err != nil { + return errors.New("error opening Atlantis port forward") + } + stderr := fmt.Sprint(output.Stderr) + if len(stderr) > 0 { + return errors.New(stderr) + } + + return nil +} diff --git a/internal/k8s/wrappers.go b/internal/k8s/wrappers.go new file mode 100644 index 000000000..c653ebaaa --- /dev/null +++ b/internal/k8s/wrappers.go @@ -0,0 +1,151 @@ +package k8s + +import ( + "github.com/kubefirst/kubefirst/configs" + "github.com/kubefirst/kubefirst/pkg" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/clientcmd" + "log" + "sync" +) + +// OpenPortForwardForLocal is a wrapper function to instantiate the necessary resources for Kubefirst +// console. OpenPortForwardForLocal receives channels as arguments, when this channels are closed, the +// port forwards are also closed. +// +// Every port forward that is open, is open in a Go routine, the function exists when all the (wg.Add(x)) x Go +// routines are done. +func OpenPortForwardForLocal( + vaultStopChannel chan struct{}, + argoStopChannel chan struct{}, + argoCDStopChannel chan struct{}, + chartmuseumStopChannel chan struct{}, + minioStopChannel chan struct{}, + minioConsoleStopChannel chan struct{}, + kubefirstConsoleStopChannel chan struct{}, + AtlantisStopChannel chan struct{}, +) error { + + var wg sync.WaitGroup + wg.Add(8) + + // Vault + go func() { + OpenPortForwardWrapper(pkg.VaultPodName, pkg.VaultNamespace, pkg.VaultPodPort, pkg.VaultPodLocalPort, vaultStopChannel) + wg.Done() + }() + + // Argo + go func() { + OpenPortForwardWrapper(pkg.ArgoPodName, pkg.ArgoNamespace, pkg.ArgoPodPort, pkg.ArgoPodLocalPort, argoStopChannel) + wg.Done() + }() + + // ArgoCD + go func() { + OpenPortForwardWrapper(pkg.ArgoCDPodName, pkg.ArgoCDNamespace, pkg.ArgoCDPodPort, pkg.ArgoCDPodLocalPort, argoCDStopChannel) + wg.Done() + }() + + // chartmuseum + go func() { + OpenPortForwardWrapper(pkg.ChartmuseumPodName, pkg.ChartmuseumNamespace, pkg.ChartmuseumPodPort, pkg.ChartmuseumPodLocalPort, chartmuseumStopChannel) + wg.Done() + }() + + // Minio + go func() { + OpenPortForwardWrapper(pkg.MinioPodName, pkg.MinioNamespace, pkg.MinioPodPort, pkg.MinioPodLocalPort, minioStopChannel) + wg.Done() + }() + + // Minio Console + go func() { + OpenPortForwardWrapper(pkg.MinioConsolePodName, pkg.MinioConsoleNamespace, pkg.MinioConsolePodPort, pkg.MinioConsolePodLocalPort, minioConsoleStopChannel) + wg.Done() + }() + + // Kubefirst console + go func() { + OpenPortForwardWrapper(pkg.KubefirstConsolePodName, pkg.KubefirstConsoleNamespace, pkg.KubefirstConsolePodPort, pkg.KubefirstConsolePodLocalPort, kubefirstConsoleStopChannel) + wg.Done() + }() + + // Atlantis + go func() { + OpenPortForwardWrapper(pkg.AtlantisPodName, pkg.AtlantisNamespace, pkg.AtlantisPodPort, pkg.AtlantisPodLocalPort, AtlantisStopChannel) + wg.Done() + }() + + wg.Wait() + return nil +} + +// OpenPortForwardWrapper wrapper for PortForwardPod function. This functions make it easier to open and close port +// forward request. By providing the function parameters, the function will manage to create the port forward. The +// parameter for the stopChannel controls when the port forward must be closed. +// +// Example: +// +// vaultStopChannel := make(chan struct{}, 1) +// go func() { +// OpenPortForwardWrapper( +// pkg.VaultPodName, +// pkg.VaultNamespace, +// pkg.VaultPodPort, +// pkg.VaultPodLocalPort, +// vaultStopChannel) +// wg.Done() +// }() +func OpenPortForwardWrapper(podName string, namespace string, podPort int, podLocalPort int, stopChannel chan struct{}) { + + config1 := configs.ReadConfig() + kubeconfig := config1.KubeConfigPath + cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + log.Println(err) + } + + // readyCh communicate when the port forward is ready to get traffic + readyCh := make(chan struct{}) + + // todo: constants for podName, PodPort and localPort, namespace + portForwardRequest := PortForwardAPodRequest{ + RestConfig: cfg, + Pod: v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: namespace, + }, + }, + PodPort: podPort, + LocalPort: podLocalPort, + StopCh: stopChannel, + ReadyCh: readyCh, + } + + clientset, err := GetClientSet(false) + + go func() { + err = PortForwardPod(clientset, portForwardRequest) + if err != nil { + log.Println(err) + } + }() + + select { + case <-stopChannel: + log.Println("leaving...") + close(stopChannel) + close(readyCh) + break + case <-readyCh: + log.Println("port forwarding is ready to get traffic") + } + + log.Printf("Pod %q at namespace %q has port-forward accepting local connections at port %d\n", podName, namespace, podLocalPort) + //<-stopChannel + + return +} diff --git a/internal/reports/section.go b/internal/reports/section.go index 619271112..4ef9e46e6 100644 --- a/internal/reports/section.go +++ b/internal/reports/section.go @@ -297,7 +297,7 @@ func LocalConnectSummary() string { localConnect.WriteString("\nKubefirst Local:\n") localConnect.WriteString(strings.Repeat("-", 70)) - localConnect.WriteString(fmt.Sprintf("\n\nKubefirst Console UI: %s\n", pkg.ConsoleUILocalURL)) + localConnect.WriteString(fmt.Sprintf("\n\nKubefirst Console UI: %s\n", pkg.KubefirstConsoleLocalURL)) localConnect.WriteString(fmt.Sprintf("ChartmuseumLocalURL: %s\n", pkg.ChartmuseumLocalURL)) localConnect.WriteString(fmt.Sprintf("Argo: %s\n", pkg.ArgoLocalURL)) localConnect.WriteString(fmt.Sprintf("ArgoCD: %s\n", pkg.ArgoCDLocalURL)) diff --git a/pkg/constants.go b/pkg/constants.go index dfe31afea..de4e1e447 100644 --- a/pkg/constants.go +++ b/pkg/constants.go @@ -1,18 +1,8 @@ package pkg const ( - ArgoCDLocalBaseURL = "https://localhost:8080/api/v1" JSONContentType = "application/json" SoftServerURI = "ssh://127.0.0.1:8022/config" - LocalAtlantisURL = "localhost:4141" - ConsoleUILocalURL = "http://localhost:9094" - ChartmuseumLocalURL = "http://localhost:8181" - ArgoLocalURL = "http://localhost:2746" - ArgoCDLocalURL = "http://localhost:8080" - VaultLocalURL = "http://localhost:8200" - AtlantisLocalURL = "http://localhost:4141" - MinioURL = "http://localhost:9000" - MinioConsoleURL = "http://localhost:9001" GitHubOAuthClientId = "2ced340927e0a6c49a45" CloudK3d = "k3d" GitHubProviderName = "github" @@ -32,6 +22,7 @@ const ( MetricMgmtClusterInstallCompleted = "kubefirst.mgmt_cluster_install.completed" ) +// Helm const ( HelmRepoName = "argo" HelmRepoURL = "https://argoproj.github.io/argo-helm" @@ -39,3 +30,77 @@ const ( HelmRepoNamespace = "argocd" HelmRepoChartVersion = "4.10.5" ) + +// Vault +const ( + VaultPodName = "vault-0" + VaultNamespace = "vault" + VaultPodPort = 8200 + VaultPodLocalPort = 8200 + VaultLocalURL = "http://localhost:8200" +) + +// Argo +const ( + ArgoPodName = "argo-server" + ArgoNamespace = "argo" + ArgoPodPort = 2746 + ArgoPodLocalPort = 2746 + ArgoLocalURL = "http://localhost:2746" +) + +// ArgoCD +const ( + ArgoCDPodName = "argocd-server" + ArgoCDNamespace = "argocd" + ArgoCDPodPort = 8080 + ArgoCDPodLocalPort = 8080 + ArgoCDLocalURL = "http://localhost:8080" + ArgoCDLocalBaseURL = "https://localhost:8080/api/v1" +) + +// ChartMuseum +const ( + ChartmuseumPodName = "chartmuseum" + ChartmuseumNamespace = "chartmuseum" + ChartmuseumPodPort = 8080 + ChartmuseumPodLocalPort = 8181 + ChartmuseumLocalURL = "http://localhost:8181" +) + +// Minio +const ( + MinioPodName = "minio" + MinioNamespace = "minio" + MinioPodPort = 9000 + MinioPodLocalPort = 9000 + MinioURL = "http://localhost:9000" +) + +// Minio Console +const ( + MinioConsolePodName = "minio" + MinioConsoleNamespace = "minio" + MinioConsolePodPort = 9001 + MinioConsolePodLocalPort = 9001 + MinioConsoleURL = "http://localhost:9001" +) + +// Kubefirst Console +const ( + KubefirstConsolePodName = "kubefirst-console" + KubefirstConsoleNamespace = "kubefirst" + KubefirstConsolePodPort = 80 + KubefirstConsolePodLocalPort = 9094 + KubefirstConsoleLocalURL = "http://localhost:9094" +) + +// Atlantis +const ( + AtlantisPodName = "atlantis-0" + AtlantisNamespace = "atlantis" + AtlantisPodPort = 4141 + AtlantisPodLocalPort = 4141 + AtlantisLocalURL = "http://localhost:4141" + LocalAtlantisURL = "localhost:4141" // todo: +)