Skip to content

Commit 8a594bb

Browse files
armcburneytherve
andauthored
Properly handle recurring downtimes definitions (#1092)
* [MA-2231] Properly handle recurring downtimes definitions in terraform. * [MA-2231] Fix getID types for active_child_id case. * [MA-2231] Fix updates for active child downtimes. * [MA-2231] Don't set the ID we store in state when we update the active child recurrence downtime. * [MA-2231] Fix build after merging origin/master. * [MA-2231] Remove deprecated golint check. * [MA-2231] Commit autogenerated documentation. * [MA-1784] Ignore start/end changes if the recurring downtime is the active_child. * [MA-1784] Use d.GetOk() interface rather than checking nil. * [MA-1784] Bugfixes for not being able to change start/end on child recurring downtimes. * Remove optional Co-authored-by: Thomas Hervé <[email protected]>
1 parent 2130519 commit 8a594bb

File tree

3 files changed

+159
-25
lines changed

3 files changed

+159
-25
lines changed

GNUmakefile

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ uninstall:
2020
@rm -vf $(DIR)/terraform-provider-datadog
2121

2222
# Run unit tests; these tests don't interact with the API and don't support/need RECORD
23-
test: get-test-deps fmtcheck lint
23+
test: get-test-deps fmtcheck
2424
gotestsum --hide-summary skipped --format testname --debug --packages $(TEST) -- $(TESTARGS) -timeout=30s
2525

2626
# Run acceptance tests (this runs integration CRUD tests through the terraform test framework)
27-
testacc: get-test-deps fmtcheck lint
27+
testacc: get-test-deps fmtcheck
2828
RECORD=$(RECORD) TF_ACC=1 gotestsum --format testname --debug --rerun-fails --packages ./... -- -v $(TESTARGS) -timeout 120m
2929

3030
# Run both unit and acceptance tests
@@ -42,9 +42,6 @@ vet:
4242
exit 1; \
4343
fi
4444

45-
lint: get-test-deps
46-
golint -set_exit_status ./...
47-
4845
fmt:
4946
goimports -format-only -local $(LOCAL_PACKAGE) -w $(GOIMPORTS_FILES)
5047
terraform fmt -recursive examples
@@ -71,7 +68,6 @@ update-go-client:
7168

7269
get-test-deps:
7370
gotestsum --version || (cd `mktemp -d`; GO111MODULE=off GOFLAGS='' go get -u gotest.tools/gotestsum; cd -)
74-
which golint || (cd `mktemp -d`; GO111MODULE=off GOFLAGS='' go get -u golang.org/x/lint/golint; cd -)
7571

7672
license-check:
7773
@sh -c "'$(CURDIR)/scripts/license-check.sh'"
@@ -84,9 +80,9 @@ docs: tools
8480

8581
check-docs: docs
8682
@if [ "`git status --porcelain docs/`" ]; then \
87-
echo "Uncommitted changes were detected in the autogenerated docs folder. Please run 'make docs' to autogenerate the docs, and commit the changes" && echo `git status --porcelain docs/` && exit 1; \
83+
echo "Uncommitted changes were detected in the autogenerated docs folder. Please run 'make docs' to autogenerate the docs, and commit the changes" && echo `git status --porcelain docs/` && exit 1; \
8884
else \
89-
echo "Success: No generated documentation changes detected"; \
85+
echo "Success: No generated documentation changes detected"; \
9086
fi
9187

9288
.PHONY: build check-docs docs test testall testacc cassettes vet fmt fmtcheck errcheck test-compile tools get-test-deps license-check

datadog/resource_datadog_downtime.go

Lines changed: 154 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package datadog
22

33
import (
44
"context"
5+
"fmt"
56
"log"
67
"reflect"
78
"strconv"
@@ -20,6 +21,85 @@ import (
2021
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
2122
)
2223

24+
type downtimeOrDowntimeChild interface {
25+
GetId() int64
26+
GetActive() bool
27+
GetDisabled() bool
28+
GetMessage() string
29+
GetMonitorIdOk() (*int64, bool)
30+
GetTimezone() string
31+
GetRecurrenceOk() (*datadogV1.DowntimeRecurrence, bool)
32+
GetMonitorTags() []string
33+
GetStart() int64
34+
GetEnd() int64
35+
GetScope() []string
36+
GetActiveChild() datadogV1.DowntimeChild
37+
GetActiveChildOk() (*datadogV1.DowntimeChild, bool)
38+
GetCanceledOk() (*int64, bool)
39+
}
40+
41+
// downtimeChild wraps the `datadogV1.DowntimeChild` struct via embedding to implement `downtimeOrDowntimeChild`
42+
// interface missing the `GetActiveChild`, `GetActiveChildOk` methods.
43+
type downtimeChild struct {
44+
child *datadogV1.DowntimeChild
45+
}
46+
47+
func (d *downtimeChild) GetId() int64 {
48+
return d.child.GetId()
49+
}
50+
51+
func (d *downtimeChild) GetActive() bool {
52+
return d.child.GetActive()
53+
}
54+
55+
func (d *downtimeChild) GetDisabled() bool {
56+
return d.child.GetDisabled()
57+
}
58+
59+
func (d *downtimeChild) GetMessage() string {
60+
return d.child.GetMessage()
61+
}
62+
63+
func (d *downtimeChild) GetMonitorIdOk() (*int64, bool) {
64+
return d.child.GetMonitorIdOk()
65+
}
66+
67+
func (d *downtimeChild) GetTimezone() string {
68+
return d.child.GetTimezone()
69+
}
70+
71+
func (d *downtimeChild) GetRecurrenceOk() (*datadogV1.DowntimeRecurrence, bool) {
72+
return d.child.GetRecurrenceOk()
73+
}
74+
75+
func (d *downtimeChild) GetMonitorTags() []string {
76+
return d.child.GetMonitorTags()
77+
}
78+
79+
func (d *downtimeChild) GetStart() int64 {
80+
return d.child.GetStart()
81+
}
82+
83+
func (d *downtimeChild) GetEnd() int64 {
84+
return d.child.GetEnd()
85+
}
86+
87+
func (d *downtimeChild) GetScope() []string {
88+
return d.child.GetScope()
89+
}
90+
91+
func (d *downtimeChild) GetActiveChild() datadogV1.DowntimeChild {
92+
return datadogV1.DowntimeChild{}
93+
}
94+
95+
func (d *downtimeChild) GetActiveChildOk() (*datadogV1.DowntimeChild, bool) {
96+
return nil, false
97+
}
98+
99+
func (d *downtimeChild) GetCanceledOk() (*int64, bool) {
100+
return d.child.GetCanceledOk()
101+
}
102+
23103
func resourceDatadogDowntime() *schema.Resource {
24104
return &schema.Resource{
25105
Description: "Provides a Datadog downtime resource. This can be used to create and manage Datadog downtimes.",
@@ -30,7 +110,6 @@ func resourceDatadogDowntime() *schema.Resource {
30110
Importer: &schema.ResourceImporter{
31111
StateContext: schema.ImportStatePassthroughContext,
32112
},
33-
34113
Schema: map[string]*schema.Schema{
35114
"active": {
36115
Type: schema.TypeBool,
@@ -48,6 +127,7 @@ func resourceDatadogDowntime() *schema.Resource {
48127
DiffSuppressFunc: func(k, oldVal, newVal string, d *schema.ResourceData) bool {
49128
_, startDatePresent := d.GetOk("start_date")
50129
now := time.Now().Unix()
130+
51131
// If "start_date" is set, ignore diff for "start". If "start" isn't set, ignore diff if start is now or in the past
52132
return startDatePresent || (newVal == "0" && oldVal != "0" && int64(d.Get("start").(int)) <= now)
53133
},
@@ -167,6 +247,11 @@ func resourceDatadogDowntime() *schema.Resource {
167247
ConflictsWith: []string{"monitor_id"},
168248
Elem: &schema.Schema{Type: schema.TypeString},
169249
},
250+
"active_child_id": {
251+
Type: schema.TypeInt,
252+
Computed: true,
253+
Description: "The id corresponding to the downtime object definition of the active child for the original parent recurring downtime. This field will only exist on recurring downtimes.",
254+
},
170255
},
171256
}
172257
}
@@ -227,8 +312,9 @@ func buildDowntimeStruct(ctx context.Context, d *schema.ResourceData, client *da
227312
var dt datadogV1.Downtime
228313
var currentStart = *datadogV1.PtrInt64(0)
229314
var currentEnd = *datadogV1.PtrInt64(0)
315+
230316
if updating {
231-
id, err := strconv.ParseInt(d.Id(), 10, 64)
317+
id, err := getID(d)
232318
if err != nil {
233319
return nil, err
234320
}
@@ -320,7 +406,7 @@ func resourceDatadogDowntimeCreate(ctx context.Context, d *schema.ResourceData,
320406

321407
d.SetId(strconv.Itoa(int(dt.GetId())))
322408

323-
return updateDowntimeState(d, &dt)
409+
return updateDowntimeState(d, &dt, true)
324410
}
325411

326412
func resourceDatadogDowntimeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -342,15 +428,28 @@ func resourceDatadogDowntimeRead(ctx context.Context, d *schema.ResourceData, me
342428
return utils.TranslateClientErrorDiag(err, httpresp.Request.URL, "error getting downtime")
343429
}
344430

431+
// Hack for recurring downtimes, compare the downtime definition in state with the most recent recurring child
432+
// downtime definition returned by the API. Fields which change on each subsequent reschedule will not be compared
433+
// (i.e., start and end), but will be mutated if the terraform resource definition changes (since we update the active
434+
// child downtime we keep a reference to in the terraform state).
435+
if activeChild, ok := dt.GetActiveChildOk(); ok && activeChild != nil {
436+
child := &downtimeChild{activeChild}
437+
if canceled, ok := child.GetCanceledOk(); ok && canceled != nil {
438+
d.SetId("")
439+
return nil
440+
}
441+
442+
return updateDowntimeState(d, child, false)
443+
}
444+
345445
if canceled, ok := dt.GetCanceledOk(); ok && canceled != nil {
346446
d.SetId("")
347447
return nil
348448
}
349-
350-
return updateDowntimeState(d, &dt)
449+
return updateDowntimeState(d, &dt, true)
351450
}
352451

353-
func updateDowntimeState(d *schema.ResourceData, dt *datadogV1.Downtime) diag.Diagnostics {
452+
func updateDowntimeState(d *schema.ResourceData, dt downtimeOrDowntimeChild, updateBounds bool) diag.Diagnostics {
354453
log.Printf("[DEBUG] downtime: %v", dt)
355454

356455
if err := d.Set("active", dt.GetActive()); err != nil {
@@ -359,9 +458,6 @@ func updateDowntimeState(d *schema.ResourceData, dt *datadogV1.Downtime) diag.Di
359458
if err := d.Set("disabled", dt.GetDisabled()); err != nil {
360459
return diag.FromErr(err)
361460
}
362-
if err := d.Set("end", dt.GetEnd()); err != nil {
363-
return diag.FromErr(err)
364-
}
365461
if err := d.Set("message", dt.GetMessage()); err != nil {
366462
return diag.FromErr(err)
367463
}
@@ -403,7 +499,7 @@ func updateDowntimeState(d *schema.ResourceData, dt *datadogV1.Downtime) diag.Di
403499
return diag.FromErr(err)
404500
}
405501
}
406-
if err := d.Set("scope", dt.Scope); err != nil {
502+
if err := d.Set("scope", dt.GetScope()); err != nil {
407503
return diag.FromErr(err)
408504
}
409505
// See the comment for monitor_tags in the schema definition above
@@ -412,8 +508,30 @@ func updateDowntimeState(d *schema.ResourceData, dt *datadogV1.Downtime) diag.Di
412508
return diag.FromErr(err)
413509
}
414510
}
415-
if err := d.Set("start", dt.GetStart()); err != nil {
416-
return diag.FromErr(err)
511+
512+
// Don't set the `start`, `end` stored in terraform unless in specific cases for recurring downtimes.
513+
if updateBounds {
514+
if err := d.Set("start", dt.GetStart()); err != nil {
515+
return diag.FromErr(err)
516+
}
517+
if err := d.Set("end", dt.GetEnd()); err != nil {
518+
return diag.FromErr(err)
519+
}
520+
}
521+
522+
switch dt.(type) {
523+
case *datadogV1.Downtime:
524+
if attr, ok := dt.GetActiveChildOk(); ok {
525+
if err := d.Set("active_child_id", attr.GetId()); err != nil {
526+
return diag.FromErr(err)
527+
}
528+
}
529+
case *downtimeChild:
530+
if err := d.Set("active_child_id", dt.GetId()); err != nil {
531+
return diag.FromErr(err)
532+
}
533+
default:
534+
return diag.FromErr(fmt.Errorf("unsupported interface passed into updateDowntimeState"))
417535
}
418536
return nil
419537
}
@@ -427,10 +545,12 @@ func resourceDatadogDowntimeUpdate(ctx context.Context, d *schema.ResourceData,
427545
if err != nil {
428546
return diag.Errorf("failed to parse resource configuration: %s", err.Error())
429547
}
430-
id, err := strconv.ParseInt(d.Id(), 10, 64)
548+
549+
id, err := getID(d)
431550
if err != nil {
432551
return diag.FromErr(err)
433552
}
553+
434554
// above downtimeStruct returns nil if downtime is not set. Hence, if we are handling the cases where downtime
435555
// is replaced, the ID of the downtime will be set to 0.
436556
dt.SetId(id)
@@ -439,18 +559,23 @@ func resourceDatadogDowntimeUpdate(ctx context.Context, d *schema.ResourceData,
439559
if err != nil {
440560
return utils.TranslateClientErrorDiag(err, httpresp.Request.URL, "error updating downtime")
441561
}
442-
// handle the case when a downtime is replaced
443-
d.SetId(strconv.FormatInt(dt.GetId(), 10))
444562

445-
return updateDowntimeState(d, &updatedDowntime)
563+
// Handle the case when a downtime is replaced. Don't set it if the `active_child_id` is set as we want to maintain
564+
// a reference to the original parent downtime ID.
565+
_, ok := d.GetOk("active_child_id")
566+
if !ok {
567+
d.SetId(strconv.FormatInt(dt.GetId(), 10))
568+
}
569+
570+
return updateDowntimeState(d, &updatedDowntime, !ok)
446571
}
447572

448573
func resourceDatadogDowntimeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
449574
providerConf := meta.(*ProviderConfiguration)
450575
datadogClientV1 := providerConf.DatadogClientV1
451576
authV1 := providerConf.AuthV1
452577

453-
id, err := strconv.ParseInt(d.Id(), 10, 64)
578+
id, err := getID(d)
454579
if err != nil {
455580
return diag.FromErr(err)
456581
}
@@ -461,3 +586,15 @@ func resourceDatadogDowntimeDelete(ctx context.Context, d *schema.ResourceData,
461586

462587
return nil
463588
}
589+
590+
func getID(d *schema.ResourceData) (int64, error) {
591+
id, err := strconv.ParseInt(d.Id(), 10, 64)
592+
if err != nil {
593+
return 0, err
594+
}
595+
596+
if activeChildID, ok := d.GetOk("active_child_id"); ok {
597+
id = int64(activeChildID.(int))
598+
}
599+
return id, nil
600+
}

docs/resources/downtime.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ resource "datadog_downtime" "foo" {
6363
### Read-Only
6464

6565
- **active** (Boolean) When true indicates this downtime is being actively applied
66+
- **active_child_id** (Number) The id corresponding to the downtime object definition of the active child for the original parent recurring downtime. This field will only exist on recurring downtimes.
6667
- **disabled** (Boolean) When true indicates this downtime is not being applied
6768
- **id** (String) The ID of this resource.
6869

0 commit comments

Comments
 (0)