Skip to content

Commit 704cb99

Browse files
committed
feat: add variable protection duration with admin-configurable max limit
Replace checkbox protection toggle with a dropdown that allows users to select protection duration (1 hour, 1 day, 2 days, 7 days, forever). The admin can now configure a maximum allowed protection duration to prevent abuse. - Change protectionRenewalDurationMinutes to protectionMaxDurationMinutes - Add protectionDurationMinutes parameter to create/update clone APIs - Update UI to use dropdown for protection duration selection - Update swagger specs with new API parameters - Adjust default protection duration from 7 days to 1 day
1 parent 3526abc commit 704cb99

22 files changed

Lines changed: 195 additions & 216 deletions

engine/api/swagger-spec/dblab_openapi.yaml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,14 @@ components:
15161516
type: array
15171517
items:
15181518
$ref: '#/components/schemas/Clone'
1519+
protectionLeaseDurationMinutes:
1520+
type: integer
1521+
format: int64
1522+
description: Default protection lease duration in minutes
1523+
protectionMaxDurationMinutes:
1524+
type: integer
1525+
format: int64
1526+
description: Maximum allowed protection duration in minutes
15191527
Retrieving:
15201528
type: object
15211529
properties:
@@ -1666,10 +1674,10 @@ components:
16661674
type: integer
16671675
format: int64
16681676
description: Protection lease duration in minutes
1669-
protectionRenewalDurationMinutes:
1677+
protectionMaxDurationMinutes:
16701678
type: integer
16711679
format: int64
1672-
description: Protection renewal duration in minutes
1680+
description: Maximum allowed protection duration in minutes
16731681
CreateClone:
16741682
type: object
16751683
properties:
@@ -1685,6 +1693,10 @@ components:
16851693
protected:
16861694
type: boolean
16871695
default:
1696+
protectionDurationMinutes:
1697+
type: integer
1698+
format: int64
1699+
description: Protection duration in minutes. 0 means forever, omit for default duration.
16881700
db:
16891701
type: object
16901702
properties:
@@ -1715,10 +1727,10 @@ components:
17151727
protected:
17161728
type: boolean
17171729
default: false
1718-
renewLease:
1719-
type: boolean
1720-
default: false
1721-
description: When true, renews the protection lease for the clone
1730+
protectionDurationMinutes:
1731+
type: integer
1732+
format: int64
1733+
description: Protection duration in minutes. 0 means forever, omit for default duration.
17221734
StartObservationRequest:
17231735
type: object
17241736
properties:

engine/api/swagger-spec/dblab_server_swagger.yaml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,14 @@ components:
947947
type: "array"
948948
items:
949949
$ref: "#/components/schemas/Clone"
950+
protectionLeaseDurationMinutes:
951+
type: "integer"
952+
format: "int64"
953+
description: "Default protection lease duration in minutes"
954+
protectionMaxDurationMinutes:
955+
type: "integer"
956+
format: "int64"
957+
description: "Maximum allowed protection duration in minutes"
950958

951959
Retrieving:
952960
type: "object"
@@ -1101,10 +1109,10 @@ components:
11011109
type: "integer"
11021110
format: "int64"
11031111
description: "Protection lease duration in minutes"
1104-
protectionRenewalDurationMinutes:
1112+
protectionMaxDurationMinutes:
11051113
type: "integer"
11061114
format: "int64"
1107-
description: "Protection renewal duration in minutes"
1115+
description: "Maximum allowed protection duration in minutes"
11081116

11091117
CreateClone:
11101118
type: "object"
@@ -1119,6 +1127,10 @@ components:
11191127
protected:
11201128
type: "boolean"
11211129
default: false
1130+
protectionDurationMinutes:
1131+
type: "integer"
1132+
format: "int64"
1133+
description: "Protection duration in minutes. 0 means forever, omit for default duration."
11221134
db:
11231135
type: "object"
11241136
properties:
@@ -1148,10 +1160,10 @@ components:
11481160
protected:
11491161
type: "boolean"
11501162
default: false
1151-
renewLease:
1152-
type: "boolean"
1153-
default: false
1154-
description: "When true, renews the protection lease for the clone"
1163+
protectionDurationMinutes:
1164+
type: "integer"
1165+
format: "int64"
1166+
description: "Protection duration in minutes. 0 means forever, omit for default duration."
11551167

11561168
StartObservationRequest:
11571169
type: "object"

