Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[datadog_integration_gcp_sts] generate GCP STS account resource #1936

Merged
merged 16 commits into from
May 31, 2023
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
1 change: 1 addition & 0 deletions datadog/fwprovider/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var Resources = []func() resource.Resource{
NewIntegrationConfluentResourceResource,
NewIntegrationFastlyAccountResource,
NewIntegrationFastlyServiceResource,
NewIntegrationGcpStsResource,
NewSensitiveDataScannerGroupOrder,
NewSpansMetricResource,
NewSyntheticsConcurrencyCapResource,
Expand Down
291 changes: 291 additions & 0 deletions datadog/fwprovider/resource_datadog_integration_gcp_sts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
package fwprovider

import (
"context"

"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
"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"
)

var (
_ resource.ResourceWithConfigure = &IntegrationGcpStsResource{}
_ resource.ResourceWithImportState = &IntegrationGcpStsResource{}
)

type IntegrationGcpStsResource struct {
Api *datadogV2.GCPIntegrationApi
Auth context.Context
}

type IntegrationGcpStsModel struct {
ID types.String `tfsdk:"id"`
Automute types.Bool `tfsdk:"automute"`
ClientEmail types.String `tfsdk:"client_email"`
DelegateAccountEmail types.String `tfsdk:"delegate_account_email"`
IsCspmEnabled types.Bool `tfsdk:"is_cspm_enabled"`
HostFilters types.Set `tfsdk:"host_filters"`
}

func NewIntegrationGcpStsResource() resource.Resource {
return &IntegrationGcpStsResource{}
}

func (r *IntegrationGcpStsResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
providerData, _ := request.ProviderData.(*FrameworkProvider)
r.Api = providerData.DatadogApiInstances.GetGCPIntegrationApiV2()
r.Auth = providerData.Auth
}

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

func (r *IntegrationGcpStsResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Description: "Provides a Datadog Integration GCP Sts resource. This can be used to create and manage Datadog - Google Cloud Platform integration.",
Attributes: map[string]schema.Attribute{
"automute": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "Silence monitors for expected GCE instance shutdowns.",
},
"client_email": schema.StringAttribute{
Required: true,
Description: "Your service account email address.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"delegate_account_email": schema.StringAttribute{
Computed: true,
Description: "Datadog's STS Delegate Email.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"is_cspm_enabled": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "When enabled, Datadog performs configuration checks across your Google Cloud environment by continuously scanning every resource.",
},
"host_filters": schema.SetAttribute{
Optional: true,
Description: "Your Host Filters.",
ElementType: types.StringType,
},
"id": utils.ResourceIDAttribute(),
},
}
}

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

func (r *IntegrationGcpStsResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var state IntegrationGcpStsModel
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}
resp, httpResp, err := r.Api.ListGCPSTSAccounts(r.Auth)
if err != nil {
if httpResp != nil && httpResp.StatusCode == 404 {
response.State.RemoveResource(ctx)
return
}
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving Integration Gcp Sts"))
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
return
}

found := false
for _, account := range resp.GetData() {
if account.GetId() == state.ID.ValueString() {
found = true
r.updateState(ctx, &state, &account)
break
}
}

if !found {
response.State.RemoveResource(ctx)
return
}

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

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

// This resource is special and uses datadog delagate account.
// The datadog delegate account cannot mutated after creation hence it is safe
// to call MakeGCPSTSDelegate multiple times. And to ensure it is created, we call it once before creating
// gcp sts resource.
delegateResponse, _, err := r.Api.MakeGCPSTSDelegate(r.Auth, *datadogV2.NewMakeGCPSTSDelegateOptionalParameters())
if err != nil {
response.Diagnostics.AddError("Error creating GCP Delegate within Datadog",
"Could not create Delegate Service Account, unexpected error: "+err.Error())
return
}
delegateEmail := delegateResponse.Data.Attributes.GetDelegateAccountEmail()
state.DelegateAccountEmail = types.StringValue(delegateEmail)

body, diags := r.buildIntegrationGcpStsRequestBody(ctx, &state)
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

resp, _, err := r.Api.CreateGCPSTSAccount(r.Auth, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving Integration Gcp Sts"))
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
return
}
r.updateState(ctx, &state, resp.Data)

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

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

id := state.ID.ValueString()

body, diags := r.buildIntegrationGcpStsUpdateRequestBody(ctx, &state)
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

resp, _, err := r.Api.UpdateGCPSTSAccount(r.Auth, id, *body)
if err != nil {
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving Integration Gcp Sts"))
return
}
if err := utils.CheckForUnparsed(resp); err != nil {
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
return
}
r.updateState(ctx, &state, resp.Data)

// Save data into Terraform state
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

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

id := state.ID.ValueString()

httpResp, err := r.Api.DeleteGCPSTSAccount(r.Auth, id)
if err != nil {
if httpResp != nil && httpResp.StatusCode == 404 {
return
}
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting integration_gcp_sts"))
return
}
}

