Skip to content

Commit 76da1fc

Browse files
committed
✨ feat(kube): add OKS aliases
1 parent 579ff6a commit 76da1fc

94 files changed

Lines changed: 4418 additions & 317 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/iaas.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func init() {
3232
b.BuildAPI(iaasCmd, func(m reflect.Method) bool {
3333
return m.Type.NumIn() == 4 && m.Type.NumOut() == 2 && !strings.HasSuffix(m.Name, "Raw")
3434
}, oapi)
35-
b.Build(iaasCmd)
35+
b.Build(iaasCmd, nil)
3636

3737
cmd, _, err := iaasCmd.Find([]string{"net"})
3838
if err != nil {
@@ -49,7 +49,7 @@ func oapi(cmd *cobra.Command, args []string) {
4949
p := loadProfile(cmd)
5050
cl, err := osc.NewClient(p, sdkOptions(cmd)...)
5151
if err == nil {
52-
err = runner.Run[osc.Client, *osc.ErrorResponse](cmd, args, cl, config.For("iaas"))
52+
err = runner.Run[*osc.Client, *osc.ErrorResponse](cmd, args, cl, config.For("iaas"))
5353
}
5454
if err != nil {
5555
messages.ExitErr(err)

cmd/kube.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ SPDX-License-Identifier: BSD-3-Clause
66
package cmd
77

88
import (
9+
"context"
10+
"fmt"
911
"reflect"
1012
"strings"
1113

14+
"github.com/google/uuid"
1215
"github.com/outscale/octl/pkg/builder"
1316
"github.com/outscale/octl/pkg/config"
1417
"github.com/outscale/octl/pkg/debug"
@@ -33,17 +36,86 @@ func init() {
3336
return m.Type.NumIn() >= 3 && m.Type.NumOut() == 2 && !strings.HasSuffix(m.Name, "Raw") &&
3437
!strings.HasSuffix(m.Name, "WithBody")
3538
}, kube)
36-
b.Build(oksCmd)
39+
b.Build(oksCmd, nil)
40+
41+
oksCmd.AddCommand(kubectlCmd)
3742
}
3843

3944
func kube(cmd *cobra.Command, args []string) {
4045
debug.Println(cmd.Name() + " called")
4146
p := loadProfile(cmd)
4247
cl, err := oks.NewClient(p, sdkOptions(cmd)...)
4348
if err == nil {
44-
err = runner.Run[oks.Client, *oks.ErrorResponse](cmd, args, cl, config.For("kube"))
49+
args = argNameToID(cmd, args, cl)
50+
flagNameToID(cmd, cl)
51+
err = runner.Run[*oks.Client, *oks.ErrorResponse](cmd, args, cl, config.For("kube"))
4552
}
4653
if err != nil {
4754
messages.ExitErr(err)
4855
}
4956
}
57+
58+
func argNameToID(cmd *cobra.Command, args []string, cl *oks.Client) []string {
59+
if len(args) == 0 {
60+
debug.Println("no arg to replace")
61+
return args
62+
}
63+
if _, err := uuid.Parse(args[0]); err == nil {
64+
debug.Println("arg is an uuid")
65+
return args
66+
}
67+
var err error
68+
switch cmd.Name() {
69+
case "GetProject", "DeleteProject", "GetProjectNets", "GetProjectQuotas", "GetProjectPublicIps", "GetProjectSnapshots":
70+
args[0], err = projectNameToID(cmd.Context(), args[0], cl)
71+
case "GetCluster", "UpdateCluster", "DeleteCluster", "GetKubeconfig":
72+
args[0], err = clusterNameToID(cmd.Context(), args[0], cl)
73+
}
74+
if err != nil {
75+
messages.ExitErr(err)
76+
}
77+
return args
78+
}
79+
80+
func flagNameToID(cmd *cobra.Command, cl *oks.Client) {
81+
f := cmd.Flags().Lookup("ProjectId")
82+
if f == nil {
83+
debug.Println("no flag to replace")
84+
return
85+
}
86+
id, err := projectNameToID(cmd.Context(), f.Value.String(), cl)
87+
if err != nil {
88+
messages.ExitErr(err)
89+
}
90+
_ = f.Value.Set(id)
91+
}
92+
93+
func projectNameToID(ctx context.Context, name string, cl *oks.Client) (string, error) {
94+
pjs, err := cl.ListProjects(ctx, &oks.ListProjectsParams{Name: &name})
95+
if err != nil {
96+
return "", err
97+
}
98+
switch len(pjs.Projects) {
99+
case 0:
100+
return "", fmt.Errorf("project %q not found", name)
101+
default:
102+
debug.Println("replacing", name, "by", pjs.Projects[0].Id)
103+
return pjs.Projects[0].Id, nil
104+
}
105+
}
106+
107+
func clusterNameToID(ctx context.Context, name string, cl *oks.Client) (string, error) {
108+
cs, err := cl.ListAllClusters(ctx, &oks.ListAllClustersParams{Name: &name})
109+
if err != nil {
110+
return "", err
111+
}
112+
switch len(cs.Clusters) {
113+
case 0:
114+
return "", fmt.Errorf("cluster %q not found", name)
115+
case 1:
116+
debug.Println("replacing", name, "by", cs.Clusters[0].Id)
117+
return cs.Clusters[0].Id, nil
118+
default:
119+
return "", fmt.Errorf("multiple clusters found with the name %q", name)
120+
}
121+
}

cmd/kube_kubeapi.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package cmd
2+
3+
import (
4+
"github.com/outscale/goutils/oks/clientset"
5+
oksv1beta2 "github.com/outscale/goutils/oks/clientset/typed/oks.dev/v1beta2"
6+
"github.com/outscale/octl/pkg/config"
7+
"github.com/outscale/octl/pkg/messages"
8+
"github.com/outscale/octl/pkg/runner"
9+
"github.com/outscale/osc-sdk-go/v3/pkg/oks"
10+
"github.com/outscale/osc-sdk-go/v3/pkg/osc"
11+
"github.com/spf13/cobra"
12+
"k8s.io/client-go/tools/clientcmd"
13+
)
14+
15+
func kubeapi(provider string) func(cmd *cobra.Command, args []string) {
16+
return func(cmd *cobra.Command, args []string) {
17+
p := loadProfile(cmd)
18+
cl, err := oks.NewClient(p, sdkOptions(cmd)...)
19+
if err != nil {
20+
messages.ExitErr(err)
21+
}
22+
cluster, _ := cmd.Flags().GetString("cluster")
23+
kubeconfig, err := getKubeconfig(cmd.Context(), cluster, cl)
24+
if err != nil {
25+
messages.ExitErr(err)
26+
}
27+
28+
cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
29+
if err != nil {
30+
messages.ExitErr(err)
31+
}
32+
client, err := clientset.NewForConfig(cfg)
33+
if err != nil {
34+
messages.ExitErr(err)
35+
}
36+
37+
err = runner.Run[oksv1beta2.NodePoolInterface, *osc.ErrorResponse](cmd, args, client.OksV1beta2().NodePools(), config.For(provider))
38+
if err != nil {
39+
messages.ExitErr(err)
40+
}
41+
}
42+
}

cmd/kube_kubectl.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"crypto/x509"
6+
"encoding/pem"
7+
"errors"
8+
"fmt"
9+
"io/fs"
10+
"os"
11+
"path/filepath"
12+
"time"
13+
14+
"github.com/outscale/octl/pkg/debug"
15+
"github.com/outscale/octl/pkg/messages"
16+
"github.com/outscale/osc-sdk-go/v3/pkg/oks"
17+
"github.com/spf13/cobra"
18+
"k8s.io/client-go/tools/clientcmd"
19+
kubecmd "k8s.io/kubectl/pkg/cmd"
20+
)
21+
22+
var kubectlCmd = &cobra.Command{
23+
Use: "kubectl cluster_name",
24+
DisableFlagParsing: true,
25+
Run: kubectl,
26+
}
27+
28+
func kubectl(cmd *cobra.Command, args []string) {
29+
p := loadProfile(cmd)
30+
cl, err := oks.NewClient(p, sdkOptions(cmd)...)
31+
if err != nil {
32+
messages.ExitErr(err)
33+
}
34+
cluster := args[0]
35+
kubeconfig, err := getKubeconfig(cmd.Context(), cluster, cl)
36+
if err != nil {
37+
messages.ExitErr(err)
38+
}
39+
newArgs := make([]string, 1, len(args)+2)
40+
newArgs[0] = "kubectl"
41+
newArgs = append(newArgs, args[1:]...)
42+
newArgs = append(newArgs, "--kubeconfig", kubeconfig)
43+
os.Args = newArgs
44+
45+
kubectlCmd := kubecmd.NewDefaultKubectlCommand()
46+
err = kubectlCmd.ExecuteContext(cmd.Context())
47+
if err != nil {
48+
messages.ExitErr(err)
49+
}
50+
}
51+
52+
func getKubeconfig(ctx context.Context, cluster string, cl *oks.Client) (string, error) {
53+
id, err := clusterNameToID(ctx, cluster, cl)
54+
if err != nil {
55+
return "", err
56+
}
57+
filename, err := kubeconfigPath(id)
58+
if err != nil {
59+
return "", err
60+
}
61+
debug.Println("kubeconfig path", filename)
62+
if _, err := os.Stat(filename); errors.Is(err, fs.ErrNotExist) {
63+
debug.Println("no kubeconfig; refreshing")
64+
err = refreshKubeconfig(ctx, id, filename, cl)
65+
if err != nil {
66+
return "", err
67+
}
68+
return filename, nil
69+
}
70+
config, err := clientcmd.LoadFromFile(filename)
71+
if err != nil {
72+
return "", err
73+
}
74+
for _, user := range config.AuthInfos {
75+
b, _ := pem.Decode([]byte(user.ClientCertificateData))
76+
if b == nil {
77+
return "", errors.New("invalid kubeconfig certificate")
78+
}
79+
var decoded *x509.Certificate
80+
decoded, err = x509.ParseCertificate(b.Bytes)
81+
debug.Println("kubeconfig valid until", decoded.NotAfter)
82+
if err == nil && time.Since(decoded.NotAfter) > 0 {
83+
debug.Println("expired kubeconfig certificate; refreshing")
84+
err = refreshKubeconfig(ctx, id, filename, cl)
85+
}
86+
if err != nil {
87+
return "", err
88+
}
89+
break
90+
}
91+
return filename, nil
92+
}
93+
94+
func kubeconfigPath(id string) (string, error) {
95+
dir, err := os.UserConfigDir()
96+
if err != nil {
97+
return "", fmt.Errorf("config dir: %w", err)
98+
}
99+
path := filepath.Join(dir, "octl", "kube", "kubeconfig")
100+
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
101+
err = os.MkdirAll(path, 0o700)
102+
if err != nil {
103+
return "", fmt.Errorf("config dir: %w", err)
104+
}
105+
}
106+
return filepath.Join(path, id+".kubeconfig"), nil
107+
}
108+
109+
func refreshKubeconfig(ctx context.Context, id, path string, cl *oks.Client) error {
110+
messages.Info("Refreshing kubeconfig")
111+
res, err := cl.GetKubeconfig(ctx, id, &oks.GetKubeconfigParams{})
112+
if err != nil {
113+
return fmt.Errorf("fetch Kubeconfig: %w", err)
114+
}
115+
return os.WriteFile(path, []byte(res.Cluster.Data.Kubeconfig), 0o600)
116+
}

cmd/kube_nodepool.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cmd
2+
3+
import (
4+
"reflect"
5+
"slices"
6+
7+
oksv1beta2 "github.com/outscale/goutils/oks/clientset/typed/oks.dev/v1beta2"
8+
"github.com/outscale/octl/pkg/builder"
9+
"github.com/samber/lo"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// oksCmd represents the kubecommand
14+
var nodepoolCmd = &cobra.Command{
15+
GroupID: "service",
16+
Use: "nodepool",
17+
Short: "nodepool commands",
18+
Aliases: []string{"np"},
19+
}
20+
21+
func init() {
22+
oksCmd.AddCommand(nodepoolCmd)
23+
b := builder.NewBuilder[oksv1beta2.NodePoolInterface]("kubeclient_nodepool", "https://docs.outscale.com/api.html")
24+
b.BuildAPI(nodepoolCmd, func(m reflect.Method) bool {
25+
return slices.Contains([]string{"List", "Get", "Create", "Update", "Delete"}, m.Name)
26+
}, kubeapi("kubeclient_nodepool"))
27+
apiCmd, _ := lo.Find(nodepoolCmd.Commands(), func(c *cobra.Command) bool { return c.Name() == "api" })
28+
apiCmd.PersistentFlags().String("cluster", "", "Name or ID of cluster")
29+
_ = apiCmd.MarkPersistentFlagRequired("cluster")
30+
// nodepool commands need to be added to the upper level, otherwise we will get kube nodepool nodepool
31+
b.Build(oksCmd, apiCmd)
32+
}

cmd/storage.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func init() {
3737
b.BuildAPI(storageCmd, func(m reflect.Method) bool {
3838
return true
3939
}, callOOS)
40-
b.Build(storageCmd)
40+
b.Build(storageCmd, nil)
4141

4242
runner.RegisterHook("auto-content-type", guessContentType)
4343
}
@@ -47,7 +47,7 @@ func callOOS(cmd *cobra.Command, args []string) {
4747
p := loadProfile(cmd)
4848
cl, err := oos.NewClient(cmd.Context(), p, awsOptions(cmd)...)
4949
if err == nil {
50-
err = runner.Run[oos.Client, oos.Error](cmd, args, cl, config.For("storage"))
50+
err = runner.Run[*oos.Client, oos.Error](cmd, args, cl, config.For("storage"))
5151
}
5252
if err != nil {
5353
_ = flags.CloseAll(cmd.Flags())

docs/reference/octl_iaas_catalog.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,4 @@ catalog commands
3333

3434
* [octl iaas](octl_iaas.md) - OUTSCALE IaaS management
3535
* [octl iaas catalog list](octl_iaas_catalog_list.md) - alias for api ReadCatalog
36-
* [octl iaas catalog list](octl_iaas_catalog_list.md) - alias for api ReadCatalogs
3736

docs/reference/octl_iaas_catalog_list.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
## octl iaas catalog list
22

3-
alias for api ReadCatalogs
3+
alias for api ReadCatalog
44

55
### Synopsis
66

7-
> *alias for api ReadCatalogs*
7+
> *alias for api ReadCatalog*
88
9-
Returns the price list of OUTSCALE services for the current Region within a specific time period.
9+
Returns the price list of OUTSCALE services for the current Region.
1010

1111
```
1212
octl iaas catalog list [flags]
@@ -15,10 +15,7 @@ octl iaas catalog list [flags]
1515
### Options
1616

1717
```
18-
--current-catalog-only By default or if set to true, only returns the current catalog.
19-
--from-date osctime The beginning of the time period, in ISO 8601 date format (for example, 2020-06-14).
20-
-h, --help help for list
21-
--to-date osctime The end of the time period, in ISO 8601 date format (for example, 2020-06-30).
18+
-h, --help help for list
2219
```
2320

2421
### Options inherited from parent commands

docs/reference/octl_iaas_usergroup.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ usergroup commands
3434
* [octl iaas](octl_iaas.md) - OUTSCALE IaaS management
3535
* [octl iaas usergroup create](octl_iaas_usergroup_create.md) - alias for api CreateUserGroup
3636
* [octl iaas usergroup delete](octl_iaas_usergroup_delete.md) - alias for api DeleteUserGroup --UserGroupName user_group_name
37-
* [octl iaas usergroup list](octl_iaas_usergroup_list.md) - alias for api ReadUserGroup
3837
* [octl iaas usergroup list](octl_iaas_usergroup_list.md) - alias for api ReadUserGroups
3938
* [octl iaas usergroup update](octl_iaas_usergroup_update.md) - alias for api UpdateUserGroup --UserGroupName user_group_name
4039

docs/reference/octl_iaas_usergrouppolicy.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,4 @@ usergrouppolicy commands
3333

3434
* [octl iaas](octl_iaas.md) - OUTSCALE IaaS management
3535
* [octl iaas usergrouppolicy list](octl_iaas_usergrouppolicy_list.md) - alias for api ReadUserGroupPolicies
36-
* [octl iaas usergrouppolicy list](octl_iaas_usergrouppolicy_list.md) - alias for api ReadUserGroupPolicy
3736

0 commit comments

Comments
 (0)