engine/configs/config.example.logical_generic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ retrieval: # Data retrieval: initial sync and ongoing updates. Two methods:
146146
cloning:
147147
accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
148148
maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
149-
protectionLeaseDurationMinutes: 10080 # Clone protection lease duration in minutes (default: 7 days); 0 - infinite protection
150-
protectionRenewalDurationMinutes: 4320 # Renewal duration in minutes (default: 3 days); 0 - use initial duration
149+
protectionLeaseDurationMinutes: 1440 # Default protection duration in minutes (default: 1 day); 0 - infinite protection
150+
protectionMaxDurationMinutes: 10080 # Maximum allowed protection duration in minutes (default: 7 days); 0 - no limit
151151
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
152152

153153
diagnostic:

engine/configs/config.example.logical_rds_iam.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ retrieval: # Data retrieval: initial sync and ongoing updates. Two methods:
146146
cloning:
147147
accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
148148
maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
149-
protectionLeaseDurationMinutes: 10080 # Clone protection lease duration in minutes (default: 7 days); 0 - infinite protection
150-
protectionRenewalDurationMinutes: 4320 # Renewal duration in minutes (default: 3 days); 0 - use initial duration
149+
protectionLeaseDurationMinutes: 1440 # Default protection duration in minutes (default: 1 day); 0 - infinite protection
150+
protectionMaxDurationMinutes: 10080 # Maximum allowed protection duration in minutes (default: 7 days); 0 - no limit
151151
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
152152

153153
diagnostic:

engine/configs/config.example.physical_generic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ retrieval: # Data retrieval: initial sync and ongoing updates. Two methods:
114114
cloning:
115115
accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
116116
maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
117-
protectionLeaseDurationMinutes: 10080 # Clone protection lease duration in minutes (default: 7 days); 0 - infinite protection
118-
protectionRenewalDurationMinutes: 4320 # Renewal duration in minutes (default: 3 days); 0 - use initial duration
117+
protectionLeaseDurationMinutes: 1440 # Default protection duration in minutes (default: 1 day); 0 - infinite protection
118+
protectionMaxDurationMinutes: 10080 # Maximum allowed protection duration in minutes (default: 7 days); 0 - no limit
119119
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
120120

121121
diagnostic:

engine/configs/config.example.physical_pgbackrest.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ retrieval: # Data retrieval: initial sync and ongoing updates. Two methods:
144144
cloning:
145145
accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
146146
maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
147-
protectionLeaseDurationMinutes: 10080 # Clone protection lease duration in minutes (default: 7 days); 0 - infinite protection
148-
protectionRenewalDurationMinutes: 4320 # Renewal duration in minutes (default: 3 days); 0 - use initial duration
147+
protectionLeaseDurationMinutes: 1440 # Default protection duration in minutes (default: 1 day); 0 - infinite protection
148+
protectionMaxDurationMinutes: 10080 # Maximum allowed protection duration in minutes (default: 7 days); 0 - no limit
149149
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
150150

151151
diagnostic:

engine/configs/config.example.physical_walg.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ retrieval: # Data retrieval: initial sync and ongoing updates. Two methods:
117117
cloning:
118118
accessHost: "localhost" # Host that will be specified in database connection info for all clones (only used to inform users)
119119
maxIdleMinutes: 120 # Automatically delete clones after the specified minutes of inactivity; 0 - disable automatic deletion
120-
protectionLeaseDurationMinutes: 10080 # Clone protection lease duration in minutes (default: 7 days); 0 - infinite protection
121-
protectionRenewalDurationMinutes: 4320 # Renewal duration in minutes (default: 3 days); 0 - use initial duration
120+
protectionLeaseDurationMinutes: 1440 # Default protection duration in minutes (default: 1 day); 0 - infinite protection
121+
protectionMaxDurationMinutes: 10080 # Maximum allowed protection duration in minutes (default: 7 days); 0 - no limit
122122
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
123123

124124
diagnostic:

