mirror of
https://github.com/foomo/posh-providers.git
synced 2025-10-16 12:35:41 +00:00
commit
1fcc40b0d5
98
foomo/beam/beam.go
Normal file
98
foomo/beam/beam.go
Normal 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
24
foomo/beam/checker.go
Normal 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
11
foomo/beam/cluster.go
Normal 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
134
foomo/beam/command.go
Normal 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
19
foomo/beam/config.go
Normal 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
21
foomo/beam/tunnel.go
Normal 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
8
go.mod
@ -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
14
go.sum
@ -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=
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()...).
|
||||
|
||||
Loading…
Reference in New Issue
Block a user