Skip to content

Commit 388f37f

Browse files
authored
feat: ske kubeconfig create. merge kubeconfig into the default kubeconfig file #556 (#557)
* feat: ske kubeconfig create merge kubeconfig into the default kubeconfig file * feat: ske kubeconfig merge add flag overwrite --------- Signed-off-by: Javier Vela <[email protected]>
1 parent 5b1782e commit 388f37f

File tree

6 files changed

+256
-52
lines changed

6 files changed

+256
-52
lines changed

docs/stackit_ske_kubeconfig.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ stackit ske kubeconfig [flags]
3030
### SEE ALSO
3131

3232
* [stackit ske](./stackit_ske.md) - Provides functionality for SKE
33-
* [stackit ske kubeconfig create](./stackit_ske_kubeconfig_create.md) - Creates a kubeconfig for an SKE cluster
33+
* [stackit ske kubeconfig create](./stackit_ske_kubeconfig_create.md) - Creates or update a kubeconfig for an SKE cluster
3434
* [stackit ske kubeconfig login](./stackit_ske_kubeconfig_login.md) - Login plugin for kubernetes clients
3535

docs/stackit_ske_kubeconfig_create.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
## stackit ske kubeconfig create
22

3-
Creates a kubeconfig for an SKE cluster
3+
Creates or update a kubeconfig for an SKE cluster
44

55
### Synopsis
66

7-
Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster.
7+
Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exits in the kubeconfig file the information will be updated.
88

9-
By default the kubeconfig is created in the .kube folder, in the user's home directory. The kubeconfig file will be overwritten if it already exists.
9+
By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created.
1010
You can override this behavior by specifying a custom filepath with the --filepath flag.
11+
1112
An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h.
13+
1214
Note that the format is <value><unit>, e.g. 30d for 30 days and you can't combine units.
1315

1416
```
@@ -18,23 +20,26 @@ stackit ske kubeconfig create CLUSTER_NAME [flags]
1820
### Examples
1921

2022
```
21-
Create a kubeconfig for the SKE cluster with name "my-cluster"
23+
Create or update a kubeconfig for the SKE cluster with name "my-cluster. If the config exits in the kubeconfig file the information will be updated."
2224
$ stackit ske kubeconfig create my-cluster
2325
2426
Get a login kubeconfig for the SKE cluster with name "my-cluster". This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command.
2527
$ stackit ske kubeconfig create my-cluster --login
2628
27-
Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days
29+
Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days. If the config exits in the kubeconfig file the information will be updated.
2830
$ stackit ske kubeconfig create my-cluster --expiration 30d
2931
30-
Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months
32+
Create or update a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months. If the config exits in the kubeconfig file the information will be updated.
3133
$ stackit ske kubeconfig create my-cluster --expiration 2M
3234
33-
Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath
35+
Create or update a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath. If the config exits in the kubeconfig file the information will be updated.
3436
$ stackit ske kubeconfig create my-cluster --filepath /path/to/config
3537
3638
Get a kubeconfig for the SKE cluster with name "my-cluster" without writing it to a file and format the output as json
3739
$ stackit ske kubeconfig create my-cluster --disable-writing --output-format json
40+
41+
Create a kubeconfig for the SKE cluster with name "my-cluster. It will OVERWRITE your current kubeconfig file."
42+
$ stackit ske kubeconfig create my-cluster --overwrite true
3843
```
3944

4045
### Options
@@ -45,6 +50,7 @@ stackit ske kubeconfig create CLUSTER_NAME [flags]
4550
--filepath string Path to create the kubeconfig file. By default, the kubeconfig is created as 'config' in the .kube folder, in the user's home directory.
4651
-h, --help Help for "stackit ske kubeconfig create"
4752
-l, --login Create a login kubeconfig that obtains valid credentials via the STACKIT CLI. This flag is mutually exclusive with the expiration flag.
53+
--overwrite Overwrite the kubeconfig file.
4854
```
4955

5056
### Options inherited from parent commands

