package release import ( "context" "crypto/rand" "encoding/hex" "encoding/json" "errors" "time" ) type ClaimPurgeRequest struct { V int `json:"v"` ID string `json:"id"` AppID string `json:"app_id"` Account string `json:"account"` App string `json:"app"` TombstoneKey string `json:"tombstone_key"` TombstoneETag string `json:"tombstone_etag"` Tombstone json.RawMessage `json:"tombstone"` } func RequestClaimAutoPurge(ctx context.Context, s Store, account, app string, now time.Time) error { if !ValidAccount(account) || !ValidApp(app) { return ErrNotTrashed } tomb, err := ReadAppTombstone(ctx, s, account, app) if err != nil { return err } if tomb == nil || now.Before(tomb.DeletedAt.Add(TrashCooloff)) { return ErrNotTrashed } key := AppTombstoneKey(account, app) body, tag, err := s.Get(ctx, key) if err != nil { return err } prefix := "apps/" + account + "/" + app + "/" admission, _, err := s.Get(ctx, prefix+"admission.json") if err != nil { return err } var identity struct { V int `json:"v"` AppID string `json:"app_id"` State string `json:"state"` Op json.RawMessage `json:"op"` } if err = json.Unmarshal(admission, &identity); err != nil { return err } if identity.V != 1 || identity.AppID == "" || tag == "" { return ErrNotTrashed } if identity.State != "open" || (len(identity.Op) > 0 && string(identity.Op) != "null") { return nil } var nonce [16]byte if _, err = rand.Read(nonce[:]); err != nil { return err } request := ClaimPurgeRequest{V: 1, ID: hex.EncodeToString(nonce[:]), AppID: identity.AppID, Account: account, App: app, TombstoneKey: key, TombstoneETag: tag, Tombstone: body} raw, err := json.Marshal(request) if err != nil { return err } _, err = s.Put(ctx, prefix+"purge-request.json", raw, "") if errors.Is(err, ErrCASConflict) { return nil } return err }