package main // `carlos apps set`'s instance update policy flags (instance update settings // spec §3.2, §5): --instance-control, --default-updates, --default-channel and // --default-major, sent to the console's PUT /api/cli/apps/{account}/{app}/updates // door (handleAPIUpdatesPolicy). import ( "context" "encoding/json" "errors" "flag" "fmt" "net/http" "strconv" "strings" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/updatewords" ) // updatePolicyReq is the door's body: a key not present is left as it is, and // a key present with null or "" clears it. type updatePolicyReq map[string]any // apiUpdatePolicy is the door's echo: what the console stored. type apiUpdatePolicy struct { InstanceControl []string `json:"instance_control"` DefaultUpdates string `json:"default_updates"` DefaultChannel string `json:"default_channel"` DefaultMajor *int `json:"default_major"` } // updatePolicyRequest builds the door's body from the flags given: a flag not // given is not sent, and an empty value clears. It returns nil when none of // the four was given. A bad --default-major costs no round trip. func updatePolicyRequest(fs *flag.FlagSet, control, updates, channel, major string) (updatePolicyReq, error) { req := updatePolicyReq{} if flagWasSet(fs, "instance-control") { var names []string // nil marshals as null, which the console reads as a clear for _, n := range strings.Split(control, ",") { if n = strings.TrimSpace(n); n != "" { names = append(names, n) } } req["instance_control"] = names } if flagWasSet(fs, "default-updates") { req["default_updates"] = strings.TrimSpace(updates) } if flagWasSet(fs, "default-channel") { req["default_channel"] = strings.TrimSpace(channel) } if flagWasSet(fs, "default-major") { if m := strings.TrimSpace(major); m == "" { req["default_major"] = nil } else { n, err := strconv.Atoi(m) if err != nil || n < 0 { return nil, errors.New(updatewords.Text("settings.cli.bad-major-flag")) } req["default_major"] = n } } if len(req) == 0 { return nil, nil } return req, nil } // setUpdatePolicy is the update policy half of `carlos apps set`. The console // refuses with its own words, and they are returned as they came. func (s *apiSession) setUpdatePolicy(ctx context.Context, a apiAcct, app string, req updatePolicyReq) (apiUpdatePolicy, error) { status, body, err := s.putJSON(ctx, apiPath(a, app, "/updates"), req) if err != nil { return apiUpdatePolicy{}, err } if status != http.StatusOK { return apiUpdatePolicy{}, apiErrorFromBody(status, body) } var out apiUpdatePolicy if err := json.Unmarshal(body, &out); err != nil { return apiUpdatePolicy{}, fmt.Errorf(appsSetUnreadable, err) } return out, nil } // appsSetRun is `carlos apps set` once its flags are checked and its session // resolved: the swap door when a swap flag was given, then the update policy // door when a policy flag was, one line for each. The first failure stops it. func appsSetRun(ctx context.Context, sess *apiSession, acct apiAcct, app string, swap *swapPolicyReq, policy updatePolicyReq) error { if swap == nil && policy == nil { return errors.New(appsSetNothing) } if swap != nil { got, err := sess.setSwapPolicy(ctx, acct, app, *swap) if err != nil { return fmt.Errorf(appsSetError, err) } if err := appsSetStuck(*swap, got); err != nil { return err } fmt.Fprintf(stdout, appsSetDone+"\n", app, got.Swap, got.Concurrency) if got.Swap == "hold" { mutedf(stdout, appsSetHoldNote) } else { mutedf(stdout, appsSetOverlapNote) } } if policy != nil { got, err := sess.setUpdatePolicy(ctx, acct, app, policy) if err != nil { return fmt.Errorf(appsSetError, err) } none := updatewords.Text("settings.cli.none") or := func(s string) string { if s == "" { return none } return s } // An unset default is auto: the setting's own value word, not copy. updates := got.DefaultUpdates if updates == "" { updates = release.UpdatesAuto } major := none if got.DefaultMajor != nil { major = strconv.Itoa(*got.DefaultMajor) } fmt.Fprintf(stdout, updatewords.Text("settings.cli.apps-set-policy-done")+"\n", app, or(strings.Join(got.InstanceControl, ", ")), updates, or(got.DefaultChannel), major) } return nil }