Skip to content

Commit

Permalink
Merge pull request #499 from skarimo/sherz/synthetics
Browse files Browse the repository at this point in the history
  • Loading branch information
gzussa authored May 11, 2020
2 parents 99c56d2 + 3cffafd commit bf37162
Show file tree
Hide file tree
Showing 12 changed files with 1,333 additions and 949 deletions.
190 changes: 111 additions & 79 deletions datadog/cassettes/TestAccDatadogSyntheticsAPITest_Basic.yaml

Large diffs are not rendered by default.

336 changes: 196 additions & 140 deletions datadog/cassettes/TestAccDatadogSyntheticsAPITest_Updated.yaml

Large diffs are not rendered by default.

194 changes: 113 additions & 81 deletions datadog/cassettes/TestAccDatadogSyntheticsAPITest_importBasic.yaml

Large diffs are not rendered by default.

185 changes: 107 additions & 78 deletions datadog/cassettes/TestAccDatadogSyntheticsBrowserTest_Basic.yaml

Large diffs are not rendered by default.

341 changes: 197 additions & 144 deletions datadog/cassettes/TestAccDatadogSyntheticsBrowserTest_Updated.yaml

Large diffs are not rendered by default.

185 changes: 107 additions & 78 deletions datadog/cassettes/TestAccDatadogSyntheticsBrowserTest_importBasic.yaml

Large diffs are not rendered by default.

179 changes: 106 additions & 73 deletions datadog/cassettes/TestAccDatadogSyntheticsSSLTest_Basic.yaml

Large diffs are not rendered by default.

325 changes: 191 additions & 134 deletions datadog/cassettes/TestAccDatadogSyntheticsSSLTest_Updated.yaml

Large diffs are not rendered by default.

183 changes: 108 additions & 75 deletions datadog/cassettes/TestAccDatadogSyntheticsSSLTest_importBasic.yaml

Large diffs are not rendered by default.

153 changes: 90 additions & 63 deletions datadog/resource_datadog_synthetics_test_.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
"strconv"
"strings"

datadogV1 "github.com/DataDog/datadog-api-client-go/api/v1/datadog"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/zorkian/go-datadog-api"
)

var syntheticsTypes = []string{"api", "browser"}
Expand Down Expand Up @@ -135,9 +135,9 @@ func syntheticsTestOptions() *schema.Schema {
return &schema.Schema{
Type: schema.TypeMap,
DiffSuppressFunc: func(key, old, new string, d *schema.ResourceData) bool {
if key == "options.follow_redirects" || key == "options.accept_self_signed" {
if key == "options.follow_redirects" || key == "options.accept_self_signed" || key == "options.allow_insecure" {
// TF nested schemas is limited to string values only
// follow_redirects and accept_self_signed being booleans in Datadog json api
// follow_redirects, accept_self_signed and allow_insecure being booleans in Datadog json api
// we need a sane way to convert from boolean to string
// and from string to boolean
oldValue, err1 := strconv.ParseBool(old)
Expand Down Expand Up @@ -172,6 +172,16 @@ func syntheticsTestOptions() *schema.Schema {
errs = append(errs, fmt.Errorf("%q.accept_self_signed must be either true or false, got: %s", key, acceptSelfSignedStr))
}
}
allowInsecureRaw, ok := val.(map[string]interface{})["allow_insecure"]
if ok {
allowInsecureStr := convertToString(allowInsecureRaw)
switch allowInsecureStr {
case "true", "false":
break
default:
errs = append(errs, fmt.Errorf("%q.allow_insecure must be either true or false, got: %s", key, allowInsecureStr))
}
}
return
},
Optional: true,
Expand All @@ -197,17 +207,22 @@ func syntheticsTestOptions() *schema.Schema {
Type: schema.TypeBool,
Optional: true,
},
"allow_insecure": {
Type: schema.TypeBool,
Optional: true,
},
},
},
}
}

