| Serge Bazanski | 1f8cad7 | 2023-03-20 16:58:10 +0100 | [diff] [blame] | 1 | package cluster |
| 2 | |
| 3 | import ( |
| Serge Bazanski | 7eeef0f | 2024-02-05 14:40:15 +0100 | [diff] [blame^] | 4 | "context" |
| 5 | "crypto/x509" |
| Serge Bazanski | 1f8cad7 | 2023-03-20 16:58:10 +0100 | [diff] [blame] | 6 | "fmt" |
| 7 | "net" |
| 8 | "os" |
| 9 | "path" |
| 10 | "sort" |
| 11 | |
| 12 | "github.com/kballard/go-shellquote" |
| 13 | |
| 14 | metroctl "source.monogon.dev/metropolis/cli/metroctl/core" |
| 15 | "source.monogon.dev/metropolis/cli/pkg/datafile" |
| 16 | ) |
| 17 | |
| 18 | const metroctlRunfile = "metropolis/cli/metroctl/metroctl_/metroctl" |
| 19 | |
| 20 | // MetroctlRunfilePath returns the absolute path to the metroctl binary available |
| 21 | // if the built target depends on //metropolis/cli/metroctl. Otherwise, an error |
| 22 | // is returned. |
| 23 | func MetroctlRunfilePath() (string, error) { |
| 24 | path, err := datafile.ResolveRunfile(metroctlRunfile) |
| 25 | if err != nil { |
| 26 | return "", fmt.Errorf("//metropolis/cli/metroctl not found in runfiles, did you include it as a data dependency? error: %w", err) |
| 27 | } |
| 28 | return path, nil |
| 29 | } |
| 30 | |
| Serge Bazanski | 7eeef0f | 2024-02-05 14:40:15 +0100 | [diff] [blame^] | 31 | type acceptall struct{} |
| 32 | |
| 33 | func (a *acceptall) Ask(ctx context.Context, _ *metroctl.ConnectOptions, _ *x509.Certificate) (bool, error) { |
| 34 | return true, nil |
| 35 | } |
| 36 | |
| Serge Bazanski | 1f8cad7 | 2023-03-20 16:58:10 +0100 | [diff] [blame] | 37 | // ConnectOptions returns metroctl.ConnectOptions that describe connectivity to |
| 38 | // the launched cluster. |
| 39 | func (c *Cluster) ConnectOptions() *metroctl.ConnectOptions { |
| 40 | // Use all metropolis nodes as endpoints. That's fine, metroctl's resolver will |
| 41 | // figure out what to actually use. |
| 42 | var endpoints []string |
| 43 | for _, n := range c.Nodes { |
| 44 | endpoints = append(endpoints, n.ManagementAddress) |
| 45 | } |
| 46 | sort.Strings(endpoints) |
| 47 | return &metroctl.ConnectOptions{ |
| 48 | ConfigPath: c.metroctlDir, |
| 49 | ProxyServer: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", c.Ports[SOCKSPort])), |
| 50 | Endpoints: endpoints, |
| Serge Bazanski | 7eeef0f | 2024-02-05 14:40:15 +0100 | [diff] [blame^] | 51 | TOFU: &acceptall{}, |
| Serge Bazanski | 1f8cad7 | 2023-03-20 16:58:10 +0100 | [diff] [blame] | 52 | } |
| 53 | } |
| 54 | |
| 55 | // MetroctlFlags return stringified flags to pass to a metroctl binary to connect |
| 56 | // to the launched cluster. |
| 57 | func (c *Cluster) MetroctlFlags() string { |
| 58 | return shellquote.Join(c.ConnectOptions().ToFlags()...) |
| 59 | } |
| 60 | |
| 61 | // MakeMetroctlWrapper builds and returns the path to a shell script which calls |
| 62 | // metroctl (from //metropolis/cli/metroctl, which must be included as a data |
| 63 | // dependency of the built target) with all the required flags to connect to the |
| 64 | // launched cluster. |
| 65 | func (c *Cluster) MakeMetroctlWrapper() (string, error) { |
| 66 | mpath, err := MetroctlRunfilePath() |
| 67 | if err != nil { |
| 68 | return "", err |
| 69 | } |
| 70 | wpath := path.Join(c.metroctlDir, "metroctl.sh") |
| 71 | |
| 72 | // Don't create wrapper if it already exists. |
| 73 | if _, err := os.Stat(wpath); err == nil { |
| 74 | return wpath, nil |
| 75 | } |
| 76 | |
| 77 | wrapper := fmt.Sprintf("#!/usr/bin/env bash\nexec %s %s \"$@\"", mpath, c.MetroctlFlags()) |
| 78 | if err := os.WriteFile(wpath, []byte(wrapper), 0555); err != nil { |
| 79 | return "", fmt.Errorf("could not write wrapper: %w", err) |
| 80 | } |
| 81 | return wpath, nil |
| 82 | } |