diff --git a/CHANGELOG.md b/CHANGELOG.md index aba9bbad4..58c69c0c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Migrate `elasticstack_fleet_agent_policy`, `elasticstack_fleet_integration` (both), and `elasticstack_fleet_server_host` to terraform-plugin-framework ([#785](https://github.com/elastic/terraform-provider-elasticstack/pull/785)) - Fix for synthetics http/tcp monitor produces inconsistent result after apply ([#801](https://github.com/elastic/terraform-provider-elasticstack/pull/801)) - Migrate `elasticstack_fleet_integration_policy` to terraform-plugin-framework. Fix drift in integration policy secrets. ([#797](https://github.com/elastic/terraform-provider-elasticstack/pull/797)) +- Migrate `elasticstack_fleet_output` to terraform-plugin-framework. ([#811](https://github.com/elastic/terraform-provider-elasticstack/pull/811)) ## [0.11.7] - 2024-09-20 diff --git a/docs/resources/fleet_output.md b/docs/resources/fleet_output.md index 000193bd1..800c06540 100644 --- a/docs/resources/fleet_output.md +++ b/docs/resources/fleet_output.md @@ -48,7 +48,7 @@ resource "elasticstack_fleet_output" "test_output" { - `default_monitoring` (Boolean) Make this output the default for agent monitoring. - `hosts` (List of String) A list of hosts. - `output_id` (String) Unique identifier of the output. -- `ssl` (Block List, Max: 1) SSL configuration. (see [below for nested schema](#nestedblock--ssl)) +- `ssl` (Block List) SSL configuration. (see [below for nested schema](#nestedblock--ssl)) ### Read-Only diff --git a/internal/clients/fleet/fleet.go b/internal/clients/fleet/fleet.go index 6c3cc2c29..607e74682 100644 --- a/internal/clients/fleet/fleet.go +++ b/internal/clients/fleet/fleet.go @@ -8,8 +8,7 @@ import ( "net/http" fleetapi "github.com/elastic/terraform-provider-elasticstack/generated/fleet" - fwdiag "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-framework/diag" ) var ( @@ -17,7 +16,7 @@ var ( ) // AllEnrollmentTokens reads all enrollment tokens from the API. -func AllEnrollmentTokens(ctx context.Context, client *Client) ([]fleetapi.EnrollmentApiKey, fwdiag.Diagnostics) { +func AllEnrollmentTokens(ctx context.Context, client *Client) ([]fleetapi.EnrollmentApiKey, diag.Diagnostics) { resp, err := client.API.GetEnrollmentApiKeysWithResponse(ctx) if err != nil { return nil, fromErr(err) @@ -26,11 +25,11 @@ func AllEnrollmentTokens(ctx context.Context, client *Client) ([]fleetapi.Enroll if resp.StatusCode() == http.StatusOK { return resp.JSON200.Items, nil } - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } // GetEnrollmentTokensByPolicy Get enrollment tokens by given policy ID -func GetEnrollmentTokensByPolicy(ctx context.Context, client *Client, policyID string) ([]fleetapi.EnrollmentApiKey, fwdiag.Diagnostics) { +func GetEnrollmentTokensByPolicy(ctx context.Context, client *Client, policyID string) ([]fleetapi.EnrollmentApiKey, diag.Diagnostics) { resp, err := client.API.GetEnrollmentApiKeysWithResponse(ctx, func(ctx context.Context, req *http.Request) error { q := req.URL.Query() q.Set("kuery", "policy_id:"+policyID) @@ -45,11 +44,11 @@ func GetEnrollmentTokensByPolicy(ctx context.Context, client *Client, policyID s if resp.StatusCode() == http.StatusOK { return resp.JSON200.Items, nil } - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } // ReadAgentPolicy reads a specific agent policy from the API. -func ReadAgentPolicy(ctx context.Context, client *Client, id string) (*fleetapi.AgentPolicy, fwdiag.Diagnostics) { +func ReadAgentPolicy(ctx context.Context, client *Client, id string) (*fleetapi.AgentPolicy, diag.Diagnostics) { resp, err := client.API.AgentPolicyInfoWithResponse(ctx, id) if err != nil { return nil, fromErr(err) @@ -61,12 +60,12 @@ func ReadAgentPolicy(ctx context.Context, client *Client, id string) (*fleetapi. case http.StatusNotFound: return nil, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // CreateAgentPolicy creates a new agent policy. -func CreateAgentPolicy(ctx context.Context, client *Client, req fleetapi.AgentPolicyCreateRequest, sysMonitoring bool) (*fleetapi.AgentPolicy, fwdiag.Diagnostics) { +func CreateAgentPolicy(ctx context.Context, client *Client, req fleetapi.AgentPolicyCreateRequest, sysMonitoring bool) (*fleetapi.AgentPolicy, diag.Diagnostics) { resp, err := client.API.CreateAgentPolicyWithResponse(ctx, req, func(ctx context.Context, req *http.Request) error { if sysMonitoring { qs := req.URL.Query() @@ -84,12 +83,12 @@ func CreateAgentPolicy(ctx context.Context, client *Client, req fleetapi.AgentPo case http.StatusOK: return resp.JSON200.Item, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // UpdateAgentPolicy updates an existing agent policy. -func UpdateAgentPolicy(ctx context.Context, client *Client, id string, req fleetapi.AgentPolicyUpdateRequest) (*fleetapi.AgentPolicy, fwdiag.Diagnostics) { +func UpdateAgentPolicy(ctx context.Context, client *Client, id string, req fleetapi.AgentPolicyUpdateRequest) (*fleetapi.AgentPolicy, diag.Diagnostics) { resp, err := client.API.UpdateAgentPolicyWithResponse(ctx, id, req) if err != nil { return nil, fromErr(err) @@ -99,12 +98,12 @@ func UpdateAgentPolicy(ctx context.Context, client *Client, id string, req fleet case http.StatusOK: return &resp.JSON200.Item, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // DeleteAgentPolicy deletes an existing agent policy -func DeleteAgentPolicy(ctx context.Context, client *Client, id string) fwdiag.Diagnostics { +func DeleteAgentPolicy(ctx context.Context, client *Client, id string) diag.Diagnostics { body := fleetapi.DeleteAgentPolicyJSONRequestBody{ AgentPolicyId: id, } @@ -120,7 +119,7 @@ func DeleteAgentPolicy(ctx context.Context, client *Client, id string) fwdiag.Di case http.StatusNotFound: return nil default: - return reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return reportUnknownError(resp.StatusCode(), resp.Body) } } @@ -128,7 +127,7 @@ func DeleteAgentPolicy(ctx context.Context, client *Client, id string) fwdiag.Di func ReadOutput(ctx context.Context, client *Client, id string) (*fleetapi.OutputCreateRequest, diag.Diagnostics) { resp, err := client.API.GetOutputWithResponse(ctx, id) if err != nil { - return nil, diag.FromErr(err) + return nil, fromErr(err) } switch resp.StatusCode() { @@ -145,7 +144,7 @@ func ReadOutput(ctx context.Context, client *Client, id string) (*fleetapi.Outpu func CreateOutput(ctx context.Context, client *Client, req fleetapi.PostOutputsJSONRequestBody) (*fleetapi.OutputCreateRequest, diag.Diagnostics) { resp, err := client.API.PostOutputsWithResponse(ctx, req) if err != nil { - return nil, diag.FromErr(err) + return nil, fromErr(err) } switch resp.StatusCode() { @@ -160,7 +159,7 @@ func CreateOutput(ctx context.Context, client *Client, req fleetapi.PostOutputsJ func UpdateOutput(ctx context.Context, client *Client, id string, req fleetapi.UpdateOutputJSONRequestBody) (*fleetapi.OutputUpdateRequest, diag.Diagnostics) { resp, err := client.API.UpdateOutputWithResponse(ctx, id, req) if err != nil { - return nil, diag.FromErr(err) + return nil, fromErr(err) } switch resp.StatusCode() { @@ -175,7 +174,7 @@ func UpdateOutput(ctx context.Context, client *Client, id string, req fleetapi.U func DeleteOutput(ctx context.Context, client *Client, id string) diag.Diagnostics { resp, err := client.API.DeleteOutputWithResponse(ctx, id) if err != nil { - return diag.FromErr(err) + return fromErr(err) } switch resp.StatusCode() { @@ -189,7 +188,7 @@ func DeleteOutput(ctx context.Context, client *Client, id string) diag.Diagnosti } // ReadFleetServerHost reads a specific fleet server host from the API. -func ReadFleetServerHost(ctx context.Context, client *Client, id string) (*fleetapi.FleetServerHost, fwdiag.Diagnostics) { +func ReadFleetServerHost(ctx context.Context, client *Client, id string) (*fleetapi.FleetServerHost, diag.Diagnostics) { resp, err := client.API.GetOneFleetServerHostsWithResponse(ctx, id) if err != nil { return nil, fromErr(err) @@ -201,12 +200,12 @@ func ReadFleetServerHost(ctx context.Context, client *Client, id string) (*fleet case http.StatusNotFound: return nil, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // CreateFleetServerHost creates a new fleet server host. -func CreateFleetServerHost(ctx context.Context, client *Client, req fleetapi.PostFleetServerHostsJSONRequestBody) (*fleetapi.FleetServerHost, fwdiag.Diagnostics) { +func CreateFleetServerHost(ctx context.Context, client *Client, req fleetapi.PostFleetServerHostsJSONRequestBody) (*fleetapi.FleetServerHost, diag.Diagnostics) { resp, err := client.API.PostFleetServerHostsWithResponse(ctx, req) if err != nil { return nil, fromErr(err) @@ -216,12 +215,12 @@ func CreateFleetServerHost(ctx context.Context, client *Client, req fleetapi.Pos case http.StatusOK: return resp.JSON200.Item, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // UpdateFleetServerHost updates an existing fleet server host. -func UpdateFleetServerHost(ctx context.Context, client *Client, id string, req fleetapi.UpdateFleetServerHostsJSONRequestBody) (*fleetapi.FleetServerHost, fwdiag.Diagnostics) { +func UpdateFleetServerHost(ctx context.Context, client *Client, id string, req fleetapi.UpdateFleetServerHostsJSONRequestBody) (*fleetapi.FleetServerHost, diag.Diagnostics) { resp, err := client.API.UpdateFleetServerHostsWithResponse(ctx, id, req) if err != nil { return nil, fromErr(err) @@ -231,12 +230,12 @@ func UpdateFleetServerHost(ctx context.Context, client *Client, id string, req f case http.StatusOK: return &resp.JSON200.Item, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // DeleteFleetServerHost deletes an existing fleet server host. -func DeleteFleetServerHost(ctx context.Context, client *Client, id string) fwdiag.Diagnostics { +func DeleteFleetServerHost(ctx context.Context, client *Client, id string) diag.Diagnostics { resp, err := client.API.DeleteFleetServerHostsWithResponse(ctx, id) if err != nil { return fromErr(err) @@ -248,12 +247,12 @@ func DeleteFleetServerHost(ctx context.Context, client *Client, id string) fwdia case http.StatusNotFound: return nil default: - return reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return reportUnknownError(resp.StatusCode(), resp.Body) } } // ReadPackagePolicy reads a specific package policy from the API. -func ReadPackagePolicy(ctx context.Context, client *Client, id string) (*fleetapi.PackagePolicy, fwdiag.Diagnostics) { +func ReadPackagePolicy(ctx context.Context, client *Client, id string) (*fleetapi.PackagePolicy, diag.Diagnostics) { format := fleetapi.GetPackagePolicyParamsFormatSimplified params := fleetapi.GetPackagePolicyParams{ Format: &format, @@ -270,12 +269,12 @@ func ReadPackagePolicy(ctx context.Context, client *Client, id string) (*fleetap case http.StatusNotFound: return nil, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // CreatePackagePolicy creates a new package policy. -func CreatePackagePolicy(ctx context.Context, client *Client, req fleetapi.CreatePackagePolicyJSONRequestBody) (*fleetapi.PackagePolicy, fwdiag.Diagnostics) { +func CreatePackagePolicy(ctx context.Context, client *Client, req fleetapi.CreatePackagePolicyJSONRequestBody) (*fleetapi.PackagePolicy, diag.Diagnostics) { format := fleetapi.CreatePackagePolicyParamsFormatSimplified params := fleetapi.CreatePackagePolicyParams{ Format: &format, @@ -290,12 +289,12 @@ func CreatePackagePolicy(ctx context.Context, client *Client, req fleetapi.Creat case http.StatusOK: return &resp.JSON200.Item, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // UpdatePackagePolicy updates an existing package policy. -func UpdatePackagePolicy(ctx context.Context, client *Client, id string, req fleetapi.UpdatePackagePolicyJSONRequestBody) (*fleetapi.PackagePolicy, fwdiag.Diagnostics) { +func UpdatePackagePolicy(ctx context.Context, client *Client, id string, req fleetapi.UpdatePackagePolicyJSONRequestBody) (*fleetapi.PackagePolicy, diag.Diagnostics) { format := fleetapi.UpdatePackagePolicyParamsFormatSimplified params := fleetapi.UpdatePackagePolicyParams{ Format: &format, @@ -310,12 +309,12 @@ func UpdatePackagePolicy(ctx context.Context, client *Client, id string, req fle case http.StatusOK: return &resp.JSON200.Item, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // DeletePackagePolicy deletes an existing package policy. -func DeletePackagePolicy(ctx context.Context, client *Client, id string, force bool) fwdiag.Diagnostics { +func DeletePackagePolicy(ctx context.Context, client *Client, id string, force bool) diag.Diagnostics { params := fleetapi.DeletePackagePolicyParams{Force: &force} resp, err := client.API.DeletePackagePolicyWithResponse(ctx, id, ¶ms) if err != nil { @@ -328,12 +327,12 @@ func DeletePackagePolicy(ctx context.Context, client *Client, id string, force b case http.StatusNotFound: return nil default: - return reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return reportUnknownError(resp.StatusCode(), resp.Body) } } // ReadPackage reads a specific package from the API. -func ReadPackage(ctx context.Context, client *Client, name, version string) fwdiag.Diagnostics { +func ReadPackage(ctx context.Context, client *Client, name, version string) diag.Diagnostics { params := fleetapi.GetPackageParams{} resp, err := client.API.GetPackage(ctx, name, version, ¶ms) @@ -353,12 +352,12 @@ func ReadPackage(ctx context.Context, client *Client, name, version string) fwdi return fromErr(err) } - return reportUnknownErrorFw(resp.StatusCode, errData) + return reportUnknownError(resp.StatusCode, errData) } } // InstallPackage installs a package. -func InstallPackage(ctx context.Context, client *Client, name, version string, force bool) fwdiag.Diagnostics { +func InstallPackage(ctx context.Context, client *Client, name, version string, force bool) diag.Diagnostics { params := fleetapi.InstallPackageParams{} body := fleetapi.InstallPackageJSONRequestBody{ Force: &force, @@ -380,12 +379,12 @@ func InstallPackage(ctx context.Context, client *Client, name, version string, f return fromErr(err) } - return reportUnknownErrorFw(resp.StatusCode, errData) + return reportUnknownError(resp.StatusCode, errData) } } // Uninstall uninstalls a package. -func Uninstall(ctx context.Context, client *Client, name, version string, force bool) fwdiag.Diagnostics { +func Uninstall(ctx context.Context, client *Client, name, version string, force bool) diag.Diagnostics { params := fleetapi.DeletePackageParams{} body := fleetapi.DeletePackageJSONRequestBody{ Force: &force, @@ -402,12 +401,12 @@ func Uninstall(ctx context.Context, client *Client, name, version string, force case http.StatusNotFound: return nil default: - return reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return reportUnknownError(resp.StatusCode(), resp.Body) } } // AllPackages returns information about the latest packages known to Fleet. -func AllPackages(ctx context.Context, client *Client, prerelease bool) ([]fleetapi.SearchResult, fwdiag.Diagnostics) { +func AllPackages(ctx context.Context, client *Client, prerelease bool) ([]fleetapi.SearchResult, diag.Diagnostics) { params := fleetapi.ListAllPackagesParams{ Prerelease: &prerelease, } @@ -421,33 +420,23 @@ func AllPackages(ctx context.Context, client *Client, prerelease bool) ([]fleeta case http.StatusOK: return resp.JSON200.Items, nil default: - return nil, reportUnknownErrorFw(resp.StatusCode(), resp.Body) + return nil, reportUnknownError(resp.StatusCode(), resp.Body) } } // fromErr recreates the sdkdiag.FromErr functionality. -func fromErr(err error) fwdiag.Diagnostics { +func fromErr(err error) diag.Diagnostics { if err == nil { return nil } - return fwdiag.Diagnostics{ - fwdiag.NewErrorDiagnostic(err.Error(), ""), + return diag.Diagnostics{ + diag.NewErrorDiagnostic(err.Error(), ""), } } func reportUnknownError(statusCode int, body []byte) diag.Diagnostics { return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Unexpected status code from server: got HTTP %d", statusCode), - Detail: string(body), - }, - } -} - -func reportUnknownErrorFw(statusCode int, body []byte) fwdiag.Diagnostics { - return fwdiag.Diagnostics{ - fwdiag.NewErrorDiagnostic( + diag.NewErrorDiagnostic( fmt.Sprintf("Unexpected status code from server: got HTTP %d", statusCode), string(body), ), diff --git a/internal/fleet/output/create.go b/internal/fleet/output/create.go new file mode 100644 index 000000000..f8cba6e80 --- /dev/null +++ b/internal/fleet/output/create.go @@ -0,0 +1,45 @@ +package output + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *outputResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var planModel outputModel + + diags := req.Plan.Get(ctx, &planModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client, err := r.client.GetFleetClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + body, diags := planModel.toAPICreateModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + output, diags := fleet.CreateOutput(ctx, client, body) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = planModel.populateFromAPICreate(ctx, output) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, planModel) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/fleet/output/delete.go b/internal/fleet/output/delete.go new file mode 100644 index 000000000..b7bf79dad --- /dev/null +++ b/internal/fleet/output/delete.go @@ -0,0 +1,28 @@ +package output + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *outputResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var stateModel outputModel + + diags := req.State.Get(ctx, &stateModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client, err := r.client.GetFleetClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + outputID := stateModel.OutputID.ValueString() + diags = fleet.DeleteOutput(ctx, client, outputID) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/fleet/output/models.go b/internal/fleet/output/models.go new file mode 100644 index 000000000..afa5b866c --- /dev/null +++ b/internal/fleet/output/models.go @@ -0,0 +1,326 @@ +package output + +import ( + "context" + "fmt" + + fleetapi "github.com/elastic/terraform-provider-elasticstack/generated/fleet" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type outputModel struct { + ID types.String `tfsdk:"id"` + OutputID types.String `tfsdk:"output_id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + Hosts types.List `tfsdk:"hosts"` //> string + CaSha256 types.String `tfsdk:"ca_sha256"` + CaTrustedFingerprint types.String `tfsdk:"ca_trusted_fingerprint"` + DefaultIntegrations types.Bool `tfsdk:"default_integrations"` + DefaultMonitoring types.Bool `tfsdk:"default_monitoring"` + Ssl types.List `tfsdk:"ssl"` //> outputSslModel + ConfigYaml types.String `tfsdk:"config_yaml"` +} + +type outputSslModel struct { + CertificateAuthorities types.List `tfsdk:"certificate_authorities"` //> string + Certificate types.String `tfsdk:"certificate"` + Key types.String `tfsdk:"key"` +} + +func (model *outputModel) populateFromAPICreate(ctx context.Context, data *fleetapi.OutputCreateRequest) (diags diag.Diagnostics) { + if data == nil { + return + } + + union, err := data.ValueByDiscriminator() + if err != nil { + diags.AddError(err.Error(), "") + return + } + + var nd diag.Diagnostics + switch data := union.(type) { + case fleetapi.OutputCreateRequestElasticsearch: + model.ID = types.StringPointerValue(data.Id) + model.OutputID = types.StringPointerValue(data.Id) + model.Name = types.StringValue(data.Name) + model.Type = types.StringValue(string(data.Type)) + model.Hosts = utils.SliceToListType_String(ctx, utils.Deref(data.Hosts), path.Root("hosts"), diags) + model.CaSha256 = types.StringPointerValue(data.CaSha256) + model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint) + model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault) + model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring) + model.ConfigYaml = types.StringPointerValue(data.ConfigYaml) + + if data.Ssl != nil { + p := path.Root("ssl") + sslModels := []outputSslModel{{ + CertificateAuthorities: utils.SliceToListType_String(ctx, utils.Deref(data.Ssl.CertificateAuthorities), p.AtName("certificate_authorities"), diags), + Certificate: types.StringPointerValue(data.Ssl.Certificate), + Key: types.StringPointerValue(data.Ssl.Key), + }} + model.Ssl, nd = types.ListValueFrom(ctx, getSslAttrTypes(), sslModels) + diags.Append(nd...) + } else { + model.Ssl = types.ListNull(getSslAttrTypes()) + } + + case fleetapi.OutputCreateRequestLogstash: + model.ID = types.StringPointerValue(data.Id) + model.OutputID = types.StringPointerValue(data.Id) + model.Name = types.StringValue(data.Name) + model.Type = types.StringValue(string(data.Type)) + model.Hosts = utils.SliceToListType_String(ctx, data.Hosts, path.Root("hosts"), diags) + model.CaSha256 = types.StringPointerValue(data.CaSha256) + model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint) + model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault) + model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring) + model.ConfigYaml = types.StringPointerValue(data.ConfigYaml) + + if data.Ssl != nil { + p := path.Root("ssl") + sslModels := []outputSslModel{{ + CertificateAuthorities: utils.SliceToListType_String(ctx, utils.Deref(data.Ssl.CertificateAuthorities), p.AtName("certificate_authorities"), diags), + Certificate: types.StringPointerValue(data.Ssl.Certificate), + Key: types.StringPointerValue(data.Ssl.Key), + }} + model.Ssl, nd = types.ListValueFrom(ctx, getSslAttrTypes(), sslModels) + diags.Append(nd...) + } else { + model.Ssl = types.ListNull(getSslAttrTypes()) + } + + default: + diags.AddError(fmt.Sprintf("unhandled output type: %T", data), "") + } + + return +} + +func (model *outputModel) populateFromAPIUpdate(ctx context.Context, data *fleetapi.OutputUpdateRequest) (diags diag.Diagnostics) { + if data == nil { + return + } + + union, err := data.ValueByDiscriminator() + if err != nil { + diags.AddError(err.Error(), "") + return + } + + var nd diag.Diagnostics + switch data := union.(type) { + case fleetapi.OutputUpdateRequestElasticsearch: + model.ID = types.StringPointerValue(data.Id) + model.OutputID = types.StringPointerValue(data.Id) + model.Name = types.StringValue(data.Name) + model.Type = types.StringValue(string(data.Type)) + model.Hosts = utils.SliceToListType_String(ctx, data.Hosts, path.Root("hosts"), diags) + model.CaSha256 = types.StringPointerValue(data.CaSha256) + model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint) + model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault) + model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring) + model.ConfigYaml = types.StringPointerValue(data.ConfigYaml) + + if data.Ssl != nil { + p := path.Root("ssl") + sslModel := []outputSslModel{{ + CertificateAuthorities: utils.SliceToListType_String(ctx, utils.Deref(data.Ssl.CertificateAuthorities), p.AtName("certificate_authorities"), diags), + Certificate: types.StringPointerValue(data.Ssl.Certificate), + Key: types.StringPointerValue(data.Ssl.Key), + }} + model.Ssl, nd = types.ListValueFrom(ctx, getSslAttrTypes(), sslModel) + diags.Append(nd...) + } else { + model.Ssl = types.ListNull(getSslAttrTypes()) + } + + case fleetapi.OutputUpdateRequestLogstash: + model.ID = types.StringPointerValue(data.Id) + model.OutputID = types.StringPointerValue(data.Id) + model.Name = types.StringValue(data.Name) + model.Type = types.StringValue(string(data.Type)) + model.Hosts = utils.SliceToListType_String(ctx, utils.Deref(data.Hosts), path.Root("hosts"), diags) + model.CaSha256 = types.StringPointerValue(data.CaSha256) + model.CaTrustedFingerprint = types.StringPointerValue(data.CaTrustedFingerprint) + model.DefaultIntegrations = types.BoolPointerValue(data.IsDefault) + model.DefaultMonitoring = types.BoolPointerValue(data.IsDefaultMonitoring) + model.ConfigYaml = types.StringPointerValue(data.ConfigYaml) + + if data.Ssl != nil { + p := path.Root("ssl") + sslModel := []outputSslModel{{ + CertificateAuthorities: utils.SliceToListType_String(ctx, utils.Deref(data.Ssl.CertificateAuthorities), p.AtName("certificate_authorities"), diags), + Certificate: types.StringPointerValue(data.Ssl.Certificate), + Key: types.StringPointerValue(data.Ssl.Key), + }} + model.Ssl, nd = types.ListValueFrom(ctx, getSslAttrTypes(), sslModel) + diags.Append(nd...) + } else { + model.Ssl = types.ListNull(getSslAttrTypes()) + } + + default: + diags.AddError(fmt.Sprintf("unhandled output type: %T", data), "") + } + + return +} + +func (model outputModel) toAPICreateModel(ctx context.Context) (union fleetapi.OutputCreateRequest, diags diag.Diagnostics) { + outputType := model.Type.ValueString() + switch outputType { + case "elasticsearch": + body := fleetapi.OutputCreateRequestElasticsearch{ + Type: fleetapi.OutputCreateRequestElasticsearchTypeElasticsearch, + CaSha256: model.CaSha256.ValueStringPointer(), + CaTrustedFingerprint: model.CaTrustedFingerprint.ValueStringPointer(), + ConfigYaml: model.ConfigYaml.ValueStringPointer(), + Hosts: utils.SliceRef(utils.ListTypeToSlice_String(ctx, model.Hosts, path.Root("hosts"), diags)), + Id: model.OutputID.ValueStringPointer(), + IsDefault: model.DefaultIntegrations.ValueBoolPointer(), + IsDefaultMonitoring: model.DefaultMonitoring.ValueBoolPointer(), + Name: model.Name.ValueString(), + } + + // Can't use helpers for anonymous structs + if utils.IsKnown(model.Ssl) { + sslModels := utils.ListTypeAs[outputSslModel](ctx, model.Ssl, path.Root("ssl"), diags) + if len(sslModels) > 0 { + body.Ssl = &struct { + Certificate *string `json:"certificate,omitempty"` + CertificateAuthorities *[]string `json:"certificate_authorities,omitempty"` + Key *string `json:"key,omitempty"` + }{ + Certificate: sslModels[0].Certificate.ValueStringPointer(), + CertificateAuthorities: utils.SliceRef(utils.ListTypeToSlice_String(ctx, sslModels[0].CertificateAuthorities, path.Root("certificate_authorities"), diags)), + Key: sslModels[0].Key.ValueStringPointer(), + } + } + } + + err := union.FromOutputCreateRequestElasticsearch(body) + if err != nil { + diags.AddError(err.Error(), "") + } + + case "logstash": + body := fleetapi.OutputCreateRequestLogstash{ + CaSha256: model.CaSha256.ValueStringPointer(), + CaTrustedFingerprint: model.CaTrustedFingerprint.ValueStringPointer(), + ConfigYaml: model.ConfigYaml.ValueStringPointer(), + Hosts: utils.ListTypeToSlice_String(ctx, model.Hosts, path.Root("hosts"), diags), + Id: model.OutputID.ValueStringPointer(), + IsDefault: model.DefaultIntegrations.ValueBoolPointer(), + IsDefaultMonitoring: model.DefaultMonitoring.ValueBoolPointer(), + Name: model.Name.ValueString(), + Type: fleetapi.OutputCreateRequestLogstashTypeLogstash, + } + + // Can't use helpers for anonymous structs + if utils.IsKnown(model.Ssl) { + sslModels := utils.ListTypeAs[outputSslModel](ctx, model.Ssl, path.Root("ssl"), diags) + if len(sslModels) > 0 { + body.Ssl = &struct { + Certificate *string `json:"certificate,omitempty"` + CertificateAuthorities *[]string `json:"certificate_authorities,omitempty"` + Key *string `json:"key,omitempty"` + }{ + Certificate: sslModels[0].Certificate.ValueStringPointer(), + CertificateAuthorities: utils.SliceRef(utils.ListTypeToSlice_String(ctx, sslModels[0].CertificateAuthorities, path.Root("certificate_authorities"), diags)), + Key: sslModels[0].Key.ValueStringPointer(), + } + } + } + + err := union.FromOutputCreateRequestLogstash(body) + if err != nil { + diags.AddError(err.Error(), "") + } + + default: + diags.AddError(fmt.Sprintf("unhandled output type: %s", outputType), "") + } + + return +} + +func (model outputModel) toAPIUpdateModel(ctx context.Context) (union fleetapi.OutputUpdateRequest, diags diag.Diagnostics) { + outputType := model.Type.ValueString() + switch outputType { + case "elasticsearch": + body := fleetapi.OutputUpdateRequestElasticsearch{ + Type: fleetapi.OutputUpdateRequestElasticsearchTypeElasticsearch, + CaSha256: model.CaSha256.ValueStringPointer(), + CaTrustedFingerprint: model.CaTrustedFingerprint.ValueStringPointer(), + ConfigYaml: model.ConfigYaml.ValueStringPointer(), + Hosts: utils.ListTypeToSlice_String(ctx, model.Hosts, path.Root("hosts"), diags), + IsDefault: model.DefaultIntegrations.ValueBoolPointer(), + IsDefaultMonitoring: model.DefaultMonitoring.ValueBoolPointer(), + Name: model.Name.ValueString(), + } + + // Can't use helpers for anonymous structs + if utils.IsKnown(model.Ssl) { + sslModels := utils.ListTypeAs[outputSslModel](ctx, model.Ssl, path.Root("ssl"), diags) + if len(sslModels) > 0 { + body.Ssl = &struct { + Certificate *string `json:"certificate,omitempty"` + CertificateAuthorities *[]string `json:"certificate_authorities,omitempty"` + Key *string `json:"key,omitempty"` + }{ + Certificate: sslModels[0].Certificate.ValueStringPointer(), + CertificateAuthorities: utils.SliceRef(utils.ListTypeToSlice_String(ctx, sslModels[0].CertificateAuthorities, path.Root("certificate_authorities"), diags)), + Key: sslModels[0].Key.ValueStringPointer(), + } + } + } + + err := union.FromOutputUpdateRequestElasticsearch(body) + if err != nil { + diags.AddError(err.Error(), "") + } + + case "logstash": + body := fleetapi.OutputUpdateRequestLogstash{ + CaSha256: model.CaSha256.ValueStringPointer(), + CaTrustedFingerprint: model.CaTrustedFingerprint.ValueStringPointer(), + ConfigYaml: model.ConfigYaml.ValueStringPointer(), + Hosts: utils.SliceRef(utils.ListTypeToSlice_String(ctx, model.Hosts, path.Root("hosts"), diags)), + IsDefault: model.DefaultIntegrations.ValueBoolPointer(), + IsDefaultMonitoring: model.DefaultMonitoring.ValueBoolPointer(), + Name: model.Name.ValueString(), + Type: fleetapi.OutputUpdateRequestLogstashTypeLogstash, + } + + // Can't use helpers for anonymous structs + if utils.IsKnown(model.Ssl) { + sslModels := utils.ListTypeAs[outputSslModel](ctx, model.Ssl, path.Root("ssl"), diags) + if len(sslModels) > 0 { + body.Ssl = &struct { + Certificate *string `json:"certificate,omitempty"` + CertificateAuthorities *[]string `json:"certificate_authorities,omitempty"` + Key *string `json:"key,omitempty"` + }{ + Certificate: sslModels[0].Certificate.ValueStringPointer(), + CertificateAuthorities: utils.SliceRef(utils.ListTypeToSlice_String(ctx, sslModels[0].CertificateAuthorities, path.Root("certificate_authorities"), diags)), + Key: sslModels[0].Key.ValueStringPointer(), + } + } + } + + err := union.FromOutputUpdateRequestLogstash(body) + if err != nil { + diags.AddError(err.Error(), "") + } + + default: + diags.AddError(fmt.Sprintf("unhandled output type: %s", outputType), "") + } + + return +} diff --git a/internal/fleet/output/read.go b/internal/fleet/output/read.go new file mode 100644 index 000000000..3e0467990 --- /dev/null +++ b/internal/fleet/output/read.go @@ -0,0 +1,46 @@ +package output + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *outputResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateModel outputModel + + diags := req.State.Get(ctx, &stateModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client, err := r.client.GetFleetClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + outputID := stateModel.OutputID.ValueString() + output, diags := fleet.ReadOutput(ctx, client, outputID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + resp.State.RemoveResource(ctx) + return + } + + if output == nil { + resp.State.RemoveResource(ctx) + return + } + + diags = stateModel.populateFromAPICreate(ctx, output) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, stateModel) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/fleet/output/resource.go b/internal/fleet/output/resource.go new file mode 100644 index 000000000..79918917e --- /dev/null +++ b/internal/fleet/output/resource.go @@ -0,0 +1,39 @@ +package output + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + _ resource.Resource = &outputResource{} + _ resource.ResourceWithConfigure = &outputResource{} + _ resource.ResourceWithImportState = &outputResource{} +) + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &outputResource{} +} + +type outputResource struct { + client *clients.ApiClient +} + +func (r *outputResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + client, diags := clients.ConvertProviderData(req.ProviderData) + resp.Diagnostics.Append(diags...) + r.client = client +} + +func (r *outputResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, "fleet_output") +} + +func (r *outputResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("output_id"), req, resp) +} diff --git a/internal/fleet/output_resource_test.go b/internal/fleet/output/resource_test.go similarity index 53% rename from internal/fleet/output_resource_test.go rename to internal/fleet/output/resource_test.go index dc1c3cea0..425c50085 100644 --- a/internal/fleet/output_resource_test.go +++ b/internal/fleet/output/resource_test.go @@ -1,14 +1,14 @@ -package fleet_test +package output_test import ( "context" - "errors" "fmt" "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" "github.com/elastic/terraform-provider-elasticstack/internal/clients" "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" "github.com/hashicorp/go-version" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -18,8 +18,52 @@ import ( var minVersionOutput = version.Must(version.NewVersion("8.6.0")) +func TestAccResourceOutputElasticsearchFromSDK(t *testing.T) { + policyName := sdkacctest.RandString(22) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + CheckDestroy: checkResourceOutputDestroy, + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "elasticstack": { + Source: "elastic/elasticstack", + VersionConstraint: "0.11.7", + }, + }, + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutput), + Config: testAccResourceOutputCreateElasticsearch(policyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Elasticsearch Output %s", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-elasticsearch-output", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "elasticsearch"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_monitoring", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "hosts.0", "https://elasticsearch:9200"), + ), + }, + { + ProtoV6ProviderFactories: acctest.Providers, + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutput), + Config: testAccResourceOutputCreateElasticsearch(policyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Elasticsearch Output %s", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-elasticsearch-output", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "elasticsearch"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_monitoring", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "hosts.0", "https://elasticsearch:9200"), + ), + }, + }, + }) +} + func TestAccResourceOutputElasticsearch(t *testing.T) { - policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + policyName := sdkacctest.RandString(22) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -31,7 +75,7 @@ func TestAccResourceOutputElasticsearch(t *testing.T) { Config: testAccResourceOutputCreateElasticsearch(policyName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Elasticsearch Output %s", policyName)), - resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", "elasticsearch-output"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-elasticsearch-output", policyName)), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "elasticsearch"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), @@ -44,7 +88,7 @@ func TestAccResourceOutputElasticsearch(t *testing.T) { Config: testAccResourceOutputUpdateElasticsearch(policyName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Updated Elasticsearch Output %s", policyName)), - resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", "elasticsearch-output"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-elasticsearch-output", policyName)), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "elasticsearch"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), @@ -54,6 +98,7 @@ func TestAccResourceOutputElasticsearch(t *testing.T) { }, { SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutput), + Config: testAccResourceOutputUpdateElasticsearch(policyName), ResourceName: "elasticstack_fleet_output.test_output", ImportState: true, ImportStateVerify: true, @@ -62,8 +107,58 @@ func TestAccResourceOutputElasticsearch(t *testing.T) { }) } +func TestAccResourceOutputLogstashFromSDK(t *testing.T) { + policyName := sdkacctest.RandString(22) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + CheckDestroy: checkResourceOutputDestroy, + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "elasticstack": { + Source: "elastic/elasticstack", + VersionConstraint: "0.11.7", + }, + }, + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutput), + Config: testAccResourceOutputCreateLogstash(policyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Logstash Output %s", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-logstash-output", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "logstash"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_monitoring", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "hosts.0", "logstash:5044"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "ssl.0.certificate_authorities.0", "placeholder"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "ssl.0.certificate", "placeholder"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "ssl.0.key", "placeholder"), + ), + }, + { + ProtoV6ProviderFactories: acctest.Providers, + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutput), + Config: testAccResourceOutputCreateLogstash(policyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Logstash Output %s", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-logstash-output", policyName)), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "logstash"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_monitoring", "false"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "hosts.0", "logstash:5044"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "ssl.0.certificate_authorities.0", "placeholder"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "ssl.0.certificate", "placeholder"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "ssl.0.key", "placeholder"), + ), + }, + }, + }) +} + func TestAccResourceOutputLogstash(t *testing.T) { - policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + policyName := sdkacctest.RandString(22) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -75,7 +170,7 @@ func TestAccResourceOutputLogstash(t *testing.T) { Config: testAccResourceOutputCreateLogstash(policyName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Logstash Output %s", policyName)), - resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", "logstash-output"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-logstash-output", policyName)), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "logstash"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), @@ -88,10 +183,10 @@ func TestAccResourceOutputLogstash(t *testing.T) { }, { SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionOutput), - Config: testAccResourceOutputLogstashUpdate(policyName), + Config: testAccResourceOutputUpdateLogstash(policyName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "name", fmt.Sprintf("Updated Logstash Output %s", policyName)), - resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", "logstash-output"), + resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "id", fmt.Sprintf("%s-logstash-output", policyName)), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "type", "logstash"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "config_yaml", "\"ssl.verification_mode\": \"none\"\n"), resource.TestCheckResourceAttr("elasticstack_fleet_output.test_output", "default_integrations", "false"), @@ -114,8 +209,8 @@ provider "elasticstack" { } resource "elasticstack_fleet_output" "test_output" { - name = "%s" - output_id = "elasticsearch-output" + name = "Elasticsearch Output %s" + output_id = "%s-elasticsearch-output" type = "elasticsearch" config_yaml = yamlencode({ "ssl.verification_mode" : "none" @@ -126,7 +221,7 @@ resource "elasticstack_fleet_output" "test_output" { "https://elasticsearch:9200" ] } -`, fmt.Sprintf("Elasticsearch Output %s", id)) +`, id, id) } func testAccResourceOutputUpdateElasticsearch(id string) string { @@ -137,8 +232,8 @@ provider "elasticstack" { } resource "elasticstack_fleet_output" "test_output" { - name = "%s" - output_id = "elasticsearch-output" + name = "Updated Elasticsearch Output %s" + output_id = "%s-elasticsearch-output" type = "elasticsearch" config_yaml = yamlencode({ "ssl.verification_mode" : "none" @@ -149,8 +244,7 @@ resource "elasticstack_fleet_output" "test_output" { "https://elasticsearch:9200" ] } - -`, fmt.Sprintf("Updated Elasticsearch Output %s", id)) +`, id, id) } func testAccResourceOutputCreateLogstash(id string) string { @@ -161,9 +255,9 @@ provider "elasticstack" { } resource "elasticstack_fleet_output" "test_output" { - name = "%s" + name = "Logstash Output %s" type = "logstash" - output_id = "logstash-output" + output_id = "%s-logstash-output" config_yaml = yamlencode({ "ssl.verification_mode" : "none" }) @@ -178,10 +272,10 @@ resource "elasticstack_fleet_output" "test_output" { key = "placeholder" } } -`, fmt.Sprintf("Logstash Output %s", id)) +`, id, id) } -func testAccResourceOutputLogstashUpdate(id string) string { +func testAccResourceOutputUpdateLogstash(id string) string { return fmt.Sprintf(` provider "elasticstack" { elasticsearch {} @@ -189,9 +283,9 @@ provider "elasticstack" { } resource "elasticstack_fleet_output" "test_output" { - name = "%s" + name = "Updated Logstash Output %s" + output_id = "%s-logstash-output" type = "logstash" - output_id = "logstash-output" config_yaml = yamlencode({ "ssl.verification_mode" : "none" }) @@ -206,8 +300,7 @@ resource "elasticstack_fleet_output" "test_output" { key = "placeholder" } } - -`, fmt.Sprintf("Updated Logstash Output %s", id)) +`, id, id) } func checkResourceOutputDestroy(s *terraform.State) error { @@ -225,11 +318,11 @@ func checkResourceOutputDestroy(s *terraform.State) error { if err != nil { return err } - packagePolicy, diag := fleet.ReadOutput(context.Background(), fleetClient, rs.Primary.ID) - if diag.HasError() { - return errors.New(diag[0].Summary) + output, diags := fleet.ReadOutput(context.Background(), fleetClient, rs.Primary.ID) + if diags.HasError() { + return utils.FwDiagsAsError(diags) } - if packagePolicy != nil { + if output != nil { return fmt.Errorf("output id=%v still exists, but it should have been removed", rs.Primary.ID) } } diff --git a/internal/fleet/output/schema.go b/internal/fleet/output/schema.go new file mode 100644 index 000000000..9801e2051 --- /dev/null +++ b/internal/fleet/output/schema.go @@ -0,0 +1,113 @@ +package output + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *outputResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = getSchema() +} + +func getSchema() schema.Schema { + return schema.Schema{ + Description: "Creates a new Fleet Output.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of this resource.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "output_id": schema.StringAttribute{ + Description: "Unique identifier of the output.", + Computed: true, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "The name of the output.", + Required: true, + }, + "type": schema.StringAttribute{ + Description: "The output type.", + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("elasticsearch", "logstash"), + }, + }, + "hosts": schema.ListAttribute{ + Description: "A list of hosts.", + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + ElementType: types.StringType, + }, + "ca_sha256": schema.StringAttribute{ + Description: "Fingerprint of the Elasticsearch CA certificate.", + Optional: true, + }, + "ca_trusted_fingerprint": schema.StringAttribute{ + Description: "Fingerprint of trusted CA.", + Optional: true, + }, + "default_integrations": schema.BoolAttribute{ + Description: "Make this output the default for agent integrations.", + Optional: true, + }, + "default_monitoring": schema.BoolAttribute{ + Description: "Make this output the default for agent monitoring.", + Optional: true, + }, + "config_yaml": schema.StringAttribute{ + Description: "Advanced YAML configuration. YAML settings here will be added to the output section of each agent policy.", + Optional: true, + Sensitive: true, + }, + }, + Blocks: map[string]schema.Block{ + "ssl": schema.ListNestedBlock{ + Description: "SSL configuration.", + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "certificate_authorities": schema.ListAttribute{ + Description: "Server SSL certificate authorities.", + Optional: true, + ElementType: types.StringType, + }, + "certificate": schema.StringAttribute{ + Description: "Client SSL certificate.", + Required: true, + }, + "key": schema.StringAttribute{ + Description: "Client SSL certificate key.", + Required: true, + Sensitive: true, + }, + }, + }, + }, + }, + } +} + +func getSslAttrTypes() attr.Type { + return getSchema().Blocks["ssl"].Type().(attr.TypeWithElementType).ElementType() +} diff --git a/internal/fleet/output/update.go b/internal/fleet/output/update.go new file mode 100644 index 000000000..6aca90f4c --- /dev/null +++ b/internal/fleet/output/update.go @@ -0,0 +1,46 @@ +package output + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *outputResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var planModel outputModel + + diags := req.Plan.Get(ctx, &planModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client, err := r.client.GetFleetClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + body, diags := planModel.toAPIUpdateModel(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + outputID := planModel.OutputID.ValueString() + output, diags := fleet.UpdateOutput(ctx, client, outputID, body) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = planModel.populateFromAPIUpdate(ctx, output) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = resp.State.Set(ctx, planModel) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/fleet/output_resource.go b/internal/fleet/output_resource.go deleted file mode 100644 index b838e66b3..000000000 --- a/internal/fleet/output_resource.go +++ /dev/null @@ -1,560 +0,0 @@ -package fleet - -import ( - "context" - - fleetapi "github.com/elastic/terraform-provider-elasticstack/generated/fleet" - "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -func ResourceOutput() *schema.Resource { - outputSchema := map[string]*schema.Schema{ - "output_id": { - Description: "Unique identifier of the output.", - Type: schema.TypeString, - Computed: true, - Optional: true, - }, - "name": { - Description: "The name of the output.", - Type: schema.TypeString, - Required: true, - }, - "type": { - Description: "The output type.", - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{"elasticsearch", "logstash"}, false), - }, - "hosts": { - Description: "A list of hosts.", - Type: schema.TypeList, - Optional: true, - MinItems: 1, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "ca_sha256": { - Description: "Fingerprint of the Elasticsearch CA certificate.", - Type: schema.TypeString, - Optional: true, - }, - "ca_trusted_fingerprint": { - Description: "Fingerprint of trusted CA.", - Type: schema.TypeString, - Optional: true, - }, - "default_integrations": { - Description: "Make this output the default for agent integrations.", - Type: schema.TypeBool, - Optional: true, - }, - "default_monitoring": { - Description: "Make this output the default for agent monitoring.", - Type: schema.TypeBool, - Optional: true, - }, - "ssl": { - Description: "SSL configuration.", - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "certificate_authorities": { - Description: "Server SSL certificate authorities.", - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "certificate": { - Description: "Client SSL certificate.", - Type: schema.TypeString, - Required: true, - }, - "key": { - Description: "Client SSL certificate key.", - Type: schema.TypeString, - Required: true, - Sensitive: true, - }, - }, - }, - }, - "config_yaml": { - Description: "Advanced YAML configuration. YAML settings here will be added to the output section of each agent policy.", - Type: schema.TypeString, - Optional: true, - Sensitive: true, - }, - } - - return &schema.Resource{ - Description: "Creates a new Fleet Output.", - - CreateContext: resourceOutputCreate, - ReadContext: resourceOutputRead, - UpdateContext: resourceOutputUpdate, - DeleteContext: resourceOutputDelete, - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: outputSchema, - } -} - -func resourceOutputCreateElasticsearch(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - fleetClient, diags := getFleetClient(d, meta) - if diags.HasError() { - return diags - } - - reqData := fleetapi.OutputCreateRequestElasticsearch{ - Name: d.Get("name").(string), - Type: fleetapi.OutputCreateRequestElasticsearchTypeElasticsearch, - } - - var hosts []string - if value := d.Get("hosts").([]interface{}); len(value) > 0 { - for _, v := range value { - if vStr, ok := v.(string); ok && vStr != "" { - hosts = append(hosts, vStr) - } - } - } - if hosts != nil { - reqData.Hosts = &hosts - } - if value, ok := d.Get("output_id").(string); ok && value != "" { - reqData.Id = &value - } - if value := d.Get("default_integrations").(bool); value { - reqData.IsDefault = &value - } - if value := d.Get("default_monitoring").(bool); value { - reqData.IsDefaultMonitoring = &value - } - if value, ok := d.Get("ca_sha256").(string); ok && value != "" { - reqData.CaSha256 = &value - } - if value, ok := d.Get("ca_trusted_fingerprint").(string); ok && value != "" { - reqData.CaTrustedFingerprint = &value - } - if value, ok := d.Get("config_yaml").(string); ok && value != "" { - reqData.ConfigYaml = &value - } - - req := fleetapi.PostOutputsJSONRequestBody{} - if err := req.FromOutputCreateRequestElasticsearch(reqData); err != nil { - return diag.FromErr(err) - } - - rawOutput, diags := fleet.CreateOutput(ctx, fleetClient, req) - if diags.HasError() { - return diags - } - - output, err := rawOutput.AsOutputCreateRequestElasticsearch() - if err != nil { - return diag.FromErr(err) - } - - d.SetId(*output.Id) - if err := d.Set("output_id", output.Id); err != nil { - return diag.FromErr(err) - } - - return nil -} - -func resourceOutputCreateLogstash(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - fleetClient, diags := getFleetClient(d, meta) - if diags.HasError() { - return diags - } - - reqData := fleetapi.OutputCreateRequestLogstash{ - Name: d.Get("name").(string), - Type: fleetapi.OutputCreateRequestLogstashTypeLogstash, - } - - var hosts []string - if value := d.Get("hosts").([]interface{}); len(value) > 0 { - for _, v := range value { - if vStr, ok := v.(string); ok && vStr != "" { - hosts = append(hosts, vStr) - } - } - } - reqData.Hosts = hosts - if value, ok := d.Get("output_id").(string); ok && value != "" { - reqData.Id = &value - } - if value := d.Get("default_integrations").(bool); value { - reqData.IsDefault = &value - } - if value := d.Get("default_monitoring").(bool); value { - reqData.IsDefaultMonitoring = &value - } - if value, ok := d.Get("ca_sha256").(string); ok && value != "" { - reqData.CaSha256 = &value - } - if value, ok := d.Get("ca_trusted_fingerprint").(string); ok && value != "" { - reqData.CaTrustedFingerprint = &value - } - if value, ok := d.GetOk("ssl"); ok { - ssl := value.([]interface{})[0].(map[string]interface{}) - reqData.Ssl = &struct { - Certificate *string `json:"certificate,omitempty"` - CertificateAuthorities *[]string `json:"certificate_authorities,omitempty"` - Key *string `json:"key,omitempty"` - }{} - if value, ok := ssl["certificate_authorities"].([]interface{}); ok { - certs := make([]string, len(value)) - for i, v := range value { - certs[i] = v.(string) - } - reqData.Ssl.CertificateAuthorities = &certs - } - if value, ok := ssl["certificate"].(string); ok { - reqData.Ssl.Certificate = &value - } - if value, ok := ssl["key"].(string); ok { - reqData.Ssl.Key = &value - } - } - if value, ok := d.Get("config_yaml").(string); ok && value != "" { - reqData.ConfigYaml = &value - } - - req := fleetapi.PostOutputsJSONRequestBody{} - if err := req.FromOutputCreateRequestLogstash(reqData); err != nil { - return diag.FromErr(err) - } - - rawOutput, diags := fleet.CreateOutput(ctx, fleetClient, req) - if diags.HasError() { - return diags - } - - output, err := rawOutput.AsOutputCreateRequestElasticsearch() - if err != nil { - return diag.FromErr(err) - } - - d.SetId(*output.Id) - if err := d.Set("output_id", output.Id); err != nil { - return diag.FromErr(err) - } - - return nil -} - -func resourceOutputCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - outputType := d.Get("type").(string) - var diags diag.Diagnostics - - if id := d.Get("output_id").(string); id != "" { - d.SetId(id) - } - - switch outputType { - case "elasticsearch": - diags = resourceOutputCreateElasticsearch(ctx, d, meta) - case "logstash": - diags = resourceOutputCreateLogstash(ctx, d, meta) - } - if diags.HasError() { - return diags - } - - return resourceOutputRead(ctx, d, meta) -} - -func resourceOutputUpdateElasticsearch(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - fleetClient, diags := getFleetClient(d, meta) - if diags.HasError() { - return diags - } - - reqData := fleetapi.OutputUpdateRequestElasticsearch{ - Name: d.Get("name").(string), - Type: fleetapi.OutputUpdateRequestElasticsearchTypeElasticsearch, - } - - var hosts []string - if value := d.Get("hosts").([]interface{}); len(value) > 0 { - for _, v := range value { - if vStr, ok := v.(string); ok && vStr != "" { - hosts = append(hosts, vStr) - } - } - } - reqData.Hosts = hosts - if value := d.Get("default_integrations").(bool); value { - reqData.IsDefault = &value - } - if value := d.Get("default_monitoring").(bool); value { - reqData.IsDefaultMonitoring = &value - } - if value, ok := d.Get("ca_sha256").(string); ok && value != "" { - reqData.CaSha256 = &value - } - if value, ok := d.Get("config_yaml").(string); ok && value != "" { - reqData.ConfigYaml = &value - } - - req := fleetapi.UpdateOutputJSONRequestBody{} - if err := req.FromOutputUpdateRequestElasticsearch(reqData); err != nil { - return diag.FromErr(err) - } - - _, diags = fleet.UpdateOutput(ctx, fleetClient, d.Id(), req) - if diags.HasError() { - return diags - } - - return nil -} - -func resourceOutputUpdateLogstash(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - fleetClient, diags := getFleetClient(d, meta) - if diags.HasError() { - return diags - } - - reqData := fleetapi.OutputUpdateRequestLogstash{ - Name: d.Get("name").(string), - Type: fleetapi.OutputUpdateRequestLogstashTypeLogstash, - } - - var hosts []string - if value := d.Get("hosts").([]interface{}); len(value) > 0 { - for _, v := range value { - if vStr, ok := v.(string); ok && vStr != "" { - hosts = append(hosts, vStr) - } - } - } - if hosts != nil { - reqData.Hosts = &hosts - } - if value := d.Get("default_integrations").(bool); value { - reqData.IsDefault = &value - } - if value := d.Get("default_monitoring").(bool); value { - reqData.IsDefaultMonitoring = &value - } - if value, ok := d.Get("ca_sha256").(string); ok && value != "" { - reqData.CaSha256 = &value - } - if value, ok := d.GetOk("ssl"); ok { - ssl := value.([]interface{})[0].(map[string]interface{}) - reqData.Ssl = &struct { - Certificate *string `json:"certificate,omitempty"` - CertificateAuthorities *[]string `json:"certificate_authorities,omitempty"` - Key *string `json:"key,omitempty"` - }{} - if value, ok := ssl["certificate_authorities"].([]interface{}); ok { - certs := make([]string, len(value)) - for i, v := range value { - certs[i] = v.(string) - } - reqData.Ssl.CertificateAuthorities = &certs - } - if value, ok := ssl["certificate"].(string); ok { - reqData.Ssl.Certificate = &value - } - if value, ok := ssl["key"].(string); ok { - reqData.Ssl.Key = &value - } - } - if value, ok := d.Get("config_yaml").(string); ok && value != "" { - reqData.ConfigYaml = &value - } - - req := fleetapi.UpdateOutputJSONRequestBody{} - if err := req.FromOutputUpdateRequestLogstash(reqData); err != nil { - return diag.FromErr(err) - } - - _, diags = fleet.UpdateOutput(ctx, fleetClient, d.Id(), req) - if diags.HasError() { - return diags - } - - return nil -} - -func resourceOutputUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - outputType := d.Get("type").(string) - switch outputType { - case "elasticsearch": - diags = resourceOutputUpdateElasticsearch(ctx, d, meta) - case "logstash": - diags = resourceOutputUpdateLogstash(ctx, d, meta) - } - if diags.HasError() { - return diags - } - - return resourceOutputRead(ctx, d, meta) -} - -func resourceOutputReadElasticsearch(d *schema.ResourceData, data fleetapi.OutputCreateRequestElasticsearch) diag.Diagnostics { - if err := d.Set("type", "elasticsearch"); err != nil { - return diag.FromErr(err) - } - if err := d.Set("name", data.Name); err != nil { - return diag.FromErr(err) - } - if data.Hosts != nil { - if err := d.Set("hosts", *data.Hosts); err != nil { - return diag.FromErr(err) - } - } - if err := d.Set("default_integrations", data.IsDefault); err != nil { - return diag.FromErr(err) - } - if data.IsDefaultMonitoring != nil { - if err := d.Set("default_monitoring", *data.IsDefaultMonitoring); err != nil { - return diag.FromErr(err) - } - } - if data.CaSha256 != nil { - if err := d.Set("ca_sha256", *data.CaSha256); err != nil { - return diag.FromErr(err) - } - } - if data.CaTrustedFingerprint != nil { - if err := d.Set("ca_trusted_fingerprint", *data.CaTrustedFingerprint); err != nil { - return diag.FromErr(err) - } - } - if data.ConfigYaml != nil { - if err := d.Set("config_yaml", *data.ConfigYaml); err != nil { - return diag.FromErr(err) - } - } - - return nil -} - -func resourceOutputReadLogstash(d *schema.ResourceData, data fleetapi.OutputCreateRequestLogstash) diag.Diagnostics { - if err := d.Set("type", "logstash"); err != nil { - return diag.FromErr(err) - } - if err := d.Set("name", data.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("hosts", data.Hosts); err != nil { - return diag.FromErr(err) - } - if err := d.Set("default_integrations", data.IsDefault); err != nil { - return diag.FromErr(err) - } - if data.IsDefaultMonitoring != nil { - if err := d.Set("default_monitoring", *data.IsDefaultMonitoring); err != nil { - return diag.FromErr(err) - } - } - if data.CaSha256 != nil { - if err := d.Set("ca_sha256", *data.CaSha256); err != nil { - return diag.FromErr(err) - } - } - if data.CaTrustedFingerprint != nil { - if err := d.Set("ca_trusted_fingerprint", *data.CaTrustedFingerprint); err != nil { - return diag.FromErr(err) - } - } - if err := d.Set("ssl", flattenSslConfig(data)); err != nil { - return diag.FromErr(err) - } - if data.ConfigYaml != nil { - if err := d.Set("config_yaml", *data.ConfigYaml); err != nil { - return diag.FromErr(err) - } - } - - return nil -} - -func resourceOutputRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - fleetClient, diags := getFleetClient(d, meta) - if diags.HasError() { - return diags - } - - rawOutput, diags := fleet.ReadOutput(ctx, fleetClient, d.Id()) - if diags.HasError() { - return diags - } - // Not found. - if rawOutput == nil { - d.SetId("") - return nil - } - - output, err := rawOutput.ValueByDiscriminator() - if err != nil { - return diag.FromErr(err) - } - switch outputType := output.(type) { - case fleetapi.OutputCreateRequestElasticsearch: - diags = resourceOutputReadElasticsearch(d, outputType) - case fleetapi.OutputCreateRequestLogstash: - diags = resourceOutputReadLogstash(d, outputType) - } - if err := d.Set("output_id", d.Id()); err != nil { - return diag.FromErr(err) - } - if diags.HasError() { - return diags - } - - return nil -} - -func resourceOutputDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - fleetClient, diags := getFleetClient(d, meta) - if diags.HasError() { - return diags - } - - if diags = fleet.DeleteOutput(ctx, fleetClient, d.Id()); diags.HasError() { - return diags - } - d.SetId("") - - return diags -} - -func flattenSslConfig(data fleetapi.OutputCreateRequestLogstash) []interface{} { - if data.Ssl == nil { - return []interface{}{} - } - - ssl := make(map[string]interface{}) - if data.Ssl.CertificateAuthorities != nil { - ssl["certificate_authorities"] = *data.Ssl.CertificateAuthorities - } - if data.Ssl.Certificate != nil { - ssl["certificate"] = *data.Ssl.Certificate - } - if data.Ssl.Key != nil { - ssl["key"] = *data.Ssl.Key - } - - return []interface{}{ssl} -} diff --git a/internal/fleet/shared.go b/internal/fleet/shared.go deleted file mode 100644 index da806e246..000000000 --- a/internal/fleet/shared.go +++ /dev/null @@ -1,21 +0,0 @@ -package fleet - -import ( - "github.com/elastic/terraform-provider-elasticstack/internal/clients" - "github.com/elastic/terraform-provider-elasticstack/internal/clients/fleet" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func getFleetClient(d *schema.ResourceData, meta interface{}) (*fleet.Client, diag.Diagnostics) { - client, diags := clients.NewApiClientFromSDKResource(d, meta) - if diags.HasError() { - return nil, diags - } - fleetClient, err := client.GetFleetClient() - if err != nil { - return nil, diag.FromErr(err) - } - - return fleetClient, nil -} diff --git a/internal/utils/tfsdk_test.go b/internal/utils/tfsdk_test.go index c62b5f9c4..19f1a9721 100644 --- a/internal/utils/tfsdk_test.go +++ b/internal/utils/tfsdk_test.go @@ -72,6 +72,7 @@ var ( "k2": {ID: "id2"}, "k3": {ID: "id3"}, } + normUnk = jsontypes.NewNormalizedUnknown() normNil = jsontypes.NewNormalizedNull() normEmpty = jsontypes.NewNormalizedValue(`{}`) diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go index a8bbb5f2a..40df41f6a 100644 --- a/provider/plugin_framework.go +++ b/provider/plugin_framework.go @@ -12,6 +12,7 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/fleet/integration" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/integration_ds" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/integration_policy" + "github.com/elastic/terraform-provider-elasticstack/internal/fleet/output" "github.com/elastic/terraform-provider-elasticstack/internal/fleet/server_host" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/data_view" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/import_saved_objects" @@ -93,6 +94,7 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { agent_policy.NewResource, integration.NewResource, integration_policy.NewResource, + output.NewResource, server_host.NewResource, } } diff --git a/provider/provider.go b/provider/provider.go index 835e0a7d4..b86ae0ab1 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -10,7 +10,6 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/security" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/transform" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/watcher" - "github.com/elastic/terraform-provider-elasticstack/internal/fleet" "github.com/elastic/terraform-provider-elasticstack/internal/kibana" providerSchema "github.com/elastic/terraform-provider-elasticstack/internal/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -107,8 +106,6 @@ func New(version string) *schema.Provider { "elasticstack_kibana_action_connector": kibana.ResourceActionConnector(), "elasticstack_kibana_security_role": kibana.ResourceRole(), "elasticstack_kibana_slo": kibana.ResourceSlo(), - - "elasticstack_fleet_output": fleet.ResourceOutput(), }, }