feat: Add rename cooldown field and cloudprov rename

This commit is contained in:
2026-08-12 10:06:31 +00:00
parent 1d6c89c368
commit e02b263054
2 changed files with 31 additions and 0 deletions
+17
View File
@@ -202,3 +202,20 @@ func ProjectedUsers(ctx context.Context, hqUserID string) ([]sharedmodels.User,
}
return users, nil
}
// RenameInstance changes a cloud instance's name and moves it to the slug that
// name derives to.
//
// It writes `instances` and nothing else, so admin's control-plane write
// boundary is unchanged. It issues no licence: a licence binds the instance
// UUID, which a rename never touches.
func RenameInstance(ctx context.Context, instanceID, name string) (*sharedmodels.Instance, error) {
return provision.RenameInstance(ctx, db.ControlDB(), instanceID, name)
}
// RestoreInstanceIdentity puts an instance's previous name and slug back, for a
// caller unwinding a rename whose admin-side write failed. Leaving the two
// databases disagreeing would have HQ print a host that is not the host.
func RestoreInstanceIdentity(ctx context.Context, instanceID, name, slug string) error {
return provision.RestoreInstanceIdentity(ctx, db.ControlDB(), instanceID, name, slug)
}
+14
View File
@@ -111,6 +111,15 @@ const GracePeriod = 3 * 24 * time.Hour
// second mechanism.
const RenewWindow = 7 * 24 * time.Hour
// RenameCooldown is how long a customer must wait between renames of one
// instance.
//
// A rename moves the instance's DNS host and invalidates every saved link to it,
// so this exists to make that a considered act rather than a slider. Staff are
// not subject to it: a support conversation about a name is already a human
// deciding.
const RenameCooldown = 24 * time.Hour
type Account struct {
ID bson.ObjectID `bson:"_id,omitempty" json:"-"`
AccountID string `bson:"account_id" json:"account_id"`
@@ -137,6 +146,11 @@ type Instance struct {
Status string `bson:"status" json:"status"`
CurrentLicense string `bson:"current_license,omitempty" json:"current_license,omitempty"`
RelinkCount int `bson:"relink_count" json:"relink_count"`
// RenamedAt is when this instance last changed name, and backs the customer
// rename cooldown. It is a pointer because absent means "never renamed"; a
// zero time.Time would read as year 1 — an inert cooldown, but only by
// accident. Staff renames deliberately leave it alone.
RenamedAt *time.Time `bson:"renamed_at,omitempty" json:"renamed_at,omitempty"`
InjectFailedAt *time.Time `bson:"inject_failed_at,omitempty" json:"inject_failed_at,omitempty"`
// NoticesSent holds the lifecycle notice keys already emailed for the
// CURRENT term ("expiring", "expired", "delete_7", "delete_1"). Renewal