diff --git a/.windsurf/rules b/.windsurf/rules new file mode 100644 index 000000000..f10e9e643 --- /dev/null +++ b/.windsurf/rules @@ -0,0 +1,9 @@ +* The project is a Terraform provider, written in Golang +* The provider manages Elastic Stack resources +* There is a mix of SDK, and plugin framework resources +* New resources should be written with the Terraform plugin framework +* Plugin framework resources should: + * Follow the example set in /internal/elasticsearch/security/system_user + * Be contained within their own module + * Have the main concepts split across their own files + * Include acceptance tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 91197a939..84f48e65c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [Unreleased] +- Add `elasticstack_apm_agent_configuration` resource ([#1196](https://github.com/elastic/terraform-provider-elasticstack/pull/1196)) - Add support for `timeslice_metric_indicator` in `elasticstack_kibana_slo` ([#1195](https://github.com/elastic/terraform-provider-elasticstack/pull/1195)) - Add `elasticstack_elasticsearch_ingest_processor_reroute` data source ([#678](https://github.com/elastic/terraform-provider-elasticstack/issues/678)) - Add support for `supports_agentless` to `elasticstack_fleet_agent_policy` ([#1197](https://github.com/elastic/terraform-provider-elasticstack/pull/1197)) diff --git a/docs/resources/apm_agent_configuration.md b/docs/resources/apm_agent_configuration.md new file mode 100644 index 000000000..8df89ceae --- /dev/null +++ b/docs/resources/apm_agent_configuration.md @@ -0,0 +1,54 @@ +--- +subcategory: "Kibana" +layout: "" +page_title: "Elasticstack: elasticstack_apm_agent_configuration Resource" +description: |- + Creates or updates an APM agent configuration +--- + +# Resource: elasticstack_apm_agent_configuration + +Creates or updates an APM agent configuration. See https://www.elastic.co/docs/solutions/observability/apm/apm-agent-central-configuration + +## Example Usage + +```terraform +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_apm_agent_configuration" "test_config" { + service_name = "my-service" + service_environment = "production" + agent_name = "go" + settings = { + "transaction_sample_rate" = "0.5" + "capture_body" = "all" + } +} +``` + + +## Schema + +### Required + +- `service_name` (String) The name of the service. +- `settings` (Map of String) Agent configuration settings. + +### Optional + +- `agent_name` (String) The agent name is used by the UI to determine which settings to display. +- `service_environment` (String) The environment of the service. + +### Read-Only + +- `id` (String) Internal identifier of the resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import elasticstack_apm_agent_configuration.test_configuration my-service:production +``` diff --git a/examples/resources/elasticstack_apm_agent_configuration/import.sh b/examples/resources/elasticstack_apm_agent_configuration/import.sh new file mode 100644 index 000000000..495c00058 --- /dev/null +++ b/examples/resources/elasticstack_apm_agent_configuration/import.sh @@ -0,0 +1 @@ +terraform import elasticstack_apm_agent_configuration.test_configuration my-service:production diff --git a/examples/resources/elasticstack_apm_agent_configuration/resource.tf b/examples/resources/elasticstack_apm_agent_configuration/resource.tf new file mode 100644 index 000000000..9c9e5d9ce --- /dev/null +++ b/examples/resources/elasticstack_apm_agent_configuration/resource.tf @@ -0,0 +1,13 @@ +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_apm_agent_configuration" "test_config" { + service_name = "my-service" + service_environment = "production" + agent_name = "go" + settings = { + "transaction_sample_rate" = "0.5" + "capture_body" = "all" + } +} diff --git a/generated/kbapi/kibana.gen.go b/generated/kbapi/kibana.gen.go index 060792f2c..52a36324e 100644 --- a/generated/kbapi/kibana.gen.go +++ b/generated/kbapi/kibana.gen.go @@ -498,6 +498,26 @@ const ( UpdateOutputSslVerificationModeStrict UpdateOutputSslVerificationMode = "strict" ) +// Defines values for APMUIElasticApiVersion. +const ( + APMUIElasticApiVersionN20231031 APMUIElasticApiVersion = "2023-10-31" +) + +// Defines values for DeleteAgentConfigurationParamsElasticApiVersion. +const ( + DeleteAgentConfigurationParamsElasticApiVersionN20231031 DeleteAgentConfigurationParamsElasticApiVersion = "2023-10-31" +) + +// Defines values for GetAgentConfigurationsParamsElasticApiVersion. +const ( + GetAgentConfigurationsParamsElasticApiVersionN20231031 GetAgentConfigurationsParamsElasticApiVersion = "2023-10-31" +) + +// Defines values for CreateUpdateAgentConfigurationParamsElasticApiVersion. +const ( + CreateUpdateAgentConfigurationParamsElasticApiVersionN20231031 CreateUpdateAgentConfigurationParamsElasticApiVersion = "2023-10-31" +) + // Defines values for GetFleetAgentPoliciesParamsSortOrder. const ( GetFleetAgentPoliciesParamsSortOrderAsc GetFleetAgentPoliciesParamsSortOrder = "asc" @@ -580,6 +600,117 @@ const ( Simplified PutFleetPackagePoliciesPackagepolicyidParamsFormat = "simplified" ) +// APMUI400Response defines model for APM_UI_400_response. +type APMUI400Response struct { + // Error Error type + Error *string `json:"error,omitempty"` + + // Message Error message + Message *string `json:"message,omitempty"` + + // StatusCode Error status code + StatusCode *float32 `json:"statusCode,omitempty"` +} + +// APMUI401Response defines model for APM_UI_401_response. +type APMUI401Response struct { + // Error Error type + Error *string `json:"error,omitempty"` + + // Message Error message + Message *string `json:"message,omitempty"` + + // StatusCode Error status code + StatusCode *float32 `json:"statusCode,omitempty"` +} + +// APMUI403Response defines model for APM_UI_403_response. +type APMUI403Response struct { + // Error Error type + Error *string `json:"error,omitempty"` + + // Message Error message + Message *string `json:"message,omitempty"` + + // StatusCode Error status code + StatusCode *float32 `json:"statusCode,omitempty"` +} + +// APMUI404Response defines model for APM_UI_404_response. +type APMUI404Response struct { + // Error Error type + Error *string `json:"error,omitempty"` + + // Message Error message + Message *string `json:"message,omitempty"` + + // StatusCode Error status code + StatusCode *float32 `json:"statusCode,omitempty"` +} + +// APMUIAgentConfigurationIntakeObject defines model for APM_UI_agent_configuration_intake_object. +type APMUIAgentConfigurationIntakeObject struct { + // AgentName The agent name is used by the UI to determine which settings to display. + AgentName *string `json:"agent_name,omitempty"` + + // Service Service + Service APMUIServiceObject `json:"service"` + + // Settings Agent configuration settings + Settings APMUISettingsObject `json:"settings"` +} + +// APMUIAgentConfigurationObject Agent configuration +type APMUIAgentConfigurationObject struct { + // Timestamp Timestamp + Timestamp float32 `json:"@timestamp"` + + // AgentName Agent name + AgentName *string `json:"agent_name,omitempty"` + + // AppliedByAgent Applied by agent + AppliedByAgent *bool `json:"applied_by_agent,omitempty"` + + // Etag `etag` is sent by the APM agent to indicate the `etag` of the last successfully applied configuration. If the `etag` matches an existing configuration its `applied_by_agent` property will be set to `true`. Every time a configuration is edited `applied_by_agent` is reset to `false`. + Etag string `json:"etag"` + + // Service Service + Service APMUIServiceObject `json:"service"` + + // Settings Agent configuration settings + Settings APMUISettingsObject `json:"settings"` +} + +// APMUIAgentConfigurationsResponse defines model for APM_UI_agent_configurations_response. +type APMUIAgentConfigurationsResponse struct { + // Configurations Agent configuration + Configurations *[]APMUIAgentConfigurationObject `json:"configurations,omitempty"` +} + +// APMUIDeleteAgentConfigurationsResponse defines model for APM_UI_delete_agent_configurations_response. +type APMUIDeleteAgentConfigurationsResponse struct { + // Result Result + Result *string `json:"result,omitempty"` +} + +// APMUIDeleteServiceObject Service +type APMUIDeleteServiceObject struct { + // Service Service + Service APMUIServiceObject `json:"service"` +} + +// APMUIServiceObject Service +type APMUIServiceObject struct { + // Environment The environment of the service. + Environment *string `json:"environment,omitempty"` + + // Name The name of the service. + Name *string `json:"name,omitempty"` +} + +// APMUISettingsObject Agent configuration settings +type APMUISettingsObject map[string]string + // DataViews400Response defines model for Data_views_400_response. type DataViews400Response struct { Error string `json:"error"` @@ -2900,12 +3031,45 @@ type UpdateOutputUnion struct { union json.RawMessage } +// APMUIElasticApiVersion defines model for APM_UI_elastic_api_version. +type APMUIElasticApiVersion string + // DataViewsViewId defines model for Data_views_view_id. type DataViewsViewId = string // SpaceId defines model for spaceId. type SpaceId = string +// DeleteAgentConfigurationParams defines parameters for DeleteAgentConfiguration. +type DeleteAgentConfigurationParams struct { + // ElasticApiVersion The version of the API to use + ElasticApiVersion DeleteAgentConfigurationParamsElasticApiVersion `json:"elastic-api-version"` +} + +// DeleteAgentConfigurationParamsElasticApiVersion defines parameters for DeleteAgentConfiguration. +type DeleteAgentConfigurationParamsElasticApiVersion string + +// GetAgentConfigurationsParams defines parameters for GetAgentConfigurations. +type GetAgentConfigurationsParams struct { + // ElasticApiVersion The version of the API to use + ElasticApiVersion GetAgentConfigurationsParamsElasticApiVersion `json:"elastic-api-version"` +} + +// GetAgentConfigurationsParamsElasticApiVersion defines parameters for GetAgentConfigurations. +type GetAgentConfigurationsParamsElasticApiVersion string + +// CreateUpdateAgentConfigurationParams defines parameters for CreateUpdateAgentConfiguration. +type CreateUpdateAgentConfigurationParams struct { + // Overwrite If the config exists ?overwrite=true is required + Overwrite *bool `form:"overwrite,omitempty" json:"overwrite,omitempty"` + + // ElasticApiVersion The version of the API to use + ElasticApiVersion CreateUpdateAgentConfigurationParamsElasticApiVersion `json:"elastic-api-version"` +} + +// CreateUpdateAgentConfigurationParamsElasticApiVersion defines parameters for CreateUpdateAgentConfiguration. +type CreateUpdateAgentConfigurationParamsElasticApiVersion string + // GetFleetAgentPoliciesParams defines parameters for GetFleetAgentPolicies. type GetFleetAgentPoliciesParams struct { Page *float32 `form:"page,omitempty" json:"page,omitempty"` @@ -3369,6 +3533,12 @@ type PutParameterJSONBody struct { Value *string `json:"value,omitempty"` } +// DeleteAgentConfigurationJSONRequestBody defines body for DeleteAgentConfiguration for application/json ContentType. +type DeleteAgentConfigurationJSONRequestBody = APMUIDeleteServiceObject + +// CreateUpdateAgentConfigurationJSONRequestBody defines body for CreateUpdateAgentConfiguration for application/json ContentType. +type CreateUpdateAgentConfigurationJSONRequestBody = APMUIAgentConfigurationIntakeObject + // PostFleetAgentPoliciesJSONRequestBody defines body for PostFleetAgentPolicies for application/json ContentType. type PostFleetAgentPoliciesJSONRequestBody PostFleetAgentPoliciesJSONBody @@ -14268,6 +14438,19 @@ func WithRequestEditorFn(fn RequestEditorFn) ClientOption { // The interface specification for the client above. type ClientInterface interface { + // DeleteAgentConfigurationWithBody request with any body + DeleteAgentConfigurationWithBody(ctx context.Context, params *DeleteAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + DeleteAgentConfiguration(ctx context.Context, params *DeleteAgentConfigurationParams, body DeleteAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAgentConfigurations request + GetAgentConfigurations(ctx context.Context, params *GetAgentConfigurationsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateUpdateAgentConfigurationWithBody request with any body + CreateUpdateAgentConfigurationWithBody(ctx context.Context, params *CreateUpdateAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateUpdateAgentConfiguration(ctx context.Context, params *CreateUpdateAgentConfigurationParams, body CreateUpdateAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetFleetAgentPolicies request GetFleetAgentPolicies(ctx context.Context, params *GetFleetAgentPoliciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -14402,6 +14585,66 @@ type ClientInterface interface { UpdateDataViewDefault(ctx context.Context, spaceId SpaceId, viewId DataViewsViewId, body UpdateDataViewDefaultJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } +func (c *Client) DeleteAgentConfigurationWithBody(ctx context.Context, params *DeleteAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteAgentConfigurationRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteAgentConfiguration(ctx context.Context, params *DeleteAgentConfigurationParams, body DeleteAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteAgentConfigurationRequest(c.Server, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAgentConfigurations(ctx context.Context, params *GetAgentConfigurationsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAgentConfigurationsRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateUpdateAgentConfigurationWithBody(ctx context.Context, params *CreateUpdateAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateUpdateAgentConfigurationRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateUpdateAgentConfiguration(ctx context.Context, params *CreateUpdateAgentConfigurationParams, body CreateUpdateAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateUpdateAgentConfigurationRequest(c.Server, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetFleetAgentPolicies(ctx context.Context, params *GetFleetAgentPoliciesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetFleetAgentPoliciesRequest(c.Server, params) if err != nil { @@ -14990,6 +15233,174 @@ func (c *Client) UpdateDataViewDefault(ctx context.Context, spaceId SpaceId, vie return c.Client.Do(req) } +// NewDeleteAgentConfigurationRequest calls the generic DeleteAgentConfiguration builder with application/json body +func NewDeleteAgentConfigurationRequest(server string, params *DeleteAgentConfigurationParams, body DeleteAgentConfigurationJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewDeleteAgentConfigurationRequestWithBody(server, params, "application/json", bodyReader) +} + +// NewDeleteAgentConfigurationRequestWithBody generates requests for DeleteAgentConfiguration with any type of body +func NewDeleteAgentConfigurationRequestWithBody(server string, params *DeleteAgentConfigurationParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/apm/settings/agent-configuration") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "elastic-api-version", runtime.ParamLocationHeader, params.ElasticApiVersion) + if err != nil { + return nil, err + } + + req.Header.Set("elastic-api-version", headerParam0) + + } + + return req, nil +} + +// NewGetAgentConfigurationsRequest generates requests for GetAgentConfigurations +func NewGetAgentConfigurationsRequest(server string, params *GetAgentConfigurationsParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/apm/settings/agent-configuration") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "elastic-api-version", runtime.ParamLocationHeader, params.ElasticApiVersion) + if err != nil { + return nil, err + } + + req.Header.Set("elastic-api-version", headerParam0) + + } + + return req, nil +} + +// NewCreateUpdateAgentConfigurationRequest calls the generic CreateUpdateAgentConfiguration builder with application/json body +func NewCreateUpdateAgentConfigurationRequest(server string, params *CreateUpdateAgentConfigurationParams, body CreateUpdateAgentConfigurationJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateUpdateAgentConfigurationRequestWithBody(server, params, "application/json", bodyReader) +} + +// NewCreateUpdateAgentConfigurationRequestWithBody generates requests for CreateUpdateAgentConfiguration with any type of body +func NewCreateUpdateAgentConfigurationRequestWithBody(server string, params *CreateUpdateAgentConfigurationParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/apm/settings/agent-configuration") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Overwrite != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "overwrite", runtime.ParamLocationQuery, *params.Overwrite); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "elastic-api-version", runtime.ParamLocationHeader, params.ElasticApiVersion) + if err != nil { + return nil, err + } + + req.Header.Set("elastic-api-version", headerParam0) + + } + + return req, nil +} + // NewGetFleetAgentPoliciesRequest generates requests for GetFleetAgentPolicies func NewGetFleetAgentPoliciesRequest(server string, params *GetFleetAgentPoliciesParams) (*http.Request, error) { var err error @@ -17120,6 +17531,19 @@ func WithBaseURL(baseURL string) ClientOption { // ClientWithResponsesInterface is the interface specification for the client with responses above. type ClientWithResponsesInterface interface { + // DeleteAgentConfigurationWithBodyWithResponse request with any body + DeleteAgentConfigurationWithBodyWithResponse(ctx context.Context, params *DeleteAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteAgentConfigurationResponse, error) + + DeleteAgentConfigurationWithResponse(ctx context.Context, params *DeleteAgentConfigurationParams, body DeleteAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteAgentConfigurationResponse, error) + + // GetAgentConfigurationsWithResponse request + GetAgentConfigurationsWithResponse(ctx context.Context, params *GetAgentConfigurationsParams, reqEditors ...RequestEditorFn) (*GetAgentConfigurationsResponse, error) + + // CreateUpdateAgentConfigurationWithBodyWithResponse request with any body + CreateUpdateAgentConfigurationWithBodyWithResponse(ctx context.Context, params *CreateUpdateAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateUpdateAgentConfigurationResponse, error) + + CreateUpdateAgentConfigurationWithResponse(ctx context.Context, params *CreateUpdateAgentConfigurationParams, body CreateUpdateAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateUpdateAgentConfigurationResponse, error) + // GetFleetAgentPoliciesWithResponse request GetFleetAgentPoliciesWithResponse(ctx context.Context, params *GetFleetAgentPoliciesParams, reqEditors ...RequestEditorFn) (*GetFleetAgentPoliciesResponse, error) @@ -17254,6 +17678,83 @@ type ClientWithResponsesInterface interface { UpdateDataViewDefaultWithResponse(ctx context.Context, spaceId SpaceId, viewId DataViewsViewId, body UpdateDataViewDefaultJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateDataViewDefaultResponse, error) } +type DeleteAgentConfigurationResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *APMUIDeleteAgentConfigurationsResponse + JSON400 *APMUI400Response + JSON401 *APMUI401Response + JSON403 *APMUI403Response + JSON404 *APMUI404Response +} + +// Status returns HTTPResponse.Status +func (r DeleteAgentConfigurationResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteAgentConfigurationResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAgentConfigurationsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *APMUIAgentConfigurationsResponse + JSON400 *APMUI400Response + JSON401 *APMUI401Response + JSON404 *APMUI404Response +} + +// Status returns HTTPResponse.Status +func (r GetAgentConfigurationsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAgentConfigurationsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateUpdateAgentConfigurationResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *map[string]interface{} + JSON400 *APMUI400Response + JSON401 *APMUI401Response + JSON403 *APMUI403Response + JSON404 *APMUI404Response +} + +// Status returns HTTPResponse.Status +func (r CreateUpdateAgentConfigurationResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateUpdateAgentConfigurationResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type GetFleetAgentPoliciesResponse struct { Body []byte HTTPResponse *http.Response @@ -18360,6 +18861,49 @@ func (r UpdateDataViewDefaultResponse) StatusCode() int { return 0 } +// DeleteAgentConfigurationWithBodyWithResponse request with arbitrary body returning *DeleteAgentConfigurationResponse +func (c *ClientWithResponses) DeleteAgentConfigurationWithBodyWithResponse(ctx context.Context, params *DeleteAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*DeleteAgentConfigurationResponse, error) { + rsp, err := c.DeleteAgentConfigurationWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteAgentConfigurationResponse(rsp) +} + +func (c *ClientWithResponses) DeleteAgentConfigurationWithResponse(ctx context.Context, params *DeleteAgentConfigurationParams, body DeleteAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*DeleteAgentConfigurationResponse, error) { + rsp, err := c.DeleteAgentConfiguration(ctx, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteAgentConfigurationResponse(rsp) +} + +// GetAgentConfigurationsWithResponse request returning *GetAgentConfigurationsResponse +func (c *ClientWithResponses) GetAgentConfigurationsWithResponse(ctx context.Context, params *GetAgentConfigurationsParams, reqEditors ...RequestEditorFn) (*GetAgentConfigurationsResponse, error) { + rsp, err := c.GetAgentConfigurations(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAgentConfigurationsResponse(rsp) +} + +// CreateUpdateAgentConfigurationWithBodyWithResponse request with arbitrary body returning *CreateUpdateAgentConfigurationResponse +func (c *ClientWithResponses) CreateUpdateAgentConfigurationWithBodyWithResponse(ctx context.Context, params *CreateUpdateAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateUpdateAgentConfigurationResponse, error) { + rsp, err := c.CreateUpdateAgentConfigurationWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateUpdateAgentConfigurationResponse(rsp) +} + +func (c *ClientWithResponses) CreateUpdateAgentConfigurationWithResponse(ctx context.Context, params *CreateUpdateAgentConfigurationParams, body CreateUpdateAgentConfigurationJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateUpdateAgentConfigurationResponse, error) { + rsp, err := c.CreateUpdateAgentConfiguration(ctx, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateUpdateAgentConfigurationResponse(rsp) +} + // GetFleetAgentPoliciesWithResponse request returning *GetFleetAgentPoliciesResponse func (c *ClientWithResponses) GetFleetAgentPoliciesWithResponse(ctx context.Context, params *GetFleetAgentPoliciesParams, reqEditors ...RequestEditorFn) (*GetFleetAgentPoliciesResponse, error) { rsp, err := c.GetFleetAgentPolicies(ctx, params, reqEditors...) @@ -18787,6 +19331,161 @@ func (c *ClientWithResponses) UpdateDataViewDefaultWithResponse(ctx context.Cont return ParseUpdateDataViewDefaultResponse(rsp) } +// ParseDeleteAgentConfigurationResponse parses an HTTP response from a DeleteAgentConfigurationWithResponse call +func ParseDeleteAgentConfigurationResponse(rsp *http.Response) (*DeleteAgentConfigurationResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteAgentConfigurationResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest APMUIDeleteAgentConfigurationsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest APMUI400Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest APMUI401Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest APMUI403Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest APMUI404Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseGetAgentConfigurationsResponse parses an HTTP response from a GetAgentConfigurationsWithResponse call +func ParseGetAgentConfigurationsResponse(rsp *http.Response) (*GetAgentConfigurationsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAgentConfigurationsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest APMUIAgentConfigurationsResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest APMUI400Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest APMUI401Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest APMUI404Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + +// ParseCreateUpdateAgentConfigurationResponse parses an HTTP response from a CreateUpdateAgentConfigurationWithResponse call +func ParseCreateUpdateAgentConfigurationResponse(rsp *http.Response) (*CreateUpdateAgentConfigurationResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateUpdateAgentConfigurationResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest map[string]interface{} + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest APMUI400Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest APMUI401Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest APMUI403Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest APMUI404Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + } + + return response, nil +} + // ParseGetFleetAgentPoliciesResponse parses an HTTP response from a GetFleetAgentPoliciesWithResponse call func ParseGetFleetAgentPoliciesResponse(rsp *http.Response) (*GetFleetAgentPoliciesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/generated/kbapi/transform_schema.go b/generated/kbapi/transform_schema.go index 130f45f0a..11453eb8a 100644 --- a/generated/kbapi/transform_schema.go +++ b/generated/kbapi/transform_schema.go @@ -570,6 +570,7 @@ func transformFilterPaths(schema *Schema) { "/api/fleet/package_policies/{packagePolicyId}": {"get", "put", "delete"}, "/api/synthetics/params": {"post"}, "/api/synthetics/params/{id}": {"get", "put", "delete"}, + "/api/apm/settings/agent-configuration": {"get", "put", "delete"}, } for path, pathInfo := range schema.Paths { diff --git a/internal/apm/agent_configuration/create.go b/internal/apm/agent_configuration/create.go new file mode 100644 index 000000000..561ce0b05 --- /dev/null +++ b/internal/apm/agent_configuration/create.go @@ -0,0 +1,71 @@ +package agent_configuration + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +const elasticAPIVersion = "2023-10-31" + +func (r *resourceAgentConfiguration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan AgentConfiguration + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + kibana, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError("Unable to get Kibana client", err.Error()) + return + } + + settings := make(map[string]string) + resp.Diagnostics.Append(plan.Settings.ElementsAs(ctx, &settings, false)...) + if resp.Diagnostics.HasError() { + return + } + + agentConfig := kbapi.CreateUpdateAgentConfigurationJSONRequestBody{ + AgentName: plan.AgentName.ValueStringPointer(), + Service: kbapi.APMUIServiceObject{ + Name: plan.ServiceName.ValueStringPointer(), + Environment: plan.ServiceEnvironment.ValueStringPointer(), + }, + Settings: settings, + } + + apiResp, err := kibana.API.CreateUpdateAgentConfiguration( + ctx, + &kbapi.CreateUpdateAgentConfigurationParams{ + ElasticApiVersion: elasticAPIVersion, + }, + agentConfig, + ) + if err != nil { + resp.Diagnostics.AddError("Failed to create APM agent configuration", err.Error()) + return + } + defer apiResp.Body.Close() + + if diags := utils.CheckHttpErrorFromFW(apiResp, "Failed to create APM agent configuration"); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + plan.SetIDFromService() + + updatedState, diags := r.read(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Trace(ctx, fmt.Sprintf("Created APM agent configuration with ID: %s", updatedState.ID.ValueString())) + resp.Diagnostics.Append(resp.State.Set(ctx, updatedState)...) +} diff --git a/internal/apm/agent_configuration/delete.go b/internal/apm/agent_configuration/delete.go new file mode 100644 index 000000000..4b1d60432 --- /dev/null +++ b/internal/apm/agent_configuration/delete.go @@ -0,0 +1,59 @@ +package agent_configuration + +import ( + "context" + "fmt" + "strings" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func (r *resourceAgentConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state AgentConfiguration + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + kibana, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError("Unable to get Kibana client", err.Error()) + return + } + + idParts := strings.Split(state.ID.ValueString(), ":") + serviceName := idParts[0] + var serviceEnv *string + if len(idParts) > 1 { + serviceEnv = &idParts[1] + } + + deleteReqBody := kbapi.APMUIDeleteServiceObject{ + Service: kbapi.APMUIServiceObject{ + Name: &serviceName, + Environment: serviceEnv, + }, + } + apiResp, err := kibana.API.DeleteAgentConfiguration( + ctx, + &kbapi.DeleteAgentConfigurationParams{ + ElasticApiVersion: elasticAPIVersion, + }, + deleteReqBody, + ) + if err != nil { + resp.Diagnostics.AddError("Failed to delete APM agent configuration", err.Error()) + return + } + defer apiResp.Body.Close() + + if diags := utils.CheckHttpErrorFromFW(apiResp, "Failed to delete APM agent configuration"); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + tflog.Trace(ctx, fmt.Sprintf("Deleted APM agent configuration with ID: %s", state.ID.ValueString())) +} diff --git a/internal/apm/agent_configuration/models.go b/internal/apm/agent_configuration/models.go new file mode 100644 index 000000000..3bbaf1950 --- /dev/null +++ b/internal/apm/agent_configuration/models.go @@ -0,0 +1,27 @@ +package agent_configuration + +import ( + "strings" + + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// AgentConfiguration holds the agent configuration. +type AgentConfiguration struct { + ID types.String `tfsdk:"id"` + ServiceName types.String `tfsdk:"service_name"` + ServiceEnvironment types.String `tfsdk:"service_environment"` + AgentName types.String `tfsdk:"agent_name"` + Settings types.Map `tfsdk:"settings"` +} + +func (ac *AgentConfiguration) SetIDFromService() string { + parts := []string{ac.ServiceName.ValueString()} + if !ac.ServiceEnvironment.IsNull() && !ac.ServiceEnvironment.IsUnknown() && ac.ServiceEnvironment.ValueString() != "" { + parts = append(parts, ac.ServiceEnvironment.ValueString()) + } + + id := strings.Join(parts, ":") + ac.ID = types.StringValue(id) + return id +} diff --git a/internal/apm/agent_configuration/read.go b/internal/apm/agent_configuration/read.go new file mode 100644 index 000000000..5c81e60da --- /dev/null +++ b/internal/apm/agent_configuration/read.go @@ -0,0 +1,111 @@ +package agent_configuration + +import ( + "context" + "fmt" + "strings" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *resourceAgentConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state AgentConfiguration + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + updatedState, diags := r.read(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if updatedState == nil { + resp.State.RemoveResource(ctx) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, updatedState)...) +} + +func (r *resourceAgentConfiguration) read(ctx context.Context, state *AgentConfiguration) (*AgentConfiguration, diag.Diagnostics) { + var diags diag.Diagnostics + + kibana, err := r.client.GetKibanaOapiClient() + if err != nil { + diags.AddError("Unable to get Kibana client", err.Error()) + return nil, diags + } + + apiResp, err := kibana.API.GetAgentConfigurationsWithResponse( + ctx, + &kbapi.GetAgentConfigurationsParams{ + ElasticApiVersion: elasticAPIVersion, + }, + ) + if err != nil { + diags.AddError("Failed to get APM agent configurations", err.Error()) + return nil, diags + } + + if httpDiags := utils.CheckHttpErrorFromFW(apiResp.HTTPResponse, "Failed to get APM agent configurations"); httpDiags.HasError() { + diags.Append(httpDiags...) + return nil, diags + } + + if apiResp.JSON200 == nil { + diags.AddError("Failed to get APM agent configurations from body", "Expected 200 response body to not be nil") + return nil, diags + } + + idFromState := state.ID.ValueString() + var foundConfig *kbapi.APMUIAgentConfigurationObject + for _, config := range *apiResp.JSON200.Configurations { + if config.Service.Name == nil { + continue + } + idFromAPI := createAgentConfigIDfromAPI(config) + if idFromAPI == idFromState { + foundConfig = &config + break + } + } + + if foundConfig == nil { + return nil, diags + } + + state.ID = types.StringValue(idFromState) + state.ServiceName = types.StringPointerValue(foundConfig.Service.Name) + state.ServiceEnvironment = types.StringPointerValue(foundConfig.Service.Environment) + state.AgentName = types.StringPointerValue(foundConfig.AgentName) + + stringSettings := make(map[string]interface{}) + if foundConfig.Settings != nil { + for k, v := range foundConfig.Settings { + stringSettings[k] = fmt.Sprintf("%v", v) + } + } + + settings, mapDiags := types.MapValueFrom(ctx, types.StringType, stringSettings) + diags.Append(mapDiags...) + if diags.HasError() { + return nil, diags + } + state.Settings = settings + + return state, diags +} + +func createAgentConfigIDfromAPI(config kbapi.APMUIAgentConfigurationObject) string { + parts := []string{*config.Service.Name} + if config.Service.Environment != nil && *config.Service.Environment != "" { + parts = append(parts, *config.Service.Environment) + } + return strings.Join(parts, ":") +} diff --git a/internal/apm/agent_configuration/resource.go b/internal/apm/agent_configuration/resource.go new file mode 100644 index 000000000..ee609ab1e --- /dev/null +++ b/internal/apm/agent_configuration/resource.go @@ -0,0 +1,36 @@ +package agent_configuration + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &resourceAgentConfiguration{} +var _ resource.ResourceWithConfigure = &resourceAgentConfiguration{} +var _ resource.ResourceWithImportState = &resourceAgentConfiguration{} + +func NewAgentConfigurationResource() resource.Resource { + return &resourceAgentConfiguration{} +} + +type resourceAgentConfiguration struct { + client *clients.ApiClient +} + +func (r *resourceAgentConfiguration) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_apm_agent_configuration" +} + +func (r *resourceAgentConfiguration) 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 *resourceAgentConfiguration) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) +} diff --git a/internal/apm/agent_configuration/resource_test.go b/internal/apm/agent_configuration/resource_test.go new file mode 100644 index 000000000..4f9e0a828 --- /dev/null +++ b/internal/apm/agent_configuration/resource_test.go @@ -0,0 +1,77 @@ +package agent_configuration_test + +import ( + "fmt" + "testing" + + "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + tf_acctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccResourceAgentConfiguration(t *testing.T) { + serviceName := tf_acctest.RandStringFromCharSet(10, tf_acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccResourceAgentConfigurationCreate(serviceName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "service_name", serviceName), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "service_environment", "production"), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "agent_name", "go"), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "settings.transaction_sample_rate", "0.5"), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "settings.capture_body", "all"), + ), + }, + { + Config: testAccResourceAgentConfigurationUpdate(serviceName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "service_name", serviceName), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "service_environment", "production"), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "agent_name", "java"), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "settings.transaction_sample_rate", "0.8"), + resource.TestCheckResourceAttr("elasticstack_apm_agent_configuration.test_config", "settings.capture_body", "off"), + ), + }, + }, + }) +} + +func testAccResourceAgentConfigurationCreate(serviceName string) string { + return fmt.Sprintf(` + provider "elasticstack" { + kibana {} + } + + resource "elasticstack_apm_agent_configuration" "test_config" { + service_name = "%s" + service_environment = "production" + agent_name = "go" + settings = { + "transaction_sample_rate" = "0.5" + "capture_body" = "all" + } + } + `, serviceName) +} + +func testAccResourceAgentConfigurationUpdate(serviceName string) string { + return fmt.Sprintf(` + provider "elasticstack" { + kibana {} + } + + resource "elasticstack_apm_agent_configuration" "test_config" { + service_name = "%s" + service_environment = "production" + agent_name = "java" + settings = { + "transaction_sample_rate" = "0.8" + "capture_body" = "off" + } + } + `, serviceName) +} diff --git a/internal/apm/agent_configuration/schema.go b/internal/apm/agent_configuration/schema.go new file mode 100644 index 000000000..f3d73269f --- /dev/null +++ b/internal/apm/agent_configuration/schema.go @@ -0,0 +1,44 @@ +package agent_configuration + +import ( + "context" + + "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" +) + +func (r *resourceAgentConfiguration) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Manages APM agent configuration.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Internal identifier of the resource.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "service_name": schema.StringAttribute{ + Description: "The name of the service.", + Required: true, + }, + "service_environment": schema.StringAttribute{ + Description: "The environment of the service.", + Optional: true, + }, + "agent_name": schema.StringAttribute{ + Description: "The agent name is used by the UI to determine which settings to display.", + Optional: true, + }, + "settings": schema.MapAttribute{ + Description: "Agent configuration settings.", + Required: true, + ElementType: types.StringType, + }, + }, + } +} diff --git a/internal/apm/agent_configuration/update.go b/internal/apm/agent_configuration/update.go new file mode 100644 index 000000000..63f7c68c6 --- /dev/null +++ b/internal/apm/agent_configuration/update.go @@ -0,0 +1,67 @@ +package agent_configuration + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func (r *resourceAgentConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan AgentConfiguration + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + kibana, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError("Unable to get Kibana client", err.Error()) + return + } + + settings := make(map[string]string) + resp.Diagnostics.Append(plan.Settings.ElementsAs(ctx, &settings, false)...) + if resp.Diagnostics.HasError() { + return + } + + agentConfig := kbapi.CreateUpdateAgentConfigurationJSONRequestBody{ + AgentName: plan.AgentName.ValueStringPointer(), + Service: kbapi.APMUIServiceObject{ + Name: plan.ServiceName.ValueStringPointer(), + Environment: plan.ServiceEnvironment.ValueStringPointer(), + }, + Settings: settings, + } + + overwrite := true + params := &kbapi.CreateUpdateAgentConfigurationParams{ + Overwrite: &overwrite, + ElasticApiVersion: elasticAPIVersion, + } + + apiResp, err := kibana.API.CreateUpdateAgentConfiguration(ctx, params, agentConfig) + if err != nil { + resp.Diagnostics.AddError("Failed to update APM agent configuration", err.Error()) + return + } + defer apiResp.Body.Close() + + if diags := utils.CheckHttpErrorFromFW(apiResp, "Failed to update APM agent configuration"); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + updatedState, diags := r.read(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Trace(ctx, fmt.Sprintf("Updated APM agent configuration with ID: %s", updatedState.ID.ValueString())) + resp.Diagnostics.Append(resp.State.Set(ctx, updatedState)...) +} diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go index fcc31bfe6..1da87c988 100644 --- a/provider/plugin_framework.go +++ b/provider/plugin_framework.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/elastic/terraform-provider-elasticstack/internal/apm/agent_configuration" "github.com/elastic/terraform-provider-elasticstack/internal/clients" "github.com/elastic/terraform-provider-elasticstack/internal/clients/config" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/data_stream_lifecycle" @@ -90,6 +91,7 @@ func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSour func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ + agent_configuration.NewAgentConfigurationResource, func() resource.Resource { return &import_saved_objects.Resource{} }, data_view.NewResource, func() resource.Resource { return ¶meter.Resource{} }, diff --git a/templates/resources/apm_agent_configuration.md.tmpl b/templates/resources/apm_agent_configuration.md.tmpl new file mode 100644 index 000000000..103e4b62e --- /dev/null +++ b/templates/resources/apm_agent_configuration.md.tmpl @@ -0,0 +1,23 @@ +--- +subcategory: "Kibana" +layout: "" +page_title: "Elasticstack: elasticstack_apm_agent_configuration Resource" +description: |- + Creates or updates an APM agent configuration +--- + +# Resource: elasticstack_apm_agent_configuration + +Creates or updates an APM agent configuration. See https://www.elastic.co/docs/solutions/observability/apm/apm-agent-central-configuration + +## Example Usage + +{{ tffile "examples/resources/elasticstack_apm_agent_configuration/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" "examples/resources/elasticstack_apm_agent_configuration/import.sh" }}