Skip to content

Commit 78ba524

Browse files
committed
feat(add secrets view)
1 parent 5e92e6f commit 78ba524

11 files changed

Lines changed: 357 additions & 1 deletion

File tree

internal/dao/common/common.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ func Inspect(cli *client.Client, ctx context.Context, resourceType, id string) (
150150
data, _, err = cli.ServiceInspectWithRaw(ctx, id, swarm.ServiceInspectOptions{})
151151
case "node":
152152
data, _, err = cli.NodeInspectWithRaw(ctx, id)
153+
case "secret":
154+
data, _, err = cli.SecretInspectWithRaw(ctx, id)
153155
default:
154156
return "", fmt.Errorf("unknown resource type: %s", resourceType)
155157
}

internal/dao/docker.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/jr-k/d4s/internal/dao/docker/container"
2222
"github.com/jr-k/d4s/internal/dao/docker/image"
2323
"github.com/jr-k/d4s/internal/dao/docker/network"
24+
"github.com/jr-k/d4s/internal/dao/docker/secret"
2425
"github.com/jr-k/d4s/internal/dao/docker/volume"
2526
"github.com/jr-k/d4s/internal/dao/swarm/node"
2627
"github.com/jr-k/d4s/internal/dao/swarm/service"
@@ -35,6 +36,7 @@ type Volume = volume.Volume
3536
type Network = network.Network
3637
type Service = service.Service
3738
type Node = node.Node
39+
type Secret = secret.Secret
3840
type ComposeProject = compose.ComposeProject
3941

4042
type DockerClient struct {
@@ -49,6 +51,7 @@ type DockerClient struct {
4951
Network *network.Manager
5052
Service *service.Manager
5153
Node *node.Manager
54+
Secret *secret.Manager
5255
Compose *compose.Manager
5356
}
5457

@@ -77,6 +80,7 @@ func NewDockerClient(contextName string) (*DockerClient, error) {
7780
Network: network.NewManager(cli, ctx),
7881
Service: service.NewManager(cli, ctx),
7982
Node: node.NewManager(cli, ctx),
83+
Secret: secret.NewManager(cli, ctx),
8084
Compose: compose.NewManager(cli, ctx),
8185
}, nil
8286
}
@@ -229,6 +233,10 @@ func (d *DockerClient) ListCompose() ([]common.Resource, error) {
229233
return d.Compose.List()
230234
}
231235