engine/internal/cloning/base.go

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ const (
3939

4040
// Config contains a cloning configuration.
4141
type Config struct {
42-
MaxIdleMinutes uint `yaml:"maxIdleMinutes"`
43-
AccessHost string `yaml:"accessHost"`
44-
ProtectionLeaseDurationMinutes uint `yaml:"protectionLeaseDurationMinutes"`
45-
ProtectionRenewalDurationMinutes uint `yaml:"protectionRenewalDurationMinutes"`
46-
ProtectionExpiryWarningMinutes uint `yaml:"protectionExpiryWarningMinutes"`
42+
MaxIdleMinutes uint `yaml:"maxIdleMinutes"`
43+
AccessHost string `yaml:"accessHost"`
44+
ProtectionLeaseDurationMinutes uint `yaml:"protectionLeaseDurationMinutes"`
45+
ProtectionMaxDurationMinutes uint `yaml:"protectionMaxDurationMinutes"`
46+
ProtectionExpiryWarningMinutes uint `yaml:"protectionExpiryWarningMinutes"`
4747
}
4848

4949
// Base provides cloning service.
@@ -181,7 +181,7 @@ func (c *Base) CreateClone(cloneRequest *types.CloneCreateRequest) (*models.Clon
181181

182182
var protectedTill *models.LocalTime
183183
if cloneRequest.Protected {
184-
protectedTill = c.calculateInitialProtectionTime()
184+
protectedTill = c.calculateProtectionTime(cloneRequest.ProtectionDurationMinutes)
185185
}
186186

187187
clone := &models.Clone{
@@ -280,10 +280,10 @@ func (c *Base) fillCloneSession(cloneID string, session *resources.Session) {
280280
clone.DB.Host, clone.DB.Port, clone.DB.Username, clone.DB.DBName)
281281

282282
clone.Metadata = models.CloneMetadata{
283-
CloningTime: w.TimeStartedAt.Sub(w.TimeCreatedAt).Seconds(),
284-
MaxIdleMinutes: c.config.MaxIdleMinutes,
285-
ProtectionLeaseDurationMinutes: c.config.ProtectionLeaseDurationMinutes,
286-
ProtectionRenewalDurationMinutes: c.config.ProtectionRenewalDurationMinutes,
283+
CloningTime: w.TimeStartedAt.Sub(w.TimeCreatedAt).Seconds(),
284+
MaxIdleMinutes: c.config.MaxIdleMinutes,
285+
ProtectionLeaseDurationMinutes: c.config.ProtectionLeaseDurationMinutes,
286+
ProtectionMaxDurationMinutes: c.config.ProtectionMaxDurationMinutes,
287287
}
288288
}
289289

@@ -464,16 +464,12 @@ func (c *Base) UpdateClone(id string, patch types.CloneUpdateRequest) (*models.C
464464

465465
c.cloneMutex.Lock()
466466

467-
if patch.RenewLease && w.Clone.Protected {
468-
w.Clone.ProtectedTill = c.calculateRenewalTime()
469-
} else if patch.Protected != w.Clone.Protected {
470-
w.Clone.Protected = patch.Protected
471-
472-
if patch.Protected {
473-
w.Clone.ProtectedTill = c.calculateInitialProtectionTime()
474-
} else {
475-
w.Clone.ProtectedTill = nil
476-
}
467+
if patch.Protected {
468+
w.Clone.Protected = true
469+
w.Clone.ProtectedTill = c.calculateProtectionTime(patch.ProtectionDurationMinutes)
470+
} else {
471+
w.Clone.Protected = false
472+
w.Clone.ProtectedTill = nil
477473
}
478474

479475
clone = w.Clone
@@ -484,29 +480,34 @@ func (c *Base) UpdateClone(id string, patch types.CloneUpdateRequest) (*models.C
484480
return clone, nil
485481
}
486482

487-
// calculateInitialProtectionTime calculates the protection expiry time for a new protection lease.
488-
func (c *Base) calculateInitialProtectionTime() *models.LocalTime {
489-
if c.config.ProtectionLeaseDurationMinutes == 0 {
490-
return nil
483+
// calculateProtectionTime calculates the protection expiry time based on the requested duration.
484+
// If durationMinutes is nil, uses the default from config.
485+
// If durationMinutes is 0, returns nil (infinite protection) unless max duration is configured.
486+
// If max duration is configured, the duration is capped at the max.
487+
func (c *Base) calculateProtectionTime(durationMinutes *uint) *models.LocalTime {
488+
var minutes uint
489+
490+
if durationMinutes != nil {
491+
minutes = *durationMinutes
492+
} else {
493+
minutes = c.config.ProtectionLeaseDurationMinutes
491494
}
492495

493-
expiry := time.Now().Add(time.Duration(c.config.ProtectionLeaseDurationMinutes) * time.Minute)
496+
maxMinutes := c.config.ProtectionMaxDurationMinutes
494497

495-
return models.NewLocalTime(expiry)
496-
}
497-
498-
// calculateRenewalTime calculates the protection expiry time for a lease renewal.
499-
func (c *Base) calculateRenewalTime() *models.LocalTime {
500-
renewalMinutes := c.config.ProtectionRenewalDurationMinutes
501-
if renewalMinutes == 0 {
502-
renewalMinutes = c.config.ProtectionLeaseDurationMinutes
498+
if minutes == 0 {
499+
if maxMinutes > 0 {
500+
minutes = maxMinutes
501+
} else {
502+
return nil
503+
}
503504
}
504505

505-
if renewalMinutes == 0 {
506-
return nil
506+
if maxMinutes > 0 && minutes > maxMinutes {
507+
minutes = maxMinutes
507508
}
508509

509-
expiry := time.Now().Add(time.Duration(renewalMinutes) * time.Minute)
510+
expiry := time.Now().Add(time.Duration(minutes) * time.Minute)
510511

511512
return models.NewLocalTime(expiry)
512513
}
@@ -644,11 +645,11 @@ func (c *Base) ResetClone(cloneID string, resetOptions types.ResetCloneRequest)
644645
func (c *Base) GetCloningState() models.Cloning {
645646
clones := c.GetClones()
646647
cloning := models.Cloning{
647-
ExpectedCloningTime: c.getExpectedCloningTime(),
648-
Clones: clones,
649-
NumClones: uint64(len(clones)),
650-
ProtectionLeaseDurationMinutes: c.config.ProtectionLeaseDurationMinutes,
651-
ProtectionRenewalDurationMinutes: c.config.ProtectionRenewalDurationMinutes,
648+
ExpectedCloningTime: c.getExpectedCloningTime(),
649+
Clones: clones,
650+
NumClones: uint64(len(clones)),
651+
ProtectionLeaseDurationMinutes: c.config.ProtectionLeaseDurationMinutes,
652+
ProtectionMaxDurationMinutes: c.config.ProtectionMaxDurationMinutes,
652653
}
653654

654655
return cloning

engine/pkg/client/dblabapi/types/clone.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@ package types
77

88
// CloneCreateRequest represents clone params of a create request.
99
type CloneCreateRequest struct {
10-
ID string `json:"id"`
11-
Protected bool `json:"protected"`
12-
DB *DatabaseRequest `json:"db"`
13-
Snapshot *SnapshotCloneFieldRequest `json:"snapshot"`
14-
ExtraConf map[string]string `json:"extra_conf"`
15-
Branch string `json:"branch"`
16-
Revision int `json:"-"`
10+
ID string `json:"id"`
11+
Protected bool `json:"protected"`
12+
ProtectionDurationMinutes *uint `json:"protectionDurationMinutes,omitempty"`
13+
DB *DatabaseRequest `json:"db"`
14+
Snapshot *SnapshotCloneFieldRequest `json:"snapshot"`
15+
ExtraConf map[string]string `json:"extra_conf"`
16+
Branch string `json:"branch"`
17+
Revision int `json:"-"`
1718
}
1819

1920
// CloneUpdateRequest represents params of an update request.
2021
type CloneUpdateRequest struct {
21-
Protected bool `json:"protected"`
22-
RenewLease bool `json:"renewLease,omitempty"`
22+
Protected bool `json:"protected"`
23+
ProtectionDurationMinutes *uint `json:"protectionDurationMinutes,omitempty"`
2324
}
2425

2526
// DatabaseRequest represents database params of a clone request.

engine/pkg/models/clone.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ func (c *Clone) ProtectionExpiresIn() time.Duration {
5151

5252
// CloneMetadata contains fields describing a clone model.
5353
type CloneMetadata struct {
54-
CloneDiffSize uint64 `json:"cloneDiffSize"`
55-
LogicalSize uint64 `json:"logicalSize"`
56-
CloningTime float64 `json:"cloningTime"`
57-
MaxIdleMinutes uint `json:"maxIdleMinutes"`
58-
ProtectionLeaseDurationMinutes uint `json:"protectionLeaseDurationMinutes,omitempty"`
59-
ProtectionRenewalDurationMinutes uint `json:"protectionRenewalDurationMinutes,omitempty"`
54+
CloneDiffSize uint64 `json:"cloneDiffSize"`
55+
LogicalSize uint64 `json:"logicalSize"`
56+
CloningTime float64 `json:"cloningTime"`
57+
MaxIdleMinutes uint `json:"maxIdleMinutes"`
58+
ProtectionLeaseDurationMinutes uint `json:"protectionLeaseDurationMinutes,omitempty"`
59+
ProtectionMaxDurationMinutes uint `json:"protectionMaxDurationMinutes,omitempty"`
6060
}
6161

6262
// CloneView represents a view of clone model.

0 commit comments

Comments
 (0)