func resourceDatadogSyntheticsTestCreate(d *schema.ResourceData, meta interface{}) error {
providerConf := meta.(*ProviderConfiguration)
client := providerConf.CommunityClient
datadogClientV1 := providerConf.DatadogClientV1
authV1 := providerConf.AuthV1

syntheticsTest := buildSyntheticsTestStruct(d)
createdSyntheticsTest, err := client.CreateSyntheticsTest(syntheticsTest)
createdSyntheticsTest, _, err := datadogClientV1.SyntheticsApi.CreateTest(authV1).Body(*syntheticsTest).Execute()
if err != nil {
// Note that Id won't be set, so no state will be saved.
return translateClientError(err, "error creating synthetics test")
Expand All @@ -223,9 +238,10 @@ func resourceDatadogSyntheticsTestCreate(d *schema.ResourceData, meta interface{

func resourceDatadogSyntheticsTestRead(d *schema.ResourceData, meta interface{}) error {
providerConf := meta.(*ProviderConfiguration)
client := providerConf.CommunityClient
datadogClientV1 := providerConf.DatadogClientV1
authV1 := providerConf.AuthV1

syntheticsTest, err := client.GetSyntheticsTest(d.Id())
syntheticsTest, _, err := datadogClientV1.SyntheticsApi.GetTest(authV1, d.Id()).Execute()
if err != nil {
if strings.Contains(err.Error(), "404 Not Found") {
// Delete the resource from the local state since it doesn't exist anymore in the actual state
Expand All @@ -235,17 +251,18 @@ func resourceDatadogSyntheticsTestRead(d *schema.ResourceData, meta interface{})
return translateClientError(err, "error getting synthetics test")
}

updateSyntheticsTestLocalState(d, syntheticsTest)
updateSyntheticsTestLocalState(d, &syntheticsTest)

return nil
}

func resourceDatadogSyntheticsTestUpdate(d *schema.ResourceData, meta interface{}) error {
providerConf := meta.(*ProviderConfiguration)
client := providerConf.CommunityClient
datadogClientV1 := providerConf.DatadogClientV1
authV1 := providerConf.AuthV1

syntheticsTest := buildSyntheticsTestStruct(d)
if _, err := client.UpdateSyntheticsTest(d.Id(), syntheticsTest); err != nil {
if _, _, err := datadogClientV1.SyntheticsApi.UpdateTest(authV1, d.Id()).Body(*syntheticsTest).Execute(); err != nil {
// If the Update callback returns with or without an error, the full state is saved.
translateClientError(err, "error updating synthetics test")
}
Expand All @@ -256,9 +273,11 @@ func resourceDatadogSyntheticsTestUpdate(d *schema.ResourceData, meta interface{

func resourceDatadogSyntheticsTestDelete(d *schema.ResourceData, meta interface{}) error {
providerConf := meta.(*ProviderConfiguration)
client := providerConf.CommunityClient
datadogClientV1 := providerConf.DatadogClientV1
authV1 := providerConf.AuthV1

if err := client.DeleteSyntheticsTests([]string{d.Id()}); err != nil {
syntheticsDeleteTestsPayload := datadogV1.SyntheticsDeleteTestsPayload{PublicIds: &[]string{d.Id()}}
if _, _, err := datadogClientV1.SyntheticsApi.DeleteTests(authV1).Body(syntheticsDeleteTestsPayload).Execute(); err != nil {
// The resource is assumed to still exist, and all prior state is preserved.
return translateClientError(err, "error deleting synthetics test")
}
Expand All @@ -267,19 +286,19 @@ func resourceDatadogSyntheticsTestDelete(d *schema.ResourceData, meta interface{
return nil
}

func isTargetOfTypeInt(assertionType string) bool {
for _, intTargetAssertionType := range []string{"responseTime", "statusCode", "certificate"} {
func isTargetOfTypeInt(assertionType datadogV1.SyntheticsAssertionType) bool {
for _, intTargetAssertionType := range []datadogV1.SyntheticsAssertionType{datadogV1.SYNTHETICSASSERTIONTYPE_RESPONSE_TIME, datadogV1.SYNTHETICSASSERTIONTYPE_STATUS_CODE, datadogV1.SYNTHETICSASSERTIONTYPE_CERTIFICATE} {
if assertionType == intTargetAssertionType {
return true
}
}
return false
}

func buildSyntheticsTestStruct(d *schema.ResourceData) *datadog.SyntheticsTest {
request := datadog.SyntheticsRequest{}
func buildSyntheticsTestStruct(d *schema.ResourceData) *datadogV1.SyntheticsTestDetails {
request := datadogV1.NewSyntheticsTestRequest()
if attr, ok := d.GetOk("request.method"); ok {
request.SetMethod(attr.(string))
request.SetMethod(datadogV1.HTTPMethod(attr.(string)))
}
if attr, ok := d.GetOk("request.url"); ok {
request.SetUrl(attr.(string))
Expand All @@ -289,64 +308,62 @@ func buildSyntheticsTestStruct(d *schema.ResourceData) *datadog.SyntheticsTest {
}
if attr, ok := d.GetOk("request.timeout"); ok {
timeoutInt, _ := strconv.Atoi(attr.(string))
request.SetTimeout(timeoutInt)
request.SetTimeout(float64(timeoutInt))
}
if attr, ok := d.GetOk("request.host"); ok {
request.SetHost(attr.(string))
}
if attr, ok := d.GetOk("request.port"); ok {
portInt, _ := strconv.Atoi(attr.(string))
request.SetPort(portInt)
request.SetPort(int64(portInt))
}
if attr, ok := d.GetOk("request_headers"); ok {
headers := attr.(map[string]interface{})
if len(headers) > 0 {
request.Headers = make(map[string]string)
request.SetHeaders(make(map[string]string))
}
for k, v := range headers {
request.Headers[k] = v.(string)
request.GetHeaders()[k] = v.(string)
}
}

config := datadog.SyntheticsConfig{
Request: &request,
Variables: []interface{}{},
}
config := datadogV1.NewSyntheticsTestConfig([]datadogV1.SyntheticsAssertion{}, *request)
config.SetVariables([]datadogV1.SyntheticsBrowserVariable{})

if attr, ok := d.GetOk("assertions"); ok {
if attr, ok := d.GetOk("assertions"); ok && attr != nil {
for _, attr := range attr.([]interface{}) {
assertion := datadog.SyntheticsAssertion{}
assertion := datadogV1.SyntheticsAssertion{}
assertionMap := attr.(map[string]interface{})
if v, ok := assertionMap["type"]; ok {
assertionType := v.(string)
assertion.Type = &assertionType
assertion.SetType(datadogV1.SyntheticsAssertionType(assertionType))
}
if v, ok := assertionMap["property"]; ok {
assertionProperty := v.(string)
assertion.Property = &assertionProperty
assertion.SetProperty(assertionProperty)
}
if v, ok := assertionMap["operator"]; ok {
assertionOperator := v.(string)
assertion.Operator = &assertionOperator
assertion.SetOperator(datadogV1.SyntheticsAssertionOperator(assertionOperator))
}
if v, ok := assertionMap["target"]; ok {
if isTargetOfTypeInt(*assertion.Type) {
if isTargetOfTypeInt(assertion.GetType()) {
assertionTargetInt, _ := strconv.Atoi(v.(string))
assertion.Target = assertionTargetInt
} else if *assertion.Operator == "validates" {
assertion.Target = json.RawMessage(v.(string))
assertion.SetTarget(assertionTargetInt)
} else if assertion.GetOperator() == datadogV1.SYNTHETICSASSERTIONOPERATOR_VALIDATES {
assertion.SetTarget(v.(string))
} else {
assertion.Target = v.(string)
assertion.SetTarget(v.(string))
}
}
config.Assertions = append(config.Assertions, assertion)
}
}

options := datadog.SyntheticsOptions{}
options := datadogV1.NewSyntheticsTestOptions()
if attr, ok := d.GetOk("options.tick_every"); ok {
tickEvery, _ := strconv.Atoi(attr.(string))
options.SetTickEvery(tickEvery)
options.SetTickEvery(datadogV1.SyntheticsTickInterval(tickEvery))
}
if attr, ok := d.GetOk("options.follow_redirects"); ok {
// follow_redirects is a string ("true" or "false") in TF state
Expand All @@ -357,41 +374,46 @@ func buildSyntheticsTestStruct(d *schema.ResourceData) *datadog.SyntheticsTest {
}
if attr, ok := d.GetOk("options.min_failure_duration"); ok {
minFailureDuration, _ := strconv.Atoi(attr.(string))
options.SetMinFailureDuration(minFailureDuration)
options.SetMinFailureDuration(int64(minFailureDuration))
}
if attr, ok := d.GetOk("options.min_location_failed"); ok {
minLocationFailed, _ := strconv.Atoi(attr.(string))
options.SetMinLocationFailed(minLocationFailed)
options.SetMinLocationFailed(int64(minLocationFailed))
}
if attr, ok := d.GetOk("options.accept_self_signed"); ok {
// for some reason, attr is equal to "1" or "0" in TF 0.11
// so ParseBool is required for retro-compatibility
acceptSelfSigned, _ := strconv.ParseBool(attr.(string))
options.SetAcceptSelfSigned(acceptSelfSigned)
}
if attr, ok := d.GetOk("options.allow_insecure"); ok {
// for some reason, attr is equal to "1" or "0" in TF 0.11
// so ParseBool is required for retro-compatibility
allowInsecure, _ := strconv.ParseBool(attr.(string))
options.SetAllowInsecure(allowInsecure)
}
if attr, ok := d.GetOk("device_ids"); ok {
var deviceIds []string
var deviceIds []datadogV1.SyntheticsDeviceID
for _, s := range attr.([]interface{}) {
deviceIds = append(deviceIds, s.(string))
deviceIds = append(deviceIds, datadogV1.SyntheticsDeviceID(s.(string)))
}
options.DeviceIds = deviceIds
options.DeviceIds = &deviceIds
}

syntheticsTest := datadog.SyntheticsTest{
Name: datadog.String(d.Get("name").(string)),
Type: datadog.String(d.Get("type").(string)),
Config: &config,
Options: &options,
Message: datadog.String(d.Get("message").(string)),
Status: datadog.String(d.Get("status").(string)),
}
syntheticsTest := datadogV1.NewSyntheticsTestDetails()
syntheticsTest.SetName(d.Get("name").(string))
syntheticsTest.SetType(datadogV1.SyntheticsTestDetailsType(d.Get("type").(string)))
syntheticsTest.SetConfig(*config)
syntheticsTest.SetOptions(*options)
syntheticsTest.SetMessage(d.Get("message").(string))
syntheticsTest.SetStatus(datadogV1.SyntheticsTestPauseStatus(d.Get("status").(string)))

if attr, ok := d.GetOk("locations"); ok {
var locations []string
for _, s := range attr.([]interface{}) {
locations = append(locations, s.(string))
}
syntheticsTest.Locations = locations
syntheticsTest.SetLocations(locations)
}

var tags []string
Expand All @@ -400,21 +422,21 @@ func buildSyntheticsTestStruct(d *schema.ResourceData) *datadog.SyntheticsTest {
tags = append(tags, s.(string))
}
}
syntheticsTest.Tags = tags
syntheticsTest.SetTags(tags)

if attr, ok := d.GetOk("subtype"); ok {
syntheticsTest.Subtype = datadog.String(attr.(string))
syntheticsTest.SetSubtype(datadogV1.SyntheticsTestDetailsSubType(attr.(string)))
} else {
if *syntheticsTest.Type == "api" {
if syntheticsTest.GetType() == "api" {
// we want to default to "http" subtype when type is "api"
syntheticsTest.Subtype = datadog.String("http")
syntheticsTest.SetSubtype(datadogV1.SYNTHETICSTESTDETAILSSUBTYPE_HTTP)
}
}

return &syntheticsTest
return syntheticsTest
}

func updateSyntheticsTestLocalState(d *schema.ResourceData, syntheticsTest *datadog.SyntheticsTest) {
func updateSyntheticsTestLocalState(d *schema.ResourceData, syntheticsTest *datadogV1.SyntheticsTestDetails) {
d.Set("type", syntheticsTest.GetType())
if syntheticsTest.HasSubtype() {
d.Set("subtype", syntheticsTest.GetSubtype())
Expand All @@ -426,7 +448,7 @@ func updateSyntheticsTestLocalState(d *schema.ResourceData, syntheticsTest *data
localRequest["body"] = actualRequest.GetBody()
}
if actualRequest.HasMethod() {
localRequest["method"] = actualRequest.GetMethod()
localRequest["method"] = convertToString(actualRequest.GetMethod())
}
if actualRequest.HasTimeout() {
localRequest["timeout"] = convertToString(actualRequest.GetTimeout())
Expand All @@ -447,17 +469,17 @@ func updateSyntheticsTestLocalState(d *schema.ResourceData, syntheticsTest *data
var localAssertions []map[string]string
for _, assertion := range actualAssertions {
localAssertion := make(map[string]string)
if assertion.HasOperator() {
localAssertion["operator"] = assertion.GetOperator()
if v, ok := assertion.GetOperatorOk(); ok {
localAssertion["operator"] = string(*v)
}
if assertion.HasProperty() {
localAssertion["property"] = assertion.GetProperty()
}
if target := assertion.Target; target != nil {
if target := assertion.GetTarget(); target != nil {
localAssertion["target"] = convertToString(target)
}
if assertion.HasType() {
localAssertion["type"] = assertion.GetType()
if v, ok := assertion.GetTypeOk(); ok {
localAssertion["type"] = string(*v)
}
localAssertions = append(localAssertions, localAssertion)
}
Expand All @@ -484,6 +506,9 @@ func updateSyntheticsTestLocalState(d *schema.ResourceData, syntheticsTest *data
if actualOptions.HasAcceptSelfSigned() {
localOptions["accept_self_signed"] = convertToString(actualOptions.GetAcceptSelfSigned())
}
if actualOptions.HasAllowInsecure() {
localOptions["allow_insecure"] = convertToString(actualOptions.GetAllowInsecure())
}

d.Set("options", localOptions)

Expand All @@ -504,6 +529,8 @@ func convertToString(i interface{}) string {
return strconv.FormatFloat(v, 'f', -1, 64)
case string:
return v
case datadogV1.HTTPMethod:
return string(v)
default:
// TODO: manage target for JSON body assertions
valStrr, err := json.Marshal(v)
Expand Down
10 changes: 6 additions & 4 deletions datadog/resource_datadog_synthetics_test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,10 +719,11 @@ resource "datadog_synthetics_test" "bar" {
func testSyntheticsTestExists(accProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
providerConf := accProvider.Meta().(*ProviderConfiguration)
client := providerConf.CommunityClient
datadogClientV1 := providerConf.DatadogClientV1
authV1 := providerConf.AuthV1

for _, r := range s.RootModule().Resources {
if _, err := client.GetSyntheticsTest(r.Primary.ID); err != nil {
if _, _, err := datadogClientV1.SyntheticsApi.GetTest(authV1, r.Primary.ID).Execute(); err != nil {
return fmt.Errorf("received an error retrieving synthetics test %s", err)
}
}
Expand All @@ -733,10 +734,11 @@ func testSyntheticsTestExists(accProvider *schema.Provider) resource.TestCheckFu
func testSyntheticsTestIsDestroyed(accProvider *schema.Provider) resource.TestCheckFunc {
return func(s *terraform.State) error {
providerConf := accProvider.Meta().(*ProviderConfiguration)
client := providerConf.CommunityClient
datadogClientV1 := providerConf.DatadogClientV1
authV1 := providerConf.AuthV1

for _, r := range s.RootModule().Resources {
if _, err := client.GetSyntheticsTest(r.Primary.ID); err != nil {
if _, _, err := datadogClientV1.SyntheticsApi.GetTest(authV1, r.Primary.ID).Execute(); err != nil {
if strings.Contains(err.Error(), "404 Not Found") {
continue
}
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/synthetics.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ The following arguments are supported:
- `min_failure_duration` - (Optional) How long the test should be in failure before alerting (integer, number of seconds, max 7200). Default is 0.
- `min_location_failed` - (Optional) Threshold below which a synthetics test is allowed to fail before sending notifications
- `accept_self_signed` - (Optional) For type=ssl, true or false
- `allow_insecure` - (Optional) For type=api, true or false. Allow your HTTP test go on with connection even if there is an error when validating the certificate.
- `locations` - (Required) Please refer to [Datadog documentation](https://docs.datadoghq.com/synthetics/api_test/#request) for available locations (e.g. "aws:eu-central-1")
- `device_ids` - (Optional) "laptop_large", "tablet" or "mobile_small" (only available if type=browser)
- `status` - (Required) "live", "paused"
Expand Down

0 comments on commit bf37162

Please sign in to comment.