internal/cmd/ske/kubeconfig/create/create.go

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,52 +22,57 @@ import (
2222
const (
2323
clusterNameArg = "CLUSTER_NAME"
2424

25-
loginFlag = "login"
25+
disableWritingFlag = "disable-writing"
2626
expirationFlag = "expiration"
2727
filepathFlag = "filepath"
28-
disableWritingFlag = "disable-writing"
28+
loginFlag = "login"
29+
overwriteFlag = "overwrite"
2930
)
3031

3132
type inputModel struct {
3233
*globalflags.GlobalFlagModel
3334
ClusterName string
34-
Filepath *string
35+
DisableWriting bool
3536
ExpirationTime *string
37+
Filepath *string
3638
Login bool
37-
DisableWriting bool
39+
Overwrite bool
3840
}
3941

4042
func NewCmd(p *print.Printer) *cobra.Command {
4143
cmd := &cobra.Command{
4244
Use: fmt.Sprintf("create %s", clusterNameArg),
43-
Short: "Creates a kubeconfig for an SKE cluster",
45+
Short: "Creates or update a kubeconfig for an SKE cluster",
4446
Long: fmt.Sprintf("%s\n\n%s\n%s\n%s\n%s",
45-
"Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster.",
46-
"By default the kubeconfig is created in the .kube folder, in the user's home directory. The kubeconfig file will be overwritten if it already exists.",
47-
"You can override this behavior by specifying a custom filepath with the --filepath flag.",
48-
"An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h.",
47+
"Creates a kubeconfig for a STACKIT Kubernetes Engine (SKE) cluster, if the config exits in the kubeconfig file the information will be updated.",
48+
"By default, the kubeconfig information of the SKE cluster is merged into the default kubeconfig file of the current user. If the kubeconfig file doesn't exist, a new one will be created.",
49+
"You can override this behavior by specifying a custom filepath with the --filepath flag.\n",
50+
"An expiration time can be set for the kubeconfig. The expiration time is set in seconds(s), minutes(m), hours(h), days(d) or months(M). Default is 1h.\n",
4951
"Note that the format is <value><unit>, e.g. 30d for 30 days and you can't combine units."),
5052
Args: args.SingleArg(clusterNameArg, nil),
5153
Example: examples.Build(
5254
examples.NewExample(
53-
`Create a kubeconfig for the SKE cluster with name "my-cluster"`,
55+
`Create or update a kubeconfig for the SKE cluster with name "my-cluster. If the config exits in the kubeconfig file the information will be updated."`,
5456
"$ stackit ske kubeconfig create my-cluster"),
5557
examples.NewExample(
5658
`Get a login kubeconfig for the SKE cluster with name "my-cluster". `+
5759
"This kubeconfig does not contain any credentials and instead obtains valid credentials via the `stackit ske kubeconfig login` command.",
5860
"$ stackit ske kubeconfig create my-cluster --login"),
5961
examples.NewExample(
60-
`Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days`,
62+
`Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 30 days. If the config exits in the kubeconfig file the information will be updated.`,
6163
"$ stackit ske kubeconfig create my-cluster --expiration 30d"),
6264
examples.NewExample(
63-
`Create a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months`,
65+
`Create or update a kubeconfig for the SKE cluster with name "my-cluster" and set the expiration time to 2 months. If the config exits in the kubeconfig file the information will be updated.`,
6466
"$ stackit ske kubeconfig create my-cluster --expiration 2M"),
6567
examples.NewExample(
66-
`Create a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath`,
68+
`Create or update a kubeconfig for the SKE cluster with name "my-cluster" in a custom filepath. If the config exits in the kubeconfig file the information will be updated.`,
6769
"$ stackit ske kubeconfig create my-cluster --filepath /path/to/config"),
6870
examples.NewExample(
6971
`Get a kubeconfig for the SKE cluster with name "my-cluster" without writing it to a file and format the output as json`,
7072
"$ stackit ske kubeconfig create my-cluster --disable-writing --output-format json"),
73+
examples.NewExample(
74+
`Create a kubeconfig for the SKE cluster with name "my-cluster. It will OVERWRITE your current kubeconfig file."`,
75+
"$ stackit ske kubeconfig create my-cluster --overwrite true"),
7176
),
7277
RunE: func(cmd *cobra.Command, args []string) error {
7378
ctx := context.Background()
@@ -83,7 +88,12 @@ func NewCmd(p *print.Printer) *cobra.Command {
8388
}
8489

8590
if !model.AssumeYes && !model.DisableWriting {
86-
prompt := fmt.Sprintf("Are you sure you want to create a kubeconfig for SKE cluster %q? This will OVERWRITE your current kubeconfig file, if it exists.", model.ClusterName)
91+
var prompt string
92+
if model.Overwrite {
93+
prompt = fmt.Sprintf("Are you sure you want to create a kubeconfig for SKE cluster %q? This will OVERWRITE your current kubeconfig file, if it exists.", model.ClusterName)
94+
} else {
95+
prompt = fmt.Sprintf("Are you sure you want to update your kubeconfig for SKE cluster %q? This will update your kubeconfig file. \nIf it the kubeconfig file doesn't exists, it will create a new one.", model.ClusterName)
96+
}
8797
err = p.PromptForConfirmation(prompt)
8898
if err != nil {
8999
return err
@@ -137,10 +147,15 @@ func NewCmd(p *print.Printer) *cobra.Command {
137147
}
138148

139149
if !model.DisableWriting {
140-
err = skeUtils.WriteConfigFile(kubeconfigPath, kubeconfig)
150+
if model.Overwrite {
151+
err = skeUtils.WriteConfigFile(kubeconfigPath, kubeconfig)
152+
} else {
153+
err = skeUtils.MergeKubeConfig(kubeconfigPath, kubeconfig)
154+
}
141155
if err != nil {
142156
return fmt.Errorf("write kubeconfig file: %w", err)
143157
}
158+
p.Outputf("\nSet kubectl context to %s with: kubectl config use-context %s\n", model.ClusterName, model.ClusterName)
144159
}
145160

146161
return outputResult(p, model, kubeconfigPath, respKubeconfig, respLogin)
@@ -151,11 +166,11 @@ func NewCmd(p *print.Printer) *cobra.Command {
151166
}
152167

153168
func configureFlags(cmd *cobra.Command) {
169+
cmd.Flags().Bool(disableWritingFlag, false, fmt.Sprintf("Disable the writing of kubeconfig. Set the output format to json or yaml using the --%s flag to display the kubeconfig.", globalflags.OutputFormatFlag))
154170
cmd.Flags().BoolP(loginFlag, "l", false, "Create a login kubeconfig that obtains valid credentials via the STACKIT CLI. This flag is mutually exclusive with the expiration flag.")
155-
cmd.Flags().StringP(expirationFlag, "e", "", "Expiration time for the kubeconfig in seconds(s), minutes(m), hours(h), days(d) or months(M). Example: 30d. By default, expiration time is 1h")
156171
cmd.Flags().String(filepathFlag, "", "Path to create the kubeconfig file. By default, the kubeconfig is created as 'config' in the .kube folder, in the user's home directory.")
157-
cmd.Flags().Bool(disableWritingFlag, false, fmt.Sprintf("Disable the writing of kubeconfig. Set the output format to json or yaml using the --%s flag to display the kubeconfig.", globalflags.OutputFormatFlag))
158-
172+
cmd.Flags().StringP(expirationFlag, "e", "", "Expiration time for the kubeconfig in seconds(s), minutes(m), hours(h), days(d) or months(M). Example: 30d. By default, expiration time is 1h")
173+
cmd.Flags().Bool(overwriteFlag, false, "Overwrite the kubeconfig file.")
159174
cmd.MarkFlagsMutuallyExclusive(loginFlag, expirationFlag)
160175
}
161176

@@ -189,12 +204,13 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu
189204
}
190205

191206
model := inputModel{
192-
GlobalFlagModel: globalFlags,
193207
ClusterName: clusterName,
194-
Filepath: flags.FlagToStringPointer(p, cmd, filepathFlag),
208+
DisableWriting: disableWriting,
195209
ExpirationTime: expTime,
210+
Filepath: flags.FlagToStringPointer(p, cmd, filepathFlag),
211+
GlobalFlagModel: globalFlags,
196212
Login: flags.FlagToBoolValue(p, cmd, loginFlag),
197-
DisableWriting: disableWriting,
213+
Overwrite: flags.FlagToBoolValue(p, cmd, overwriteFlag),
198214
}
199215

200216
if p.IsVerbosityDebug() {
@@ -260,7 +276,7 @@ func outputResult(p *print.Printer, model *inputModel, kubeconfigPath string, re
260276
if respKubeconfig != nil {
261277
expiration = fmt.Sprintf(", with expiration date %v (UTC)", *respKubeconfig.ExpirationTimestamp)
262278
}
263-
p.Outputf("Created kubeconfig file for cluster %s in %q%s\n", model.ClusterName, kubeconfigPath, expiration)
279+
p.Outputf("Updated kubeconfig file for cluster %s in %q%s\n", model.ClusterName, kubeconfigPath, expiration)
264280

265281
return nil
266282
}

internal/cmd/ske/kubeconfig/create/create_test.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import (
44
"context"
55
"testing"
66

7-
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
8-
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
9-
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
10-
117
"github.com/google/go-cmp/cmp"
128
"github.com/google/go-cmp/cmp/cmpopts"
139
"github.com/google/uuid"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
1413
"github.com/stackitcloud/stackit-sdk-go/services/ske"
1514
)
1615

@@ -177,6 +176,28 @@ func TestParseInput(t *testing.T) {
177176
}),
178177
isValid: true,
179178
},
179+
{
180+
description: "enable overwrite",
181+
argValues: fixtureArgValues(),
182+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
183+
flagValues[overwriteFlag] = "true"
184+
}),
185+
expectedModel: fixtureInputModel(func(model *inputModel) {
186+
model.Overwrite = true
187+
}),
188+
isValid: true,
189+
},
190+
{
191+
description: "disable overwrite",
192+
argValues: fixtureArgValues(),
193+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
194+
flagValues[overwriteFlag] = "false"
195+
}),
196+
expectedModel: fixtureInputModel(func(model *inputModel) {
197+
model.Overwrite = false
198+
}),
199+
isValid: true,
200+
},
180201
}
181202

