Merge pull request #128 from foomo/feat/stackit

fix: stackit provider
This commit is contained in:
Kevin Franklin Kim 2024-07-31 14:16:59 +02:00 committed by GitHub
commit 1fcc40b0d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 327 additions and 14 deletions

98
foomo/beam/beam.go Normal file
View File

@ -0,0 +1,98 @@
package beam
import (
"fmt"
"net"
"os/exec"
"time"
"github.com/foomo/posh-providers/onepassword"
"github.com/foomo/posh/pkg/log"
"github.com/spf13/viper"
)
type (
Beam struct {
l log.Logger
cfg Config
op *onepassword.OnePassword
configKey string
}
Option func(*Beam) error
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func CommandWithConfigKey(v string) Option {
return func(o *Beam) error {
o.configKey = v
return nil
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
// NewBeam command
func NewBeam(l log.Logger, op *onepassword.OnePassword, opts ...Option) (*Beam, error) {
inst := &Beam{
l: l,
op: op,
configKey: "beam",
}
for _, opt := range opts {
if opt != nil {
if err := opt(inst); err != nil {
return nil, err
}
}
}
if err := viper.UnmarshalKey(inst.configKey, &inst.cfg); err != nil {
return nil, err
}
return inst, nil
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (t *Beam) Config() Config {
return t.cfg
}
func (t *Beam) Start() {
t.l.Info("Starting beam tunnels")
for _, tunnel := range t.cfg {
for _, cluster := range tunnel.Clusters {
go t.tunnel(cluster.Hostname, cluster.Port)
}
}
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (t *Beam) tunnel(hostname string, port int) {
for {
addr := fmt.Sprintf("127.0.0.1:%d", port)
if _, err := net.DialTimeout("tcp", addr, time.Second); err == nil {
t.l.Debug("tunnel/port already exists", "addr", addr, "err", err)
time.Sleep(10 * time.Second)
continue
}
cmd := exec.Command("cloudflared", "access", "tcp", "--hostname", hostname, "--url", fmt.Sprintf("127.0.0.1:%d", port))
t.l.Info("started tunnel", "addr", addr)
if err := cmd.Run(); err != nil {
t.l.Warn("failed to start tunnel", "error", err)
time.Sleep(time.Second)
continue
}
t.l.Info("done?", "addr", addr)
}
}

24
foomo/beam/checker.go Normal file
View File

@ -0,0 +1,24 @@
package beam
import (
"context"
"fmt"
"net"
"time"
"github.com/foomo/posh/pkg/log"
"github.com/foomo/posh/pkg/prompt/check"
)
func TunnelChecker(p *Beam, tunnel, cluster string) check.Checker {
return func(ctx context.Context, l log.Logger) check.Info {
name := "Beam"
c := p.Config().GetTunnel(tunnel).GetCluster(cluster)
addr := fmt.Sprintf("127.0.0.1:%d", c.Port)
if _, err := net.DialTimeout("tcp", addr, time.Second); err != nil {
return check.NewNoteInfo(name, fmt.Sprintf("Tunnel `%s` to cluster `%s` is closed", tunnel, cluster))
} else {
return check.NewSuccessInfo(name, fmt.Sprintf("Tunnel `%s` to cluster `%s` is open", tunnel, cluster))
}
}
}

11
foomo/beam/cluster.go Normal file
View File

@ -0,0 +1,11 @@
package beam
import (
"github.com/foomo/posh-providers/onepassword"
)
type Cluster struct {
Port int `json:"port" yaml:"port"`
Hostname string `json:"hostname" yaml:"hostname"`
Credentials onepassword.Secret `json:"credentials" yaml:"credentials"`
}

134
foomo/beam/command.go Normal file
View File

@ -0,0 +1,134 @@
package beam
import (
"context"
"fmt"
"os"
"path"
"strings"
"github.com/foomo/posh-providers/kubernets/kubectl"
"github.com/foomo/posh/pkg/command/tree"
"github.com/foomo/posh/pkg/log"
"github.com/foomo/posh/pkg/prompt/goprompt"
"github.com/foomo/posh/pkg/readline"
"github.com/foomo/posh/pkg/util/suggests"
)
type (
Command struct {
l log.Logger
beam *Beam
name string
kubectl *kubectl.Kubectl
commandTree tree.Root
}
CommandOption func(*Command)
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func CommandWithName(v string) CommandOption {
return func(o *Command) {
o.name = v
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewCommand(l log.Logger, beam *Beam, kubectl *kubectl.Kubectl, opts ...CommandOption) (*Command, error) {
inst := &Command{
l: l.Named("beam"),
name: "beam",
beam: beam,
kubectl: kubectl,
}
for _, opt := range opts {
if opt != nil {
opt(inst)
}
}
inst.commandTree = tree.New(&tree.Node{
Name: inst.name,
Description: "Run beam",
Nodes: tree.Nodes{
{
Name: "tunnel",
Values: func(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.beam.cfg.GetTunnelNames())
},
Description: "Tunnel",
Nodes: tree.Nodes{
{
Name: "kubeconfig",
Description: "Download kubeconfig",
Args: tree.Args{
{
Name: "cluster",
Description: "Cluster name",
Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
return suggests.List(inst.beam.Config().GetTunnel(r.Args().At(0)).GetClusterNames())
},
},
},
Execute: inst.kubeconfig,
},
},
},
},
})
return inst, nil
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Command) Name() string {
return c.commandTree.Node().Name
}
func (c *Command) Description() string {
return c.commandTree.Node().Description
}
func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return c.commandTree.Complete(ctx, r)
}
func (c *Command) Execute(ctx context.Context, r *readline.Readline) error {
return c.commandTree.Execute(ctx, r)
}
func (c *Command) Help(ctx context.Context, r *readline.Readline) string {
return c.commandTree.Help(ctx, r)
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (c *Command) kubeconfig(ctx context.Context, r *readline.Readline) error {
tunnel := c.beam.Config().GetTunnel(r.Args().At(0))
cluster := tunnel.GetCluster(r.Args().At(2))
kubeconfig, err := c.beam.op.GetDocument(ctx, cluster.Credentials)
if err != nil {
return err
}
filename := path.Join(c.kubectl.Config().ConfigPath, r.Args().At(2)+".yaml")
c.l.Info("Retrieving kubeconfig", "tunnel", r.Args().At(0), "cluster", r.Args().At(2), "filename", filename)
kubeconfig = strings.ReplaceAll(kubeconfig, "$PORT", fmt.Sprintf("%d", cluster.Port))
if err := os.WriteFile(filename, []byte(kubeconfig), 0600); err != nil {
return err
}
return nil
}

19
foomo/beam/config.go Normal file
View File

@ -0,0 +1,19 @@
package beam
import (
"sort"
"github.com/samber/lo"
)
type Config map[string]Tunnel
func (c Config) GetTunnel(name string) Tunnel {
return c[name]
}
func (c Config) GetTunnelNames() []string {
ret := lo.Keys(c)
sort.Strings(ret)
return ret
}

21
foomo/beam/tunnel.go Normal file
View File

@ -0,0 +1,21 @@
package beam
import (
"sort"
"github.com/samber/lo"
)
type Tunnel struct {
Clusters map[string]Cluster `json:"clusters" yaml:"clusters"`
}
func (c Tunnel) GetCluster(name string) Cluster {
return c.Clusters[name]
}
func (c Tunnel) GetClusterNames() []string {
ret := lo.Keys(c.Clusters)
sort.Strings(ret)
return ret
}

8
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/c-bata/go-prompt v0.2.6
github.com/cloudrecipes/packagejson v1.0.0
github.com/digitalocean/godo v1.119.0
github.com/foomo/posh v0.5.9
github.com/foomo/posh v0.5.10
github.com/google/go-github/v47 v47.1.0
github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
@ -19,7 +19,7 @@ require (
github.com/slack-go/slack v0.13.1
github.com/spf13/viper v1.19.0
go.uber.org/zap v1.27.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/oauth2 v0.21.0
golang.org/x/sync v0.7.0
gopkg.in/yaml.v3 v3.0.1
@ -30,7 +30,7 @@ require (
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/charlievieth/fastwalk v1.0.6 // indirect
github.com/charlievieth/fastwalk v1.0.8 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
@ -65,7 +65,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect

14
go.sum
View File

@ -24,8 +24,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat6
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/charlievieth/fastwalk v1.0.6 h1:C7nXgxQIjEkpKWT1fbXGFzQiblwqq2ZsxrR0ohh5IRs=
github.com/charlievieth/fastwalk v1.0.6/go.mod h1:rV19+IF9Y2TYQNy4MqEk5M/spNHjKsA0i71yrsv2p4E=
github.com/charlievieth/fastwalk v1.0.8 h1:uaoH6cAKSk73aK7aKXqs0+bL+J3Txzd3NGH8tRXgHko=
github.com/charlievieth/fastwalk v1.0.8/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
github.com/cloudrecipes/packagejson v1.0.0 h1:f6InIxXWQ9/u1XNk7pX2BeUIw7IOYZS5B26o685XbZk=
github.com/cloudrecipes/packagejson v1.0.0/go.mod h1:ZENm9DGj5m+2WMImPunZuW3Qn2Ljw/0kHOP4BcWhrrA=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
@ -40,8 +40,8 @@ github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/foomo/posh v0.5.9 h1:KZEkxX8zCeeO0Cx9FvFIdH7OvMpv9KynEWPxSH7mm4E=
github.com/foomo/posh v0.5.9/go.mod h1:xLRYrPt54UWaNX1QTC9gS21nJ0sMpsGlnuik1tqYjgw=
github.com/foomo/posh v0.5.10 h1:P3qNVZlh/7eKJG8dBTUaxLR9D3cwxgHrqCZGxsWpXqw=
github.com/foomo/posh v0.5.10/go.mod h1:NmElP3QZO52qSDGdVlhNyRms8Cryw7XqTRP6rNkuOos=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/franklinkim/go-prompt v0.2.7-0.20210427061716-a8f4995d7aa5 h1:kXNtle4AoQnngdm+gwt4ku6Llbzw3EFHgZYpL618JaI=
@ -198,6 +198,8 @@ golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -231,8 +233,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@ -80,6 +80,10 @@ func New(l log.Logger, cache cache.Cache, opts ...Option) (*Kubectl, error) {
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (k *Kubectl) Config() Config {
return k.cfg
}
func (k *Kubectl) Cluster(name string) *Cluster {
return NewCluster(k, name)
}

View File

@ -93,7 +93,7 @@ func NewCommand(l log.Logger, cache cache.Cache, stackit *Stackit, kubectl *kube
Name: "cluster",
Description: "Name of the cluster.",
Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []goprompt.Suggest {
project, err := inst.stackit.Config().Project(r.Args().At(0))
project, err := inst.stackit.Config().Project(r.Args().At(1))
if err != nil {
return []goprompt.Suggest{}
}
@ -147,11 +147,11 @@ func (c *Command) Help(ctx context.Context, r *readline.Readline) string {
func (c *Command) kubeconfig(ctx context.Context, r *readline.Readline) error {
ifs := r.FlagSets().Internal()
project, err := c.stackit.Config().Project(r.Args().At(0))
project, err := c.stackit.Config().Project(r.Args().At(1))
if err != nil {
return err
}
clusterName := r.Args().At(2)
clusterName := r.Args().At(3)
cluster, err := project.Cluster(clusterName)
if err != nil {
return err
@ -167,7 +167,7 @@ func (c *Command) kubeconfig(ctx context.Context, r *readline.Readline) error {
return err
}
return shell.New(ctx, c.l, "stackit", "ske", "cluster", "kubeconfig", "create", cluster.Name).
return shell.New(ctx, c.l, "stackit", "ske", "kubeconfig", "create", cluster.Name).
Args("--filepath", kubectlCluster.Config(profile)).
Args("--project-id", project.ID).
Args(r.AdditionalArgs()...).