package console import ( "context" "errors" "fmt" "math" "net/http" "os" "slices" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/release" ) // refuseSeedMove answers a move of a seed that is not placed (--from) or // not committed (import) with the deck's sentence (spec ยง5.8). Both move // doors call it after their gate and before their transition, so each // answers the sentence itself; the transitions refuse it again under the // record CAS. func (s *Server) refuseSeedMove(w http.ResponseWriter, rec *provision.Record) bool { if b := rec.Seed.MoveBlock(); b != "" { jsonError(w, http.StatusConflict, provision.SeedMoveSentence(b)) return true } return false } // placeSeedReadFailed is carlos apps place's answer when the strict read of // the app's records fails, before anything has moved. The listing after the // placement is written keeps main's sentence (placeInstancesFailed). const placeSeedReadFailed = "could not update this app's instances. Try again." // placeSeedBlock is carlos apps place's seed rule (preflight M8(a)): a // place re-homes every record of the app, so it is a move of each. It reads // the app's records strictly and returns the first record's MoveBlock, "" // when none blocks. A record it cannot read could be anything, so it is an // error and the place is refused. A record deleted between the listing and // its read (the race the place's own walk skips) is read again once. // // No cap: the place walks every record of the app, and a cap would refuse // a large app's place for good. func placeSeedBlock(ctx context.Context, st release.Store, acct, app string) (string, error) { ctx, cancel := context.WithTimeout(ctx, instanceStoreTimeout) defer cancel() recs, unreadable, err := provision.ListRecordsStrict(ctx, st, acct, app, math.MaxInt) if errors.Is(err, os.ErrNotExist) { recs, unreadable, err = provision.ListRecordsStrict(ctx, st, acct, app, math.MaxInt) } if err != nil { return "", err } if len(unreadable) > 0 { hosts := make([]string, 0, len(unreadable)) for h := range unreadable { hosts = append(hosts, h) } slices.Sort(hosts) return "", fmt.Errorf("records that do not read: %v", hosts) } for _, rec := range recs { if b := rec.Seed.MoveBlock(); b != "" { return b, nil } } return "", nil } // seedMoveConflict is the sentence of a write the record's seed refused // under its CAS (provision.SeedMoveError), "" for any other error. func seedMoveConflict(err error) string { var refusal *provision.SeedMoveError if errors.As(err, &refusal) { return provision.SeedMoveSentence(refusal.Block) } return "" }