Skip to content

Commit 675070f

Browse files
committed
feat(attach network to service or container)
1 parent 7e3d8aa commit 675070f

5 files changed

Lines changed: 223 additions & 0 deletions

File tree

internal/dao/docker.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,14 @@ func (d *DockerClient) RemoveNetwork(id string) error {
290290
return d.Network.Remove(id)
291291
}
292292

293+
func (d *DockerClient) ConnectNetwork(networkID, containerID string) error {
294+
return d.Network.Connect(networkID, containerID)
295+
}
296+
297+
func (d *DockerClient) DisconnectNetwork(networkID, containerID string) error {
298+
return d.Network.Disconnect(networkID, containerID)
299+
}
300+
293301
func (d *DockerClient) PruneNetworks() error {
294302
return d.Network.Prune()
295303
}
@@ -375,6 +383,14 @@ func (d *DockerClient) SetServiceSecrets(id string, secretRefs []*swarm.SecretRe
375383
return d.Service.SetSecrets(id, secretRefs)
376384
}
377385

386+
func (d *DockerClient) GetServiceNetworks(id string) ([]swarm.NetworkAttachmentConfig, error) {
387+
return d.Service.GetNetworks(id)
388+
}
389+
390+
func (d *DockerClient) SetServiceNetworks(id string, networks []swarm.NetworkAttachmentConfig) error {
391+
return d.Service.SetNetworks(id, networks)
392+
}
393+
378394

379395
func (d *DockerClient) ListServicesForSecret(secretID string) ([]common.Resource, error) {
380396
services, err := d.Service.List()

internal/dao/docker/network/network.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,11 @@ func (m *Manager) Prune() error {
148148
_, err := m.cli.NetworksPrune(m.ctx, filters.NewArgs())
149149
return err
150150
}
151+
152+
func (m *Manager) Connect(networkID, containerID string) error {
153+
return m.cli.NetworkConnect(m.ctx, networkID, containerID, nil)
154+
}
155+
156+
func (m *Manager) Disconnect(networkID, containerID string) error {
157+
return m.cli.NetworkDisconnect(m.ctx, networkID, containerID, true)
158+
}

internal/dao/swarm/service/service.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,24 @@ func (m *Manager) SetSecrets(id string, secretRefs []*swarm.SecretReference) err
255255
_, err = m.cli.ServiceUpdate(m.ctx, id, service.Version, service.Spec, swarm.ServiceUpdateOptions{})
256256
return err
257257
}
258+
259+
func (m *Manager) GetNetworks(id string) ([]swarm.NetworkAttachmentConfig, error) {
260+
service, _, err := m.cli.ServiceInspectWithRaw(m.ctx, id, swarm.ServiceInspectOptions{})
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
return service.Spec.TaskTemplate.Networks, nil
266+
}
267+
268+
func (m *Manager) SetNetworks(id string, networks []swarm.NetworkAttachmentConfig) error {
269+
service, _, err := m.cli.ServiceInspectWithRaw(m.ctx, id, swarm.ServiceInspectOptions{})
270+
if err != nil {
271+
return err
272+
}
273+
274+
service.Spec.TaskTemplate.Networks = networks
275+
276+
_, err = m.cli.ServiceUpdate(m.ctx, id, service.Version, service.Spec, swarm.ServiceUpdateOptions{})
277+
return err
278+
}

