├── docs ├── guides │ └── .gitkeep ├── data-sources │ ├── .gitkeep │ ├── synthetic_location.md │ ├── builtin_event_spec.md │ └── alerting_channel.md ├── _config.yml ├── resources │ ├── website_monitoring_config.md │ ├── custom_dashboard.md │ ├── alerting_config.md │ ├── rbac_group.md │ ├── application_config.md │ ├── synthetic_test.md │ ├── api_token.md │ └── alerting_channel.md └── index.md ├── .vscode ├── settings.json └── launch.json ├── utils ├── generics-utils.go ├── bool-utils.go ├── int-utils.go ├── bool-utils_test.go ├── int-utils_test.go ├── slice-utils.go ├── generica-utils_test.go ├── slice-utils_test.go ├── string-utils.go └── string-utils_test.go ├── generate-changelog.sh ├── .gitignore ├── instana ├── restapi │ ├── access-rule.go │ ├── custom-payload-field_test.go │ ├── access-type_test.go │ ├── log-level_test.go │ ├── granularity_test.go │ ├── relation-type_test.go │ ├── website-alert-rule.go │ ├── synthetic-location.go │ ├── website-impact-measurement-method_test.go │ ├── application-alert-evaluation-type_test.go │ ├── application-config-scope_test.go │ ├── aggregation_test.go │ ├── application-alert-rule.go │ ├── threshold_test.go │ ├── custom-dashboard.go │ ├── boundary-scope_test.go │ ├── website-time-threshold.go │ ├── website-monitoring-config-api.go │ ├── included-application.go │ ├── access-type.go │ ├── builtin-event-specification-api.go │ ├── log-level.go │ ├── tag-filter_test.go │ ├── severity_test.go │ ├── read-only-rest-resource.go │ ├── default-json-unmarshaller.go │ ├── granularity.go │ ├── relation-type.go │ ├── alerting-channel-type.go │ ├── website-impact-measurement-method.go │ ├── operator_test.go │ ├── instana-rest-resource.go │ ├── alerting-channels-api.go │ ├── alert-event-type.go │ ├── application-alert-evaluation-type.go │ ├── boundary-scope.go │ ├── application-configs-api.go │ ├── application-config-scope.go │ ├── custom-payload-field.go │ ├── alerts-api.go │ ├── severity.go │ ├── synthetic-test.go │ ├── website-alert-config.go │ ├── sli-config-api.go │ ├── synthetic-test-rest-resource.go │ ├── custom-payload-fields-unmarshaller-adapter.go │ ├── default-json-unmarshaller_test.go │ ├── website-monitoring-config-rest-resource.go │ ├── application-alert-config.go │ ├── Instana-api_test.go │ ├── api-tokens-api.go │ ├── aggregation.go │ ├── threshold.go │ ├── custom-event-specficiations-api.go │ ├── default-rest-resource_create-put-update-put_test.go │ ├── operator.go │ ├── groups-api_test.go │ ├── default-rest-resource_create-post-update-not-supported_test.go │ ├── default-rest-resource_create-put-update-not-supported_test.go │ └── default-rest-resource_create-post-update-put_test.go ├── terraform-provider-instana-data-source.go ├── resource-global-application-alert-config_test.go ├── tagfilter │ ├── tag-filter-mapper.go │ ├── tag-filter-mapper_test.go │ └── tag-filter-to-instana-api-model.go ├── tag-filter-schema.go ├── test-helpers.go ├── provider_test.go ├── data-source-synthetic-location.go ├── provider.go ├── resource-website-monitoring-config.go ├── common_test.go ├── data-source-builtin-event.go └── tag-filter-schema_test.go ├── tfutils └── update-state.tf.go ├── main.go ├── .golangci.yml ├── .github ├── workflows │ ├── dependency-submisssion.yml │ ├── codeql-analysis.yml │ ├── release.yml │ └── build.yml └── dependabot.yml ├── generate-mock-for-file.sh ├── sonar-project.properties ├── testutils ├── utils.go ├── test-http-server_test.go ├── test-server.pem └── test-server.key ├── Makefile ├── .goreleaser.yml ├── README.md └── go.mod /docs/guides/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/data-sources/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.formatTool": "goimports", 3 | "go.testTimeout": "120s" 4 | } -------------------------------------------------------------------------------- /utils/generics-utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func GetZeroValue[T any]() T { 4 | var result T 5 | return result 6 | } 7 | -------------------------------------------------------------------------------- /utils/bool-utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // BoolPtr converts a bool to a bool pointer 4 | func BoolPtr(input bool) *bool { 5 | return &input 6 | } 7 | -------------------------------------------------------------------------------- /generate-changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | github_changelog_generator --token $2 -u $1 -p terraform-provider-instana --enhancement-labels "improvement,feature,enhancement" -------------------------------------------------------------------------------- /utils/int-utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // Int64Ptr converts an int64 to a int64 pointer 4 | func Int64Ptr(input int64) *int64 { 5 | return &input 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | output/ 3 | *.test 4 | vendor/ 5 | .idea 6 | .DS_Store 7 | 8 | golangci-lint-report.json 9 | checkstyle-golangci-lint-report.xml 10 | 11 | rest-playground.http -------------------------------------------------------------------------------- /instana/restapi/access-rule.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | type AccessRule struct { 4 | AccessType AccessType `json:"accessType"` 5 | RelatedID *string `json:"relatedId"` 6 | RelationType RelationType `json:"relationType"` 7 | } 8 | -------------------------------------------------------------------------------- /instana/terraform-provider-instana-data-source.go: -------------------------------------------------------------------------------- 1 | package instana 2 | 3 | import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 4 | 5 | // DataSource interface definition of a Terraform DataSource implementation in this provider 6 | type DataSource interface { 7 | CreateResource() *schema.Resource 8 | } 9 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: Terraform Provider Instana 2 | description: Terraform provider implementation for the Instana REST API 3 | 4 | remote_theme: rundocs/jekyll-rtd-theme 5 | 6 | addons: 7 | - github: 8 | - homepage 9 | - issues 10 | - downloads 11 | 12 | addons_branch: true 13 | 14 | copyright: 15 | revision: true -------------------------------------------------------------------------------- /tfutils/update-state.tf.go: -------------------------------------------------------------------------------- 1 | package tfutils 2 | 3 | import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 4 | 5 | func UpdateState(d *schema.ResourceData, data map[string]interface{}) error { 6 | for k, v := range data { 7 | err := d.Set(k, v) 8 | if err != nil { 9 | return err 10 | } 11 | } 12 | return nil 13 | } 14 | -------------------------------------------------------------------------------- /utils/bool-utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/utils" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldCreateBoolPointerFromBool(t *testing.T) { 11 | value := true 12 | 13 | require.Equal(t, &value, BoolPtr(value)) 14 | } 15 | -------------------------------------------------------------------------------- /utils/int-utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/utils" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldCreateInt64PointerFromInt64(t *testing.T) { 11 | value := int64(123) 12 | 13 | require.Equal(t, &value, Int64Ptr(value)) 14 | } 15 | -------------------------------------------------------------------------------- /instana/restapi/custom-payload-field_test.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestShouldReturnSupportedCustomPayloadTypesAsStringSlice(t *testing.T) { 10 | expected := []string{"staticString", "dynamic"} 11 | require.Equal(t, expected, SupportedCustomPayloadTypes.ToStringSlice()) 12 | } 13 | -------------------------------------------------------------------------------- /utils/slice-utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // StringSliceElementsAreUnique checks if the given string slice contains unique elements only 4 | func StringSliceElementsAreUnique(slice []string) bool { 5 | checkMap := make(map[string]bool) 6 | 7 | for _, v := range slice { 8 | if checkMap[v] { 9 | return false 10 | } 11 | checkMap[v] = true 12 | } 13 | return true 14 | } 15 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gessnerfl/terraform-provider-instana/instana" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 8 | ) 9 | 10 | func main() { 11 | plugin.Serve(&plugin.ServeOpts{ 12 | ProviderFunc: func() *schema.Provider { 13 | return instana.Provider() 14 | }, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /instana/restapi/access-type_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedAccessTypesAsStringSlice(t *testing.T) { 11 | expected := []string{"READ", "READ_WRITE"} 12 | require.Equal(t, expected, SupportedAccessTypes.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /instana/restapi/log-level_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedLogLevelsAsStringSlice(t *testing.T) { 11 | expected := []string{"WARN", "ERROR", "ANY"} 12 | require.Equal(t, expected, SupportedLogLevels.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /instana/restapi/granularity_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedGranularitiesAsIntSlice(t *testing.T) { 11 | expected := []int{300000, 600000, 900000, 1200000, 1800000} 12 | require.Equal(t, expected, SupportedGranularities.ToIntSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /instana/restapi/relation-type_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedRelationTypesAsStringSlice(t *testing.T) { 11 | expected := []string{"USER", "API_TOKEN", "ROLE", "TEAM", "GLOBAL"} 12 | require.Equal(t, expected, SupportedRelationTypes.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /instana/restapi/website-alert-rule.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // WebsiteAlertRule struct representing the API model of a website alert rule 4 | type WebsiteAlertRule struct { 5 | AlertType string `json:"alertType"` 6 | MetricName string `json:"metricName"` 7 | Aggregation *Aggregation `json:"aggregation"` 8 | Operator *ExpressionOperator `json:"operator"` 9 | Value *string `json:"value"` 10 | } 11 | -------------------------------------------------------------------------------- /instana/restapi/synthetic-location.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | type SyntheticLocation struct { 4 | ID string `json:"id"` 5 | Label string `json:"label"` 6 | Description string `json:"description"` 7 | LocationType string `json:"locationType"` 8 | } 9 | 10 | // GetIDForResourcePath implementation of the interface InstanaDataObject for SyntheticLocation 11 | func (s *SyntheticLocation) GetIDForResourcePath() string { 12 | return s.ID 13 | } 14 | -------------------------------------------------------------------------------- /instana/restapi/website-impact-measurement-method_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedWebsiteImpactMeasurementMethodsAsStringSlice(t *testing.T) { 11 | expected := []string{"AGGREGATED", "PER_WINDOW"} 12 | require.Equal(t, expected, SupportedWebsiteImpactMeasurementMethods.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /instana/restapi/application-alert-evaluation-type_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedApplicationAlertEvaluationTypesAsStringSlice(t *testing.T) { 11 | expected := []string{"PER_AP", "PER_AP_SERVICE", "PER_AP_ENDPOINT"} 12 | require.Equal(t, expected, SupportedApplicationAlertEvaluationTypes.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /instana/restapi/application-config-scope_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 9 | ) 10 | 11 | func TestShouldReturnStringRepresentationOfSupporedApplicationConfigScopes(t *testing.T) { 12 | require.Equal(t, []string{"INCLUDE_NO_DOWNSTREAM", "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING", "INCLUDE_ALL_DOWNSTREAM"}, SupportedApplicationConfigScopes.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /instana/resource-global-application-alert-config_test.go: -------------------------------------------------------------------------------- 1 | package instana_test 2 | 3 | import ( 4 | . "github.com/gessnerfl/terraform-provider-instana/instana" 5 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 6 | "testing" 7 | ) 8 | 9 | func TestGlobalApplicationAlertConfig(t *testing.T) { 10 | commonTests := createApplicationAlertConfigTestFor("instana_global_application_alert_config", restapi.GlobalApplicationAlertConfigsResourcePath, NewGlobalApplicationAlertConfigResourceHandle()) 11 | commonTests.run(t) 12 | } 13 | -------------------------------------------------------------------------------- /instana/restapi/aggregation_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedAggregationsAsStringSlice(t *testing.T) { 11 | expected := []string{"SUM", "MEAN", "MAX", "MIN", "P25", "P50", "P75", "P90", "P95", "P98", "P99", "P99_9", "P99_99", "DISTRIBUTION", "DISTINCT_COUNT", "SUM_POSITIVE"} 12 | require.Equal(t, expected, SupportedAggregations.ToStringSlice()) 13 | } 14 | -------------------------------------------------------------------------------- /utils/generica-utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/utils" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturn1ForIntegers(t *testing.T) { 11 | require.Equal(t, 0, GetZeroValue[int]()) 12 | } 13 | 14 | func TestShouldReturnEmptyStringForString(t *testing.T) { 15 | require.Equal(t, "", GetZeroValue[string]()) 16 | } 17 | 18 | func TestShouldReturnNilStringForPointers(t *testing.T) { 19 | require.Nil(t, GetZeroValue[*string]()) 20 | } 21 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | output: 3 | formats: 4 | json: 5 | path: golangci-lint-report.json 6 | checkstyle: 7 | path: checkstyle-golangci-lint-report.xml 8 | linters: 9 | enable: 10 | - gocheckcompilerdirectives 11 | - gochecknoinits 12 | - gochecksumtype 13 | - gocognit 14 | - gocyclo 15 | - godox 16 | - goheader 17 | - gomoddirectives 18 | - gomodguard 19 | - goprintffuncname 20 | - gosmopolitan 21 | - govet 22 | - ineffassign 23 | - unused 24 | disable: 25 | - errcheck 26 | - staticcheck 27 | -------------------------------------------------------------------------------- /.github/workflows/dependency-submisssion.yml: -------------------------------------------------------------------------------- 1 | name: Go Dependency Submission 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | go-action-detection: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: 'Checkout Repository' 15 | uses: actions/checkout@v4 16 | 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: ">=1.22.0" 20 | 21 | - name: Run snapshot action 22 | uses: actions/go-dependency-submission@v2 23 | with: 24 | go-mod-path: go.mod -------------------------------------------------------------------------------- /generate-mock-for-file.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | _cwd=$(pwd) 3 | _base_dir=$(dirname "$0") 4 | 5 | if [[ -f $1 ]]; then 6 | echo "Generate mocks for $1" 7 | else 8 | echo "Usage mock-file.sh " 9 | exit 1 10 | fi 11 | 12 | _filepath=$1 13 | _package_folder=$(dirname $1) 14 | _package="" 15 | _filename="$(basename -s .go ${_filepath})" 16 | _destination="mocks/${_filename}_mocks.go" 17 | 18 | cd ${_base_dir} 19 | echo "mockgen -source=${_filepath} -destination=${_destination} -package=mocks" 20 | mockgen -source=${_filepath} -destination=${_destination} -package=mocks 21 | cd ${_cwd} -------------------------------------------------------------------------------- /instana/restapi/application-alert-rule.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // ApplicationAlertRule is the representation of an application alert rule in Instana 4 | type ApplicationAlertRule struct { 5 | AlertType string `json:"alertType"` 6 | MetricName string `json:"metricName"` 7 | Aggregation Aggregation `json:"aggregation"` 8 | 9 | StatusCodeStart *int32 `json:"statusCodeStart"` 10 | StatusCodeEnd *int32 `json:"statusCodeEnd"` 11 | 12 | Level *LogLevel `json:"level"` 13 | Message *string `json:"message"` 14 | Operator *ExpressionOperator `json:"operator"` 15 | } 16 | -------------------------------------------------------------------------------- /instana/restapi/threshold_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnSupportedThresholdOperatorsAsStringSlice(t *testing.T) { 11 | expected := []string{">", ">=", "<", "<="} 12 | require.Equal(t, expected, SupportedThresholdOperators.ToStringSlice()) 13 | } 14 | 15 | func TestShouldReturnSupportedThresholdSeasonalitiesAsStringSlice(t *testing.T) { 16 | expected := []string{"WEEKLY", "DAILY"} 17 | require.Equal(t, expected, SupportedThresholdSeasonalities.ToStringSlice()) 18 | } 19 | -------------------------------------------------------------------------------- /docs/data-sources/synthetic_location.md: -------------------------------------------------------------------------------- 1 | # Synthetic Location Data Source 2 | 3 | Data source to get the synthetic locations from Instana API. This allows you to retrieve the specification 4 | by label and location type and reference it in other resources such as Synthetic tests. 5 | 6 | API Documentation: 7 | 8 | ## Example Usage 9 | 10 | ```hcl 11 | data "instana_synthetic_location" "locations" {} 12 | ``` 13 | 14 | ## Argument Reference 15 | 16 | * `label` - Required - the label of the synthetic location 17 | * `location_type` - Required - indicates if the location is public or private 18 | -------------------------------------------------------------------------------- /instana/restapi/custom-dashboard.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import "encoding/json" 4 | 5 | // CustomDashboardsResourcePath the API resource path for Custom Dashboards 6 | const CustomDashboardsResourcePath = InstanaAPIBasePath + "/custom-dashboard" 7 | 8 | type CustomDashboard struct { 9 | ID string `json:"id"` 10 | Title string `json:"title"` 11 | AccessRules []AccessRule `json:"accessRules"` 12 | Widgets json.RawMessage `json:"widgets"` 13 | } 14 | 15 | // GetIDForResourcePath implementation of the interface InstanaDataObject for CustomDashboard 16 | func (a *CustomDashboard) GetIDForResourcePath() string { 17 | return a.ID 18 | } 19 | -------------------------------------------------------------------------------- /instana/restapi/boundary-scope_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnStringRepresentationOfSupportedApplicationConfigBoundaryScopes(t *testing.T) { 11 | require.Equal(t, []string{"ALL", "INBOUND", "DEFAULT"}, SupportedApplicationConfigBoundaryScopes.ToStringSlice()) 12 | } 13 | 14 | func TestShouldReturnStringRepresentationOfSupportedApplicationAlertConfigBoundaryScopes(t *testing.T) { 15 | require.Equal(t, []string{"ALL", "INBOUND"}, SupportedApplicationAlertConfigBoundaryScopes.ToStringSlice()) 16 | } 17 | -------------------------------------------------------------------------------- /instana/restapi/website-time-threshold.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // WebsiteTimeThreshold struct representing the API model of a website time threshold 4 | type WebsiteTimeThreshold struct { 5 | Type string `json:"type"` 6 | TimeWindow *int64 `json:"timeWindow"` 7 | Violations *int32 `json:"violations"` 8 | ImpactMeasurementMethod *WebsiteImpactMeasurementMethod `json:"impactMeasurementMethod"` 9 | UserPercentage *float64 `json:"userPercentage"` 10 | Users *int32 `json:"users"` 11 | } 12 | -------------------------------------------------------------------------------- /instana/restapi/website-monitoring-config-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // WebsiteMonitoringConfigResourcePath path to website monitoring config resource of Instana RESTful API 4 | const WebsiteMonitoringConfigResourcePath = WebsiteMonitoringResourcePath + "/config" 5 | 6 | // WebsiteMonitoringConfig data structure of a Website Monitoring Configuration of the Instana API 7 | type WebsiteMonitoringConfig struct { 8 | ID string `json:"id"` 9 | Name string `json:"name"` 10 | AppName string `json:"appName"` 11 | } 12 | 13 | // GetIDForResourcePath implemention of the interface InstanaDataObject 14 | func (r *WebsiteMonitoringConfig) GetIDForResourcePath() string { 15 | return r.ID 16 | } 17 | -------------------------------------------------------------------------------- /docs/resources/website_monitoring_config.md: -------------------------------------------------------------------------------- 1 | # Website Monitoring Config Resource 2 | 3 | Resource to configure websites in Instana 4 | 5 | API Documentation: 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | resource "instana_website_monitoring_config" "example" { 11 | name = "my-website-monitoring-config" 12 | } 13 | ``` 14 | 15 | ## Argument Reference 16 | 17 | * `name` - Required - the name of the website monitoring config 18 | 19 | ## Import 20 | 21 | Website Monitoring Configs can be imported using the `id`, e.g.: 22 | 23 | ``` 24 | $ terraform import instana_website_monitoring_config.my_website 60845e4e5e6b9cf8fc2868da 25 | ``` 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "github-actions" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=de.gessnerfl.terraform-provider-instana 2 | sonar.projectName=Terraform Provider Instana 3 | sonar.organization=gessnerfl-github 4 | 5 | sonar.sources=. 6 | sonar.exclusions=**/*_test.go,**/*_generated*.go,mocks/**/*_mocks.go 7 | sonar.tests=. 8 | sonar.test.inclusions=**/*_test.go 9 | sonar.test.exclusions=**/*_generated*.go,mocks/**/*_mocks.go 10 | 11 | sonar.sourceEncoding=UTF-8 12 | 13 | sonar.go.coverage.reportPaths=/github/workspace/coverage.out 14 | sonar.go.golangci-lint.reportPaths=/github/workspace/checkstyle-golangci-lint-report.xml 15 | sonar.go.tests.reportPaths=/github/workspace/unit-test-report.json 16 | 17 | #sonar.externalIssuesReportPaths=output/gosec-report.json 18 | 19 | sonar.coverage.exclusions=**/*_test.go,**/*_mocks.go,testutils/**/*.go -------------------------------------------------------------------------------- /utils/slice-utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/utils" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestShouldReturnTrueWhenCheckingForUniqueElementsStringSliceAndSliceIsEmpty(t *testing.T) { 11 | assert.True(t, StringSliceElementsAreUnique([]string{})) 12 | } 13 | 14 | func TestShouldReturnTrueWhenCheckingForUniqueElementsInStringSliceAndSliceContainsUniqueElementsOnly(t *testing.T) { 15 | assert.True(t, StringSliceElementsAreUnique([]string{"a", "b", "c", "d"})) 16 | } 17 | 18 | func TestShouldReturnFalseWhenCheckingForUniqueElementsInStringSliceAndSliceContainsDuplicateElements(t *testing.T) { 19 | assert.False(t, StringSliceElementsAreUnique([]string{"a", "b", "c", "d", "a"})) 20 | } 21 | -------------------------------------------------------------------------------- /utils/string-utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // IsBlank returns true when the provided string is empty or only contains whitespace characters 9 | func IsBlank(input string) bool { 10 | return len(strings.TrimSpace(input)) == 0 11 | } 12 | 13 | // StringPtr converts a string to a string pointer 14 | func StringPtr(input string) *string { 15 | return &input 16 | } 17 | 18 | var multiSpacesRegexp = regexp.MustCompile("( ){2,}") 19 | 20 | // RemoveNewLinesAndTabs removes all new lines and tabs from a string 21 | func RemoveNewLinesAndTabs(input string) string { 22 | spacesOnly := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(input, "\n\r", " "), "\n", " "), "\t", " ") 23 | return multiSpacesRegexp.ReplaceAllString(spacesOnly, " ") 24 | } 25 | -------------------------------------------------------------------------------- /testutils/utils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | // GetRootFolder determines the root folder of the project 11 | func GetRootFolder() (string, error) { 12 | wd, _ := os.Getwd() 13 | return lookupRootFolder(wd, 0) 14 | } 15 | 16 | func lookupRootFolder(dir string, level int) (string, error) { 17 | if level > 5 { 18 | return "", errors.New("Failed to find root folder") 19 | } 20 | mainFile := fmt.Sprintf("%s/main.go", dir) 21 | if fileExists(mainFile) { 22 | return dir, nil 23 | } 24 | nextLevel := level + 1 25 | parentDir := filepath.Dir(dir) 26 | return lookupRootFolder(parentDir, nextLevel) 27 | } 28 | 29 | func fileExists(file string) bool { 30 | if stat, err := os.Stat(file); err == nil { 31 | return !stat.IsDir() 32 | } 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /instana/restapi/included-application.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // IncludedEndpoint custom type to include of a specific endpoint in an alert config 4 | type IncludedEndpoint struct { 5 | EndpointID string `json:"endpointId"` 6 | Inclusive bool `json:"inclusive"` 7 | } 8 | 9 | // IncludedService custom type to include of a specific service in an alert config 10 | type IncludedService struct { 11 | ServiceID string `json:"serviceId"` 12 | Inclusive bool `json:"inclusive"` 13 | 14 | Endpoints map[string]IncludedEndpoint `json:"endpoints"` 15 | } 16 | 17 | // IncludedApplication custom type to include specific applications in an alert config 18 | type IncludedApplication struct { 19 | ApplicationID string `json:"applicationId"` 20 | Inclusive bool `json:"inclusive"` 21 | 22 | Services map[string]IncludedService `json:"services"` 23 | } 24 | -------------------------------------------------------------------------------- /docs/data-sources/builtin_event_spec.md: -------------------------------------------------------------------------------- 1 | # Builtin Event Specification Data Source 2 | 3 | Data source to get the specification of builtin events from Instana API. This allows you to retrieve the specification 4 | by UI name and Plugin ID and reference it in other resources such as Alerting Configurations. 5 | 6 | API Documentation: 7 | 8 | ## Example Usage 9 | 10 | ```hcl 11 | data "instana_builtin_event_spec" "host_system_load_too_high" { 12 | name = "System load too high" 13 | short_plugin_id = "host" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | * `name` - Required - the name of the builtin event 20 | * `short_plugin_id` - Required - the short plugin ID of the builtin event (can be retrieved from ) 21 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master, main ] 6 | pull_request: 7 | branches: [ master, main ] 8 | schedule: 9 | - cron: '32 0 * * 2' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | language: [ 'go' ] 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Initialize CodeQL 26 | uses: github/codeql-action/init@v3 27 | with: 28 | languages: ${{ matrix.language }} 29 | queries: security-extended,security-and-quality 30 | 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v3 33 | 34 | - name: Perform CodeQL Analysis 35 | uses: github/codeql-action/analyze@v3 36 | -------------------------------------------------------------------------------- /instana/restapi/access-type.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // AccessType custom type for access type 4 | type AccessType string 5 | 6 | // AccessTypes custom type for a slice of AccessType 7 | type AccessTypes []AccessType 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (types AccessTypes) ToStringSlice() []string { 11 | result := make([]string, len(types)) 12 | for i, v := range types { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //AccessTypeRead constant value for the READ AccessType 20 | AccessTypeRead = AccessType("READ") 21 | //AccessTypeReadWrite constant value for the READ_WRITE AccessType 22 | AccessTypeReadWrite = AccessType("READ_WRITE") 23 | ) 24 | 25 | // SupportedAccessTypes list of all supported AccessType 26 | var SupportedAccessTypes = AccessTypes{AccessTypeRead, AccessTypeReadWrite} 27 | -------------------------------------------------------------------------------- /instana/restapi/builtin-event-specification-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // BuiltinEventSpecificationResourcePath path to Builtin Event Specification settings resource of Instana RESTful API 4 | const BuiltinEventSpecificationResourcePath = EventSpecificationBasePath + "/built-in" 5 | 6 | // BuiltinEventSpecification is the representation of a builtin event specification in Instana 7 | type BuiltinEventSpecification struct { 8 | ID string `json:"id"` 9 | ShortPluginID string `json:"shortPluginId"` 10 | Name string `json:"name"` 11 | Description *string `json:"description"` 12 | Severity int `json:"severity"` 13 | Triggering bool `json:"triggering"` 14 | Enabled bool `json:"enabled"` 15 | } 16 | 17 | // GetIDForResourcePath implemention of the interface InstanaDataObject 18 | func (spec *BuiltinEventSpecification) GetIDForResourcePath() string { 19 | return spec.ID 20 | } 21 | -------------------------------------------------------------------------------- /instana/restapi/log-level.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // LogLevel custom type for log level 4 | type LogLevel string 5 | 6 | // LogLevels custom type for a slice of LogLevel 7 | type LogLevels []LogLevel 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (levels LogLevels) ToStringSlice() []string { 11 | result := make([]string, len(levels)) 12 | for i, v := range levels { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //LogLevelWarning constant value for the warning log level 20 | LogLevelWarning = LogLevel("WARN") 21 | //LogLevelError constant value for the error log level 22 | LogLevelError = LogLevel("ERROR") 23 | //LogLevelAny constant value for the any log level 24 | LogLevelAny = LogLevel("ANY") 25 | ) 26 | 27 | // SupportedLogLevels list of all supported LogLevel 28 | var SupportedLogLevels = LogLevels{LogLevelWarning, LogLevelError, LogLevelAny} 29 | -------------------------------------------------------------------------------- /utils/string-utils_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/utils" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnTrueWhenStringIsEmpty(t *testing.T) { 11 | require.True(t, IsBlank("")) 12 | } 13 | 14 | func TestShouldReturnTrueWhenStringContainsOnlySpaces(t *testing.T) { 15 | require.True(t, IsBlank(" ")) 16 | } 17 | 18 | func TestShouldReturnFalseWhenStringContainsNonWhitespaceCharacters(t *testing.T) { 19 | require.False(t, IsBlank(" ba ")) 20 | } 21 | 22 | func TestShouldCreateStringPointerFromString(t *testing.T) { 23 | value := "string" 24 | 25 | require.Equal(t, &value, StringPtr(value)) 26 | } 27 | 28 | func TestShouldProperlyConvertMultilineStringToSingleLineIncludingFormatting(t *testing.T) { 29 | input := `This 30 | is a test 31 | of multiline strings` 32 | 33 | require.Equal(t, "This is a test of multiline strings", RemoveNewLinesAndTabs(input)) 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - "!*" 7 | tags: 8 | - "v*" 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Unshallow 18 | run: git fetch --prune --unshallow 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: 1.22.0 24 | 25 | - name: Import GPG key 26 | id: import_gpg 27 | uses: paultyng/ghaction-import-gpg@v2.1.0 28 | env: 29 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 30 | PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 31 | - 32 | name: Run GoReleaser 33 | uses: goreleaser/goreleaser-action@v5 34 | with: 35 | version: latest 36 | args: release --rm-dist 37 | env: 38 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /instana/restapi/tag-filter_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "go.uber.org/mock/gomock" 5 | "testing" 6 | 7 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestShouldPrependElementToListOfElementsOfTagFilterExpression(t *testing.T) { 12 | ctrl := gomock.NewController(t) 13 | defer ctrl.Finish() 14 | 15 | element1 := NewStringTagFilter(TagFilterEntityDestination, "entity.type", EqualsOperator, "foo") 16 | element2 := NewStringTagFilter(TagFilterEntityDestination, "entity.type", EqualsOperator, "bar") 17 | element3 := NewStringTagFilter(TagFilterEntityDestination, "entity.type", EqualsOperator, "baz") 18 | elements := []*TagFilter{element1, element2} 19 | 20 | sut := NewLogicalAndTagFilter(elements) 21 | sut.PrependElement(element3) 22 | 23 | require.Len(t, sut.Elements, 3) 24 | require.Equal(t, element3, sut.Elements[2]) 25 | } 26 | 27 | func TestShouldConvertTagFilterEntitiesToStringSlice(t *testing.T) { 28 | expectedResult := []string{"SOURCE", "DESTINATION", "NOT_APPLICABLE"} 29 | require.Equal(t, expectedResult, SupportedTagFilterEntities.ToStringSlice()) 30 | } 31 | -------------------------------------------------------------------------------- /instana/restapi/severity_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | 7 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestShouldReturnTheProperRepresentationsForSeverityWarning(t *testing.T) { 12 | assert.Equal(t, 5, SeverityWarning.GetAPIRepresentation()) 13 | assert.Equal(t, "warning", SeverityWarning.GetTerraformRepresentation()) 14 | } 15 | 16 | func TestShouldReturnTheProperRepresentationsForSeverityCritical(t *testing.T) { 17 | assert.Equal(t, 10, SeverityCritical.GetAPIRepresentation()) 18 | assert.Equal(t, "critical", SeverityCritical.GetTerraformRepresentation()) 19 | } 20 | 21 | func TestShouldReturnSupportedSeveritiesAsStringSliceOfTerraformRepresentations(t *testing.T) { 22 | expected := []string{"warning", "critical"} 23 | require.Equal(t, expected, SupportedSeverities.TerraformRepresentations()) 24 | } 25 | 26 | func TestShouldReturnSupportedSeveritiesAsStringSliceOfInstanaAPIRepresentations(t *testing.T) { 27 | expected := []int{5, 10} 28 | require.Equal(t, expected, SupportedSeverities.APIRepresentations()) 29 | } 30 | -------------------------------------------------------------------------------- /instana/tagfilter/tag-filter-mapper.go: -------------------------------------------------------------------------------- 1 | package tagfilter 2 | 3 | import ( 4 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 5 | ) 6 | 7 | // NewMapper creates a new instance of the Mapper 8 | func NewMapper() Mapper { 9 | return &tagFilterMapper{} 10 | } 11 | 12 | // Mapper interface of the tag filter expression mapper 13 | type Mapper interface { 14 | FromAPIModel(input *restapi.TagFilter) (*FilterExpression, error) 15 | ToAPIModel(input *FilterExpression) *restapi.TagFilter 16 | } 17 | 18 | // struct for the filter expression mapper implementation for tag filter expressions 19 | type tagFilterMapper struct{} 20 | 21 | // MapTagFilterToNormalizedString maps a TagFilterExpressionElement to its normalized string. Returns nil in case an empty expression is provided and an error in case of any error occurred during mapping. 22 | func MapTagFilterToNormalizedString(element *restapi.TagFilter) (*string, error) { 23 | mapper := NewMapper() 24 | expr, err := mapper.FromAPIModel(element) 25 | if err != nil { 26 | return nil, err 27 | } 28 | if expr != nil { 29 | renderedExpression := expr.Render() 30 | return &renderedExpression, nil 31 | } 32 | return nil, nil 33 | } 34 | -------------------------------------------------------------------------------- /instana/restapi/read-only-rest-resource.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import "github.com/gessnerfl/terraform-provider-instana/utils" 4 | 5 | // NewReadOnlyRestResource creates a new instance of ReadOnlyRestResource 6 | func NewReadOnlyRestResource[T InstanaDataObject](resourcePath string, unmarshaller JSONUnmarshaller[T], client RestClient) ReadOnlyRestResource[T] { 7 | return &readOnlyRestResource[T]{ 8 | resourcePath: resourcePath, 9 | unmarshaller: unmarshaller, 10 | client: client, 11 | } 12 | } 13 | 14 | type readOnlyRestResource[T InstanaDataObject] struct { 15 | resourcePath string 16 | unmarshaller JSONUnmarshaller[T] 17 | client RestClient 18 | } 19 | 20 | func (r *readOnlyRestResource[T]) GetAll() (*[]T, error) { 21 | data, err := r.client.Get(r.resourcePath) 22 | if err != nil { 23 | return nil, err 24 | } 25 | objects, err := r.unmarshaller.UnmarshalArray(data) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return objects, nil 30 | } 31 | 32 | func (r *readOnlyRestResource[T]) GetOne(id string) (T, error) { 33 | data, err := r.client.GetOne(id, r.resourcePath) 34 | if err != nil { 35 | return utils.GetZeroValue[T](), err 36 | } 37 | return r.unmarshaller.Unmarshal(data) 38 | } 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | export CGO_ENABLED:=0 3 | export GO111MODULE=on 4 | #export GOFLAGS=-mod=vendor 5 | 6 | VERSION=$(shell git describe --tags --match "v*" --always --dirty) 7 | 8 | .PHONY: all 9 | all: build test vet lint fmt 10 | 11 | .PHONY: build 12 | build: clean bin/terraform-provider-instana 13 | 14 | bin/terraform-provider-instana: 15 | @echo "+++++++++++ Run GO Build +++++++++++ " 16 | @go build -o $@ github.com/gessnerfl/terraform-provider-instana 17 | 18 | .PHONY: test 19 | test: 20 | @echo "+++++++++++ Run GO Test +++++++++++ " 21 | @go test -v ./... -cover 22 | 23 | .PHONY: gosec 24 | gosec: 25 | @echo "+++++++++++ Run GO SEC +++++++++++ " 26 | @gosec ./... 27 | 28 | .PHONY: vet 29 | vet: 30 | @echo "+++++++++++ Run GO VET +++++++++++ " 31 | @go vet -all ./... 32 | 33 | .PHONY: lint 34 | lint: 35 | @echo "+++++++++++ Run GO Lint +++++++++++ " 36 | @golangci-lint run 37 | 38 | .PHONY: fmt 39 | fmt: 40 | @echo "+++++++++++ Run GO FMT +++++++++++ " 41 | @test -z $$(go fmt ./...) 42 | 43 | .PHONY: update 44 | update: 45 | @GOFLAGS="" go get -u 46 | @go mod tidy 47 | 48 | .PHONY: vendor 49 | vendor: 50 | @go mod vendor 51 | 52 | .PHONY: clean 53 | clean: 54 | @echo "+++++++++++ Clean up project +++++++++++ " 55 | @rm -rf bin -------------------------------------------------------------------------------- /instana/restapi/default-json-unmarshaller.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // NewDefaultJSONUnmarshaller creates a new instance of a generic JSONUnmarshaller without specific nested marshalling 9 | func NewDefaultJSONUnmarshaller[T InstanaDataObject](objectType T) JSONUnmarshaller[T] { 10 | arrayType := make([]T, 0) 11 | return &defaultJSONUnmarshaller[T]{ 12 | objectType: objectType, 13 | arrayType: &arrayType, 14 | } 15 | } 16 | 17 | type defaultJSONUnmarshaller[T any] struct { 18 | objectType T 19 | arrayType *[]T 20 | } 21 | 22 | // Unmarshal JSONUnmarshaller interface implementation 23 | func (u *defaultJSONUnmarshaller[T]) Unmarshal(data []byte) (T, error) { 24 | target := u.objectType 25 | if err := json.Unmarshal(data, &target); err != nil { 26 | return target, fmt.Errorf("failed to parse json; %s", err) 27 | } 28 | return target, nil 29 | } 30 | 31 | // UnmarshalArray JSONUnmarshaller interface implementation 32 | func (u *defaultJSONUnmarshaller[T]) UnmarshalArray(data []byte) (*[]T, error) { 33 | target := u.arrayType 34 | if err := json.Unmarshal(data, &target); err != nil { 35 | return target, fmt.Errorf("failed to parse json; %s", err) 36 | } 37 | return target, nil 38 | } 39 | -------------------------------------------------------------------------------- /testutils/test-http-server_test.go: -------------------------------------------------------------------------------- 1 | package testutils_test 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "github.com/gessnerfl/terraform-provider-instana/testutils" 7 | "io" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestShouldStartNewInstanceWithDynamicPortAndStopTheServerOnClose(t *testing.T) { 16 | path := "/test" 17 | server := testutils.NewTestHTTPServer() 18 | server.AddRoute(http.MethodPost, path, testutils.EchoHandlerFunc) 19 | server.Start() 20 | defer server.Close() 21 | 22 | assert.NotNil(t, server.GetPort()) 23 | 24 | url := fmt.Sprintf("https://localhost:%d%s", server.GetPort(), path) 25 | testString := "test string" 26 | 27 | tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec 28 | defer tr.CloseIdleConnections() 29 | client := &http.Client{Transport: tr} 30 | resp, err := client.Post(url, "test/plain", strings.NewReader(testString)) 31 | 32 | assert.Nil(t, err) 33 | assert.Equal(t, 200, resp.StatusCode) 34 | 35 | defer resp.Body.Close() 36 | responseBytes, err := io.ReadAll(resp.Body) 37 | assert.Nil(t, err) 38 | responseString := string(responseBytes) 39 | 40 | assert.Equal(t, testString, responseString) 41 | } 42 | -------------------------------------------------------------------------------- /instana/restapi/granularity.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // Granularity custom type for an Alter Granularity 4 | type Granularity int32 5 | 6 | // Granularities custom type for a slice of Granularity 7 | type Granularities []Granularity 8 | 9 | // ToIntSlice Returns the corresponding int representations 10 | func (granularities Granularities) ToIntSlice() []int { 11 | result := make([]int, len(granularities)) 12 | for i, v := range granularities { 13 | result[i] = int(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //Granularity300000 constant value for granularity of 30sec 20 | Granularity300000 = Granularity(300000) 21 | //Granularity600000 constant value for granularity of 1min 22 | Granularity600000 = Granularity(600000) 23 | //Granularity900000 constant value for granularity of 1min 30sec 24 | Granularity900000 = Granularity(900000) 25 | //Granularity1200000 constant value for granularity of 2min 26 | Granularity1200000 = Granularity(1200000) 27 | //Granularity1800000 constant value for granularity of 3min 28 | Granularity1800000 = Granularity(1800000) 29 | ) 30 | 31 | // SupportedGranularities list of all supported Granularities 32 | var SupportedGranularities = Granularities{Granularity300000, Granularity600000, Granularity900000, Granularity1200000, Granularity1800000} 33 | -------------------------------------------------------------------------------- /instana/restapi/relation-type.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // RelationType custom type for relation type 4 | type RelationType string 5 | 6 | // RelationTypes custom type for a slice of RelationType 7 | type RelationTypes []RelationType 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (types RelationTypes) ToStringSlice() []string { 11 | result := make([]string, len(types)) 12 | for i, v := range types { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //RelationTypeUser constant value for the USER RelationType 20 | RelationTypeUser = RelationType("USER") 21 | //RelationTypeApiToken constant value for the API_TOKEN RelationType 22 | RelationTypeApiToken = RelationType("API_TOKEN") 23 | //RelationTypeRole constant value for the ROLE RelationType 24 | RelationTypeRole = RelationType("ROLE") 25 | //RelationTypeTeam constant value for the TEAM RelationType 26 | RelationTypeTeam = RelationType("TEAM") 27 | //RelationTypeGlobal constant value for the GLOBAL RelationType 28 | RelationTypeGlobal = RelationType("GLOBAL") 29 | ) 30 | 31 | // SupportedRelationTypes list of all supported RelationType 32 | var SupportedRelationTypes = RelationTypes{RelationTypeUser, RelationTypeApiToken, RelationTypeRole, RelationTypeTeam, RelationTypeGlobal} 33 | -------------------------------------------------------------------------------- /instana/restapi/alerting-channel-type.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // AlertingChannelType type of the alerting channel 4 | type AlertingChannelType string 5 | 6 | const ( 7 | //EmailChannelType constant value for alerting channel type EMAIL 8 | EmailChannelType = AlertingChannelType("EMAIL") 9 | //GoogleChatChannelType constant value for alerting channel type GOOGLE_CHAT 10 | GoogleChatChannelType = AlertingChannelType("GOOGLE_CHAT") 11 | //Office365ChannelType constant value for alerting channel type OFFICE_365 12 | Office365ChannelType = AlertingChannelType("OFFICE_365") 13 | //OpsGenieChannelType constant value for alerting channel type OPS_GENIE 14 | OpsGenieChannelType = AlertingChannelType("OPS_GENIE") 15 | //PagerDutyChannelType constant value for alerting channel type PAGER_DUTY 16 | PagerDutyChannelType = AlertingChannelType("PAGER_DUTY") 17 | //SlackChannelType constant value for alerting channel type SLACK 18 | SlackChannelType = AlertingChannelType("SLACK") 19 | //SplunkChannelType constant value for alerting channel type SPLUNK 20 | SplunkChannelType = AlertingChannelType("SPLUNK") 21 | //VictorOpsChannelType constant value for alerting channel type VICTOR_OPS 22 | VictorOpsChannelType = AlertingChannelType("VICTOR_OPS") 23 | //WebhookChannelType constant value for alerting channel type WEB_HOOK 24 | WebhookChannelType = AlertingChannelType("WEB_HOOK") 25 | ) 26 | -------------------------------------------------------------------------------- /instana/restapi/website-impact-measurement-method.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // WebsiteImpactMeasurementMethod custom type for impact measurement method of website alert rules 4 | type WebsiteImpactMeasurementMethod string 5 | 6 | // WebsiteImpactMeasurementMethods custom type for a slice of WebsiteImpactMeasurementMethod 7 | type WebsiteImpactMeasurementMethods []WebsiteImpactMeasurementMethod 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (methods WebsiteImpactMeasurementMethods) ToStringSlice() []string { 11 | result := make([]string, len(methods)) 12 | for i, v := range methods { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //WebsiteImpactMeasurementMethodAggregated constant value for the website impact measurement method aggregated 20 | WebsiteImpactMeasurementMethodAggregated = WebsiteImpactMeasurementMethod("AGGREGATED") 21 | //WebsiteImpactMeasurementMethodPerWindow constant value for the website impact measurement method per_window 22 | WebsiteImpactMeasurementMethodPerWindow = WebsiteImpactMeasurementMethod("PER_WINDOW") 23 | ) 24 | 25 | // SupportedWebsiteImpactMeasurementMethods list of all supported WebsiteImpactMeasurementMethod 26 | var SupportedWebsiteImpactMeasurementMethods = WebsiteImpactMeasurementMethods{WebsiteImpactMeasurementMethodAggregated, WebsiteImpactMeasurementMethodPerWindow} 27 | -------------------------------------------------------------------------------- /instana/restapi/operator_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnTrueForAllSupportedComparisonOperators(t *testing.T) { 11 | for _, v := range SupportedComparisonOperators { 12 | require.True(t, SupportedComparisonOperators.IsSupported(v)) 13 | } 14 | } 15 | 16 | func TestShouldReturnFalseForAllNonSupportedComparisonOperators(t *testing.T) { 17 | for _, v := range append(SupportedUnaryExpressionOperators, "INVALID_OPERATOR") { 18 | require.False(t, SupportedComparisonOperators.IsSupported(v)) 19 | } 20 | } 21 | 22 | func TestShouldReturnTrueForAllSupportedUnaryExpressionOperators(t *testing.T) { 23 | for _, v := range SupportedUnaryExpressionOperators { 24 | require.True(t, SupportedUnaryExpressionOperators.IsSupported(v)) 25 | } 26 | } 27 | 28 | func TestShouldReturnFalseForAllNonSupportedUnaryExpressionOperators(t *testing.T) { 29 | for _, v := range append(SupportedComparisonOperators, "INVALID_OPERATOR") { 30 | require.False(t, SupportedUnaryExpressionOperators.IsSupported(v)) 31 | } 32 | } 33 | 34 | func TestShouldReturnSupportedOperatorsAsStringSlice(t *testing.T) { 35 | expected := []string{"IS_EMPTY", "NOT_EMPTY", "IS_BLANK", "NOT_BLANK"} 36 | require.Equal(t, expected, SupportedUnaryExpressionOperators.ToStringSlice()) 37 | } 38 | -------------------------------------------------------------------------------- /instana/tagfilter/tag-filter-mapper_test.go: -------------------------------------------------------------------------------- 1 | package tagfilter_test 2 | 3 | import ( 4 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 5 | . "github.com/gessnerfl/terraform-provider-instana/instana/tagfilter" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | ) 9 | 10 | func TestShouldReturnNilWhenMappingEmptyTagFilterExpressionToNormalizedString(t *testing.T) { 11 | operatorType := restapi.LogicalOr 12 | input := &restapi.TagFilter{ 13 | Type: restapi.TagFilterExpressionType, 14 | LogicalOperator: &operatorType, 15 | } 16 | 17 | result, err := MapTagFilterToNormalizedString(input) 18 | 19 | require.NoError(t, err) 20 | require.Nil(t, result) 21 | } 22 | 23 | func TestShouldReturnErrorWhenMappingAnInvalidTagFilterExpressionToNormalizedString(t *testing.T) { 24 | input := &restapi.TagFilter{ 25 | Type: restapi.TagFilterExpressionElementType("invalid"), 26 | } 27 | 28 | result, err := MapTagFilterToNormalizedString(input) 29 | 30 | require.Error(t, err) 31 | require.Nil(t, result) 32 | } 33 | 34 | func TestShouldReturnStringWhenMappingAValidTagFilterExpressionToNormalizedString(t *testing.T) { 35 | value := int64(1234) 36 | input := restapi.NewNumberTagFilter(restapi.TagFilterEntityDestination, tagFilterName, restapi.EqualsOperator, value) 37 | 38 | result, err := MapTagFilterToNormalizedString(input) 39 | 40 | require.NoError(t, err) 41 | require.Equal(t, "name@dest EQUALS 1234", *result) 42 | } 43 | -------------------------------------------------------------------------------- /instana/restapi/instana-rest-resource.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // InstanaDataObject is a marker interface for any data object provided by any resource of the Instana REST API 4 | type InstanaDataObject interface { 5 | GetIDForResourcePath() string 6 | } 7 | 8 | // RestResource interface definition of a instana REST resource. 9 | type RestResource[T InstanaDataObject] interface { 10 | GetAll() (*[]T, error) 11 | GetOne(id string) (T, error) 12 | Create(data T) (T, error) 13 | Update(data T) (T, error) 14 | Delete(data T) error 15 | DeleteByID(id string) error 16 | } 17 | 18 | // DataFilterFunc function definition for filtering data received from Instana API 19 | type DataFilterFunc func(o InstanaDataObject) bool 20 | 21 | // ReadOnlyRestResource interface definition for a read only REST resource. The resource at instana might 22 | // implement more methods but the implementation of the provider is limited to read only. 23 | type ReadOnlyRestResource[T InstanaDataObject] interface { 24 | GetAll() (*[]T, error) 25 | GetOne(id string) (T, error) 26 | } 27 | 28 | // JSONUnmarshaller interface definition for unmarshalling that unmarshalls JSON to go data structures 29 | type JSONUnmarshaller[T any] interface { 30 | //Unmarshal converts the provided json bytes into the go data structure as provided in the target 31 | Unmarshal(data []byte) (T, error) 32 | //UnmarshalArray converts the provided json bytes into the go data structure as provided in the target 33 | UnmarshalArray(data []byte) (*[]T, error) 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build, Test and Verify 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | pull-requests: write 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Setup Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: '1.22.0' 26 | 27 | - name: Set up gotestfmt 28 | uses: gotesttools/gotestfmt-action@v2 29 | with: 30 | token: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Get version number 33 | id: get_version 34 | run: echo ::set-output name=VERSION::$(git describe --tags --match "v*" --always --dirty) 35 | 36 | - name: Run build 37 | run: go build . 38 | 39 | - name: golangci-lint 40 | uses: golangci/golangci-lint-action@v8 41 | continue-on-error: true 42 | with: 43 | version: v2.1 44 | skip-cache: true 45 | 46 | - name: Run testing 47 | run: | 48 | set -euo pipefail 49 | go test -json -v ./... -cover -coverprofile=coverage.out 2>&1 | tee unit-test-report.json | gotestfmt 50 | set +euo pipefail 51 | 52 | - name: Upload test log 53 | uses: actions/upload-artifact@v4 54 | if: always() 55 | with: 56 | name: unit-test-report 57 | path: unit-test-report.json 58 | if-no-files-found: error -------------------------------------------------------------------------------- /instana/restapi/alerting-channels-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // AlertingChannelsResourcePath path to Alerting channels resource of Instana RESTful API 4 | const AlertingChannelsResourcePath = EventSettingsBasePath + "/alertingChannels" 5 | 6 | // AlertingChannel is the representation of an alerting channel in Instana 7 | type AlertingChannel struct { 8 | ID string `json:"id"` 9 | Name string `json:"name"` 10 | Kind AlertingChannelType `json:"kind"` 11 | Emails []string `json:"emails"` 12 | WebhookURL *string `json:"webhookUrl"` 13 | APIKey *string `json:"apiKey"` 14 | Tags *string `json:"tags"` 15 | Region *string `json:"region"` 16 | RoutingKey *string `json:"routingKey"` 17 | ServiceIntegrationKey *string `json:"serviceIntegrationKey"` 18 | IconURL *string `json:"iconUrl"` 19 | Channel *string `json:"channel"` 20 | URL *string `json:"url"` 21 | Token *string `json:"token"` 22 | WebhookURLs []string `json:"webhookUrls"` 23 | Headers []string `json:"headers"` 24 | } 25 | 26 | // GetIDForResourcePath implementation of the interface InstanaDataObject 27 | func (r *AlertingChannel) GetIDForResourcePath() string { 28 | return r.ID 29 | } 30 | -------------------------------------------------------------------------------- /instana/restapi/alert-event-type.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // AlertEventType type definition of EventTypes of an Instana Alert 4 | type AlertEventType string 5 | 6 | const ( 7 | //IncidentAlertEventType constant value for alert event type incident 8 | IncidentAlertEventType = AlertEventType("incident") 9 | //CriticalAlertEventType constant value for alert event type critical 10 | CriticalAlertEventType = AlertEventType("critical") 11 | //WarningAlertEventType constant value for alert event type warning 12 | WarningAlertEventType = AlertEventType("warning") 13 | //ChangeAlertEventType constant value for alert event type change 14 | ChangeAlertEventType = AlertEventType("change") 15 | //OnlineAlertEventType constant value for alert event type online 16 | OnlineAlertEventType = AlertEventType("online") 17 | //OfflineAlertEventType constant value for alert event type offline 18 | OfflineAlertEventType = AlertEventType("offline") 19 | //NoneAlertEventType constant value for alert event type none 20 | NoneAlertEventType = AlertEventType("none") 21 | //AgentMonitoringIssueEventType constant value for alert event type none 22 | AgentMonitoringIssueEventType = AlertEventType("agent_monitoring_issue") 23 | ) 24 | 25 | // SupportedAlertEventTypes list of supported alert event types of Instana API 26 | var SupportedAlertEventTypes = []AlertEventType{ 27 | IncidentAlertEventType, 28 | CriticalAlertEventType, 29 | WarningAlertEventType, 30 | ChangeAlertEventType, 31 | OnlineAlertEventType, 32 | OfflineAlertEventType, 33 | NoneAlertEventType, 34 | AgentMonitoringIssueEventType, 35 | } 36 | -------------------------------------------------------------------------------- /instana/restapi/application-alert-evaluation-type.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // ApplicationAlertEvaluationType custom type representing the application alert evaluation type from the instana API 4 | type ApplicationAlertEvaluationType string 5 | 6 | // ApplicationAlertEvaluationTypes custom type representing a slice of ApplicationAlertEvaluationType 7 | type ApplicationAlertEvaluationTypes []ApplicationAlertEvaluationType 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (types ApplicationAlertEvaluationTypes) ToStringSlice() []string { 11 | result := make([]string, len(types)) 12 | for i, v := range types { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //EvaluationTypePerApplication constant value for ApplicationAlertEvaluationType PER_AP 20 | EvaluationTypePerApplication = ApplicationAlertEvaluationType("PER_AP") 21 | //EvaluationTypePerApplicationService constant value for ApplicationAlertEvaluationType PER_AP_SERVICE 22 | EvaluationTypePerApplicationService = ApplicationAlertEvaluationType("PER_AP_SERVICE") 23 | //EvaluationTypePerApplicationEndpoint constant value for ApplicationAlertEvaluationType PER_AP_ENDPOINT 24 | EvaluationTypePerApplicationEndpoint = ApplicationAlertEvaluationType("PER_AP_ENDPOINT") 25 | ) 26 | 27 | // SupportedApplicationAlertEvaluationTypes list of all supported ApplicationAlertEvaluationTypes 28 | var SupportedApplicationAlertEvaluationTypes = ApplicationAlertEvaluationTypes{EvaluationTypePerApplication, EvaluationTypePerApplicationService, EvaluationTypePerApplicationEndpoint} 29 | -------------------------------------------------------------------------------- /instana/restapi/boundary-scope.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // BoundaryScope type definition of the application config boundary scope of the Instana Web REST API 4 | type BoundaryScope string 5 | 6 | // BoundaryScopes type definition of slice of BoundaryScopes 7 | type BoundaryScopes []BoundaryScope 8 | 9 | // ToStringSlice returns a slice containing the string representations of the given boundary scopes 10 | func (scopes BoundaryScopes) ToStringSlice() []string { 11 | result := make([]string, len(scopes)) 12 | for i, s := range scopes { 13 | result[i] = string(s) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //BoundaryScopeAll constant value for the boundary scope ALL of an application config of the Instana Web REST API 20 | BoundaryScopeAll = BoundaryScope("ALL") 21 | //BoundaryScopeInbound constant value for the boundary scope INBOUND of an application config of the Instana Web REST API 22 | BoundaryScopeInbound = BoundaryScope("INBOUND") 23 | //BoundaryScopeDefault constant value for the boundary scope DEFAULT of an application config of the Instana Web REST API 24 | BoundaryScopeDefault = BoundaryScope("DEFAULT") 25 | ) 26 | 27 | // SupportedApplicationConfigBoundaryScopes supported BoundaryScopes of the Instana Web REST API 28 | var SupportedApplicationConfigBoundaryScopes = BoundaryScopes{BoundaryScopeAll, BoundaryScopeInbound, BoundaryScopeDefault} 29 | 30 | // SupportedApplicationAlertConfigBoundaryScopes supported BoundaryScopes of the Instana Web REST API 31 | var SupportedApplicationAlertConfigBoundaryScopes = BoundaryScopes{BoundaryScopeAll, BoundaryScopeInbound} 32 | -------------------------------------------------------------------------------- /instana/tag-filter-schema.go: -------------------------------------------------------------------------------- 1 | package instana 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gessnerfl/terraform-provider-instana/instana/tagfilter" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | var tagFilterDiffSuppressFunc = func(k, old, new string, d *schema.ResourceData) bool { 10 | normalized, err := tagfilter.Normalize(new) 11 | if err == nil { 12 | return normalized == old 13 | } 14 | return old == new 15 | } 16 | 17 | var tagFilterStateFunc = func(val interface{}) string { 18 | normalized, err := tagfilter.Normalize(val.(string)) 19 | if err == nil { 20 | return normalized 21 | } 22 | return val.(string) 23 | } 24 | 25 | var tagFilterValidateFunc = func(val interface{}, key string) (warns []string, errs []error) { 26 | v := val.(string) 27 | if _, err := tagfilter.NewParser().Parse(v); err != nil { 28 | errs = append(errs, fmt.Errorf("%q is not a valid tag filter; %s", key, err)) 29 | } 30 | 31 | return 32 | } 33 | 34 | var OptionalTagFilterExpressionSchema = &schema.Schema{ 35 | Type: schema.TypeString, 36 | Optional: true, 37 | Description: "The tag filter expression", 38 | DiffSuppressFunc: tagFilterDiffSuppressFunc, 39 | StateFunc: tagFilterStateFunc, 40 | ValidateFunc: tagFilterValidateFunc, 41 | } 42 | 43 | var RequiredTagFilterExpressionSchema = &schema.Schema{ 44 | Type: schema.TypeString, 45 | Required: true, 46 | Description: "The tag filter expression", 47 | DiffSuppressFunc: tagFilterDiffSuppressFunc, 48 | StateFunc: tagFilterStateFunc, 49 | ValidateFunc: tagFilterValidateFunc, 50 | } 51 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: '{{ .CommitTimestamp }}' 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 18 | goos: 19 | - windows 20 | - linux 21 | - darwin 22 | goarch: 23 | - amd64 24 | - arm64 25 | binary: '{{ .ProjectName }}_v{{ .Version }}' 26 | archives: 27 | - format: zip 28 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 29 | checksum: 30 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 31 | algorithm: sha256 32 | signs: 33 | - artifacts: checksum 34 | args: 35 | # if you are using this is a GitHub action or some other automated pipeline, you 36 | # need to pass the batch flag to indicate its not interactive. 37 | - "--batch" 38 | - "--local-user" 39 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 40 | - "--output" 41 | - "${signature}" 42 | - "--detach-sign" 43 | - "${artifact}" 44 | release: 45 | # If you want to manually examine the release before its live, uncomment this line: 46 | # draft: true 47 | changelog: 48 | skip: false 49 | sort: asc -------------------------------------------------------------------------------- /instana/restapi/application-configs-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | const ( 4 | //ApplicationMonitoringBasePath path to application monitoring resource of Instana RESTful API 5 | ApplicationMonitoringBasePath = InstanaAPIBasePath + "/application-monitoring" 6 | //ApplicationMonitoringSettingsBasePath path to application monitoring settings resource of Instana RESTful API 7 | ApplicationMonitoringSettingsBasePath = ApplicationMonitoringBasePath + settingsPathElement 8 | //ApplicationConfigsResourcePath path to application config resource of Instana RESTful API 9 | ApplicationConfigsResourcePath = ApplicationMonitoringSettingsBasePath + "/application" 10 | ) 11 | 12 | // ApplicationConfigResource represents the REST resource of application perspective configuration at Instana 13 | type ApplicationConfigResource interface { 14 | GetOne(id string) (ApplicationConfig, error) 15 | Upsert(rule ApplicationConfig) (ApplicationConfig, error) 16 | Delete(rule ApplicationConfig) error 17 | DeleteByID(applicationID string) error 18 | } 19 | 20 | // ApplicationConfig is the representation of a application perspective configuration in Instana 21 | type ApplicationConfig struct { 22 | ID string `json:"id"` 23 | Label string `json:"label"` 24 | TagFilterExpression *TagFilter `json:"tagFilterExpression"` 25 | Scope ApplicationConfigScope `json:"scope"` 26 | BoundaryScope BoundaryScope `json:"boundaryScope"` 27 | } 28 | 29 | // GetIDForResourcePath implementation of the interface InstanaDataObject 30 | func (a *ApplicationConfig) GetIDForResourcePath() string { 31 | return a.ID 32 | } 33 | -------------------------------------------------------------------------------- /instana/restapi/application-config-scope.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // ApplicationConfigScope type definition of the application config scope of the Instana Web REST API 4 | type ApplicationConfigScope string 5 | 6 | // ApplicationConfigScopes type definition of slice of ApplicationConfigScope 7 | type ApplicationConfigScopes []ApplicationConfigScope 8 | 9 | // ToStringSlice returns a slice containing the string representations of the given scopes 10 | func (scopes ApplicationConfigScopes) ToStringSlice() []string { 11 | result := make([]string, len(scopes)) 12 | for i, s := range scopes { 13 | result[i] = string(s) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //ApplicationConfigScopeIncludeNoDownstream constant for the scope INCLUDE_NO_DOWNSTREAM 20 | ApplicationConfigScopeIncludeNoDownstream = ApplicationConfigScope("INCLUDE_NO_DOWNSTREAM") 21 | //ApplicationConfigScopeIncludeImmediateDownstreamDatabaseAndMessaging constant for the scope INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING 22 | ApplicationConfigScopeIncludeImmediateDownstreamDatabaseAndMessaging = ApplicationConfigScope("INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING") 23 | //ApplicationConfigScopeIncludeAllDownstream constant for the scope INCLUDE_ALL_DOWNSTREAM 24 | ApplicationConfigScopeIncludeAllDownstream = ApplicationConfigScope("INCLUDE_ALL_DOWNSTREAM") 25 | ) 26 | 27 | // SupportedApplicationConfigScopes supported ApplicationConfigScopes of the Instana Web REST API 28 | var SupportedApplicationConfigScopes = ApplicationConfigScopes{ 29 | ApplicationConfigScopeIncludeNoDownstream, 30 | ApplicationConfigScopeIncludeImmediateDownstreamDatabaseAndMessaging, 31 | ApplicationConfigScopeIncludeAllDownstream, 32 | } 33 | -------------------------------------------------------------------------------- /instana/restapi/custom-payload-field.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // CustomPayloadType custom type for the type of a custom payload 4 | type CustomPayloadType string 5 | 6 | // CustomPayloadTypes custom type for a slice of CustomPayloadType 7 | type CustomPayloadTypes []CustomPayloadType 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (types CustomPayloadTypes) ToStringSlice() []string { 11 | result := make([]string, len(types)) 12 | for i, v := range types { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //StaticStringCustomPayloadType constant value for the static CustomPayloadType 20 | StaticStringCustomPayloadType = CustomPayloadType("staticString") 21 | //DynamicCustomPayloadType constant value for the dynamic CustomPayloadType 22 | DynamicCustomPayloadType = CustomPayloadType("dynamic") 23 | ) 24 | 25 | // SupportedCustomPayloadTypes list of all supported CustomPayloadType 26 | var SupportedCustomPayloadTypes = CustomPayloadTypes{StaticStringCustomPayloadType, DynamicCustomPayloadType} 27 | 28 | // StaticStringCustomPayloadFieldValue type for static string values of custom payload field 29 | type StaticStringCustomPayloadFieldValue string 30 | 31 | // DynamicCustomPayloadFieldValue type for dynamic values of custom payload field 32 | type DynamicCustomPayloadFieldValue struct { 33 | Key *string `json:"key"` 34 | TagName string `json:"tagName"` 35 | } 36 | 37 | // CustomPayloadField custom type to represent static fields with a string value for custom payloads 38 | type CustomPayloadField[T any] struct { 39 | Type CustomPayloadType `json:"type"` 40 | Key string `json:"key"` 41 | Value T `json:"value"` 42 | } 43 | -------------------------------------------------------------------------------- /instana/restapi/alerts-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // AlertsResourcePath path to Alerts resource of Instana RESTful API 4 | const AlertsResourcePath = EventSettingsBasePath + "/alerts" 5 | 6 | // EventFilteringConfiguration type definiton of an EventFilteringConfiguration of a AlertingConfiguration of the Instana ReST AOI 7 | type EventFilteringConfiguration struct { 8 | Query *string `json:"query"` 9 | RuleIDs []string `json:"ruleIds"` 10 | EventTypes []AlertEventType `json:"eventTypes"` 11 | } 12 | 13 | // AlertingConfiguration type definition of an Alerting Configuration in Instana REST API 14 | type AlertingConfiguration struct { 15 | ID string `json:"id"` 16 | AlertName string `json:"alertName"` 17 | IntegrationIDs []string `json:"integrationIds"` 18 | EventFilteringConfiguration EventFilteringConfiguration `json:"eventFilteringConfiguration"` 19 | CustomerPayloadFields []CustomPayloadField[any] `json:"customPayloadFields"` 20 | } 21 | 22 | // GetIDForResourcePath implementation of the interface InstanaDataObject 23 | func (c *AlertingConfiguration) GetIDForResourcePath() string { 24 | return c.ID 25 | } 26 | 27 | // GetCustomerPayloadFields implementation of the interface customPayloadFieldsAwareInstanaDataObject 28 | func (a *AlertingConfiguration) GetCustomerPayloadFields() []CustomPayloadField[any] { 29 | return a.CustomerPayloadFields 30 | } 31 | 32 | // SetCustomerPayloadFields implementation of the interface customPayloadFieldsAwareInstanaDataObject 33 | func (a *AlertingConfiguration) SetCustomerPayloadFields(fields []CustomPayloadField[any]) { 34 | a.CustomerPayloadFields = fields 35 | } 36 | -------------------------------------------------------------------------------- /instana/restapi/severity.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // Severity representation of the severity in both worlds Instana API and Terraform Provider 4 | type Severity struct { 5 | apiRepresentation int 6 | terraformRepresentation string 7 | } 8 | 9 | // GetAPIRepresentation returns the integer representation of the Instana API 10 | func (s Severity) GetAPIRepresentation() int { return s.apiRepresentation } 11 | 12 | // GetTerraformRepresentation returns the string representation of the Terraform Provider 13 | func (s Severity) GetTerraformRepresentation() string { return s.terraformRepresentation } 14 | 15 | // SeverityCritical representation of the critical severity 16 | var SeverityCritical = Severity{apiRepresentation: 10, terraformRepresentation: "critical"} 17 | 18 | // SeverityWarning representation of the warning severity 19 | var SeverityWarning = Severity{apiRepresentation: 5, terraformRepresentation: "warning"} 20 | 21 | // Severities custom type representing a slice of Severity 22 | type Severities []Severity 23 | 24 | // TerraformRepresentations returns the corresponding Terraform representations as string slice 25 | func (severities Severities) TerraformRepresentations() []string { 26 | result := make([]string, len(severities)) 27 | for i, v := range severities { 28 | result[i] = v.terraformRepresentation 29 | } 30 | return result 31 | } 32 | 33 | // APIRepresentations returns the corresponding Instana API representations as int slice 34 | func (severities Severities) APIRepresentations() []int { 35 | result := make([]int, len(severities)) 36 | for i, v := range severities { 37 | result[i] = v.apiRepresentation 38 | } 39 | return result 40 | } 41 | 42 | // SupportedSeverities slice of all supported severities of the Instana REST API 43 | var SupportedSeverities = Severities{SeverityWarning, SeverityCritical} 44 | -------------------------------------------------------------------------------- /instana/restapi/synthetic-test.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | type SyntheticTestConfig struct { 4 | MarkSyntheticCall bool `json:"markSyntheticCall"` 5 | Retries int32 `json:"retries"` 6 | RetryInterval int32 `json:"retryInterval"` 7 | SyntheticType string `json:"syntheticType"` 8 | Timeout *string `json:"timeout"` 9 | // HttpAction 10 | URL *string `json:"url"` 11 | Operation *string `json:"operation"` 12 | Headers map[string]interface{} `json:"headers"` 13 | Body *string `json:"body"` 14 | ValidationString *string `json:"validationString"` 15 | FollowRedirect *bool `json:"followRedirect"` 16 | AllowInsecure *bool `json:"allowInsecure"` 17 | ExpectStatus *int32 `json:"expectStatus"` 18 | ExpectMatch *string `json:"expectMatch"` 19 | // HttpScript 20 | Script *string `json:"script"` 21 | } 22 | 23 | type SyntheticTest struct { 24 | ID string `json:"id"` 25 | Label string `json:"label"` 26 | Description *string `json:"description"` 27 | Active bool `json:"active"` 28 | ApplicationID *string `json:"applicationId"` 29 | Configuration SyntheticTestConfig `json:"configuration"` 30 | CustomProperties map[string]interface{} `json:"customProperties"` 31 | Locations []string `json:"locations"` 32 | PlaybackMode string `json:"playbackMode"` 33 | TestFrequency *int32 `json:"testFrequency"` 34 | } 35 | 36 | // GetIDForResourcePath implementation of the interface InstanaDataObject for SyntheticTest 37 | func (s *SyntheticTest) GetIDForResourcePath() string { 38 | return s.ID 39 | } 40 | -------------------------------------------------------------------------------- /instana/restapi/website-alert-config.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // WebsiteAlertConfigResourcePath path to website alert config resource of Instana RESTful API 4 | const WebsiteAlertConfigResourcePath = EventSettingsBasePath + "/website-alert-configs" 5 | 6 | // WebsiteAlertConfig is the representation of an website alert configuration in Instana 7 | type WebsiteAlertConfig struct { 8 | ID string `json:"id"` 9 | Name string `json:"name"` 10 | Description string `json:"description"` 11 | Severity int `json:"severity"` 12 | Triggering bool `json:"triggering"` 13 | WebsiteID string `json:"websiteId"` 14 | TagFilterExpression *TagFilter `json:"tagFilterExpression"` 15 | AlertChannelIDs []string `json:"alertChannelIds"` 16 | Granularity Granularity `json:"granularity"` 17 | CustomerPayloadFields []CustomPayloadField[any] `json:"customPayloadFields"` 18 | Rule WebsiteAlertRule `json:"rule"` 19 | Threshold Threshold `json:"threshold"` 20 | TimeThreshold WebsiteTimeThreshold `json:"timeThreshold"` 21 | } 22 | 23 | // GetIDForResourcePath implementation of the interface InstanaDataObject 24 | func (r *WebsiteAlertConfig) GetIDForResourcePath() string { 25 | return r.ID 26 | } 27 | 28 | // GetCustomerPayloadFields implementation of the interface customPayloadFieldsAwareInstanaDataObject 29 | func (a *WebsiteAlertConfig) GetCustomerPayloadFields() []CustomPayloadField[any] { 30 | return a.CustomerPayloadFields 31 | } 32 | 33 | // SetCustomerPayloadFields implementation of the interface customPayloadFieldsAwareInstanaDataObject 34 | func (a *WebsiteAlertConfig) SetCustomerPayloadFields(fields []CustomPayloadField[any]) { 35 | a.CustomerPayloadFields = fields 36 | } 37 | -------------------------------------------------------------------------------- /instana/restapi/sli-config-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | const ( 4 | //SliConfigResourcePath path to sli config resource of Instana RESTful API 5 | SliConfigResourcePath = SettingsBasePath + "/v2/sli" 6 | ) 7 | 8 | // MetricConfiguration represents the nested object metric configuration of the sli config REST resource at Instana 9 | type MetricConfiguration struct { 10 | Name string `json:"metricName"` 11 | Aggregation string `json:"metricAggregation"` 12 | Threshold float64 `json:"threshold"` 13 | } 14 | 15 | // SliEntity represents the nested object sli entity of the sli config REST resource at Instana 16 | type SliEntity struct { 17 | Type string `json:"sliType"` 18 | ApplicationID *string `json:"applicationId"` 19 | ServiceID *string `json:"serviceId"` 20 | EndpointID *string `json:"endpointId"` 21 | BoundaryScope *string `json:"boundaryScope"` 22 | IncludeSynthetic *bool `json:"includeSynthetic"` 23 | IncludeInternal *bool `json:"includeInternal"` 24 | WebsiteId *string `json:"websiteId"` 25 | BeaconType *string `json:"beaconType"` 26 | GoodEventFilterExpression *TagFilter `json:"goodEventFilterExpression"` 27 | BadEventFilterExpression *TagFilter `json:"badEventFilterExpression"` 28 | FilterExpression *TagFilter `json:"filterExpression"` 29 | } 30 | 31 | // SliConfig represents the REST resource of sli configuration at Instana 32 | type SliConfig struct { 33 | ID string `json:"id"` 34 | Name string `json:"sliName"` 35 | InitialEvaluationTimestamp int `json:"initialEvaluationTimestamp"` 36 | MetricConfiguration *MetricConfiguration `json:"metricConfiguration"` 37 | SliEntity SliEntity `json:"sliEntity"` 38 | } 39 | 40 | // GetIDForResourcePath implementation of the interface InstanaDataObject 41 | func (s *SliConfig) GetIDForResourcePath() string { 42 | return s.ID 43 | } 44 | -------------------------------------------------------------------------------- /testutils/test-server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF1DCCA7wCCQD2HHbKumiUNjANBgkqhkiG9w0BAQsFADCBqzELMAkGA1UEBhMC 3 | REUxGzAZBgNVBAgMEkJhZGVuIFd1ZXJ0dGVtYmVyZzETMBEGA1UEBwwKSGVpZGVs 4 | YmVyZzEYMBYGA1UECgwPRmxvcmlhbiBHZXNzbmVyMRQwEgYDVQQLDAtkZXZlbG9w 5 | bWVudDEUMBIGA1UEAwwLdGVzdC1zZXJ2ZXIxJDAiBgkqhkiG9w0BCQEWFWZsby5n 6 | ZXNzbmVyQGdtYWlsLmNvbTAeFw0xOTAzMTYxOTQwMDFaFw0yOTAzMTMxOTQwMDFa 7 | MIGrMQswCQYDVQQGEwJERTEbMBkGA1UECAwSQmFkZW4gV3VlcnR0ZW1iZXJnMRMw 8 | EQYDVQQHDApIZWlkZWxiZXJnMRgwFgYDVQQKDA9GbG9yaWFuIEdlc3NuZXIxFDAS 9 | BgNVBAsMC2RldmVsb3BtZW50MRQwEgYDVQQDDAt0ZXN0LXNlcnZlcjEkMCIGCSqG 10 | SIb3DQEJARYVZmxvLmdlc3NuZXJAZ21haWwuY29tMIICIjANBgkqhkiG9w0BAQEF 11 | AAOCAg8AMIICCgKCAgEA0w0jmL3nofT/0la8C6FRsD+8BsJ3FKqRJq0la4KKZ1RJ 12 | 9Nv7SgvoomqNj+mmV3prmzQXWI8aDGWhZuBZe6U+ey48yQeaaIxQ9gG2GY31PeIl 13 | irB/NuCMYTFnbkZHPsJd5VAvbu95Egt9gvIORqISSwOcPl0/RD5jAakF3UpQXfQ3 14 | E48pOMg9jfPWTrzTwtfDNidT8I88VmOADchGqYn5GjvxVzShz06B7c0Kftpx2jUo 15 | PZpz5DHJmeZ+ZGa4AZCWwtRuJibCZ1ls4iWmocfJPx8IwzLVHN6lZlwRUPE1lq7j 16 | l3wkJe9JqdfKTToDaxgiqVSYj8j2CvR00NGDTG46IZeZbd7HbUr30hJTB4INlhJu 17 | r/gVQZovTJnC7rl7dYLLsuZCJbJeUpclg4wnf9Uxo4B1Q0XQZtOqTrTOHdSH+LAL 18 | 9XA4cHtBVLwSAhX3GdfE+C07jSo96XUpE3ijWUkf/YuzY/cRiU5rbU6XDtxsWZFh 19 | FANVNQ4CQqu4ygLP3MLqzIz6pK5H3rO5GzaCSfN7p7wnfn4GfzkCxh8AdhigtuMw 20 | zV0yyLdbLFAUvmQmW2WG+PworHIFLDhbiC21Dz3nhWNkmKbIZPnW572SZ+AO4dNO 21 | AyHENyl6P3S+PHkh6udojXB5GRbGCNlKQ2GMJQ2WwoKNWPxb1lIt5Fe7+IhHhcUC 22 | AwEAATANBgkqhkiG9w0BAQsFAAOCAgEAjoUvT1yCsh1xOFPT6yXh1KqVeJ4Qilbp 23 | nhF8ELDbk8xFAKhZ1MKEJpMyaY5cXnJh2+kPVJINyNGXmh7s0D+5Hj+X3CcokE8Z 24 | Ri9PN4zHcRQMD7niskwyHif8jR+RnltJeqnDT69uWqYCasxZ01WJhYqUri+j0c9D 25 | 7k/QL9N/pGB10KlqlTB+ZdbiYRYKSuo8+XhRoiU0Wrm0IdR1F0VJwaTP4qTzdmYQ 26 | 8+kCfS7GhXfIWaxKZjIOqJb6IRkPk7Z/hR2Ef5W5V2BrBW+yKWrJCYwoeQQFmTA7 27 | zTfA80RHiWlSWS1tnORkcaG93q46E6rgWYvzNPBpnIulLVJqbCbKSY64EVNLaY3N 28 | vH9A+rWEbsRaQterBXZuylgbqjDemlDUROEkC+YAIHTML4gfrKXGltpd0N+0Ru5E 29 | xsV/trXFBBmmHb6PzuYKa9No60gNkNX5//m1dFX2HCiI2CrbI1mj+ke6xfQxzOzA 30 | oalCVdMOvj0ykmjDNinVtKFZslHgcHU/5Xem4veXw8pgJ1q6nxNJ8djci9jPBhXS 31 | tk7TT9YePh6mcCTg8mwa6FMS1if6GLSX3wRBIQrKwUoraDs6RNbog5Vcd20aK9YC 32 | J1sTnqA9ih/qpW26ZgNgf8ss8PunSiY5N3syKS28Nke9w9YV1rTayPRmLinRE4QR 33 | iJiUtwIZeS4= 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # !!!Deprecated!!! Terraform Provider Instana 2 | 3 | > [!WARNING] 4 | > **Deprecated**. This project has been handed over to and is maintained under IBM's offical [Instana](https://github.com/instana) org. Please use the official IBM Instana Terraform provider [https://github.com/instana/terraform-provider-instana](https://github.com/instana/terraform-provider-instana). 5 | 6 | [![Build, Test and Verify](https://github.com/gessnerfl/terraform-provider-instana/actions/workflows/build.yml/badge.svg)](https://github.com/gessnerfl/terraform-provider-instana/actions/workflows/build.yml) 7 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=de.gessnerfl.terraform-provider-instana&metric=alert_status)](https://sonarcloud.io/dashboard?id=de.gessnerfl.terraform-provider-instana) 8 | 9 | Terraform provider implementation for the Instana REST API. 10 | 11 | Terraform Registry: 12 | 13 | Changes Log: **[CHANGELOG.md](https://github.com/gessnerfl/terraform-provider-instana/blob/master/CHANGELOG.md)** 14 | 15 | ## Documentation 16 | 17 | The documentation of the provider can be found on the Terraform Registry Page . 18 | 19 | ## Implementation Details 20 | 21 | ### Testing 22 | 23 | Mocking: 24 | Tests are co-located in the package next to the implementation. We use gomock () for mocking. Mocks are 25 | created using the *source mode*. All mocks are create in the `mock` package. To generate mocks you can use the helper script 26 | `generate-mock-for-file ` from the root directory of this project. 27 | 28 | Alternatively you can manually execute `mockgen` as follows 29 | 30 | ```bash 31 | mockgen -source= -destination=mocks/_mocks.go -package=mocks 32 | ``` 33 | 34 | ### Release a new version 35 | 36 | 1. Create a new tag follow semantic versioning approach 37 | 2. Update changelog before creating a new release by using [github-changelog-generator](https://github.com/github-changelog-generator/github-changelog-generator) 38 | 3. Push the tag to the remote to build the new release 39 | -------------------------------------------------------------------------------- /instana/test-helpers.go: -------------------------------------------------------------------------------- 1 | package instana 2 | 3 | import ( 4 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 5 | "testing" 6 | 7 | "github.com/gessnerfl/terraform-provider-instana/mocks" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "go.uber.org/mock/gomock" 10 | ) 11 | 12 | // NewTestHelper creates a new instance of TestHelper 13 | func NewTestHelper[T restapi.InstanaDataObject](t *testing.T) TestHelper[T] { 14 | return &testHelperImpl[T]{t: t} 15 | } 16 | 17 | // TestHelper definition of the test helper utility 18 | type TestHelper[T restapi.InstanaDataObject] interface { 19 | WithMocking(t *testing.T, testFunction func(ctrl *gomock.Controller, meta *ProviderMeta, mockInstanApi *mocks.MockInstanaAPI)) 20 | CreateProviderMetaMock(ctrl *gomock.Controller) (*ProviderMeta, *mocks.MockInstanaAPI) 21 | CreateEmptyResourceDataForResourceHandle(resourceHandle ResourceHandle[T]) *schema.ResourceData 22 | CreateResourceDataForResourceHandle(resourceHandle ResourceHandle[T], data map[string]interface{}) *schema.ResourceData 23 | } 24 | 25 | type testHelperImpl[T restapi.InstanaDataObject] struct { 26 | t *testing.T 27 | } 28 | 29 | func (inst *testHelperImpl[T]) WithMocking(t *testing.T, testFunction func(ctrl *gomock.Controller, meta *ProviderMeta, mockInstanApi *mocks.MockInstanaAPI)) { 30 | ctrl := gomock.NewController(t) 31 | defer ctrl.Finish() 32 | 33 | providerMeta, mockInstanaAPI := inst.CreateProviderMetaMock(ctrl) 34 | testFunction(ctrl, providerMeta, mockInstanaAPI) 35 | } 36 | 37 | func (inst *testHelperImpl[T]) CreateProviderMetaMock(ctrl *gomock.Controller) (*ProviderMeta, *mocks.MockInstanaAPI) { 38 | mockInstanaAPI := mocks.NewMockInstanaAPI(ctrl) 39 | providerMeta := &ProviderMeta{ 40 | InstanaAPI: mockInstanaAPI, 41 | } 42 | return providerMeta, mockInstanaAPI 43 | } 44 | 45 | func (inst *testHelperImpl[T]) CreateEmptyResourceDataForResourceHandle(resourceHandle ResourceHandle[T]) *schema.ResourceData { 46 | data := make(map[string]interface{}) 47 | return inst.CreateResourceDataForResourceHandle(resourceHandle, data) 48 | } 49 | 50 | func (inst *testHelperImpl[T]) CreateResourceDataForResourceHandle(resourceHandle ResourceHandle[T], data map[string]interface{}) *schema.ResourceData { 51 | return schema.TestResourceDataRaw(inst.t, resourceHandle.MetaData().Schema, data) 52 | } 53 | -------------------------------------------------------------------------------- /docs/resources/custom_dashboard.md: -------------------------------------------------------------------------------- 1 | # Custom Dashboard 2 | 3 | Management of custom dashboards. 4 | 5 | API Documentation: 6 | 7 | Requires permission `canCreatePublicCustomDashboards` (Creation of public custom dashboards) and 8 | `canEditAllAccessibleCustomDashboards` (Management of all accessible custom dashboards) to be activated 9 | 10 | The ID of the resource which is also used as unique identifier in Instana is auto generated! 11 | 12 | ## Example Usage 13 | 14 | ```hcl 15 | resource "instana_custom_dashboard" "example" { 16 | title = "Example Dashboard" 17 | 18 | access_rule { 19 | access_type = "READ_WRITE" 20 | relation_type = "USER" 21 | related_id = "user-id-1" 22 | } 23 | 24 | access_rule { 25 | access_type = "READ_WRITE" 26 | related_id = "user-id-2" 27 | relation_type = "USER" 28 | } 29 | 30 | access_rule { 31 | access_type = "READ" 32 | relation_type = "GLOBAL" 33 | } 34 | 35 | widgets = file("${path.module}/widgets.json") 36 | } 37 | ``` 38 | 39 | ## Argument Reference 40 | 41 | * `title` - Required - the name of the custom dashboard 42 | * `access_rule` - Required - configuration of access rules (sharing/permissions) of the custom dashboard 43 | * `access_type` - Required - type of granted access. Supported values are `READ` and `READ_WRITE` 44 | * `relation_type` - Required - type of the entity for which the access is granted. Supported values are: 45 | `USER`, `API_TOKEN`, `ROLE`, `TEAM`, `GLOBAL` 46 | * `related_id` - Optional - the id of the related entity for which access is granted. Required for all 47 | `relation_type` except `GLOBAL` 48 | * `widgets` - Required - JSON array of widget configurations. It is recommended to get this configuration via the 49 | `Edit as Json` feature of custom dashboards in Instana UI and to adopt the configuration afterwards. It is also 50 | recommended to store the configuration in dedicated json files. This allows the use of the built-in terraform functions 51 | `file` () or `templatefile` (https://www.terraform.io/language/functions/templatefile) 52 | 53 | ## Import 54 | 55 | Custom Dashboards can be imported using the `id` of the custom dashboard, e.g.: 56 | 57 | ``` 58 | $ terraform import instana_custom_dashboard.example 60845e4e5e6b9cf8fc2868da 59 | ``` 60 | -------------------------------------------------------------------------------- /instana/restapi/synthetic-test-rest-resource.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // NewSyntheticTestRestResource creates a new REST resource using the provided unmarshaller function to convert the response from the REST API to the corresponding InstanaDataObject. The REST resource is using PUT as operation for create and update 4 | func NewSyntheticTestRestResource(unmarshaller JSONUnmarshaller[*SyntheticTest], client RestClient) RestResource[*SyntheticTest] { 5 | return &SyntheticTestRestResource{ 6 | resourcePath: SyntheticTestResourcePath, 7 | unmarshaller: unmarshaller, 8 | client: client, 9 | } 10 | } 11 | 12 | type SyntheticTestRestResource struct { 13 | resourcePath string 14 | unmarshaller JSONUnmarshaller[*SyntheticTest] 15 | client RestClient 16 | } 17 | 18 | func (r *SyntheticTestRestResource) GetAll() (*[]*SyntheticTest, error) { 19 | data, err := r.client.Get(r.resourcePath) 20 | if err != nil { 21 | return nil, err 22 | } 23 | objects, err := r.unmarshaller.UnmarshalArray(data) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return objects, nil 28 | } 29 | 30 | func (r *SyntheticTestRestResource) GetOne(id string) (*SyntheticTest, error) { 31 | data, err := r.client.GetOne(id, r.resourcePath) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return r.validateResponseAndConvertToStruct(data) 36 | } 37 | 38 | func (r *SyntheticTestRestResource) Create(data *SyntheticTest) (*SyntheticTest, error) { 39 | response, err := r.client.Post(data, r.resourcePath) 40 | if err != nil { 41 | return data, err 42 | } 43 | return r.validateResponseAndConvertToStruct(response) 44 | } 45 | 46 | func (r *SyntheticTestRestResource) Update(data *SyntheticTest) (*SyntheticTest, error) { 47 | _, err := r.client.Put(data, r.resourcePath) 48 | if err != nil { 49 | return data, err 50 | } 51 | return r.GetOne(data.GetIDForResourcePath()) 52 | } 53 | 54 | func (r *SyntheticTestRestResource) validateResponseAndConvertToStruct(data []byte) (*SyntheticTest, error) { 55 | dataObject, err := r.unmarshaller.Unmarshal(data) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return dataObject, nil 60 | } 61 | 62 | func (r *SyntheticTestRestResource) Delete(data *SyntheticTest) error { 63 | return r.DeleteByID(data.GetIDForResourcePath()) 64 | } 65 | 66 | func (r *SyntheticTestRestResource) DeleteByID(id string) error { 67 | return r.client.Delete(id, r.resourcePath) 68 | } 69 | -------------------------------------------------------------------------------- /instana/provider_test.go: -------------------------------------------------------------------------------- 1 | package instana_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana" 7 | "github.com/gessnerfl/terraform-provider-instana/testutils" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestProviderShouldValidateInternally(t *testing.T) { 12 | err := Provider().InternalValidate() 13 | 14 | assert.Nil(t, err) 15 | } 16 | 17 | func TestProviderShouldContainValidSchemaDefinition(t *testing.T) { 18 | config := Provider() 19 | 20 | assert.NotNil(t, config.Schema) 21 | assert.Equal(t, 3, len(config.Schema)) 22 | 23 | schemaAssert := testutils.NewTerraformSchemaAssert(config.Schema, t) 24 | schemaAssert.AssertSchemaIsRequiredAndOfTypeString(SchemaFieldAPIToken) 25 | schemaAssert.AssertSchemaIsRequiredAndOfTypeString(SchemaFieldEndpoint) 26 | schemaAssert.AssertSchemaIsOfTypeBooleanWithDefault(SchemaFieldTlsSkipVerify, false) 27 | } 28 | 29 | func TestProviderShouldContainValidResourceDefinitions(t *testing.T) { 30 | config := Provider() 31 | 32 | assert.Equal(t, 13, len(config.ResourcesMap)) 33 | 34 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaAPIToken]) 35 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaApplicationConfig]) 36 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaApplicationAlertConfig]) 37 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaGlobalApplicationAlertConfig]) 38 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaSliConfig]) 39 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaWebsiteMonitoringConfig]) 40 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaWebsiteAlertConfig]) 41 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaGroup]) 42 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaCustomDashboard]) 43 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaSyntheticTest]) 44 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaCustomEventSpecification]) 45 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaAlertingChannel]) 46 | assert.NotNil(t, config.ResourcesMap[ResourceInstanaAlertingConfig]) 47 | } 48 | 49 | func TestProviderShouldContainValidDataSourceDefinitions(t *testing.T) { 50 | config := Provider() 51 | 52 | assert.Equal(t, 3, len(config.DataSourcesMap)) 53 | 54 | assert.NotNil(t, config.DataSourcesMap[DataSourceBuiltinEvent]) 55 | assert.NotNil(t, config.DataSourcesMap[DataSourceSyntheticLocation]) 56 | assert.NotNil(t, config.DataSourcesMap[DataSourceAlertingChannel]) 57 | 58 | } 59 | -------------------------------------------------------------------------------- /instana/restapi/custom-payload-fields-unmarshaller-adapter.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | type CustomPayloadFieldsAware interface { 4 | GetCustomerPayloadFields() []CustomPayloadField[any] 5 | SetCustomerPayloadFields([]CustomPayloadField[any]) 6 | } 7 | 8 | type customPayloadFieldsAwareInstanaDataObject interface { 9 | CustomPayloadFieldsAware 10 | InstanaDataObject 11 | } 12 | 13 | // NewCustomPayloadFieldsUnmarshallerAdapter creates a new Unmarshaller instance which can be added as an adapter to the default unmarchallers to map custom payload fields 14 | func NewCustomPayloadFieldsUnmarshallerAdapter[T customPayloadFieldsAwareInstanaDataObject](unmarshaller JSONUnmarshaller[T]) JSONUnmarshaller[T] { 15 | return &customPayloadFieldsUnmarshallerAdapter[T]{unmarshaller: unmarshaller} 16 | } 17 | 18 | type customPayloadFieldsUnmarshallerAdapter[T customPayloadFieldsAwareInstanaDataObject] struct { 19 | unmarshaller JSONUnmarshaller[T] 20 | } 21 | 22 | // UnmarshalArray Unmarshaller interface implementation 23 | func (a *customPayloadFieldsUnmarshallerAdapter[T]) UnmarshalArray(data []byte) (*[]T, error) { 24 | temp, err := a.unmarshaller.UnmarshalArray(data) 25 | if err != nil { 26 | return temp, err 27 | } 28 | if temp != nil { 29 | for _, v := range *temp { 30 | a.mapCustomPayloadFields(v) 31 | } 32 | } 33 | return temp, nil 34 | } 35 | 36 | func (a *customPayloadFieldsUnmarshallerAdapter[T]) Unmarshal(data []byte) (T, error) { 37 | temp, err := a.unmarshaller.Unmarshal(data) 38 | if err != nil { 39 | return temp, err 40 | } 41 | a.mapCustomPayloadFields(temp) 42 | return temp, nil 43 | } 44 | 45 | func (a *customPayloadFieldsUnmarshallerAdapter[T]) mapCustomPayloadFields(temp T) { 46 | customFields := temp.GetCustomerPayloadFields() 47 | for i, v := range customFields { 48 | customFields[i] = a.mapCustomPayloadField(v) 49 | } 50 | temp.SetCustomerPayloadFields(customFields) 51 | } 52 | 53 | func (a *customPayloadFieldsUnmarshallerAdapter[T]) mapCustomPayloadField(field CustomPayloadField[any]) CustomPayloadField[any] { 54 | if field.Type == DynamicCustomPayloadType { 55 | data := field.Value.(map[string]interface{}) 56 | var keyPtr *string 57 | if val, ok := data["key"]; ok && val != nil { 58 | key := val.(string) 59 | keyPtr = &key 60 | } 61 | field.Value = DynamicCustomPayloadFieldValue{ 62 | TagName: data["tagName"].(string), 63 | Key: keyPtr, 64 | } 65 | } else { 66 | 67 | value := field.Value.(string) 68 | field.Value = StaticStringCustomPayloadFieldValue(value) 69 | } 70 | return field 71 | } 72 | -------------------------------------------------------------------------------- /instana/restapi/default-json-unmarshaller_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | 9 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 10 | ) 11 | 12 | const ( 13 | defaultObjectId = "object-id" 14 | defaultObjectName = "object-name" 15 | ) 16 | 17 | func TestShouldSuccessfullyUnmarshalSingleObject(t *testing.T) { 18 | testData := &testObject{ 19 | ID: defaultObjectId, 20 | Name: defaultObjectName, 21 | } 22 | 23 | serializedJSON, _ := json.Marshal(testData) 24 | 25 | sut := NewDefaultJSONUnmarshaller(&testObject{}) 26 | 27 | result, err := sut.Unmarshal(serializedJSON) 28 | 29 | require.NoError(t, err) 30 | require.Equal(t, testData, result) 31 | } 32 | 33 | func TestShouldSuccessfullyUnmarshalArrayOfObjects(t *testing.T) { 34 | testData := &testObject{ 35 | ID: defaultObjectId, 36 | Name: defaultObjectName, 37 | } 38 | testObjects := &[]*testObject{testData, testData} 39 | 40 | serializedJSON, _ := json.Marshal(testObjects) 41 | 42 | sut := NewDefaultJSONUnmarshaller(testData) 43 | 44 | result, err := sut.UnmarshalArray(serializedJSON) 45 | 46 | require.NoError(t, err) 47 | require.Equal(t, testObjects, result) 48 | } 49 | 50 | func TestShouldFailToUnmarshalArrayWhenNoValidJsonIsProvided(t *testing.T) { 51 | sut := NewDefaultJSONUnmarshaller(&testObject{}) 52 | 53 | _, err := sut.UnmarshalArray([]byte("invalid json data")) 54 | 55 | require.Error(t, err) 56 | } 57 | 58 | func TestShouldFailToUnmarshalWhenObjectIsRequestedButResponseIsAJsonArray(t *testing.T) { 59 | testData := &testObject{ 60 | ID: defaultObjectId, 61 | Name: defaultObjectName, 62 | } 63 | testObjects := []*testObject{testData, testData} 64 | 65 | serializedJSON, _ := json.Marshal(testObjects) 66 | 67 | sut := NewDefaultJSONUnmarshaller(&testObject{}) 68 | 69 | _, err := sut.Unmarshal(serializedJSON) 70 | 71 | require.Error(t, err) 72 | } 73 | 74 | func TestShouldFailToUnmarshalWhenResponseIsNotAJsonMessage(t *testing.T) { 75 | response := `foo bar` 76 | 77 | sut := NewDefaultJSONUnmarshaller(&testObject{}) 78 | 79 | _, err := sut.Unmarshal([]byte(response)) 80 | 81 | require.Error(t, err) 82 | } 83 | 84 | func TestShouldReturnEmptyObjectWhenJsonObjectIsReturnWhereNoFiledMatches(t *testing.T) { 85 | response := `{"foo" : "bar" }` 86 | 87 | sut := NewDefaultJSONUnmarshaller(&testObject{}) 88 | 89 | result, err := sut.Unmarshal([]byte(response)) 90 | 91 | require.NoError(t, err) 92 | require.Equal(t, &testObject{}, result) 93 | } 94 | -------------------------------------------------------------------------------- /instana/restapi/website-monitoring-config-rest-resource.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // NewWebsiteMonitoringConfigRestResource creates a new REST for the website monitoring config 4 | func NewWebsiteMonitoringConfigRestResource(unmarshaller JSONUnmarshaller[*WebsiteMonitoringConfig], client RestClient) RestResource[*WebsiteMonitoringConfig] { 5 | return &websiteMonitoringConfigRestResource{ 6 | resourcePath: WebsiteMonitoringConfigResourcePath, 7 | unmarshaller: unmarshaller, 8 | client: client, 9 | } 10 | } 11 | 12 | type websiteMonitoringConfigRestResource struct { 13 | resourcePath string 14 | unmarshaller JSONUnmarshaller[*WebsiteMonitoringConfig] 15 | client RestClient 16 | } 17 | 18 | func (r *websiteMonitoringConfigRestResource) GetAll() (*[]*WebsiteMonitoringConfig, error) { 19 | data, err := r.client.Get(r.resourcePath) 20 | if err != nil { 21 | return nil, err 22 | } 23 | objects, err := r.unmarshaller.UnmarshalArray(data) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return objects, nil 28 | } 29 | 30 | func (r *websiteMonitoringConfigRestResource) GetOne(id string) (*WebsiteMonitoringConfig, error) { 31 | data, err := r.client.GetOne(id, r.resourcePath) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return r.validateResponseAndConvertToStruct(data) 36 | } 37 | 38 | func (r *websiteMonitoringConfigRestResource) Create(data *WebsiteMonitoringConfig) (*WebsiteMonitoringConfig, error) { 39 | response, err := r.client.PostByQuery(r.resourcePath, map[string]string{"name": data.Name}) 40 | if err != nil { 41 | return data, err 42 | } 43 | return r.validateResponseAndConvertToStruct(response) 44 | } 45 | 46 | func (r *websiteMonitoringConfigRestResource) Update(data *WebsiteMonitoringConfig) (*WebsiteMonitoringConfig, error) { 47 | response, err := r.client.PutByQuery(r.resourcePath, data.GetIDForResourcePath(), map[string]string{"name": data.Name}) 48 | if err != nil { 49 | return data, err 50 | } 51 | return r.validateResponseAndConvertToStruct(response) 52 | } 53 | 54 | func (r *websiteMonitoringConfigRestResource) validateResponseAndConvertToStruct(data []byte) (*WebsiteMonitoringConfig, error) { 55 | dataObject, err := r.unmarshaller.Unmarshal(data) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return dataObject, nil 60 | } 61 | 62 | func (r *websiteMonitoringConfigRestResource) Delete(data *WebsiteMonitoringConfig) error { 63 | return r.DeleteByID(data.GetIDForResourcePath()) 64 | } 65 | 66 | func (r *websiteMonitoringConfigRestResource) DeleteByID(id string) error { 67 | return r.client.Delete(id, r.resourcePath) 68 | } 69 | -------------------------------------------------------------------------------- /instana/restapi/application-alert-config.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // ApplicationAlertConfigsResourcePath the base path of the Instana REST API for application alert configs 4 | const ApplicationAlertConfigsResourcePath = EventSettingsBasePath + "/application-alert-configs" 5 | 6 | // GlobalApplicationAlertConfigsResourcePath the base path of the Instana REST API for global application alert configs 7 | const GlobalApplicationAlertConfigsResourcePath = EventSettingsBasePath + "/global-alert-configs/applications" 8 | 9 | // ApplicationAlertConfig is the representation of an application alert configuration in Instana 10 | type ApplicationAlertConfig struct { 11 | ID string `json:"id"` 12 | Name string `json:"name"` 13 | Description string `json:"description"` 14 | Severity int `json:"severity"` 15 | Triggering bool `json:"triggering"` 16 | Applications map[string]IncludedApplication `json:"applications"` 17 | BoundaryScope BoundaryScope `json:"boundaryScope"` 18 | TagFilterExpression *TagFilter `json:"tagFilterExpression"` 19 | IncludeInternal bool `json:"includeInternal"` 20 | IncludeSynthetic bool `json:"includeSynthetic"` 21 | EvaluationType ApplicationAlertEvaluationType `json:"evaluationType"` 22 | AlertChannelIDs []string `json:"alertChannelIds"` 23 | Granularity Granularity `json:"granularity"` 24 | CustomerPayloadFields []CustomPayloadField[any] `json:"customPayloadFields"` 25 | Rule ApplicationAlertRule `json:"rule"` 26 | Threshold Threshold `json:"threshold"` 27 | TimeThreshold TimeThreshold `json:"timeThreshold"` 28 | } 29 | 30 | // GetIDForResourcePath implementation of the interface InstanaDataObject 31 | func (a *ApplicationAlertConfig) GetIDForResourcePath() string { 32 | return a.ID 33 | } 34 | 35 | // GetCustomerPayloadFields implementation of the interface customPayloadFieldsAwareInstanaDataObject 36 | func (a *ApplicationAlertConfig) GetCustomerPayloadFields() []CustomPayloadField[any] { 37 | return a.CustomerPayloadFields 38 | } 39 | 40 | // SetCustomerPayloadFields implementation of the interface customPayloadFieldsAwareInstanaDataObject 41 | func (a *ApplicationAlertConfig) SetCustomerPayloadFields(fields []CustomPayloadField[any]) { 42 | a.CustomerPayloadFields = fields 43 | } 44 | -------------------------------------------------------------------------------- /docs/resources/alerting_config.md: -------------------------------------------------------------------------------- 1 | # Alerting Configuration 2 | 3 | Management of alert configurations. Alert configurations define how either event types or 4 | event (aka rules) are reported to integrated services (Alerting Channels). 5 | 6 | API Documentation: 7 | 8 | The ID of the resource which is also used as unique identifier in Instana is auto generated! 9 | 10 | --- 11 | **Note:** 12 | 13 | Instana web UI provides the option to select for applications or a dynamic filter query. This is a UI feature only. To setup 14 | altering configuration for applications you need to express this by a dynamic filter query: 15 | 16 | `entity.application.id:\"my-application-perspective-id\""` 17 | 18 | --- 19 | 20 | ## Example Usage 21 | 22 | ### Rule ids 23 | 24 | ```hcl 25 | resource "instana_alerting_config" "example" { 26 | alert_name = "name" 27 | integration_ids = [ "alerting-channel-id1", "alerting-channel-id2" ] 28 | event_filter_query = "query" 29 | event_filter_rule_ids = [ "rule-1", "rule-2" ] 30 | } 31 | ``` 32 | 33 | ### Event types 34 | 35 | ```hcl 36 | resource "instana_alerting_config" "example" { 37 | alert_name = "name" 38 | integration_ids = [ "alerting-channel-id1", "alerting-channel-id2" ] 39 | event_filter_query = "query" 40 | event_filter_event_types = [ "incident", "critical" ] 41 | 42 | custom_payload_field { 43 | key = "test" 44 | value = "test123" 45 | } 46 | } 47 | ``` 48 | 49 | ## Argument Reference 50 | 51 | * `alert_name` - Required - the name of the alerting configuration 52 | * `integration_ids` - Optional - the list of target alerting channel ids 53 | * `event_filter_query` - Optional - a dynamic focus query to restrict the alert configuration to a sub set of entities 54 | * `event_filter_rule_ids` - Optional - list of rule IDs which are included by the alerting config. 55 | * `event_filter_event_types` - Optional - list of event types which are included by the alerting config. 56 | Allowed values: `incident`, `critical`, `warning`, `change`, `online`, `offline`, `agent_monitoring_issue`, `none` 57 | * `custom_payload_filed` - Optional - An optional list of custom payload fields (static key/value pairs added to the event). [Details](#custom-payload-field-argument-reference) 58 | 59 | ### Custom Payload Field Argument Reference 60 | 61 | * `key` - Required - The key of the custom payload field 62 | * `value` - Required - The value of the custom payload field 63 | 64 | ## Import 65 | 66 | Alerting configs can be imported using the `id`, e.g.: 67 | 68 | ``` 69 | $ terraform import instana_alerting_config.my_alerting_config 60845e4e5e6b9cf8fc2868da 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/data-sources/alerting_channel.md: -------------------------------------------------------------------------------- 1 | # Alerting Channel Data Source 2 | 3 | Data source to retrieve details about existing alerting channels 4 | 5 | API Documentation: 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | data "instana_alerting_channel" "example" { 11 | name = "my-alerting-channel" 12 | } 13 | ``` 14 | 15 | ## Argument Reference 16 | 17 | * `name` - Required - the name of the alerting channel 18 | 19 | ## Attribute Reference 20 | 21 | Exactly one of the following items is provided depending on the type of the alerting channel: 22 | 23 | * `email` - configuration of a email alerting channel - [Details](#email) 24 | * `google_chat` - configuration of a Google Chat alerting channel - [Details](#google-chat) 25 | * `office_365` - configuration of a Office 365 alerting channel - [Details](#office-365) 26 | * `ops_genie` - configuration of a OpsGenie alerting channel - [Details](#opsgenie) 27 | * `pager_duty` - configuration of a PagerDuty alerting channel - [Details](#pagerduty) 28 | * `slack` - configuration of a Slack alerting channel - [Details](#slack) 29 | * `splunk` - configuration of a Splunk alerting channel - [Details](#splunk) 30 | * `victor_ops` - configuration of a VictorOps alerting channel - [Details](#victorops) 31 | * `webhook` - configuration of a webhook alerting channel - [Details](#webhook) 32 | 33 | ### Email 34 | 35 | * `emails` - the list of target email addresses 36 | 37 | ### Google Chat 38 | 39 | * `webhook_url` - the URL of the Google Chat Webhook where the alert will be sent to 40 | 41 | ### Office 365 42 | 43 | * `webhook_url` - the URL of the Google Chat Webhook where the alert will be sent to 44 | 45 | ### OpsGenie 46 | 47 | * `api_key` - the API Key for authentication at the Ops Genie API 48 | * `tags` - a list of tags (strings) for the alert in Ops Genie 49 | * `region` - the target Ops Genie region 50 | 51 | ### PagerDuty 52 | 53 | * `service_integration_key` - the key for the service integration in pager duty 54 | 55 | ### Slack 56 | 57 | * `webhook_url` - the URL of the Slack webhook to send alerts to 58 | * `icon_url` - the URL to the icon which should be rendered in the slack message 59 | * `channel` - the target Slack channel where the alert should be posted 60 | 61 | ### Splunk 62 | 63 | * `url` - the target Splunk endpoint URL 64 | * `token` - the authentication token to login at the Splunk API 65 | 66 | ### VictorOps 67 | 68 | * `api_key` - the api key to authenticate at the VictorOps API 69 | * `routing_key` - the routing key used by VictoryOps to route the alert to the desired targe 70 | 71 | ### Webhook 72 | 73 | * `webhook_urls` - the list of webhook URLs where the alert will be sent to 74 | * `http_headers` - key/value map of additional http headers which will be sent to the webhook 75 | -------------------------------------------------------------------------------- /instana/restapi/Instana-api_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestShouldReturnResourcesFromInstanaAPI(t *testing.T) { 11 | api := NewInstanaAPI("api-token", "endpoint", false) 12 | 13 | t.Run("Should return CustomEventSpecification instance", func(t *testing.T) { 14 | resource := api.CustomEventSpecifications() 15 | 16 | require.NotNil(t, resource) 17 | }) 18 | 19 | t.Run("Should return BuiltinEventSpecifications instance", func(t *testing.T) { 20 | resource := api.BuiltinEventSpecifications() 21 | 22 | require.NotNil(t, resource) 23 | }) 24 | t.Run("Should return APITokens instance", func(t *testing.T) { 25 | resource := api.APITokens() 26 | 27 | require.NotNil(t, resource) 28 | }) 29 | t.Run("Should return ApplicationConfig instance", func(t *testing.T) { 30 | resource := api.ApplicationConfigs() 31 | 32 | require.NotNil(t, resource) 33 | }) 34 | t.Run("Should return ApplicationAlertConfig instance", func(t *testing.T) { 35 | resource := api.ApplicationAlertConfigs() 36 | 37 | require.NotNil(t, resource) 38 | }) 39 | t.Run("Should return GlobalApplicationAlertConfig instance", func(t *testing.T) { 40 | resource := api.GlobalApplicationAlertConfigs() 41 | 42 | require.NotNil(t, resource) 43 | }) 44 | t.Run("Should return AlertingChannel instance", func(t *testing.T) { 45 | resource := api.AlertingChannels() 46 | 47 | require.NotNil(t, resource) 48 | }) 49 | t.Run("Should return AlertingConfiguration instance", func(t *testing.T) { 50 | resource := api.AlertingConfigurations() 51 | 52 | require.NotNil(t, resource) 53 | }) 54 | t.Run("Should return SliConfig instance", func(t *testing.T) { 55 | resource := api.SliConfigs() 56 | 57 | require.NotNil(t, resource) 58 | }) 59 | t.Run("Should return WebsiteMonitoringConfig instance", func(t *testing.T) { 60 | resource := api.WebsiteMonitoringConfig() 61 | 62 | require.NotNil(t, resource) 63 | }) 64 | t.Run("Should return WebsiteAlertConfig instance", func(t *testing.T) { 65 | resource := api.WebsiteAlertConfig() 66 | 67 | require.NotNil(t, resource) 68 | }) 69 | t.Run("Should return Groups instance", func(t *testing.T) { 70 | resource := api.Groups() 71 | 72 | require.NotNil(t, resource) 73 | }) 74 | t.Run("Should return Custom Dashboard instance", func(t *testing.T) { 75 | resource := api.CustomDashboards() 76 | 77 | require.NotNil(t, resource) 78 | }) 79 | t.Run("Should return Synthetic test instance", func(t *testing.T) { 80 | resource := api.SyntheticTest() 81 | 82 | require.NotNil(t, resource) 83 | }) 84 | t.Run("Should return Synthetic location instance", func(t *testing.T) { 85 | resource := api.SyntheticLocation() 86 | 87 | require.NotNil(t, resource) 88 | }) 89 | 90 | } 91 | -------------------------------------------------------------------------------- /instana/restapi/api-tokens-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // APITokensResourcePath path to API Tokens resource of Instana RESTful API 4 | const APITokensResourcePath = SettingsBasePath + "/api-tokens" 5 | 6 | // APIToken is the representation of a API Token in Instana 7 | type APIToken struct { 8 | ID string `json:"id"` 9 | AccessGrantingToken string `json:"accessGrantingToken"` 10 | InternalID string `json:"internalId"` 11 | Name string `json:"name"` 12 | CanConfigureServiceMapping bool `json:"canConfigureServiceMapping"` 13 | CanConfigureEumApplications bool `json:"canConfigureEumApplications"` 14 | CanConfigureMobileAppMonitoring bool `json:"canConfigureMobileAppMonitoring"` //NEW 15 | CanConfigureUsers bool `json:"canConfigureUsers"` 16 | CanInstallNewAgents bool `json:"canInstallNewAgents"` 17 | CanSeeUsageInformation bool `json:"canSeeUsageInformation"` 18 | CanConfigureIntegrations bool `json:"canConfigureIntegrations"` 19 | CanSeeOnPremiseLicenseInformation bool `json:"canSeeOnPremLicenseInformation"` 20 | CanConfigureCustomAlerts bool `json:"canConfigureCustomAlerts"` 21 | CanConfigureAPITokens bool `json:"canConfigureApiTokens"` 22 | CanConfigureAgentRunMode bool `json:"canConfigureAgentRunMode"` 23 | CanViewAuditLog bool `json:"canViewAuditLog"` 24 | CanConfigureAgents bool `json:"canConfigureAgents"` 25 | CanConfigureAuthenticationMethods bool `json:"canConfigureAuthenticationMethods"` 26 | CanConfigureApplications bool `json:"canConfigureApplications"` 27 | CanConfigureTeams bool `json:"canConfigureTeams"` 28 | CanConfigureReleases bool `json:"canConfigureReleases"` 29 | CanConfigureLogManagement bool `json:"canConfigureLogManagement"` 30 | CanCreatePublicCustomDashboards bool `json:"canCreatePublicCustomDashboards"` 31 | CanViewLogs bool `json:"canViewLogs"` 32 | CanViewTraceDetails bool `json:"canViewTraceDetails"` 33 | CanConfigureSessionSettings bool `json:"canConfigureSessionSettings"` 34 | CanConfigureServiceLevelIndicators bool `json:"canConfigureServiceLevelIndicators"` 35 | CanConfigureGlobalAlertPayload bool `json:"canConfigureGlobalAlertPayload"` 36 | CanConfigureGlobalAlertConfigs bool `json:"canConfigureGlobalAlertConfigs"` 37 | CanViewAccountAndBillingInformation bool `json:"canViewAccountAndBillingInformation"` 38 | CanEditAllAccessibleCustomDashboards bool `json:"canEditAllAccessibleCustomDashboards"` 39 | } 40 | 41 | // GetIDForResourcePath implemention of the interface InstanaDataObject 42 | func (r *APIToken) GetIDForResourcePath() string { 43 | return r.InternalID 44 | } 45 | -------------------------------------------------------------------------------- /instana/restapi/aggregation.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // Aggregation custom type for an Aggregation 4 | type Aggregation string 5 | 6 | // Aggregations custom type for a slice of Aggregation 7 | type Aggregations []Aggregation 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (aggregations Aggregations) ToStringSlice() []string { 11 | result := make([]string, len(aggregations)) 12 | for i, v := range aggregations { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //SumAggregation constant value for the sum aggregation type 20 | SumAggregation = Aggregation("SUM") 21 | //MeanAggregation constant value for the mean aggregation type 22 | MeanAggregation = Aggregation("MEAN") 23 | //MaxAggregation constant value for the max aggregation type 24 | MaxAggregation = Aggregation("MAX") 25 | //MinAggregation constant value for the min aggregation type 26 | MinAggregation = Aggregation("MIN") 27 | //Percentile25Aggregation constant value for the 25th percentile aggregation type 28 | Percentile25Aggregation = Aggregation("P25") 29 | //Percentile50Aggregation constant value for the 50th percentile aggregation type 30 | Percentile50Aggregation = Aggregation("P50") 31 | //Percentile75Aggregation constant value for the 75th percentile aggregation type 32 | Percentile75Aggregation = Aggregation("P75") 33 | //Percentile90Aggregation constant value for the 90th percentile aggregation type 34 | Percentile90Aggregation = Aggregation("P90") 35 | //Percentile95Aggregation constant value for the 95th percentile aggregation type 36 | Percentile95Aggregation = Aggregation("P95") 37 | //Percentile98Aggregation constant value for the 98th percentile aggregation type 38 | Percentile98Aggregation = Aggregation("P98") 39 | //Percentile99Aggregation constant value for the 99th percentile aggregation type 40 | Percentile99Aggregation = Aggregation("P99") 41 | //Percentile99_9Aggregation constant value for the 99.9th percentile aggregation type 42 | Percentile99_9Aggregation = Aggregation("P99_9") 43 | //Percentile99_99Aggregation constant value for the 99.99th percentile aggregation type 44 | Percentile99_99Aggregation = Aggregation("P99_99") 45 | //DistributionAggregation constant value for the distribution aggregation type 46 | DistributionAggregation = Aggregation("DISTRIBUTION") 47 | //DistinctCountAggregation constant value for the distinct count aggregation type 48 | DistinctCountAggregation = Aggregation("DISTINCT_COUNT") 49 | //SumPositiveAggregation constant value for the sum positive aggregation type 50 | SumPositiveAggregation = Aggregation("SUM_POSITIVE") 51 | ) 52 | 53 | // SupportedAggregations list of all supported Aggregation 54 | var SupportedAggregations = Aggregations{SumAggregation, MeanAggregation, MaxAggregation, MinAggregation, Percentile25Aggregation, Percentile50Aggregation, Percentile75Aggregation, Percentile90Aggregation, Percentile95Aggregation, Percentile98Aggregation, Percentile99Aggregation, Percentile99_9Aggregation, Percentile99_99Aggregation, DistributionAggregation, DistinctCountAggregation, SumPositiveAggregation} 55 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Instana Provider 2 | 3 | **Deprecated:** This project has been handed over to and is maintained under IBM's offical [Instana](https://github.com/instana) org. Please use the official IBM Instana Terraform provider [https://registry.terraform.io/providers/instana/instana/latest](https://registry.terraform.io/providers/instana/instana/latest). 4 | 5 | Terraform provider implementation of the Instana Web REST API. The provider can be used to configure different 6 | assents in Instana. The provider is aligned with the REST API and links to the endpoint is provided for each 7 | resource. 8 | 9 | **NOTE:** Starting with version 0.6.0 Terraform version 0.12.x or later is required. 10 | 11 | ## Supported Resources: 12 | 13 | * Application Settings 14 | * Application Configuration - `instana_application_config` 15 | * Application Alert Configuration - `instana_application_alert_config` 16 | * Global Application Alert Configuration - `instana_global_application_alert_config` 17 | * Event Settings 18 | * Custom Event Specification - `instana_custom_event_specification` 19 | * Alerting Channels - `instana_alerting_channel` 20 | * Alerting Config - `instana_alerting_config` 21 | * Settings 22 | * API Tokens - `instana_api_token` 23 | * Groups - `instana_rbac_group` 24 | * SLI Settings 25 | * SLI Config - `instana_sli_config` 26 | * Synthetic Settings 27 | * Synthetic Test - `instana_synthetic_test` 28 | * Website Monitoring 29 | * Website Monitoring Config - `instana_website_monitoring_config` 30 | * Website Alert Config - `instana_website_alert_config` 31 | * Custom Dashboard - `instana_custom_dashboard` 32 | 33 | ## Supported Data Source: 34 | 35 | * Event Settings 36 | * Alerting Channel - `instana_alerting_channel` 37 | * Builtin Event Specifications - `instana_builtin_event_spec` 38 | * Synthetic Settings 39 | * Synthetic Location - `instana_synthetic_location` 40 | 41 | ## Example Usage 42 | 43 | ```hcl 44 | provider "instana" { 45 | api_token = "secure-api-token" 46 | endpoint = "-.instana.io" 47 | tls_skip_verify = false 48 | } 49 | ``` 50 | 51 | ## Argument Reference 52 | 53 | * `api_token` - Required - The API token which is created in the Settings area of Instana for remote access through 54 | the REST API. You have to make sure that you assign the proper permissions for this token to configure the desired 55 | resources with this provider. E.g. when User Roles should be provisioned by terraform using this provider implementation 56 | then the permission 'Access role configuration' must be activated. (Defaults to the environment variable `INSTANA_API_TOKEN`). 57 | * `endpoint` - Required - The endpoint of the instana backend. For SaaS the endpoint URL has the pattern 58 | `-.instana.io`. For onPremise installation the endpoint URL depends on your local setup. (Defaults to the environment variable `INSTANA_ENDPOINT`). 59 | * `tls_skip_verify` - `Òptional` - Default `false` - If set to true, TLS verification will be skipped when calling Instana API 60 | 61 | ## Import support 62 | 63 | All resources of the terraform provider instana support resource import. 64 | -------------------------------------------------------------------------------- /docs/resources/rbac_group.md: -------------------------------------------------------------------------------- 1 | # RBAC Group 2 | 3 | Management of Groups for role based access control. 4 | 5 | API Documentation: 6 | 7 | The ID of the resource which is also used as unique identifier in Instana is auto generated! 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "instana_rbac_group" "example" { 13 | name = "test" 14 | 15 | permission_set { 16 | application_ids = ["app_id1", "app_id2"] 17 | kubernetes_cluster_uuids = ["k8s_cluster_id1", "k8s_cluster_id2"] 18 | kubernetes_namespaces_uuids = ["k8s_namespace_id1", "k8s_namespace_id2"] 19 | mobile_app_ids = ["mobile_app_id1", "mobile_app_id2"] 20 | website_ids = ["website_id1", "website_id2"] 21 | infra_dfq_filter = "infra_dfq" 22 | permissions = ["CAN_CONFIGURE_APPLICATIONS", "CAN_CONFIGURE_AGENTS"] 23 | } 24 | } 25 | ``` 26 | 27 | ## Argument Reference 28 | 29 | * `name` - Required - the name of the RBAC group 30 | * `permission_set` - Optional - resource block to describe the assigned permissions 31 | * `application_ids` - Optional - list of application ids which are permitted to the given group 32 | * `kubernetes_cluster_uuids` - Optional - list of Kubernetes Cluster UUIDs which are permitted to the given group 33 | * `kubernetes_namespaces_uuids` - Optional - list of Kubernetes Namespaces UUIDs which are permitted to the given 34 | group 35 | * `mobile_app_ids` - Optional - list of mobile app ids which are permitted to the given group 36 | * `website_ids` - Optional -list of website ids which are permitted to the given group 37 | * `infra_dfq_filter` - Optional - a dynamic focus query to restrict access to a limited set of infrastructure 38 | resources 39 | * `permissions` - Optional - the list of permissions granted to the given group. Allowed values 40 | are: 41 | * `CAN_CONFIGURE_APPLICATIONS` 42 | * `CAN_SEE_ON_PREM_LICENE_INFORMATION` 43 | * `CAN_CONFIGURE_EUM_APPLICATIONS` 44 | * `CAN_CONFIGURE_AGENTS` 45 | * `CAN_VIEW_TRACE_DETAILS` 46 | * `CAN_VIEW_LOGS` 47 | * `CAN_CONFIGURE_SESSION_SETTINGS` 48 | * `CAN_CONFIGURE_INTEGRATIONS` 49 | * `CAN_CONFIGURE_GLOBAL_ALERT_CONFIGS` 50 | * `CAN_CONFIGURE_GLOBAL_ALERT_PAYLOAD` 51 | * `CAN_CONFIGURE_MOBILE_APP_MONITORING` 52 | * `CAN_CONFIGURE_API_TOKENS` 53 | * `CAN_CONFIGURE_SERVICE_LEVEL_INDICATORS` 54 | * `CAN_CONFIGURE_AUTHENTICATION_METHODS` 55 | * `CAN_CONFIGURE_RELEASES` 56 | * `CAN_VIEW_AUDIT_LOG` 57 | * `CAN_CONFIGURE_CUSTOM_ALERTS` 58 | * `CAN_CONFIGURE_AGENT_RUN_MODE` 59 | * `CAN_CONFIGURE_SERVICE_MAPPING` 60 | * `CAN_SEE_USAGE_INFORMATION` 61 | * `CAN_EDIT_ALL_ACCESSIBLE_CUSTOM_DASHBOARDS` 62 | * `CAN_CONFIGURE_USERS` 63 | * `CAN_INSTALL_NEW_AGENTS` 64 | * `CAN_CONFIGURE_TEAMS` 65 | * `CAN_CREATE_PUBLIC_CUSTOM_DASHBOARDS` 66 | * `CAN_CONFIGURE_LOG_MANAGEMENT` 67 | * `CAN_VIEW_ACCOUNT_AND_BILLING_INFORMATION` 68 | 69 | ## Import 70 | 71 | RBAC Groups can be imported using the `id` of the group, e.g.: 72 | 73 | ``` 74 | $ terraform import instana_rbac_group.my_group 60845e4e5e6b9cf8fc2868da 75 | ``` 76 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gessnerfl/terraform-provider-instana 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.4 6 | 7 | require ( 8 | github.com/alecthomas/participle v0.7.1 9 | github.com/google/go-cmp v0.7.0 10 | github.com/gorilla/mux v1.8.1 11 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 12 | github.com/rs/xid v1.6.0 13 | github.com/stretchr/testify v1.9.0 14 | go.uber.org/mock v0.5.2 15 | gopkg.in/resty.v1 v1.12.0 16 | ) 17 | 18 | require ( 19 | github.com/ProtonMail/go-crypto v1.1.6 // indirect 20 | github.com/agext/levenshtein v1.2.3 // indirect 21 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 22 | github.com/cloudflare/circl v1.6.0 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/fatih/color v1.18.0 // indirect 25 | github.com/golang/protobuf v1.5.4 // indirect 26 | github.com/hashicorp/errwrap v1.1.0 // indirect 27 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 28 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 29 | github.com/hashicorp/go-cty v1.5.0 // indirect 30 | github.com/hashicorp/go-hclog v1.6.3 // indirect 31 | github.com/hashicorp/go-multierror v1.1.1 // indirect 32 | github.com/hashicorp/go-plugin v1.6.3 // indirect 33 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 34 | github.com/hashicorp/go-uuid v1.0.3 // indirect 35 | github.com/hashicorp/go-version v1.7.0 // indirect 36 | github.com/hashicorp/hc-install v0.9.2 // indirect 37 | github.com/hashicorp/hcl/v2 v2.24.0 // indirect 38 | github.com/hashicorp/logutils v1.0.0 // indirect 39 | github.com/hashicorp/terraform-exec v0.23.0 // indirect 40 | github.com/hashicorp/terraform-json v0.25.0 // indirect 41 | github.com/hashicorp/terraform-plugin-go v0.28.0 // indirect 42 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 43 | github.com/hashicorp/terraform-registry-address v0.3.0 // indirect 44 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 45 | github.com/hashicorp/yamux v0.1.2 // indirect 46 | github.com/mattn/go-colorable v0.1.14 // indirect 47 | github.com/mattn/go-isatty v0.0.20 // indirect 48 | github.com/mitchellh/copystructure v1.2.0 // indirect 49 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 50 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 51 | github.com/mitchellh/mapstructure v1.5.0 // indirect 52 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 53 | github.com/oklog/run v1.2.0 // indirect 54 | github.com/pmezard/go-difflib v1.0.0 // indirect 55 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 56 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 57 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 58 | github.com/zclconf/go-cty v1.16.3 // indirect 59 | golang.org/x/crypto v0.39.0 // indirect 60 | golang.org/x/mod v0.25.0 // indirect 61 | golang.org/x/net v0.41.0 // indirect 62 | golang.org/x/sync v0.15.0 // indirect 63 | golang.org/x/sys v0.33.0 // indirect 64 | golang.org/x/text v0.26.0 // indirect 65 | golang.org/x/tools v0.34.0 // indirect 66 | google.golang.org/appengine v1.6.8 // indirect 67 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect 68 | google.golang.org/grpc v1.73.0 // indirect 69 | google.golang.org/protobuf v1.36.6 // indirect 70 | gopkg.in/yaml.v3 v3.0.1 // indirect 71 | ) 72 | -------------------------------------------------------------------------------- /instana/restapi/threshold.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // ThresholdOperator custom type for the operator of a threshold data structure 4 | type ThresholdOperator string 5 | 6 | // ThresholdOperators custom type for a slice of LogLevel 7 | type ThresholdOperators []ThresholdOperator 8 | 9 | // ToStringSlice Returns the corresponding string representations 10 | func (operators ThresholdOperators) ToStringSlice() []string { 11 | result := make([]string, len(operators)) 12 | for i, v := range operators { 13 | result[i] = string(v) 14 | } 15 | return result 16 | } 17 | 18 | const ( 19 | //ThresholdOperatorGreaterThan constant value for the threshold operator > (greater than) 20 | ThresholdOperatorGreaterThan = ThresholdOperator(">") 21 | //ThresholdOperatorGreaterThanOrEqual constant value for the threshold operator >= (greater than or equal) 22 | ThresholdOperatorGreaterThanOrEqual = ThresholdOperator(">=") 23 | //ThresholdOperatorLessThan constant value for the threshold operator < (less than) 24 | ThresholdOperatorLessThan = ThresholdOperator("<") 25 | //ThresholdOperatorLessThanOrEqual constant value for the threshold operator <= (less than or equal) 26 | ThresholdOperatorLessThanOrEqual = ThresholdOperator("<=") 27 | ) 28 | 29 | // SupportedThresholdOperators list of all supported ThresholdOperator 30 | var SupportedThresholdOperators = ThresholdOperators{ThresholdOperatorGreaterThan, ThresholdOperatorGreaterThanOrEqual, ThresholdOperatorLessThan, ThresholdOperatorLessThanOrEqual} 31 | 32 | // ThresholdSeasonality custom type for the seasonality of a threshold data structure 33 | type ThresholdSeasonality string 34 | 35 | // ThresholdSeasonalities custom type for a slice of ThresholdSeasonality 36 | type ThresholdSeasonalities []ThresholdSeasonality 37 | 38 | // ToStringSlice Returns the corresponding string representations 39 | func (seasonalities ThresholdSeasonalities) ToStringSlice() []string { 40 | result := make([]string, len(seasonalities)) 41 | for i, v := range seasonalities { 42 | result[i] = string(v) 43 | } 44 | return result 45 | } 46 | 47 | const ( 48 | //ThresholdSeasonalityWeekly constant value for the threshold seasonality type weekly 49 | ThresholdSeasonalityWeekly = ThresholdSeasonality("WEEKLY") 50 | //ThresholdSeasonalityDaily constant value for the threshold seasonality type daily 51 | ThresholdSeasonalityDaily = ThresholdSeasonality("DAILY") 52 | ) 53 | 54 | // SupportedThresholdSeasonalities list of all supported ThresholdSeasonality 55 | var SupportedThresholdSeasonalities = ThresholdSeasonalities{ThresholdSeasonalityWeekly, ThresholdSeasonalityDaily} 56 | 57 | // Threshold custom data structure representing the threshold type of the instana API 58 | type Threshold struct { 59 | Type string `json:"type"` 60 | Operator ThresholdOperator `json:"operator"` 61 | Baseline *[][]float64 `json:"baseline"` 62 | DeviationFactor *float32 `json:"deviationFactor"` 63 | LastUpdated *int64 `json:"lastUpdated"` 64 | Seasonality *ThresholdSeasonality `json:"seasonality"` 65 | Value *float64 `json:"value"` 66 | } 67 | 68 | // TimeThreshold custom data structure representing the time threshold type of the instana API 69 | type TimeThreshold struct { 70 | Type string `json:"type"` 71 | TimeWindow int64 `json:"timeWindow"` 72 | Requests *int32 `json:"requests"` 73 | Violations *int32 `json:"violations"` 74 | } 75 | -------------------------------------------------------------------------------- /testutils/test-server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDTDSOYveeh9P/S 3 | VrwLoVGwP7wGwncUqpEmrSVrgopnVEn02/tKC+iiao2P6aZXemubNBdYjxoMZaFm 4 | 4Fl7pT57LjzJB5pojFD2AbYZjfU94iWKsH824IxhMWduRkc+wl3lUC9u73kSC32C 5 | 8g5GohJLA5w+XT9EPmMBqQXdSlBd9DcTjyk4yD2N89ZOvNPC18M2J1PwjzxWY4AN 6 | yEapifkaO/FXNKHPToHtzQp+2nHaNSg9mnPkMcmZ5n5kZrgBkJbC1G4mJsJnWWzi 7 | Jaahx8k/HwjDMtUc3qVmXBFQ8TWWruOXfCQl70mp18pNOgNrGCKpVJiPyPYK9HTQ 8 | 0YNMbjohl5lt3sdtSvfSElMHgg2WEm6v+BVBmi9MmcLuuXt1gsuy5kIlsl5SlyWD 9 | jCd/1TGjgHVDRdBm06pOtM4d1If4sAv1cDhwe0FUvBICFfcZ18T4LTuNKj3pdSkT 10 | eKNZSR/9i7Nj9xGJTmttTpcO3GxZkWEUA1U1DgJCq7jKAs/cwurMjPqkrkfes7kb 11 | NoJJ83unvCd+fgZ/OQLGHwB2GKC24zDNXTLIt1ssUBS+ZCZbZYb4/CiscgUsOFuI 12 | LbUPPeeFY2SYpshk+dbnvZJn4A7h004DIcQ3KXo/dL48eSHq52iNcHkZFsYI2UpD 13 | YYwlDZbCgo1Y/FvWUi3kV7v4iEeFxQIDAQABAoICAGE3MCpvb38ex6gv47bHvEUf 14 | VpEWipuuZeYW46G+I9qs7NQpcHsPOT13L3YW/lRVQ7PeLtLQOAN13P7yYMWfvEQp 15 | bD59jhaSa8ilySfESC8VOI3F9xNkI8WxaB9XqAG0rmOup+rFzFMPj+T9cq3+gcak 16 | BxiDA12AD0L4hIcGcDaXA7eyvh41voTgyNrECBc8I+OSAgxRLk3sz1OJ6WK291Oq 17 | QOUWjWJcm2Taifcc0EzfJ4YY20FF3cNSdO3yktfFrmxYRdS1Dd9lRc514WesC3tT 18 | 5Ell5NzDghTAXAmY0kJylMt2l4AR1TCzAbDJBEIUgALXwN6ahxtMt3Joz74pGAUC 19 | 3k+49dtKlMmeBZIVhRp5/Cqngm6dw3AYzEC3ahtMq4P8HiDMZnBvNkynV1UWyQ/A 20 | acHMD3YKU87ORz3KPGJ4/dVwDxwdM9faUl9CrllRBOBLSDXfftYN0S/duam8spSq 21 | M36pOeetq1CjAP8V4jYYF8buwVD62AxD38eripiR5wMjnZ+kpg7Np3bXFWkoUKlF 22 | i0oXqRV+Bq1HLgnRTJ4bYG01EMewg6u5xhv/mhIN98nFDDyVQcYWHDts3bJZELa0 23 | SU8kpnzRcnWmFHX5fYa1ZOmFAlTfMnBCzjrRVWMgCW2A/DVSlLBOXdsfvuq2qG+E 24 | NYi2Ylgh3JasB/QelE0RAoIBAQDzWPmdpSlOl1IaCGltKETFFNekiUaMTr1wJhv6 25 | BGxdEeMlTtcD7TWPL+QaM8P+1VC7H1xg+I+IJg9FI4NxEefMYnH9yAYkzaPYgocf 26 | NSsHPUKbGnWD6PDf4pMnPMvKx6QyKjcsC8VFy3ErlGJc3hmi1ylTUEd7t/Z4DaeB 27 | 3BbdIokLHnQBqs5FtuC4yctPBwnNMNO3n07wWjenotzM4t2AabNJV+3hVDPAvbcZ 28 | vbvrlsGHy9huJNlJaaS789jup27DrSCj3zRzBB8l2CvKsw7IJQozQCD56tQWbDp6 29 | jbljxP/9WFO3srX8kCjeMHeuh3in9JvHuBx+Kv0SL091gcT7AoIBAQDeBkq92Nvh 30 | Gp8V1+WGxlBk3psAF66pucWjJdD8olRcLb9Fdf8MRezZf651RsSjh3Ly+uhe+ysq 31 | dkv4HL7xTKSI4znbWfVXo7a9JK8xZJU8Nb7dH7EcGUqQZbGukWYxelwoljom11q/ 32 | S/pAXznSWGI9dNw3Qlxa9O05b0S8S1sCUCzkzO6cfJtNw8ayqzrJRy7+bTkmPHj7 33 | yuPlnShpxlWajlBFMVirzSJN23laB1L8E6HSreKN8pzhsjTG65X6A/Lll+S19vuc 34 | XoLHeFOBkfXST+OC66orSsS0hprpt0N66qMsJrix/qQ2U8kxA96TtkMQdDLX8S34 35 | mxFCpNg8UmQ/AoIBAQCY9H8JOKvrCy/+zvS9OMdo/P3CHMrsG/30hO+myar3IOwe 36 | Ih0CCdJVauu8EHeMMLZqt36QMQu+rvWAJp9Srcd97w7pThWWgm8PApQ96GRgS4AX 37 | D513yS3F+5s02YDat3ucUG6XT9qyUPqUQtNEvhFtCkKebGJSAEJ6GVMFS7pPSorL 38 | I1wVRDAdH6LGY4hX5vuhLqSU0RyD9KDwzGxul9ZUBZw8aHlL3WoeJuD03A5HOV3x 39 | pg4mxSCShUWXIeFjuHflpGdUuVI1TQzq86Iq6TtMUAPGTPOtQo4GfgaPy48Qh93D 40 | dwY/+6Np2Y7mgZ5oZBxRrW1RLocyQoEJMhWGrYeLAoIBAQDTMaD/Hb1qTuxs1sVa 41 | gmIO0UEXwW8/u5qoKly1M6STWmDLLgbCIfG+opnMrNh3xWwufUV0s+7HoWd6R73J 42 | wmHwZFSwts2N5Aj7ul172nWqpGU96duLIvOuj02JBH31KrNnWB9QuyFzPk551Rsn 43 | XzNzyil/1vwK+Vh+fb0gb33OoX1S1y1SZaVFP6lOY1q0A6ZMFpF6MaPTv5cw32GF 44 | 3EepkR4B84KpSaQL821nmffyGKqhwQ30gIeCmQVjAs3S2NbAVtAT1zBX+f7kxyyH 45 | 39O5joyYCJwCOkkKf8/z6YOi9HbqlJqzlcmWc/S5Tzj1wTXh/OYwSxazmK2+exfF 46 | qYn5AoIBAEK2XdhpiRs/plUPfVS+2DG6zRCqYJy1xv9LQ54t09LYRaEauey9arNE 47 | fi1AgFQl4AcV4Kwtvfvf0KKkeWgXp5+Z2LsyRP1FHnW3a4zHgIRQu4hWi0SNzZ/v 48 | o8h/5NQyu3hui8iUSYBd987tQqzc35GtFD4fLH5ogztDssg7w4AjgHiC+6M2FRuR 49 | NY7t/wZqs2QZD78bDMgL9XmUm5NIeLkLmOkzghDlYLG1o6w2XkUtM9mR/SY7b8Ey 50 | R6SC5Xei2Ppck/yVZ0eOE9Dfdv71NLYMYy6DNAO4sYJyjIxXsd6p4rzvIsDeJhSG 51 | SXzNdtJfwDNF6lUs8UdDW5IvahBddwM= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /instana/restapi/custom-event-specficiations-api.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | const ( 4 | //EventSpecificationBasePath path to Event Specification settings of Instana RESTful API 5 | EventSpecificationBasePath = EventSettingsBasePath + "/event-specifications" 6 | //CustomEventSpecificationResourcePath path to Custom Event Specification settings resource of Instana RESTful API 7 | CustomEventSpecificationResourcePath = EventSpecificationBasePath + "/custom" 8 | ) 9 | 10 | const ( 11 | //SystemRuleType const for rule type of System 12 | SystemRuleType = "system" 13 | //ThresholdRuleType const for rule type of Threshold 14 | ThresholdRuleType = "threshold" 15 | //EntityVerificationRuleType const for rule type of Entity Verification 16 | EntityVerificationRuleType = "entity_verification" 17 | //EntityCountRuleType const for rule type of Entity Count 18 | EntityCountRuleType = "entity_count" 19 | //EntityCountVerificationRuleType const for rule type of Entity Count Verification 20 | EntityCountVerificationRuleType = "entity_count_verification" 21 | //HostAvailabilityRuleType const for rule type of Host Availability 22 | HostAvailabilityRuleType = "host_availability" 23 | ) 24 | 25 | // MetricPattern representation of a metric pattern for dynamic built-in metrics for CustomEventSpecification 26 | type MetricPattern struct { 27 | Prefix string `json:"prefix"` 28 | Postfix *string `json:"postfix"` 29 | Placeholder *string `json:"placeholder"` 30 | Operator string `json:"operator"` 31 | } 32 | 33 | // RuleSpecification representation of a rule specification for a CustomEventSpecification 34 | type RuleSpecification struct { 35 | //Common Fields 36 | DType string `json:"ruleType"` 37 | Severity int `json:"severity"` 38 | 39 | //System Rule fields 40 | SystemRuleID *string `json:"systemRuleId"` 41 | 42 | //Threshold Rule fields 43 | MetricName *string `json:"metricName"` 44 | Rollup *int `json:"rollup"` 45 | Window *int `json:"window"` 46 | Aggregation *string `json:"aggregation"` 47 | ConditionOperator *string `json:"conditionOperator"` 48 | ConditionValue *float64 `json:"conditionValue"` 49 | MetricPattern *MetricPattern `json:"metricPattern"` 50 | 51 | //Entity Verification Rule 52 | MatchingEntityType *string `json:"matchingEntityType"` 53 | MatchingOperator *string `json:"matchingOperator"` 54 | MatchingEntityLabel *string `json:"matchingEntityLabel"` 55 | OfflineDuration *int `json:"offlineDuration"` 56 | CloseAfter *int `json:"closeAfter"` 57 | TagFilter *TagFilter `jsom:"tagFilter"` 58 | } 59 | 60 | // CustomEventSpecification is the representation of a custom event specification in Instana 61 | type CustomEventSpecification struct { 62 | ID string `json:"id"` 63 | Name string `json:"name"` 64 | EntityType string `json:"entityType"` 65 | Query *string `json:"query"` 66 | Triggering bool `json:"triggering"` 67 | Description *string `json:"description"` 68 | ExpirationTime *int `json:"expirationTime"` 69 | Enabled bool `json:"enabled"` 70 | RuleLogicalOperator string `json:"ruleLogicalOperator"` 71 | Rules []RuleSpecification `json:"rules"` 72 | } 73 | 74 | // GetIDForResourcePath implementation of the interface InstanaDataObject 75 | func (spec *CustomEventSpecification) GetIDForResourcePath() string { 76 | return spec.ID 77 | } 78 | -------------------------------------------------------------------------------- /docs/resources/application_config.md: -------------------------------------------------------------------------------- 1 | # Application Configuration Resource 2 | 3 | Management of application configurations (definition of application perspectives). 4 | 5 | API Documentation: 6 | 7 | The ID of the resource which is also used as unique identifier in Instana is auto generated! 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "instana_application_config" "example" { 13 | label = "label" 14 | scope = "INCLUDE_ALL_DOWNSTREAM" #Optional, default = INCLUDE_NO_DOWNSTREAM 15 | boundary_scope = "INBOUND" #Optional, default = INBOUND 16 | tag_filter = "agent.tag:stage EQUALS 'test' OR aws.ec2.tag:stage EQUALS 'test' OR call.tag:stage@na EQUALS 'test'" 17 | } 18 | ``` 19 | 20 | ## Argument Reference 21 | 22 | * `label` - Required - The name/label of the application perspective 23 | * `scope` - Optional - The scope of the application perspective. Default value: `INCLUDE_NO_DOWNSTREAM`. Allowed valued: `INCLUDE_ALL_DOWNSTREAM`, `INCLUDE_NO_DOWNSTREAM`, `INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING` 24 | * `boundary_scope` - Optional - The boundary scope of the application perspective. Default value `DEFAULT`. Allowed values: `INBOUND`, `ALL`, `DEFAULT` 25 | * `tag_filter` - Optional - specifies which entities should be included in the application; one of match_specification and tag_filter must be provided 26 | 27 | ### Tag Filter 28 | The **tag_filter** defines which entities should be included into the application. It supports: 29 | 30 | * logical AND and/or logical OR conjunctions whereas AND has higher precedence then OR 31 | * comparison operators EQUALS, NOT_EQUAL, CONTAINS | NOT_CONTAIN, STARTS_WITH, ENDS_WITH, NOT_STARTS_WITH, NOT_ENDS_WITH, GREATER_OR_EQUAL_THAN, LESS_OR_EQUAL_THAN, LESS_THAN, GREATER_THAN 32 | * unary operators IS_EMPTY, NOT_EMPTY, IS_BLANK, NOT_BLANK. 33 | 34 | The **tag_filter** is defined by the following eBNF: 35 | 36 | ```plain 37 | tag_filter := logical_or 38 | logical_or := logical_and OR logical_or | logical_and 39 | logical_and := primary_expression AND logical_and | bracket_expression 40 | bracket_expression := ( logical_or ) | primary_expression 41 | primary_expression := comparison | unary_operator_expression 42 | comparison := identifier comparison_operator value | identifier@entity_origin comparison_operator value | identifier:tag_key comparison_operator value | identifier:tag_key@entity_origin comparison_operator value 43 | comparison_operator := EQUALS | NOT_EQUAL | CONTAINS | NOT_CONTAIN | STARTS_WITH | ENDS_WITH | NOT_STARTS_WITH | NOT_ENDS_WITH | GREATER_OR_EQUAL_THAN | LESS_OR_EQUAL_THAN | LESS_THAN | GREATER_THAN 44 | unary_operator_expression := identifier unary_operator | identifier@entity_origin unary_operator 45 | unary_operator := IS_EMPTY | NOT_EMPTY | IS_BLANK | NOT_BLANK 46 | tag_key := identifier | string_value 47 | entity_origin := src | dest | na 48 | value := string_value | number_value | boolean_value 49 | string_value := "'" "'" 50 | number_value := (+-)?[0-9]+ 51 | boolean_value := TRUE | FALSE 52 | identifier := [a-zA-Z_][\.a-zA-Z0-9_\-/]* 53 | 54 | ``` 55 | 56 | #### Examples: 57 | 58 | **Basic** 59 | 60 | ```plain 61 | entity.service.name EQUALS 'my-service' AND entity.tag:stage EQUALS 'PROD' AND call.http.status EQUALS 404 62 | ``` 63 | 64 | **Calls filtered on source** 65 | 66 | ```plain 67 | entity.service.name@src EQUALS 'my-service' AND entity.tag:stage@src EQUALS PROD 68 | ``` 69 | 70 | ## Import 71 | 72 | Application Configs can be imported using the `id`, e.g.: 73 | 74 | ``` 75 | $ terraform import instana_application_config.my_app_config 60845e4e5e6b9cf8fc2868da 76 | ``` 77 | -------------------------------------------------------------------------------- /instana/data-source-synthetic-location.go: -------------------------------------------------------------------------------- 1 | package instana 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gessnerfl/terraform-provider-instana/tfutils" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | 9 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 12 | ) 13 | 14 | // NewSyntheticLocationDataSource creates a new DataSource for Synthetic Locations 15 | func NewSyntheticLocationDataSource() DataSource { 16 | return &syntheticLocationDataSource{} 17 | } 18 | 19 | const ( 20 | //SyntheticLocationFieldLabel constant value for the schema field label 21 | SyntheticLocationFieldLabel = "label" 22 | //SyntheticLocationFieldDescription constant value for the computed schema field description 23 | SyntheticLocationFieldDescription = "description" 24 | //SyntheticLocationFieldLocationType constant value for the schema field location_type 25 | SyntheticLocationFieldLocationType = "location_type" 26 | //DataSourceSyntheticLocation the name of the terraform-provider-instana data sourcefor synthetic location specifications 27 | DataSourceSyntheticLocation = "instana_synthetic_location" 28 | ) 29 | 30 | type syntheticLocationDataSource struct{} 31 | 32 | // CreateResource creates the resource handle Synthetic Locations 33 | func (ds *syntheticLocationDataSource) CreateResource() *schema.Resource { 34 | return &schema.Resource{ 35 | ReadContext: ds.read, 36 | Schema: map[string]*schema.Schema{ 37 | SyntheticLocationFieldLabel: { 38 | Type: schema.TypeString, 39 | Optional: true, 40 | Description: "Friendly name of the Synthetic Location", 41 | ValidateFunc: validation.StringLenBetween(0, 512), 42 | }, 43 | SyntheticLocationFieldDescription: { 44 | Type: schema.TypeString, 45 | Optional: true, 46 | Description: "The description of the Synthetic location", 47 | ValidateFunc: validation.StringLenBetween(0, 512), 48 | }, 49 | SyntheticLocationFieldLocationType: { 50 | Type: schema.TypeString, 51 | Optional: true, 52 | Description: "Indicates if the location is public or private", 53 | ValidateFunc: validation.StringInSlice([]string{"Public", "Private"}, true), 54 | }, 55 | }, 56 | } 57 | } 58 | 59 | func (ds *syntheticLocationDataSource) read(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 60 | providerMeta := meta.(*ProviderMeta) 61 | instanaAPI := providerMeta.InstanaAPI 62 | 63 | label := d.Get(SyntheticLocationFieldLabel).(string) 64 | locationType := d.Get(SyntheticLocationFieldLocationType).(string) 65 | 66 | data, err := instanaAPI.SyntheticLocation().GetAll() 67 | if err != nil { 68 | return diag.FromErr(err) 69 | } 70 | 71 | syntheticLocation, err := ds.findSyntheticLocations(label, locationType, data) 72 | if err != nil { 73 | return diag.FromErr(err) 74 | } 75 | 76 | err = ds.updateState(d, syntheticLocation) 77 | if err != nil { 78 | return diag.FromErr(err) 79 | } 80 | return nil 81 | } 82 | 83 | func (ds *syntheticLocationDataSource) findSyntheticLocations(label string, locationType string, data *[]*restapi.SyntheticLocation) (*restapi.SyntheticLocation, error) { 84 | for _, e := range *data { 85 | if e.Label == label && e.LocationType == locationType { 86 | return e, nil 87 | } 88 | } 89 | return nil, fmt.Errorf("no synthetic location found") 90 | } 91 | 92 | func (ds *syntheticLocationDataSource) updateState(d *schema.ResourceData, syntheticLocation *restapi.SyntheticLocation) error { 93 | d.SetId(syntheticLocation.ID) 94 | return tfutils.UpdateState(d, map[string]interface{}{ 95 | SyntheticLocationFieldLabel: syntheticLocation.Label, 96 | SyntheticLocationFieldDescription: syntheticLocation.Description, 97 | SyntheticLocationFieldLocationType: syntheticLocation.LocationType, 98 | }) 99 | } 100 | -------------------------------------------------------------------------------- /instana/restapi/default-rest-resource_create-put-update-put_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "testing" 8 | 9 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 10 | "github.com/gessnerfl/terraform-provider-instana/mocks" 11 | "github.com/stretchr/testify/assert" 12 | "go.uber.org/mock/gomock" 13 | ) 14 | 15 | func TestSuccessfulCreateOrUpdateOfTestObjectThroughCreatePUTUpdatePUTRestResource(t *testing.T) { 16 | executeCreateOrUpdateOperationThroughCreatePUTUpdatePUTRestResourceTest(t, func(t *testing.T, resourceFunc createUpdateFunc, client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 17 | testObject := makeTestObject() 18 | serializedJSON, _ := json.Marshal(testObject) 19 | 20 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(serializedJSON, nil) 21 | unmarshaller.EXPECT().Unmarshal(serializedJSON).Times(1).Return(testObject, nil) 22 | 23 | result, err := resourceFunc(testObject) 24 | 25 | assert.NoError(t, err) 26 | assert.Equal(t, testObject, result) 27 | }) 28 | } 29 | 30 | func TestShouldFailToCreateOrUpdateTestObjectThroughCreatePUTUpdatePUTRestResourceWhenErrorIsReturnedFromRestClient(t *testing.T) { 31 | executeCreateOrUpdateOperationThroughCreatePUTUpdatePUTRestResourceTest(t, func(t *testing.T, resourceFunc createUpdateFunc, client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 32 | testObject := makeTestObject() 33 | 34 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(nil, errors.New("Error during test")) 35 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(0) 36 | 37 | _, err := resourceFunc(testObject) 38 | 39 | assert.Error(t, err) 40 | }) 41 | } 42 | 43 | func TestShouldFailToCreateOrUpdateTestObjectThroughCreatePUTUpdatePUTRestResourceWhenResponseCannotBeUnmarshalled(t *testing.T) { 44 | executeCreateOrUpdateOperationThroughCreatePUTUpdatePUTRestResourceTest(t, func(t *testing.T, resourceFunc createUpdateFunc, client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 45 | testObject := makeTestObject() 46 | response := []byte("invalid response") 47 | expectedError := errors.New("test") 48 | 49 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(response, nil) 50 | unmarshaller.EXPECT().Unmarshal(response).Times(1).Return(nil, expectedError) 51 | 52 | _, err := resourceFunc(testObject) 53 | 54 | assert.Error(t, err) 55 | assert.Equal(t, expectedError, err) 56 | }) 57 | } 58 | 59 | type createUpdateFunc func(data *testObject) (*testObject, error) 60 | type createPutUpdatePutContext struct { 61 | operation string 62 | resourceFuncFactory func(RestResource[*testObject]) createUpdateFunc 63 | } 64 | 65 | func executeCreateOrUpdateOperationThroughCreatePUTUpdatePUTRestResourceTest(t *testing.T, testFunction func(t *testing.T, resourceFunc createUpdateFunc, client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject])) { 66 | contexts := []createPutUpdatePutContext{ 67 | {operation: "Create", resourceFuncFactory: func(sut RestResource[*testObject]) createUpdateFunc { return sut.Create }}, 68 | {operation: "Update", resourceFuncFactory: func(sut RestResource[*testObject]) createUpdateFunc { return sut.Update }}, 69 | } 70 | 71 | caller := getCallerName() 72 | for _, context := range contexts { 73 | fullName := fmt.Sprintf("%s[%s]", caller, context.operation) 74 | t.Run(fullName, func(t *testing.T) { 75 | ctrl := gomock.NewController(t) 76 | defer ctrl.Finish() 77 | client := mocks.NewMockRestClient(ctrl) 78 | unmarshaller := mocks.NewMockJSONUnmarshaller[*testObject](ctrl) 79 | 80 | sut := NewCreatePUTUpdatePUTRestResource[*testObject](testObjectResourcePath, unmarshaller, client) 81 | 82 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 83 | testFunction(t, context.resourceFuncFactory(sut), client, unmarshaller) 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /instana/tagfilter/tag-filter-to-instana-api-model.go: -------------------------------------------------------------------------------- 1 | package tagfilter 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 6 | ) 7 | 8 | // ToAPIModel Implementation of the mapping form filter expression model to the Instana API model 9 | func (m *tagFilterMapper) ToAPIModel(input *FilterExpression) *restapi.TagFilter { 10 | return m.mapLogicalOrToAPIModel(input.Expression) 11 | } 12 | 13 | func (m *tagFilterMapper) mapLogicalOrToAPIModel(input *LogicalOrExpression) *restapi.TagFilter { 14 | left := m.mapLogicalAndToAPIModel(input.Left) 15 | if input.Operator != nil { 16 | right := m.mapLogicalOrToAPIModel(input.Right) 17 | leftElements := m.unwrapExpressionElements(left, restapi.LogicalOr) 18 | rightElements := m.unwrapExpressionElements(right, restapi.LogicalOr) 19 | return restapi.NewLogicalOrTagFilter(append(leftElements, rightElements...)) 20 | } 21 | return left 22 | } 23 | 24 | func (m *tagFilterMapper) mapLogicalAndToAPIModel(input *LogicalAndExpression) *restapi.TagFilter { 25 | left := m.mapBracketExpressionToAPIModel(input.Left) 26 | if input.Operator != nil { 27 | right := m.mapLogicalAndToAPIModel(input.Right) 28 | leftElements := m.unwrapExpressionElements(left, restapi.LogicalAnd) 29 | rightElements := m.unwrapExpressionElements(right, restapi.LogicalAnd) 30 | return restapi.NewLogicalAndTagFilter(append(leftElements, rightElements...)) 31 | } 32 | return left 33 | } 34 | 35 | func (m *tagFilterMapper) unwrapExpressionElements(element *restapi.TagFilter, operator restapi.LogicalOperatorType) []*restapi.TagFilter { 36 | if element.GetType() == restapi.TagFilterExpressionType && element.LogicalOperator != nil && *element.LogicalOperator == operator { 37 | return element.Elements 38 | } 39 | return []*restapi.TagFilter{element} 40 | } 41 | 42 | func (m *tagFilterMapper) mapBracketExpressionToAPIModel(input *BracketExpression) *restapi.TagFilter { 43 | if input.Bracket != nil { 44 | return m.mapLogicalOrToAPIModel(input.Bracket) 45 | } 46 | return m.mapPrimaryExpressionToAPIModel(input.Primary) 47 | } 48 | 49 | func (m *tagFilterMapper) mapPrimaryExpressionToAPIModel(input *PrimaryExpression) *restapi.TagFilter { 50 | if input.UnaryOperation != nil { 51 | return m.mapUnaryOperatorExpressionToAPIModel(input.UnaryOperation) 52 | } 53 | return m.mapComparisonExpressionToAPIModel(input.Comparison) 54 | } 55 | 56 | func (m *tagFilterMapper) mapUnaryOperatorExpressionToAPIModel(input *UnaryOperationExpression) *restapi.TagFilter { 57 | origin := EntityOriginDestination.TagFilterEntity() 58 | if input.Entity.Origin != nil { 59 | origin = SupportedEntityOrigins.ForKey(*input.Entity.Origin).TagFilterEntity() 60 | } 61 | return restapi.NewUnaryTagFilterWithTagKey(origin, input.Entity.Identifier, input.Entity.TagKey, restapi.ExpressionOperator(input.Operator)) 62 | } 63 | 64 | func (m *tagFilterMapper) mapComparisonExpressionToAPIModel(input *ComparisonExpression) *restapi.TagFilter { 65 | origin := EntityOriginDestination.TagFilterEntity() 66 | if input.Entity.Origin != nil { 67 | origin = SupportedEntityOrigins.ForKey(*input.Entity.Origin).TagFilterEntity() 68 | } 69 | if input.Entity.TagKey != nil { 70 | return restapi.NewTagTagFilter(origin, input.Entity.Identifier, restapi.ExpressionOperator(input.Operator), *input.Entity.TagKey, m.mapValueAsString(input)) 71 | } else if input.NumberValue != nil { 72 | return restapi.NewNumberTagFilter(origin, input.Entity.Identifier, restapi.ExpressionOperator(input.Operator), *input.NumberValue) 73 | } else if input.BooleanValue != nil { 74 | return restapi.NewBooleanTagFilter(origin, input.Entity.Identifier, restapi.ExpressionOperator(input.Operator), *input.BooleanValue) 75 | } 76 | return restapi.NewStringTagFilter(origin, input.Entity.Identifier, restapi.ExpressionOperator(input.Operator), *input.StringValue) 77 | } 78 | 79 | func (m *tagFilterMapper) mapValueAsString(input *ComparisonExpression) string { 80 | if input.NumberValue != nil { 81 | return fmt.Sprintf("%d", *input.NumberValue) 82 | } else if input.BooleanValue != nil { 83 | return fmt.Sprintf("%t", *input.BooleanValue) 84 | } 85 | return *input.StringValue 86 | } 87 | -------------------------------------------------------------------------------- /instana/restapi/operator.go: -------------------------------------------------------------------------------- 1 | package restapi 2 | 3 | // LogicalOperatorType custom type for logical operators 4 | type LogicalOperatorType string 5 | 6 | // LogicalOperatorTypes custom type for slice of logical operators 7 | type LogicalOperatorTypes []LogicalOperatorType 8 | 9 | const ( 10 | //LogicalAnd constant for logical AND conjunction 11 | LogicalAnd = LogicalOperatorType("AND") 12 | //LogicalOr constant for logical OR conjunction 13 | LogicalOr = LogicalOperatorType("OR") 14 | ) 15 | 16 | // SupportedLogicalOperatorTypes list of supported logical operators of Instana API 17 | var SupportedLogicalOperatorTypes = LogicalOperatorTypes{LogicalAnd, LogicalOr} 18 | 19 | // ExpressionOperator custom type for tag matcher operators 20 | type ExpressionOperator string 21 | 22 | // ExpressionOperators custom type representing a slice of ExpressionOperator 23 | type ExpressionOperators []ExpressionOperator 24 | 25 | // IsSupported check if the provided tag filter operator is supported 26 | func (operators ExpressionOperators) IsSupported(o ExpressionOperator) bool { 27 | for _, v := range operators { 28 | if v == o { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | // ToStringSlice Returns the corresponding string representations 36 | func (operators ExpressionOperators) ToStringSlice() []string { 37 | result := make([]string, len(operators)) 38 | for i, v := range operators { 39 | result[i] = string(v) 40 | } 41 | return result 42 | } 43 | 44 | const ( 45 | //EqualsOperator constant for the EQUALS operator 46 | EqualsOperator = ExpressionOperator("EQUALS") 47 | //NotEqualOperator constant for the NOT_EQUAL operator 48 | NotEqualOperator = ExpressionOperator("NOT_EQUAL") 49 | //ContainsOperator constant for the CONTAINS operator 50 | ContainsOperator = ExpressionOperator("CONTAINS") 51 | //NotContainOperator constant for the NOT_CONTAIN operator 52 | NotContainOperator = ExpressionOperator("NOT_CONTAIN") 53 | 54 | //IsEmptyOperator constant for the IS_EMPTY operator 55 | IsEmptyOperator = ExpressionOperator("IS_EMPTY") 56 | //NotEmptyOperator constant for the NOT_EMPTY operator 57 | NotEmptyOperator = ExpressionOperator("NOT_EMPTY") 58 | //IsBlankOperator constant for the IS_BLANK operator 59 | IsBlankOperator = ExpressionOperator("IS_BLANK") 60 | //NotBlankOperator constant for the NOT_BLANK operator 61 | NotBlankOperator = ExpressionOperator("NOT_BLANK") 62 | 63 | //StartsWithOperator constant for the STARTS_WITH operator 64 | StartsWithOperator = ExpressionOperator("STARTS_WITH") 65 | //EndsWithOperator constant for the ENDS_WITH operator 66 | EndsWithOperator = ExpressionOperator("ENDS_WITH") 67 | //NotStartsWithOperator constant for the NOT_STARTS_WITH operator 68 | NotStartsWithOperator = ExpressionOperator("NOT_STARTS_WITH") 69 | //NotEndsWithOperator constant for the NOT_ENDS_WITH operator 70 | NotEndsWithOperator = ExpressionOperator("NOT_ENDS_WITH") 71 | //GreaterOrEqualThanOperator constant for the GREATER_OR_EQUAL_THAN operator 72 | GreaterOrEqualThanOperator = ExpressionOperator("GREATER_OR_EQUAL_THAN") 73 | //LessOrEqualThanOperator constant for the LESS_OR_EQUAL_THAN operator 74 | LessOrEqualThanOperator = ExpressionOperator("LESS_OR_EQUAL_THAN") 75 | //GreaterThanOperator constant for the GREATER_THAN operator 76 | GreaterThanOperator = ExpressionOperator("GREATER_THAN") 77 | //LessThanOperator constant for the LESS_THAN operator 78 | LessThanOperator = ExpressionOperator("LESS_THAN") 79 | ) 80 | 81 | // SupportedComparisonOperators list of supported comparison operators of Instana API 82 | var SupportedComparisonOperators = ExpressionOperators{ 83 | EqualsOperator, 84 | NotEqualOperator, 85 | ContainsOperator, 86 | NotContainOperator, 87 | StartsWithOperator, 88 | EndsWithOperator, 89 | NotStartsWithOperator, 90 | NotEndsWithOperator, 91 | GreaterOrEqualThanOperator, 92 | LessOrEqualThanOperator, 93 | GreaterThanOperator, 94 | LessThanOperator, 95 | } 96 | 97 | // SupportedUnaryExpressionOperators list of supported unary expression operators of Instana API 98 | var SupportedUnaryExpressionOperators = ExpressionOperators{ 99 | IsEmptyOperator, 100 | NotEmptyOperator, 101 | IsBlankOperator, 102 | NotBlankOperator, 103 | } 104 | 105 | // SupportedExpressionOperators list of all supported operators of Instana API 106 | var SupportedExpressionOperators = append(SupportedComparisonOperators, SupportedUnaryExpressionOperators...) 107 | -------------------------------------------------------------------------------- /instana/restapi/groups-api_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | groupId = "group-id" 12 | groupName = "group-name" 13 | groupPermissionScopeID = "group-permission-scope-id" 14 | ) 15 | 16 | func TestShouldReturnSupportedInstanaPermissionsAsString(t *testing.T) { 17 | expectedResult := []string{ 18 | "CAN_CONFIGURE_APPLICATIONS", 19 | "CAN_SEE_ON_PREM_LICENE_INFORMATION", 20 | "CAN_CONFIGURE_EUM_APPLICATIONS", 21 | "CAN_CONFIGURE_AGENTS", 22 | "CAN_VIEW_TRACE_DETAILS", 23 | "CAN_VIEW_LOGS", 24 | "CAN_CONFIGURE_SESSION_SETTINGS", 25 | "CAN_CONFIGURE_INTEGRATIONS", 26 | "CAN_CONFIGURE_GLOBAL_ALERT_CONFIGS", 27 | "CAN_CONFIGURE_GLOBAL_ALERT_PAYLOAD", 28 | "CAN_CONFIGURE_MOBILE_APP_MONITORING", 29 | "CAN_CONFIGURE_API_TOKENS", 30 | "CAN_CONFIGURE_SERVICE_LEVEL_INDICATORS", 31 | "CAN_CONFIGURE_AUTHENTICATION_METHODS", 32 | "CAN_CONFIGURE_RELEASES", 33 | "CAN_VIEW_AUDIT_LOG", 34 | "CAN_CONFIGURE_CUSTOM_ALERTS", 35 | "CAN_CONFIGURE_AGENT_RUN_MODE", 36 | "CAN_CONFIGURE_SERVICE_MAPPING", 37 | "CAN_SEE_USAGE_INFORMATION", 38 | "CAN_EDIT_ALL_ACCESSIBLE_CUSTOM_DASHBOARDS", 39 | "CAN_CONFIGURE_USERS", 40 | "CAN_INSTALL_NEW_AGENTS", 41 | "CAN_CONFIGURE_TEAMS", 42 | "CAN_CREATE_PUBLIC_CUSTOM_DASHBOARDS", 43 | "CAN_CONFIGURE_LOG_MANAGEMENT", 44 | "CAN_VIEW_ACCOUNT_AND_BILLING_INFORMATION", 45 | } 46 | assert.Equal(t, expectedResult, SupportedInstanaPermissions.ToStringSlice()) 47 | } 48 | 49 | func TestShouldReturnIDOfGroupAsIDForAPIPaths(t *testing.T) { 50 | group := Group{ 51 | ID: groupId, 52 | Name: groupName, 53 | } 54 | 55 | assert.Equal(t, groupId, group.GetIDForResourcePath()) 56 | } 57 | 58 | func TestShouldReturnTrueWhenPermissionSetIsEmpty(t *testing.T) { 59 | p := APIPermissionSetWithRoles{} 60 | 61 | require.True(t, p.IsEmpty()) 62 | 63 | emptyScopeBinding := ScopeBinding{} 64 | p = APIPermissionSetWithRoles{ 65 | InfraDFQFilter: &emptyScopeBinding, 66 | } 67 | 68 | require.True(t, p.IsEmpty()) 69 | 70 | emptyScopeBindingSlice := make([]ScopeBinding, 0) 71 | p = APIPermissionSetWithRoles{ 72 | ApplicationIDs: emptyScopeBindingSlice, 73 | KubernetesNamespaceUIDs: emptyScopeBindingSlice, 74 | KubernetesClusterUUIDs: emptyScopeBindingSlice, 75 | InfraDFQFilter: &emptyScopeBinding, 76 | MobileAppIDs: emptyScopeBindingSlice, 77 | WebsiteIDs: emptyScopeBindingSlice, 78 | Permissions: make([]InstanaPermission, 0), 79 | } 80 | } 81 | 82 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenApplicationIDsAreSet(t *testing.T) { 83 | p := APIPermissionSetWithRoles{ 84 | ApplicationIDs: []ScopeBinding{{ScopeID: groupPermissionScopeID}}, 85 | } 86 | require.False(t, p.IsEmpty()) 87 | } 88 | 89 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenKubernetesClusterUUIDsAreSet(t *testing.T) { 90 | p := APIPermissionSetWithRoles{ 91 | KubernetesClusterUUIDs: []ScopeBinding{{ScopeID: groupPermissionScopeID}}, 92 | } 93 | require.False(t, p.IsEmpty()) 94 | } 95 | 96 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenKubernetesNamespaceUIDsAreSet(t *testing.T) { 97 | p := APIPermissionSetWithRoles{ 98 | KubernetesNamespaceUIDs: []ScopeBinding{{ScopeID: groupPermissionScopeID}}, 99 | } 100 | require.False(t, p.IsEmpty()) 101 | } 102 | 103 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenMobileAppIDsAreSet(t *testing.T) { 104 | p := APIPermissionSetWithRoles{ 105 | MobileAppIDs: []ScopeBinding{{ScopeID: groupPermissionScopeID}}, 106 | } 107 | require.False(t, p.IsEmpty()) 108 | } 109 | 110 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenWebsiteIDsAreSet(t *testing.T) { 111 | p := APIPermissionSetWithRoles{ 112 | WebsiteIDs: []ScopeBinding{{ScopeID: groupPermissionScopeID}}, 113 | } 114 | require.False(t, p.IsEmpty()) 115 | } 116 | 117 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenPermissionsAreSet(t *testing.T) { 118 | p := APIPermissionSetWithRoles{ 119 | Permissions: []InstanaPermission{PermissionCanConfigureApplications}, 120 | } 121 | require.False(t, p.IsEmpty()) 122 | } 123 | 124 | func TestShouldReturnFalseWhenPermissionSetIsNotEmptyWhenInfrastructureDFQIsSet(t *testing.T) { 125 | scopeBinding := ScopeBinding{ScopeID: groupPermissionScopeID} 126 | p := APIPermissionSetWithRoles{ 127 | InfraDFQFilter: &scopeBinding, 128 | } 129 | require.False(t, p.IsEmpty()) 130 | } 131 | -------------------------------------------------------------------------------- /instana/provider.go: -------------------------------------------------------------------------------- 1 | package instana 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | 9 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | // SchemaFieldAPIToken the name of the provider configuration option for the api token 14 | const SchemaFieldAPIToken = "api_token" 15 | 16 | // SchemaFieldEndpoint the name of the provider configuration option for the instana endpoint 17 | const SchemaFieldEndpoint = "endpoint" 18 | 19 | // SchemaFieldTlsSkipVerify flag to deactivate skip tls verification 20 | const SchemaFieldTlsSkipVerify = "tls_skip_verify" 21 | 22 | // ProviderMeta data structure for the metadata which is configured and provided to the resources by this provider 23 | type ProviderMeta struct { 24 | InstanaAPI restapi.InstanaAPI 25 | } 26 | 27 | // Provider interface implementation of hashicorp terraform provider 28 | func Provider() *schema.Provider { 29 | return &schema.Provider{ 30 | Schema: providerSchema(), 31 | ResourcesMap: providerResources(), 32 | DataSourcesMap: providerDataSources(), 33 | ConfigureContextFunc: providerConfigure, 34 | } 35 | } 36 | 37 | func providerSchema() map[string]*schema.Schema { 38 | return map[string]*schema.Schema{ 39 | SchemaFieldAPIToken: { 40 | Type: schema.TypeString, 41 | Sensitive: true, 42 | Required: true, 43 | DefaultFunc: schema.EnvDefaultFunc("INSTANA_API_TOKEN", nil), 44 | Description: "API token used to authenticate with the Instana Backend", 45 | Deprecated: "This project has been handed over to and is maintained under IBM's offical Instana org. Please use the official IBM Instana Terraform provider instana/instana (https://registry.terraform.io/providers/instana/instana/latest/) instead", 46 | }, 47 | SchemaFieldEndpoint: { 48 | Type: schema.TypeString, 49 | Required: true, 50 | DefaultFunc: schema.EnvDefaultFunc("INSTANA_ENDPOINT", nil), 51 | Description: "The DNS Name of the Instana Endpoint (eg. saas-eu-west-1.instana.io)", 52 | }, 53 | SchemaFieldTlsSkipVerify: { 54 | Type: schema.TypeBool, 55 | Optional: true, 56 | Default: false, 57 | Description: "If set to true, TLS verification will be skipped when calling Instana API", 58 | }, 59 | } 60 | } 61 | 62 | func providerResources() map[string]*schema.Resource { 63 | resources := make(map[string]*schema.Resource) 64 | bindResourceHandle(resources, NewAPITokenResourceHandle()) 65 | bindResourceHandle(resources, NewApplicationConfigResourceHandle()) 66 | bindResourceHandle(resources, NewApplicationAlertConfigResourceHandle()) 67 | bindResourceHandle(resources, NewGlobalApplicationAlertConfigResourceHandle()) 68 | bindResourceHandle(resources, NewCustomEventSpecificationResourceHandle()) 69 | bindResourceHandle(resources, NewAlertingChannelResourceHandle()) 70 | bindResourceHandle(resources, NewAlertingConfigResourceHandle()) 71 | bindResourceHandle(resources, NewSliConfigResourceHandle()) 72 | bindResourceHandle(resources, NewWebsiteMonitoringConfigResourceHandle()) 73 | bindResourceHandle(resources, NewWebsiteAlertConfigResourceHandle()) 74 | bindResourceHandle(resources, NewGroupResourceHandle()) 75 | bindResourceHandle(resources, NewCustomDashboardResourceHandle()) 76 | bindResourceHandle(resources, NewSyntheticTestResourceHandle()) 77 | return resources 78 | } 79 | 80 | func bindResourceHandle[T restapi.InstanaDataObject](resources map[string]*schema.Resource, resourceHandle ResourceHandle[T]) { 81 | resources[resourceHandle.MetaData().ResourceName] = NewTerraformResource(resourceHandle).ToSchemaResource() 82 | } 83 | 84 | func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 85 | apiToken := strings.TrimSpace(d.Get(SchemaFieldAPIToken).(string)) 86 | endpoint := strings.TrimSpace(d.Get(SchemaFieldEndpoint).(string)) 87 | skipTlsVerify := d.Get(SchemaFieldTlsSkipVerify).(bool) 88 | instanaAPI := restapi.NewInstanaAPI(apiToken, endpoint, skipTlsVerify) 89 | return &ProviderMeta{ 90 | InstanaAPI: instanaAPI, 91 | }, nil 92 | } 93 | 94 | func providerDataSources() map[string]*schema.Resource { 95 | dataSources := make(map[string]*schema.Resource) 96 | dataSources[DataSourceBuiltinEvent] = NewBuiltinEventDataSource().CreateResource() 97 | dataSources[DataSourceSyntheticLocation] = NewSyntheticLocationDataSource().CreateResource() 98 | dataSources[DataSourceAlertingChannel] = NewAlertingChannelDataSource().CreateResource() 99 | return dataSources 100 | } 101 | -------------------------------------------------------------------------------- /instana/restapi/default-rest-resource_create-post-update-not-supported_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "testing" 7 | 8 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 9 | "github.com/gessnerfl/terraform-provider-instana/mocks" 10 | "github.com/stretchr/testify/assert" 11 | "go.uber.org/mock/gomock" 12 | ) 13 | 14 | func TestSuccessfulCreateOfTestObjectThroughCreatePOSTUpdateNotSupportedRestResource(t *testing.T) { 15 | executeCreateOrUpdateOperationThroughCreatePOSTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 16 | testObject := makeTestObject() 17 | serializedJSON, _ := json.Marshal(testObject) 18 | 19 | client.EXPECT().Post(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(serializedJSON, nil) 20 | unmarshaller.EXPECT().Unmarshal(serializedJSON).Times(1).Return(testObject, nil) 21 | 22 | result, err := sut.Create(testObject) 23 | 24 | assert.NoError(t, err) 25 | assert.Equal(t, testObject, result) 26 | }) 27 | } 28 | 29 | func TestShouldFailToCreateTestObjectThroughCreatePOSTUpdateNotSupportedRestResourceWhenErrorIsReturnedFromRestClient(t *testing.T) { 30 | executeCreateOrUpdateOperationThroughCreatePOSTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 31 | testObject := makeTestObject() 32 | 33 | client.EXPECT().Post(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(nil, errors.New("error during test")) 34 | client.EXPECT().Put(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 35 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(0) 36 | 37 | _, err := sut.Create(testObject) 38 | 39 | assert.Error(t, err) 40 | }) 41 | } 42 | 43 | func TestShouldFailToCreateTestObjectThroughCreatePOSTUpdateNotSupportedRestResourceWhenResponseCannotBeUnmarshalled(t *testing.T) { 44 | executeCreateOrUpdateOperationThroughCreatePOSTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 45 | testObject := makeTestObject() 46 | expectedError := errors.New("test") 47 | 48 | client.EXPECT().Post(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(invalidResponse, nil) 49 | client.EXPECT().Put(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 50 | unmarshaller.EXPECT().Unmarshal(invalidResponse).Times(1).Return(nil, expectedError) 51 | 52 | _, err := sut.Create(testObject) 53 | 54 | assert.Error(t, err) 55 | assert.Equal(t, expectedError, err) 56 | }) 57 | } 58 | 59 | func TestShouldFailToUpdateTestObjectThroughCreatePOSTUpdateNotSupportedRestResourceWhenEmptyObjectCanBeCreated(t *testing.T) { 60 | executeCreateOrUpdateOperationThroughCreatePOSTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 61 | testData := makeTestObject() 62 | emptyObject := &testObject{} 63 | 64 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(1).Return(emptyObject, nil) 65 | 66 | _, err := sut.Update(testData) 67 | 68 | assert.Error(t, err) 69 | assert.ErrorContains(t, err, "update is not supported for /test") 70 | }) 71 | } 72 | 73 | func TestShouldFailToUpdateTestObjectThroughCreatePOSTUpdateNotSupportedRestResourceWhenEmptyObjectCannotBeCreated(t *testing.T) { 74 | executeCreateOrUpdateOperationThroughCreatePOSTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 75 | testData := makeTestObject() 76 | emptyObject := &testObject{} 77 | unmarshallingError := errors.New("unmarshalling-error") 78 | 79 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(1).Return(emptyObject, unmarshallingError) 80 | 81 | _, err := sut.Update(testData) 82 | 83 | assert.Error(t, err) 84 | assert.ErrorContains(t, err, "update is not supported for /test; unmarshalling-error") 85 | }) 86 | } 87 | 88 | func executeCreateOrUpdateOperationThroughCreatePOSTUpdateNotSupportedRestResourceTest(t *testing.T, testFunction func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject])) { 89 | ctrl := gomock.NewController(t) 90 | defer ctrl.Finish() 91 | client := mocks.NewMockRestClient(ctrl) 92 | unmarshaller := mocks.NewMockJSONUnmarshaller[*testObject](ctrl) 93 | 94 | sut := NewCreatePOSTUpdateNotSupportedRestResource[*testObject](testObjectResourcePath, unmarshaller, client) 95 | 96 | testFunction(t, sut, client, unmarshaller) 97 | } 98 | -------------------------------------------------------------------------------- /instana/restapi/default-rest-resource_create-put-update-not-supported_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "testing" 7 | 8 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 9 | "github.com/gessnerfl/terraform-provider-instana/mocks" 10 | "github.com/stretchr/testify/assert" 11 | "go.uber.org/mock/gomock" 12 | ) 13 | 14 | func TestSuccessfulCreateOfTestObjectThroughCreatePUTUpdateNotSupportedRestResource(t *testing.T) { 15 | executeCreateOrUpdateOperationThroughCreatePUTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 16 | testObject := makeTestObject() 17 | serializedJSON, _ := json.Marshal(testObject) 18 | 19 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(serializedJSON, nil) 20 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 21 | unmarshaller.EXPECT().Unmarshal(serializedJSON).Times(1).Return(testObject, nil) 22 | 23 | result, err := sut.Create(testObject) 24 | 25 | assert.NoError(t, err) 26 | assert.Equal(t, testObject, result) 27 | }) 28 | } 29 | 30 | func TestShouldFailToCreateTestObjectThroughCreatePUTUpdateNotSupportedRestResourceWhenErrorIsReturnedFromRestClient(t *testing.T) { 31 | executeCreateOrUpdateOperationThroughCreatePUTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 32 | testObject := makeTestObject() 33 | 34 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(nil, errors.New("error during test")) 35 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 36 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(0) 37 | 38 | _, err := sut.Create(testObject) 39 | 40 | assert.Error(t, err) 41 | }) 42 | } 43 | 44 | func TestShouldFailToCreateTestObjectThroughCreatePUTUpdateNotSupportedRestResourceWhenResponseCannotBeUnmarshalled(t *testing.T) { 45 | executeCreateOrUpdateOperationThroughCreatePUTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 46 | testObject := makeTestObject() 47 | expectedError := errors.New("test") 48 | 49 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(invalidResponse, nil) 50 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 51 | unmarshaller.EXPECT().Unmarshal(invalidResponse).Times(1).Return(nil, expectedError) 52 | 53 | _, err := sut.Create(testObject) 54 | 55 | assert.Error(t, err) 56 | assert.Equal(t, expectedError, err) 57 | }) 58 | } 59 | 60 | func TestShouldFailToUpdateTestObjectThroughCreatePUTUpdateNotSupportedRestResourceWhenEmptyObjectCanBeCreated(t *testing.T) { 61 | executeCreateOrUpdateOperationThroughCreatePUTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 62 | testData := makeTestObject() 63 | emptyObject := &testObject{} 64 | 65 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(1).Return(emptyObject, nil) 66 | 67 | _, err := sut.Update(testData) 68 | 69 | assert.Error(t, err) 70 | assert.ErrorContains(t, err, "update is not supported for /test") 71 | }) 72 | } 73 | 74 | func TestShouldFailToUpdateTestObjectThroughCreatePUTUpdateNotSupportedRestResourceWhenEmptyObjectCannotBeCreated(t *testing.T) { 75 | executeCreateOrUpdateOperationThroughCreatePUTUpdateNotSupportedRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 76 | testData := makeTestObject() 77 | emptyObject := &testObject{} 78 | unmarshallingError := errors.New("unmarshalling-error") 79 | 80 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(1).Return(emptyObject, unmarshallingError) 81 | 82 | _, err := sut.Update(testData) 83 | 84 | assert.Error(t, err) 85 | assert.ErrorContains(t, err, "update is not supported for /test; unmarshalling-error") 86 | }) 87 | } 88 | 89 | func executeCreateOrUpdateOperationThroughCreatePUTUpdateNotSupportedRestResourceTest(t *testing.T, testFunction func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject])) { 90 | ctrl := gomock.NewController(t) 91 | defer ctrl.Finish() 92 | client := mocks.NewMockRestClient(ctrl) 93 | unmarshaller := mocks.NewMockJSONUnmarshaller[*testObject](ctrl) 94 | 95 | sut := NewCreatePUTUpdateNotSupportedRestResource[*testObject](testObjectResourcePath, unmarshaller, client) 96 | 97 | testFunction(t, sut, client, unmarshaller) 98 | } 99 | -------------------------------------------------------------------------------- /docs/resources/synthetic_test.md: -------------------------------------------------------------------------------- 1 | # Synthetic Test Resource 2 | 3 | Synthetic test configuration used to manage synthetic tests in Instana API. Right now, only `HTTPActionConfiguration` 4 | and `HTTPScriptConfiguration` are supported. 5 | 6 | API Documentation: 7 | 8 | ## Example Usage 9 | 10 | 11 | ### Create a HTTPAction test 12 | ```hcl 13 | resource "instana_synthetic_test" "http_action" { 14 | label = "test" 15 | active = true 16 | application_id = "my-app-id" 17 | locations = [data.instana_synthetic_location.loc1.id] 18 | test_frequency = 10 19 | playback_mode = "Staggered" 20 | 21 | http_action { 22 | mark_synthetic_call = true 23 | retries = 0 24 | retry_interval = 1 25 | timeout = "3m" 26 | url = "https://example.com" 27 | operation = "GET" 28 | } 29 | 30 | custom_properties = { 31 | "foo" = "bar" 32 | } 33 | } 34 | ``` 35 | 36 | ### Create a HTTPScript test 37 | ```hcl 38 | resource "instana_synthetic_test" "http_script" { 39 | label = "test" 40 | active = true 41 | application_id = "my-app-id" 42 | locations = [data.instana_synthetic_location.loc1.id] 43 | 44 | http_script { 45 | synthetic_type = "HTTPScript" 46 | script = < 6 | 7 | The ID of the resource which is also used as unique identifier in Instana is auto generated! 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | resource "instana_api_token" "example" { 13 | name = "name" 14 | can_configure_service_mapping = true 15 | can_configure_eum_applications = true 16 | can_configure_mobile_app_monitoring = true 17 | can_configure_users = true 18 | can_install_new_agents = true 19 | can_see_usage_information = true 20 | can_configure_integrations = true 21 | can_see_on_premise_license_information = true 22 | can_configure_custom_alerts = true 23 | can_configure_api_tokens = true 24 | can_configure_agent_run_mode = true 25 | can_view_audit_log = true 26 | can_configure_agents = true 27 | can_configure_authentication_methods = true 28 | can_configure_applications = true 29 | can_configure_teams = true 30 | can_configure_releases = true 31 | can_configure_log_management = true 32 | can_create_public_custom_dashboards = true 33 | can_view_logs = true 34 | can_view_trace_details = true 35 | can_configure_session_settings = true 36 | can_configure_service_level_indicators = true 37 | can_configure_global_alert_payload = true 38 | can_configure_global_alert_configs = true 39 | can_view_account_and_billing_information = true 40 | can_edit_all_accessible_custom_dashboards = true 41 | } 42 | ``` 43 | 44 | ## Argument Reference 45 | 46 | * `access_granting_token`- Calculated - The token used for the api Client used in the Authorization header to authenticate the client 47 | * `name` - Required - the name of the alerting channel 48 | * `can_configure_service_mapping` - Optional - default false - enables permission to configure service mappings 49 | * `can_configure_eum_applications` - Optional - default false - enables permission to configure EUM applications 50 | * `can_configure_mobile_app_monitoring` - Optional - default false - enables permission to configure mobile app monitoring 51 | * `can_configure_users` - Optional - default false - enables permission to configure users 52 | * `can_install_new_agents` - Optional - default false - enables permission to install new agents 53 | * `can_see_usage_information` - Optional - default false - enables permission to see usage information 54 | * `can_configure_integrations` - Optional - default false - enables permission to configure integrations 55 | * `can_see_on_premise_license_information` - Optional - default false - enables permission to see on premise license information 56 | * `can_configure_custom_alerts` - Optional - default false - enables permission to configure custom alerts 57 | * `can_configure_api_tokens` - Optional - default false - enables permission to configure api tokes 58 | * `can_configure_agent_run_mode` - Optional - default false - enables permission to configure agent run mode 59 | * `can_view_audit_log` - Optional - default false - enables permission to view audit logs 60 | * `can_configure_agents` - Optional - default false - enables permission to configure agents 61 | * `can_configure_authentication_methods` - Optional - default false - enables permission to configure authentication methods 62 | * `can_configure_applications` - Optional - default false - enables permission to configure applications 63 | * `can_configure_teams` - Optional - default false - enables permission to configure teams (groups) 64 | * `can_configure_releases` - Optional - default false - enables permission to configure releases 65 | * `can_configure_log_management` - Optional - default false - enables permission to configure log management 66 | * `can_create_public_custom_dashboards` - Optional - default false - enables permission to create public custom dashboards 67 | * `can_view_logs` - Optional - default false - enables permission to view logs 68 | * `can_view_trace_details` - Optional - default false - enables permission to view trace details 69 | * `can_configure_session_settings` - Optional - default false - enables permission to configure session settings 70 | * `can_configure_service_level_indicators` - Optional - default false - enables permission to configure service level indicators 71 | * `can_configure_global_alert_payload` - Optional - default false - enables permission to configure global alert payload 72 | * `can_configure_global_alert_configs` - Optional - default false - enables permission to configure global alert configs 73 | * `can_view_account_and_billing_information` - Optional - default false - enables permission to view account and billing information 74 | * `can_edit_all_accessible_custom_dashboards` - Optional - default false - enables permission to edit all accessible custom dashboards 75 | 76 | ## Import 77 | 78 | API Tokens can be imported using the `internal_id`, e.g.: 79 | 80 | ``` 81 | $ terraform import instana_api_token.my_token 60845e4e5e6b9cf8fc2868da 82 | ``` 83 | -------------------------------------------------------------------------------- /instana/data-source-builtin-event.go: -------------------------------------------------------------------------------- 1 | package instana 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/gessnerfl/terraform-provider-instana/tfutils" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | 9 | "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | // NewBuiltinEventDataSource creates a new DataSource for Builtin Events 14 | func NewBuiltinEventDataSource() DataSource { 15 | return &builtInEventDataSource{} 16 | } 17 | 18 | const ( 19 | //BuiltinEventSpecificationFieldName constant value for the schema field name 20 | BuiltinEventSpecificationFieldName = "name" 21 | //BuiltinEventSpecificationFieldDescription constant value for the schema field description 22 | BuiltinEventSpecificationFieldDescription = "description" 23 | //BuiltinEventSpecificationFieldShortPluginID constant value for the schema field short_plugin_id 24 | BuiltinEventSpecificationFieldShortPluginID = "short_plugin_id" 25 | //BuiltinEventSpecificationFieldSeverity constant value for the schema field severity 26 | BuiltinEventSpecificationFieldSeverity = "severity" 27 | //BuiltinEventSpecificationFieldSeverityCode constant value for the schema field severity_code 28 | BuiltinEventSpecificationFieldSeverityCode = "severity_code" 29 | //BuiltinEventSpecificationFieldTriggering constant value for the schema field triggering 30 | BuiltinEventSpecificationFieldTriggering = "triggering" 31 | //BuiltinEventSpecificationFieldEnabled constant value for the schema field enabled 32 | BuiltinEventSpecificationFieldEnabled = "enabled" 33 | 34 | //DataSourceBuiltinEvent the name of the terraform-provider-instana data sourcefor builtin event specifications 35 | DataSourceBuiltinEvent = "instana_builtin_event_spec" 36 | // 37 | ) 38 | 39 | type builtInEventDataSource struct{} 40 | 41 | // CreateResource creates the terraform Resource for the data source for Instana builtin events 42 | func (ds *builtInEventDataSource) CreateResource() *schema.Resource { 43 | return &schema.Resource{ 44 | ReadContext: ds.read, 45 | Schema: map[string]*schema.Schema{ 46 | BuiltinEventSpecificationFieldName: { 47 | Type: schema.TypeString, 48 | Required: true, 49 | Description: "The name of the builtin event", 50 | }, 51 | BuiltinEventSpecificationFieldDescription: { 52 | Type: schema.TypeString, 53 | Computed: true, 54 | Description: "The description text of the builtin event.", 55 | }, 56 | BuiltinEventSpecificationFieldShortPluginID: { 57 | Type: schema.TypeString, 58 | Required: true, 59 | Description: "The plugin id for which the builtin event is created.", 60 | }, 61 | BuiltinEventSpecificationFieldSeverity: { 62 | Type: schema.TypeString, 63 | Computed: true, 64 | Description: "The severity (WARNING, CRITICAL, etc.) of the builtin event.", 65 | }, 66 | BuiltinEventSpecificationFieldSeverityCode: { 67 | Type: schema.TypeInt, 68 | Computed: true, 69 | Description: "The severity code used by Instana API (5, 10, etc.) of the builtin event.", 70 | }, 71 | BuiltinEventSpecificationFieldTriggering: { 72 | Type: schema.TypeBool, 73 | Computed: true, 74 | Description: "Indicates if an incident is triggered the builtin event or not.", 75 | }, 76 | BuiltinEventSpecificationFieldEnabled: { 77 | Type: schema.TypeBool, 78 | Computed: true, 79 | Description: "Indicates if the builtin event is enabled or not", 80 | }, 81 | }, 82 | } 83 | } 84 | 85 | func (ds *builtInEventDataSource) read(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 86 | providerMeta := meta.(*ProviderMeta) 87 | instanaAPI := providerMeta.InstanaAPI 88 | 89 | name := d.Get(BuiltinEventSpecificationFieldName).(string) 90 | shortPluginID := d.Get(BuiltinEventSpecificationFieldShortPluginID).(string) 91 | 92 | data, err := instanaAPI.BuiltinEventSpecifications().GetAll() 93 | if err != nil { 94 | return diag.FromErr(err) 95 | } 96 | 97 | builtInEvent, err := ds.findBuiltInEventByNameAndPluginID(name, shortPluginID, data) 98 | 99 | if err != nil { 100 | return diag.FromErr(err) 101 | } 102 | 103 | err = ds.updateState(d, builtInEvent) 104 | if err != nil { 105 | return diag.FromErr(err) 106 | } 107 | return nil 108 | } 109 | 110 | func (ds *builtInEventDataSource) findBuiltInEventByNameAndPluginID(name string, shortPluginID string, data *[]*restapi.BuiltinEventSpecification) (*restapi.BuiltinEventSpecification, error) { 111 | for _, builtInEvent := range *data { 112 | if builtInEvent.Name == name && builtInEvent.ShortPluginID == shortPluginID { 113 | return builtInEvent, nil 114 | } 115 | } 116 | return nil, fmt.Errorf("no built in event found for name '%s' and short plugin ID '%s'", name, shortPluginID) 117 | } 118 | 119 | func (ds *builtInEventDataSource) updateState(d *schema.ResourceData, builtInEvent *restapi.BuiltinEventSpecification) error { 120 | severity, err := ConvertSeverityFromInstanaAPIToTerraformRepresentation(builtInEvent.Severity) 121 | if err != nil { 122 | return err 123 | } 124 | d.SetId(builtInEvent.ID) 125 | return tfutils.UpdateState(d, map[string]interface{}{ 126 | BuiltinEventSpecificationFieldDescription: builtInEvent.Description, 127 | BuiltinEventSpecificationFieldSeverity: severity, 128 | BuiltinEventSpecificationFieldSeverityCode: builtInEvent.Severity, 129 | BuiltinEventSpecificationFieldTriggering: builtInEvent.Triggering, 130 | BuiltinEventSpecificationFieldEnabled: builtInEvent.Enabled, 131 | }) 132 | } 133 | -------------------------------------------------------------------------------- /instana/restapi/default-rest-resource_create-post-update-put_test.go: -------------------------------------------------------------------------------- 1 | package restapi_test 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "testing" 7 | 8 | . "github.com/gessnerfl/terraform-provider-instana/instana/restapi" 9 | "github.com/gessnerfl/terraform-provider-instana/mocks" 10 | "github.com/stretchr/testify/assert" 11 | "go.uber.org/mock/gomock" 12 | ) 13 | 14 | var ( 15 | invalidResponse = []byte("invalid response") 16 | ) 17 | 18 | func TestSuccessfulCreateOfTestObjectThroughCreatePOSTUpdatePUTRestResource(t *testing.T) { 19 | executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 20 | testObject := makeTestObject() 21 | serializedJSON, _ := json.Marshal(testObject) 22 | 23 | client.EXPECT().Post(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(serializedJSON, nil) 24 | unmarshaller.EXPECT().Unmarshal(serializedJSON).Times(1).Return(testObject, nil) 25 | 26 | result, err := sut.Create(testObject) 27 | 28 | assert.NoError(t, err) 29 | assert.Equal(t, testObject, result) 30 | }) 31 | } 32 | 33 | func TestShouldFailToCreateTestObjectThroughCreatePOSTUpdatePUTRestResourceWhenErrorIsReturnedFromRestClient(t *testing.T) { 34 | executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 35 | testObject := makeTestObject() 36 | 37 | client.EXPECT().Post(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(nil, errors.New("error during test")) 38 | client.EXPECT().Put(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 39 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(0) 40 | 41 | _, err := sut.Create(testObject) 42 | 43 | assert.Error(t, err) 44 | }) 45 | } 46 | 47 | func TestShouldFailToCreateTestObjectThroughCreatePOSTUpdatePUTRestResourceWhenResponseCannotBeUnmarshalled(t *testing.T) { 48 | executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 49 | testObject := makeTestObject() 50 | expectedError := errors.New("test") 51 | 52 | client.EXPECT().Post(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(invalidResponse, nil) 53 | client.EXPECT().Put(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 54 | unmarshaller.EXPECT().Unmarshal(invalidResponse).Times(1).Return(nil, expectedError) 55 | 56 | _, err := sut.Create(testObject) 57 | 58 | assert.Error(t, err) 59 | assert.Equal(t, expectedError, err) 60 | }) 61 | } 62 | 63 | func TestSuccessfulUpdateOfTestObjectThroughCreatePOSTUpdatePUTRestResource(t *testing.T) { 64 | executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 65 | testObject := makeTestObject() 66 | serializedJSON, _ := json.Marshal(testObject) 67 | 68 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(serializedJSON, nil) 69 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 70 | unmarshaller.EXPECT().Unmarshal(serializedJSON).Times(1).Return(testObject, nil) 71 | 72 | result, err := sut.Update(testObject) 73 | 74 | assert.NoError(t, err) 75 | assert.Equal(t, testObject, result) 76 | }) 77 | } 78 | 79 | func TestShouldFailToUpdateTestObjectThroughCreatePOSTUpdatePUTRestResourceWhenErrorIsReturnedFromRestClient(t *testing.T) { 80 | executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 81 | testObject := makeTestObject() 82 | 83 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(nil, errors.New("error during test")) 84 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 85 | unmarshaller.EXPECT().Unmarshal(gomock.Any()).Times(0) 86 | 87 | _, err := sut.Update(testObject) 88 | 89 | assert.Error(t, err) 90 | }) 91 | } 92 | 93 | func TestShouldFailToUpdateTestObjectThroughCreatePOSTUpdatePUTRestResourceWhenResponseCannotBeUnmarshalled(t *testing.T) { 94 | executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t, func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject]) { 95 | testObject := makeTestObject() 96 | expectedError := errors.New("test") 97 | 98 | client.EXPECT().Put(gomock.Eq(testObject), gomock.Eq(testObjectResourcePath)).Return(invalidResponse, nil) 99 | client.EXPECT().Post(gomock.Any(), gomock.Eq(testObjectResourcePath)).Times(0) 100 | unmarshaller.EXPECT().Unmarshal(invalidResponse).Times(1).Return(nil, expectedError) 101 | 102 | _, err := sut.Update(testObject) 103 | 104 | assert.Error(t, err) 105 | assert.Equal(t, expectedError, err) 106 | }) 107 | } 108 | 109 | func executeCreateOrUpdateOperationThroughCreatePOSTUpdatePUTRestResourceTest(t *testing.T, testFunction func(t *testing.T, sut RestResource[*testObject], client *mocks.MockRestClient, unmarshaller *mocks.MockJSONUnmarshaller[*testObject])) { 110 | ctrl := gomock.NewController(t) 111 | defer ctrl.Finish() 112 | client := mocks.NewMockRestClient(ctrl) 113 | unmarshaller := mocks.NewMockJSONUnmarshaller[*testObject](ctrl) 114 | 115 | sut := NewCreatePOSTUpdatePUTRestResource[*testObject](testObjectResourcePath, unmarshaller, client) 116 | 117 | testFunction(t, sut, client, unmarshaller) 118 | } 119 | -------------------------------------------------------------------------------- /instana/tag-filter-schema_test.go: -------------------------------------------------------------------------------- 1 | package instana_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gessnerfl/terraform-provider-instana/instana" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | ) 10 | 11 | const tagFilterExpression = "entity.type EQUALS 'foo'" 12 | const validTagFilterExpressionString = "entity.type EQUALS 'foo'" 13 | const invalidTagFilterExpressionString = "entity.type bla bla bla" 14 | 15 | func TestTagFilterSchema(t *testing.T) { 16 | for k, tagFilterExpressionSchema := range map[string]*schema.Schema{"optional": instana.OptionalTagFilterExpressionSchema, "required": instana.RequiredTagFilterExpressionSchema} { 17 | t.Run(fmt.Sprintf("DiffSuppressFunc of %s TagFilterExpression Schema should return true when value can be normalized and old and new normalized value are equal", k), createTestOfDiffSuppressFuncOfTagFilterShouldReturnTrueWhenValueCanBeNormalizedAndOldAndNewNormalizedValueAreEqual(tagFilterExpressionSchema)) 18 | t.Run(fmt.Sprintf("DiffSuppressFunc of %s TagFilterExpression Schema should return false when value can be normalized and old and new normalized value are not equal", k), createTestOfDiffSuppressFuncOfTagFilterShouldReturnFalseWhenValueCanBeNormalizedAndOldAndNewNormalizedValueAreNotEqual(tagFilterExpressionSchema)) 19 | t.Run(fmt.Sprintf("DiffSuppressFunc of %s TagFilterExpression Schema should return true when value can be normalized and old and new value are equal", k), createTestOfDiffSuppressFuncOfTagFilterShouldReturnTrueWhenValueCannotBeNormalizedAndOldAndNewValueAreEqual(tagFilterExpressionSchema)) 20 | t.Run(fmt.Sprintf("DiffSuppressFunc of %s TagFilterExpression Schema should return false when value cannot be normalized and old and new value are not equal", k), createTestOfDiffSuppressFuncOfTagFilterShouldReturnFalseWhenValueCannotBeNormalizedAndOldAndNewValueAreNotEqual(tagFilterExpressionSchema)) 21 | t.Run(fmt.Sprintf("StateFunc of %s TagFilterExpression Schema should return normalized value when value can be normalized", k), createTestOfStateFuncOfTagFilterShouldReturnNormalizedValueWhenValueCanBeNormalized(tagFilterExpressionSchema)) 22 | t.Run(fmt.Sprintf("StateFunc of %s TagFilterExpression Schema should return provided value when value cannot be normalized", k), createTestOfStateFuncOfTagFilterShouldReturnProvidedValueWhenValueCannotBeNormalized(tagFilterExpressionSchema)) 23 | t.Run(fmt.Sprintf("ValidateFunc of %s TagFilterExpression Schema should return no errors and warnings when value can be parsed", k), createTestOfValidateFuncOfTagFilterShouldReturnNoErrorsAndWarningsWhenValueCanBeParsed(tagFilterExpressionSchema)) 24 | t.Run(fmt.Sprintf("ValidateFunc of %s TagFilterExpression Schema should return one error and no warnings when value can be parsed", k), createTestOfValidateFuncOfTagFilterShouldReturnOneErrorAndNoWarningsWhenValueCannotBeParsed(tagFilterExpressionSchema)) 25 | } 26 | } 27 | 28 | func createTestOfDiffSuppressFuncOfTagFilterShouldReturnTrueWhenValueCanBeNormalizedAndOldAndNewNormalizedValueAreEqual(tagFilterSchema *schema.Schema) func(t *testing.T) { 29 | return func(t *testing.T) { 30 | oldValue := expressionEntityTypeDestEqValue 31 | newValue := "entity.type EQUALS 'foo'" 32 | 33 | require.True(t, tagFilterSchema.DiffSuppressFunc(tagFilterExpression, oldValue, newValue, nil)) 34 | } 35 | } 36 | 37 | func createTestOfDiffSuppressFuncOfTagFilterShouldReturnFalseWhenValueCanBeNormalizedAndOldAndNewNormalizedValueAreNotEqual(tagFilterSchema *schema.Schema) func(t *testing.T) { 38 | return func(t *testing.T) { 39 | oldValue := expressionEntityTypeSrcEqValue 40 | newValue := validTagFilterExpressionString 41 | 42 | require.False(t, tagFilterSchema.DiffSuppressFunc(tagFilterExpression, oldValue, newValue, nil)) 43 | } 44 | } 45 | 46 | func createTestOfDiffSuppressFuncOfTagFilterShouldReturnTrueWhenValueCannotBeNormalizedAndOldAndNewValueAreEqual(tagFilterSchema *schema.Schema) func(t *testing.T) { 47 | return func(t *testing.T) { 48 | invalidValue := invalidTagFilterExpressionString 49 | 50 | require.True(t, tagFilterSchema.DiffSuppressFunc(tagFilterExpression, invalidValue, invalidValue, nil)) 51 | } 52 | } 53 | 54 | func createTestOfDiffSuppressFuncOfTagFilterShouldReturnFalseWhenValueCannotBeNormalizedAndOldAndNewValueAreNotEqual(tagFilterSchema *schema.Schema) func(t *testing.T) { 55 | return func(t *testing.T) { 56 | oldValue := invalidTagFilterExpressionString 57 | newValue := "entity.type foo foo foo" 58 | 59 | require.False(t, tagFilterSchema.DiffSuppressFunc(tagFilterExpression, oldValue, newValue, nil)) 60 | } 61 | } 62 | 63 | func createTestOfStateFuncOfTagFilterShouldReturnNormalizedValueWhenValueCanBeNormalized(tagFilterSchema *schema.Schema) func(t *testing.T) { 64 | return func(t *testing.T) { 65 | expectedValue := expressionEntityTypeDestEqValue 66 | newValue := validTagFilterExpressionString 67 | 68 | require.Equal(t, expectedValue, tagFilterSchema.StateFunc(newValue)) 69 | } 70 | } 71 | 72 | func createTestOfStateFuncOfTagFilterShouldReturnProvidedValueWhenValueCannotBeNormalized(tagFilterSchema *schema.Schema) func(t *testing.T) { 73 | return func(t *testing.T) { 74 | value := invalidTagFilterExpressionString 75 | 76 | require.Equal(t, value, tagFilterSchema.StateFunc(value)) 77 | } 78 | } 79 | 80 | func createTestOfValidateFuncOfTagFilterShouldReturnNoErrorsAndWarningsWhenValueCanBeParsed(tagFilterSchema *schema.Schema) func(t *testing.T) { 81 | return func(t *testing.T) { 82 | value := validTagFilterExpressionString 83 | 84 | warns, errs := tagFilterSchema.ValidateFunc(value, tagFilterExpression) 85 | require.Empty(t, warns) 86 | require.Empty(t, errs) 87 | } 88 | } 89 | 90 | func createTestOfValidateFuncOfTagFilterShouldReturnOneErrorAndNoWarningsWhenValueCannotBeParsed(tagFilterSchema *schema.Schema) func(t *testing.T) { 91 | return func(t *testing.T) { 92 | value := invalidTagFilterExpressionString 93 | 94 | warns, errs := tagFilterSchema.ValidateFunc(value, tagFilterExpression) 95 | require.Empty(t, warns) 96 | require.Len(t, errs, 1) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /docs/resources/alerting_channel.md: -------------------------------------------------------------------------------- 1 | # Alerting Channel Resource 2 | 3 | Alerting channel configuration for notifications to a specified target channel. 4 | 5 | API Documentation: 6 | 7 | ## Example Usage 8 | 9 | ### Email Alerting Channel 10 | 11 | ```hcl 12 | resource "instana_alerting_channel" "example" { 13 | name = "my-email-alerting-channel" 14 | 15 | email { 16 | emails = [ "email1@example.com", "email2@example.com" ] 17 | } 18 | } 19 | ``` 20 | 21 | ### Google Chat Alerting Channel 22 | 23 | ```hcl 24 | resource "instana_alerting_channel" "example" { 25 | name = "my-google-chat-alerting-channel" 26 | 27 | google_chat { 28 | webhook_url = "https://my.google.chat.weebhook.exmaple.com/" 29 | } 30 | } 31 | ``` 32 | 33 | ### Office 365 Alerting Channel 34 | 35 | ```hcl 36 | resource "instana_alerting_channel" "example" { 37 | name = "my-google-chat-alerting-channel" 38 | 39 | office_365 { 40 | webhook_url = "https://my.google.chat.weebhook.exmaple.com/" 41 | } 42 | } 43 | ``` 44 | 45 | ### OpsGenie Alerting Channel 46 | 47 | ```hcl 48 | resource "instana_alerting_channel" "example" { 49 | name = "my-ops-genie-alerting-channel" 50 | 51 | ops_genie { 52 | api_key = "my-secure-api-key" 53 | tags = [ "tag1", "tag2" ] 54 | region = "EU" 55 | } 56 | } 57 | ``` 58 | 59 | ### PagerDuty Alerting Channel 60 | 61 | ```hcl 62 | resource "instana_alerting_channel" "example" { 63 | name = "my-pager-duty-alerting-channel" 64 | 65 | pager_duty { 66 | service_integration_key = "my-service-integration-key" 67 | } 68 | } 69 | ``` 70 | 71 | ### Slack Alerting Channel 72 | 73 | ```hcl 74 | resource "instana_alerting_channel" "example" { 75 | name = "my-slack-alerting-channel" 76 | 77 | slack { 78 | webhook_url = "https://my.slack.weebhook.exmaple.com/" 79 | icon_url = "https://my.slack.icon.exmaple.com/" 80 | channel = "my-channel" 81 | } 82 | } 83 | ``` 84 | 85 | ### Splunk Alerting Channel 86 | 87 | ```hcl 88 | resource "instana_alerting_channel" "example" { 89 | name = "my-splunk-alerting-channel" 90 | 91 | splunk { 92 | url = "https://my.splunk.url.example.com" 93 | token = "my-splunk-token" 94 | } 95 | } 96 | ``` 97 | 98 | ### VictorOps Alerting Channel 99 | 100 | ```hcl 101 | resource "instana_alerting_channel" "example" { 102 | name = "my-victor-ops-alerting-channel" 103 | 104 | victor_ops { 105 | api_key = "my-victor-ops-api-key" 106 | routing_key = "my-victor-ops-routing-key" 107 | } 108 | } 109 | ``` 110 | 111 | ### Webhook Alerting Channel 112 | 113 | ```hcl 114 | resource "instana_alerting_channel" "example" { 115 | name = "my-generic-webhook-alerting-channel" 116 | 117 | webhook { 118 | webhook_urls = [ 119 | "https://my.weebhook1.exmaple.com/", 120 | "https://my.weebhook2.exmaple.com/" 121 | ] 122 | 123 | http_headers = { 124 | header1 = "headerValue1" 125 | header2 = "headerValue2" 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | ## Argument Reference 132 | 133 | * `name` - Required - the name of the alerting channel 134 | 135 | Exactly one of the following channel types must be configured: 136 | 137 | * `email` - Optional - configuration of a email alerting channel - [Details](#email) 138 | * `google_chat` - Optional - configuration of a Google Chat alerting channel - [Details](#google-chat) 139 | * `office_365` - Optional - configuration of a Office 365 alerting channel - [Details](#office-365) 140 | * `ops_genie` - Optional - configuration of a OpsGenie alerting channel - [Details](#opsgenie) 141 | * `pager_duty` - Optional - configuration of a PagerDuty alerting channel - [Details](#pagerduty) 142 | * `slack` - Optional - configuration of a Slack alerting channel - [Details](#slack) 143 | * `splunk` - Optional - configuration of a Splunk alerting channel - [Details](#splunk) 144 | * `victor_ops` - Optional - configuration of a VictorOps alerting channel - [Details](#victorops) 145 | * `webhook` - Optional - configuration of a webhook alerting channel - [Details](#webhook) 146 | 147 | ### Email 148 | 149 | * `emails` - Required - the list of target email addresses 150 | 151 | ### Google Chat 152 | 153 | * `webhook_url` - Required - the URL of the Google Chat Webhook where the alert will be sent to 154 | 155 | ### Office 365 156 | 157 | * `webhook_url` - Required - the URL of the Google Chat Webhook where the alert will be sent to 158 | 159 | ### OpsGenie 160 | 161 | * `api_key` - Required - the API Key for authentication at the Ops Genie API 162 | * `tags` - Required - a list of tags (strings) for the alert in Ops Genie 163 | * `region` - Required - the target Ops Genie region 164 | 165 | ### PagerDuty 166 | 167 | * `service_integration_key` - Required - the key for the service integration in pager duty 168 | 169 | ### Slack 170 | 171 | * `webhook_url` - Required - the URL of the Slack webhook to send alerts to 172 | * `icon_url` - Optional - the URL to the icon which should be rendered in the slack message 173 | * `channel` - Optional - the target Slack channel where the alert should be posted 174 | 175 | ### Splunk 176 | 177 | * `url` - Required - the target Splunk endpoint URL 178 | * `token` - Required - the authentication token to login at the Splunk API 179 | 180 | ### VictorOps 181 | 182 | * `api_key` - Required - the api key to authenticate at the VictorOps API 183 | * `routing_key` - Required - the routing key used by VictoryOps to route the alert to the desired targe 184 | 185 | ### Webhook 186 | 187 | * `webhook_urls` - Required - the list of webhook URLs where the alert will be sent to 188 | * `http_headers` - Optional - key/value map of additional http headers which will be sent to the webhook 189 | 190 | ## Import 191 | 192 | Email alerting channels can be imported using the `id`, e.g.: 193 | 194 | ``` 195 | $ terraform import instana_alerting_channel_email.my_channel 60845e4e5e6b9cf8fc2868da 196 | ``` --------------------------------------------------------------------------------