-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshared.go
More file actions
190 lines (177 loc) · 5.37 KB
/
Copy pathshared.go
File metadata and controls
190 lines (177 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package cli
import (
"fmt"
"os"
"path/filepath"
"slices"
"github.com/spf13/cobra"
"github.com/open-source-cloud/devstack/internal/config"
"github.com/open-source-cloud/devstack/internal/docker"
"github.com/open-source-cloud/devstack/internal/state"
"github.com/open-source-cloud/devstack/internal/workspace"
"github.com/open-source-cloud/devstack/internal/xdg"
)
// newSharedCmd wires `shared status|gc|doctor`. `status` is the M2 read-only
// projection of the ledger (shared services + ref counts + consuming projects);
// `gc`/`doctor` (which stop/reconcile against the daemon) land with the up saga.
func newSharedCmd(g *GlobalOpts) *cobra.Command {
cmd := &cobra.Command{
Use: "shared",
Short: "Inspect and reclaim shared services",
}
cmd.AddCommand(
newSharedStatusCmd(g),
newSharedGcCmd(g),
newSharedDoctorCmd(g),
// `shared expose`/`shared ports` stay as aliases of the top-level `expose`/
// `ports` (fresh instances; same logic) for backward compatibility.
newExposeCmd(g),
newPortsCmd(g),
)
return cmd
}
// newSharedGcCmd wires `shared gc [--stop]` — find shared services at zero refs
// and (with --stop) stop them. Default is a dry-run report: warm DBs are cheap,
// so reclamation is opt-in (spec 03/09). Volumes are never touched.
func newSharedGcCmd(g *GlobalOpts) *cobra.Command {
var stop bool
cmd := &cobra.Command{
Use: "gc",
Short: "Report (or with --stop, stop) shared services at zero references",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
mgr, closeFn, err := buildManager(cmd)
if err != nil {
return err
}
defer closeFn()
res, err := mgr.GC(cmd.Context(), stop)
if err != nil {
return err
}
if g.JSON {
return writeJSON(cmd, res)
}
w := cmd.OutOrStdout()
if len(res.Candidates) == 0 {
fmt.Fprintln(w, "no shared services at zero references")
return nil
}
for _, c := range res.Candidates {
if slices.Contains(res.Stopped, c) {
fmt.Fprintf(w, "stopped %s\n", c)
} else if stop {
fmt.Fprintf(w, "%s (zero refs, left running)\n", c)
} else {
fmt.Fprintf(w, "%s (zero refs — run `shared gc --stop` to stop)\n", c)
}
}
return nil
},
}
cmd.Flags().BoolVar(&stop, "stop", false, "actually stop the zero-ref services (default: report only)")
return cmd
}
// newSharedDoctorCmd wires `shared doctor` — the self-healing reconcile: prune
// ref rows for projects no longer live (the count is derived from reality).
func newSharedDoctorCmd(g *GlobalOpts) *cobra.Command {
return &cobra.Command{
Use: "doctor",
Short: "Reconcile the ledger against live containers (prune dead refs)",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
mgr, closeFn, err := buildManager(cmd)
if err != nil {
return err
}
defer closeFn()
pruned, err := mgr.Reconcile(cmd.Context())
if err != nil {
return err
}
if g.JSON {
return writeJSON(cmd, map[string]any{"pruned": pruned})
}
w := cmd.OutOrStdout()
fmt.Fprintf(w, "reconciled: pruned %d stale ref row(s)\n", len(pruned))
return nil
},
}
}
func newSharedStatusCmd(g *GlobalOpts) *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "Show shared-service ref counts and consuming projects",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
mgr, closeFn, err := buildManager(cmd)
if err != nil {
return err
}
defer closeFn()
// Best-effort self-healing reconcile: prune refs for projects whose
// containers are no longer live. Skipped silently if the daemon is down
// (the ledger view below is still truthful about recorded refs).
_, _ = mgr.Reconcile(cmd.Context())
rows, err := mgr.Status()
if err != nil {
return err
}
if g.JSON {
return writeJSON(cmd, map[string]any{"shared": rows})
}
w := cmd.OutOrStdout()
if len(rows) == 0 {
fmt.Fprintln(w, "no shared services recorded yet (run `devstack up`)")
return nil
}
for _, r := range rows {
fmt.Fprintf(w, "%-20s %-10s refs=%d %v\n", r.Alias, r.Status, r.RefCount, r.Projects)
}
return nil
},
}
}
// buildManager assembles a workspace.Manager from the current directory: the
// loaded workspace, the read-only docker client (context keys the ledger), the
// machine-global state ledger, and the embedded template source. The returned
// closer releases the ledger and the docker client.
func buildManager(cmd *cobra.Command) (*workspace.Manager, func(), error) {
cwd, err := os.Getwd()
if err != nil {
return nil, nil, err
}
model, err := config.Load(cwd)
if err != nil {
return nil, nil, err
}
ctx := cmd.Context()
ctxName := state.DefaultContext
var dockerClient docker.Client
if c, err := docker.NewClient(ctx); err == nil {
dockerClient = c
ctxName = c.ContextName()
} else {
// No daemon client: ledger reads still work; reconcile degrades to a no-op.
dockerClient = &docker.MockClient{Context: ctxName}
}
db, err := state.Open(ctx, xdg.DataHome(), ctxName)
if err != nil {
if c, ok := dockerClient.(interface{ Close() error }); ok {
_ = c.Close()
}
return nil, nil, err
}
mgr := &workspace.Manager{
Model: model,
DB: db,
Docker: dockerClient,
Source: builtinSource(),
LockPath: filepath.Join(xdg.RuntimeDir(), "devstack.lock"),
}
closeFn := func() {
db.Close()
_ = dockerClient.Close()
}
return mgr, closeFn, nil
}