Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ ARGS:
[open-id-connect-config.groups-prefix] Prefix prepended to group claims to prevent name collision (such as `system:` groups). For example, the value `oidc:` will create group names like `oidc:engineering` and `oidc:infra`
[open-id-connect-config.required-claim.{index}] Multiple key=value pairs describing a required claim in the ID token. If set, the claims are verified to be present in the ID token with a matching value
[apiserver-cert-sans.{index}] Additional Subject Alternative Names for the Kubernetes API server certificate
[private-network-id] Private network ID for internal cluster communication (cannot be changed later)
[private-network-id] Private network ID for internal cluster communication (cannot be changed later). For Kapsule clusters, if none is provided, a private network will be created
[organization-id] Organization ID to use. If none is passed the default organization ID will be used
[region=fr-par] Region to target. If none is passed will use default region from the config (fr-par | nl-ams | pl-waw)

Expand Down
2 changes: 1 addition & 1 deletion docs/commands/k8s.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ scw k8s cluster create [arg=value ...]
| open-id-connect-config.groups-prefix | | Prefix prepended to group claims to prevent name collision (such as `system:` groups). For example, the value `oidc:` will create group names like `oidc:engineering` and `oidc:infra` |
| open-id-connect-config.required-claim.{index} | | Multiple key=value pairs describing a required claim in the ID token. If set, the claims are verified to be present in the ID token with a matching value |
| apiserver-cert-sans.{index} | | Additional Subject Alternative Names for the Kubernetes API server certificate |
| private-network-id | | Private network ID for internal cluster communication (cannot be changed later) |
| private-network-id | | Private network ID for internal cluster communication (cannot be changed later). For Kapsule clusters, if none is provided, a private network will be created |
| organization-id | | Organization ID to use. If none is passed the default organization ID will be used |
| region | Default: `fr-par`<br />One of: `fr-par`, `nl-ams`, `pl-waw` | Region to target. If none is passed will use default region from the config |

Expand Down
96 changes: 93 additions & 3 deletions internal/namespaces/k8s/v1/custom_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import (
"fmt"
"net/http"
"reflect"
"strings"
"time"

"github.com/fatih/color"
"github.com/scaleway/scaleway-cli/v2/internal/core"
"github.com/scaleway/scaleway-cli/v2/internal/human"
k8s "github.com/scaleway/scaleway-sdk-go/api/k8s/v1"
"github.com/scaleway/scaleway-sdk-go/api/vpc/v2"
"github.com/scaleway/scaleway-sdk-go/scw"
)

Expand Down Expand Up @@ -88,6 +90,7 @@ func clusterCreateBuilder(c *core.Command) *core.Command {

c.ArgSpecs.GetByName("cni").Default = core.DefaultValueSetter("cilium")
c.ArgSpecs.GetByName("version").Default = core.DefaultValueSetter("latest")
c.ArgSpecs.GetByName("private-network-id").Short += ". For Kapsule clusters, if none is provided, a private network will be created"

c.Interceptor = func(ctx context.Context, argsI interface{}, runner core.CommandRunner) (interface{}, error) {
args := argsI.(*k8s.CreateClusterRequest)
Expand All @@ -104,6 +107,84 @@ func clusterCreateBuilder(c *core.Command) *core.Command {
return runner(ctx, args)
}

c.Run = func(ctx context.Context, args interface{}) (i interface{}, e error) {
request := args.(*k8s.CreateClusterRequest)

client := core.ExtractClient(ctx)
k8sAPI := k8s.NewAPI(client)
vpcAPI := vpc.NewAPI(client)

pnCreated := false
var pn *vpc.PrivateNetwork
var err error

if request.Type == "" || strings.HasPrefix(request.Type, "kapsule") {
if request.PrivateNetworkID == nil {
pn, err = vpcAPI.CreatePrivateNetwork(&vpc.CreatePrivateNetworkRequest{
Region: request.Region,
Tags: []string{"created-along-with-k8s-cluster", "created-by-cli"},
}, scw.WithContext(ctx))
if err != nil {
return nil, err
}
request.PrivateNetworkID = scw.StringPtr(pn.ID)
pnCreated = true
} else {
pn, err = vpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{
Region: request.Region,
PrivateNetworkID: pn.ID,
}, scw.WithContext(ctx))
if err != nil {
return nil, err
}
}
}

cluster, err := k8sAPI.CreateCluster(request, scw.WithContext(ctx))
if err != nil {
if pnCreated {
errPN := vpcAPI.DeletePrivateNetwork(&vpc.DeletePrivateNetworkRequest{
Region: request.Region,
PrivateNetworkID: pn.ID,
}, scw.WithContext(ctx))

if err != nil {
return nil, errors.Join(err, errPN)
}
}
return nil, err
}

return struct {
*k8s.Cluster
PrivateNetwork *vpc.PrivateNetwork `json:"private_network"`
}{
cluster,
pn,
}, nil
}

c.View = &core.View{
Sections: []*core.ViewSection{
{
FieldName: "AutoscalerConfig",
Title: "Autoscaler configuration",
},
{
FieldName: "AutoUpgrade",
Title: "Auto-upgrade settings",
},
{
FieldName: "OpenIDConnectConfig",
Title: "Open ID Connect configuration",
},
{
FieldName: "PrivateNetwork",
Title: "Private Network",
},
},
}

return c
}