internal/ui/views/containers/containers.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func GetShortcuts() []string {
105105
common.FormatSCHeader("m", "Monitor"),
106106
common.FormatSCHeader("v", "Volumes"),
107107
common.FormatSCHeader("n", "Networks"),
108+
common.FormatSCHeader("shift-n", "Attach Network"),
108109
common.FormatSCHeader("r", "(Re)Start"),
109110
common.FormatSCHeader("p", "Prune"),
110111
common.FormatSCHeader("ctrl-k", "Stop"),
@@ -138,6 +139,9 @@ func InputHandler(v *view.ResourceView, event *tcell.EventKey) *tcell.EventKey {
138139
case 'n':
139140
Networks(app, v)
140141
return nil
142+
case 'N':
143+
NetworksPicker(app, v)
144+
return nil
141145
case 'l':
142146
Logs(app, v)
143147
return nil
@@ -448,3 +452,101 @@ func resolveContainerSubject(v *view.ResourceView, id string) string {
448452
}
449453
return subject
450454
}
455+
456+
func NetworksPicker(app common.AppController, v *view.ResourceView) {
457+
id, err := v.GetSelectedID()
458+
if err != nil {
459+
return
460+
}
461+
462+
// Get all networks
463+
allNetworks, err := app.GetDocker().ListNetworks()
464+
if err != nil {
465+
app.SetFlashError(fmt.Sprintf("%v", err))
466+
return
467+
}
468+
469+
// Get current container networks
470+
currentNetworks, err := app.GetDocker().ListNetworksForContainer(id)
471+
if err != nil {
472+
app.SetFlashError(fmt.Sprintf("%v", err))
473+
return
474+
}
475+
476+
attachedIDs := make(map[string]bool)
477+
for _, res := range currentNetworks {
478+
if n, ok := res.(dao.Network); ok {
479+
attachedIDs[n.ID] = true
480+
}
481+
}
482+
483+
var items []dialogs.MultiPickerItem
484+
for _, res := range allNetworks {
485+
if n, ok := res.(dao.Network); ok {
486+
if n.Scope == "swarm" {
487+
continue
488+
}
489+
items = append(items, dialogs.MultiPickerItem{
490+
ID: n.ID,
491+
Label: n.Name,
492+
Selected: attachedIDs[n.ID],
493+
})
494+
}
495+
}
496+
497+
subject := resolveContainerSubject(v, id)
498+
499+
dialogs.ShowMultiPicker(app, fmt.Sprintf("Networks for %s", subject), items, func(selected []string) {
500+
selectedMap := make(map[string]bool)
501+
for _, sid := range selected {
502+
selectedMap[sid] = true
503+
}
504+
505+
var toConnect []string
506+
var toDisconnect []string
507+
508+
// Check for new connections
509+
for _, sid := range selected {
510+
if !attachedIDs[sid] {
511+
toConnect = append(toConnect, sid)
512+
}
513+
}
514+
515+
// Check for disconnections
516+
for attachedID := range attachedIDs {
517+
if !selectedMap[attachedID] {
518+
toDisconnect = append(toDisconnect, attachedID)
519+
}
520+
}
521+
522+
if len(toConnect) == 0 && len(toDisconnect) == 0 {
523+
return
524+
}
525+
526+
app.SetFlashPending("updating networks...")
527+
app.RunInBackground(func() {
528+
var errs []string
529+
530+
for _, netID := range toConnect {
531+
if err := app.GetDocker().ConnectNetwork(netID, id); err != nil {
532+
errs = append(errs, fmt.Sprintf("connect %s: %v", netID, err))
533+
}
534+
}
535+
536+
for _, netID := range toDisconnect {
537+
if err := app.GetDocker().DisconnectNetwork(netID, id); err != nil {
538+
errs = append(errs, fmt.Sprintf("disconnect %s: %v", netID, err))
539+
}
540+
}
541+
542+
app.GetTviewApp().QueueUpdateDraw(func() {
543+
if len(errs) > 0 {
544+
app.SetFlashError(strings.Join(errs, "; "))
545+
} else {
546+
app.SetFlashSuccess("networks updated")
547+
app.RefreshCurrentView()
548+
}
549+
})
550+
})
551+
})
552+
}

internal/ui/views/services/services.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ func GetShortcuts() []string {
150150
common.FormatSCHeader("s", "Scale"),
151151
common.FormatSCHeader("z", "No Replica"),
152152
common.FormatSCHeader("shift-x", "Attach Secrets"),
153+
common.FormatSCHeader("shift-n", "Attach Networks"),
153154
common.FormatSCHeader("shift-i", "Edit Image"),
154155
common.FormatSCHeader("ctrl-d", "Delete"),
155156
}
@@ -178,6 +179,9 @@ func InputHandler(v *view.ResourceView, event *tcell.EventKey) *tcell.EventKey {
178179
case 'X':
179180
SecretsPicker(app, v)
180181
return nil
182+
case 'N':
183+
NetworksPicker(app, v)
184+
return nil
181185
case 'I':
182186
EditAction(app, v)
183187
return nil
@@ -418,6 +422,78 @@ func SecretsPicker(app common.AppController, v *view.ResourceView) {
418422
})
419423
}
420424

425+
func NetworksPicker(app common.AppController, v *view.ResourceView) {
426+
id, err := v.GetSelectedID()
427+
if err != nil {
428+
return
429+
}
430+
431+
// Get all networks
432+
allNetworks, err := app.GetDocker().ListNetworks()
433+
if err != nil {
434+
app.SetFlashError(fmt.Sprintf("%v", err))
435+
return
436+
}
437+
438+
if len(allNetworks) == 0 {
439+
app.SetFlashError("no networks available")
440+
return
441+
}
442+
443+
// Get current service networks
444+
currentNetworks, err := app.GetDocker().GetServiceNetworks(id)
445+
if err != nil {
446+
app.SetFlashError(fmt.Sprintf("%v", err))
447+
return
448+
}
449+
450+
// Build map of attached network IDs
451+
attachedIDs := make(map[string]bool)
452+
for _, n := range currentNetworks {
453+
attachedIDs[n.Target] = true
454+
}
455+
456+
// Build picker items
457+
var items []dialogs.MultiPickerItem
458+
for _, res := range allNetworks {
459+
if n, ok := res.(dao.Network); ok {
460+
if n.Scope != "swarm" {
461+
continue
462+
}
463+
items = append(items, dialogs.MultiPickerItem{
464+
ID: n.ID,
465+
Label: n.Name,
466+
Selected: attachedIDs[n.ID],
467+
})
468+
}
469+
}
470+
471+
subject := resolveServiceSubject(v, id)
472+
473+
dialogs.ShowMultiPicker(app, fmt.Sprintf("Networks for %s", subject), items, func(selected []string) {
474+
// Build new network configs
475+
var newNetworks []swarm.NetworkAttachmentConfig
476+
for _, netID := range selected {
477+
newNetworks = append(newNetworks, swarm.NetworkAttachmentConfig{
478+
Target: netID,
479+
})
480+
}
481+
482+
app.SetFlashPending("updating service networks...")
483+
app.RunInBackground(func() {
484+
err := app.GetDocker().SetServiceNetworks(id, newNetworks)
485+
app.GetTviewApp().QueueUpdateDraw(func() {
486+
if err != nil {
487+
app.SetFlashError(fmt.Sprintf("%v", err))
488+
} else {
489+
app.SetFlashSuccess("service networks updated")
490+
app.RefreshCurrentView()
491+
}
492+
})
493+
})
494+
})
495+
}
496+
421497
func EditAction(app common.AppController, v *view.ResourceView) {
422498
id, err := v.GetSelectedID()
423499
if err != nil {

0 commit comments

Comments
 (0)