Skip to content

Commit 19a9668

Browse files
committed
Merge branch 'claude/add-protection-lease-J8jl0' into 'master'
feat: add protection lease concept for time-limited clone protection Closes #669 See merge request postgres-ai/database-lab!1088
2 parents e62eeda + 75e09d2 commit 19a9668

29 files changed

Lines changed: 749 additions & 99 deletions

engine/api/swagger-spec/dblab_openapi.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,16 @@ components:
15161516
type: array
15171517
items:
15181518
$ref: '#/components/schemas/Clone'
1519+
protectionLeaseDurationMinutes:
1520+
type: integer
1521+
format: int64
1522+
minimum: 0
1523+
description: Default protection lease duration in minutes
1524+
protectionMaxDurationMinutes:
1525+
type: integer
1526+
format: int64
1527+
minimum: 0
1528+
description: Maximum allowed protection duration in minutes
15191529
Retrieving:
15201530
type: object
15211531
properties:
@@ -1631,6 +1641,10 @@ components:
16311641
protected:
16321642
type: boolean
16331643
default: false
1644+
protectedTill:
1645+
type: string
1646+
format: date-time
1647+
description: Time when the protection lease expires
16341648
deleteAt:
16351649
type: string
16361650
format: date-time
@@ -1658,6 +1672,16 @@ components:
16581672
maxIdleMinutes:
16591673
type: integer
16601674
format: int64
1675+
protectionLeaseDurationMinutes:
1676+
type: integer
1677+
format: int64
1678+
minimum: 0
1679+
description: Protection lease duration in minutes
1680+
protectionMaxDurationMinutes:
1681+
type: integer
1682+
format: int64
1683+
minimum: 0
1684+
description: Maximum allowed protection duration in minutes
16611685
CreateClone:
16621686
type: object
16631687
properties:
@@ -1673,6 +1697,11 @@ components:
16731697
protected:
16741698
type: boolean
16751699
default:
1700+
protectionDurationMinutes:
1701+
type: integer
1702+
format: int64
1703+
minimum: 0
1704+
description: Protection duration in minutes. 0 means forever, omit for default duration.
16761705
db:
16771706
type: object
16781707
properties:
@@ -1703,6 +1732,11 @@ components:
17031732
protected:
17041733
type: boolean
17051734
default: false
1735+
protectionDurationMinutes:
1736+
type: integer
1737+
format: int64
1738+
minimum: 0
1739+
description: Protection duration in minutes. 0 means forever, omit for default duration.
17061740
StartObservationRequest:
17071741
type: object
17081742
properties:

engine/api/swagger-spec/dblab_server_swagger.yaml

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

951961
Retrieving:
952962
type: "object"
@@ -1065,6 +1075,10 @@ components:
10651075
protected:
10661076
type: "boolean"
10671077
default: false
1078+
protectedTill:
1079+
type: "string"
1080+
format: "date-time"
1081+
description: "Time when the protection lease expires"
10681082
deleteAt:
10691083
type: "string"
10701084
format: "date-time"
@@ -1093,6 +1107,16 @@ components:
10931107
maxIdleMinutes:
10941108
type: "integer"
10951109
format: "int64"
1110+
protectionLeaseDurationMinutes:
1111+
type: "integer"
1112+
format: "int64"
1113+
minimum: 0
1114+
description: "Protection lease duration in minutes"
1115+
protectionMaxDurationMinutes:
1116+
type: "integer"
1117+
format: "int64"
1118+
minimum: 0
1119+
description: "Maximum allowed protection duration in minutes"
10961120

10971121
CreateClone:
10981122
type: "object"
@@ -1107,6 +1131,11 @@ components:
11071131
protected:
11081132
type: "boolean"
11091133
default: false
1134+
protectionDurationMinutes:
1135+
type: "integer"
1136+
format: "int64"
1137+
minimum: 0
1138+
description: "Protection duration in minutes. 0 means forever, omit for default duration."
11101139
db:
11111140
type: "object"
11121141
properties:
@@ -1136,6 +1165,11 @@ components:
11361165
protected:
11371166
type: "boolean"
11381167
default: false
1168+
protectionDurationMinutes:
1169+
type: "integer"
1170+
format: "int64"
1171+
minimum: 0
1172+
description: "Protection duration in minutes. 0 means forever, omit for default duration."
11391173

11401174
StartObservationRequest:
11411175
type: "object"

engine/cmd/cli/commands/clone/actions.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net/url"
1313
"os"
1414
"path"
15+
"strconv"
1516
"strings"
1617
"sync"
1718