Expand Down Expand Up @@ -204,9 +285,18 @@ func clusterUpdateBuilder(c *core.Command) *core.Command {

func waitForClusterFunc(action int) core.WaitFunc {
return func(ctx context.Context, _, respI interface{}) (interface{}, error) {
var clusterResponse *k8s.Cluster
if action == clusterActionCreate {
clusterResponse = respI.(struct {
*k8s.Cluster
PrivateNetwork *vpc.PrivateNetwork `json:"private_network"`
}).Cluster
} else {
clusterResponse = respI.(*k8s.Cluster)
}
cluster, err := k8s.NewAPI(core.ExtractClient(ctx)).WaitForCluster(&k8s.WaitForClusterRequest{
Region: respI.(*k8s.Cluster).Region,
ClusterID: respI.(*k8s.Cluster).ID,
Region: clusterResponse.Region,
ClusterID: clusterResponse.ID,
Timeout: scw.TimeDurationPtr(clusterActionTimeout),
RetryInterval: core.DefaultRetryInterval,
})
Expand All @@ -223,7 +313,7 @@ func waitForClusterFunc(action int) core.WaitFunc {
notFoundError := &scw.ResourceNotFoundError{}
responseError := &scw.ResponseError{}
if errors.As(err, &responseError) && responseError.StatusCode == http.StatusNotFound || errors.As(err, &notFoundError) {
return fmt.Sprintf("Cluster %s successfully deleted.", respI.(*k8s.Cluster).ID), nil
return fmt.Sprintf("Cluster %s successfully deleted.", clusterResponse.ID), nil
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/namespaces/k8s/v1/custom_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
func Test_GetCluster(t *testing.T) {
t.Run("Simple", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createCluster("Cluster", kapsuleVersion, 1, "DEV1-M"),
BeforeFunc: createCluster("get-cluster", "Cluster", kapsuleVersion, 1, "DEV1-M"),
Cmd: "scw k8s cluster get {{ .Cluster.ID }}",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
Expand All @@ -22,7 +22,7 @@ func Test_GetCluster(t *testing.T) {
func Test_WaitCluster(t *testing.T) {
t.Run("wait for pools", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createCluster("Cluster", kapsuleVersion, 1, "GP1-XS"),
BeforeFunc: createCluster("wait-cluster", "Cluster", kapsuleVersion, 1, "GP1-XS"),
Cmd: "scw k8s cluster wait {{ .Cluster.ID }} wait-for-pools=true",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
Expand Down
2 changes: 1 addition & 1 deletion internal/namespaces/k8s/v1/custom_kubeconfig_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Test_GetKubeconfig(t *testing.T) {
////
t.Run("simple", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createClusterAndWaitAndKubeconfig("Cluster", "Kubeconfig", kapsuleVersion),
BeforeFunc: createClusterAndWaitAndKubeconfig("get-kubeconfig", "Cluster", "Kubeconfig", kapsuleVersion),
Cmd: "scw k8s kubeconfig get {{ .Cluster.ID }}",
Check: core.TestCheckCombine(
core.TestCheckGolden(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func Test_InstallKubeconfig(t *testing.T) {
////
t.Run("simple", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createClusterAndWaitAndKubeconfig("Cluster", "Kubeconfig", kapsuleVersion),
BeforeFunc: createClusterAndWaitAndKubeconfig("install-kubeconfig-simple", "Cluster", "Kubeconfig", kapsuleVersion),
Cmd: "scw k8s kubeconfig install {{ .Cluster.ID }}",
Check: core.TestCheckCombine(
// no golden tests since it's os specific
Expand All @@ -158,7 +158,7 @@ func Test_InstallKubeconfig(t *testing.T) {

t.Run("merge", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createClusterAndWaitAndKubeconfigAndPopulateFile("Cluster", "Kubeconfig", kapsuleVersion, path.Join(os.TempDir(), "cli-merge-test"), []byte(existingKubeconfig)),
BeforeFunc: createClusterAndWaitAndKubeconfigAndPopulateFile("install-kubeconfig-merge", "Cluster", "Kubeconfig", kapsuleVersion, path.Join(os.TempDir(), "cli-merge-test"), []byte(existingKubeconfig)),
Cmd: "scw k8s kubeconfig install {{ .Cluster.ID }}",
Check: core.TestCheckCombine(
// no golden tests since it's os specific
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

// testIfKubeconfigNotInFile checks if the given kubeconfig is not in the given file
// it test if the user, cluster and context of the kubeconfig file are not in the given file
// it tests if the user, cluster and context of the kubeconfig file are not in the given file
func testIfKubeconfigNotInFile(t *testing.T, filePath string, suffix string, kubeconfig api.Config) {
kubeconfigBytes, err := os.ReadFile(filePath)
assert.Nil(t, err)
Expand Down Expand Up @@ -55,7 +55,7 @@ func Test_UninstallKubeconfig(t *testing.T) {
////
t.Run("uninstall", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createClusterAndWaitAndInstallKubeconfig("Cluster", "Kubeconfig", kapsuleVersion),
BeforeFunc: createClusterAndWaitAndInstallKubeconfig("uninstall-kubeconfig", "Cluster", "Kubeconfig", kapsuleVersion),
Cmd: "scw k8s kubeconfig uninstall {{ .Cluster.ID }}",
Check: core.TestCheckCombine(
// no golden tests since it's os specific
Expand All @@ -71,7 +71,7 @@ func Test_UninstallKubeconfig(t *testing.T) {
}))
t.Run("empty file", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createClusterAndWaitAndKubeconfig("EmptyCluster", "Kubeconfig", kapsuleVersion),
BeforeFunc: createClusterAndWaitAndKubeconfig("uninstall-kubeconfig-empty", "EmptyCluster", "Kubeconfig", kapsuleVersion),
Cmd: "scw k8s kubeconfig uninstall {{ .EmptyCluster.ID }}",
Check: core.TestCheckCombine(
// no golden tests since it's os specific
Expand All @@ -88,7 +88,7 @@ func Test_UninstallKubeconfig(t *testing.T) {
}))
t.Run("uninstall-merge", core.Test(&core.TestConfig{
Commands: GetCommands(),
BeforeFunc: createClusterAndWaitAndKubeconfigAndPopulateFileAndInstall("Cluster", "Kubeconfig", kapsuleVersion, path.Join(os.TempDir(), "cli-uninstall-merge-test"), []byte(existingKubeconfig)),
BeforeFunc: createClusterAndWaitAndKubeconfigAndPopulateFileAndInstall("uninstall-kubeconfig-merge", "Cluster", "Kubeconfig", kapsuleVersion, path.Join(os.TempDir(), "cli-uninstall-merge-test"), []byte(existingKubeconfig)),
Cmd: "scw k8s kubeconfig uninstall {{ .Cluster.ID }}",
Check: core.TestCheckCombine(
// no golden tests since it's os specific
Expand Down
2 changes: 1 addition & 1 deletion internal/namespaces/k8s/v1/custom_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func Test_GetVersion(t *testing.T) {
////
t.Run("simple", core.Test(&core.TestConfig{
Commands: GetCommands(),
Cmd: "scw k8s version get 1.20.2",
Cmd: "scw k8s version get " + kapsuleVersion,
Check: core.TestCheckCombine(
core.TestCheckGolden(),
core.TestCheckExitCode(0),
Expand Down
22 changes: 11 additions & 11 deletions internal/namespaces/k8s/v1/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

const (
kapsuleVersion = "1.17.3"
kapsuleVersion = "1.27.1"
)

//
Expand All @@ -21,17 +21,17 @@ const (

// createCluster creates a basic cluster with "poolSize" dev1-m as nodes, the given version and
// register it in the context Meta at metaKey.
func createCluster(metaKey string, version string, poolSize int, nodeType string) core.BeforeFunc {
func createCluster(clusterNameSuffix string, metaKey string, version string, poolSize int, nodeType string) core.BeforeFunc {
return core.ExecStoreBeforeCmd(
metaKey,
fmt.Sprintf("scw k8s cluster create name=cli-test version=%s cni=cilium pools.0.node-type=%s pools.0.size=%d pools.0.name=default", version, nodeType, poolSize))
fmt.Sprintf("scw k8s cluster create name=cli-test-%s version=%s cni=cilium pools.0.node-type=%s pools.0.size=%d pools.0.name=default", clusterNameSuffix, version, nodeType, poolSize))
}

// createClusterAndWaitAndKubeconfig creates a basic cluster with 1 dev1-m as node, the given version and
// register it in the context Meta at metaKey.
func createClusterAndWaitAndKubeconfig(metaKey string, kubeconfigMetaKey string, version string) core.BeforeFunc {
func createClusterAndWaitAndKubeconfig(clusterNameSuffix string, metaKey string, kubeconfigMetaKey string, version string) core.BeforeFunc {
return func(ctx *core.BeforeFuncCtx) error {
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", version)
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test-%s version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", clusterNameSuffix, version)
res := ctx.ExecuteCmd(strings.Split(cmd, " "))
cluster := res.(*k8s.Cluster)
ctx.Meta[metaKey] = cluster
Expand All @@ -58,9 +58,9 @@ func createClusterAndWaitAndKubeconfig(metaKey string, kubeconfigMetaKey string,

// createClusterAndWaitAndInstallKubeconfig creates a basic cluster with 1 dev1-m as node, the given version and
// register it in the context Meta at metaKey. And install the kubeconfig
func createClusterAndWaitAndInstallKubeconfig(metaKey string, kubeconfigMetaKey string, version string) core.BeforeFunc {
func createClusterAndWaitAndInstallKubeconfig(clusterNameSuffix string, metaKey string, kubeconfigMetaKey string, version string) core.BeforeFunc {
return func(ctx *core.BeforeFuncCtx) error {
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", version)
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test-%s version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", clusterNameSuffix, version)
res := ctx.ExecuteCmd(strings.Split(cmd, " "))
cluster := res.(*k8s.Cluster)
ctx.Meta[metaKey] = cluster
Expand Down Expand Up @@ -89,9 +89,9 @@ func createClusterAndWaitAndInstallKubeconfig(metaKey string, kubeconfigMetaKey

// createClusterAndWaitAndKubeconfigAndPopulateFile creates a basic cluster with 1 dev1-m as node, the given version and
// register it in the context Meta at metaKey. It also populates the given file with the given content
func createClusterAndWaitAndKubeconfigAndPopulateFile(metaKey string, kubeconfigMetaKey string, version string, file string, content []byte) core.BeforeFunc {
func createClusterAndWaitAndKubeconfigAndPopulateFile(clusterNameSuffix string, metaKey string, kubeconfigMetaKey string, version string, file string, content []byte) core.BeforeFunc {
return func(ctx *core.BeforeFuncCtx) error {
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", version)
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test-%s version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", clusterNameSuffix, version)
res := ctx.ExecuteCmd(strings.Split(cmd, " "))
cluster := res.(*k8s.Cluster)
ctx.Meta[metaKey] = cluster
Expand Down Expand Up @@ -119,9 +119,9 @@ func createClusterAndWaitAndKubeconfigAndPopulateFile(metaKey string, kubeconfig

// createClusterAndWaitAndKubeconfigAndPopulateFileAndInstall creates a basic cluster with 1 dev1-m as node, the given version and
// register it in the context Meta at metaKey. It also populates the given file with the given content and install the new kubeconfig
func createClusterAndWaitAndKubeconfigAndPopulateFileAndInstall(metaKey string, kubeconfigMetaKey string, version string, file string, content []byte) core.BeforeFunc {
func createClusterAndWaitAndKubeconfigAndPopulateFileAndInstall(clusterNameSuffix string, metaKey string, kubeconfigMetaKey string, version string, file string, content []byte) core.BeforeFunc {
return func(ctx *core.BeforeFuncCtx) error {
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", version)
cmd := fmt.Sprintf("scw k8s cluster create name=cli-test-%s version=%s cni=cilium pools.0.node-type=DEV1-M pools.0.size=1 pools.0.name=default --wait", clusterNameSuffix, version)
res := ctx.ExecuteCmd(strings.Split(cmd, " "))
cluster := res.(*k8s.Cluster)
ctx.Meta[metaKey] = cluster
Expand Down
Loading