236+
func (d *DockerClient) ListSecrets() ([]common.Resource, error) {
237+
return d.Secret.List()
238+
}
239+
232240
// Actions wrappers
233241
func (d *DockerClient) StopContainer(id string) error {
234242
return d.Container.Stop(id)
@@ -302,6 +310,10 @@ func (d *DockerClient) RemoveNode(id string, force bool) error {
302310
return d.Node.Remove(id, force)
303311
}
304312

313+
func (d *DockerClient) RemoveSecret(id string) error {
314+
return d.Secret.Remove(id)
315+
}
316+
305317
func (d *DockerClient) StopComposeProject(projectName string) error {
306318
return d.Compose.Stop(projectName)
307319
}
@@ -347,6 +359,14 @@ func (d *DockerClient) GetServiceLogs(id string, since string, tail string, time
347359
return d.Service.Logs(id, since, tail, timestamps)
348360
}
349361

362+
func (d *DockerClient) GetServiceEnv(id string) ([]string, error) {
363+
return d.Service.GetEnv(id)
364+
}
365+
366+
func (d *DockerClient) GetServiceSecrets(id string) ([]*swarm.SecretReference, error) {
367+
return d.Service.GetSecrets(id)
368+
}
369+
350370
func (d *DockerClient) GetComposeLogs(projectName string, since string, tail string, timestamps bool) (io.ReadCloser, error) {
351371
return d.Compose.Logs(projectName, since, tail, timestamps)
352372
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package secret
2+
3+
import (
4+
"context"
5+
"strings"
6+
7+
"github.com/docker/docker/api/types"
8+
"github.com/docker/docker/client"
9+
"github.com/gdamore/tcell/v2"
10+
"github.com/jr-k/d4s/internal/dao/common"
11+
"github.com/jr-k/d4s/internal/ui/styles"
12+
)
13+
14+
type Manager struct {
15+
cli *client.Client
16+
ctx context.Context
17+
}
18+
19+
func NewManager(cli *client.Client, ctx context.Context) *Manager {
20+
return &Manager{cli: cli, ctx: ctx}
21+
}
22+
23+
// Secret Model
24+
type Secret struct {
25+
ID string
26+
Name string
27+
Created string
28+
Updated string
29+
Labels string
30+
}
31+
32+
func (s Secret) GetID() string { return s.ID }
33+
func (s Secret) GetCells() []string {
34+
id := s.ID
35+
if len(id) > 12 {
36+
id = id[:12]
37+
}
38+
return []string{id, s.Name, s.Created, s.Updated, s.Labels}
39+
}
40+
41+
func (s Secret) GetStatusColor() (tcell.Color, tcell.Color) {
42+
return styles.ColorIdle, styles.ColorBlack
43+
}
44+
45+
func (s Secret) GetColumnValue(column string) string {
46+
switch strings.ToLower(column) {
47+
case "id":
48+
return s.ID
49+
case "name":
50+
return s.Name
51+
case "created":
52+
return s.Created
53+
case "updated":
54+
return s.Updated
55+
case "labels":
56+
return s.Labels
57+
}
58+
return ""
59+
}
60+
61+
func (s Secret) GetDefaultColumn() string {
62+
return "Name"
63+
}
64+
65+
func (s Secret) GetDefaultSortColumn() string {
66+
return "Name"
67+
}
68+
69+
func (m *Manager) List() ([]common.Resource, error) {
70+
list, err := m.cli.SecretList(m.ctx, types.SecretListOptions{})
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
var res []common.Resource
76+
for _, s := range list {
77+
labels := formatLabels(s.Spec.Labels)
78+
79+
res = append(res, Secret{
80+
ID: s.ID,
81+
Name: s.Spec.Name,
82+
Created: common.FormatTime(s.CreatedAt.Unix()),
83+
Updated: common.FormatTime(s.UpdatedAt.Unix()),
84+
Labels: labels,
85+
})
86+
}
87+
return res, nil
88+
}
89+
90+
func (m *Manager) Remove(id string) error {
91+
return m.cli.SecretRemove(m.ctx, id)
92+
}
93+
94+
func formatLabels(labels map[string]string) string {
95+
if len(labels) == 0 {
96+
return "-"
97+
}
98+
var parts []string
99+
for k, v := range labels {
100+
parts = append(parts, k+"="+v)
101+
}
102+
return strings.Join(parts, ", ")
103+
}

internal/dao/swarm/service/service.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,29 @@ func (m *Manager) Logs(id string, since string, tail string, timestamps bool) (i
213213
}
214214
return m.cli.ServiceLogs(m.ctx, id, opts)
215215
}
216+
217+
func (m *Manager) GetEnv(id string) ([]string, error) {
218+
service, _, err := m.cli.ServiceInspectWithRaw(m.ctx, id, swarm.ServiceInspectOptions{})
219+
if err != nil {
220+
return nil, err
221+
}
222+
223+
if service.Spec.TaskTemplate.ContainerSpec == nil {
224+
return []string{}, nil
225+
}
226+
227+
return service.Spec.TaskTemplate.ContainerSpec.Env, nil
228+
}
229+
230+
func (m *Manager) GetSecrets(id string) ([]*swarm.SecretReference, error) {
231+
service, _, err := m.cli.ServiceInspectWithRaw(m.ctx, id, swarm.ServiceInspectOptions{})
232+
if err != nil {
233+
return nil, err
234+
}
235+
236+
if service.Spec.TaskTemplate.ContainerSpec == nil {
237+
return []*swarm.SecretReference{}, nil
238+
}
239+
240+
return service.Spec.TaskTemplate.ContainerSpec.Secrets, nil
241+
}

internal/ui/app.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/jr-k/d4s/internal/ui/views/images"
2323
"github.com/jr-k/d4s/internal/ui/views/networks"
2424
"github.com/jr-k/d4s/internal/ui/views/nodes"
25+
"github.com/jr-k/d4s/internal/ui/views/secrets"
2526
"github.com/jr-k/d4s/internal/ui/views/services"
2627
"github.com/jr-k/d4s/internal/ui/views/volumes"
2728
"github.com/jr-k/d4s/internal/updater"
@@ -298,6 +299,18 @@ func (a *App) initUI() {
298299
}
299300
a.Views[styles.TitleAliases] = vAliases
300301

302+
// Secrets
303+
vSecrets := view.NewResourceView(a, styles.TitleSecrets)
304+
vSecrets.ShortcutsFunc = secrets.GetShortcuts
305+
vSecrets.FetchFunc = secrets.Fetch
306+
vSecrets.InspectFunc = secrets.Inspect
307+
vSecrets.RemoveFunc = secrets.Remove
308+
vSecrets.Headers = secrets.Headers
309+
vSecrets.InputHandler = func(event *tcell.EventKey) *tcell.EventKey {
310+
return secrets.InputHandler(vSecrets, event)
311+
}
312+
a.Views[styles.TitleSecrets] = vSecrets
313+
301314
for title, view := range a.Views {
302315
a.Pages.AddPage(title, view.Table, true, false)
303316
}

internal/ui/app_navigation.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func (a *App) ExecuteCmd(cmd string) {
3939
switchToRoot(styles.TitleCompose)
4040
case "a", "al", "alias", "aliases":
4141
switchToRoot(styles.TitleAliases)
42+
case "x", "sec", "secret", "secrets":
43+
switchToRoot(styles.TitleSecrets)
4244
case "h", "help", "?":
4345
a.Pages.AddPage("help", a.Help, true, true)
4446
default:

internal/ui/dialogs/help.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func NewHelpView(app common.AppController) tview.Primitive {
2727
{"", ""},
2828
{"[orange::b]SWARM", ""},
2929
{"[#5f87ff]:s[-] Services", "[#5f87ff]:no[-] Nodes"},
30+
{"[#5f87ff]:x[-] Secrets", ""},
3031
{"", ""},
3132
{"[orange::b]NAVIGATION", ""},
3233
{"[#5f87ff]←/→[-], [#5f87ff]j/k[-] Navigate", "[#5f87ff]enter[-] Drill Down"},

internal/ui/styles/styles.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ const (
7272
TitleNodes = "Nodes"
7373
TitleCompose = "Compose"
7474
TitleAliases = "Aliases"
75+
TitleSecrets = "Secrets"
7576
)

internal/ui/views/aliases/aliases.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func Fetch(app common.AppController, v *view.ResourceView) ([]dao.Resource, erro
5959
{Title: styles.TitleNetworks, Resource: "networks", Group: "docker"},
6060
{Title: styles.TitleServices, Resource: "services", Group: "swarm"},
6161
{Title: styles.TitleNodes, Resource: "nodes", Group: "swarm"},
62+
{Title: styles.TitleSecrets, Resource: "secrets", Group: "swarm"},
6263
{Title: styles.TitleCompose, Resource: "compose", Group: "compose"},
6364
}
6465

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package secrets
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/gdamore/tcell/v2"
7+
"github.com/jr-k/d4s/internal/dao"
8+
"github.com/jr-k/d4s/internal/ui/common"
9+
"github.com/jr-k/d4s/internal/ui/components/inspect"
10+
"github.com/jr-k/d4s/internal/ui/components/view"
11+
"github.com/jr-k/d4s/internal/ui/dialogs"
12+
"github.com/jr-k/d4s/internal/ui/styles"
13+
)
14+
15+
var Headers = []string{"ID", "NAME", "CREATED", "UPDATED", "LABELS"}
16+
17+
func Fetch(app common.AppController, v *view.ResourceView) ([]dao.Resource, error) {
18+
return app.GetDocker().ListSecrets()
19+
}
20+
21+
func GetShortcuts() []string {
22+
return []string{
23+
common.FormatSCHeader("d", "Describe"),
24+
common.FormatSCHeader("ctrl-d", "Delete"),
25+
}
26+
}
27+
28+
func InputHandler(v *view.ResourceView, event *tcell.EventKey) *tcell.EventKey {
29+
app := v.App
30+
31+
if event.Key() == tcell.KeyCtrlD {
32+
DeleteAction(app, v)
33+
return nil
34+
}
35+
36+
switch event.Rune() {
37+
case 'd':
38+
app.InspectCurrentSelection()
39+
return nil
40+
}
41+
42+
return event
43+
}
44+
45+
func DeleteAction(app common.AppController, v *view.ResourceView) {
46+
ids, err := v.GetSelectedIDs()
47+
if err != nil {
48+
return
49+
}
50+
51+
label := ids[0]
52+
if len(ids) > 1 {
53+
label = fmt.Sprintf("%d items", len(ids))
54+
}
55+
56+
dialogs.ShowConfirmation(app, "DELETE", label, func(force bool) {
57+
simpleAction := func(id string) error {
58+
return Remove(id, force, app)
59+
}
60+
app.PerformAction(simpleAction, "deleting", styles.ColorStatusRed)
61+
})
62+
}
63+
64+
func Remove(id string, force bool, app common.AppController) error {
65+
return app.GetDocker().RemoveSecret(id)
66+
}
67+
68+
func Inspect(app common.AppController, id string) {
69+
content, err := app.GetDocker().Inspect("secret", id)
70+
if err != nil {
71+
app.SetFlashError(fmt.Sprintf("%v", err))
72+
return
73+
}
74+
75+
subject := id
76+
if len(id) > 12 {
77+
subject = id[:12]
78+
}
79+
80+
// Resolve Name
81+
secrets, err := app.GetDocker().ListSecrets()
82+
if err == nil {
83+
for _, item := range secrets {
84+
if item.GetID() == id {
85+
if s, ok := item.(dao.Secret); ok {
86+
subject = fmt.Sprintf("%s@%s", s.Name, subject)
87+
}
88+
break
89+
}
90+
}
91+
}
92+
93+
app.OpenInspector(inspect.NewTextInspector("Describe secret", subject, content, "json"))
94+
}

0 commit comments

Comments
 (0)