Skip to content

Commit 1d17605

Browse files
committed
feat: add dry-run and prune flag for apply command
1 parent cb23c7f commit 1d17605

File tree

2 files changed

+255
-49
lines changed

2 files changed

+255
-49
lines changed

cmd/internal/apply/command.go

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ import (
2727

2828
"github.com/goccy/go-yaml"
2929
"github.com/googleapis/genai-toolbox/cmd/internal"
30-
"github.com/googleapis/genai-toolbox/internal/log"
3130
"github.com/spf13/cobra"
3231
)
3332

3433
type applyCmd struct {
3534
*cobra.Command
3635
port int
3736
address string
37+
dryRun bool
38+
prune bool
3839
}
3940

4041
func NewCommand(opts *internal.ToolboxOptions) *cobra.Command {
@@ -48,6 +49,8 @@ func NewCommand(opts *internal.ToolboxOptions) *cobra.Command {
4849
internal.ConfigFileFlags(flags, opts)
4950
flags.StringVarP(&cmd.address, "address", "a", "127.0.0.1", "Address of the server that is running on.")
5051
flags.IntVarP(&cmd.port, "port", "p", 5000, "Port of the server that is listening on.")
52+
flags.BoolVar(&cmd.dryRun, "dry-run", false, "Simulate the apply process and report intended updates.")
53+
flags.BoolVar(&cmd.prune, "prune", false, "Remove resources from the server that are not defined in the provided YAML files.")
5154
cmd.RunE = func(*cobra.Command, []string) error { return runApply(cmd, opts) }
5255
return cmd.Command
5356
}
@@ -92,6 +95,16 @@ func (p primitives) Remove(kind, name string) {
9295
}
9396
}
9497

98+
func (p primitives) GetRemaining() []struct{ kind, name string } {
99+
var remaining []struct{ kind, name string }
100+
for kind, names := range p {
101+
for name := range names {
102+
remaining = append(remaining, struct{ kind, name string }{kind, name})
103+
}
104+
}
105+
return remaining
106+
}
107+
95108
// adminRequest is a generic helper for admin api requests.
96109
func adminRequest(ctx context.Context, method, url string, body any) ([]byte, error) {
97110
var bodyReader io.Reader
@@ -171,6 +184,17 @@ func applyPrimitive(ctx context.Context, address string, port int, kind, name st
171184
return nil
172185
}
173186

187+
func deletePrimitive(ctx context.Context, address string, port int, kind, name string) error {
188+
url := fmt.Sprintf("http://%s:%d/admin/%s/%s", address, port, kind, name)
189+
190+
_, err := adminRequest(ctx, http.MethodDelete, url, nil)
191+
if err != nil {
192+
return err
193+
}
194+
195+
return nil
196+
}
197+
174198
func runApply(cmd *applyCmd, opts *internal.ToolboxOptions) error {
175199
ctx, cancel := context.WithCancel(cmd.Context())
176200
defer cancel()
@@ -196,18 +220,40 @@ func runApply(cmd *applyCmd, opts *internal.ToolboxOptions) error {
196220
return err
197221
}
198222

223+
if cmd.dryRun {
224+
opts.Logger.InfoContext(ctx, "simulating dry run")
225+
}
199226
opts.Logger.InfoContext(ctx, "starting apply sequence", "count", len(filePaths))
200227
for _, filePath := range filePaths {
201-
if err := processFile(ctx, opts.Logger, filePath, p, cmd.address, cmd.port); err != nil {
228+
if err := processFile(ctx, opts, filePath, p, cmd); err != nil {
202229
opts.Logger.ErrorContext(ctx, err.Error())
203230
return err
204231
}
205232
}
233+
if cmd.prune {
234+
remaining := p.GetRemaining()
235+
opts.Logger.InfoContext(ctx, "starting prune sequence", "count", len(remaining))
236+
for _, resource := range remaining {
237+
if cmd.dryRun {
238+
fmt.Fprintf(opts.IOStreams.Out, "[dry-run] would delete %s: %s\n", resource.kind, resource.name)
239+
continue
240+
}
241+
242+
// Perform the actual deletion
243+
if err := deletePrimitive(ctx, cmd.address, cmd.port, resource.kind, resource.name); err != nil {
244+
return fmt.Errorf("failed to prune %s/%s: %w", resource.kind, resource.name, err)
245+
}
246+
247+
// Remove from map so we don't try to delete it again
248+
// if processFile is called for multiple files
249+
p.Remove(resource.kind, resource.name)
250+
}
251+
}
206252
opts.Logger.InfoContext(ctx, "Done applying")
207253
return nil
208254
}
209255

210-
func processFile(ctx context.Context, logger log.Logger, path string, p primitives, address string, port int) error {
256+
func processFile(ctx context.Context, opts *internal.ToolboxOptions, path string, p primitives, cmd *applyCmd) error {
211257
f, err := os.Open(path)
212258
if err != nil {
213259
return fmt.Errorf("unable to open file at %q: %w", path, err)
@@ -231,15 +277,15 @@ func processFile(ctx context.Context, logger log.Logger, path string, p primitiv
231277
kind, kOk := doc["kind"].(string)
232278
name, nOk := doc["name"].(string)
233279
if !kOk || !nOk || kind == "" || name == "" {
234-
logger.WarnContext(ctx, fmt.Sprintf("invalid primitive schema: missing metadata in %s: kind and name are required", path))
280+
opts.Logger.WarnContext(ctx, fmt.Sprintf("invalid primitive schema: missing metadata in %s: kind and name are required", path))
235281
continue
236282
}
237283

238284
delete(doc, "kind")
239285

240286
if p.Exists(kind, name) {
241287
p.Remove(kind, name)
242-
remoteBody, err := getPrimitiveByName(ctx, address, port, kind, name)
288+
remoteBody, err := getPrimitiveByName(ctx, cmd.address, cmd.port, kind, name)
243289
if err != nil {
244290
return err
245291
}
@@ -253,17 +299,20 @@ func processFile(ctx context.Context, logger log.Logger, path string, p primitiv
253299
}
254300

255301
if bytes.Equal(localJSON, remoteJSON) {
256-
logger.DebugContext(ctx, "skipping: no changes detected", "kind", kind, "name", name)
302+
opts.Logger.DebugContext(ctx, "skipping: no changes detected", "kind", kind, "name", name)
257303
continue
258304
}
259-
logger.DebugContext(ctx, "change detected, updating resource", "kind", kind, "name", name)
305+
opts.Logger.DebugContext(ctx, "change detected, updating resource", "kind", kind, "name", name)
260306
}
261307

262308
// TODO: check --prune flag: if prune, delete primitives that are left
263309
// in the primitive list
264-
// TODO: check for --dry-run flag.
265310

266-
if err := applyPrimitive(ctx, address, port, kind, name, doc); err != nil {
311+
if cmd.dryRun {
312+
fmt.Fprintf(opts.IOStreams.Out, "[dry-run] would update %s: %s\n", kind, name)
313+
continue
314+
}
315+
if err := applyPrimitive(ctx, cmd.address, cmd.port, kind, name, doc); err != nil {
267316
return err
268317
}
269318
}

0 commit comments

Comments
 (0)