Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions cfn-resources/search-deployment/cmd/resource/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

package resource

import admin20231115014 "go.mongodb.org/atlas-sdk/v20231115014/admin"
import "go.mongodb.org/atlas-sdk/v20250312012/admin"

func NewCFNSearchDeployment(prevModel *Model, apiResp *admin20231115014.ApiSearchDeploymentResponse) Model {
func NewCFNSearchDeployment(prevModel *Model, apiResp *admin.ApiSearchDeploymentResponse) Model {
respSpecs := apiResp.GetSpecs()
resultSpecs := make([]ApiSearchDeploymentSpec, len(respSpecs))
for i := range respSpecs {
Expand All @@ -25,25 +25,27 @@ func NewCFNSearchDeployment(prevModel *Model, apiResp *admin20231115014.ApiSearc
NodeCount: &respSpecs[i].NodeCount,
}
}

return Model{
Profile: prevModel.Profile,
ClusterName: prevModel.ClusterName,
ProjectId: prevModel.ProjectId,
Id: apiResp.Id,
Specs: resultSpecs,
StateName: apiResp.StateName,
Profile: prevModel.Profile,
ClusterName: prevModel.ClusterName,
ProjectId: prevModel.ProjectId,
Id: apiResp.Id,
Specs: resultSpecs,
StateName: apiResp.StateName,
EncryptionAtRestProvider: apiResp.EncryptionAtRestProvider,
}
}

func NewSearchDeploymentReq(model *Model) admin20231115014.ApiSearchDeploymentRequest {
func NewSearchDeploymentReq(model *Model) admin.ApiSearchDeploymentRequest {
modelSpecs := model.Specs
requestSpecs := make([]admin20231115014.ApiSearchDeploymentSpec, len(modelSpecs))
requestSpecs := make([]admin.ApiSearchDeploymentSpec, len(modelSpecs))
for i, spec := range modelSpecs {
// Both spec fields are required in CFN model and will be defined
requestSpecs[i] = admin20231115014.ApiSearchDeploymentSpec{
requestSpecs[i] = admin.ApiSearchDeploymentSpec{
InstanceSize: *spec.InstanceSize,
NodeCount: *spec.NodeCount,
}
}
return admin20231115014.ApiSearchDeploymentRequest{Specs: requestSpecs}
return admin.ApiSearchDeploymentRequest{Specs: requestSpecs}
}
94 changes: 47 additions & 47 deletions cfn-resources/search-deployment/cmd/resource/mappings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import (

"github.com/mongodb/mongodbatlas-cloudformation-resources/search-deployment/cmd/resource"
"github.com/stretchr/testify/assert"
admin20231115014 "go.mongodb.org/atlas-sdk/v20231115014/admin"
"go.mongodb.org/atlas-sdk/v20250312012/admin"
)

type sdkToCFNModelTestCase struct {
prevModel resource.Model
expectedModel resource.Model
SDKResp admin20231115014.ApiSearchDeploymentResponse
SDKResp admin.ApiSearchDeploymentResponse
name string
}

Expand All @@ -44,54 +44,54 @@ func TestSDKToCFNModel(t *testing.T) {
{
name: "Complete SDK response",
prevModel: resource.Model{
Profile: admin20231115014.PtrString(profile),
ClusterName: admin20231115014.PtrString(clusterName),
ProjectId: admin20231115014.PtrString(dummyProjectID),
Profile: admin.PtrString(profile),
ClusterName: admin.PtrString(clusterName),
ProjectId: admin.PtrString(dummyProjectID),
},
SDKResp: admin20231115014.ApiSearchDeploymentResponse{
Id: admin20231115014.PtrString(dummyDeploymentID),
GroupId: admin20231115014.PtrString(dummyProjectID),
StateName: admin20231115014.PtrString(stateName),
Specs: &[]admin20231115014.ApiSearchDeploymentSpec{
SDKResp: admin.ApiSearchDeploymentResponse{
Id: admin.PtrString(dummyDeploymentID),
GroupId: admin.PtrString(dummyProjectID),
StateName: admin.PtrString(stateName),
Specs: &[]admin.ApiSearchDeploymentSpec{
{
InstanceSize: instanceSize,
NodeCount: nodeCount,
},
},
},
expectedModel: resource.Model{
Profile: admin20231115014.PtrString(profile),
ClusterName: admin20231115014.PtrString(clusterName),
ProjectId: admin20231115014.PtrString(dummyProjectID),
Id: admin20231115014.PtrString(dummyDeploymentID),
StateName: admin20231115014.PtrString(stateName),
Profile: admin.PtrString(profile),
ClusterName: admin.PtrString(clusterName),
ProjectId: admin.PtrString(dummyProjectID),
Id: admin.PtrString(dummyDeploymentID),
StateName: admin.PtrString(stateName),
Specs: []resource.ApiSearchDeploymentSpec{
{
InstanceSize: admin20231115014.PtrString(instanceSize),
NodeCount: admin20231115014.PtrInt(nodeCount),
InstanceSize: admin.PtrString(instanceSize),
NodeCount: admin.PtrInt(nodeCount),
},
},
},
},
{
name: "Empty specs array",
prevModel: resource.Model{
Profile: admin20231115014.PtrString(profile),
ClusterName: admin20231115014.PtrString(clusterName),
ProjectId: admin20231115014.PtrString(dummyProjectID),
Profile: admin.PtrString(profile),
ClusterName: admin.PtrString(clusterName),
ProjectId: admin.PtrString(dummyProjectID),
},
SDKResp: admin20231115014.ApiSearchDeploymentResponse{
Id: admin20231115014.PtrString(dummyDeploymentID),
GroupId: admin20231115014.PtrString(dummyProjectID),
StateName: admin20231115014.PtrString(stateName),
Specs: &[]admin20231115014.ApiSearchDeploymentSpec{},
SDKResp: admin.ApiSearchDeploymentResponse{
Id: admin.PtrString(dummyDeploymentID),
GroupId: admin.PtrString(dummyProjectID),
StateName: admin.PtrString(stateName),
Specs: &[]admin.ApiSearchDeploymentSpec{},
},
expectedModel: resource.Model{
Profile: admin20231115014.PtrString(profile),
ClusterName: admin20231115014.PtrString(clusterName),
ProjectId: admin20231115014.PtrString(dummyProjectID),
Id: admin20231115014.PtrString(dummyDeploymentID),
StateName: admin20231115014.PtrString(stateName),
Profile: admin.PtrString(profile),
ClusterName: admin.PtrString(clusterName),
ProjectId: admin.PtrString(dummyProjectID),
Id: admin.PtrString(dummyDeploymentID),
StateName: admin.PtrString(stateName),
Specs: []resource.ApiSearchDeploymentSpec{},
},
},
Expand All @@ -109,25 +109,25 @@ func TestCFNModelToSDK(t *testing.T) {
testCases := []struct {
model resource.Model
name string
expectedSDKReq admin20231115014.ApiSearchDeploymentRequest
expectedSDKReq admin.ApiSearchDeploymentRequest
}{
{
name: "Complete CFN model",
model: resource.Model{
Profile: admin20231115014.PtrString(profile),
ClusterName: admin20231115014.PtrString(clusterName),
ProjectId: admin20231115014.PtrString(dummyProjectID),
Id: admin20231115014.PtrString(dummyDeploymentID),
StateName: admin20231115014.PtrString(stateName),
Profile: admin.PtrString(profile),
ClusterName: admin.PtrString(clusterName),
ProjectId: admin.PtrString(dummyProjectID),
Id: admin.PtrString(dummyDeploymentID),
StateName: admin.PtrString(stateName),
Specs: []resource.ApiSearchDeploymentSpec{
{
InstanceSize: admin20231115014.PtrString(instanceSize),
NodeCount: admin20231115014.PtrInt(nodeCount),
InstanceSize: admin.PtrString(instanceSize),
NodeCount: admin.PtrInt(nodeCount),
},
},
},
expectedSDKReq: admin20231115014.ApiSearchDeploymentRequest{
Specs: []admin20231115014.ApiSearchDeploymentSpec{
expectedSDKReq: admin.ApiSearchDeploymentRequest{
Specs: []admin.ApiSearchDeploymentSpec{
{
InstanceSize: instanceSize,
NodeCount: nodeCount,
Expand All @@ -138,15 +138,15 @@ func TestCFNModelToSDK(t *testing.T) {
{
name: "Empty specs array",
model: resource.Model{
Profile: admin20231115014.PtrString(profile),
ClusterName: admin20231115014.PtrString(clusterName),
ProjectId: admin20231115014.PtrString(dummyProjectID),
Id: admin20231115014.PtrString(dummyDeploymentID),
StateName: admin20231115014.PtrString(stateName),
Profile: admin.PtrString(profile),
ClusterName: admin.PtrString(clusterName),
ProjectId: admin.PtrString(dummyProjectID),
Id: admin.PtrString(dummyDeploymentID),
StateName: admin.PtrString(stateName),
Specs: []resource.ApiSearchDeploymentSpec{},
},
expectedSDKReq: admin20231115014.ApiSearchDeploymentRequest{
Specs: []admin20231115014.ApiSearchDeploymentSpec{},
expectedSDKReq: admin.ApiSearchDeploymentRequest{
Specs: []admin.ApiSearchDeploymentSpec{},
},
},
}
Expand Down
13 changes: 7 additions & 6 deletions cfn-resources/search-deployment/cmd/resource/model.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 33 additions & 12 deletions cfn-resources/search-deployment/cmd/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"net/http"
"strings"

admin20231115014 "go.mongodb.org/atlas-sdk/v20231115014/admin"
"go.mongodb.org/atlas-sdk/v20250312012/admin"

"github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler"
"github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
Expand Down Expand Up @@ -58,7 +58,7 @@ func Create(req handler.Request, prevModel *Model, currentModel *Model) (handler
if progressErr != nil {
return *progressErr, nil
}
connV2 := client.Atlas20231115014
connV2 := client.AtlasSDK

// handling of subsequent retry calls
if _, ok := req.CallbackContext[constants.ID]; ok {
Expand All @@ -68,7 +68,7 @@ func Create(req handler.Request, prevModel *Model, currentModel *Model) (handler
projectID := util.SafeString(currentModel.ProjectId)
clusterName := util.SafeString(currentModel.ClusterName)
apiReq := NewSearchDeploymentReq(currentModel)
apiResp, resp, err := connV2.AtlasSearchApi.CreateAtlasSearchDeployment(context.Background(), projectID, clusterName, &apiReq).Execute()
apiResp, resp, err := connV2.AtlasSearchApi.CreateClusterSearchDeployment(context.Background(), projectID, clusterName, &apiReq).Execute()
if err != nil {
return handleError(resp, err)
}
Expand All @@ -89,18 +89,29 @@ func Read(req handler.Request, prevModel *Model, currentModel *Model) (handler.P
if progressErr != nil {
return *progressErr, nil
}
connV2 := client.Atlas20231115014
connV2 := client.AtlasSDK

projectID := util.SafeString(currentModel.ProjectId)
clusterName := util.SafeString(currentModel.ClusterName)
apiResp, resp, err := connV2.AtlasSearchApi.GetAtlasSearchDeployment(context.Background(), projectID, clusterName).Execute()
apiResp, resp, err := connV2.AtlasSearchApi.GetClusterSearchDeployment(context.Background(), projectID, clusterName).Execute()
if err != nil {
return handleError(resp, err)
}

newModel := NewCFNSearchDeployment(currentModel, apiResp)

// If Specs is empty, the search deployment has been deleted
// Return NotFound instead of Success
if len(newModel.Specs) == 0 {
return handler.ProgressEvent{
OperationStatus: handler.Failed,
Message: "Resource not found",
HandlerErrorCode: string(types.HandlerErrorCodeNotFound)}, nil
}

return handler.ProgressEvent{
OperationStatus: handler.Success,
ResourceModel: NewCFNSearchDeployment(currentModel, apiResp),
ResourceModel: newModel,
}, nil
}

Expand All @@ -116,7 +127,7 @@ func Update(req handler.Request, prevModel *Model, currentModel *Model) (handler
if progressErr != nil {
return *progressErr, nil
}
connV2 := client.Atlas20231115014
connV2 := client.AtlasSDK

// handling of subsequent retry calls
if _, ok := req.CallbackContext[constants.ID]; ok {
Expand All @@ -126,12 +137,22 @@ func Update(req handler.Request, prevModel *Model, currentModel *Model) (handler
projectID := util.SafeString(currentModel.ProjectId)
clusterName := util.SafeString(currentModel.ClusterName)
apiReq := NewSearchDeploymentReq(currentModel)
apiResp, res, err := connV2.AtlasSearchApi.UpdateAtlasSearchDeployment(context.Background(), projectID, clusterName, &apiReq).Execute()
apiResp, res, err := connV2.AtlasSearchApi.UpdateClusterSearchDeployment(context.Background(), projectID, clusterName, &apiReq).Execute()
if err != nil {
// Update should return NotFound if resource doesn't exist - this is already handled by handleError
return handleError(res, err)
}

newModel := NewCFNSearchDeployment(currentModel, apiResp)

// If Specs is empty, the search deployment has been deleted - return NotFound
if len(newModel.Specs) == 0 {
return handler.ProgressEvent{
OperationStatus: handler.Failed,
Message: "Resource not found",
HandlerErrorCode: string(types.HandlerErrorCodeNotFound)}, nil
}

return inProgressEvent("Updating Search Deployment", &newModel), nil
}

Expand All @@ -147,7 +168,7 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler
if progressErr != nil {
return *progressErr, nil
}
connV2 := client.Atlas20231115014
connV2 := client.AtlasSDK

// handling of subsequent retry calls
if _, ok := req.CallbackContext[constants.ID]; ok {
Expand All @@ -156,7 +177,7 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler

projectID := util.SafeString(currentModel.ProjectId)
clusterName := util.SafeString(currentModel.ClusterName)
if resp, err := connV2.AtlasSearchApi.DeleteAtlasSearchDeployment(context.Background(), projectID, clusterName).Execute(); err != nil {
if resp, err := connV2.AtlasSearchApi.DeleteClusterSearchDeployment(context.Background(), projectID, clusterName).Execute(); err != nil {
return handleError(resp, err)
}

Expand All @@ -169,13 +190,13 @@ func List(req handler.Request, prevModel *Model, currentModel *Model) (handler.P

// specific handling for search deployment API where 400 status code can include AlreadyExists or DoesNotExist that need specific mapping to CFN error codes
func handleError(res *http.Response, err error) (handler.ProgressEvent, error) {
if apiError, ok := admin20231115014.AsError(err); ok && *apiError.Error == http.StatusBadRequest && strings.Contains(*apiError.ErrorCode, SearchDeploymentAlreadyExistsError) {
if apiError, ok := admin.AsError(err); ok && apiError.Error == http.StatusBadRequest && strings.Contains(apiError.ErrorCode, SearchDeploymentAlreadyExistsError) {
return handler.ProgressEvent{
OperationStatus: handler.Failed,
Message: err.Error(),
HandlerErrorCode: string(types.HandlerErrorCodeAlreadyExists)}, nil
}
if apiError, ok := admin20231115014.AsError(err); ok && *apiError.Error == http.StatusBadRequest && strings.Contains(*apiError.ErrorCode, SearchDeploymentDoesNotExistsError) {
if apiError, ok := admin.AsError(err); ok && apiError.Error == http.StatusBadRequest && strings.Contains(apiError.ErrorCode, SearchDeploymentDoesNotExistsError) {
return handler.ProgressEvent{
OperationStatus: handler.Failed,
Message: err.Error(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import (
"github.com/mongodb/mongodbatlas-cloudformation-resources/util"
"github.com/mongodb/mongodbatlas-cloudformation-resources/util/constants"
"github.com/mongodb/mongodbatlas-cloudformation-resources/util/progressevent"
admin20231115014 "go.mongodb.org/atlas-sdk/v20231115014/admin"
"go.mongodb.org/atlas-sdk/v20250312012/admin"
)

func HandleStateTransition(connV2 admin20231115014.APIClient, currentModel *Model, targetState string) handler.ProgressEvent {
func HandleStateTransition(connV2 admin.APIClient, currentModel *Model, targetState string) handler.ProgressEvent {
projectID := util.SafeString(currentModel.ProjectId)
clusterName := util.SafeString(currentModel.ClusterName)
apiResp, resp, err := connV2.AtlasSearchApi.GetAtlasSearchDeployment(context.Background(), projectID, clusterName).Execute()
apiResp, resp, err := connV2.AtlasSearchApi.GetClusterSearchDeployment(context.Background(), projectID, clusterName).Execute()
if err != nil {
if targetState == constants.DeletedState && resp.StatusCode == http.StatusBadRequest && strings.Contains(err.Error(), SearchDeploymentDoesNotExistsError) {
if targetState == constants.DeletedState && resp != nil && resp.StatusCode == http.StatusBadRequest && strings.Contains(err.Error(), SearchDeploymentDoesNotExistsError) {
return handler.ProgressEvent{
OperationStatus: handler.Success,
ResourceModel: nil,
Expand All @@ -42,6 +42,17 @@ func HandleStateTransition(connV2 admin20231115014.APIClient, currentModel *Mode
}
Comment on lines 32 to 42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:
Variable resp is likely modified and later used on error. In some cases this could result in panics due to a nil dereference

To resolve this comment:

✨ Commit Assistant Fix Suggestion
  1. Add a nil check for resp before accessing its fields inside the if err != nil block.
  2. Update the first inner if condition to if targetState == constants.DeletedState && resp != nil && resp.StatusCode == http.StatusBadRequest && strings.Contains(err.Error(), SearchDeploymentDoesNotExistsError).
  3. Update the final error return to also ensure resp is not nil if it will be dereferenced, for example: return progressevent.GetFailedEventByResponse(err.Error(), resp) becomes return progressevent.GetFailedEventByResponse(err.Error(), resp) if that function handles nil safely, otherwise check if resp != nil { ... } else { ... }.

This change prevents a potential panic if resp is nil by ensuring the code only accesses resp.StatusCode when resp is not nil.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by invalid-usage-of-modified-variable.

🛟 Help? Slack #semgrep-help or go/semgrep-help.

Resolution Options:

  • Fix the code
  • Reply /fp $reason (if security gap doesn’t exist)
  • Reply /ar $reason (if gap is valid but intentional; add mitigations/monitoring)
  • Reply /other $reason (e.g., test-only)

You can view more details about this finding in the Semgrep AppSec Platform.


newModel := NewCFNSearchDeployment(currentModel, apiResp)

// For delete operations, check if Specs is empty - this indicates deletion is complete
// The Atlas API returns 200 with only basic fields (no Specs) when deployment is deleted
if targetState == constants.DeletedState && len(newModel.Specs) == 0 {
return handler.ProgressEvent{
OperationStatus: handler.Success,
ResourceModel: nil,
Message: constants.Complete,
}
}

if util.SafeString(newModel.StateName) == targetState {
return handler.ProgressEvent{
OperationStatus: handler.Success,
Expand Down
Loading
Loading