@@ -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
3433type applyCmd struct {
3534 * cobra.Command
3635 port int
3736 address string
37+ dryRun bool
38+ prune bool
3839}
3940
4041func 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.
96109func 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+
174198func 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