Skip to content

Commit

Permalink
[datadog_integration_gcp] Migrate to FW Provider, Add ResourceCollect…
Browse files Browse the repository at this point in the history
…ionEnabled and IsSecurityCommandCenterEnabled fields (#2230)

* [GCP V1] Migrate to FW Provider, Add ResourceCollectionEnabled and IsSecurityCommandCenterEnabled fields

Update datadog client library

Remove unused variable

Fixes for migration to fwprovider

More fixes

Update codeowners for changes to gcp

* Updating docs

* Fixes for tests (recording now works properly)

* Add new default value ("is_security_command_center_enabled":false) to cassettes interacting with the GCP APIs

* Updates to docs

* Update docs to indicate that the resource is deprecated

fix docs

* PR Comments

* Small doc upda†e
  • Loading branch information
smuhit authored Mar 12, 2024
1 parent a226134 commit ab5bb04
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 225 deletions.
3 changes: 1 addition & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
datadog/*datadog_dashboard* @DataDog/integrations-tools-and-libraries @DataDog/dashboards
datadog/*datadog_downtime* @DataDog/integrations-tools-and-libraries @DataDog/monitor-app
datadog/*datadog_integration_aws* @DataDog/integrations-tools-and-libraries @DataDog/cloud-integrations
datadog/*datadog_integration_gcp* @DataDog/integrations-tools-and-libraries @DataDog/cloud-integrations
datadog/*datadog_integration_pagerduty* @DataDog/integrations-tools-and-libraries @DataDog/saas-integrations @DataDog/saas-integrations
datadog/*datadog_integration_opsgenie* @DataDog/integrations-tools-and-libraries @Datadog/collaboration-integrations
datadog/*datadog_logs* @DataDog/integrations-tools-and-libraries @DataDog/logs-backend @DataDog/logs-app
Expand All @@ -37,7 +36,7 @@ datadog/**/*datadog_integration_azure* @DataDog/integrations-tools-and-
datadog/**/*datadog_integration_cloudflare* @DataDog/integrations-tools-and-libraries @DataDog/saas-integrations
datadog/**/*datadog_integration_confluent* @DataDog/integrations-tools-and-libraries @DataDog/saas-integrations
datadog/**/*datadog_integration_fastly* @DataDog/integrations-tools-and-libraries @DataDog/saas-integrations
datadog/**/*datadog_integration_gcp_sts* @DataDog/integrations-tools-and-libraries @DataDog/gcp-integrations
datadog/**/*datadog_integration_gcp* @DataDog/integrations-tools-and-libraries @DataDog/gcp-integrations
datadog/**/*datadog_restriction_policy* @DataDog/integrations-tools-and-libraries @DataDog/aaa-granular-access
datadog/**/*datadog_sensitive_data_scanner* @DataDog/integrations-tools-and-libraries @DataDog/logs-app @DataDog/sensitive-data-scanner
datadog/**/*datadog_service_account* @DataDog/integrations-tools-and-libraries @DataDog/team-aaa
Expand Down
1 change: 1 addition & 0 deletions datadog/fwprovider/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var Resources = []func() resource.Resource{
NewIntegrationConfluentResourceResource,
NewIntegrationFastlyAccountResource,
NewIntegrationFastlyServiceResource,
NewIntegrationGcpResource,
NewIntegrationGcpStsResource,
NewIpAllowListResource,
NewRestrictionPolicyResource,
Expand Down
352 changes: 352 additions & 0 deletions datadog/fwprovider/resource_datadog_integration_gcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
package fwprovider

import (
"context"
"sync"

"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"

"github.com/DataDog/datadog-api-client-go/v2/api/datadogV1"
"github.com/hashicorp/terraform-plugin-framework/diag"
frameworkPath "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils"
)

const (
defaultType = "service_account"
defaultAuthURI = "https://accounts.google.com/o/oauth2/auth"
defaultTokenURI = "https://oauth2.googleapis.com/token"
defaultAuthProviderX509CertURL = "https://www.googleapis.com/oauth2/v1/certs"
defaultClientX509CertURLPrefix = "https://www.googleapis.com/robot/v1/metadata/x509/"
)

var (
integrationGcpMutex sync.Mutex
_ resource.ResourceWithConfigure = (*integrationGcpResource)(nil)
_ resource.ResourceWithImportState = (*integrationGcpResource)(nil)
)

type integrationGcpResource struct {
api *datadogV1.GCPIntegrationApi
auth context.Context
}

type integrationGcpModel struct {
ID types.String `tfsdk:"id"`
ProjectID types.String `tfsdk:"project_id"`
PrivateKeyId types.String `tfsdk:"private_key_id"`
PrivateKey types.String `tfsdk:"private_key"`
ClientEmail types.String `tfsdk:"client_email"`
ClientId types.String `tfsdk:"client_id"`
Automute types.Bool `tfsdk:"automute"`
HostFilters types.String `tfsdk:"host_filters"`
ResourceCollectionEnabled types.Bool `tfsdk:"resource_collection_enabled"`
CspmResourceCollectionEnabled types.Bool `tfsdk:"cspm_resource_collection_enabled"`
IsSecurityCommandCenterEnabled types.Bool `tfsdk:"is_security_command_center_enabled"`
}

func NewIntegrationGcpResource() resource.Resource {
return &integrationGcpResource{}
}

func (r *integrationGcpResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
providerData, _ := request.ProviderData.(*FrameworkProvider)
r.api = providerData.DatadogApiInstances.GetGCPIntegrationApiV1()
r.auth = providerData.Auth
}

func (r *integrationGcpResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "integration_gcp"
}

func (r *integrationGcpResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Description: "This resource is deprecated—use the `datadog_integration_gcp_sts` resource instead. Provides a Datadog - Google Cloud Platform integration resource. This can be used to create and manage Datadog - Google Cloud Platform integration.",
Attributes: map[string]schema.Attribute{
"project_id": schema.StringAttribute{
Description: "Your Google Cloud project ID found in your JSON service account key.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"private_key_id": schema.StringAttribute{
Description: "Your private key ID found in your JSON service account key.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"private_key": schema.StringAttribute{
Description: "Your private key name found in your JSON service account key.",
Required: true,
Sensitive: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"client_email": schema.StringAttribute{
Description: "Your email found in your JSON service account key.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"client_id": schema.StringAttribute{
Description: "Your ID found in your JSON service account key.",
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"host_filters": schema.StringAttribute{
Description: "Limit the GCE instances that are pulled into Datadog by using tags. Only hosts that match one of the defined tags are imported into Datadog.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},
"automute": schema.BoolAttribute{
Description: "Silence monitors for expected GCE instance shutdowns.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"resource_collection_enabled": schema.BoolAttribute{
Description: "When enabled, Datadog scans for all resources in your GCP environment.",
Optional: true,
Computed: true,
},
"cspm_resource_collection_enabled": schema.BoolAttribute{
Description: "Whether Datadog collects cloud security posture management resources from your GCP project. If enabled, requires `resource_collection_enabled` to also be enabled.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"is_security_command_center_enabled": schema.BoolAttribute{
Description: "When enabled, Datadog will attempt to collect Security Command Center Findings. Note: This requires additional permissions on the service account.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"id": utils.ResourceIDAttribute(),
},
}
}

func (r *integrationGcpResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, frameworkPath.Root("id"), request, response)
}

func (r *integrationGcpResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var state integrationGcpModel
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

integration, err := r.getGCPIntegration(state)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error listing GCP integration"))
return
}

if integration == nil {
response.State.RemoveResource(ctx)
return
}

// Save data into Terraform state
r.updateState(ctx, &state, integration)

response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *integrationGcpResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var state integrationGcpModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

integrationGcpMutex.Lock()
defer integrationGcpMutex.Unlock()

diags := diag.Diagnostics{}
body := r.buildIntegrationGcpRequestBodyBase(state)
r.addDefaultsToBody(body, state)
r.addRequiredFieldsToBody(body, state)
r.addOptionalFieldsToBody(body, state)

response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

_, _, err := r.api.CreateGCPIntegration(r.auth, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating GCP integration"))
return
}
integration, err := r.getGCPIntegration(state)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error listing GCP integration"))
return
}
if integration == nil {
response.Diagnostics.AddError("error retrieving GCP integration", "")
return
}

// Save data into Terraform state
r.updateState(ctx, &state, integration)

response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *integrationGcpResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var state integrationGcpModel
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

integrationGcpMutex.Lock()
defer integrationGcpMutex.Unlock()

diags := diag.Diagnostics{}
body := r.buildIntegrationGcpRequestBodyBase(state)
r.addOptionalFieldsToBody(body, state)

response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

_, _, err := r.api.UpdateGCPIntegration(r.auth, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error updating GCP integration"))
return
}
integration, err := r.getGCPIntegration(state)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error listing GCP integration"))
return
}
if integration == nil {
response.Diagnostics.AddError("error retrieving GCP integration", "")
return
}

// Save data into Terraform state
r.updateState(ctx, &state, integration)

response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *integrationGcpResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var state integrationGcpModel
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

integrationGcpMutex.Lock()
defer integrationGcpMutex.Unlock()

diags := diag.Diagnostics{}
body := r.buildIntegrationGcpRequestBodyBase(state)

response.Diagnostics.Append(diags...)

_, httpResp, err := r.api.DeleteGCPIntegration(r.auth, *body)
if err != nil {
if httpResp != nil && httpResp.StatusCode == 404 {
return
}
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting GCP integration"))
return
}
}

func (r *integrationGcpResource) updateState(ctx context.Context, state *integrationGcpModel, resp *datadogV1.GCPAccount) {
projectId := types.StringValue(resp.GetProjectId())
// ProjectID and ClientEmail are the only parameters required in all mutating API requests
state.ID = projectId
state.ProjectID = projectId
state.ClientEmail = types.StringValue(resp.GetClientEmail())

// Computed Values
state.Automute = types.BoolValue(resp.GetAutomute())
state.HostFilters = types.StringValue(resp.GetHostFilters())
state.CspmResourceCollectionEnabled = types.BoolValue(resp.GetIsCspmEnabled())
state.ResourceCollectionEnabled = types.BoolValue(resp.GetResourceCollectionEnabled())
state.IsSecurityCommandCenterEnabled = types.BoolValue(resp.GetIsSecurityCommandCenterEnabled())

// Non-computed values
if clientId, ok := resp.GetClientIdOk(); ok {
state.ClientId = types.StringValue(*clientId)
}
if privateKey, ok := resp.GetPrivateKeyOk(); ok {
state.PrivateKey = types.StringValue(*privateKey)
}
if privateKeyId, ok := resp.GetPrivateKeyIdOk(); ok {
state.PrivateKeyId = types.StringValue(*privateKeyId)
}
}

func (r *integrationGcpResource) getGCPIntegration(state integrationGcpModel) (*datadogV1.GCPAccount, error) {
resp, _, err := r.api.ListGCPIntegration(r.auth)
if err != nil {
return nil, err
}

for _, integration := range resp {
if integration.GetProjectId() == state.ProjectID.ValueString() && integration.GetClientEmail() == state.ClientEmail.ValueString() {
if err := utils.CheckForUnparsed(integration); err != nil {
return nil, err
}
return &integration, nil
}
}

return nil, nil // Leave handling of how to deal with nil account to the caller
}

func (r *integrationGcpResource) buildIntegrationGcpRequestBodyBase(state integrationGcpModel) *datadogV1.GCPAccount {
body := datadogV1.NewGCPAccountWithDefaults()
body.SetProjectId(state.ProjectID.ValueString())
body.SetClientEmail(state.ClientEmail.ValueString())

return body
}

func (r *integrationGcpResource) addDefaultsToBody(body *datadogV1.GCPAccount, state integrationGcpModel) {
body.SetType(defaultType)
body.SetAuthUri(defaultAuthURI)
body.SetAuthProviderX509CertUrl(defaultAuthProviderX509CertURL)
body.SetClientX509CertUrl(defaultClientX509CertURLPrefix + state.ClientEmail.ValueString())
body.SetTokenUri(defaultTokenURI)
}

func (r *integrationGcpResource) addRequiredFieldsToBody(body *datadogV1.GCPAccount, state integrationGcpModel) {
body.SetClientId(state.ClientId.ValueString())
body.SetPrivateKey(state.PrivateKey.ValueString())
body.SetPrivateKeyId(state.PrivateKeyId.ValueString())
}

func (r *integrationGcpResource) addOptionalFieldsToBody(body *datadogV1.GCPAccount, state integrationGcpModel) {
body.SetAutomute(state.Automute.ValueBool())
body.SetIsCspmEnabled(state.CspmResourceCollectionEnabled.ValueBool())
body.SetIsSecurityCommandCenterEnabled(state.IsSecurityCommandCenterEnabled.ValueBool())
body.SetHostFilters(state.HostFilters.ValueString())
if !state.ResourceCollectionEnabled.IsUnknown() {
body.SetResourceCollectionEnabled(state.ResourceCollectionEnabled.ValueBool())
}
}
1 change: 0 additions & 1 deletion datadog/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ func Provider() *schema.Provider {
"datadog_integration_aws_tag_filter": resourceDatadogIntegrationAwsTagFilter(),
"datadog_integration_aws_lambda_arn": resourceDatadogIntegrationAwsLambdaArn(),
"datadog_integration_aws_log_collection": resourceDatadogIntegrationAwsLogCollection(),
"datadog_integration_gcp": resourceDatadogIntegrationGcp(),
"datadog_integration_opsgenie_service_object": resourceDatadogIntegrationOpsgenieService(),
"datadog_integration_pagerduty": resourceDatadogIntegrationPagerduty(),
"datadog_integration_pagerduty_service_object": resourceDatadogIntegrationPagerdutySO(),
Expand Down
Loading

0 comments on commit ab5bb04

Please sign in to comment.