Skip to content

Commit d301651

Browse files
committed
Address auto-standby review feedback
1 parent d2892cb commit d301651

5 files changed

Lines changed: 120 additions & 7 deletions

File tree

cmd/api/api/auto_standby.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func toDomainAutoStandbyPolicy(policy *oapi.AutoStandbyPolicy) (*autostandby.Pol
2626
if policy.IgnoreDestinationPorts != nil {
2727
out.IgnoreDestinationPorts = make([]uint16, 0, len(*policy.IgnoreDestinationPorts))
2828
for _, port := range *policy.IgnoreDestinationPorts {
29-
if port < 0 || port > 65535 {
29+
if port < 1 || port > 65535 {
3030
return nil, fmt.Errorf("auto_standby.ignore_destination_ports must be between 1 and 65535")
3131
}
3232
out.IgnoreDestinationPorts = append(out.IgnoreDestinationPorts, uint16(port))

cmd/api/api/instances_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,48 @@ func TestUpdateInstance_MapsAutoStandbyPatch(t *testing.T) {
636636
assert.True(t, *instance.AutoStandby.Enabled)
637637
}
638638

639+
func TestUpdateInstance_RejectsZeroAutoStandbyIgnoreDestinationPort(t *testing.T) {
640+
t.Parallel()
641+
svc := newTestService(t)
642+
643+
origMgr := svc.InstanceManager
644+
now := time.Now()
645+
mockMgr := &captureUpdateManager{Manager: origMgr}
646+
svc.InstanceManager = mockMgr
647+
648+
resolved := &instances.Instance{
649+
StoredMetadata: instances.StoredMetadata{
650+
Id: "inst-update-auto-standby",
651+
Name: "inst-update-auto-standby",
652+
Image: "docker.io/library/alpine:latest",
653+
CreatedAt: now,
654+
HypervisorType: hypervisor.TypeCloudHypervisor,
655+
},
656+
State: instances.StateStopped,
657+
}
658+
enabled := true
659+
idleTimeout := "10m"
660+
ignoreDestinationPorts := []int{0}
661+
662+
resp, err := svc.UpdateInstance(mw.WithResolvedInstance(ctx(), resolved.Id, resolved), oapi.UpdateInstanceRequestObject{
663+
Id: resolved.Id,
664+
Body: &oapi.UpdateInstanceRequest{
665+
AutoStandby: &oapi.AutoStandbyPolicy{
666+
Enabled: &enabled,
667+
IdleTimeout: &idleTimeout,
668+
IgnoreDestinationPorts: &ignoreDestinationPorts,
669+
},
670+
},
671+
})
672+
require.NoError(t, err)
673+
674+
badReq, ok := resp.(oapi.UpdateInstance400JSONResponse)
675+
require.True(t, ok, "expected 400 response")
676+
assert.Equal(t, "invalid_auto_standby", badReq.Code)
677+
assert.Contains(t, badReq.Message, "between 1 and 65535")
678+
assert.Nil(t, mockMgr.lastReq)
679+
}
680+
639681
func TestUpdateInstance_RequiresBody(t *testing.T) {
640682
t.Parallel()
641683
svc := newTestService(t)

lib/instances/metadata_clone.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package instances
2+
3+
func cloneMetadata(src *metadata) *metadata {
4+
if src == nil {
5+
return nil
6+
}
7+
8+
return &metadata{
9+
StoredMetadata: cloneStoredMetadataForFork(src.StoredMetadata),
10+
}
11+
}

lib/instances/update.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@ func (m *manager) updateInstance(ctx context.Context, id string, req UpdateInsta
4343
if len(req.Env) > 0 && inst.State != StateRunning && inst.State != StateInitializing {
4444
return nil, fmt.Errorf("%w: instance must be running or initializing to update env (current state: %s)", ErrInvalidState, inst.State)
4545
}
46+
nextMeta := meta
4647
if req.AutoStandby != nil {
47-
meta.AutoStandby = cloneAutoStandbyPolicy(req.AutoStandby)
48+
nextMeta = cloneMetadata(meta)
49+
nextMeta.AutoStandby = cloneAutoStandbyPolicy(req.AutoStandby)
4850
}
4951
if len(req.Env) == 0 {
50-
if err := m.saveMetadata(meta); err != nil {
52+
if err := m.saveMetadata(nextMeta); err != nil {
5153
return nil, fmt.Errorf("save metadata: %w", err)
5254
}
5355

@@ -60,16 +62,16 @@ func (m *manager) updateInstance(ctx context.Context, id string, req UpdateInsta
6062
return updated, nil
6163
}
6264

63-
prevEnv := cloneEnvMap(meta.Env)
64-
nextEnv := cloneEnvMap(meta.Env)
65+
prevEnv := cloneEnvMap(nextMeta.Env)
66+
nextEnv := cloneEnvMap(nextMeta.Env)
6567
if nextEnv == nil {
6668
nextEnv = make(map[string]string)
6769
}
6870
for k, v := range req.Env {
6971
nextEnv[k] = v
7072
}
7173

72-
if err := validateCredentialEnvBindings(meta.Credentials, nextEnv); err != nil {
74+
if err := validateCredentialEnvBindings(nextMeta.Credentials, nextEnv); err != nil {
7375
return nil, err
7476
}
7577

@@ -79,7 +81,7 @@ func (m *manager) updateInstance(ctx context.Context, id string, req UpdateInsta
7981
return nil, fmt.Errorf("egress proxy service unavailable")
8082
}
8183

82-
if err := applyUpdatedInstanceEnv(ctx, log, id, meta, prevEnv, nextEnv, m.saveMetadata, svc); err != nil {
84+
if err := applyUpdatedInstanceEnv(ctx, log, id, nextMeta, prevEnv, nextEnv, m.saveMetadata, svc); err != nil {
8385
return nil, err
8486
}
8587

lib/instances/update_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,61 @@ func TestApplyUpdatedInstanceEnvReturnsRollbackFailure(t *testing.T) {
181181
assert.Equal(t, prevEnv, meta.Env)
182182
require.Len(t, svc.calls, 2)
183183
}
184+
185+
func TestApplyUpdatedInstanceEnvSavesAutoStandbyAlongsideEnvWithoutMutatingOriginal(t *testing.T) {
186+
t.Parallel()
187+
188+
original := &metadata{
189+
StoredMetadata: StoredMetadata{
190+
Id: "inst-autostandby-copy",
191+
NetworkEgress: &NetworkEgressPolicy{Enabled: true},
192+
Credentials: map[string]CredentialPolicy{
193+
"OUTBOUND_OPENAI_KEY": {
194+
Source: CredentialSource{Env: "OUTBOUND_OPENAI_KEY"},
195+
Inject: []CredentialInjectRule{{
196+
As: CredentialInjectAs{
197+
Header: "Authorization",
198+
Format: "Bearer ${value}",
199+
},
200+
}},
201+
},
202+
},
203+
Env: map[string]string{"OUTBOUND_OPENAI_KEY": "old"},
204+
AutoStandby: &autostandby.Policy{
205+
Enabled: false,
206+
IdleTimeout: "5m0s",
207+
},
208+
},
209+
}
210+
updated := cloneMetadata(original)
211+
updated.AutoStandby = &autostandby.Policy{
212+
Enabled: true,
213+
IdleTimeout: "10m0s",
214+
IgnoreSourceCIDRs: []string{"10.0.0.0/8"},
215+
IgnoreDestinationPorts: []uint16{22},
216+
}
217+
218+
prevEnv := cloneEnvMap(updated.Env)
219+
nextEnv := map[string]string{"OUTBOUND_OPENAI_KEY": "new"}
220+
svc := &fakeUpdateInstanceRulesService{}
221+
222+
var saved *metadata
223+
err := applyUpdatedInstanceEnv(context.Background(), nil, updated.Id, updated, prevEnv, nextEnv, func(meta *metadata) error {
224+
saved = cloneMetadata(meta)
225+
return nil
226+
}, svc)
227+
require.NoError(t, err)
228+
229+
require.NotNil(t, saved)
230+
require.NotNil(t, saved.AutoStandby)
231+
assert.True(t, saved.AutoStandby.Enabled)
232+
assert.Equal(t, "10m0s", saved.AutoStandby.IdleTimeout)
233+
assert.Equal(t, []string{"10.0.0.0/8"}, saved.AutoStandby.IgnoreSourceCIDRs)
234+
assert.Equal(t, []uint16{22}, saved.AutoStandby.IgnoreDestinationPorts)
235+
assert.Equal(t, nextEnv, saved.Env)
236+
237+
require.NotNil(t, original.AutoStandby)
238+
assert.False(t, original.AutoStandby.Enabled)
239+
assert.Equal(t, "5m0s", original.AutoStandby.IdleTimeout)
240+
assert.Equal(t, map[string]string{"OUTBOUND_OPENAI_KEY": "old"}, original.Env)
241+
}

0 commit comments

Comments
 (0)