diff --git a/spinifex/daemon/daemon_handlers_instance.go b/spinifex/daemon/daemon_handlers_instance.go index 4af6d39e3..b0e16b5f4 100644 --- a/spinifex/daemon/daemon_handlers_instance.go +++ b/spinifex/daemon/daemon_handlers_instance.go @@ -1168,6 +1168,18 @@ func (d *Daemon) handleEC2ModifyInstanceAttribute(msg *nats.Msg) { instanceID := *input.InstanceId + // SourceDestCheck is a networking concept that doesn't apply to bare-metal VMs. + // Accept the call as a no-op so Terraform and the AWS CLI don't error out. + // Unlike InstanceType/UserData, AWS allows this on running instances, so handle + // it before the stopped-state gate. + if input.SourceDestCheck != nil { + slog.Info("handleEC2ModifyInstanceAttribute: accepting SourceDestCheck (no-op on bare metal)", "instanceId", instanceID) + if err := msg.Respond([]byte(`{}`)); err != nil { + slog.Error("Failed to respond to NATS request", "err", err) + } + return + } + if d.jsManager == nil { slog.Error("handleEC2ModifyInstanceAttribute: JetStream not available") respondWithError(msg, awserrors.ErrorServerInternal) diff --git a/spinifex/daemon/daemon_handlers_test.go b/spinifex/daemon/daemon_handlers_test.go index 2d64ae7b2..44200ee35 100644 --- a/spinifex/daemon/daemon_handlers_test.go +++ b/spinifex/daemon/daemon_handlers_test.go @@ -1736,6 +1736,27 @@ func TestHandleEC2ModifyInstanceAttribute_ChangeUserData(t *testing.T) { assert.Equal(t, "IyEvYmluL2Jhc2g=", *updated.RunInstancesInput.UserData) } +func TestHandleEC2ModifyInstanceAttribute_SourceDestCheck(t *testing.T) { + natsURL := sharedJSNATSURL + + daemon := createFullTestDaemonWithJetStream(t, natsURL) + + sub, err := daemon.natsConn.QueueSubscribe("ec2.ModifyInstanceAttribute", "spinifex-workers", daemon.handleEC2ModifyInstanceAttribute) + require.NoError(t, err) + defer sub.Unsubscribe() + + // SourceDestCheck is a no-op that succeeds without requiring a stopped instance + // in KV — Terraform sends this on running instances right after creation. + input := &ec2.ModifyInstanceAttributeInput{ + InstanceId: aws.String("i-modify-sdc-001"), + SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(false)}, + } + reqData, _ := json.Marshal(input) + reply, err := natsRequest(daemon.natsConn, "ec2.ModifyInstanceAttribute", reqData, 5*time.Second) + require.NoError(t, err) + assert.Equal(t, `{}`, string(reply.Data)) +} + func TestHandleEC2ModifyInstanceAttribute_InstanceNotFound(t *testing.T) { natsURL := sharedJSNATSURL diff --git a/spinifex/gateway/ec2/instance/ModifyInstanceAttribute.go b/spinifex/gateway/ec2/instance/ModifyInstanceAttribute.go index 81305766f..5dbed5072 100644 --- a/spinifex/gateway/ec2/instance/ModifyInstanceAttribute.go +++ b/spinifex/gateway/ec2/instance/ModifyInstanceAttribute.go @@ -35,6 +35,9 @@ func ValidateModifyInstanceAttributeInput(input *ec2.ModifyInstanceAttributeInpu if input.UserData != nil { count++ } + if input.SourceDestCheck != nil { + count++ + } if count != 1 { return errors.New(awserrors.ErrorInvalidParameterValue) } diff --git a/spinifex/gateway/ec2/instance/ModifyInstanceAttribute_test.go b/spinifex/gateway/ec2/instance/ModifyInstanceAttribute_test.go index b9686c180..192ffcf43 100644 --- a/spinifex/gateway/ec2/instance/ModifyInstanceAttribute_test.go +++ b/spinifex/gateway/ec2/instance/ModifyInstanceAttribute_test.go @@ -95,6 +95,24 @@ func TestValidateModifyInstanceAttributeInput_ValidUserData(t *testing.T) { assert.NoError(t, err) } +func TestValidateModifyInstanceAttributeInput_ValidSourceDestCheck(t *testing.T) { + err := ValidateModifyInstanceAttributeInput(&ec2.ModifyInstanceAttributeInput{ + InstanceId: aws.String("i-abc123"), + SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(true)}, + }) + assert.NoError(t, err) +} + +func TestValidateModifyInstanceAttributeInput_SourceDestCheckWithOtherAttribute(t *testing.T) { + err := ValidateModifyInstanceAttributeInput(&ec2.ModifyInstanceAttributeInput{ + InstanceId: aws.String("i-abc123"), + SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(false)}, + InstanceType: &ec2.AttributeValue{Value: aws.String("t3.micro")}, + }) + require.Error(t, err) + assert.Equal(t, awserrors.ErrorInvalidParameterValue, err.Error()) +} + // --- Gateway function tests --- func TestModifyInstanceAttribute_Success(t *testing.T) { @@ -117,6 +135,22 @@ func TestModifyInstanceAttribute_Success(t *testing.T) { assert.NoError(t, err) } +func TestModifyInstanceAttribute_SourceDestCheck(t *testing.T) { + _, nc := startTestNATSServer(t) + + nc.QueueSubscribe("ec2.ModifyInstanceAttribute", "spinifex-workers", func(msg *nats.Msg) { + msg.Respond([]byte(`{}`)) + }) + + input := &ec2.ModifyInstanceAttributeInput{ + InstanceId: aws.String("i-test123"), + SourceDestCheck: &ec2.AttributeBooleanValue{Value: aws.Bool(true)}, + } + + _, err := ModifyInstanceAttribute(input, nc, "123456789012") + assert.NoError(t, err) +} + func TestModifyInstanceAttribute_DaemonError(t *testing.T) { _, nc := startTestNATSServer(t)