182203
for _, tt := range tests {

internal/pkg/services/ske/utils/utils.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package utils
33
import (
44
"context"
55
"fmt"
6+
"maps"
67
"os"
78
"path/filepath"
89
"strconv"
910

1011
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
12+
"k8s.io/client-go/tools/clientcmd"
13+
1114
"github.com/stackitcloud/stackit-sdk-go/services/ske"
1215
"golang.org/x/mod/semver"
1316
)
@@ -228,6 +231,39 @@ func ConvertToSeconds(timeStr string) (*string, error) {
228231
return utils.Ptr(strconv.FormatUint(result, 10)), nil
229232
}
230233

234+
// Merge new Kubeconfig into existing Kubeconfig. If it doesn´t exits, creates a new one
235+
func MergeKubeConfig(pathDestionationKubeConfig, contentNewKubeConfig string) error {
236+
if contentNewKubeConfig == "" {
237+
return fmt.Errorf("no data to merge. the new kubeconfig is empty")
238+
}
239+
240+
newConfig, err := clientcmd.Load([]byte(contentNewKubeConfig))
241+
if err != nil {
242+
return fmt.Errorf("error loading new kubeconfig: %w", err)
243+
}
244+
245+
// if the destionation kubeconfig does not exist, create a new one
246+
if _, err := os.Stat(pathDestionationKubeConfig); os.IsNotExist(err) {
247+
return WriteConfigFile(pathDestionationKubeConfig, contentNewKubeConfig)
248+
}
249+
250+
existingConfig, err := clientcmd.LoadFromFile(pathDestionationKubeConfig)
251+
if err != nil {
252+
return fmt.Errorf("error loading existing kubeconfig: %w", err)
253+
}
254+
255+
maps.Copy(existingConfig.AuthInfos, newConfig.AuthInfos)
256+
maps.Copy(existingConfig.Contexts, newConfig.Contexts)
257+
maps.Copy(existingConfig.Clusters, newConfig.Clusters)
258+
259+
err = clientcmd.WriteToFile(*existingConfig, pathDestionationKubeConfig)
260+
if err != nil {
261+
return fmt.Errorf("error writing merged kubeconfig: %w", err)
262+
}
263+
264+
return nil
265+
}
266+
231267
// WriteConfigFile writes the given data to the given path.
232268
// The directory is created if it does not exist.
233269
func WriteConfigFile(configPath, data string) error {

0 commit comments

Comments
 (0)