Skip to content

Commit 4dc0e85

Browse files
committed
feat(cli): add --create-context flag to create-workspace for direct context creation
- Adds --create-context=<name> to create-workspace command - Allows creating a kubeconfig context for the new workspace without switching - Supports --create-context with --enter to create and switch context in one step - Updates tests for both scenarios Signed-off-by: Vishal Anarase <[email protected]>
1 parent e734ff2 commit 4dc0e85

File tree

3 files changed

+119
-4
lines changed

3 files changed

+119
-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: 54 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,56 @@ 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+
createContextOptions.KeepCurrent = false
244+
245+
// If --enter is not set, do not switch to the new context
246+
if !o.EnterAfterCreate {
247+
createContextOptions.KeepCurrent = true
248+
}
249+
250+
startingConfig, err := o.ClientConfig.RawConfig()
251+
if err != nil {
252+
return fmt.Errorf("failed to get raw config: %w", err)
253+
}
254+
createContextOptions.startingConfig = &startingConfig
255+
256+
// if err := o.modifyConfig(o.ClientConfig.ConfigAccess(), &startingConfig); err != nil {
257+
// return err
258+
// }
259+
260+
// only for unit test needs
261+
if o.modifyConfig != nil {
262+
createContextOptions.modifyConfig = o.modifyConfig
263+
}
264+
265+
if err := createContextOptions.Complete(nil); err != nil {
266+
return err
267+
}
268+
if err := createContextOptions.Validate(); err != nil {
269+
return err
270+
}
271+
272+
err = createContextOptions.Run(ctx)
273+
if err != nil {
274+
return fmt.Errorf("failed to create context: %w", err)
275+
}
276+
277+
// Print output
278+
if o.EnterAfterCreate {
279+
fmt.Fprintf(o.Out, "Created context %q and switched to it.\n", o.CreateContextName)
280+
} else {
281+
fmt.Fprintf(o.Out, "Created context %q.\n", o.CreateContextName)
282+
}
283+
284+
return nil
285+
}
286+
287+
// If --enter is set, enter the newly created workspace
234288
if o.EnterAfterCreate {
235289
useOptions := NewUseWorkspaceOptions(o.IOStreams)
236290
useOptions.Name = ws.Name

cli/pkg/workspace/plugin/create_test.go

Lines changed: 50 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,52 @@ 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{CurrentContext: "test",
196+
Contexts: map[string]*clientcmdapi.Context{"test": {Cluster: "test", AuthInfo: "test"}},
197+
Clusters: map[string]*clientcmdapi.Cluster{"test": {Server: "https://test/clusters/root:foo"}},
198+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
199+
},
200+
existingWorkspaces: []string{"test"},
201+
createContextName: "bar",
202+
useAfterCreation: false,
203+
markReady: true,
204+
expected: &clientcmdapi.Config{CurrentContext: "test",
205+
Contexts: map[string]*clientcmdapi.Context{
206+
"test": {Cluster: "test", AuthInfo: "test"},
207+
"bar": {Cluster: "bar", AuthInfo: "test"},
208+
},
209+
Clusters: map[string]*clientcmdapi.Cluster{
210+
"test": {Server: "https://test/clusters/root:foo"},
211+
"bar": {Server: "https://test/clusters/root:foo"},
212+
},
213+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
214+
},
215+
},
216+
{
217+
name: "create with create-context and enter",
218+
config: clientcmdapi.Config{CurrentContext: "test",
219+
Contexts: map[string]*clientcmdapi.Context{"test": {Cluster: "test", AuthInfo: "test"}},
220+
Clusters: map[string]*clientcmdapi.Cluster{"test": {Server: "https://test/clusters/root:foo"}},
221+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
222+
},
223+
existingWorkspaces: []string{"test"},
224+
createContextName: "bar",
225+
useAfterCreation: true,
226+
markReady: true,
227+
expected: &clientcmdapi.Config{CurrentContext: "bar",
228+
Contexts: map[string]*clientcmdapi.Context{
229+
"test": {Cluster: "test", AuthInfo: "test"},
230+
"bar": {Cluster: "bar", AuthInfo: "test"},
231+
},
232+
Clusters: map[string]*clientcmdapi.Cluster{
233+
"test": {Server: "https://test/clusters/root:foo"},
234+
"bar": {Server: "https://test/clusters/root:foo"},
235+
},
236+
AuthInfos: map[string]*clientcmdapi.AuthInfo{"test": {Token: "test"}},
237+
},
238+
},
192239
}
193240
for _, tt := range tests {
194241
// TODO(sttts): tt has a data race here due to the parallel test execution. But unaliasing it breaks the tests. WTF.
@@ -273,6 +320,9 @@ func TestCreate(t *testing.T) {
273320
if !tt.skipInitialType {
274321
opts.Type = workspaceType.Path + ":" + string(workspaceType.Name)
275322
}
323+
if tt.createContextName != "" {
324+
opts.CreateContextName = tt.createContextName
325+
}
276326
opts.IgnoreExisting = tt.ignoreExisting
277327
opts.EnterAfterCreate = tt.useAfterCreation
278328
opts.ReadyWaitTimeout = time.Second

0 commit comments

Comments
 (0)