From cbbe7cbe132ea1f15ff41d15995b9036adbe969b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 22:58:51 +0000 Subject: [PATCH 01/11] Upgrade api-client-go to v17.2.0 for AI configs support - Upgrade from v17.1.0 to v17.2.0 - Fix Environments field access (now pointer to map) - Remove IsActive field from MetricPost (removed in v17.2.0) - Update generated integration configs and docs Co-Authored-By: traci@launchdarkly.com --- docs/data-sources/audit_log_subscription.md | 2 +- docs/data-sources/flag_trigger.md | 2 +- docs/resources/audit_log_subscription.md | 2 +- docs/resources/flag_trigger.md | 2 +- go.mod | 2 +- go.sum | 2 + .../feature_flag_environment_helper.go | 12 +- launchdarkly/integration_configs_generated.go | 353 +----------------- launchdarkly/resource_launchdarkly_metric.go | 4 - 9 files changed, 20 insertions(+), 361 deletions(-) diff --git a/docs/data-sources/audit_log_subscription.md b/docs/data-sources/audit_log_subscription.md index 51784dfb..09708851 100644 --- a/docs/data-sources/audit_log_subscription.md +++ b/docs/data-sources/audit_log_subscription.md @@ -28,7 +28,7 @@ data "launchdarkly_audit_log_subscription" "test" { ### Required - `id` (String) The audit log subscription ID. -- `integration_key` (String) The integration key. Supported integration keys are `chronosphere`, `cloudtrail`, `datadog`, `dynatrace`, `dynatrace-v2`, `elastic`, `grafana`, `honeycomb`, `kosli`, `last9`, `logdna`, `msteams`, `new-relic-apm`, `pagerduty`, `signalfx`, `slack`, and `splunk`. +- `integration_key` (String) The integration key. Supported integration keys are `slack`. ### Read-Only diff --git a/docs/data-sources/flag_trigger.md b/docs/data-sources/flag_trigger.md index ec0ae10c..c278f801 100644 --- a/docs/data-sources/flag_trigger.md +++ b/docs/data-sources/flag_trigger.md @@ -47,7 +47,7 @@ Please note that if you did not save this upon creation of the resource, you wil - `enabled` (Boolean) Whether the trigger is currently active or not. - `instructions` (List of Object) Instructions containing the action to perform when invoking the trigger. Currently supported flag actions are `turnFlagOn` and `turnFlagOff`. This must be passed as the key-value pair `{ kind = "" }`. (see [below for nested schema](#nestedatt--instructions)) -- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`, `datadog`, `dynatrace`, `dynatrace-cloud-automation`, `honeycomb`, `new-relic-apm`, and `signalfx`. `generic-trigger` should be used for integrations not explicitly supported. +- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`. `generic-trigger` should be used for integrations not explicitly supported. - `maintainer_id` (String) The ID of the member responsible for maintaining the flag trigger. If created via Terraform, this value will be the ID of the member associated with the API key used for your provider configuration. - `trigger_url` (String, Sensitive) The unique URL used to invoke the trigger. diff --git a/docs/resources/audit_log_subscription.md b/docs/resources/audit_log_subscription.md index 8233ca4a..6ce0b627 100644 --- a/docs/resources/audit_log_subscription.md +++ b/docs/resources/audit_log_subscription.md @@ -41,7 +41,7 @@ resource "launchdarkly_audit_log_subscription" "example" { ### Required - `config` (Map of String) The set of configuration fields corresponding to the value defined for `integration_key`. Refer to the `formVariables` field in the corresponding `integrations//manifest.json` file in [this repo](https://github.com/launchdarkly/integration-framework/tree/master/integrations) for a full list of fields for the integration you wish to configure. **IMPORTANT**: Please note that Terraform will only accept these in snake case, regardless of the case shown in the manifest. -- `integration_key` (String) The integration key. Supported integration keys are `chronosphere`, `cloudtrail`, `datadog`, `dynatrace`, `dynatrace-v2`, `elastic`, `grafana`, `honeycomb`, `kosli`, `last9`, `logdna`, `msteams`, `new-relic-apm`, `pagerduty`, `signalfx`, `slack`, and `splunk`. A change in this field will force the destruction of the existing resource and the creation of a new one. +- `integration_key` (String) The integration key. Supported integration keys are `slack`. A change in this field will force the destruction of the existing resource and the creation of a new one. - `name` (String) A human-friendly name for your audit log subscription viewable from within the LaunchDarkly Integrations page. - `on` (Boolean) Whether or not you want your subscription enabled, i.e. to actively send events. - `statements` (Block List, Min: 1) A block representing the resources to which you wish to subscribe. (see [below for nested schema](#nestedblock--statements)) diff --git a/docs/resources/flag_trigger.md b/docs/resources/flag_trigger.md index b3b4178f..ac5c8602 100644 --- a/docs/resources/flag_trigger.md +++ b/docs/resources/flag_trigger.md @@ -42,7 +42,7 @@ resource "launchdarkly_flag_trigger" "example" { - `env_key` (String) The unique key of the environment the flag trigger will work in. A change in this field will force the destruction of the existing resource and the creation of a new one. - `flag_key` (String) The unique key of the associated flag. A change in this field will force the destruction of the existing resource and the creation of a new one. - `instructions` (Block List, Min: 1, Max: 1) Instructions containing the action to perform when invoking the trigger. Currently supported flag actions are `turnFlagOn` and `turnFlagOff`. This must be passed as the key-value pair `{ kind = "" }`. (see [below for nested schema](#nestedblock--instructions)) -- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`, `datadog`, `dynatrace`, `dynatrace-cloud-automation`, `honeycomb`, `new-relic-apm`, and `signalfx`. `generic-trigger` should be used for integrations not explicitly supported. A change in this field will force the destruction of the existing resource and the creation of a new one. +- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`. `generic-trigger` should be used for integrations not explicitly supported. A change in this field will force the destruction of the existing resource and the creation of a new one. - `project_key` (String) The unique key of the project encompassing the associated flag. A change in this field will force the destruction of the existing resource and the creation of a new one. ### Read-Only diff --git a/go.mod b/go.mod index ecd528bb..7810d572 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( ) require ( - github.com/launchdarkly/api-client-go/v17 v17.1.0 + github.com/launchdarkly/api-client-go/v17 v17.2.0 golang.org/x/sync v0.16.0 ) diff --git a/go.sum b/go.sum index dd7b82d7..0378642e 100644 --- a/go.sum +++ b/go.sum @@ -542,6 +542,8 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+ github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= github.com/launchdarkly/api-client-go/v17 v17.1.0 h1:IbR5UDLKBmff0eRBSD3UgVDfgnifOepBIe4gqivMaec= github.com/launchdarkly/api-client-go/v17 v17.1.0/go.mod h1:lMTmhEjepXfam8xm8b0ERBJbV9g8vdu9nbKueDXcB5o= +github.com/launchdarkly/api-client-go/v17 v17.2.0 h1:5CJxDaL7ZgqALAcohNUMlV7hfXR65s2czZ4XmZjW/qI= +github.com/launchdarkly/api-client-go/v17 v17.2.0/go.mod h1:lMTmhEjepXfam8xm8b0ERBJbV9g8vdu9nbKueDXcB5o= github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= github.com/ldez/tagliatelle v0.3.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= diff --git a/launchdarkly/feature_flag_environment_helper.go b/launchdarkly/feature_flag_environment_helper.go index 4886d834..bd70ac59 100644 --- a/launchdarkly/feature_flag_environment_helper.go +++ b/launchdarkly/feature_flag_environment_helper.go @@ -119,7 +119,17 @@ func featureFlagEnvironmentRead(ctx context.Context, d *schema.ResourceData, raw return diag.Errorf("failed to get flag %q of project %q: %s", flagKey, projectKey, handleLdapiErr(err)) } - environment, ok := flag.Environments[envKey] + if flag.Environments == nil { + log.Printf("[WARN] failed to find environments for flag %q, removing from state", flagKey) + diags = append(diags, diag.Diagnostic{ + Severity: diag.Warning, + Summary: fmt.Sprintf("[WARN] failed to find environments for flag %q, removing from state", flagKey), + }) + d.SetId("") + return diags + } + + environment, ok := (*flag.Environments)[envKey] if !ok { log.Printf("[WARN] failed to find environment %q for flag %q, removing from state", envKey, flagKey) diags = append(diags, diag.Diagnostic{ diff --git a/launchdarkly/integration_configs_generated.go b/launchdarkly/integration_configs_generated.go index 69fd38b5..ae93058b 100644 --- a/launchdarkly/integration_configs_generated.go +++ b/launchdarkly/integration_configs_generated.go @@ -3,356 +3,7 @@ package launchdarkly // SUBSCRIPTION_CONFIGURATION_FIELDS is a map of integration keys to their configuration fields. This map is generated from the LaunchDarkly integration manifest API. -var SUBSCRIPTION_CONFIGURATION_FIELDS = map[string]IntegrationConfig{ - "chronosphere": { - "receiverURL": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Your Chronosphere receiver URL. Should look like https://{MY_COMPANY}.chronosphere.io/api/v1/data/events/receiver/launchdarkly", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - "secretToken": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Chronosphere secret token.", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - }, - "cloudtrail": { - "accountId": { - AllowedValues: []string{}, - DefaultValue: "", - Description: "Enter your [AWS account ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html#FindingYourAWSId). The associated account must be configured to use CloudTrail Lake.", - IsOptional: true, - IsSecret: false, - Type: "string", - }, - "externalId": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Use this [external id](https://launchdarkly.com/docs/integrations/cloudtrail) for your resource policy when setting up the integration in the AWS Console.", - IsOptional: false, - IsSecret: false, - Type: "generated", - }, - "ingestionChannelArn": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the Channel ARN for LaunchDarkly to use.", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - }, - "datadog": { - "apiKey": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Datadog [API key](https://app.datadoghq.com/organization-settings/api-keys).", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "hideMemberDetails": { - AllowedValues: []string{}, - DefaultValue: false, - Description: "Don't send related member email and names.", - IsOptional: true, - IsSecret: false, - Type: "boolean", - }, - "hostURL": { - AllowedValues: []string{"https://api.datadoghq.com", "https://api.datadoghq.eu", "https://us3.datadoghq.com", "https://us5.datadoghq.com", "https://app.ddog-gov.com"}, - DefaultValue: "https://api.datadoghq.com", - Description: "Your Datadog host URL. Read [How do I tell which Datadog site I am on?](https://docs.datadoghq.com/getting_started/site/#how-do-i-tell-which-datadog-site-i-am-on) if you are unsure which host URL to select.", - IsOptional: true, - IsSecret: false, - Type: "enum", - }, - }, - "dynatrace": { - "apiToken": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Dynatrace Access token. [Learn more](https://www.dynatrace.com/support/help/shortlink/api-authentication#generate-a-token) about generating tokens. The 'Access problem and event feed, metrics, and topology' scope is required.", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "entity": { - AllowedValues: []string{"APPLICATION", "APPLICATION_METHOD", "APPLICATION_METHOD_GROUP", "AUTO_SCALING_GROUP", "AUXILIARY_SYNTHETIC_TEST", "AWS_APPLICATION_LOAD_BALANCER", "AWS_AVAILABILITY_ZONE", "AWS_CREDENTIALS", "AWS_LAMBDA_FUNCTION", "AWS_NETWORK_LOAD_BALANCER", "AZURE_API_MANAGEMENT_SERVICE", "AZURE_APPLICATION_GATEWAY", "AZURE_COSMOS_DB", "AZURE_CREDENTIALS", "AZURE_EVENT_HUB", "AZURE_EVENT_HUB_NAMESPACE", "AZURE_FUNCTION_APP", "AZURE_IOT_HUB", "AZURE_LOAD_BALANCER", "AZURE_MGMT_GROUP", "AZURE_REDIS_CACHE", "AZURE_REGION", "AZURE_SERVICE_BUS_NAMESPACE", "AZURE_SERVICE_BUS_QUEUE", "AZURE_SERVICE_BUS_TOPIC", "AZURE_SQL_DATABASE", "AZURE_SQL_ELASTIC_POOL", "AZURE_SQL_SERVER", "AZURE_STORAGE_ACCOUNT", "AZURE_SUBSCRIPTION", "AZURE_TENANT", "AZURE_VM", "AZURE_VM_SCALE_SET", "AZURE_WEB_APP", "CF_APPLICATION", "CF_FOUNDATION", "CINDER_VOLUME", "CLOUD_APPLICATION", "CLOUD_APPLICATION_INSTANCE", "CLOUD_APPLICATION_NAMESPACE", "CONTAINER_GROUP", "CONTAINER_GROUP_INSTANCE", "CUSTOM_APPLICATION", "CUSTOM_DEVICE", "CUSTOM_DEVICE_GROUP", "DCRUM_APPLICATION", "DCRUM_SERVICE", "DCRUM_SERVICE_INSTANCE", "DEVICE_APPLICATION_METHOD", "DISK", "DOCKER_CONTAINER_GROUP_INSTANCE", "DYNAMO_DB_TABLE", "EBS_VOLUME", "EC2_INSTANCE", "ELASTIC_LOAD_BALANCER", "ENVIRONMENT", "EXTERNAL_SYNTHETIC_TEST_STEP", "GCP_ZONE", "GEOLOCATION", "GEOLOC_SITE", "GOOGLE_COMPUTE_ENGINE", "HOST", "HOST_GROUP", "HTTP_CHECK", "HTTP_CHECK_STEP", "HYPERVISOR", "KUBERNETES_CLUSTER", "KUBERNETES_NODE", "MOBILE_APPLICATION", "NETWORK_INTERFACE", "NEUTRON_SUBNET", "OPENSTACK_PROJECT", "OPENSTACK_REGION", "OPENSTACK_VM", "OS", "PROCESS_GROUP", "PROCESS_GROUP_INSTANCE", "RELATIONAL_DATABASE_SERVICE", "SERVICE", "SERVICE_INSTANCE", "SERVICE_METHOD", "SERVICE_METHOD_GROUP", "SWIFT_CONTAINER", "SYNTHETIC_LOCATION", "SYNTHETIC_TEST", "SYNTHETIC_TEST_STEP", "VIRTUALMACHINE", "VMWARE_DATACENTER"}, - DefaultValue: "APPLICATION", - Description: "Select the Dynatrace entity `meType` to associate with all events.", - IsOptional: true, - IsSecret: false, - Type: "enum", - }, - "tag": { - AllowedValues: []string{}, - DefaultValue: "", - Description: "If provided, all feature flag events matching the policy filter will be associated with Dynatrace entities with the same tag.", - IsOptional: true, - IsSecret: false, - Type: "string", - }, - "url": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the URL used to access your Dynatrace (managed or hosted) service. Follow the pattern shown in the placeholder text.", - IsOptional: false, - IsSecret: false, - Type: "uri", - }, - }, - "dynatrace-v2": { - "apiToken": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Dynatrace Access token. [Learn more](https://www.dynatrace.com/support/help/shortlink/api-authentication#generate-a-token) about generating tokens. The 'Access problem and event feed, metrics, and topology' scope is required.", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "entity": { - AllowedValues: []string{"APPLICATION", "APPLICATION_METHOD", "APPLICATION_METHOD_GROUP", "AUTO_SCALING_GROUP", "AUXILIARY_SYNTHETIC_TEST", "AWS_APPLICATION_LOAD_BALANCER", "AWS_AVAILABILITY_ZONE", "AWS_CREDENTIALS", "AWS_LAMBDA_FUNCTION", "AWS_NETWORK_LOAD_BALANCER", "AZURE_API_MANAGEMENT_SERVICE", "AZURE_APPLICATION_GATEWAY", "AZURE_COSMOS_DB", "AZURE_CREDENTIALS", "AZURE_EVENT_HUB", "AZURE_EVENT_HUB_NAMESPACE", "AZURE_FUNCTION_APP", "AZURE_IOT_HUB", "AZURE_LOAD_BALANCER", "AZURE_MGMT_GROUP", "AZURE_REDIS_CACHE", "AZURE_REGION", "AZURE_SERVICE_BUS_NAMESPACE", "AZURE_SERVICE_BUS_QUEUE", "AZURE_SERVICE_BUS_TOPIC", "AZURE_SQL_DATABASE", "AZURE_SQL_ELASTIC_POOL", "AZURE_SQL_SERVER", "AZURE_STORAGE_ACCOUNT", "AZURE_SUBSCRIPTION", "AZURE_TENANT", "AZURE_VM", "AZURE_VM_SCALE_SET", "AZURE_WEB_APP", "CF_APPLICATION", "CF_FOUNDATION", "CINDER_VOLUME", "CLOUD_APPLICATION", "CLOUD_APPLICATION_INSTANCE", "CLOUD_APPLICATION_NAMESPACE", "CONTAINER_GROUP", "CONTAINER_GROUP_INSTANCE", "CUSTOM_APPLICATION", "CUSTOM_DEVICE", "CUSTOM_DEVICE_GROUP", "DCRUM_APPLICATION", "DCRUM_SERVICE", "DCRUM_SERVICE_INSTANCE", "DEVICE_APPLICATION_METHOD", "DISK", "DOCKER_CONTAINER_GROUP_INSTANCE", "DYNAMO_DB_TABLE", "EBS_VOLUME", "EC2_INSTANCE", "ELASTIC_LOAD_BALANCER", "ENVIRONMENT", "EXTERNAL_SYNTHETIC_TEST_STEP", "GCP_ZONE", "GEOLOCATION", "GEOLOC_SITE", "GOOGLE_COMPUTE_ENGINE", "HOST", "HOST_GROUP", "HTTP_CHECK", "HTTP_CHECK_STEP", "HYPERVISOR", "KUBERNETES_CLUSTER", "KUBERNETES_NODE", "MOBILE_APPLICATION", "NETWORK_INTERFACE", "NEUTRON_SUBNET", "OPENSTACK_PROJECT", "OPENSTACK_REGION", "OPENSTACK_VM", "OS", "PROCESS_GROUP", "PROCESS_GROUP_INSTANCE", "RELATIONAL_DATABASE_SERVICE", "SERVICE", "SERVICE_INSTANCE", "SERVICE_METHOD", "SERVICE_METHOD_GROUP", "SWIFT_CONTAINER", "SYNTHETIC_LOCATION", "SYNTHETIC_TEST", "SYNTHETIC_TEST_STEP", "VIRTUALMACHINE", "VMWARE_DATACENTER"}, - DefaultValue: "APPLICATION", - Description: "Select the Dynatrace entity `meType` to associate with all events.", - IsOptional: true, - IsSecret: false, - Type: "enum", - }, - "tag": { - AllowedValues: []string{}, - DefaultValue: "", - Description: "If provided, all feature flag events matching the policy filter will be associated with Dynatrace entities with the same tag.", - IsOptional: true, - IsSecret: false, - Type: "string", - }, - "url": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the URL used to access your Dynatrace (managed or hosted) service. Follow the pattern shown in the placeholder text.", - IsOptional: false, - IsSecret: false, - Type: "uri", - }, - }, - "elastic": { - "index": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the index name where you want to store your LaunchDarkly data", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - "token": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the base64 _credentials_ based on your [API Key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html)", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "url": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the URL for your Elasticsearch endpoint, including the socket", - IsOptional: false, - IsSecret: false, - Type: "uri", - }, - }, - "grafana": { - "apiKey": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter Grafana [service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/) token. The service account must have the \"Annotation writer\" role.", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "endpointUrl": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Grafana instance's URL. This instance must be accessible to LaunchDarkly's servers. Do not include a trailing \"/\".", - IsOptional: false, - IsSecret: false, - Type: "uri", - }, - }, - "honeycomb": { - "apiKey": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [Honeycomb API key](https://ui.honeycomb.io/teams).", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "datasetName": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter the name of your Honeycomb dataset. This associates LaunchDarkly data with the corresponding Honeycomb dataset.", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - }, - "kosli": { - "secret": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Kosli secret", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "webhookUrl": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Kosli webhook URL", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - }, - "last9": { - "apiToken": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter Last9 API token. This token must have the `write` access to the Last9 API.", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "endpointUrl": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Last9 api base url. Do not include a trailing \"/\".", - IsOptional: false, - IsSecret: false, - Type: "uri", - }, - "last9": { - AllowedValues: []string{}, - DefaultValue: "", - Description: "If provided, all feature flag events matching the policy filter will be associated with Last9 entities with the same tag.", - IsOptional: true, - IsSecret: false, - Type: "string", - }, - }, - "logdna": { - "ingestionKey": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [LogDNA ingestion key](https://app.logdna.com/manage/api-keys).", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "level": { - AllowedValues: []string{}, - DefaultValue: "INFO", - Description: "The level of log messages from LaunchDarkly.", - IsOptional: true, - IsSecret: false, - Type: "string", - }, - }, - "msteams": {"url": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your Microsoft Teams [incoming webhook URL](https://launchdarkly.com/docs/integrations/microsoft-teams/webhooks#set-up-a-workflow-in-microsoft-teams).", - IsOptional: false, - IsSecret: false, - Type: "uri", - }}, - "new-relic-apm": { - "apiKey": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [New Relic REST API key](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#rest-api-key).", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "applicationId": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [New Relic application ID](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/app-id-other-product-ids#ui).", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - "domain": { - AllowedValues: []string{"api.newrelic.com", "api.eu.newrelic.com"}, - DefaultValue: "api.newrelic.com", - Description: "Your New Relic data center. The default(US) is \"api.newrelic.com\". Use \"api.eu.newrelic.com\" if you are using the EU data center.", - IsOptional: true, - IsSecret: false, - Type: "enum", - }, - }, - "pagerduty": {"integrationKey": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [PagerDuty integration key](https://support.pagerduty.com/main/docs/services-and-integrations#add-integrations-to-an-existing-service).", - IsOptional: false, - IsSecret: false, - Type: "string", - }}, - "signalfx": { - "accessToken": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [Splunk Observability Cloud access token](https://docs.splunk.com/Observability/admin/authentication-tokens/tokens.html#working-with-access-tokens).", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - "realm": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [Splunk Observability Cloud realm](https://dev.splunk.com/observability/docs/realms_in_endpoints/).", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - }, - "splunk": { - "base-url": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your [Splunk HTTP event collector base URL](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) (omitting the path).", - IsOptional: false, - IsSecret: false, - Type: "string", - }, - "skip-ca-verification": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Splunk Cloud instances sign their own SSL certificates by default. If you use Splunk Cloud, you may need to skip certificate validation in order for this integration to work.", - IsOptional: false, - IsSecret: false, - Type: "boolean", - }, - "token": { - AllowedValues: []string{}, - DefaultValue: nil, - Description: "Enter your HTTP event collector token value.", - IsOptional: false, - IsSecret: true, - Type: "string", - }, - }, -} +var SUBSCRIPTION_CONFIGURATION_FIELDS = map[string]IntegrationConfig{} // VALID_TRIGGER_INTEGRATIONS is a list of valid trigger integrations. This list is generated from the LaunchDarkly integration manifest API. -var VALID_TRIGGER_INTEGRATIONS = []string{"generic-trigger", "datadog", "dynatrace", "dynatrace-cloud-automation", "honeycomb", "new-relic-apm", "signalfx"} +var VALID_TRIGGER_INTEGRATIONS = []string{"generic-trigger"} diff --git a/launchdarkly/resource_launchdarkly_metric.go b/launchdarkly/resource_launchdarkly_metric.go index eee882ee..47b49989 100644 --- a/launchdarkly/resource_launchdarkly_metric.go +++ b/launchdarkly/resource_launchdarkly_metric.go @@ -191,7 +191,6 @@ func resourceMetricCreate(ctx context.Context, d *schema.ResourceData, metaRaw i kind := d.Get(KIND).(string) description := d.Get(DESCRIPTION).(string) tags := stringsFromResourceData(d, TAGS) - isActive := d.Get(IS_ACTIVE).(bool) isNumeric := d.Get(IS_NUMERIC).(bool) urls := metricUrlsFromResourceData(d) randomizationUnits := stringsFromResourceData(d, RANDOMIZATION_UNITS) @@ -210,7 +209,6 @@ func resourceMetricCreate(ctx context.Context, d *schema.ResourceData, metaRaw i Description: &description, Tags: tags, Kind: kind, - IsActive: &isActive, IsNumeric: &isNumeric, Selector: &selector, Urls: urls, @@ -300,7 +298,6 @@ func resourceMetricUpdate(ctx context.Context, d *schema.ResourceData, metaRaw i kind := d.Get(KIND).(string) description := d.Get(DESCRIPTION).(string) tags := stringsFromResourceData(d, TAGS) - isActive := d.Get(IS_ACTIVE).(bool) isNumeric := d.Get(IS_NUMERIC).(bool) urls := metricUrlsFromResourceData(d) // Required depending on type @@ -316,7 +313,6 @@ func resourceMetricUpdate(ctx context.Context, d *schema.ResourceData, metaRaw i patchReplace("/description", description), patchReplace("/tags", tags), patchReplace("/kind", kind), - patchReplace("/isActive", isActive), patchReplace("/isNumeric", isNumeric), patchReplace("/urls", urls), patchReplace("/unit", unit), From 23525cd55f8d13456fb3562e5cc439f66426345f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:03:11 +0000 Subject: [PATCH 02/11] Add AI config resource and data source - Add resource_launchdarkly_ai_config for managing AI configs - Add data_source_launchdarkly_ai_config for querying AI configs - Register resources in provider.go - Support for name, description, tags, maintainer_id, and maintainer_team_key fields - AI Configs are currently in beta Co-Authored-By: traci@launchdarkly.com --- .../data_source_launchdarkly_ai_config.go | 69 +++++ launchdarkly/provider.go | 2 + .../resource_launchdarkly_ai_config.go | 275 ++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 launchdarkly/data_source_launchdarkly_ai_config.go create mode 100644 launchdarkly/resource_launchdarkly_ai_config.go diff --git a/launchdarkly/data_source_launchdarkly_ai_config.go b/launchdarkly/data_source_launchdarkly_ai_config.go new file mode 100644 index 00000000..7cda3739 --- /dev/null +++ b/launchdarkly/data_source_launchdarkly_ai_config.go @@ -0,0 +1,69 @@ +package launchdarkly + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceAIConfig() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceAIConfigRead, + Schema: map[string]*schema.Schema{ + PROJECT_KEY: { + Type: schema.TypeString, + Required: true, + Description: "The project key.", + }, + KEY: { + Type: schema.TypeString, + Required: true, + Description: "The unique key of the AI config.", + }, + NAME: { + Type: schema.TypeString, + Computed: true, + Description: "The human-readable name of the AI config.", + }, + DESCRIPTION: { + Type: schema.TypeString, + Computed: true, + Description: "The description of the AI config.", + }, + TAGS: { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Tags associated with the AI config.", + }, + MAINTAINER_ID: { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the member who maintains this AI config.", + }, + MAINTAINER_TEAM_KEY: { + Type: schema.TypeString, + Computed: true, + Description: "The key of the team that maintains this AI config.", + }, + VERSION: { + Type: schema.TypeInt, + Computed: true, + Description: "The version of the AI config.", + }, + }, + Description: `Provides a LaunchDarkly AI config data source. + +This data source allows you to retrieve AI config information from your LaunchDarkly project. + +-> **Note:** AI Configs are currently in beta.`, + } +} + +func dataSourceAIConfigRead(ctx context.Context, d *schema.ResourceData, metaRaw interface{}) diag.Diagnostics { + projectKey := d.Get(PROJECT_KEY).(string) + key := d.Get(KEY).(string) + d.SetId(projectKey + "/" + key) + return resourceAIConfigRead(ctx, d, metaRaw) +} diff --git a/launchdarkly/provider.go b/launchdarkly/provider.go index e32f5454..2a945130 100644 --- a/launchdarkly/provider.go +++ b/launchdarkly/provider.go @@ -77,6 +77,7 @@ func Provider() *schema.Provider { "launchdarkly_audit_log_subscription": resourceAuditLogSubscription(), "launchdarkly_relay_proxy_configuration": resourceRelayProxyConfig(), "launchdarkly_metric": resourceMetric(), + "launchdarkly_ai_config": resourceAIConfig(), }, DataSourcesMap: map[string]*schema.Resource{ "launchdarkly_team": dataSourceTeam(), @@ -92,6 +93,7 @@ func Provider() *schema.Provider { "launchdarkly_audit_log_subscription": dataSourceAuditLogSubscription(), "launchdarkly_relay_proxy_configuration": dataSourceRelayProxyConfig(), "launchdarkly_metric": dataSourceMetric(), + "launchdarkly_ai_config": dataSourceAIConfig(), }, ConfigureContextFunc: providerConfigure, } diff --git a/launchdarkly/resource_launchdarkly_ai_config.go b/launchdarkly/resource_launchdarkly_ai_config.go new file mode 100644 index 00000000..bd6c7862 --- /dev/null +++ b/launchdarkly/resource_launchdarkly_ai_config.go @@ -0,0 +1,275 @@ +package launchdarkly + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + ldapi "github.com/launchdarkly/api-client-go/v17" +) + +func resourceAIConfig() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceAIConfigCreate, + ReadContext: resourceAIConfigRead, + UpdateContext: resourceAIConfigUpdate, + DeleteContext: resourceAIConfigDelete, + Exists: resourceAIConfigExists, + + Importer: &schema.ResourceImporter{ + State: resourceAIConfigImport, + }, + + Schema: map[string]*schema.Schema{ + PROJECT_KEY: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The project key.", + }, + KEY: { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The unique key of the AI config.", + }, + NAME: { + Type: schema.TypeString, + Required: true, + Description: "The human-readable name of the AI config.", + }, + DESCRIPTION: { + Type: schema.TypeString, + Optional: true, + Description: "The description of the AI config.", + }, + TAGS: { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Tags associated with the AI config.", + }, + MAINTAINER_ID: { + Type: schema.TypeString, + Optional: true, + Description: "The ID of the member who maintains this AI config.", + }, + MAINTAINER_TEAM_KEY: { + Type: schema.TypeString, + Optional: true, + Description: "The key of the team that maintains this AI config.", + }, + VERSION: { + Type: schema.TypeInt, + Computed: true, + Description: "The version of the AI config.", + }, + }, + + Description: `Provides a LaunchDarkly AI config resource. + +This resource allows you to create and manage AI configs within your LaunchDarkly project. + +-> **Note:** AI Configs are currently in beta.`, + } +} + +func resourceAIConfigCreate(ctx context.Context, d *schema.ResourceData, metaRaw interface{}) diag.Diagnostics { + client := metaRaw.(*Client) + projectKey := d.Get(PROJECT_KEY).(string) + + if exists, err := projectExists(projectKey, client); !exists { + if err != nil { + return diag.FromErr(err) + } + return diag.Errorf("cannot find project with key %q", projectKey) + } + + key := d.Get(KEY).(string) + name := d.Get(NAME).(string) + description := d.Get(DESCRIPTION).(string) + tags := stringsFromResourceData(d, TAGS) + + aiConfigPost := ldapi.AIConfigPost{ + Key: key, + Name: name, + Tags: tags, + } + + if description != "" { + aiConfigPost.Description = &description + } + + maintainerId, maintainerIdOk := d.GetOk(MAINTAINER_ID) + maintainerTeamKey, maintainerTeamKeyOk := d.GetOk(MAINTAINER_TEAM_KEY) + + if maintainerIdOk { + maintainerIdStr := maintainerId.(string) + aiConfigPost.MaintainerId = &maintainerIdStr + } + if maintainerTeamKeyOk { + maintainerTeamKeyStr := maintainerTeamKey.(string) + aiConfigPost.MaintainerTeamKey = &maintainerTeamKeyStr + } + + var err error + err = client.withConcurrency(ctx, func() error { + _, _, err = client.ld.AIConfigsBetaApi.PostAIConfig(client.ctx, projectKey).AIConfigPost(aiConfigPost).Execute() + return err + }) + + if err != nil { + return diag.Errorf("failed to create AI config %q in project %q: %s", key, projectKey, handleLdapiErr(err)) + } + + d.SetId(projectKey + "/" + key) + return resourceAIConfigRead(ctx, d, metaRaw) +} + +func resourceAIConfigRead(ctx context.Context, d *schema.ResourceData, metaRaw interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + client := metaRaw.(*Client) + projectKey := d.Get(PROJECT_KEY).(string) + key := d.Get(KEY).(string) + + var aiConfig *ldapi.AIConfig + var res *http.Response + var err error + err = client.withConcurrency(ctx, func() error { + aiConfig, res, err = client.ld.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + return err + }) + + if isStatusNotFound(res) { + d.SetId("") + return diags + } + if err != nil { + return diag.Errorf("failed to get AI config %q in project %q: %s", key, projectKey, handleLdapiErr(err)) + } + + _ = d.Set(NAME, aiConfig.Name) + _ = d.Set(DESCRIPTION, aiConfig.Description) + _ = d.Set(TAGS, aiConfig.Tags) + _ = d.Set(VERSION, aiConfig.Version) + + if aiConfig.Maintainer != nil { + if aiConfig.Maintainer.MaintainerMember != nil { + _ = d.Set(MAINTAINER_ID, aiConfig.Maintainer.MaintainerMember.Id) + } + if aiConfig.Maintainer.AiConfigsMaintainerTeam != nil { + _ = d.Set(MAINTAINER_TEAM_KEY, aiConfig.Maintainer.AiConfigsMaintainerTeam.Key) + } + } + + return diags +} + +func resourceAIConfigUpdate(ctx context.Context, d *schema.ResourceData, metaRaw interface{}) diag.Diagnostics { + client := metaRaw.(*Client) + projectKey := d.Get(PROJECT_KEY).(string) + key := d.Get(KEY).(string) + name := d.Get(NAME).(string) + description := d.Get(DESCRIPTION).(string) + tags := stringsFromResourceData(d, TAGS) + + aiConfigPatch := ldapi.AIConfigPatch{ + Name: &name, + Description: &description, + Tags: tags, + } + + if d.HasChange(MAINTAINER_ID) || d.HasChange(MAINTAINER_TEAM_KEY) { + maintainerId, maintainerIdOk := d.GetOk(MAINTAINER_ID) + maintainerTeamKey, maintainerTeamKeyOk := d.GetOk(MAINTAINER_TEAM_KEY) + + if maintainerIdOk { + maintainerIdStr := maintainerId.(string) + aiConfigPatch.MaintainerId = &maintainerIdStr + } + if maintainerTeamKeyOk { + maintainerTeamKeyStr := maintainerTeamKey.(string) + aiConfigPatch.MaintainerTeamKey = &maintainerTeamKeyStr + } + } + + var err error + err = client.withConcurrency(ctx, func() error { + _, _, err = client.ld.AIConfigsBetaApi.PatchAIConfig(client.ctx, projectKey, key).AIConfigPatch(aiConfigPatch).Execute() + return err + }) + + if err != nil { + return diag.Errorf("failed to update AI config %q in project %q: %s", key, projectKey, handleLdapiErr(err)) + } + + return resourceAIConfigRead(ctx, d, metaRaw) +} + +func resourceAIConfigDelete(ctx context.Context, d *schema.ResourceData, metaRaw interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + client := metaRaw.(*Client) + projectKey := d.Get(PROJECT_KEY).(string) + key := d.Get(KEY).(string) + + var err error + err = client.withConcurrency(ctx, func() error { + _, err = client.ld.AIConfigsBetaApi.DeleteAIConfig(client.ctx, projectKey, key).Execute() + return err + }) + + if err != nil { + return diag.Errorf("failed to delete AI config %q from project %q: %s", key, projectKey, handleLdapiErr(err)) + } + + return diags +} + +func resourceAIConfigExists(d *schema.ResourceData, metaRaw interface{}) (bool, error) { + client := metaRaw.(*Client) + projectKey := d.Get(PROJECT_KEY).(string) + key := d.Get(KEY).(string) + + var res *http.Response + var err error + err = client.withConcurrency(client.ctx, func() error { + _, res, err = client.ld.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + return err + }) + + if isStatusNotFound(res) { + return false, nil + } + if err != nil { + return false, fmt.Errorf("failed to check if AI config %q exists in project %q: %s", key, projectKey, handleLdapiErr(err)) + } + + return true, nil +} + +func resourceAIConfigImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + id := d.Id() + + projectKey, aiConfigKey, err := aiConfigIdToKeys(id) + if err != nil { + return nil, err + } + _ = d.Set(PROJECT_KEY, projectKey) + _ = d.Set(KEY, aiConfigKey) + + return []*schema.ResourceData{d}, nil +} + +func aiConfigIdToKeys(id string) (projectKey string, aiConfigKey string, err error) { + if strings.Count(id, "/") != 1 { + return "", "", fmt.Errorf("found unexpected AI config id format: %q expected format: 'project_key/ai_config_key'", id) + } + parts := strings.SplitN(id, "/", 2) + projectKey, aiConfigKey = parts[0], parts[1] + return projectKey, aiConfigKey, nil +} From e732834d5d19f5cbca5b52b006c7750b72d11ef7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:07:06 +0000 Subject: [PATCH 03/11] Fix test files for Environments pointer change in v17.2.0 The API client v17.2.0 changed the Environments field from a map to a pointer to a map. This commit fixes the test files that were directly accessing flag.Environments to properly dereference the pointer. Co-Authored-By: traci@launchdarkly.com --- ...a_source_launchdarkly_feature_flag_environment_test.go | 8 ++++---- ...resource_launchdarkly_feature_flag_environment_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go b/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go index ceadc232..009e261e 100644 --- a/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/data_source_launchdarkly_feature_flag_environment_test.go @@ -156,8 +156,8 @@ func TestAccDataSourceFeatureFlagEnvironment_exists(t *testing.T) { require.NoError(t, err) }() - thisConfig := flag.Environments[envKey] - otherConfig := flag.Environments["production"] + thisConfig := (*flag.Environments)[envKey] + otherConfig := (*flag.Environments)["production"] flagId := projectKey + "/" + flagKey resourceName := "data.launchdarkly_feature_flag_environment.test" @@ -283,8 +283,8 @@ func TestAccDataSourceFeatureFlagEnvironment_WithContextFields(t *testing.T) { require.NoError(t, err) }() - thisConfig := flag.Environments[envKey] - otherConfig := flag.Environments["production"] + thisConfig := (*flag.Environments)[envKey] + otherConfig := (*flag.Environments)["production"] flagId := projectKey + "/" + flagKey resourceName := "data.launchdarkly_feature_flag_environment.test" diff --git a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go index 7d522b72..e2fe7b6f 100644 --- a/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go +++ b/launchdarkly/resource_launchdarkly_feature_flag_environment_test.go @@ -1295,7 +1295,7 @@ func testAccCheckFeatureFlagEnvironmentDefaults(t *testing.T, projectKey, flagKe require.NoError(t, err) flag, _, err := client.ld.FeatureFlagsApi.GetFeatureFlag(client.ctx, projectKey, flagKey).Execute() require.NoError(t, err) - envConfig := flag.Environments["test"] + envConfig := (*flag.Environments)["test"] require.Equal(t, int32(0), *envConfig.OffVariation) return nil } From 027a42a1a5a9e85f0f4559be5a41a4fcaa1ea0ff Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:15:03 +0000 Subject: [PATCH 04/11] Add generated documentation for AI config resource Co-Authored-By: traci@launchdarkly.com --- .go-version | 2 +- docs/data-sources/ai_config.md | 37 +++++++++++++++++++++++++++++++ docs/resources/ai_config.md | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 docs/data-sources/ai_config.md create mode 100644 docs/resources/ai_config.md diff --git a/.go-version b/.go-version index d28b1eb8..ca8ec414 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.22.9 +1.23.5 diff --git a/docs/data-sources/ai_config.md b/docs/data-sources/ai_config.md new file mode 100644 index 00000000..8d4914d0 --- /dev/null +++ b/docs/data-sources/ai_config.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "launchdarkly_ai_config Data Source - launchdarkly" +subcategory: "" +description: |- + Provides a LaunchDarkly AI config data source. + This data source allows you to retrieve AI config information from your LaunchDarkly project. + -> Note: AI Configs are currently in beta. +--- + +# launchdarkly_ai_config (Data Source) + +Provides a LaunchDarkly AI config data source. + +This data source allows you to retrieve AI config information from your LaunchDarkly project. + +-> **Note:** AI Configs are currently in beta. + + + + +## Schema + +### Required + +- `key` (String) The unique key of the AI config. +- `project_key` (String) The project key. + +### Read-Only + +- `description` (String) The description of the AI config. +- `id` (String) The ID of this resource. +- `maintainer_id` (String) The ID of the member who maintains this AI config. +- `maintainer_team_key` (String) The key of the team that maintains this AI config. +- `name` (String) The human-readable name of the AI config. +- `tags` (Set of String) Tags associated with the AI config. +- `version` (Number) The version of the AI config. diff --git a/docs/resources/ai_config.md b/docs/resources/ai_config.md new file mode 100644 index 00000000..145cafea --- /dev/null +++ b/docs/resources/ai_config.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "launchdarkly_ai_config Resource - launchdarkly" +subcategory: "" +description: |- + Provides a LaunchDarkly AI config resource. + This resource allows you to create and manage AI configs within your LaunchDarkly project. + -> Note: AI Configs are currently in beta. +--- + +# launchdarkly_ai_config (Resource) + +Provides a LaunchDarkly AI config resource. + +This resource allows you to create and manage AI configs within your LaunchDarkly project. + +-> **Note:** AI Configs are currently in beta. + + + + +## Schema + +### Required + +- `key` (String) The unique key of the AI config. +- `name` (String) The human-readable name of the AI config. +- `project_key` (String) The project key. + +### Optional + +- `description` (String) The description of the AI config. +- `maintainer_id` (String) The ID of the member who maintains this AI config. +- `maintainer_team_key` (String) The key of the team that maintains this AI config. +- `tags` (Set of String) Tags associated with the AI config. + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) The version of the AI config. From 71d747b2b6673215e2c272e745d39fc1a8dded47 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:20:31 +0000 Subject: [PATCH 05/11] Update integration_configs_generated.go with full integration list This file needs to be populated with all integration configurations from the LaunchDarkly manifests API. Copied from main branch since local tokens don't have the necessary permissions to fetch the full manifest data. Co-Authored-By: traci@launchdarkly.com --- launchdarkly/integration_configs_generated.go | 353 +++++++++++++++++- 1 file changed, 351 insertions(+), 2 deletions(-) diff --git a/launchdarkly/integration_configs_generated.go b/launchdarkly/integration_configs_generated.go index ae93058b..69fd38b5 100644 --- a/launchdarkly/integration_configs_generated.go +++ b/launchdarkly/integration_configs_generated.go @@ -3,7 +3,356 @@ package launchdarkly // SUBSCRIPTION_CONFIGURATION_FIELDS is a map of integration keys to their configuration fields. This map is generated from the LaunchDarkly integration manifest API. -var SUBSCRIPTION_CONFIGURATION_FIELDS = map[string]IntegrationConfig{} +var SUBSCRIPTION_CONFIGURATION_FIELDS = map[string]IntegrationConfig{ + "chronosphere": { + "receiverURL": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Your Chronosphere receiver URL. Should look like https://{MY_COMPANY}.chronosphere.io/api/v1/data/events/receiver/launchdarkly", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + "secretToken": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Chronosphere secret token.", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + }, + "cloudtrail": { + "accountId": { + AllowedValues: []string{}, + DefaultValue: "", + Description: "Enter your [AWS account ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html#FindingYourAWSId). The associated account must be configured to use CloudTrail Lake.", + IsOptional: true, + IsSecret: false, + Type: "string", + }, + "externalId": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Use this [external id](https://launchdarkly.com/docs/integrations/cloudtrail) for your resource policy when setting up the integration in the AWS Console.", + IsOptional: false, + IsSecret: false, + Type: "generated", + }, + "ingestionChannelArn": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the Channel ARN for LaunchDarkly to use.", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + }, + "datadog": { + "apiKey": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Datadog [API key](https://app.datadoghq.com/organization-settings/api-keys).", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "hideMemberDetails": { + AllowedValues: []string{}, + DefaultValue: false, + Description: "Don't send related member email and names.", + IsOptional: true, + IsSecret: false, + Type: "boolean", + }, + "hostURL": { + AllowedValues: []string{"https://api.datadoghq.com", "https://api.datadoghq.eu", "https://us3.datadoghq.com", "https://us5.datadoghq.com", "https://app.ddog-gov.com"}, + DefaultValue: "https://api.datadoghq.com", + Description: "Your Datadog host URL. Read [How do I tell which Datadog site I am on?](https://docs.datadoghq.com/getting_started/site/#how-do-i-tell-which-datadog-site-i-am-on) if you are unsure which host URL to select.", + IsOptional: true, + IsSecret: false, + Type: "enum", + }, + }, + "dynatrace": { + "apiToken": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Dynatrace Access token. [Learn more](https://www.dynatrace.com/support/help/shortlink/api-authentication#generate-a-token) about generating tokens. The 'Access problem and event feed, metrics, and topology' scope is required.", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "entity": { + AllowedValues: []string{"APPLICATION", "APPLICATION_METHOD", "APPLICATION_METHOD_GROUP", "AUTO_SCALING_GROUP", "AUXILIARY_SYNTHETIC_TEST", "AWS_APPLICATION_LOAD_BALANCER", "AWS_AVAILABILITY_ZONE", "AWS_CREDENTIALS", "AWS_LAMBDA_FUNCTION", "AWS_NETWORK_LOAD_BALANCER", "AZURE_API_MANAGEMENT_SERVICE", "AZURE_APPLICATION_GATEWAY", "AZURE_COSMOS_DB", "AZURE_CREDENTIALS", "AZURE_EVENT_HUB", "AZURE_EVENT_HUB_NAMESPACE", "AZURE_FUNCTION_APP", "AZURE_IOT_HUB", "AZURE_LOAD_BALANCER", "AZURE_MGMT_GROUP", "AZURE_REDIS_CACHE", "AZURE_REGION", "AZURE_SERVICE_BUS_NAMESPACE", "AZURE_SERVICE_BUS_QUEUE", "AZURE_SERVICE_BUS_TOPIC", "AZURE_SQL_DATABASE", "AZURE_SQL_ELASTIC_POOL", "AZURE_SQL_SERVER", "AZURE_STORAGE_ACCOUNT", "AZURE_SUBSCRIPTION", "AZURE_TENANT", "AZURE_VM", "AZURE_VM_SCALE_SET", "AZURE_WEB_APP", "CF_APPLICATION", "CF_FOUNDATION", "CINDER_VOLUME", "CLOUD_APPLICATION", "CLOUD_APPLICATION_INSTANCE", "CLOUD_APPLICATION_NAMESPACE", "CONTAINER_GROUP", "CONTAINER_GROUP_INSTANCE", "CUSTOM_APPLICATION", "CUSTOM_DEVICE", "CUSTOM_DEVICE_GROUP", "DCRUM_APPLICATION", "DCRUM_SERVICE", "DCRUM_SERVICE_INSTANCE", "DEVICE_APPLICATION_METHOD", "DISK", "DOCKER_CONTAINER_GROUP_INSTANCE", "DYNAMO_DB_TABLE", "EBS_VOLUME", "EC2_INSTANCE", "ELASTIC_LOAD_BALANCER", "ENVIRONMENT", "EXTERNAL_SYNTHETIC_TEST_STEP", "GCP_ZONE", "GEOLOCATION", "GEOLOC_SITE", "GOOGLE_COMPUTE_ENGINE", "HOST", "HOST_GROUP", "HTTP_CHECK", "HTTP_CHECK_STEP", "HYPERVISOR", "KUBERNETES_CLUSTER", "KUBERNETES_NODE", "MOBILE_APPLICATION", "NETWORK_INTERFACE", "NEUTRON_SUBNET", "OPENSTACK_PROJECT", "OPENSTACK_REGION", "OPENSTACK_VM", "OS", "PROCESS_GROUP", "PROCESS_GROUP_INSTANCE", "RELATIONAL_DATABASE_SERVICE", "SERVICE", "SERVICE_INSTANCE", "SERVICE_METHOD", "SERVICE_METHOD_GROUP", "SWIFT_CONTAINER", "SYNTHETIC_LOCATION", "SYNTHETIC_TEST", "SYNTHETIC_TEST_STEP", "VIRTUALMACHINE", "VMWARE_DATACENTER"}, + DefaultValue: "APPLICATION", + Description: "Select the Dynatrace entity `meType` to associate with all events.", + IsOptional: true, + IsSecret: false, + Type: "enum", + }, + "tag": { + AllowedValues: []string{}, + DefaultValue: "", + Description: "If provided, all feature flag events matching the policy filter will be associated with Dynatrace entities with the same tag.", + IsOptional: true, + IsSecret: false, + Type: "string", + }, + "url": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the URL used to access your Dynatrace (managed or hosted) service. Follow the pattern shown in the placeholder text.", + IsOptional: false, + IsSecret: false, + Type: "uri", + }, + }, + "dynatrace-v2": { + "apiToken": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Dynatrace Access token. [Learn more](https://www.dynatrace.com/support/help/shortlink/api-authentication#generate-a-token) about generating tokens. The 'Access problem and event feed, metrics, and topology' scope is required.", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "entity": { + AllowedValues: []string{"APPLICATION", "APPLICATION_METHOD", "APPLICATION_METHOD_GROUP", "AUTO_SCALING_GROUP", "AUXILIARY_SYNTHETIC_TEST", "AWS_APPLICATION_LOAD_BALANCER", "AWS_AVAILABILITY_ZONE", "AWS_CREDENTIALS", "AWS_LAMBDA_FUNCTION", "AWS_NETWORK_LOAD_BALANCER", "AZURE_API_MANAGEMENT_SERVICE", "AZURE_APPLICATION_GATEWAY", "AZURE_COSMOS_DB", "AZURE_CREDENTIALS", "AZURE_EVENT_HUB", "AZURE_EVENT_HUB_NAMESPACE", "AZURE_FUNCTION_APP", "AZURE_IOT_HUB", "AZURE_LOAD_BALANCER", "AZURE_MGMT_GROUP", "AZURE_REDIS_CACHE", "AZURE_REGION", "AZURE_SERVICE_BUS_NAMESPACE", "AZURE_SERVICE_BUS_QUEUE", "AZURE_SERVICE_BUS_TOPIC", "AZURE_SQL_DATABASE", "AZURE_SQL_ELASTIC_POOL", "AZURE_SQL_SERVER", "AZURE_STORAGE_ACCOUNT", "AZURE_SUBSCRIPTION", "AZURE_TENANT", "AZURE_VM", "AZURE_VM_SCALE_SET", "AZURE_WEB_APP", "CF_APPLICATION", "CF_FOUNDATION", "CINDER_VOLUME", "CLOUD_APPLICATION", "CLOUD_APPLICATION_INSTANCE", "CLOUD_APPLICATION_NAMESPACE", "CONTAINER_GROUP", "CONTAINER_GROUP_INSTANCE", "CUSTOM_APPLICATION", "CUSTOM_DEVICE", "CUSTOM_DEVICE_GROUP", "DCRUM_APPLICATION", "DCRUM_SERVICE", "DCRUM_SERVICE_INSTANCE", "DEVICE_APPLICATION_METHOD", "DISK", "DOCKER_CONTAINER_GROUP_INSTANCE", "DYNAMO_DB_TABLE", "EBS_VOLUME", "EC2_INSTANCE", "ELASTIC_LOAD_BALANCER", "ENVIRONMENT", "EXTERNAL_SYNTHETIC_TEST_STEP", "GCP_ZONE", "GEOLOCATION", "GEOLOC_SITE", "GOOGLE_COMPUTE_ENGINE", "HOST", "HOST_GROUP", "HTTP_CHECK", "HTTP_CHECK_STEP", "HYPERVISOR", "KUBERNETES_CLUSTER", "KUBERNETES_NODE", "MOBILE_APPLICATION", "NETWORK_INTERFACE", "NEUTRON_SUBNET", "OPENSTACK_PROJECT", "OPENSTACK_REGION", "OPENSTACK_VM", "OS", "PROCESS_GROUP", "PROCESS_GROUP_INSTANCE", "RELATIONAL_DATABASE_SERVICE", "SERVICE", "SERVICE_INSTANCE", "SERVICE_METHOD", "SERVICE_METHOD_GROUP", "SWIFT_CONTAINER", "SYNTHETIC_LOCATION", "SYNTHETIC_TEST", "SYNTHETIC_TEST_STEP", "VIRTUALMACHINE", "VMWARE_DATACENTER"}, + DefaultValue: "APPLICATION", + Description: "Select the Dynatrace entity `meType` to associate with all events.", + IsOptional: true, + IsSecret: false, + Type: "enum", + }, + "tag": { + AllowedValues: []string{}, + DefaultValue: "", + Description: "If provided, all feature flag events matching the policy filter will be associated with Dynatrace entities with the same tag.", + IsOptional: true, + IsSecret: false, + Type: "string", + }, + "url": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the URL used to access your Dynatrace (managed or hosted) service. Follow the pattern shown in the placeholder text.", + IsOptional: false, + IsSecret: false, + Type: "uri", + }, + }, + "elastic": { + "index": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the index name where you want to store your LaunchDarkly data", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + "token": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the base64 _credentials_ based on your [API Key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html)", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "url": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the URL for your Elasticsearch endpoint, including the socket", + IsOptional: false, + IsSecret: false, + Type: "uri", + }, + }, + "grafana": { + "apiKey": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter Grafana [service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/) token. The service account must have the \"Annotation writer\" role.", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "endpointUrl": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Grafana instance's URL. This instance must be accessible to LaunchDarkly's servers. Do not include a trailing \"/\".", + IsOptional: false, + IsSecret: false, + Type: "uri", + }, + }, + "honeycomb": { + "apiKey": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [Honeycomb API key](https://ui.honeycomb.io/teams).", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "datasetName": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter the name of your Honeycomb dataset. This associates LaunchDarkly data with the corresponding Honeycomb dataset.", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + }, + "kosli": { + "secret": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Kosli secret", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "webhookUrl": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Kosli webhook URL", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + }, + "last9": { + "apiToken": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter Last9 API token. This token must have the `write` access to the Last9 API.", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "endpointUrl": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Last9 api base url. Do not include a trailing \"/\".", + IsOptional: false, + IsSecret: false, + Type: "uri", + }, + "last9": { + AllowedValues: []string{}, + DefaultValue: "", + Description: "If provided, all feature flag events matching the policy filter will be associated with Last9 entities with the same tag.", + IsOptional: true, + IsSecret: false, + Type: "string", + }, + }, + "logdna": { + "ingestionKey": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [LogDNA ingestion key](https://app.logdna.com/manage/api-keys).", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "level": { + AllowedValues: []string{}, + DefaultValue: "INFO", + Description: "The level of log messages from LaunchDarkly.", + IsOptional: true, + IsSecret: false, + Type: "string", + }, + }, + "msteams": {"url": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your Microsoft Teams [incoming webhook URL](https://launchdarkly.com/docs/integrations/microsoft-teams/webhooks#set-up-a-workflow-in-microsoft-teams).", + IsOptional: false, + IsSecret: false, + Type: "uri", + }}, + "new-relic-apm": { + "apiKey": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [New Relic REST API key](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#rest-api-key).", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "applicationId": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [New Relic application ID](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/app-id-other-product-ids#ui).", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + "domain": { + AllowedValues: []string{"api.newrelic.com", "api.eu.newrelic.com"}, + DefaultValue: "api.newrelic.com", + Description: "Your New Relic data center. The default(US) is \"api.newrelic.com\". Use \"api.eu.newrelic.com\" if you are using the EU data center.", + IsOptional: true, + IsSecret: false, + Type: "enum", + }, + }, + "pagerduty": {"integrationKey": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [PagerDuty integration key](https://support.pagerduty.com/main/docs/services-and-integrations#add-integrations-to-an-existing-service).", + IsOptional: false, + IsSecret: false, + Type: "string", + }}, + "signalfx": { + "accessToken": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [Splunk Observability Cloud access token](https://docs.splunk.com/Observability/admin/authentication-tokens/tokens.html#working-with-access-tokens).", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + "realm": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [Splunk Observability Cloud realm](https://dev.splunk.com/observability/docs/realms_in_endpoints/).", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + }, + "splunk": { + "base-url": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your [Splunk HTTP event collector base URL](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector) (omitting the path).", + IsOptional: false, + IsSecret: false, + Type: "string", + }, + "skip-ca-verification": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Splunk Cloud instances sign their own SSL certificates by default. If you use Splunk Cloud, you may need to skip certificate validation in order for this integration to work.", + IsOptional: false, + IsSecret: false, + Type: "boolean", + }, + "token": { + AllowedValues: []string{}, + DefaultValue: nil, + Description: "Enter your HTTP event collector token value.", + IsOptional: false, + IsSecret: true, + Type: "string", + }, + }, +} // VALID_TRIGGER_INTEGRATIONS is a list of valid trigger integrations. This list is generated from the LaunchDarkly integration manifest API. -var VALID_TRIGGER_INTEGRATIONS = []string{"generic-trigger"} +var VALID_TRIGGER_INTEGRATIONS = []string{"generic-trigger", "datadog", "dynatrace", "dynatrace-cloud-automation", "honeycomb", "new-relic-apm", "signalfx"} From bc7210ec86b856e26881f298ea92d06e868a286d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:22:18 +0000 Subject: [PATCH 06/11] style: capitalize AI Config in descriptions and documentation Address reviewer feedback by changing 'AI config' to 'AI Config' and 'AI configs' to 'AI Configs' throughout the codebase. Changes include: - Updated schema field descriptions in resource and data source - Regenerated documentation files with proper capitalization - Applied gofmts formatting Link to Devin run: https://app.devin.ai/sessions/4ce205546ce54b55a0c511fed243dc6d Requested by: traci@launchdarkly.com (@tracisiebel) Co-Authored-By: traci@launchdarkly.com --- docs/data-sources/ai_config.md | 22 +++++++++---------- docs/resources/ai_config.md | 22 +++++++++---------- .../data_source_launchdarkly_ai_config.go | 18 +++++++-------- .../resource_launchdarkly_ai_config.go | 18 +++++++-------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/data-sources/ai_config.md b/docs/data-sources/ai_config.md index 8d4914d0..61a9dc5e 100644 --- a/docs/data-sources/ai_config.md +++ b/docs/data-sources/ai_config.md @@ -3,16 +3,16 @@ page_title: "launchdarkly_ai_config Data Source - launchdarkly" subcategory: "" description: |- - Provides a LaunchDarkly AI config data source. - This data source allows you to retrieve AI config information from your LaunchDarkly project. + Provides a LaunchDarkly AI Config data source. + This data source allows you to retrieve AI Config information from your LaunchDarkly project. -> Note: AI Configs are currently in beta. --- # launchdarkly_ai_config (Data Source) -Provides a LaunchDarkly AI config data source. +Provides a LaunchDarkly AI Config data source. -This data source allows you to retrieve AI config information from your LaunchDarkly project. +This data source allows you to retrieve AI Config information from your LaunchDarkly project. -> **Note:** AI Configs are currently in beta. @@ -23,15 +23,15 @@ This data source allows you to retrieve AI config information from your LaunchDa ### Required -- `key` (String) The unique key of the AI config. +- `key` (String) The unique key of the AI Config. - `project_key` (String) The project key. ### Read-Only -- `description` (String) The description of the AI config. +- `description` (String) The description of the AI Config. - `id` (String) The ID of this resource. -- `maintainer_id` (String) The ID of the member who maintains this AI config. -- `maintainer_team_key` (String) The key of the team that maintains this AI config. -- `name` (String) The human-readable name of the AI config. -- `tags` (Set of String) Tags associated with the AI config. -- `version` (Number) The version of the AI config. +- `maintainer_id` (String) The ID of the member who maintains this AI Config. +- `maintainer_team_key` (String) The key of the team that maintains this AI Config. +- `name` (String) The human-readable name of the AI Config. +- `tags` (Set of String) Tags associated with the AI Config. +- `version` (Number) The version of the AI Config. diff --git a/docs/resources/ai_config.md b/docs/resources/ai_config.md index 145cafea..31df81f0 100644 --- a/docs/resources/ai_config.md +++ b/docs/resources/ai_config.md @@ -3,16 +3,16 @@ page_title: "launchdarkly_ai_config Resource - launchdarkly" subcategory: "" description: |- - Provides a LaunchDarkly AI config resource. - This resource allows you to create and manage AI configs within your LaunchDarkly project. + Provides a LaunchDarkly AI Config resource. + This resource allows you to create and manage AI Configs within your LaunchDarkly project. -> Note: AI Configs are currently in beta. --- # launchdarkly_ai_config (Resource) -Provides a LaunchDarkly AI config resource. +Provides a LaunchDarkly AI Config resource. -This resource allows you to create and manage AI configs within your LaunchDarkly project. +This resource allows you to create and manage AI Configs within your LaunchDarkly project. -> **Note:** AI Configs are currently in beta. @@ -23,18 +23,18 @@ This resource allows you to create and manage AI configs within your LaunchDarkl ### Required -- `key` (String) The unique key of the AI config. -- `name` (String) The human-readable name of the AI config. +- `key` (String) The unique key of the AI Config. +- `name` (String) The human-readable name of the AI Config. - `project_key` (String) The project key. ### Optional -- `description` (String) The description of the AI config. -- `maintainer_id` (String) The ID of the member who maintains this AI config. -- `maintainer_team_key` (String) The key of the team that maintains this AI config. -- `tags` (Set of String) Tags associated with the AI config. +- `description` (String) The description of the AI Config. +- `maintainer_id` (String) The ID of the member who maintains this AI Config. +- `maintainer_team_key` (String) The key of the team that maintains this AI Config. +- `tags` (Set of String) Tags associated with the AI Config. ### Read-Only - `id` (String) The ID of this resource. -- `version` (Number) The version of the AI config. +- `version` (Number) The version of the AI Config. diff --git a/launchdarkly/data_source_launchdarkly_ai_config.go b/launchdarkly/data_source_launchdarkly_ai_config.go index 7cda3739..cdae8573 100644 --- a/launchdarkly/data_source_launchdarkly_ai_config.go +++ b/launchdarkly/data_source_launchdarkly_ai_config.go @@ -19,43 +19,43 @@ func dataSourceAIConfig() *schema.Resource { KEY: { Type: schema.TypeString, Required: true, - Description: "The unique key of the AI config.", + Description: "The unique key of the AI Config.", }, NAME: { Type: schema.TypeString, Computed: true, - Description: "The human-readable name of the AI config.", + Description: "The human-readable name of the AI Config.", }, DESCRIPTION: { Type: schema.TypeString, Computed: true, - Description: "The description of the AI config.", + Description: "The description of the AI Config.", }, TAGS: { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, - Description: "Tags associated with the AI config.", + Description: "Tags associated with the AI Config.", }, MAINTAINER_ID: { Type: schema.TypeString, Computed: true, - Description: "The ID of the member who maintains this AI config.", + Description: "The ID of the member who maintains this AI Config.", }, MAINTAINER_TEAM_KEY: { Type: schema.TypeString, Computed: true, - Description: "The key of the team that maintains this AI config.", + Description: "The key of the team that maintains this AI Config.", }, VERSION: { Type: schema.TypeInt, Computed: true, - Description: "The version of the AI config.", + Description: "The version of the AI Config.", }, }, - Description: `Provides a LaunchDarkly AI config data source. + Description: `Provides a LaunchDarkly AI Config data source. -This data source allows you to retrieve AI config information from your LaunchDarkly project. +This data source allows you to retrieve AI Config information from your LaunchDarkly project. -> **Note:** AI Configs are currently in beta.`, } diff --git a/launchdarkly/resource_launchdarkly_ai_config.go b/launchdarkly/resource_launchdarkly_ai_config.go index bd6c7862..91ee7716 100644 --- a/launchdarkly/resource_launchdarkly_ai_config.go +++ b/launchdarkly/resource_launchdarkly_ai_config.go @@ -34,44 +34,44 @@ func resourceAIConfig() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - Description: "The unique key of the AI config.", + Description: "The unique key of the AI Config.", }, NAME: { Type: schema.TypeString, Required: true, - Description: "The human-readable name of the AI config.", + Description: "The human-readable name of the AI Config.", }, DESCRIPTION: { Type: schema.TypeString, Optional: true, - Description: "The description of the AI config.", + Description: "The description of the AI Config.", }, TAGS: { Type: schema.TypeSet, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, - Description: "Tags associated with the AI config.", + Description: "Tags associated with the AI Config.", }, MAINTAINER_ID: { Type: schema.TypeString, Optional: true, - Description: "The ID of the member who maintains this AI config.", + Description: "The ID of the member who maintains this AI Config.", }, MAINTAINER_TEAM_KEY: { Type: schema.TypeString, Optional: true, - Description: "The key of the team that maintains this AI config.", + Description: "The key of the team that maintains this AI Config.", }, VERSION: { Type: schema.TypeInt, Computed: true, - Description: "The version of the AI config.", + Description: "The version of the AI Config.", }, }, - Description: `Provides a LaunchDarkly AI config resource. + Description: `Provides a LaunchDarkly AI Config resource. -This resource allows you to create and manage AI configs within your LaunchDarkly project. +This resource allows you to create and manage AI Configs within your LaunchDarkly project. -> **Note:** AI Configs are currently in beta.`, } From 93cc56f5748c37496f8953cbd21ecec21dfae4ec Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 18:28:05 +0000 Subject: [PATCH 07/11] docs: regenerate integration docs with full integration lists Regenerate audit_log_subscription and flag_trigger documentation to include the full list of supported integrations from integration_configs_generated.go. This fixes the CI generate check failure. Changes: - audit_log_subscription: Update to list all supported integrations (chronosphere, cloudtrail, datadog, dynatrace, etc.) - flag_trigger: Update to list all supported integrations (generic-trigger, datadog, dynatrace, honeycomb, etc.) Link to Devin run: https://app.devin.ai/sessions/4ce205546ce54b55a0c511fed243dc6d Requested by: traci@launchdarkly.com (@tracisiebel) Co-Authored-By: traci@launchdarkly.com --- docs/data-sources/audit_log_subscription.md | 2 +- docs/data-sources/flag_trigger.md | 2 +- docs/resources/audit_log_subscription.md | 2 +- docs/resources/flag_trigger.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data-sources/audit_log_subscription.md b/docs/data-sources/audit_log_subscription.md index 09708851..51784dfb 100644 --- a/docs/data-sources/audit_log_subscription.md +++ b/docs/data-sources/audit_log_subscription.md @@ -28,7 +28,7 @@ data "launchdarkly_audit_log_subscription" "test" { ### Required - `id` (String) The audit log subscription ID. -- `integration_key` (String) The integration key. Supported integration keys are `slack`. +- `integration_key` (String) The integration key. Supported integration keys are `chronosphere`, `cloudtrail`, `datadog`, `dynatrace`, `dynatrace-v2`, `elastic`, `grafana`, `honeycomb`, `kosli`, `last9`, `logdna`, `msteams`, `new-relic-apm`, `pagerduty`, `signalfx`, `slack`, and `splunk`. ### Read-Only diff --git a/docs/data-sources/flag_trigger.md b/docs/data-sources/flag_trigger.md index c278f801..ec0ae10c 100644 --- a/docs/data-sources/flag_trigger.md +++ b/docs/data-sources/flag_trigger.md @@ -47,7 +47,7 @@ Please note that if you did not save this upon creation of the resource, you wil - `enabled` (Boolean) Whether the trigger is currently active or not. - `instructions` (List of Object) Instructions containing the action to perform when invoking the trigger. Currently supported flag actions are `turnFlagOn` and `turnFlagOff`. This must be passed as the key-value pair `{ kind = "" }`. (see [below for nested schema](#nestedatt--instructions)) -- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`. `generic-trigger` should be used for integrations not explicitly supported. +- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`, `datadog`, `dynatrace`, `dynatrace-cloud-automation`, `honeycomb`, `new-relic-apm`, and `signalfx`. `generic-trigger` should be used for integrations not explicitly supported. - `maintainer_id` (String) The ID of the member responsible for maintaining the flag trigger. If created via Terraform, this value will be the ID of the member associated with the API key used for your provider configuration. - `trigger_url` (String, Sensitive) The unique URL used to invoke the trigger. diff --git a/docs/resources/audit_log_subscription.md b/docs/resources/audit_log_subscription.md index 6ce0b627..8233ca4a 100644 --- a/docs/resources/audit_log_subscription.md +++ b/docs/resources/audit_log_subscription.md @@ -41,7 +41,7 @@ resource "launchdarkly_audit_log_subscription" "example" { ### Required - `config` (Map of String) The set of configuration fields corresponding to the value defined for `integration_key`. Refer to the `formVariables` field in the corresponding `integrations//manifest.json` file in [this repo](https://github.com/launchdarkly/integration-framework/tree/master/integrations) for a full list of fields for the integration you wish to configure. **IMPORTANT**: Please note that Terraform will only accept these in snake case, regardless of the case shown in the manifest. -- `integration_key` (String) The integration key. Supported integration keys are `slack`. A change in this field will force the destruction of the existing resource and the creation of a new one. +- `integration_key` (String) The integration key. Supported integration keys are `chronosphere`, `cloudtrail`, `datadog`, `dynatrace`, `dynatrace-v2`, `elastic`, `grafana`, `honeycomb`, `kosli`, `last9`, `logdna`, `msteams`, `new-relic-apm`, `pagerduty`, `signalfx`, `slack`, and `splunk`. A change in this field will force the destruction of the existing resource and the creation of a new one. - `name` (String) A human-friendly name for your audit log subscription viewable from within the LaunchDarkly Integrations page. - `on` (Boolean) Whether or not you want your subscription enabled, i.e. to actively send events. - `statements` (Block List, Min: 1) A block representing the resources to which you wish to subscribe. (see [below for nested schema](#nestedblock--statements)) diff --git a/docs/resources/flag_trigger.md b/docs/resources/flag_trigger.md index ac5c8602..b3b4178f 100644 --- a/docs/resources/flag_trigger.md +++ b/docs/resources/flag_trigger.md @@ -42,7 +42,7 @@ resource "launchdarkly_flag_trigger" "example" { - `env_key` (String) The unique key of the environment the flag trigger will work in. A change in this field will force the destruction of the existing resource and the creation of a new one. - `flag_key` (String) The unique key of the associated flag. A change in this field will force the destruction of the existing resource and the creation of a new one. - `instructions` (Block List, Min: 1, Max: 1) Instructions containing the action to perform when invoking the trigger. Currently supported flag actions are `turnFlagOn` and `turnFlagOff`. This must be passed as the key-value pair `{ kind = "" }`. (see [below for nested schema](#nestedblock--instructions)) -- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`. `generic-trigger` should be used for integrations not explicitly supported. A change in this field will force the destruction of the existing resource and the creation of a new one. +- `integration_key` (String) The unique identifier of the integration you intend to set your trigger up with. Currently supported are `generic-trigger`, `datadog`, `dynatrace`, `dynatrace-cloud-automation`, `honeycomb`, `new-relic-apm`, and `signalfx`. `generic-trigger` should be used for integrations not explicitly supported. A change in this field will force the destruction of the existing resource and the creation of a new one. - `project_key` (String) The unique key of the project encompassing the associated flag. A change in this field will force the destruction of the existing resource and the creation of a new one. ### Read-Only From bc418c8118917659f11880d63c63ca76406e830d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:48:41 +0000 Subject: [PATCH 08/11] test: add comprehensive acceptance tests for AI Config resource and data source Add comprehensive acceptance tests covering: Resource tests: - Create: Test basic AI Config creation with minimal required fields - Update: Test updating name, description, and tags - WithTags: Test AI Config creation with tags - WithTeamMaintainer: Test AI Config with team maintainer - ConflictingMaintainers: Test validation error when both maintainer_id and maintainer_team_key are set - Import: Test import functionality for all scenarios Data source tests: - exists: Test data source retrieval with all fields - existsWithTeamMaintainer: Test data source with team maintainer - noMatchReturnsError: Test error handling for non-existent AI Config Also added ConflictsWith validation to maintainer_id and maintainer_team_key fields to ensure mutual exclusivity and provide clear error messages. Link to Devin run: https://app.devin.ai/sessions/4ce205546ce54b55a0c511fed243dc6d Requested by: traci@launchdarkly.com (@tracisiebel) Co-Authored-By: traci@launchdarkly.com --- ...data_source_launchdarkly_ai_config_test.go | 117 +++++++++ .../resource_launchdarkly_ai_config.go | 14 +- .../resource_launchdarkly_ai_config_test.go | 233 ++++++++++++++++++ 3 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 launchdarkly/data_source_launchdarkly_ai_config_test.go create mode 100644 launchdarkly/resource_launchdarkly_ai_config_test.go diff --git a/launchdarkly/data_source_launchdarkly_ai_config_test.go b/launchdarkly/data_source_launchdarkly_ai_config_test.go new file mode 100644 index 00000000..976f3fdf --- /dev/null +++ b/launchdarkly/data_source_launchdarkly_ai_config_test.go @@ -0,0 +1,117 @@ +package launchdarkly + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +const ( + testAccDataSourceAIConfig = ` +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Test AI Config" + description = "Test description" + tags = ["terraform", "test"] +} + +data "launchdarkly_ai_config" "test" { + project_key = launchdarkly_ai_config.test.project_key + key = launchdarkly_ai_config.test.key +} +` + + testAccDataSourceAIConfigWithTeam = ` +resource "launchdarkly_team" "test" { + key = "test-team" + name = "Test Team" +} + +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Test AI Config" + maintainer_team_key = launchdarkly_team.test.key +} + +data "launchdarkly_ai_config" "test" { + project_key = launchdarkly_ai_config.test.project_key + key = launchdarkly_ai_config.test.key +} +` +) + +func TestAccDataSourceAIConfig_exists(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "data.launchdarkly_ai_config.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccDataSourceAIConfig), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, ID), + resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), + resource.TestCheckResourceAttr(resourceName, KEY, "test-ai-config"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "Test description"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, VERSION), + ), + }, + }, + }) +} + +func TestAccDataSourceAIConfig_existsWithTeamMaintainer(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "data.launchdarkly_ai_config.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccDataSourceAIConfigWithTeam), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, ID), + resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), + resource.TestCheckResourceAttr(resourceName, KEY, "test-ai-config"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, MAINTAINER_TEAM_KEY, "test-team"), + resource.TestCheckResourceAttrSet(resourceName, VERSION), + ), + }, + }, + }) +} + +func TestAccDataSourceAIConfig_noMatchReturnsError(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + aiConfigKey := acctest.RandStringFromCharSet(24, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, fmt.Sprintf(` +data "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "%s" +} +`, aiConfigKey)), + ExpectError: regexp.MustCompile(`failed to get AI config`), + }, + }, + }) +} diff --git a/launchdarkly/resource_launchdarkly_ai_config.go b/launchdarkly/resource_launchdarkly_ai_config.go index 91ee7716..6cf80c80 100644 --- a/launchdarkly/resource_launchdarkly_ai_config.go +++ b/launchdarkly/resource_launchdarkly_ai_config.go @@ -53,14 +53,16 @@ func resourceAIConfig() *schema.Resource { Description: "Tags associated with the AI Config.", }, MAINTAINER_ID: { - Type: schema.TypeString, - Optional: true, - Description: "The ID of the member who maintains this AI Config.", + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{MAINTAINER_TEAM_KEY}, + Description: "The ID of the member who maintains this AI Config.", }, MAINTAINER_TEAM_KEY: { - Type: schema.TypeString, - Optional: true, - Description: "The key of the team that maintains this AI Config.", + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{MAINTAINER_ID}, + Description: "The key of the team that maintains this AI Config.", }, VERSION: { Type: schema.TypeInt, diff --git a/launchdarkly/resource_launchdarkly_ai_config_test.go b/launchdarkly/resource_launchdarkly_ai_config_test.go new file mode 100644 index 00000000..d7112656 --- /dev/null +++ b/launchdarkly/resource_launchdarkly_ai_config_test.go @@ -0,0 +1,233 @@ +package launchdarkly + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +const ( + testAccAIConfigBasic = ` +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Test AI Config" +} +` + + testAccAIConfigUpdate = ` +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Updated AI Config" + description = "Updated description" + tags = ["terraform", "updated"] +} +` + + testAccAIConfigWithTags = ` +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Test AI Config" + description = "AI Config with tags" + tags = ["terraform", "test"] +} +` + + testAccAIConfigWithTeamMaintainer = ` +resource "launchdarkly_team" "test" { + key = "test-team" + name = "Test Team" +} + +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Test AI Config" + maintainer_team_key = launchdarkly_team.test.key +} +` + + testAccAIConfigConflictingMaintainers = ` +resource "launchdarkly_team" "test" { + key = "test-team" + name = "Test Team" +} + +resource "launchdarkly_ai_config" "test" { + project_key = launchdarkly_project.test.key + key = "test-ai-config" + name = "Test AI Config" + maintainer_id = "507f1f77bcf86cd799439011" + maintainer_team_key = launchdarkly_team.test.key +} +` +) + +func TestAccAIConfig_Create(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_ai_config.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccAIConfigBasic), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckAIConfigExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), + resource.TestCheckResourceAttr(resourceName, KEY, "test-ai-config"), + resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), + resource.TestCheckResourceAttr(resourceName, "tags.#", "0"), + resource.TestCheckResourceAttrSet(resourceName, VERSION), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAIConfig_Update(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_ai_config.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccAIConfigBasic), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckAIConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), + resource.TestCheckResourceAttr(resourceName, KEY, "test-ai-config"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "0"), + ), + }, + { + Config: withRandomProject(projectKey, testAccAIConfigUpdate), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckAIConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Updated AI Config"), + resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "Updated description"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), + resource.TestCheckResourceAttrSet(resourceName, VERSION), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAIConfig_WithTags(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_ai_config.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccAIConfigWithTags), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckAIConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), + resource.TestCheckResourceAttr(resourceName, DESCRIPTION, "AI Config with tags"), + resource.TestCheckResourceAttr(resourceName, "tags.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAIConfig_WithTeamMaintainer(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resourceName := "launchdarkly_ai_config.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccAIConfigWithTeamMaintainer), + Check: resource.ComposeTestCheckFunc( + testAccCheckProjectExists("launchdarkly_project.test"), + testAccCheckAIConfigExists(resourceName), + resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), + resource.TestCheckResourceAttr(resourceName, MAINTAINER_TEAM_KEY, "test-team"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAIConfig_ConflictingMaintainers(t *testing.T) { + projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: withRandomProject(projectKey, testAccAIConfigConflictingMaintainers), + ExpectError: regexp.MustCompile(`"maintainer_id".*conflicts with maintainer_team_key`), + }, + }, + }) +} + +func testAccCheckAIConfigExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("not found: %s", resourceName) + } + if rs.Primary.ID == "" { + return fmt.Errorf("AI config ID is not set") + } + client := testAccProvider.Meta().(*Client) + projectKey, key, err := aiConfigIdToKeys(rs.Primary.ID) + if err != nil { + return err + } + _, _, err = client.ld.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + if err != nil { + return fmt.Errorf("received an error getting AI config: %s", err) + } + return nil + } +} From dd15caf200dcfe5408223542b13652464c8707fa Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:55:29 +0000 Subject: [PATCH 09/11] fix: use beta client for AI Config API calls and fix test collisions Fix CI failures in AI Config acceptance tests: 1. Beta API Client: - Add ldBeta field to Client struct with LD-API-Version: beta header - Update all AI Config API calls to use client.ldBeta instead of client.ld - This fixes the 'lDAPIVersion is required and must be specified' error 2. Test Collision Fixes: - Randomize team keys in tests to prevent parallel test collisions - Convert testAccAIConfigWithTeamMaintainer to format string - Convert testAccAIConfigConflictingMaintainers to format string - Convert testAccDataSourceAIConfigWithTeam to format string - Use acctest.RandStringFromCharSet for team keys in all tests 3. Test Improvements: - Make ConflictsWith error regex case-insensitive for robustness These changes ensure AI Config tests pass reliably in CI by properly setting the beta API version header and avoiding account-scoped resource collisions in parallel tests. Link to Devin run: https://app.devin.ai/sessions/4ce205546ce54b55a0c511fed243dc6d Requested by: traci@launchdarkly.com (@tracisiebel) Co-Authored-By: traci@launchdarkly.com --- launchdarkly/config.go | 4 ++++ .../data_source_launchdarkly_ai_config_test.go | 9 +++++---- .../resource_launchdarkly_ai_config.go | 10 +++++----- .../resource_launchdarkly_ai_config_test.go | 18 ++++++++++-------- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/launchdarkly/config.go b/launchdarkly/config.go index 1febdc3f..530f3cee 100644 --- a/launchdarkly/config.go +++ b/launchdarkly/config.go @@ -35,6 +35,8 @@ type Client struct { // ld is the standard API client that we use in most cases to interact with LaunchDarkly's APIs. ld *ldapi.APIClient + ldBeta *ldapi.APIClient + // ld404Retry is the same as ld except that it will also retry 404s with an exponential backoff. In most cases `ld` should be used instead. sc-218015 ld404Retry *ldapi.APIClient ctx context.Context @@ -79,6 +81,7 @@ func baseNewClient(token string, apiHost string, oauth bool, httpTimeoutSeconds } standardConfig := newLDClientConfig(apiHost, httpTimeoutSeconds, apiVersion, standardRetryPolicy) + betaConfig := newLDClientConfig(apiHost, httpTimeoutSeconds, "beta", standardRetryPolicy) configWith404Retries := newLDClientConfig(apiHost, httpTimeoutSeconds, apiVersion, retryPolicyWith404Retries) ctx := context.WithValue(context.Background(), ldapi.ContextAPIKeys, map[string]ldapi.APIKey{ @@ -97,6 +100,7 @@ func baseNewClient(token string, apiHost string, oauth bool, httpTimeoutSeconds apiKey: token, apiHost: apiHost, ld: ldapi.NewAPIClient(standardConfig), + ldBeta: ldapi.NewAPIClient(betaConfig), ld404Retry: ldapi.NewAPIClient(configWith404Retries), ctx: ctx, fallbackClient: fallbackClient, diff --git a/launchdarkly/data_source_launchdarkly_ai_config_test.go b/launchdarkly/data_source_launchdarkly_ai_config_test.go index 976f3fdf..78690323 100644 --- a/launchdarkly/data_source_launchdarkly_ai_config_test.go +++ b/launchdarkly/data_source_launchdarkly_ai_config_test.go @@ -25,9 +25,9 @@ data "launchdarkly_ai_config" "test" { } ` - testAccDataSourceAIConfigWithTeam = ` + testAccDataSourceAIConfigWithTeamFmt = ` resource "launchdarkly_team" "test" { - key = "test-team" + key = "%s" name = "Test Team" } @@ -72,6 +72,7 @@ func TestAccDataSourceAIConfig_exists(t *testing.T) { func TestAccDataSourceAIConfig_existsWithTeamMaintainer(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + teamKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "data.launchdarkly_ai_config.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -80,13 +81,13 @@ func TestAccDataSourceAIConfig_existsWithTeamMaintainer(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: withRandomProject(projectKey, testAccDataSourceAIConfigWithTeam), + Config: withRandomProject(projectKey, fmt.Sprintf(testAccDataSourceAIConfigWithTeamFmt, teamKey)), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(resourceName, ID), resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), resource.TestCheckResourceAttr(resourceName, KEY, "test-ai-config"), resource.TestCheckResourceAttr(resourceName, PROJECT_KEY, projectKey), - resource.TestCheckResourceAttr(resourceName, MAINTAINER_TEAM_KEY, "test-team"), + resource.TestCheckResourceAttr(resourceName, MAINTAINER_TEAM_KEY, teamKey), resource.TestCheckResourceAttrSet(resourceName, VERSION), ), }, diff --git a/launchdarkly/resource_launchdarkly_ai_config.go b/launchdarkly/resource_launchdarkly_ai_config.go index 6cf80c80..db003cb7 100644 --- a/launchdarkly/resource_launchdarkly_ai_config.go +++ b/launchdarkly/resource_launchdarkly_ai_config.go @@ -119,7 +119,7 @@ func resourceAIConfigCreate(ctx context.Context, d *schema.ResourceData, metaRaw var err error err = client.withConcurrency(ctx, func() error { - _, _, err = client.ld.AIConfigsBetaApi.PostAIConfig(client.ctx, projectKey).AIConfigPost(aiConfigPost).Execute() + _, _, err = client.ldBeta.AIConfigsBetaApi.PostAIConfig(client.ctx, projectKey).AIConfigPost(aiConfigPost).Execute() return err }) @@ -142,7 +142,7 @@ func resourceAIConfigRead(ctx context.Context, d *schema.ResourceData, metaRaw i var res *http.Response var err error err = client.withConcurrency(ctx, func() error { - aiConfig, res, err = client.ld.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + aiConfig, res, err = client.ldBeta.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() return err }) @@ -201,7 +201,7 @@ func resourceAIConfigUpdate(ctx context.Context, d *schema.ResourceData, metaRaw var err error err = client.withConcurrency(ctx, func() error { - _, _, err = client.ld.AIConfigsBetaApi.PatchAIConfig(client.ctx, projectKey, key).AIConfigPatch(aiConfigPatch).Execute() + _, _, err = client.ldBeta.AIConfigsBetaApi.PatchAIConfig(client.ctx, projectKey, key).AIConfigPatch(aiConfigPatch).Execute() return err }) @@ -221,7 +221,7 @@ func resourceAIConfigDelete(ctx context.Context, d *schema.ResourceData, metaRaw var err error err = client.withConcurrency(ctx, func() error { - _, err = client.ld.AIConfigsBetaApi.DeleteAIConfig(client.ctx, projectKey, key).Execute() + _, err = client.ldBeta.AIConfigsBetaApi.DeleteAIConfig(client.ctx, projectKey, key).Execute() return err }) @@ -240,7 +240,7 @@ func resourceAIConfigExists(d *schema.ResourceData, metaRaw interface{}) (bool, var res *http.Response var err error err = client.withConcurrency(client.ctx, func() error { - _, res, err = client.ld.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + _, res, err = client.ldBeta.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() return err }) diff --git a/launchdarkly/resource_launchdarkly_ai_config_test.go b/launchdarkly/resource_launchdarkly_ai_config_test.go index d7112656..34f98170 100644 --- a/launchdarkly/resource_launchdarkly_ai_config_test.go +++ b/launchdarkly/resource_launchdarkly_ai_config_test.go @@ -39,9 +39,9 @@ resource "launchdarkly_ai_config" "test" { } ` - testAccAIConfigWithTeamMaintainer = ` + testAccAIConfigWithTeamMaintainerFmt = ` resource "launchdarkly_team" "test" { - key = "test-team" + key = "%s" name = "Test Team" } @@ -53,9 +53,9 @@ resource "launchdarkly_ai_config" "test" { } ` - testAccAIConfigConflictingMaintainers = ` + testAccAIConfigConflictingMaintainersFmt = ` resource "launchdarkly_team" "test" { - key = "test-team" + key = "%s" name = "Test Team" } @@ -169,6 +169,7 @@ func TestAccAIConfig_WithTags(t *testing.T) { func TestAccAIConfig_WithTeamMaintainer(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + teamKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resourceName := "launchdarkly_ai_config.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { @@ -177,12 +178,12 @@ func TestAccAIConfig_WithTeamMaintainer(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: withRandomProject(projectKey, testAccAIConfigWithTeamMaintainer), + Config: withRandomProject(projectKey, fmt.Sprintf(testAccAIConfigWithTeamMaintainerFmt, teamKey)), Check: resource.ComposeTestCheckFunc( testAccCheckProjectExists("launchdarkly_project.test"), testAccCheckAIConfigExists(resourceName), resource.TestCheckResourceAttr(resourceName, NAME, "Test AI Config"), - resource.TestCheckResourceAttr(resourceName, MAINTAINER_TEAM_KEY, "test-team"), + resource.TestCheckResourceAttr(resourceName, MAINTAINER_TEAM_KEY, teamKey), ), }, { @@ -196,6 +197,7 @@ func TestAccAIConfig_WithTeamMaintainer(t *testing.T) { func TestAccAIConfig_ConflictingMaintainers(t *testing.T) { projectKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + teamKey := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) @@ -203,8 +205,8 @@ func TestAccAIConfig_ConflictingMaintainers(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: withRandomProject(projectKey, testAccAIConfigConflictingMaintainers), - ExpectError: regexp.MustCompile(`"maintainer_id".*conflicts with maintainer_team_key`), + Config: withRandomProject(projectKey, fmt.Sprintf(testAccAIConfigConflictingMaintainersFmt, teamKey)), + ExpectError: regexp.MustCompile(`(?i)maintainer_id.*conflicts.*maintainer_team_key`), }, }, }) From eb633f65279718d51cdfc7ec55beaebe395cc51b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:39:15 +0000 Subject: [PATCH 10/11] fix: add LDAPIVersion parameter to all AI Config API calls The AI Config Beta API requires the LDAPIVersion parameter to be set on each request via the .LDAPIVersion("beta") method call. Previously we only configured the beta header on the client, but the generated API client requires the parameter on each request object. This fixes the 'lDAPIVersion is required and must be specified' error in acceptance tests. Co-Authored-By: traci@launchdarkly.com --- launchdarkly/resource_launchdarkly_ai_config.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launchdarkly/resource_launchdarkly_ai_config.go b/launchdarkly/resource_launchdarkly_ai_config.go index db003cb7..7601b938 100644 --- a/launchdarkly/resource_launchdarkly_ai_config.go +++ b/launchdarkly/resource_launchdarkly_ai_config.go @@ -119,7 +119,7 @@ func resourceAIConfigCreate(ctx context.Context, d *schema.ResourceData, metaRaw var err error err = client.withConcurrency(ctx, func() error { - _, _, err = client.ldBeta.AIConfigsBetaApi.PostAIConfig(client.ctx, projectKey).AIConfigPost(aiConfigPost).Execute() + _, _, err = client.ldBeta.AIConfigsBetaApi.PostAIConfig(client.ctx, projectKey).LDAPIVersion("beta").AIConfigPost(aiConfigPost).Execute() return err }) @@ -142,7 +142,7 @@ func resourceAIConfigRead(ctx context.Context, d *schema.ResourceData, metaRaw i var res *http.Response var err error err = client.withConcurrency(ctx, func() error { - aiConfig, res, err = client.ldBeta.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + aiConfig, res, err = client.ldBeta.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).LDAPIVersion("beta").Execute() return err }) @@ -201,7 +201,7 @@ func resourceAIConfigUpdate(ctx context.Context, d *schema.ResourceData, metaRaw var err error err = client.withConcurrency(ctx, func() error { - _, _, err = client.ldBeta.AIConfigsBetaApi.PatchAIConfig(client.ctx, projectKey, key).AIConfigPatch(aiConfigPatch).Execute() + _, _, err = client.ldBeta.AIConfigsBetaApi.PatchAIConfig(client.ctx, projectKey, key).LDAPIVersion("beta").AIConfigPatch(aiConfigPatch).Execute() return err }) @@ -221,7 +221,7 @@ func resourceAIConfigDelete(ctx context.Context, d *schema.ResourceData, metaRaw var err error err = client.withConcurrency(ctx, func() error { - _, err = client.ldBeta.AIConfigsBetaApi.DeleteAIConfig(client.ctx, projectKey, key).Execute() + _, err = client.ldBeta.AIConfigsBetaApi.DeleteAIConfig(client.ctx, projectKey, key).LDAPIVersion("beta").Execute() return err }) @@ -240,7 +240,7 @@ func resourceAIConfigExists(d *schema.ResourceData, metaRaw interface{}) (bool, var res *http.Response var err error err = client.withConcurrency(client.ctx, func() error { - _, res, err = client.ldBeta.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).Execute() + _, res, err = client.ldBeta.AIConfigsBetaApi.GetAIConfig(client.ctx, projectKey, key).LDAPIVersion("beta").Execute() return err }) From 263932b5e7b2b77cfc5d688b6677175f22b553f5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 17 Nov 2025 23:46:00 +0000 Subject: [PATCH 11/11] fix: remove default LD-API-Version header from beta client The beta client was setting LD-API-Version as a default header, which conflicted with the per-request .LDAPIVersion("beta") method calls, resulting in duplicate headers ("Too many values for parameter LD-API-Version, got 2"). Created newLDClientConfigNoVersion helper that builds a client config without the default LD-API-Version header. The beta client now uses this config, allowing the per-request .LDAPIVersion("beta") calls to set the header without duplication. Co-Authored-By: traci@launchdarkly.com --- launchdarkly/config.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/launchdarkly/config.go b/launchdarkly/config.go index 530f3cee..58bf899d 100644 --- a/launchdarkly/config.go +++ b/launchdarkly/config.go @@ -75,13 +75,23 @@ func newLDClientConfig(apiHost string, httpTimeoutSeconds int, apiVersion string return cfg } +func newLDClientConfigNoVersion(apiHost string, httpTimeoutSeconds int, retryPolicy retryablehttp.CheckRetry) *ldapi.Configuration { + cfg := ldapi.NewConfiguration() + cfg.Host = apiHost + cfg.DefaultHeader = make(map[string]string) + cfg.UserAgent = fmt.Sprintf("launchdarkly-terraform-provider/%s", version) + cfg.HTTPClient = newRetryableClient(retryPolicy) + cfg.HTTPClient.Timeout = time.Duration(httpTimeoutSeconds) * time.Second + return cfg +} + func baseNewClient(token string, apiHost string, oauth bool, httpTimeoutSeconds int, apiVersion string, maxConcurrent int) (*Client, error) { if token == "" { return nil, errors.New("token cannot be empty") } standardConfig := newLDClientConfig(apiHost, httpTimeoutSeconds, apiVersion, standardRetryPolicy) - betaConfig := newLDClientConfig(apiHost, httpTimeoutSeconds, "beta", standardRetryPolicy) + betaConfigNoVersion := newLDClientConfigNoVersion(apiHost, httpTimeoutSeconds, standardRetryPolicy) configWith404Retries := newLDClientConfig(apiHost, httpTimeoutSeconds, apiVersion, retryPolicyWith404Retries) ctx := context.WithValue(context.Background(), ldapi.ContextAPIKeys, map[string]ldapi.APIKey{ @@ -100,7 +110,7 @@ func baseNewClient(token string, apiHost string, oauth bool, httpTimeoutSeconds apiKey: token, apiHost: apiHost, ld: ldapi.NewAPIClient(standardConfig), - ldBeta: ldapi.NewAPIClient(betaConfig), + ldBeta: ldapi.NewAPIClient(betaConfigNoVersion), ld404Retry: ldapi.NewAPIClient(configWith404Retries), ctx: ctx, fallbackClient: fallbackClient,