2
0

datastore.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package commands
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "os/signal"
  9. "time"
  10. "github.com/briandowns/spinner"
  11. "github.com/fatih/color"
  12. api "github.com/porter-dev/porter/api/client"
  13. "github.com/porter-dev/porter/api/types"
  14. "github.com/porter-dev/porter/cli/cmd/config"
  15. "github.com/spf13/cobra"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. "k8s.io/client-go/rest"
  18. "k8s.io/client-go/tools/portforward"
  19. "k8s.io/client-go/transport/spdy"
  20. )
  21. var port int
  22. const (
  23. // Address_Localhost is the localhost address
  24. Address_Localhost = "localhost"
  25. )
  26. func registerCommand_Datastore(cliConf config.CLIConfig) *cobra.Command {
  27. datastoreCmd := &cobra.Command{
  28. Use: "datastore",
  29. Short: "Runs a command for your datastore.",
  30. }
  31. datastoreConnectCmd := &cobra.Command{
  32. Use: "connect <DATASTORE_NAME>",
  33. Short: "Forward a local port to a remote datastore.",
  34. Args: cobra.MinimumNArgs(1),
  35. Run: func(cmd *cobra.Command, args []string) {
  36. err := checkLoginAndRunWithConfig(cmd, cliConf, args, datastoreConnect)
  37. if err != nil {
  38. os.Exit(1)
  39. }
  40. },
  41. }
  42. datastoreConnectCmd.PersistentFlags().IntVarP(
  43. &port,
  44. "port",
  45. "p",
  46. 8122,
  47. "the local port to forward",
  48. )
  49. datastoreCmd.AddCommand(datastoreConnectCmd)
  50. return datastoreCmd
  51. }
  52. func forwardPorts(
  53. method string,
  54. url *url.URL,
  55. kubeConfig *rest.Config,
  56. ports []string,
  57. stopChan <-chan struct{},
  58. readyChan chan struct{},
  59. ) error {
  60. transport, upgrader, err := spdy.RoundTripperFor(kubeConfig)
  61. if err != nil {
  62. return err
  63. }
  64. dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
  65. fw, err := portforward.NewOnAddresses(
  66. dialer, []string{Address_Localhost}, ports, stopChan, readyChan, os.Stdout, os.Stderr)
  67. if err != nil {
  68. return err
  69. }
  70. return fw.ForwardPorts()
  71. }
  72. func datastoreConnect(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client api.Client, cliConf config.CLIConfig, ff config.FeatureFlags, _ *cobra.Command, args []string) error {
  73. if cliConf.Project == 0 {
  74. return fmt.Errorf("project not set; please select a project with porter config set-project and try again")
  75. }
  76. projectId := cliConf.Project
  77. datastoreName := args[0]
  78. if datastoreName == "" {
  79. return fmt.Errorf("no datastore name provided")
  80. }
  81. if port == 0 {
  82. return fmt.Errorf("port must be provided")
  83. }
  84. s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
  85. s.Color("cyan") // nolint:errcheck,gosec
  86. s.Suffix = fmt.Sprintf(" Creating secure tunnel to datastore named %s in project %d...", datastoreName, projectId)
  87. s.Start()
  88. resp, err := client.CreateDatastoreProxy(ctx, projectId, datastoreName, &types.CreateDatastoreProxyRequest{})
  89. if err != nil {
  90. return fmt.Errorf("could not create secure tunnel: %s", err.Error())
  91. }
  92. s.Stop()
  93. datastoreCredential := resp.Credential
  94. cliConf.Cluster = resp.ClusterID
  95. config := &KubernetesSharedConfig{
  96. Client: client,
  97. CLIConfig: cliConf,
  98. }
  99. err = config.setSharedConfig(ctx)
  100. if err != nil {
  101. return fmt.Errorf("could not retrieve kube credentials: %s", err.Error())
  102. }
  103. proxyPod, err := config.Clientset.CoreV1().Pods(resp.Namespace).Get(
  104. ctx,
  105. resp.PodName,
  106. metav1.GetOptions{},
  107. )
  108. if err != nil {
  109. return fmt.Errorf("could not connect to secure tunnel: %s", err.Error())
  110. }
  111. defer appDeletePod(ctx, config, resp.PodName, resp.Namespace) //nolint:errcheck,gosec
  112. s = spinner.New(spinner.CharSets[9], 100*time.Millisecond)
  113. s.Color("green") // nolint:errcheck,gosec
  114. s.Suffix = " Waiting for secure tunnel to datastore to be ready..."
  115. s.Start()
  116. if err = appWaitForPod(ctx, config, proxyPod); err != nil {
  117. color.New(color.FgRed).Println("error occurred while waiting for secure tunnel to be ready") // nolint:errcheck,gosec
  118. return err
  119. }
  120. s.Stop()
  121. stopChannel := make(chan struct{}, 1)
  122. readyChannel := make(chan struct{})
  123. signals := make(chan os.Signal, 1)
  124. signal.Notify(signals, os.Interrupt)
  125. defer signal.Stop(signals)
  126. go func() {
  127. <-signals
  128. if stopChannel != nil {
  129. close(stopChannel)
  130. }
  131. }()
  132. req := config.RestClient.Post().
  133. Resource("pods").
  134. Namespace(resp.Namespace).
  135. Name(proxyPod.Name).
  136. SubResource("portforward")
  137. printDatastoreConnectionInformation(resp.Type, port, datastoreCredential)
  138. color.New(color.FgGreen).Println("Starting proxy...[CTRL-C to exit]") // nolint:errcheck,gosec
  139. return forwardPorts("POST", req.URL(), config.RestConf, []string{fmt.Sprintf("%d:%d", port, datastoreCredential.Port)}, stopChannel, readyChannel)
  140. }
  141. func printDatastoreConnectionInformation(datastoreType string, port int, credential types.DatastoreCredential) {
  142. color.New(color.FgGreen).Println("Secure tunnel setup complete! While the tunnel is running, you can connect to your datastore using the following credentials:") //nolint:errcheck,gosec
  143. fmt.Printf(" Host: 127.0.0.1\n")
  144. fmt.Printf(" Port: %d\n", port)
  145. if credential.DatabaseName != "" {
  146. fmt.Printf(" Database name: %s\n", credential.DatabaseName)
  147. }
  148. if credential.Username != "" {
  149. fmt.Printf(" Username: %s\n", credential.Username)
  150. }
  151. if credential.Password != "" {
  152. fmt.Printf(" Password: %s\n", credential.Password)
  153. }
  154. switch datastoreType {
  155. case string(types.DatastoreType_ElastiCache):
  156. fmt.Println()
  157. color.New(color.FgGreen).Println("For example, you can connect to your datastore using the following command:") //nolint:errcheck,gosec
  158. fmt.Printf(" redis-cli -p %d -a %s --tls\n", port, credential.Password)
  159. case string(types.DatastoreType_RDS):
  160. fmt.Println()
  161. color.New(color.FgGreen).Println("For example, you can connect to your datastore using the following command:") //nolint:errcheck,gosec
  162. fmt.Printf(" PGPASSWORD=%s psql -h 127.0.0.1 -p %d -U %s -d %s\n", credential.Password, port, credential.Username, credential.DatabaseName)
  163. }
  164. fmt.Println()
  165. }