@@ -134,6 +134,24 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
134134 resourceTags = toMapTags (request .Body .Tags )
135135 }
136136
137+ if request .Body .Ttl != nil && request .Body .ExpiresAt != nil {
138+ return oapi.CreateInstance400JSONResponse {
139+ Code : "invalid_request" ,
140+ Message : "ttl and expires_at are mutually exclusive" ,
141+ }, nil
142+ }
143+ var ttl time.Duration
144+ if request .Body .Ttl != nil {
145+ parsedTTL , err := parseTTL (* request .Body .Ttl )
146+ if err != nil {
147+ return oapi.CreateInstance400JSONResponse {
148+ Code : "invalid_ttl" ,
149+ Message : err .Error (),
150+ }, nil
151+ }
152+ ttl = parsedTTL
153+ }
154+
137155 // Parse network enabled (default: true)
138156 networkEnabled := true
139157 if request .Body .Network != nil && request .Body .Network .Enabled != nil {
@@ -314,6 +332,8 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
314332 NetworkBandwidthUpload : networkBandwidthUpload ,
315333 Env : env ,
316334 Tags : resourceTags ,
335+ TTL : ttl ,
336+ ExpiresAt : request .Body .ExpiresAt ,
317337 NetworkEnabled : networkEnabled ,
318338 NetworkEgress : networkEgress ,
319339 Credentials : credentials ,
@@ -363,6 +383,11 @@ func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInst
363383 Code : "insufficient_resources" ,
364384 Message : err .Error (),
365385 }, nil
386+ case errors .Is (err , instances .ErrInvalidExpiresAt ):
387+ return oapi.CreateInstance400JSONResponse {
388+ Code : "invalid_expires_at" ,
389+ Message : err .Error (),
390+ }, nil
366391 case errors .Is (err , instances .ErrInvalidRequest ):
367392 return oapi.CreateInstance400JSONResponse {
368393 Code : "invalid_request" ,
@@ -986,9 +1011,8 @@ func (s *ApiService) StatInstancePath(ctx context.Context, request oapi.StatInst
9861011 return response , nil
9871012}
9881013
989- // UpdateInstance updates mutable properties of a running instance.
990- // Currently supports updating env vars referenced by credential policies for key rotation.
991- // Note: Resolution is handled by ResolveResource middleware
1014+ // UpdateInstance updates mutable instance properties.
1015+ // Note: Resolution is handled by ResolveResource middleware.
9921016func (s * ApiService ) UpdateInstance (ctx context.Context , request oapi.UpdateInstanceRequestObject ) (oapi.UpdateInstanceResponseObject , error ) {
9931017 inst := mw.GetResolvedInstance [instances.Instance ](ctx )
9941018 if inst == nil {
@@ -1031,13 +1055,32 @@ func (s *ApiService) UpdateInstance(ctx context.Context, request oapi.UpdateInst
10311055 Message : err .Error (),
10321056 }, nil
10331057 }
1058+ if request .Body .Ttl != nil && request .Body .ExpiresAt != nil {
1059+ return oapi.UpdateInstance400JSONResponse {
1060+ Code : "invalid_request" ,
1061+ Message : "ttl and expires_at are mutually exclusive" ,
1062+ }, nil
1063+ }
1064+ var ttl * time.Duration
1065+ if request .Body .Ttl != nil {
1066+ parsedTTL , err := parseTTL (* request .Body .Ttl )
1067+ if err != nil {
1068+ return oapi.UpdateInstance400JSONResponse {
1069+ Code : "invalid_ttl" ,
1070+ Message : err .Error (),
1071+ }, nil
1072+ }
1073+ ttl = & parsedTTL
1074+ }
10341075
10351076 result , err := s .InstanceManager .UpdateInstance (ctx , inst .Id , instances.UpdateInstanceRequest {
10361077 Env : env ,
10371078 AutoStandby : autoStandby ,
10381079 HealthCheck : healthCheck ,
10391080 RestartPolicy : restartPolicy ,
10401081 RestartPolicySet : request .Body .RestartPolicy != nil ,
1082+ TTL : ttl ,
1083+ ExpiresAt : request .Body .ExpiresAt ,
10411084 })
10421085 if err != nil {
10431086 switch {
@@ -1051,6 +1094,16 @@ func (s *ApiService) UpdateInstance(ctx context.Context, request oapi.UpdateInst
10511094 Code : "invalid_state" ,
10521095 Message : err .Error (),
10531096 }, nil
1097+ case errors .Is (err , instances .ErrInstanceExpired ):
1098+ return oapi.UpdateInstance409JSONResponse {
1099+ Code : "instance_expired" ,
1100+ Message : err .Error (),
1101+ }, nil
1102+ case errors .Is (err , instances .ErrInvalidExpiresAt ):
1103+ return oapi.UpdateInstance400JSONResponse {
1104+ Code : "invalid_expires_at" ,
1105+ Message : err .Error (),
1106+ }, nil
10541107 case errors .Is (err , instances .ErrInvalidRequest ):
10551108 return oapi.UpdateInstance400JSONResponse {
10561109 Code : "invalid_request" ,
@@ -1142,6 +1195,7 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance {
11421195 DiskIoBps : diskIoBpsStr ,
11431196 Network : nil ,
11441197 CreatedAt : inst .CreatedAt ,
1198+ ExpiresAt : inst .ExpiresAt ,
11451199 StartedAt : inst .StartedAt ,
11461200 StoppedAt : inst .StoppedAt ,
11471201 ExitCode : inst .ExitCode ,
@@ -1155,7 +1209,6 @@ func instanceToOAPI(inst instances.Instance) oapi.Instance {
11551209 if inst .Platform != "" {
11561210 oapiInst .Platform = lo .ToPtr (inst .Platform )
11571211 }
1158-
11591212 if inst .ExitMessage != "" {
11601213 oapiInst .ExitMessage = lo .ToPtr (inst .ExitMessage )
11611214 }
@@ -1308,6 +1361,17 @@ func toOAPISnapshotPolicy(policy instances.SnapshotPolicy) oapi.SnapshotPolicy {
13081361 return out
13091362}
13101363
1364+ func parseTTL (value string ) (time.Duration , error ) {
1365+ ttl , err := time .ParseDuration (value )
1366+ if err != nil {
1367+ return 0 , fmt .Errorf ("ttl must be a valid duration: %w" , err )
1368+ }
1369+ if ttl < 0 {
1370+ return 0 , fmt .Errorf ("ttl cannot be negative" )
1371+ }
1372+ return ttl , nil
1373+ }
1374+
13111375func parseOptionalDuration (value string , field string ) (* time.Duration , error ) {
13121376 duration , err := time .ParseDuration (value )
13131377 if err != nil {
0 commit comments