From e167e735124ae570dc5bc451328a2fbf18a34370 Mon Sep 17 00:00:00 2001
From: obs-gh-owengoebel <159819293+obs-gh-owengoebel@users.noreply.github.com>
Date: Mon, 5 Aug 2024 16:31:05 -0700
Subject: [PATCH] feat: add ability to link monitorv2 to shared actions (#134)
---
client/api.go | 8 +
.../internal/meta/operation/monitorv2.graphql | 23 +-
.../meta/schema/monitorv2_extend.graphql | 4 +-
client/meta/genqlient.generated.go | 291 ++++++++++++++++++
client/meta/monitorv2.go | 5 +
client/oid/oid.go | 4 +
docs/data-sources/monitor_v2.md | 10 +
docs/resources/monitor_v2.md | 13 +
observe/data_source_monitor_v2.go | 22 ++
observe/descriptions/monitorv2.yaml | 9 +-
observe/resource_monitor_v2.go | 109 ++++++-
observe/resource_monitor_v2_action_test.go | 140 +++++++++
12 files changed, 630 insertions(+), 8 deletions(-)
diff --git a/client/api.go b/client/api.go
index ae61c133..e4c51852 100644
--- a/client/api.go
+++ b/client/api.go
@@ -521,6 +521,14 @@ func (c *Client) DeleteMonitorV2(ctx context.Context, id string) error {
return c.Meta.DeleteMonitorV2(ctx, id)
}
+func (c *Client) SaveMonitorV2Relations(ctx context.Context, monitorId string, actionRelations []meta.ActionRelationInput) (*meta.MonitorV2, error) {
+ if !c.Flags[flagObs2110] {
+ c.obs2110.Lock()
+ defer c.obs2110.Unlock()
+ }
+ return c.Meta.SaveMonitorV2Relations(ctx, monitorId, actionRelations)
+}
+
func (c *Client) GetMonitorV2(ctx context.Context, id string) (*meta.MonitorV2, error) {
return c.Meta.GetMonitorV2(ctx, id)
}
diff --git a/client/internal/meta/operation/monitorv2.graphql b/client/internal/meta/operation/monitorv2.graphql
index ef92f657..c6500c78 100644
--- a/client/internal/meta/operation/monitorv2.graphql
+++ b/client/internal/meta/operation/monitorv2.graphql
@@ -150,6 +150,11 @@ fragment MonitorV2SearchResult on MonitorV2SearchResult {
}
}
+fragment MonitorV2ActionRule on MonitorV2ActionRule {
+ actionID
+ levels
+}
+
# @genqlient(for: "MonitorV2Input.comment", omitempty: true)
# @genqlient(for: "MonitorV2Input.iconUrl", omitempty: true)
# @genqlient(for: "MonitorV2Input.description", omitempty: true)
@@ -191,6 +196,10 @@ fragment MonitorV2 on MonitorV2 {
definition {
...MonitorV2Definition
}
+ # @genqlient(flatten: true)
+ actionRules {
+ ...MonitorV2ActionRule
+ }
}
# definitions of monitorv2 CRUD ops
@@ -282,4 +291,16 @@ query lookupMonitorV2($workspaceId: ObjectId, $folderId: ObjectId, $nameExact: S
monitorV2s: searchMonitorV2(workspaceId: $workspaceId, folderId: $folderId, nameExact: $nameExact, nameSubstring: $nameSubstring) {
...MonitorV2SearchResult
}
-}
\ No newline at end of file
+}
+
+# @genqlient(for: "ActionDestinationLinkInput.sendEndNotifications", omitempty: true)
+# @genqlient(for: "ActionDestinationLinkInput.sendRemindersInterval", omitempty: true)
+mutation saveMonitorV2Relations(
+ $monitorId: ObjectId!,
+ $actionRelations: [ActionRelationInput!]
+) {
+ # @genqlient(flatten: true)
+ monitorV2: saveMonitorV2Relations(monitorId: $monitorId, actionRelations: $actionRelations) {
+ ...MonitorV2
+ }
+}
diff --git a/client/internal/meta/schema/monitorv2_extend.graphql b/client/internal/meta/schema/monitorv2_extend.graphql
index 683d77f3..845cc601 100644
--- a/client/internal/meta/schema/monitorv2_extend.graphql
+++ b/client/internal/meta/schema/monitorv2_extend.graphql
@@ -38,7 +38,7 @@ extend type Mutation {
relationships with the destinations are mutateable.Hence, this API will error out if you provide destinationLinks
where the action is shared.
"""
- saveMonitorV2Relations(monitorId: ObjectId!, actionRelations: [ActionRelationInput!]!): MonitorV2!
+ saveMonitorV2Relations(monitorId: ObjectId!, actionRelations: [ActionRelationInput!]): MonitorV2!
"""
saveActionsWithDestinations replaces all action's links to the destinations (MonitorV2) for the provided
@@ -47,7 +47,7 @@ extend type Mutation {
The purpose of the API is such that the users can create a shared action and make links to the destinations
from the shared actions page or when an action has been shared from within the monitor editing page.
"""
- saveActionWithDestinationLinks(actionId: ObjectId!, destinationLinks: [ActionDestinationLinkInput!]!): MonitorV2Action!
+ saveActionWithDestinationLinks(actionId: ObjectId!, destinationLinks: [ActionDestinationLinkInput!]): MonitorV2Action!
"""
terminateMonitorV2Alarm allows an explicit termination of an active alarm. The purpose is to
diff --git a/client/meta/genqlient.generated.go b/client/meta/genqlient.generated.go
index 2a168211..edb89db6 100644
--- a/client/meta/genqlient.generated.go
+++ b/client/meta/genqlient.generated.go
@@ -91,6 +91,18 @@ func (v *ActionInput) GetEmail() *EmailActionInput { return v.Email }
// GetWebhook returns ActionInput.Webhook, and is useful for accessing the field via an interface.
func (v *ActionInput) GetWebhook() *WebhookActionInput { return v.Webhook }
+// ActionRelationInput maps the action's relationship to the destinations the user desires to link with.
+type ActionRelationInput struct {
+ ActionRule MonitorV2ActionRuleInput `json:"actionRule"`
+ DestLinks []ActionDestinationLinkInput `json:"destLinks"`
+}
+
+// GetActionRule returns ActionRelationInput.ActionRule, and is useful for accessing the field via an interface.
+func (v *ActionRelationInput) GetActionRule() MonitorV2ActionRuleInput { return v.ActionRule }
+
+// GetDestLinks returns ActionRelationInput.DestLinks, and is useful for accessing the field via an interface.
+func (v *ActionRelationInput) GetDestLinks() []ActionDestinationLinkInput { return v.DestLinks }
+
type AggregateFunction string
const (
@@ -4402,6 +4414,10 @@ type MonitorV2 struct {
// Describes the type of each of the rules in the definition (they must all be the same type).
RuleKind MonitorV2RuleKind `json:"ruleKind"`
Definition MonitorV2Definition `json:"definition"`
+ // List of actions and conditions for dispatching. Each entry will
+ // contain the action definition regardless of whether the definition is
+ // shared or provided inline.
+ ActionRules []MonitorV2ActionRule `json:"actionRules"`
}
// GetId returns MonitorV2.Id, and is useful for accessing the field via an interface.
@@ -4443,6 +4459,9 @@ func (v *MonitorV2) GetRuleKind() MonitorV2RuleKind { return v.RuleKind }
// GetDefinition returns MonitorV2.Definition, and is useful for accessing the field via an interface.
func (v *MonitorV2) GetDefinition() MonitorV2Definition { return v.Definition }
+// GetActionRules returns MonitorV2.ActionRules, and is useful for accessing the field via an interface.
+func (v *MonitorV2) GetActionRules() []MonitorV2ActionRule { return v.ActionRules }
+
// MonitorV2Action includes the GraphQL fields of MonitorV2Action requested by the fragment MonitorV2Action.
type MonitorV2Action struct {
// The inline field determines whether the object is inlined within another object or not. If not inlined, it can be shared with other objects.
@@ -4538,6 +4557,31 @@ func (v *MonitorV2ActionInput) GetManagedById() *string { return v.ManagedById }
// GetFolderId returns MonitorV2ActionInput.FolderId, and is useful for accessing the field via an interface.
func (v *MonitorV2ActionInput) GetFolderId() *string { return v.FolderId }
+// MonitorV2ActionRule includes the GraphQL fields of MonitorV2ActionRule requested by the fragment MonitorV2ActionRule.
+type MonitorV2ActionRule struct {
+ // Takes in a private or public action id created from an earlier createAction API call.
+ ActionID string `json:"actionID"`
+ // Dispatch this action when the alarm matches any of the provided levels.
+ Levels []MonitorV2AlarmLevel `json:"levels"`
+}
+
+// GetActionID returns MonitorV2ActionRule.ActionID, and is useful for accessing the field via an interface.
+func (v *MonitorV2ActionRule) GetActionID() string { return v.ActionID }
+
+// GetLevels returns MonitorV2ActionRule.Levels, and is useful for accessing the field via an interface.
+func (v *MonitorV2ActionRule) GetLevels() []MonitorV2AlarmLevel { return v.Levels }
+
+type MonitorV2ActionRuleInput struct {
+ ActionID string `json:"actionID"`
+ Levels []MonitorV2AlarmLevel `json:"levels"`
+}
+
+// GetActionID returns MonitorV2ActionRuleInput.ActionID, and is useful for accessing the field via an interface.
+func (v *MonitorV2ActionRuleInput) GetActionID() string { return v.ActionID }
+
+// GetLevels returns MonitorV2ActionRuleInput.Levels, and is useful for accessing the field via an interface.
+func (v *MonitorV2ActionRuleInput) GetLevels() []MonitorV2AlarmLevel { return v.Levels }
+
// MonitorV2ActionType defines the type of monitor returned when querying all
// actions for a monitor.
type MonitorV2ActionType string
@@ -9384,6 +9428,20 @@ func (v *__saveDatasetInput) GetQuery() MultiStageQueryInput { return v.Query }
// GetDep returns __saveDatasetInput.Dep, and is useful for accessing the field via an interface.
func (v *__saveDatasetInput) GetDep() *DependencyHandlingInput { return v.Dep }
+// __saveMonitorV2RelationsInput is used internally by genqlient
+type __saveMonitorV2RelationsInput struct {
+ MonitorId string `json:"monitorId"`
+ ActionRelations []ActionRelationInput `json:"actionRelations"`
+}
+
+// GetMonitorId returns __saveMonitorV2RelationsInput.MonitorId, and is useful for accessing the field via an interface.
+func (v *__saveMonitorV2RelationsInput) GetMonitorId() string { return v.MonitorId }
+
+// GetActionRelations returns __saveMonitorV2RelationsInput.ActionRelations, and is useful for accessing the field via an interface.
+func (v *__saveMonitorV2RelationsInput) GetActionRelations() []ActionRelationInput {
+ return v.ActionRelations
+}
+
// __saveSourceDatasetInput is used internally by genqlient
type __saveSourceDatasetInput struct {
WorkspaceId string `json:"workspaceId"`
@@ -11228,6 +11286,21 @@ type saveDatasetResponse struct {
// GetDataset returns saveDatasetResponse.Dataset, and is useful for accessing the field via an interface.
func (v *saveDatasetResponse) GetDataset() *saveDatasetDatasetDatasetSaveResult { return v.Dataset }
+// saveMonitorV2RelationsResponse is returned by saveMonitorV2Relations on success.
+type saveMonitorV2RelationsResponse struct {
+ // saveMonitorV2Relations replaces all monitor relations (MonitorV2ActionRule, ActionDestinationLink)
+ // for the provided monitor with the provided list of actionRules and destinationLinks.
+ // Shared Actions can't be mutated through this call other than attaching it to the monitor, so you will need to used
+ // saveActionWithDestinationLinks to mutate sharedAction's links to the destinations.
+ // It does not allow you to mutate any shared actions' relationships with the destinations. Only the inlined actions'
+ // relationships with the destinations are mutateable.Hence, this API will error out if you provide destinationLinks
+ // where the action is shared.
+ MonitorV2 MonitorV2 `json:"monitorV2"`
+}
+
+// GetMonitorV2 returns saveMonitorV2RelationsResponse.MonitorV2, and is useful for accessing the field via an interface.
+func (v *saveMonitorV2RelationsResponse) GetMonitorV2() MonitorV2 { return v.MonitorV2 }
+
// saveSourceDatasetDatasetDatasetSaveResult includes the requested fields of the GraphQL type DatasetSaveResult.
type saveSourceDatasetDatasetDatasetSaveResult struct {
// this is what you got out when saving
@@ -12789,6 +12862,9 @@ fragment MonitorV2 on MonitorV2 {
definition {
... MonitorV2Definition
}
+ actionRules {
+ ... MonitorV2ActionRule
+ }
}
fragment MonitorV2Definition on MonitorV2Definition {
inputQuery {
@@ -12809,6 +12885,10 @@ fragment MonitorV2Definition on MonitorV2Definition {
... MonitorV2Scheduling
}
}
+fragment MonitorV2ActionRule on MonitorV2ActionRule {
+ actionID
+ levels
+}
fragment StageQuery on StageQuery {
id
pipeline
@@ -16332,6 +16412,9 @@ fragment MonitorV2 on MonitorV2 {
definition {
... MonitorV2Definition
}
+ actionRules {
+ ... MonitorV2ActionRule
+ }
}
fragment MonitorV2Definition on MonitorV2Definition {
inputQuery {
@@ -16352,6 +16435,10 @@ fragment MonitorV2Definition on MonitorV2Definition {
... MonitorV2Scheduling
}
}
+fragment MonitorV2ActionRule on MonitorV2ActionRule {
+ actionID
+ levels
+}
fragment StageQuery on StageQuery {
id
pipeline
@@ -17880,6 +17967,9 @@ fragment MonitorV2 on MonitorV2 {
definition {
... MonitorV2Definition
}
+ actionRules {
+ ... MonitorV2ActionRule
+ }
}
fragment MonitorV2Definition on MonitorV2Definition {
inputQuery {
@@ -17900,6 +17990,10 @@ fragment MonitorV2Definition on MonitorV2Definition {
... MonitorV2Scheduling
}
}
+fragment MonitorV2ActionRule on MonitorV2ActionRule {
+ actionID
+ levels
+}
fragment StageQuery on StageQuery {
id
pipeline
@@ -18508,6 +18602,196 @@ func saveDataset(
return &data, err
}
+// The query or mutation executed by saveMonitorV2Relations.
+const saveMonitorV2Relations_Operation = `
+mutation saveMonitorV2Relations ($monitorId: ObjectId!, $actionRelations: [ActionRelationInput!]) {
+ monitorV2: saveMonitorV2Relations(monitorId: $monitorId, actionRelations: $actionRelations) {
+ ... MonitorV2
+ }
+}
+fragment MonitorV2 on MonitorV2 {
+ id
+ workspaceId
+ createdBy
+ createdDate
+ name
+ iconUrl
+ description
+ managedById
+ folderId
+ comment
+ rollupStatus
+ ruleKind
+ definition {
+ ... MonitorV2Definition
+ }
+ actionRules {
+ ... MonitorV2ActionRule
+ }
+}
+fragment MonitorV2Definition on MonitorV2Definition {
+ inputQuery {
+ outputStage
+ stages {
+ ... StageQuery
+ }
+ }
+ rules {
+ ... MonitorV2Rule
+ }
+ lookbackTime
+ dataStabilizationDelay
+ groupings {
+ ... MonitorV2Column
+ }
+ scheduling {
+ ... MonitorV2Scheduling
+ }
+}
+fragment MonitorV2ActionRule on MonitorV2ActionRule {
+ actionID
+ levels
+}
+fragment StageQuery on StageQuery {
+ id
+ pipeline
+ params
+ layout
+ input {
+ inputName
+ inputRole
+ datasetId
+ datasetPath
+ stageId
+ }
+}
+fragment MonitorV2Rule on MonitorV2Rule {
+ level
+ count {
+ ... MonitorV2CountRule
+ }
+ threshold {
+ ... MonitorV2ThresholdRule
+ }
+ promote {
+ ... MonitorV2PromoteRule
+ }
+}
+fragment MonitorV2Column on MonitorV2Column {
+ linkColumn {
+ ... MonitorV2LinkColumn
+ }
+ columnPath {
+ ... MonitorV2ColumnPath
+ }
+}
+fragment MonitorV2Scheduling on MonitorV2Scheduling {
+ interval {
+ ... MonitorV2IntervalSchedule
+ }
+ transform {
+ ... MonitorV2TransformSchedule
+ }
+}
+fragment MonitorV2CountRule on MonitorV2CountRule {
+ compareValues {
+ ... MonitorV2Comparison
+ }
+ compareGroups {
+ ... MonitorV2ColumnComparison
+ }
+}
+fragment MonitorV2ThresholdRule on MonitorV2ThresholdRule {
+ compareValues {
+ ... MonitorV2Comparison
+ }
+ valueColumnName
+ aggregation
+ compareGroups {
+ ... MonitorV2ColumnComparison
+ }
+}
+fragment MonitorV2PromoteRule on MonitorV2PromoteRule {
+ compareColumns {
+ ... MonitorV2ColumnComparison
+ }
+}
+fragment MonitorV2LinkColumn on MonitorV2LinkColumn {
+ name
+ meta {
+ ... MonitorV2LinkColumnMeta
+ }
+}
+fragment MonitorV2ColumnPath on MonitorV2ColumnPath {
+ name
+ path
+}
+fragment MonitorV2IntervalSchedule on MonitorV2IntervalSchedule {
+ interval
+ randomize
+}
+fragment MonitorV2TransformSchedule on MonitorV2TransformSchedule {
+ freshnessGoal
+}
+fragment MonitorV2Comparison on MonitorV2Comparison {
+ compareFn
+ compareValue {
+ ... PrimitiveValue
+ }
+}
+fragment MonitorV2ColumnComparison on MonitorV2ColumnComparison {
+ column {
+ ... MonitorV2Column
+ }
+ compareValues {
+ ... MonitorV2Comparison
+ }
+}
+fragment MonitorV2LinkColumnMeta on MonitorV2LinkColumnMeta {
+ srcFields {
+ ... MonitorV2ColumnPath
+ }
+ dstFields
+ targetDataset
+}
+fragment PrimitiveValue on PrimitiveValue {
+ bool
+ float64
+ int64
+ string
+ timestamp
+ duration
+}
+`
+
+func saveMonitorV2Relations(
+ ctx context.Context,
+ client graphql.Client,
+ monitorId string,
+ actionRelations []ActionRelationInput,
+) (*saveMonitorV2RelationsResponse, error) {
+ req := &graphql.Request{
+ OpName: "saveMonitorV2Relations",
+ Query: saveMonitorV2Relations_Operation,
+ Variables: &__saveMonitorV2RelationsInput{
+ MonitorId: monitorId,
+ ActionRelations: actionRelations,
+ },
+ }
+ var err error
+
+ var data saveMonitorV2RelationsResponse
+ resp := &graphql.Response{Data: &data}
+
+ err = client.MakeRequest(
+ ctx,
+ req,
+ resp,
+ )
+
+ return &data, err
+}
+
// The query or mutation executed by saveSourceDataset.
const saveSourceDataset_Operation = `
mutation saveSourceDataset ($workspaceId: ObjectId!, $datasetDefinition: DatasetDefinitionInput!, $sourceTable: SourceTableDefinitionInput!, $dep: DependencyHandlingInput) {
@@ -19903,6 +20187,9 @@ fragment MonitorV2 on MonitorV2 {
definition {
... MonitorV2Definition
}
+ actionRules {
+ ... MonitorV2ActionRule
+ }
}
fragment MonitorV2Definition on MonitorV2Definition {
inputQuery {
@@ -19923,6 +20210,10 @@ fragment MonitorV2Definition on MonitorV2Definition {
... MonitorV2Scheduling
}
}
+fragment MonitorV2ActionRule on MonitorV2ActionRule {
+ actionID
+ levels
+}
fragment StageQuery on StageQuery {
id
pipeline
diff --git a/client/meta/monitorv2.go b/client/meta/monitorv2.go
index fdd00ac8..cf62a7e6 100644
--- a/client/meta/monitorv2.go
+++ b/client/meta/monitorv2.go
@@ -38,6 +38,11 @@ func (client *Client) DeleteMonitorV2(ctx context.Context, id string) error {
return resultStatusError(resp, err)
}
+func (client *Client) SaveMonitorV2Relations(ctx context.Context, monitorId string, actionRelations []ActionRelationInput) (*MonitorV2, error) {
+ resp, err := saveMonitorV2Relations(ctx, client.Gql, monitorId, actionRelations)
+ return monitorV2OrError(resp, err)
+}
+
func (client *Client) LookupMonitorV2(ctx context.Context, workspaceId *string, nameExact *string) (*MonitorV2, error) {
resp, err := lookupMonitorV2(ctx, client.Gql, workspaceId, nil, nameExact, nil)
if err != nil || resp == nil || len(resp.MonitorV2s.Results) != 1 {
diff --git a/client/oid/oid.go b/client/oid/oid.go
index 3602c856..2bc7e010 100644
--- a/client/oid/oid.go
+++ b/client/oid/oid.go
@@ -234,6 +234,10 @@ func MonitorV2Oid(id string) OID {
return OID{Id: id, Type: TypeMonitorV2}
}
+func MonitorV2ActionOid(id string) OID {
+ return OID{Id: id, Type: TypeMonitorV2Action}
+}
+
func PollerOid(id string) OID {
return OID{Id: id, Type: TypePoller}
}
diff --git a/docs/data-sources/monitor_v2.md b/docs/data-sources/monitor_v2.md
index 94199da4..d3335cbe 100644
--- a/docs/data-sources/monitor_v2.md
+++ b/docs/data-sources/monitor_v2.md
@@ -32,6 +32,7 @@ template and destinations to configure the receiver.
### Read-Only
+- `actions` (Block List) The list of shared actions to which this monitor is connected. (see [below for nested schema](#nestedblock--actions))
- `comment` (String) A longer description of the monitor. This can include details like how to resolve the issue, links to runbooks, etc.
- `data_stabilization_delay` (String) expresses the minimum time that should elapse before data is considered "good enough" to evaluate. Choosing a delay really depends on the expectations of latency of data and whether data is expected to arrive later than other data and thus would change previously evaluated results.
- `description` (String) A brief description of the monitor.
@@ -48,6 +49,15 @@ stage pipelines.
input is provided, a stage will implicitly follow on from the result of
its predecessor. (see [below for nested schema](#nestedblock--stage))
+
+### Nested Schema for `actions`
+
+Read-Only:
+
+- `levels` (List of String) The alarm level(s) at which this monitor should trigger this shared action.
+- `oid` (String) The OID of this shared action.
+
+
### Nested Schema for `groupings`
diff --git a/docs/resources/monitor_v2.md b/docs/resources/monitor_v2.md
index 33289bd0..8923bcee 100644
--- a/docs/resources/monitor_v2.md
+++ b/docs/resources/monitor_v2.md
@@ -36,6 +36,7 @@ its predecessor. (see [below for nested schema](#nestedblock--stage))
### Optional
+- `actions` (Block List) The list of shared actions to which this monitor is connected. (see [below for nested schema](#nestedblock--actions))
- `comment` (String) A longer description of the monitor. This can include details like how to resolve the issue, links to runbooks, etc.
- `data_stabilization_delay` (String) expresses the minimum time that should elapse before data is considered "good enough" to evaluate. Choosing a delay really depends on the expectations of latency of data and whether data is expected to arrive later than other data and thus would change previously evaluated results.
- `description` (String) A brief description of the monitor.
@@ -416,6 +417,18 @@ a stage preceding the last stage. The last stage is an output stage by default.
- `pipeline` (String) An OPAL snippet defining a transformation on the selected input.
+
+### Nested Schema for `actions`
+
+Required:
+
+- `oid` (String) The OID of this shared action.
+
+Optional:
+
+- `levels` (List of String) The alarm level(s) at which this monitor should trigger this shared action.
+
+
### Nested Schema for `groupings`
diff --git a/observe/data_source_monitor_v2.go b/observe/data_source_monitor_v2.go
index a4cb8064..37aaf442 100644
--- a/observe/data_source_monitor_v2.go
+++ b/observe/data_source_monitor_v2.go
@@ -250,6 +250,28 @@ func dataSourceMonitorV2() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
+ // the following field describes how monitorv2 is connected to shared actions.
+ "actions": { // [MonitorV2ActionRuleInput]
+ Type: schema.TypeList,
+ Optional: true,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "oid": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: descriptions.Get("monitorv2", "schema", "actions", "oid"),
+ },
+ "levels": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: descriptions.Get("monitorv2", "schema", "actions", "levels"),
+ },
+ },
+ },
+ Description: descriptions.Get("monitorv2", "schema", "actions", "description"),
+ },
},
}
}
diff --git a/observe/descriptions/monitorv2.yaml b/observe/descriptions/monitorv2.yaml
index b6753d56..928d1cb2 100644
--- a/observe/descriptions/monitorv2.yaml
+++ b/observe/descriptions/monitorv2.yaml
@@ -102,4 +102,11 @@ schema:
Represents two possible column types (link column, columnPath) of an observe dataset.
column_comparison:
description: |
- Specifies the one or multiple values you'd like to compare against the column.
\ No newline at end of file
+ Specifies the one or multiple values you'd like to compare against the column.
+ actions:
+ description: |
+ The list of shared actions to which this monitor is connected.
+ oid: |
+ The OID of this shared action.
+ levels: |
+ The alarm level(s) at which this monitor should trigger this shared action.
\ No newline at end of file
diff --git a/observe/resource_monitor_v2.go b/observe/resource_monitor_v2.go
index 8165957f..f2187e1b 100644
--- a/observe/resource_monitor_v2.go
+++ b/observe/resource_monitor_v2.go
@@ -16,9 +16,6 @@ import (
"github.com/observeinc/terraform-provider-observe/observe/descriptions"
)
-// TODO: make the schema keys constants?
-// annoying to change varnames in 3 non-obvious places
-
func resourceMonitorV2() *schema.Resource {
return &schema.Resource{
Description: descriptions.Get("monitorv2", "description"),
@@ -268,6 +265,31 @@ func resourceMonitorV2() *schema.Resource {
},
},
// end of fields of MonitorV2DefinitionInput
+ // the following field describes how monitorv2 is connected to shared actions.
+ "actions": { // [MonitorV2ActionRuleInput]
+ Type: schema.TypeList,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "oid": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: validateOID(oid.TypeMonitorV2Action),
+ Description: descriptions.Get("monitorv2", "schema", "actions", "oid"),
+ },
+ "levels": {
+ Type: schema.TypeList,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ ValidateDiagFunc: validateEnums(gql.AllMonitorV2AlarmLevels),
+ },
+ Description: descriptions.Get("monitorv2", "schema", "actions", "levels"),
+ },
+ },
+ },
+ Description: descriptions.Get("monitorv2", "schema", "actions", "description"),
+ },
// the following fields are those that aren't given as input to CU ops, but can be read by R ops.
"oid": { // ObjectId!
Type: schema.TypeString,
@@ -456,6 +478,11 @@ func resourceMonitorV2Create(ctx context.Context, data *schema.ResourceData, met
return diag.Errorf("failed to create monitor: %s", err.Error())
}
+ result, err = relateMonitorV2ToActions(ctx, result.Id, data, client)
+ if err != nil {
+ return diags
+ }
+
data.SetId(result.Id)
return append(diags, resourceMonitorV2Read(ctx, data, meta)...)
}
@@ -477,7 +504,12 @@ func resourceMonitorV2Update(ctx context.Context, data *schema.ResourceData, met
}
return nil
}
- return diag.Errorf("failed to create monitor: %s", err.Error())
+ return diag.Errorf("failed to update monitor: %s", err.Error())
+ }
+
+ _, err = relateMonitorV2ToActions(ctx, data.Id(), data, client)
+ if err != nil {
+ return diag.Errorf("failed to update monitor: %s", err.Error())
}
return append(diags, resourceMonitorV2Read(ctx, data, meta)...)
@@ -557,6 +589,12 @@ func resourceMonitorV2Read(ctx context.Context, data *schema.ResourceData, meta
}
}
+ if len(monitor.ActionRules) > 0 {
+ if err := data.Set("actions", monitorV2FlattenActionRules(monitor.ActionRules)); err != nil {
+ diags = append(diags, diag.FromErr(err)...)
+ }
+ }
+
return diags
}
@@ -592,6 +630,28 @@ func monitorV2FlattenRule(gqlRule gql.MonitorV2Rule) interface{} {
return rule
}
+func monitorV2FlattenActionRules(gqlActionRules []gql.MonitorV2ActionRule) []interface{} {
+ var actionRules []interface{}
+ for _, gqlActionRule := range gqlActionRules {
+ actionRules = append(actionRules, monitorV2FlattenActionRule(gqlActionRule))
+ }
+ return actionRules
+}
+
+func monitorV2FlattenActionRule(gqlActionRule gql.MonitorV2ActionRule) interface{} {
+ rules := map[string]interface{}{
+ "oid": oid.MonitorV2ActionOid(gqlActionRule.ActionID).String(),
+ }
+ if len(gqlActionRule.Levels) > 0 {
+ levels := make([]interface{}, 0)
+ for _, level := range gqlActionRule.Levels {
+ levels = append(levels, toSnake(string(level)))
+ }
+ rules["levels"] = levels
+ }
+ return rules
+}
+
func monitorV2FlattenCountRule(gqlCount gql.MonitorV2CountRule) []interface{} {
countRule := map[string]interface{}{}
if gqlCount.CompareValues != nil {
@@ -1016,6 +1076,7 @@ func newMonitorV2ThresholdRuleInput(path string, data *schema.ResourceData) (thr
}
compareValues = append(compareValues, *comparisonInput)
}
+
valueColumnName := data.Get(fmt.Sprintf("%svalue_column_name", path)).(string)
aggregation := gql.MonitorV2ValueAggregation(toCamel(data.Get(fmt.Sprintf("%saggregation", path)).(string)))
@@ -1220,3 +1281,43 @@ func newMonitorV2PrimitiveValue(path string, data *schema.ResourceData, ret *gql
}
return nil
}
+
+func relateMonitorV2ToActions(ctx context.Context, monitorId string, data *schema.ResourceData, client *observe.Client) (*gql.MonitorV2, error) {
+ var actionRelations []gql.ActionRelationInput
+ if _, ok := data.GetOk("actions"); ok {
+ actionRelations = make([]gql.ActionRelationInput, 0)
+ for i := range data.Get("actions").([]interface{}) {
+ actionRule, err := newMonitorV2ActionRuleInput(fmt.Sprintf("actions.%d.", i), data)
+ if err != nil {
+ return nil, err
+ }
+ actionRelations = append(actionRelations, gql.ActionRelationInput{
+ ActionRule: *actionRule,
+ })
+ }
+ }
+ return client.SaveMonitorV2Relations(ctx, monitorId, actionRelations)
+}
+
+func newMonitorV2ActionRuleInput(path string, data *schema.ResourceData) (*gql.MonitorV2ActionRuleInput, error) {
+ // required
+ actOID, err := oid.NewOID(data.Get(fmt.Sprintf("%soid", path)).(string))
+ if err != nil {
+ return nil, err
+ }
+
+ // instantiation
+ act := &gql.MonitorV2ActionRuleInput{
+ ActionID: actOID.Id,
+ }
+
+ // optional
+ if _, ok := data.GetOk(fmt.Sprintf("%slevels", path)); ok {
+ act.Levels = make([]gql.MonitorV2AlarmLevel, 0)
+ for i := range data.Get(fmt.Sprintf("%slevels", path)).([]interface{}) {
+ act.Levels = append(act.Levels, gql.MonitorV2AlarmLevel(toCamel(data.Get(fmt.Sprintf("%slevels.%d", path, i)).(string))))
+ }
+ }
+
+ return act, nil
+}
diff --git a/observe/resource_monitor_v2_action_test.go b/observe/resource_monitor_v2_action_test.go
index 9158f32e..b1ac2a8c 100644
--- a/observe/resource_monitor_v2_action_test.go
+++ b/observe/resource_monitor_v2_action_test.go
@@ -52,6 +52,9 @@ func TestAccObserveMonitorV2ActionEmail(t *testing.T) {
randomize = "0"
}
}
+ actions {
+ oid = observe_monitor_v2_action.act.oid
+ }
}
data "observe_user" "system" {
@@ -80,6 +83,7 @@ func TestAccObserveMonitorV2ActionEmail(t *testing.T) {
}
`, randomPrefix, systemUser()),
Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("observe_monitor_v2.first", "actions.#", "1"),
resource.TestCheckResourceAttrSet("observe_monitor_v2_action.act", "workspace"),
resource.TestCheckResourceAttr("observe_monitor_v2_action.act", "name", randomPrefix),
resource.TestCheckResourceAttr("observe_monitor_v2_action.act", "type", "email"),
@@ -140,6 +144,9 @@ func TestAccObserveMonitorV2ActionWebhook(t *testing.T) {
randomize = "0"
}
}
+ actions {
+ oid = observe_monitor_v2_action.act.oid
+ }
}
resource "observe_monitor_v2_action" "act" {
@@ -167,6 +174,7 @@ func TestAccObserveMonitorV2ActionWebhook(t *testing.T) {
}
`, randomPrefix),
Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("observe_monitor_v2.first", "actions.#", "1"),
resource.TestCheckResourceAttrSet("observe_monitor_v2_action.act", "workspace"),
resource.TestCheckResourceAttr("observe_monitor_v2_action.act", "name", randomPrefix),
resource.TestCheckResourceAttr("observe_monitor_v2_action.act", "type", "webhook"),
@@ -183,3 +191,135 @@ func TestAccObserveMonitorV2ActionWebhook(t *testing.T) {
},
})
}
+
+func TestAccObserveMonitorV2MultipleActionsEmail(t *testing.T) {
+ randomPrefix := acctest.RandomWithPrefix("tf")
+
+ resource.ParallelTest(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ Steps: []resource.TestStep{
+ {
+ Config: fmt.Sprintf(monitorV2ConfigPreamble+`
+ resource "observe_monitor_v2" "first" {
+ workspace = data.observe_workspace.default.oid
+ rule_kind = "count"
+ name = "%[1]s"
+ lookback_time = "30m"
+ comment = "a descriptive comment"
+ inputs = {
+ "test" = observe_datastream.test.dataset
+ }
+ stage {
+ pipeline = <<-EOF
+ colmake kind:"test", description:"test"
+ EOF
+ output_stage = true
+ }
+ stage {
+ pipeline = <<-EOF
+ filter kind ~ "test"
+ EOF
+ }
+ rules {
+ level = "informational"
+ count {
+ compare_values {
+ compare_fn = "greater"
+ value_int64 = [0]
+ }
+ }
+ }
+ scheduling {
+ interval {
+ interval = "15m"
+ randomize = "0"
+ }
+ }
+ actions {
+ oid = observe_monitor_v2_action.act1.oid
+ levels = ["informational"]
+ }
+ actions {
+ oid = observe_monitor_v2_action.act2.oid
+ levels = ["informational"]
+ }
+ }
+
+ data "observe_user" "system" {
+ email = "%[2]s"
+ }
+
+ resource "observe_monitor_v2_action" "act1" {
+ workspace = data.observe_workspace.default.oid
+ type = "email"
+ email {
+ subject = "somebody once told me"
+ body = "the world is gonna roll me"
+ fragments = jsonencode({
+ foo = "bar"
+ })
+ }
+ destination {
+ email {
+ addresses = ["test@observeinc.com"]
+ users = [data.observe_user.system.oid]
+ }
+ description = "an interesting dest description 1"
+ }
+ name = "%[1]s-1"
+ description = "an interesting description 1"
+ }
+
+ resource "observe_monitor_v2_action" "act2" {
+ workspace = data.observe_workspace.default.oid
+ type = "email"
+ email {
+ subject = "never gonna give you up"
+ body = "never gonna let you down"
+ fragments = jsonencode({
+ fizz = "buzz"
+ })
+ }
+ destination {
+ email {
+ addresses = ["test@observeinc.com"]
+ users = [data.observe_user.system.oid]
+ }
+ description = "an interesting dest description 2"
+ }
+ name = "%[1]s-2"
+ description = "an interesting description 2"
+ }
+ `, randomPrefix, systemUser()),
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("observe_monitor_v2.first", "actions.#", "2"),
+ resource.TestCheckResourceAttr("observe_monitor_v2.first", "actions.0.levels.0", "informational"),
+ resource.TestCheckResourceAttr("observe_monitor_v2.first", "actions.1.levels.0", "informational"),
+
+ resource.TestCheckResourceAttrSet("observe_monitor_v2_action.act1", "workspace"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "name", randomPrefix+"-1"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "type", "email"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "description", "an interesting description 1"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "email.0.fragments", "{\"foo\":\"bar\"}"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "email.0.subject", "somebody once told me"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "email.0.body", "the world is gonna roll me"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "destination.0.email.0.addresses.0", "test@observeinc.com"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "destination.0.description", "an interesting dest description 1"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act1", "destination.0.email.0.users.#", "1"),
+
+ resource.TestCheckResourceAttrSet("observe_monitor_v2_action.act2", "workspace"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "name", randomPrefix+"-2"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "type", "email"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "description", "an interesting description 2"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "email.0.fragments", "{\"fizz\":\"buzz\"}"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "email.0.subject", "never gonna give you up"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "email.0.body", "never gonna let you down"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "destination.0.email.0.addresses.0", "test@observeinc.com"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "destination.0.description", "an interesting dest description 2"),
+ resource.TestCheckResourceAttr("observe_monitor_v2_action.act2", "destination.0.email.0.users.#", "1"),
+ ),
+ },
+ },
+ })
+}