Skip to content

Commit f49f89b

Browse files
authored
Merge pull request #3550 from vishalanarase/feat/3536-create-context-flag
feat(cli): add --create-context flag to create-workspace for direct c…
2 parents e23e1b8 + 87025ed commit f49f89b

File tree

3 files changed

+123
-4
lines changed

3 files changed

+123
-4
lines changed

cli/pkg/workspace/plugin/context.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ type CreateContextOptions struct {
3939
// Overwrite indicates the context should be updated if it already exists. This is required to perform the update.
4040
Overwrite bool
4141

42+
// KeepCurrent indicates whether to keep the current context. When creating a new context, if KeepCurrent is true, the current context will be preserved.
43+
KeepCurrent bool
44+
4245
startingConfig *clientcmdapi.Config
4346

4447
// for testing
@@ -68,10 +71,13 @@ func (o *CreateContextOptions) Complete(args []string) error {
6871
return err
6972
}
7073

71-
var err error
72-
o.startingConfig, err = o.ClientConfig.ConfigAccess().GetStartingConfig()
73-
if err != nil {
74-
return err
74+
// Get the starting config if it hasn't been set yet.
75+
if o.startingConfig == nil {
76+
var err error
77+
o.startingConfig, err = o.ClientConfig.ConfigAccess().GetStartingConfig()
78+
if err != nil {
79+
return err
80+
}
7581
}
7682

7783
if o.Name == "" && len(args) > 0 {
@@ -122,6 +128,11 @@ func (o *CreateContextOptions) Run(ctx context.Context) error {
122128
newKubeConfig.Contexts[o.Name] = &newContext
123129
newKubeConfig.CurrentContext = o.Name
124130

131+
// If KeepCurrent is true, preserve the current context.
132+
if o.KeepCurrent {
133+
newKubeConfig.CurrentContext = config.CurrentContext
134+
}
135+
125136
if err := o.modifyConfig(o.ClientConfig.ConfigAccess(), newKubeConfig); err != nil {
126137
return err
127138
}

cli/pkg/workspace/plugin/create.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ type CreateWorkspaceOptions struct {
6262

6363
kcpClusterClient kcpclientset.ClusterInterface
6464

65+
// CreateContextName is the name of the context to create after workspace creation (if set).
66+
CreateContextName string
67+
6568
// for testing - passed to UseWorkspaceOptions
6669
newKCPClusterClient func(config clientcmd.ClientConfig) (kcpclientset.ClusterInterface, error)
6770
modifyConfig func(configAccess clientcmd.ConfigAccess, newConfig *clientcmdapi.Config) error
@@ -111,6 +114,7 @@ func (o *CreateWorkspaceOptions) BindFlags(cmd *cobra.Command) {
111114
cmd.Flags().BoolVar(&o.EnterAfterCreate, "enter", o.EnterAfterCreate, "Immediately enter the created workspace")
112115
cmd.Flags().BoolVar(&o.IgnoreExisting, "ignore-existing", o.IgnoreExisting, "Ignore if the workspace already exists. Requires none or absolute type path.")
113116
cmd.Flags().StringVar(&o.LocationSelector, "location-selector", o.LocationSelector, "A label selector to select the scheduling location of the created workspace.")
117+
cmd.Flags().StringVar(&o.CreateContextName, "create-context", o.CreateContextName, "Create a kubeconfig context for the new workspace with the given name.")
114118
}
115119

116120
// Run creates a workspace.
@@ -231,6 +235,48 @@ func (o *CreateWorkspaceOptions) Run(ctx context.Context) error {
231235
return err
232236
}
233237

238+
// Create context if --create-context is set
239+
if o.CreateContextName != "" {
240+
createContextOptions := NewCreateContextOptions(o.IOStreams)
241+
createContextOptions.Name = o.CreateContextName
242+
createContextOptions.ClientConfig = o.ClientConfig
243+
244+
// If --enter is set, switch to the new context
245+
createContextOptions.KeepCurrent = !o.EnterAfterCreate
246+
247+
startingConfig, err := o.ClientConfig.RawConfig()
248+
if err != nil {
249+
return fmt.Errorf("failed to get raw config: %w", err)
250+
}
251+
createContextOptions.startingConfig = &startingConfig
252+
253+
// only for unit test needs
254+
if o.modifyConfig != nil {
255+
createContextOptions.modifyConfig = o.modifyConfig
256+
}
257+
258+
if err := createContextOptions.Complete(nil); err != nil {
259+
return err
260+
}
261+
if err := createContextOptions.Validate(); err != nil {
262+
return err
263+
}
264+
265+
if err := createContextOptions.Run(ctx); err != nil {
266+
return fmt.Errorf("failed to create context: %w", err)
267+
}
268+
269+
// Print output
270+
if o.EnterAfterCreate {
271+
fmt.Fprintf(o.Out, "Created context %q and switched to it.\n", o.CreateContextName)
272+
} else {
273+
fmt.Fprintf(o.Out, "Created context %q.\n", o.CreateContextName)
274+
}
275+
276+
return nil
277+
}
278+
279+
// If --enter is set, enter the newly created workspace
234280
if o.EnterAfterCreate {
235281
useOptions := NewUseWorkspaceOptions(o.IOStreams)
236282
useOptions.Name = ws.Name

cli/pkg/workspace/plugin/create_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func TestCreate(t *testing.T) {
5454
newWorkspaceName string
5555
newWorkspaceType *tenancyv1alpha1.WorkspaceTypeReference
5656
useAfterCreation, ignoreExisting bool
57+
createContextName string
5758

5859
expected *clientcmdapi.Config
5960
wantErr bool
@@ -189,6 +190,64 @@ func TestCreate(t *testing.T) {
189190
newWorkspaceType: &tenancyv1alpha1.WorkspaceTypeReference{Name: "universal"},
190191
wantErr: true,
191192
},
193+
{
194+
name: "create with create-context only",
195+
config: clientcmdapi.Config{
196+
CurrentContext: "test",
197+
Contexts: map[string]*clientcmdapi.Context{
198+
"test": {Cluster: "test", AuthInfo: "test"},
199+
},
200+
Clusters: map[string]*clientcmdapi.Cluster{
201+
"test": {Server: "https://test/clusters/root:foo"},
202+
},
203+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
204+
},
205+
expected: &clientcmdapi.Config{
206+
CurrentContext: "test",
207+
Contexts: map[string]*clientcmdapi.Context{
208+
"test": {Cluster: "test", AuthInfo: "test"},
209+
"bar": {Cluster: "bar", AuthInfo: "test"},
210+
},
211+
Clusters: map[string]*clientcmdapi.Cluster{
212+
"test": {Server: "https://test/clusters/root:foo"},
213+
"bar": {Server: "https://test/clusters/root:foo"},
214+
},
215+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
216+
},
217+
existingWorkspaces: []string{"test"},
218+
createContextName: "bar",
219+
useAfterCreation: false,
220+
markReady: true,
221+
},
222+
{
223+
name: "create with create-context and enter",
224+
config: clientcmdapi.Config{
225+
CurrentContext: "test",
226+
Contexts: map[string]*clientcmdapi.Context{
227+
"test": {Cluster: "test", AuthInfo: "test"},
228+
},
229+
Clusters: map[string]*clientcmdapi.Cluster{
230+
"test": {Server: "https://test/clusters/root:foo"},
231+
},
232+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
233+
},
234+
expected: &clientcmdapi.Config{
235+
CurrentContext: "bar",
236+
Contexts: map[string]*clientcmdapi.Context{
237+
"test": {Cluster: "test", AuthInfo: "test"},
238+
"bar": {Cluster: "bar", AuthInfo: "test"},
239+
},
240+
Clusters: map[string]*clientcmdapi.Cluster{
241+
"test": {Server: "https://test/clusters/root:foo"},
242+
"bar": {Server: "https://test/clusters/root:foo"},
243+
},
244+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
245+
},
246+
existingWorkspaces: []string{"test"},
247+
createContextName: "bar",
248+
useAfterCreation: true,
249+
markReady: true,
250+
},
192251
}
193252
for _, tt := range tests {
194253
// TODO(sttts): tt has a data race here due to the parallel test execution. But unaliasing it breaks the tests. WTF.
@@ -273,6 +332,9 @@ func TestCreate(t *testing.T) {
273332
if !tt.skipInitialType {
274333
opts.Type = workspaceType.Path + ":" + string(workspaceType.Name)
275334
}
335+
if tt.createContextName != "" {
336+
opts.CreateContextName = tt.createContextName
337+
}
276338
opts.IgnoreExisting = tt.ignoreExisting
277339
opts.EnterAfterCreate = tt.useAfterCreation
278340
opts.ReadyWaitTimeout = time.Second

0 commit comments

Comments
 (0)