|
| 1 | +package fwprovider |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "regexp" |
| 7 | + |
| 8 | + "github.com/DataDog/datadog-api-client-go/v2/api/datadogV2" |
| 9 | + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" |
| 10 | + "github.com/hashicorp/terraform-plugin-framework/diag" |
| 11 | + "github.com/hashicorp/terraform-plugin-framework/path" |
| 12 | + "github.com/hashicorp/terraform-plugin-framework/resource" |
| 13 | + "github.com/hashicorp/terraform-plugin-framework/resource/schema" |
| 14 | + "github.com/hashicorp/terraform-plugin-framework/schema/validator" |
| 15 | + "github.com/hashicorp/terraform-plugin-framework/types" |
| 16 | + |
| 17 | + "github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils" |
| 18 | +) |
| 19 | + |
| 20 | +var ( |
| 21 | + _ resource.ResourceWithConfigure = &agentlessScanningGcpScanOptionsResource{} |
| 22 | +) |
| 23 | + |
| 24 | +type agentlessScanningGcpScanOptionsResource struct { |
| 25 | + Api *datadogV2.AgentlessScanningApi |
| 26 | + Auth context.Context |
| 27 | +} |
| 28 | + |
| 29 | +type agentlessScanningGcpScanOptionsResourceModel struct { |
| 30 | + ID types.String `tfsdk:"id"` |
| 31 | + GcpProjectId types.String `tfsdk:"gcp_project_id"` |
| 32 | + VulnContainersOs types.Bool `tfsdk:"vuln_containers_os"` |
| 33 | + VulnHostOs types.Bool `tfsdk:"vuln_host_os"` |
| 34 | +} |
| 35 | + |
| 36 | +func NewAgentlessScanningGcpScanOptionsResource() resource.Resource { |
| 37 | + return &agentlessScanningGcpScanOptionsResource{} |
| 38 | +} |
| 39 | + |
| 40 | +func (r *agentlessScanningGcpScanOptionsResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { |
| 41 | + providerData := request.ProviderData.(*FrameworkProvider) |
| 42 | + r.Api = providerData.DatadogApiInstances.GetAgentlessScanningApiV2() |
| 43 | + r.Auth = providerData.Auth |
| 44 | +} |
| 45 | + |
| 46 | +func (r *agentlessScanningGcpScanOptionsResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { |
| 47 | + response.TypeName = "agentless_scanning_gcp_scan_options" |
| 48 | +} |
| 49 | + |
| 50 | +func (r *agentlessScanningGcpScanOptionsResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { |
| 51 | + response.Schema = schema.Schema{ |
| 52 | + Description: "Provides a Datadog Agentless Scanning GCP scan options resource. This can be used to activate and configure Agentless scan options for a GCP project.", |
| 53 | + Attributes: map[string]schema.Attribute{ |
| 54 | + // Resource ID |
| 55 | + "id": utils.ResourceIDAttribute(), |
| 56 | + "gcp_project_id": schema.StringAttribute{ |
| 57 | + Description: "The GCP project ID for which agentless scanning is configured.", |
| 58 | + Required: true, |
| 59 | + Validators: []validator.String{ |
| 60 | + stringvalidator.RegexMatches( |
| 61 | + regexp.MustCompile(`^[a-z]([a-z0-9-]{4,28}[a-z0-9])?$`), |
| 62 | + "must be a valid GCP project ID: 6–30 characters, start with a lowercase letter, and include only lowercase letters, digits, or hyphens.", |
| 63 | + ), |
| 64 | + }, |
| 65 | + }, |
| 66 | + "vuln_containers_os": schema.BoolAttribute{ |
| 67 | + Description: "Indicates if scanning for vulnerabilities in containers is enabled.", |
| 68 | + Required: true, |
| 69 | + }, |
| 70 | + "vuln_host_os": schema.BoolAttribute{ |
| 71 | + Description: "Indicates if scanning for vulnerabilities in hosts is enabled.", |
| 72 | + Required: true, |
| 73 | + }, |
| 74 | + }, |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +func (r *agentlessScanningGcpScanOptionsResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { |
| 79 | + var state agentlessScanningGcpScanOptionsResourceModel |
| 80 | + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) |
| 81 | + if response.Diagnostics.HasError() { |
| 82 | + return |
| 83 | + } |
| 84 | + |
| 85 | + body := datadogV2.GcpScanOptions{ |
| 86 | + Data: &datadogV2.GcpScanOptionsData{ |
| 87 | + Id: state.GcpProjectId.ValueString(), |
| 88 | + Type: datadogV2.GCPSCANOPTIONSDATATYPE_GCP_SCAN_OPTIONS, |
| 89 | + Attributes: &datadogV2.GcpScanOptionsDataAttributes{ |
| 90 | + VulnContainersOs: boolPtr(state.VulnContainersOs.ValueBool()), |
| 91 | + VulnHostOs: boolPtr(state.VulnHostOs.ValueBool()), |
| 92 | + }, |
| 93 | + }, |
| 94 | + } |
| 95 | + |
| 96 | + gcpScanOptionsResponse, _, err := r.Api.CreateGcpScanOptions(r.Auth, body) |
| 97 | + if err != nil { |
| 98 | + response.Diagnostics.AddError("Error creating GCP scan options", err.Error()) |
| 99 | + return |
| 100 | + } |
| 101 | + |
| 102 | + r.updateStateFromResponse(&state, gcpScanOptionsResponse) |
| 103 | + // Set the Terraform resource ID to the GCP project ID |
| 104 | + state.ID = types.StringValue(state.GcpProjectId.ValueString()) |
| 105 | + |
| 106 | + response.Diagnostics.Append(response.State.Set(ctx, &state)...) |
| 107 | +} |
| 108 | + |
| 109 | +func (r *agentlessScanningGcpScanOptionsResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { |
| 110 | + var state agentlessScanningGcpScanOptionsResourceModel |
| 111 | + response.Diagnostics.Append(request.State.Get(ctx, &state)...) |
| 112 | + if response.Diagnostics.HasError() { |
| 113 | + return |
| 114 | + } |
| 115 | + |
| 116 | + projectID := state.GcpProjectId.ValueString() |
| 117 | + |
| 118 | + gcpScanOptionsResponse, _, err := r.Api.GetGcpScanOptions(r.Auth, projectID) |
| 119 | + if err != nil { |
| 120 | + response.Diagnostics.AddError("Error reading GCP scan options", err.Error()) |
| 121 | + return |
| 122 | + } |
| 123 | + |
| 124 | + r.updateStateFromScanOptionsData(&state, *gcpScanOptionsResponse.Data) |
| 125 | + // Set the Terraform resource ID to the GCP project ID |
| 126 | + state.ID = types.StringValue(state.GcpProjectId.ValueString()) |
| 127 | + |
| 128 | + response.Diagnostics.Append(response.State.Set(ctx, &state)...) |
| 129 | +} |
| 130 | + |
| 131 | +func (r *agentlessScanningGcpScanOptionsResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { |
| 132 | + var state agentlessScanningGcpScanOptionsResourceModel |
| 133 | + response.Diagnostics.Append(request.Plan.Get(ctx, &state)...) |
| 134 | + if response.Diagnostics.HasError() { |
| 135 | + return |
| 136 | + } |
| 137 | + |
| 138 | + projectID := state.GcpProjectId.ValueString() |
| 139 | + |
| 140 | + body := datadogV2.GcpScanOptionsInputUpdate{ |
| 141 | + Data: &datadogV2.GcpScanOptionsInputUpdateData{ |
| 142 | + Id: state.GcpProjectId.ValueString(), |
| 143 | + Type: datadogV2.GCPSCANOPTIONSINPUTUPDATEDATATYPE_GCP_SCAN_OPTIONS, |
| 144 | + Attributes: &datadogV2.GcpScanOptionsInputUpdateDataAttributes{ |
| 145 | + VulnContainersOs: boolPtr(state.VulnContainersOs.ValueBool()), |
| 146 | + VulnHostOs: boolPtr(state.VulnHostOs.ValueBool()), |
| 147 | + }, |
| 148 | + }, |
| 149 | + } |
| 150 | + |
| 151 | + _, res, err := r.Api.UpdateGcpScanOptions(r.Auth, projectID, body) |
| 152 | + if err != nil { |
| 153 | + errorMsg := "Error updating GCP scan options" |
| 154 | + if res != nil { |
| 155 | + errorMsg += fmt.Sprintf(". API response: %s", res.Body) |
| 156 | + } |
| 157 | + response.Diagnostics.AddError(errorMsg, err.Error()) |
| 158 | + return |
| 159 | + } |
| 160 | + |
| 161 | + // After update, we need to read the current state since the API doesn't return the updated object |
| 162 | + readReq := resource.ReadRequest{State: response.State} |
| 163 | + readResp := resource.ReadResponse{State: response.State, Diagnostics: diag.Diagnostics{}} |
| 164 | + |
| 165 | + // Set the state with current values before reading |
| 166 | + response.Diagnostics.Append(response.State.Set(ctx, &state)...) |
| 167 | + if response.Diagnostics.HasError() { |
| 168 | + return |
| 169 | + } |
| 170 | + |
| 171 | + r.Read(ctx, readReq, &readResp) |
| 172 | + response.Diagnostics.Append(readResp.Diagnostics...) |
| 173 | + response.State = readResp.State |
| 174 | +} |
| 175 | + |
| 176 | +func (r *agentlessScanningGcpScanOptionsResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { |
| 177 | + var state agentlessScanningGcpScanOptionsResourceModel |
| 178 | + response.Diagnostics.Append(request.State.Get(ctx, &state)...) |
| 179 | + if response.Diagnostics.HasError() { |
| 180 | + return |
| 181 | + } |
| 182 | + |
| 183 | + projectID := state.GcpProjectId.ValueString() |
| 184 | + |
| 185 | + _, err := r.Api.DeleteGcpScanOptions(r.Auth, projectID) |
| 186 | + if err != nil { |
| 187 | + response.Diagnostics.AddError("Error deleting GCP scan options", err.Error()) |
| 188 | + return |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +func (r *agentlessScanningGcpScanOptionsResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { |
| 193 | + // Import the GCP project ID as both the Terraform resource ID and the gcp_project_id attribute |
| 194 | + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) |
| 195 | + // Also set the gcp_project_id to the same value |
| 196 | + response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("gcp_project_id"), request.ID)...) |
| 197 | +} |
| 198 | + |
| 199 | +func (r *agentlessScanningGcpScanOptionsResource) updateStateFromResponse(state *agentlessScanningGcpScanOptionsResourceModel, resp datadogV2.GcpScanOptions) { |
| 200 | + data := resp.GetData() |
| 201 | + r.updateStateFromScanOptionsData(state, data) |
| 202 | +} |
| 203 | + |
| 204 | +func (r *agentlessScanningGcpScanOptionsResource) updateStateFromScanOptionsData(state *agentlessScanningGcpScanOptionsResourceModel, data datadogV2.GcpScanOptionsData) { |
| 205 | + state.GcpProjectId = types.StringValue(data.GetId()) |
| 206 | + |
| 207 | + attributes := data.GetAttributes() |
| 208 | + state.VulnContainersOs = types.BoolValue(attributes.GetVulnContainersOs()) |
| 209 | + state.VulnHostOs = types.BoolValue(attributes.GetVulnHostOs()) |
| 210 | +} |
0 commit comments