Skip to content

Commit

Permalink
Merge f3f4069 into b657dad
Browse files Browse the repository at this point in the history
  • Loading branch information
lubien authored Feb 18, 2025
2 parents b657dad + f3f4069 commit d4c19ca
Show file tree
Hide file tree
Showing 10 changed files with 464 additions and 38 deletions.
43 changes: 5 additions & 38 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,13 @@ issues:
linters:
disable-all: true
enable:
# - gofumpt
# - goimports
- gofmt
- gosimple
- govet
- ineffassign
- staticcheck
- unconvert
- unused
fast: true

# options for analysis running
run:
Expand All @@ -57,18 +54,6 @@ run:
# list of build tags, all linters use it. Default is empty list.
#build-tags:
# - mytag

# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true

# which files to skip: they will be analyzed, but issues from them
# won't be reported. Default value is empty list, but there is
# no need to include all autogenerated files, we confidently recognize
# autogenerated files. If it's not please let us know.
skip-files:
- ".*\\.hcl2spec\\.go$"
- "docstrings/gen.go"
# - lib/bad.go

# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
Expand All @@ -83,39 +68,21 @@ run:

# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: colored-line-number

# print lines of code with issue, default is true
print-issued-lines: true

# print linter name in the end of issue text, default is true
print-linter-name: true

# make issues output unique by line, default is true
uniq-by-line: true

sort-results: true

# all available settings of specific linters
linters-settings:
gofumpt:
module-path: github.com/superfly/flyctl
errcheck:
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: false

# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
# default is false: such cases aren't reported by default.
check-blank: false

# [deprecated] comma-separated list of pairs of the form pkg:regex
# the regex is used to ignore names within pkg. (default "fmt:.*").
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
ignore: fmt:.*,io:Close

# path to a file containing a list of functions to exclude from checking
# see https://github.com/kisielk/errcheck#excluding-functions for details
#exclude: /path/to/file.txt
exclude-functions:
- fmt.*
- io.Close
govet:
settings:
printf:
Expand Down
20 changes: 20 additions & 0 deletions internal/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"github.com/superfly/flyctl/internal/command/auth/webauth"
"github.com/superfly/flyctl/internal/flyutil"
"github.com/superfly/flyctl/internal/prompt"
"github.com/superfly/flyctl/internal/uiex"
"github.com/superfly/flyctl/internal/uiexutil"
"github.com/superfly/flyctl/iostreams"

"github.com/superfly/flyctl/internal/appconfig"
Expand Down Expand Up @@ -603,6 +605,24 @@ func RequireSession(ctx context.Context) (context.Context, error) {
return ctx, nil
}

// Apply uiex client to uiex
func RequireUiex(ctx context.Context) (context.Context, error) {
cfg := config.FromContext(ctx)

if uiexutil.ClientFromContext(ctx) == nil {
client, err := uiexutil.NewClientWithOptions(ctx, uiex.NewClientOpts{
Logger: logger.FromContext(ctx),
Tokens: cfg.Tokens,
})
if err != nil {
return nil, err
}
ctx = uiexutil.NewContextWithClient(ctx, client)
}

return ctx, nil
}