func (r *IntegrationGcpStsResource) updateState(ctx context.Context, state *IntegrationGcpStsModel, resp *datadogV2.GCPSTSServiceAccount) {
state.ID = types.StringValue(resp.GetId())

attributes := resp.GetAttributes()
if automute, ok := attributes.GetAutomuteOk(); ok {
state.Automute = types.BoolValue(*automute)
}
if clientEmail, ok := attributes.GetClientEmailOk(); ok {
state.ClientEmail = types.StringValue(*clientEmail)
}
if hostFilters, ok := attributes.GetHostFiltersOk(); ok && len(*hostFilters) > 0 {
state.HostFilters, _ = types.SetValueFrom(ctx, types.StringType, *hostFilters)
}
if isCspmEnabled, ok := attributes.GetIsCspmEnabledOk(); ok {
state.IsCspmEnabled = types.BoolValue(*isCspmEnabled)
}
}

func (r *IntegrationGcpStsResource) buildIntegrationGcpStsRequestBody(ctx context.Context, state *IntegrationGcpStsModel) (*datadogV2.GCPSTSServiceAccountCreateRequest, diag.Diagnostics) {
diags := diag.Diagnostics{}
attributes := datadogV2.NewGCPSTSServiceAccountAttributesWithDefaults()

if !state.Automute.IsNull() {
attributes.SetAutomute(state.Automute.ValueBool())
}
if !state.ClientEmail.IsNull() {
attributes.SetClientEmail(state.ClientEmail.ValueString())
}
if !state.IsCspmEnabled.IsNull() {
attributes.SetIsCspmEnabled(state.IsCspmEnabled.ValueBool())
}

hostFilters := make([]string, 0)
if !state.HostFilters.IsNull() {
diags.Append(state.HostFilters.ElementsAs(ctx, &hostFilters, false)...)
}
attributes.SetHostFilters(hostFilters)

req := datadogV2.NewGCPSTSServiceAccountCreateRequestWithDefaults()
req.Data = datadogV2.NewGCPSTSServiceAccountDataWithDefaults()
req.Data.SetAttributes(*attributes)

return req, diags
}

func (r *IntegrationGcpStsResource) buildIntegrationGcpStsUpdateRequestBody(ctx context.Context, state *IntegrationGcpStsModel) (*datadogV2.GCPSTSServiceAccountUpdateRequest, diag.Diagnostics) {
diags := diag.Diagnostics{}
attributes := datadogV2.NewGCPSTSServiceAccountAttributesWithDefaults()

if !state.Automute.IsNull() {
attributes.SetAutomute(state.Automute.ValueBool())
}
if !state.ClientEmail.IsNull() {
attributes.SetClientEmail(state.ClientEmail.ValueString())
}
if !state.IsCspmEnabled.IsNull() {
attributes.SetIsCspmEnabled(state.IsCspmEnabled.ValueBool())
}

hostFilters := make([]string, 0)
if !state.HostFilters.IsNull() {
diags.Append(state.HostFilters.ElementsAs(ctx, &hostFilters, false)...)
}
attributes.SetHostFilters(hostFilters)

req := datadogV2.NewGCPSTSServiceAccountUpdateRequestWithDefaults()
req.Data = datadogV2.NewGCPSTSServiceAccountUpdateRequestDataWithDefaults()
req.Data.SetAttributes(*attributes)

return req, diags
}
9 changes: 9 additions & 0 deletions datadog/internal/utils/api_instances_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type ApiInstances struct {
dashboardListsApiV2 *datadogV2.DashboardListsApi
eventsApiV2 *datadogV2.EventsApi
fastlyIntegrationApiV2 *datadogV2.FastlyIntegrationApi
gcpStsIntegrationApiV2 *datadogV2.GCPIntegrationApi
incidentServicesApiV2 *datadogV2.IncidentServicesApi
incidentTeamsApiV2 *datadogV2.IncidentTeamsApi
incidentsApiV2 *datadogV2.IncidentsApi
Expand Down Expand Up @@ -364,6 +365,14 @@ func (i *ApiInstances) GetEventsApiV2() *datadogV2.EventsApi {
return i.eventsApiV2
}

// GetGCPStsIntegrationApiV2 get instance of GetGCPStsIntegration
func (i *ApiInstances) GetGCPIntegrationApiV2() *datadogV2.GCPIntegrationApi {
if i.gcpStsIntegrationApiV2 == nil {
i.gcpStsIntegrationApiV2 = datadogV2.NewGCPIntegrationApi(i.HttpClient)
}
return i.gcpStsIntegrationApiV2
}

// GetIncidentServicesApiV2 get instance of IncidentServicesApi
func (i *ApiInstances) GetIncidentServicesApiV2() *datadogV2.IncidentServicesApi {
if i.incidentServicesApiV2 == nil {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2023-05-30T16:56:07.258984-04:00
Loading