@@ -25,6 +26,33 @@ import (
2526
"gitlab.com/postgres-ai/database-lab/v3/pkg/models"
2627
)
2728

29+
// parseProtectedFlag parses the --protected flag value.
30+
// Returns (isProtected, durationMinutes, error).
31+
// durationMinutes is nil if default duration should be used.
32+
func parseProtectedFlag(cliCtx *cli.Context) (bool, *uint, error) {
33+
if !cliCtx.IsSet("protected") {
34+
return false, nil, nil
35+
}
36+
37+
value := cliCtx.String("protected")
38+
39+
switch strings.ToLower(value) {
40+
case "", "true":
41+
return true, nil, nil
42+
case "false":
43+
return false, nil, nil
44+
default:
45+
duration, err := strconv.ParseUint(value, 10, 32)
46+
if err != nil {
47+
return false, nil, errors.Errorf("invalid --protected value: %q (use 'true', 'false', or minutes)", value)
48+
}
49+
50+
d := uint(duration)
51+
52+
return true, &d, nil
53+
}
54+
}
55+
2856
// list runs a request to list clones of an instance.
2957
func list(cliCtx *cli.Context) error {
3058
dblabClient, err := commands.ClientByCLIContext(cliCtx)
@@ -96,9 +124,15 @@ func create(cliCtx *cli.Context) error {
96124
return err
97125
}
98126

127+
isProtected, protectionDuration, err := parseProtectedFlag(cliCtx)
128+
if err != nil {
129+
return err
130+
}
131+
99132
cloneRequest := types.CloneCreateRequest{
100-
ID: cliCtx.String("id"),
101-
Protected: cliCtx.Bool("protected"),
133+
ID: cliCtx.String("id"),
134+
Protected: isProtected,
135+
ProtectionDurationMinutes: protectionDuration,
102136
DB: &types.DatabaseRequest{
103137
Username: cliCtx.String("username"),
104138
Password: cliCtx.String("password"),
@@ -184,8 +218,14 @@ func update(cliCtx *cli.Context) error {
184218
return err
185219
}
186220

221+
isProtected, protectionDuration, err := parseProtectedFlag(cliCtx)
222+
if err != nil {
223+
return err
224+
}
225+
187226
updateRequest := types.CloneUpdateRequest{
188-
Protected: cliCtx.Bool("protected"),
227+
Protected: isProtected,
228+
ProtectionDurationMinutes: protectionDuration,
189229
}
190230

191231
cloneID := cliCtx.Args().First()

engine/cmd/cli/commands/clone/command_list.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ func CommandList() []*cli.Command {
6868
Name: "branch",
6969
Usage: "branch name (optional)",
7070
},
71-
&cli.BoolFlag{
71+
&cli.StringFlag{
7272
Name: "protected",
73-
Usage: "mark instance as protected from deletion",
73+
Usage: "enable deletion protection: 'true' for default duration, minutes for custom, 0=forever",
7474
Aliases: []string{"p"},
7575
},
7676
&cli.BoolFlag{
@@ -91,9 +91,9 @@ func CommandList() []*cli.Command {
9191
Before: checkCloneIDBefore,
9292
Action: update,
9393
Flags: []cli.Flag{
94-
&cli.BoolFlag{
94+
&cli.StringFlag{
9595
Name: "protected",
96-
Usage: "mark instance as protected from deletion",
96+
Usage: "deletion protection: 'true' for default, minutes for custom, 0=forever, 'false' to disable",
9797
Aliases: []string{"p"},
9898
},
9999
},

engine/configs/config.example.logical_generic.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ 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: 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
151+
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
149152

150153
diagnostic:
151154
logsRetentionDays: 7 # How many days to keep logs

engine/configs/config.example.logical_rds_iam.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ 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: 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
151+
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
149152

150153
diagnostic:
151154
logsRetentionDays: 7 # How many days to keep logs

engine/configs/config.example.physical_generic.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ 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: 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
119+
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
117120

118121
diagnostic:
119122
logsRetentionDays: 7 # How many days to keep logs

engine/configs/config.example.physical_pgbackrest.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ 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: 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
149+
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
147150

148151
diagnostic:
149152
logsRetentionDays: 7 # How many days to keep logs

engine/configs/config.example.physical_walg.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ 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: 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
122+
protectionExpiryWarningMinutes: 1440 # Send warning webhook N minutes before expiry (default: 24 hours)
120123

121124
diagnostic:
122125
logsRetentionDays: 7 # How many days to keep logs

0 commit comments

Comments
 (0)