func tryOpenUserURL(ctx context.Context, url string) error {
io := iostreams.FromContext(ctx)

Expand Down
66 changes: 66 additions & 0 deletions internal/command/mpg/connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package mpg

import (
"context"
"fmt"
"os/exec"

"github.com/spf13/cobra"

"github.com/superfly/flyctl/iostreams"
"github.com/superfly/flyctl/proxy"

"github.com/superfly/flyctl/internal/command"
"github.com/superfly/flyctl/internal/flag"
)

func newConnect() (cmd *cobra.Command) {
const (
long = `Connect to a Redis database using psql`

short = long
usage = "connect"
)

cmd = command.New(usage, short, long, runConnect, command.RequireSession, command.RequireUiex)

flag.Add(cmd,
flag.Org(),
)

return cmd
}

func runConnect(ctx context.Context) (err error) {
io := iostreams.FromContext(ctx)

localProxyPort := "16380"

cluster, params, password, err := getMpgProxyParams(ctx, localProxyPort)
if err != nil {
return err
}

psqlPath, err := exec.LookPath("psql")
if err != nil {
fmt.Fprintf(io.Out, "Could not find psql in your $PATH. Install it or point your psql at: %s", "someurl")
return
}

err = proxy.Start(ctx, params)
if err != nil {
return err
}

name := fmt.Sprintf("pgdb-%s", cluster.Id)
connectUrl := fmt.Sprintf("postgres://%s:%s@localhost:%s/%s?sslmode=require", name, password, localProxyPort, name)
cmd := exec.CommandContext(ctx, psqlPath, connectUrl)
cmd.Stdout = io.Out
cmd.Stderr = io.ErrOut
cmd.Stdin = io.In

cmd.Start()
cmd.Wait()

return
}
25 changes: 25 additions & 0 deletions internal/command/mpg/mpg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package mpg

import (
"github.com/spf13/cobra"
"github.com/superfly/flyctl/internal/command"
)

func New() *cobra.Command {
const (
short = `Manage Managed Postgres clusters.`

long = short + "\n"
)

cmd := command.New("managed-postgres", short, long, nil)

cmd.Aliases = []string{"mpg"}

cmd.AddCommand(
newProxy(),
newConnect(),
)

return cmd
}
130 changes: 130 additions & 0 deletions internal/command/mpg/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package mpg

import (
"context"
"fmt"

"github.com/spf13/cobra"
"github.com/superfly/flyctl/agent"
"github.com/superfly/flyctl/gql"
"github.com/superfly/flyctl/internal/command"
"github.com/superfly/flyctl/internal/command/orgs"
"github.com/superfly/flyctl/internal/flag"
"github.com/superfly/flyctl/internal/flyutil"
"github.com/superfly/flyctl/internal/prompt"
"github.com/superfly/flyctl/internal/uiex"
"github.com/superfly/flyctl/internal/uiexutil"
"github.com/superfly/flyctl/proxy"
"github.com/superfly/flyctl/terminal"
)

func newProxy() (cmd *cobra.Command) {
const (
long = `Proxy to a MPG database`

short = long
usage = "proxy"
)

cmd = command.New(usage, short, long, runProxy, command.RequireSession, command.RequireUiex)

flag.Add(cmd,
flag.Org(),
flag.Region(),
)

return cmd
}

func runProxy(ctx context.Context) (err error) {

localProxyPort := "16380"
cluster, params, password, err := getMpgProxyParams(ctx, localProxyPort)
if err != nil {
return err
}

name := fmt.Sprintf("pgdb-%s", cluster.Id)

terminal.Infof("Proxying postgres to port \"%s\" with user \"%s\" password \"%s\"", localProxyPort, name, password)

return proxy.Connect(ctx, params)
}

func getMpgProxyParams(ctx context.Context, localProxyPort string) (*uiex.ManagedCluster, *proxy.ConnectParams, string, error) {
// This `org.Slug` could be "personal" and we need that for wireguard connections
org, err := orgs.OrgFromFlagOrSelect(ctx)
if err != nil {
return nil, nil, "", err
}

client := flyutil.ClientFromContext(ctx)
genqClient := flyutil.ClientFromContext(ctx).GenqClient()

// For ui-ex request we need the real org slug
var fullOrg *gql.GetOrganizationResponse
if fullOrg, err = gql.GetOrganization(ctx, genqClient, org.Slug); err != nil {
err = fmt.Errorf("failed fetching org: %w", err)
return nil, nil, "", err
}

uiexClient := uiexutil.ClientFromContext(ctx)

var index int
var options []string

clustersResponse, err := uiexClient.ListManagedClusters(ctx, fullOrg.Organization.RawSlug)
if err != nil {
return nil, nil, "", err
}

// fmt.Printf("%+v\n", clustersResponse)
// fmt.Printf("%+v\n", err)

if len(clustersResponse.Data) == 0 {
err := fmt.Errorf("No Managed Postgres clusters found on this organization")
return nil, nil, "", err
}

for _, cluster := range clustersResponse.Data {
options = append(options, fmt.Sprintf("%s (%s)", cluster.Name, cluster.Region))
}

selectErr := prompt.Select(ctx, &index, "Select a database to connect to", "", options...)
if selectErr != nil {
return nil, nil, "", selectErr
}

selectedCluster := clustersResponse.Data[index]

response, err := uiexClient.GetManagedCluster(ctx, selectedCluster.Organization.Slug, selectedCluster.Id)
if err != nil {
return nil, nil, "", err
}
cluster := response.Data

if response.Password.Status == "initializing" {
return nil, nil, "", fmt.Errorf("Cluster is still initializing, wait a bit more")
}

if response.Password.Status == "error" {
return nil, nil, "", fmt.Errorf("Error getting cluster password")
}

agentclient, err := agent.Establish(ctx, client)
if err != nil {
return nil, nil, "", err
}

dialer, err := agentclient.ConnectToTunnel(ctx, org.Slug, "", false)
if err != nil {
return nil, nil, "", err
}

return &cluster, &proxy.ConnectParams{
Ports: []string{localProxyPort, "5432"},
OrganizationSlug: org.Slug,
Dialer: dialer,
RemoteHost: cluster.IpAssignments.Direct,
}, response.Password.Value, nil
}
2 changes: 2 additions & 0 deletions internal/command/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/superfly/flyctl/internal/command/machine"
"github.com/superfly/flyctl/internal/command/metrics"
"github.com/superfly/flyctl/internal/command/move"
"github.com/superfly/flyctl/internal/command/mpg"
"github.com/superfly/flyctl/internal/command/mysql"
"github.com/superfly/flyctl/internal/command/open"
"github.com/superfly/flyctl/internal/command/orgs"
Expand Down Expand Up @@ -126,6 +127,7 @@ func New() *cobra.Command {
group(ping.New(), "upkeep"),
group(proxy.New(), "upkeep"),
group(postgres.New(), "dbs_and_extensions"),
group(mpg.New(), "dbs_and_extensions"),
group(ips.New(), "configuring"),
group(secrets.New(), "configuring"),
group(ssh.New(), "upkeep"),
Expand Down
Loading

0 comments on commit d4c19ca

Please sign in to comment.