Skip to content

Commit 8ea1cc4

Browse files
authored
Merge pull request #77 from shvbsle/editrs
feat: key binding to edit kubernetes resoruce
2 parents 3ab567f + 1916f69 commit 8ea1cc4

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

internal/tui/commands.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,66 @@ func (m *Model) getResourceYaml() tea.Cmd {
559559
}
560560
}
561561

562+
// editCurrentResource opens the selected resource in an external editor using kubectl edit.
563+
func (m *Model) editCurrentResource() tea.Cmd {
564+
if len(m.resources) == 0 {
565+
return func() tea.Msg {
566+
return commandErrMsg{message: "no resource selected"}
567+
}
568+
}
569+
570+
actualIdx := m.paginator.Page*m.paginator.PerPage + m.table.Cursor()
571+
if actualIdx >= len(m.resources) {
572+
return func() tea.Msg {
573+
return commandErrMsg{message: "invalid selection"}
574+
}
575+
}
576+
577+
selectedResource := m.resources[actualIdx]
578+
579+
// Extract name and namespace from the selected row
580+
var selectedName, selectedNamespace string
581+
if nameIndex, ok := k8s.NameColumn(m.table.Columns()); ok {
582+
selectedName = selectedResource[nameIndex]
583+
}
584+
if namespaceIndex, ok := k8s.NamespaceColumn(m.table.Columns()); ok {
585+
selectedNamespace = selectedResource[namespaceIndex]
586+
}
587+
588+
// Use the current namespace if no namespace column exists (cluster-scoped resources)
589+
if selectedNamespace == "" {
590+
selectedNamespace = m.currentNamespace
591+
}
592+
593+
log.G().Info("editing resource", "gvr", m.currentGVR, "name", selectedName, "namespace", selectedNamespace)
594+
595+
// Build kubectl edit command
596+
resourceType := m.currentGVR.Resource
597+
var args []string
598+
if selectedNamespace != "" && selectedNamespace != metav1.NamespaceAll {
599+
args = []string{"edit", resourceType, selectedName, "-n", selectedNamespace}
600+
} else {
601+
args = []string{"edit", resourceType, selectedName}
602+
}
603+
604+
// Create the command that will run in the terminal
605+
cmd := exec.Command("kubectl", args...)
606+
607+
// Use tea.ExecProcess to suspend the TUI, run kubectl edit, and resume when done
608+
return tea.ExecProcess(cmd, func(err error) tea.Msg {
609+
if err != nil {
610+
log.G().Error("failed to edit resource", "error", err)
611+
return commandErrMsg{message: fmt.Sprintf("failed to edit resource: %v", err)}
612+
}
613+
// After editing, return message to trigger resource reload
614+
log.G().Info("resource edit completed")
615+
return resourceEditedMsg{
616+
resourceName: selectedName,
617+
resourceType: resourceType,
618+
}
619+
})
620+
}
621+
562622
// executeCtxCommand handles the ctx command for listing and switching Kubernetes contexts.
563623
func (m *Model) executeCtxCommand(args []string) tea.Cmd {
564624
// If no arguments, list all available contexts

internal/tui/keys.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type keyMap struct {
2626
// Resource actions
2727
Describe key.Binding
2828
YamlView key.Binding
29+
Edit key.Binding
2930
}
3031

3132
// newKeyMap creates a new keyMap with all bindings configured
@@ -111,5 +112,9 @@ func newKeyMap() keyMap {
111112
key.WithKeys("y"),
112113
key.WithHelp("y", "yaml"),
113114
),
115+
Edit: key.NewBinding(
116+
key.WithKeys("e"),
117+
key.WithHelp("e", "edit"),
118+
),
114119
}
115120
}

internal/tui/model.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ type resourceYamlMsg struct {
133133
gvr schema.GroupVersionResource
134134
}
135135

136+
type resourceEditedMsg struct {
137+
resourceName string
138+
resourceType string
139+
}
140+
136141
type contextsLoadedMsg struct {
137142
contexts []k8s.OrderedResourceFields
138143
}
@@ -529,6 +534,17 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
529534
m.commandSuccess = ""
530535
return m, nil
531536

537+
case resourceEditedMsg:
538+
// Show success message
539+
m.commandSuccess = fmt.Sprintf("Successfully edited %s/%s", msg.resourceType, msg.resourceName)
540+
// Reload the current resources to reflect any changes
541+
return m, tea.Batch(
542+
m.loadResourcesWithNamespace(m.currentGVR, m.currentNamespace, metav1.ListOptions{}),
543+
tea.Tick(5*time.Second, func(t time.Time) tea.Msg {
544+
return clearCommandSuccessMsg{}
545+
}),
546+
)
547+
532548
case launchPluginMsg:
533549
m.pluginToLaunch = msg.plugin
534550
return m, tea.Quit
@@ -847,6 +863,21 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
847863
m.getResourceYaml(),
848864
m.requireConnection,
849865
)
866+
case "e":
867+
// Edit the currently selected resource
868+
if m.currentGVR.Resource == k8s.ResourceLogs ||
869+
m.currentGVR.Resource == k8s.ResourceDescribe ||
870+
m.currentGVR.Resource == k8s.ResourceYaml ||
871+
m.currentGVR.Resource == k8s.ResourceContainers ||
872+
m.currentGVR.Resource == k8s.ResourceAPIResources {
873+
return m, nil // Can't edit these resource types
874+
}
875+
if !m.isConnected() {
876+
m.err = fmt.Errorf("not connected to cluster. Use :reconnect")
877+
return m, nil
878+
}
879+
// Edit command directly returns tea.ExecProcess, no need for commandWithPreflights
880+
return m, m.editCurrentResource()
850881
case "d":
851882
// Describe the currently selected resource
852883
if m.currentGVR.Resource == k8s.ResourceLogs ||

roadmap.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
- [ ] **Search for pods/resources** ([#60](https://github.com/shvbsle/k10s/issues/60)) - Triggered by `/` keybinding. Opens a search box with live filtering - as the user types, the current view updates in real-time to show matching results
66
- [x] **Switch between cluster contexts** ([#61](https://github.com/shvbsle/k10s/issues/61)) - Triggered by `:ctx` in command mode. Opens a context selector to switch between different kubeconfig contexts without leaving the TUI
7-
- [ ] **Edit manifests** ([#62](https://github.com/shvbsle/k10s/issues/62)) - Triggered by `e` keybinding when cursor is on any resource. Opens the resource manifest in the user's default editor (e.g., vim). Saving and closing the editor automatically applies the changes to the cluster
7+
- [x] **Edit manifests** ([#62](https://github.com/shvbsle/k10s/issues/62)) - Triggered by `e` keybinding when cursor is on any resource. Opens the resource manifest in the user's default editor (e.g., vim). Saving and closing the editor automatically applies the changes to the cluster
88
- [x] **Describe output for resources** ([#63](https://github.com/shvbsle/k10s/issues/63)) - Triggered by `d` keybinding when cursor is on any resource. Renders `kubectl describe` output for the selected resource
99
- [x] **YAML view for resources** - Triggered by `y` keybinding when cursor is on any resource. Renders the resource YAML manifest with syntax highlighting (bold keys), scrollable with arrow keys, line number toggle (`n`), and line wrapping toggle (`w`)
1010
- [ ] **Syntax highlighting for describe output** ([#64](https://github.com/shvbsle/k10s/issues/64)) - Add syntax highlighting to the describe output view for better readability

0 commit comments

Comments
 (0)