package main import ( "context" "flag" "fmt" "os" "time" ) func runDeploy(args []string) error { fs := flag.NewFlagSet("deploy", flag.ExitOnError) g := addGlobalFlags(fs) ref := fs.String("ref", "", "image tag / git ref / source-specific deploy target") note := fs.String("note", "", "free-text note recorded with the deploy") timeout := fs.Duration("timeout", 15*time.Minute, "max time to wait for the deploy to finish") fs.Usage = func() { fmt.Fprint(os.Stderr, "Usage: tinyforge deploy [--ref TAG] [--note TEXT] [--timeout DUR]\n\n"+ "Trigger a deploy and wait for it to finish. Requires an admin token.\n") fs.PrintDefaults() } if err := fs.Parse(args); err != nil { return err } if fs.NArg() != 1 { fs.Usage() return fmt.Errorf("expected exactly one app (name or id)") } sess, err := newSession(g) if err != nil { return err } // Resolve the app on a short deadline; the deploy itself gets the full one. resolveCtx, cancelResolve := context.WithTimeout(context.Background(), 30*time.Second) defer cancelResolve() app, err := resolveApp(resolveCtx, sess.client, fs.Arg(0)) if err != nil { return err } body := map[string]string{} if *ref != "" { body["reference"] = *ref } if *note != "" { body["note"] = *note } fmt.Printf("Deploying %s%s…\n", app.Name, refSuffix(*ref)) // The endpoint returns 202 but blocks until the deploy completes, so a // success here means it finished; allow plenty of time for pull/build. ctx, cancel := context.WithTimeout(context.Background(), *timeout) defer cancel() var result DeployResult if err := sess.client.doJSON(ctx, "POST", "/api/workloads/"+app.ID+"/deploy", body, &result); err != nil { return err } fmt.Printf("Deploy of %s completed (triggered by %s).\n", app.Name, result.TriggeredBy) fmt.Printf("Follow with: tinyforge logs %s -f\n", app.Name) return nil } func refSuffix(ref string) string { if ref == "" { return "" } return fmt.Sprintf(" @ %s", ref) }