diff --git a/PROJECT b/PROJECT
index e400710..7186b4c 100644
--- a/PROJECT
+++ b/PROJECT
@@ -10,6 +10,9 @@ resources:
- group: nr
kind: ApmAlertCondition
version: v1
+- group: nr
+ kind: AlertsChannel
+ version: v1
- group: nr
kind: AlertsNrqlCondition
version: v1
diff --git a/README.md b/README.md
index cec2a91..fe58500 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ Currently the operator supports managing the following resources:
- Alert Policies
- NRQL Alert Conditions.
- Alert Conditions for APM, Browser and mobile
+- Alert Channels
# Quick Start
@@ -198,6 +199,44 @@ The operator will create and update alert policies and NRQL alert conditions as
region: "US"
```
+
+### Create an Alerts Channel
+
+1. We'll be using the following [example alerts channel](/examples/example_alerts_channel.yaml) configuration file. You will need to update the [`api_key`](/examples/example_alerts_channel.yaml#6) field with your New Relic [personal API key](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#personal-api-key).
+
+ **examples/example_alerts_channel.yaml**
+
+ ```yaml
+ apiVersion: nr.k8s.newrelic.com/v1
+ kind: AlertsChannel
+ metadata:
+ name: my-channel1
+ spec:
+ api_key:
+ # api_key_secret:
+ # name: nr-api-key
+ # namespace: default
+ # key_name: api-key
+ name: "my alert channel"
+ region: "US"
+ type: "email"
+ links:
+ # Policy links can be by NR PolicyID, NR PolicyName AND/OR K8s AlertPolicy object reference
+ policy_ids:
+ - 1
+ policy_names:
+ - "k8s created policy"
+ policy_kubernetes_objects:
+ - name: "my-policy"
+ namespace: "default"
+ configuration:
+ recipients: "me@email.com"
+ ```
+
+ > **Note:** The New Relic Alerts API does not allow updating Alerts Channels. In order to change a channel, you will need to either rename the k8s AlertsChannel object to create a new one and delete the old one or manually delete the k8s AlertsChannel object and create a new one.
+
+
+
### Uninstall the operator
The Operator can be removed with the reverse of installation, namely building the kubernetes resource files with `kustomize` and running `kubectl delete`
diff --git a/api/v1/alerts_apmcondition_webhook_test.go b/api/v1/alerts_apmcondition_webhook_test.go
index 1e7b739..0a4b22c 100644
--- a/api/v1/alerts_apmcondition_webhook_test.go
+++ b/api/v1/alerts_apmcondition_webhook_test.go
@@ -64,9 +64,9 @@ var _ = Describe("alertsAPMCondition_webhook", func() {
}, nil
}
})
+
Context("ValidateCreate", func() {
Context("With a valid Apm Condition", func() {
-
It("Should create the apm condition", func() {
err := r.ValidateCreate()
Expect(err).ToNot(HaveOccurred())
@@ -75,7 +75,6 @@ var _ = Describe("alertsAPMCondition_webhook", func() {
})
Context("With an invalid Type", func() {
-
BeforeEach(func() {
r.Spec.Type = "burritos"
})
@@ -88,7 +87,6 @@ var _ = Describe("alertsAPMCondition_webhook", func() {
})
Context("With an invalid Metric", func() {
-
BeforeEach(func() {
r.Spec.Type = "moar burritos"
})
@@ -99,8 +97,8 @@ var _ = Describe("alertsAPMCondition_webhook", func() {
Expect(err.Error()).To(ContainSubstring("moar burritos"))
})
})
- Context("With an invalid APMTerms", func() {
+ Context("With an invalid APMTerms", func() {
BeforeEach(func() {
r.Spec.APMTerms[0].TimeFunction = "moar burritos"
r.Spec.APMTerms[0].Priority = "moar tacos"
@@ -118,7 +116,6 @@ var _ = Describe("alertsAPMCondition_webhook", func() {
})
Context("With an invalid userDefined type", func() {
-
BeforeEach(func() {
r.Spec.UserDefined = alerts.ConditionUserDefined{
Metric: "Custom/foo",
@@ -132,12 +129,12 @@ var _ = Describe("alertsAPMCondition_webhook", func() {
Expect(err.Error()).To(ContainSubstring("invalid type"))
})
})
-
})
Context("ValidateUpdate", func() {
Context("When deleting an existing apm Condition with a delete policy", func() {
var update AlertsAPMCondition
+
BeforeEach(func() {
currentTime := v1.Time{Time: time.Now()}
//make copy of existing object to update
diff --git a/api/v1/alerts_nrqlcondition_types_test.go b/api/v1/alerts_nrqlcondition_types_test.go
index a5a1b80..95942f5 100644
--- a/api/v1/alerts_nrqlcondition_types_test.go
+++ b/api/v1/alerts_nrqlcondition_types_test.go
@@ -16,7 +16,6 @@ var _ = Describe("AlertsNrqlConditionSpec", func() {
var condition AlertsNrqlConditionSpec
BeforeEach(func() {
-
condition = AlertsNrqlConditionSpec{}
condition.Enabled = true
condition.ExistingPolicyID = "42"
diff --git a/api/v1/alerts_policy_types.go b/api/v1/alerts_policy_types.go
index 41dd79b..c92092b 100644
--- a/api/v1/alerts_policy_types.go
+++ b/api/v1/alerts_policy_types.go
@@ -116,6 +116,7 @@ func (p *AlertsPolicyCondition) SpecHash() uint32 {
strippedAlertsPolicy.Spec.ExistingPolicyID = ""
conditionTemplateSpecHasher := fnv.New32a()
DeepHashObject(conditionTemplateSpecHasher, strippedAlertsPolicy)
+
return conditionTemplateSpecHasher.Sum32()
}
@@ -158,6 +159,7 @@ func (in AlertsPolicySpec) Equals(policyToCompare AlertsPolicySpec) bool {
return false
}
}
+
return true
}
@@ -166,6 +168,7 @@ func GetAlertsConditionType(condition AlertsPolicyCondition) string {
if condition.Spec.Type == "NRQL" {
return "AlertsNrqlCondition"
}
+
return "AlertsAPMCondition"
}
@@ -182,11 +185,13 @@ func (p *AlertsPolicyCondition) GenerateSpecFromApmConditionSpec(apmConditionSpe
func (p *AlertsPolicyCondition) ReturnNrqlConditionSpec() (nrqlConditionSpec AlertsNrqlConditionSpec) {
jsonString, _ := json.Marshal(p.Spec)
json.Unmarshal(jsonString, &nrqlConditionSpec) //nolint
+
return
}
func (p *AlertsPolicyCondition) ReturnApmConditionSpec() (apmConditionSpec AlertsAPMConditionSpec) {
jsonString, _ := json.Marshal(p.Spec)
json.Unmarshal(jsonString, &apmConditionSpec) //nolint
+
return
}
diff --git a/api/v1/alerts_policy_webhook.go b/api/v1/alerts_policy_webhook.go
index 886eb1d..0699a71 100644
--- a/api/v1/alerts_policy_webhook.go
+++ b/api/v1/alerts_policy_webhook.go
@@ -77,15 +77,17 @@ func (r *AlertsPolicy) ValidateCreate() error {
if err != nil {
collectedErrors.Collect(err)
}
- err = r.ValidateIncidentPreference()
+ err = r.ValidateIncidentPreference()
if err != nil {
collectedErrors.Collect(err)
}
+
if len(*collectedErrors) > 0 {
AlertsPolicyLog.Info("Errors encountered validating policy", "collectedErrors", collectedErrors)
return collectedErrors
}
+
return nil
}
@@ -126,6 +128,7 @@ func (r *AlertsPolicy) ValidateDelete() error {
if err != nil {
return err
}
+
return nil
}
@@ -133,16 +136,17 @@ func (r *AlertsPolicy) DefaultIncidentPreference() {
if r.Spec.IncidentPreference == "" {
r.Spec.IncidentPreference = string(defaultAlertsPolicyIncidentPreference)
}
- r.Spec.IncidentPreference = strings.ToUpper(r.Spec.IncidentPreference)
+ r.Spec.IncidentPreference = strings.ToUpper(r.Spec.IncidentPreference)
}
func (r *AlertsPolicy) CheckForDuplicateConditions() error {
-
var conditionHashMap = make(map[uint32]bool)
+
for _, condition := range r.Spec.Conditions {
conditionHashMap[condition.SpecHash()] = true
}
+
if len(conditionHashMap) != len(r.Spec.Conditions) {
AlertsPolicyLog.Info("duplicate conditions detected or hash collision", "conditionHash", conditionHashMap)
return errors.New("duplicate conditions detected or hash collision")
@@ -156,7 +160,9 @@ func (r *AlertsPolicy) ValidateIncidentPreference() error {
case "PER_POLICY", "PER_CONDITION", "PER_CONDITION_AND_TARGET":
return nil
}
+
AlertsPolicyLog.Info("Incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET", "IncidentPreference value", r.Spec.IncidentPreference)
+
return errors.New("incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET")
}
@@ -164,10 +170,12 @@ func (r *AlertsPolicy) CheckForAPIKeyOrSecret() error {
if r.Spec.APIKey != "" {
return nil
}
+
if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
return nil
}
}
+
return errors.New("either api_key or api_key_secret must be set")
}
diff --git a/api/v1/alerts_policy_webhook_test.go b/api/v1/alerts_policy_webhook_test.go
index bc4a14c..5f8e720 100644
--- a/api/v1/alerts_policy_webhook_test.go
+++ b/api/v1/alerts_policy_webhook_test.go
@@ -86,6 +86,7 @@ var _ = Describe("AlertsPolicy_webhooks", func() {
err := r.ValidateCreate()
Expect(err).ToNot(HaveOccurred())
})
+
AfterEach(func() {
k8Client.Delete(context.Background(), secret)
})
@@ -179,9 +180,8 @@ var _ = Describe("AlertsPolicy_webhooks", func() {
})
Describe("Default", func() {
- var (
- r AlertsPolicy
- )
+ var r AlertsPolicy
+
conditionSpec := AlertsPolicyConditionSpec{}
conditionSpec.Terms = []AlertsNrqlConditionTerm{
{
diff --git a/api/v1/alertschannel_types.go b/api/v1/alertschannel_types.go
new file mode 100644
index 0000000..7b0561b
--- /dev/null
+++ b/api/v1/alertschannel_types.go
@@ -0,0 +1,91 @@
+package v1
+
+import (
+ "encoding/json"
+
+ "github.com/newrelic/newrelic-client-go/pkg/alerts"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// AlertsChannelSpec defines the desired state of AlertsChannel
+type AlertsChannelSpec struct {
+ ID int `json:"id,omitempty"`
+ Name string `json:"name"`
+ APIKey string `json:"api_key,omitempty"`
+ APIKeySecret NewRelicAPIKeySecret `json:"api_key_secret,omitempty"`
+ Region string `json:"region,omitempty"`
+ Type string `json:"type,omitempty"`
+ Links ChannelLinks `json:"links,omitempty"`
+ Configuration AlertsChannelConfiguration `json:"configuration,omitempty"`
+}
+
+// ChannelLinks - copy of alerts.ChannelLinks
+type ChannelLinks struct {
+ PolicyIDs []int `json:"policy_ids,omitempty"`
+ PolicyNames []string `json:"policy_names,omitempty"`
+ PolicyKubernetesObjects []metav1.ObjectMeta `json:"policy_kubernetes_objects,omitempty"`
+}
+
+// AlertsChannelStatus defines the observed state of AlertsChannel
+type AlertsChannelStatus struct {
+ AppliedSpec *AlertsChannelSpec `json:"applied_spec"`
+ ChannelID int `json:"channel_id"`
+ AppliedPolicyIDs []int `json:"appliedPolicyIDs"`
+}
+
+// AlertsChannelConfiguration - copy of alerts.ChannelConfiguration
+type AlertsChannelConfiguration struct {
+ Recipients string `json:"recipients,omitempty"`
+ IncludeJSONAttachment string `json:"include_json_attachment,omitempty"`
+ AuthToken string `json:"auth_token,omitempty"`
+ APIKey string `json:"api_key,omitempty"`
+ Teams string `json:"teams,omitempty"`
+ Tags string `json:"tags,omitempty"`
+ URL string `json:"url,omitempty"`
+ Channel string `json:"channel,omitempty"`
+ Key string `json:"key,omitempty"`
+ RouteKey string `json:"route_key,omitempty"`
+ ServiceKey string `json:"service_key,omitempty"`
+ BaseURL string `json:"base_url,omitempty"`
+ AuthUsername string `json:"auth_username,omitempty"`
+ AuthPassword string `json:"auth_password,omitempty"`
+ PayloadType string `json:"payload_type,omitempty"`
+ Region string `json:"region,omitempty"`
+ UserID string `json:"user_id,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:printcolumn:name="Created",type="boolean",JSONPath=".status.created"
+
+// AlertsChannel is the Schema for the AlertsChannel API
+type AlertsChannel struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec AlertsChannelSpec `json:"spec,omitempty"`
+ Status AlertsChannelStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// AlertsChannelList contains a list of AlertsChannel
+type AlertsChannelList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []AlertsChannel `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&AlertsChannel{}, &AlertsChannelList{})
+}
+
+// APIChannel - Converts AlertsChannelSpec object to alerts.Channel
+func (in AlertsChannelSpec) APIChannel() alerts.Channel {
+ jsonString, _ := json.Marshal(in)
+
+ var APIChannel alerts.Channel
+ json.Unmarshal(jsonString, &APIChannel) //nolint
+ APIChannel.Links = alerts.ChannelLinks{}
+
+ return APIChannel
+}
diff --git a/api/v1/alertschannel_types_test.go b/api/v1/alertschannel_types_test.go
new file mode 100644
index 0000000..4ff7e4b
--- /dev/null
+++ b/api/v1/alertschannel_types_test.go
@@ -0,0 +1,49 @@
+package v1
+
+import (
+ "fmt"
+ "reflect"
+
+ "github.com/newrelic/newrelic-client-go/pkg/alerts"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("AlertsChannelSpec", func() {
+ var alertsChannelSpec AlertsChannelSpec
+
+ BeforeEach(func() {
+ alertsChannelSpec = AlertsChannelSpec{
+ ID: 88,
+ Name: "my alert channel",
+ APIKey: "api-key",
+ APIKeySecret: NewRelicAPIKeySecret{},
+ Region: "US",
+ Type: "email",
+ Links: ChannelLinks{
+ PolicyIDs: []int{
+ 1,
+ 2,
+ },
+ },
+ Configuration: AlertsChannelConfiguration{
+ Recipients: "me@email.com",
+ },
+ }
+
+ })
+
+ Describe("APIChannel", func() {
+ It("converts AlertsChannelSpec object to alerts.Channel object from go client, retaining field values", func() {
+ apiChannel := alertsChannelSpec.APIChannel()
+
+ Expect(fmt.Sprint(reflect.TypeOf(apiChannel))).To(Equal("alerts.Channel"))
+ Expect(apiChannel.ID).To(Equal(88))
+ Expect(apiChannel.Type).To(Equal(alerts.ChannelTypes.Email))
+ Expect(apiChannel.Name).To(Equal("my alert channel"))
+ apiConfiguration := apiChannel.Configuration
+ Expect(apiConfiguration.Recipients).To(Equal("me@email.com"))
+ })
+ })
+})
diff --git a/api/v1/alertschannel_webhook.go b/api/v1/alertschannel_webhook.go
new file mode 100644
index 0000000..39be6f5
--- /dev/null
+++ b/api/v1/alertschannel_webhook.go
@@ -0,0 +1,132 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ "errors"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/webhook"
+
+ "github.com/newrelic/newrelic-client-go/pkg/alerts"
+
+ "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
+)
+
+// log is for logging in this package.
+var (
+ alertschannellog = logf.Log.WithName("alertschannel-resource")
+)
+
+// SetupWebhookWithManager - instantiates the Webhook
+func (r *AlertsChannel) SetupWebhookWithManager(mgr ctrl.Manager) error {
+ alertClientFunc = interfaces.InitializeAlertsClient
+ k8Client = mgr.GetClient()
+ return ctrl.NewWebhookManagedBy(mgr).
+ For(r).
+ Complete()
+}
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+
+// +kubebuilder:webhook:path=/mutate-nr-k8s-newrelic-com-v1-alertschannel,mutating=true,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertschannels,verbs=create;update,versions=v1,name=malertschannel.kb.io,sideEffects=None
+
+var _ webhook.Defaulter = &AlertsChannel{}
+
+// Default implements webhook.Defaulter so a webhook will be registered for the type
+func (r *AlertsChannel) Default() {
+ alertschannellog.Info("default", "name", r.Name)
+
+ if r.Status.AppliedSpec == nil {
+ log.Info("Setting null Applied Spec to empty interface")
+ r.Status.AppliedSpec = &AlertsChannelSpec{}
+ }
+
+ if r.Status.AppliedPolicyIDs == nil {
+ log.Info("Setting null AppliedPolicyIDs to empty interface")
+ r.Status.AppliedPolicyIDs = []int{}
+ }
+}
+
+// +kubebuilder:webhook:verbs=create;update,path=/validate-nr-k8s-newrelic-com-v1-alertschannel,mutating=false,failurePolicy=fail,groups=nr.k8s.newrelic.com,resources=alertschannels,versions=v1,name=valertschannel.kb.io,sideEffects=None
+
+var _ webhook.Validator = &AlertsChannel{}
+
+// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
+func (r *AlertsChannel) ValidateCreate() error {
+ alertschannellog.Info("validate create", "name", r.Name)
+
+ return r.ValidateAlertsChannel()
+}
+
+// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
+func (r *AlertsChannel) ValidateUpdate(old runtime.Object) error {
+ alertschannellog.Info("validate update", "name", r)
+
+ return r.ValidateAlertsChannel()
+}
+
+// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
+func (r *AlertsChannel) ValidateDelete() error {
+ alertschannellog.Info("validate delete", "name", r.Name)
+
+ // TODO(user): fill in your validation logic upon object deletion.
+ return nil
+}
+
+// ValidateAlertsChannel - Validates create/update of AlertsChannel
+func (r *AlertsChannel) ValidateAlertsChannel() error {
+ err := CheckForAPIKeyOrSecret(r.Spec.APIKey, r.Spec.APIKeySecret)
+ if err != nil {
+ return err
+ }
+
+ if !ValidRegion(r.Spec.Region) {
+ return errors.New("Invalid region set, value was: " + r.Spec.Region)
+ }
+
+ var invalidAttributes InvalidAttributeSlice
+
+ r.ValidateType()
+ invalidAttributes = append(invalidAttributes, r.ValidateType()...)
+
+ if len(invalidAttributes) > 0 {
+ return errors.New("error with invalid attributes: \n" + invalidAttributes.errorString())
+ }
+
+ return nil
+}
+
+//ValidateType - Validates the Type attribute
+func (r *AlertsChannel) ValidateType() InvalidAttributeSlice {
+ switch r.Spec.Type {
+ case string(alerts.ChannelTypes.Email),
+ string(alerts.ChannelTypes.OpsGenie),
+ string(alerts.ChannelTypes.PagerDuty),
+ string(alerts.ChannelTypes.Slack),
+ string(alerts.ChannelTypes.User),
+ string(alerts.ChannelTypes.VictorOps),
+ string(alerts.ChannelTypes.Webhook):
+
+ return []invalidAttribute{}
+ default:
+ alertschannellog.Info("Invalid Type attribute", "Type", r.Spec.Type)
+
+ return []invalidAttribute{{attribute: "Type", value: r.Spec.Type}}
+ }
+}
diff --git a/api/v1/alertschannel_webhook_test.go b/api/v1/alertschannel_webhook_test.go
new file mode 100644
index 0000000..bbb10cd
--- /dev/null
+++ b/api/v1/alertschannel_webhook_test.go
@@ -0,0 +1,100 @@
+package v1
+
+import (
+ "github.com/newrelic/newrelic-client-go/pkg/alerts"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
+ "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
+)
+
+var _ = Describe("AlertsChannel_webhook", func() {
+ var (
+ r AlertsChannel
+ alertsClient *interfacesfakes.FakeNewRelicAlertsClient
+ )
+
+ BeforeEach(func() {
+ k8Client = testk8sClient
+ alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
+ fakeAlertFunc := func(string, string) (interfaces.NewRelicAlertsClient, error) {
+ return alertsClient, nil
+ }
+ alertClientFunc = fakeAlertFunc
+ r = AlertsChannel{
+ ObjectMeta: v1.ObjectMeta{
+ Name: "test alert channel",
+ },
+ Spec: AlertsChannelSpec{
+ ID: 88,
+ Name: "my alert channel",
+ APIKey: "api-key",
+ APIKeySecret: NewRelicAPIKeySecret{},
+ Region: "US",
+ Type: "email",
+ Links: ChannelLinks{
+ PolicyIDs: []int{
+ 1,
+ 2,
+ },
+ },
+ Configuration: AlertsChannelConfiguration{
+ Recipients: "me@email.com",
+ },
+ },
+ }
+
+ alertsClient.GetPolicyStub = func(int) (*alerts.Policy, error) {
+ return &alerts.Policy{
+ ID: 46286,
+ }, nil
+ }
+ })
+
+ Context("ValidateCreate", func() {
+ Context("With a valid Alert Channel", func() {
+ It("Should create the Alert Channel", func() {
+ err := r.ValidateCreate()
+ Expect(err).ToNot(HaveOccurred())
+ })
+ })
+
+ Context("With an invalid Region", func() {
+ BeforeEach(func() {
+ r.Spec.Region = "hamburgers"
+ })
+
+ It("Should reject the Alert Channel creation", func() {
+ err := r.ValidateCreate()
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("hamburgers"))
+ })
+ })
+
+ Context("With an invalid Type", func() {
+ BeforeEach(func() {
+ r.Spec.Type = "burritos"
+ })
+
+ It("Should reject the Alert Channel creation", func() {
+ err := r.ValidateCreate()
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("burritos"))
+ })
+ })
+
+ Context("With no API Key or secret", func() {
+ BeforeEach(func() {
+ r.Spec.APIKey = ""
+ })
+
+ It("Should reject the Alert Channel creation", func() {
+ err := r.ValidateCreate()
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("either api_key or api_key_secret must be set"))
+ })
+ })
+ })
+})
diff --git a/api/v1/apmalertcondition_webhook.go b/api/v1/apmalertcondition_webhook.go
index 9e85f09..2a63b67 100644
--- a/api/v1/apmalertcondition_webhook.go
+++ b/api/v1/apmalertcondition_webhook.go
@@ -132,6 +132,7 @@ func (r *ApmAlertCondition) ValidateUpdate(old runtime.Object) error {
if len(invalidAttributes) > 0 {
return errors.New("error with invalid attributes")
}
+
return r.CheckExistingPolicyID()
}
@@ -150,9 +151,11 @@ func (r *ApmAlertCondition) ValidateType() InvalidAttributeSlice {
string(alerts.ConditionTypes.BrowserMetric),
string(alerts.ConditionTypes.MobileMetric),
string(alerts.ConditionTypes.ServersMetric):
+
return []invalidAttribute{}
default:
log.Info("Invalid Type attribute", "Type", r.Spec.Type)
+
return []invalidAttribute{{attribute: "Type", value: r.Spec.Type}}
}
}
@@ -192,15 +195,18 @@ func (r *ApmAlertCondition) ValidateMetric() InvalidAttributeSlice {
alerts.MetricTypes.UserDefined,
alerts.MetricTypes.ViewLoading,
alerts.MetricTypes.WebApplication:
+
return []invalidAttribute{}
default:
log.Info("Invalid Metric attribute", "Metric", r.Spec.Metric)
+
return []invalidAttribute{{attribute: "Type", value: r.Spec.Metric}}
}
}
func (r *ApmAlertCondition) ValidateTerms() InvalidAttributeSlice {
var invalidTerms InvalidAttributeSlice
+
for _, term := range r.Spec.Terms {
switch alerts.TimeFunctionType(term.TimeFunction) {
case alerts.TimeFunctionTypes.All, alerts.TimeFunctionTypes.Any:
@@ -212,6 +218,7 @@ func (r *ApmAlertCondition) ValidateTerms() InvalidAttributeSlice {
value: term.TimeFunction,
})
}
+
switch alerts.OperatorType(term.Operator) {
case alerts.OperatorTypes.Equal, alerts.OperatorTypes.Above, alerts.OperatorTypes.Below:
continue
@@ -222,6 +229,7 @@ func (r *ApmAlertCondition) ValidateTerms() InvalidAttributeSlice {
value: term.Operator,
})
}
+
switch alerts.PriorityType(term.Priority) {
case alerts.PriorityTypes.Critical, alerts.PriorityTypes.Warning:
continue
@@ -246,9 +254,11 @@ func (r *ApmAlertCondition) ValidateUserDefinedValueFunction() InvalidAttributeS
alerts.ValueFunctionTypes.SampleSize,
alerts.ValueFunctionTypes.SingleValue,
alerts.ValueFunctionTypes.Total:
+
return []invalidAttribute{}
default:
log.Info("Invalid UserDefined.ValueFunction passed", "UserDefined.ValueFunction", r.Spec.UserDefined.ValueFunction)
+
return []invalidAttribute{{attribute: "UserDefined.ValueFunction: ", value: string(r.Spec.UserDefined.ValueFunction)}}
}
}
@@ -257,16 +267,18 @@ func (r *ApmAlertCondition) CheckExistingPolicyID() error {
log.Info("Checking existing", "policyId", r.Spec.ExistingPolicyID)
ctx := context.Background()
var apiKey string
+
if r.Spec.APIKey == "" {
key := types.NamespacedName{Namespace: r.Spec.APIKeySecret.Namespace, Name: r.Spec.APIKeySecret.Name}
var apiKeySecret v1.Secret
+
getErr := k8Client.Get(ctx, key, &apiKeySecret)
if getErr != nil {
log.Error(getErr, "Error getting secret")
return getErr
}
- apiKey = string(apiKeySecret.Data[r.Spec.APIKeySecret.KeyName])
+ apiKey = string(apiKeySecret.Data[r.Spec.APIKeySecret.KeyName])
} else {
apiKey = r.Spec.APIKey
}
@@ -278,28 +290,35 @@ func (r *ApmAlertCondition) CheckExistingPolicyID() error {
"API Key", interfaces.PartialAPIKey(apiKey),
"region", r.Spec.Region,
)
+
return errAlertClient
}
+
alertPolicy, errAlertPolicy := alertsClient.GetPolicy(r.Spec.ExistingPolicyID)
if errAlertPolicy != nil {
if r.GetDeletionTimestamp() != nil {
log.Info("Deleting resource", "errAlertPolicy", errAlertPolicy)
if strings.Contains(errAlertPolicy.Error(), "no alert policy found for id") {
log.Info("ExistingAlertPolicy not found but we are deleting the condition so this is ok")
+
return nil
}
}
+
log.Error(errAlertPolicy, "failed to get policy",
"policyId", r.Spec.ExistingPolicyID,
"API Key", interfaces.PartialAPIKey(apiKey),
"region", r.Spec.Region,
)
+
return errAlertPolicy
}
if alertPolicy.ID != r.Spec.ExistingPolicyID {
log.Info("Alert policy returned by the API failed to match provided policy ID")
+
return errors.New("alert policy returned by API did not match")
}
+
return nil
}
@@ -307,25 +326,30 @@ func (r *ApmAlertCondition) CheckForAPIKeyOrSecret() error {
if r.Spec.APIKey != "" {
return nil
}
+
if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
return nil
}
}
+
return errors.New("either api_key or api_key_secret must be set")
}
func (r *ApmAlertCondition) CheckRequiredFields() error {
-
missingFields := []string{}
+
if r.Spec.Region == "" {
missingFields = append(missingFields, "region")
}
+
if r.Spec.ExistingPolicyID == 0 {
missingFields = append(missingFields, "existing_policy_id")
}
+
if len(missingFields) > 0 {
return errors.New(strings.Join(missingFields, " and ") + " must be set")
}
+
return nil
}
diff --git a/api/v1/apmalertcondition_webhook_test.go b/api/v1/apmalertcondition_webhook_test.go
index 66ceb03..07b00d2 100644
--- a/api/v1/apmalertcondition_webhook_test.go
+++ b/api/v1/apmalertcondition_webhook_test.go
@@ -66,16 +66,13 @@ var _ = Describe("apmAlertCondition_webhook", func() {
})
Context("ValidateCreate", func() {
Context("With a valid Apm Condition", func() {
-
It("Should create the apm condition", func() {
err := r.ValidateCreate()
Expect(err).ToNot(HaveOccurred())
-
})
})
Context("With an invalid Type", func() {
-
BeforeEach(func() {
r.Spec.Type = "burritos"
})
@@ -88,7 +85,6 @@ var _ = Describe("apmAlertCondition_webhook", func() {
})
Context("With an invalid Metric", func() {
-
BeforeEach(func() {
r.Spec.Type = "moar burritos"
})
@@ -99,13 +95,12 @@ var _ = Describe("apmAlertCondition_webhook", func() {
Expect(err.Error()).To(ContainSubstring("moar burritos"))
})
})
- Context("With an invalid Terms", func() {
+ Context("With an invalid Terms", func() {
BeforeEach(func() {
r.Spec.Terms[0].TimeFunction = "moar burritos"
r.Spec.Terms[0].Priority = "moar tacos"
r.Spec.Terms[0].Operator = "moar hamburgers"
-
})
It("Should reject the apm condition creation", func() {
@@ -118,7 +113,6 @@ var _ = Describe("apmAlertCondition_webhook", func() {
})
Context("With an invalid userDefined type", func() {
-
BeforeEach(func() {
r.Spec.UserDefined = alerts.ConditionUserDefined{
Metric: "Custom/foo",
@@ -155,5 +149,4 @@ var _ = Describe("apmAlertCondition_webhook", func() {
})
})
})
-
})
diff --git a/api/v1/common.go b/api/v1/common.go
index 884aec1..473d7da 100644
--- a/api/v1/common.go
+++ b/api/v1/common.go
@@ -1,9 +1,11 @@
package v1
import (
+ "errors"
"hash"
"github.com/davecgh/go-spew/spew"
+ "github.com/newrelic/newrelic-client-go/pkg/region"
)
// DeepHashObject writes specified object to hash using the spew library
@@ -35,3 +37,30 @@ type NewRelicAPIKeySecret struct {
Namespace string `json:"namespace,omitempty"`
KeyName string `json:"key_name,omitempty"`
}
+
+//ValidRegion - returns true if a valid region is passed
+func ValidRegion(input string) bool {
+ _, err := region.Parse(input)
+ if err != nil {
+ return false
+ } else if input == "" {
+ return false
+ }
+
+ return true
+}
+
+//CheckForAPIKeyOrSecret - returns error if a API KEY or k8 secret is not passed in
+func CheckForAPIKeyOrSecret(apiKey string, secret NewRelicAPIKeySecret) error {
+ if apiKey != "" {
+ return nil
+ }
+
+ if secret != (NewRelicAPIKeySecret{}) {
+ if secret.Name != "" && secret.Namespace != "" && secret.KeyName != "" {
+ return nil
+ }
+ }
+
+ return errors.New("either api_key or api_key_secret must be set")
+}
diff --git a/api/v1/nrqlalertcondition_types.go b/api/v1/nrqlalertcondition_types.go
index 19611fd..6ffd469 100644
--- a/api/v1/nrqlalertcondition_types.go
+++ b/api/v1/nrqlalertcondition_types.go
@@ -76,5 +76,6 @@ func (in NrqlAlertConditionSpec) APICondition() alerts.NrqlCondition {
jsonString, _ := json.Marshal(in)
var APICondition alerts.NrqlCondition
json.Unmarshal(jsonString, &APICondition) //nolint
+
return APICondition
}
diff --git a/api/v1/nrqlalertcondition_webhook.go b/api/v1/nrqlalertcondition_webhook.go
index f4d9d9e..27846ed 100644
--- a/api/v1/nrqlalertcondition_webhook.go
+++ b/api/v1/nrqlalertcondition_webhook.go
@@ -77,6 +77,7 @@ func (r *NrqlAlertCondition) ValidateCreate() error {
if err != nil {
return err
}
+
return r.CheckExistingPolicyID()
}
@@ -92,6 +93,7 @@ func (r *NrqlAlertCondition) ValidateUpdate(old runtime.Object) error {
if err != nil {
return err
}
+
return r.CheckExistingPolicyID()
}
@@ -107,6 +109,7 @@ func (r *NrqlAlertCondition) CheckExistingPolicyID() error {
log.Info("Checking existing", "policyId", r.Spec.ExistingPolicyID)
ctx := context.Background()
var apiKey string
+
if r.Spec.APIKey == "" {
key := types.NamespacedName{Namespace: r.Spec.APIKeySecret.Namespace, Name: r.Spec.APIKeySecret.Name}
var apiKeySecret v1.Secret
@@ -116,7 +119,6 @@ func (r *NrqlAlertCondition) CheckExistingPolicyID() error {
return getErr
}
apiKey = string(apiKeySecret.Data[r.Spec.APIKeySecret.KeyName])
-
} else {
apiKey = r.Spec.APIKey
}
@@ -130,6 +132,7 @@ func (r *NrqlAlertCondition) CheckExistingPolicyID() error {
)
return errAlertClient
}
+
alertPolicy, errAlertPolicy := alertsClient.GetPolicy(r.Spec.ExistingPolicyID)
if errAlertPolicy != nil {
if r.GetDeletionTimestamp() != nil {
@@ -146,10 +149,12 @@ func (r *NrqlAlertCondition) CheckExistingPolicyID() error {
)
return errAlertPolicy
}
+
if alertPolicy.ID != r.Spec.ExistingPolicyID {
log.Info("Alert policy returned by the API failed to match provided policy ID")
return errors.New("alert policy returned by API did not match")
}
+
return nil
}
@@ -157,25 +162,30 @@ func (r *NrqlAlertCondition) CheckForAPIKeyOrSecret() error {
if r.Spec.APIKey != "" {
return nil
}
+
if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
return nil
}
}
+
return errors.New("either api_key or api_key_secret must be set")
}
func (r *NrqlAlertCondition) CheckRequiredFields() error {
-
missingFields := []string{}
+
if r.Spec.Region == "" {
missingFields = append(missingFields, "region")
}
+
if r.Spec.ExistingPolicyID == 0 {
missingFields = append(missingFields, "existing_policy_id")
}
+
if len(missingFields) > 0 {
return errors.New(strings.Join(missingFields, " and ") + " must be set")
}
+
return nil
}
diff --git a/api/v1/nrqlalertcondition_webhook_test.go b/api/v1/nrqlalertcondition_webhook_test.go
index f906057..94be0cb 100644
--- a/api/v1/nrqlalertcondition_webhook_test.go
+++ b/api/v1/nrqlalertcondition_webhook_test.go
@@ -170,8 +170,6 @@ var _ = Describe("ValidateCreate", func() {
})
Describe("CheckExistingPolicyID", func() {
- BeforeEach(func() {})
-
Context("With a valid API Key", func() {
BeforeEach(func() {})
@@ -213,6 +211,7 @@ var _ = Describe("ValidateCreate", func() {
Context("ValidateUpdate", func() {
Context("When deleting an existing nrql Condition with a delete policy", func() {
var update NrqlAlertCondition
+
BeforeEach(func() {
currentTime := metav1.Time{Time: time.Now()}
//make copy of existing object to update
diff --git a/api/v1/policy_types.go b/api/v1/policy_types.go
index c910623..d7d2616 100644
--- a/api/v1/policy_types.go
+++ b/api/v1/policy_types.go
@@ -102,6 +102,7 @@ func (p *PolicyCondition) SpecHash() uint32 {
strippedPolicy.Spec.ExistingPolicyID = 0
conditionTemplateSpecHasher := fnv.New32a()
DeepHashObject(conditionTemplateSpecHasher, strippedPolicy)
+
return conditionTemplateSpecHasher.Sum32()
}
@@ -117,18 +118,23 @@ func (in PolicySpec) Equals(policyToCompare PolicySpec) bool {
if in.IncidentPreference != policyToCompare.IncidentPreference {
return false
}
+
if in.Name != policyToCompare.Name {
return false
}
+
if in.APIKey != policyToCompare.APIKey {
return false
}
+
if in.Region != policyToCompare.Region {
return false
}
+
if in.APIKeySecret != policyToCompare.APIKeySecret {
return false
}
+
if len(in.Conditions) != len(policyToCompare.Conditions) {
return false
}
@@ -144,6 +150,7 @@ func (in PolicySpec) Equals(policyToCompare PolicySpec) bool {
return false
}
}
+
return true
}
@@ -152,8 +159,8 @@ func GetConditionType(condition PolicyCondition) string {
if condition.Spec.Type == "NRQL" {
return "NrqlAlertCondition"
}
- return "ApmAlertCondition"
+ return "ApmAlertCondition"
}
func (p *PolicyCondition) GenerateSpecFromNrqlConditionSpec(nrqlConditionSpec NrqlAlertConditionSpec) {
@@ -169,11 +176,13 @@ func (p *PolicyCondition) GenerateSpecFromApmConditionSpec(apmConditionSpec ApmA
func (p *PolicyCondition) ReturnNrqlConditionSpec() (nrqlAlertConditionSpec NrqlAlertConditionSpec) {
jsonString, _ := json.Marshal(p.Spec)
json.Unmarshal(jsonString, &nrqlAlertConditionSpec) //nolint
+
return
}
func (p *PolicyCondition) ReturnApmConditionSpec() (apmAlertConditionSpec ApmAlertConditionSpec) {
jsonString, _ := json.Marshal(p.Spec)
json.Unmarshal(jsonString, &apmAlertConditionSpec) //nolint
+
return
}
diff --git a/api/v1/policy_types_test.go b/api/v1/policy_types_test.go
index e525337..20204f6 100644
--- a/api/v1/policy_types_test.go
+++ b/api/v1/policy_types_test.go
@@ -295,6 +295,7 @@ var _ = Describe("GetNrqlConditionSpec", func() {
APMSpecificSpec{},
},
}
+
It("Should return a matching NrqlConditionSpec", func() {
nrqlConditionSpec := condition.ReturnNrqlConditionSpec()
Expect(nrqlConditionSpec.Type).To(Equal("NRQL"))
@@ -363,6 +364,7 @@ var _ = Describe("GetApmConditionSpec", func() {
},
},
}
+
It("Should return a matching ApmConditionSpec", func() {
apmConditionSpec := condition.ReturnApmConditionSpec()
Expect(apmConditionSpec.Type).To(Equal("apm_app_metric"))
diff --git a/api/v1/policy_webhook.go b/api/v1/policy_webhook.go
index f063da4..1d93178 100644
--- a/api/v1/policy_webhook.go
+++ b/api/v1/policy_webhook.go
@@ -64,8 +64,8 @@ func (r *Policy) ValidateCreate() error {
Log.Info("validate create", "name", r.Name)
collectedErrors := new(customErrors.ErrorCollector)
- err := r.CheckForAPIKeyOrSecret()
+ err := r.CheckForAPIKeyOrSecret()
if err != nil {
collectedErrors.Collect(err)
}
@@ -74,15 +74,17 @@ func (r *Policy) ValidateCreate() error {
if err != nil {
collectedErrors.Collect(err)
}
- err = r.ValidateIncidentPreference()
+ err = r.ValidateIncidentPreference()
if err != nil {
collectedErrors.Collect(err)
}
+
if len(*collectedErrors) > 0 {
Log.Info("Errors encountered validating policy", "collectedErrors", collectedErrors)
return collectedErrors
}
+
return nil
}
@@ -123,6 +125,7 @@ func (r *Policy) ValidateDelete() error {
if err != nil {
return err
}
+
return nil
}
@@ -130,18 +133,20 @@ func (r *Policy) DefaultIncidentPreference() {
if r.Spec.IncidentPreference == "" {
r.Spec.IncidentPreference = defaultPolicyIncidentPreference
}
- r.Spec.IncidentPreference = strings.ToUpper(r.Spec.IncidentPreference)
+ r.Spec.IncidentPreference = strings.ToUpper(r.Spec.IncidentPreference)
}
func (r *Policy) CheckForDuplicateConditions() error {
-
var conditionHashMap = make(map[uint32]bool)
+
for _, condition := range r.Spec.Conditions {
conditionHashMap[condition.SpecHash()] = true
}
+
if len(conditionHashMap) != len(r.Spec.Conditions) {
log.Info("duplicate conditions detected or hash collision", "conditionHash", conditionHashMap)
+
return errors.New("duplicate conditions detected or hash collision")
}
@@ -153,7 +158,9 @@ func (r *Policy) ValidateIncidentPreference() error {
case "PER_POLICY", "PER_CONDITION", "PER_CONDITION_AND_TARGET":
return nil
}
+
log.Info("Incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET", "IncidentPreference value", r.Spec.IncidentPreference)
+
return errors.New("incident preference must be PER_POLICY, PER_CONDITION, or PER_CONDITION_AND_TARGET")
}
@@ -161,10 +168,12 @@ func (r *Policy) CheckForAPIKeyOrSecret() error {
if r.Spec.APIKey != "" {
return nil
}
+
if r.Spec.APIKeySecret != (NewRelicAPIKeySecret{}) {
if r.Spec.APIKeySecret.Name != "" && r.Spec.APIKeySecret.Namespace != "" && r.Spec.APIKeySecret.KeyName != "" {
return nil
}
}
+
return errors.New("either api_key or api_key_secret must be set")
}
diff --git a/api/v1/policy_webhook_test.go b/api/v1/policy_webhook_test.go
index ef2fc30..be2f131 100644
--- a/api/v1/policy_webhook_test.go
+++ b/api/v1/policy_webhook_test.go
@@ -26,6 +26,7 @@ var _ = Describe("Policy_webhooks", func() {
}))
Expect(err).ToNot(HaveOccurred())
})
+
Describe("validateCreate", func() {
var (
r Policy
@@ -96,6 +97,7 @@ var _ = Describe("Policy_webhooks", func() {
err := r.ValidateCreate()
Expect(err).ToNot(HaveOccurred())
})
+
AfterEach(func() {
k8Client.Delete(ctx, secret)
})
@@ -203,48 +205,49 @@ var _ = Describe("Policy_webhooks", func() {
})
Describe("Default", func() {
- var (
- r Policy
- )
- r = Policy{
- Spec: PolicySpec{
- Name: "Test Policy",
- IncidentPreference: "PER_POLICY",
- APIKey: "api-key",
- Conditions: []PolicyCondition{
- {
- Spec: ConditionSpec{
- GenericConditionSpec{
- Terms: []AlertConditionTerm{
- {
- Duration: "30",
- Operator: "above",
- Priority: "critical",
- Threshold: "5",
- TimeFunction: "all",
+ var r Policy
+
+ BeforeEach(func() {
+ r = Policy{
+ Spec: PolicySpec{
+ Name: "Test Policy",
+ IncidentPreference: "PER_POLICY",
+ APIKey: "api-key",
+ Conditions: []PolicyCondition{
+ {
+ Spec: ConditionSpec{
+ GenericConditionSpec{
+ Terms: []AlertConditionTerm{
+ {
+ Duration: "30",
+ Operator: "above",
+ Priority: "critical",
+ Threshold: "5",
+ TimeFunction: "all",
+ },
},
+ Type: "NRQL",
+ Name: "NRQL Condition",
+ RunbookURL: "http://test.com/runbook",
+ Enabled: true,
},
- Type: "NRQL",
- Name: "NRQL Condition",
- RunbookURL: "http://test.com/runbook",
- Enabled: true,
- },
- NrqlSpecificSpec{
- Nrql: NrqlQuery{
- Query: "SELECT 1 FROM MyEvents",
- SinceValue: "5",
+ NrqlSpecificSpec{
+ Nrql: NrqlQuery{
+ Query: "SELECT 1 FROM MyEvents",
+ SinceValue: "5",
+ },
+ ValueFunction: "max",
+ ViolationCloseTimer: 60,
+ ExpectedGroups: 2,
+ IgnoreOverlap: true,
},
- ValueFunction: "max",
- ViolationCloseTimer: 60,
- ExpectedGroups: 2,
- IgnoreOverlap: true,
+ APMSpecificSpec{},
},
- APMSpecificSpec{},
},
},
},
- },
- }
+ }
+ })
Context("when given a policy with no incident_preference set", func() {
It("should set default value of PER_POLICY", func() {
diff --git a/api/v1/suite_test.go b/api/v1/suite_test.go
index 931142b..edc1800 100644
--- a/api/v1/suite_test.go
+++ b/api/v1/suite_test.go
@@ -66,5 +66,6 @@ func ignoreAlreadyExists(err error) error {
if apierrors.IsAlreadyExists(err) {
return nil
}
+
return err
}
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 19cad79..f84d254 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -21,6 +21,7 @@ package v1
import (
"github.com/newrelic/newrelic-client-go/pkg/alerts"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
@@ -177,6 +178,123 @@ func (in *AlertsAPMSpecificSpec) DeepCopy() *AlertsAPMSpecificSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlertsChannel) DeepCopyInto(out *AlertsChannel) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertsChannel.
+func (in *AlertsChannel) DeepCopy() *AlertsChannel {
+ if in == nil {
+ return nil
+ }
+ out := new(AlertsChannel)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *AlertsChannel) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlertsChannelConfiguration) DeepCopyInto(out *AlertsChannelConfiguration) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertsChannelConfiguration.
+func (in *AlertsChannelConfiguration) DeepCopy() *AlertsChannelConfiguration {
+ if in == nil {
+ return nil
+ }
+ out := new(AlertsChannelConfiguration)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlertsChannelList) DeepCopyInto(out *AlertsChannelList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]AlertsChannel, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertsChannelList.
+func (in *AlertsChannelList) DeepCopy() *AlertsChannelList {
+ if in == nil {
+ return nil
+ }
+ out := new(AlertsChannelList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *AlertsChannelList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlertsChannelSpec) DeepCopyInto(out *AlertsChannelSpec) {
+ *out = *in
+ out.APIKeySecret = in.APIKeySecret
+ in.Links.DeepCopyInto(&out.Links)
+ out.Configuration = in.Configuration
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertsChannelSpec.
+func (in *AlertsChannelSpec) DeepCopy() *AlertsChannelSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(AlertsChannelSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AlertsChannelStatus) DeepCopyInto(out *AlertsChannelStatus) {
+ *out = *in
+ if in.AppliedSpec != nil {
+ in, out := &in.AppliedSpec, &out.AppliedSpec
+ *out = new(AlertsChannelSpec)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.AppliedPolicyIDs != nil {
+ in, out := &in.AppliedPolicyIDs, &out.AppliedPolicyIDs
+ *out = make([]int, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AlertsChannelStatus.
+func (in *AlertsChannelStatus) DeepCopy() *AlertsChannelStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(AlertsChannelStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AlertsGenericConditionSpec) DeepCopyInto(out *AlertsGenericConditionSpec) {
*out = *in
@@ -567,6 +685,38 @@ func (in *ApmAlertConditionStatus) DeepCopy() *ApmAlertConditionStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ChannelLinks) DeepCopyInto(out *ChannelLinks) {
+ *out = *in
+ if in.PolicyIDs != nil {
+ in, out := &in.PolicyIDs, &out.PolicyIDs
+ *out = make([]int, len(*in))
+ copy(*out, *in)
+ }
+ if in.PolicyNames != nil {
+ in, out := &in.PolicyNames, &out.PolicyNames
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.PolicyKubernetesObjects != nil {
+ in, out := &in.PolicyKubernetesObjects, &out.PolicyKubernetesObjects
+ *out = make([]metav1.ObjectMeta, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChannelLinks.
+func (in *ChannelLinks) DeepCopy() *ChannelLinks {
+ if in == nil {
+ return nil
+ }
+ out := new(ChannelLinks)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConditionSpec) DeepCopyInto(out *ConditionSpec) {
*out = *in
diff --git a/cmd/manager/alerts.go b/cmd/manager/alerts.go
index a65580f..265352c 100644
--- a/cmd/manager/alerts.go
+++ b/cmd/manager/alerts.go
@@ -123,6 +123,25 @@ func registerAlerts(mgr *ctrl.Manager) error {
os.Exit(1)
}
+ //alertsChannel
+ alertsChannelReconciler := &controllers.AlertsChannelReconciler{
+ Client: (*mgr).GetClient(),
+ Log: ctrl.Log.WithName("controllers").WithName("alertsChannel"),
+ Scheme: (*mgr).GetScheme(),
+ AlertClientFunc: interfaces.InitializeAlertsClient,
+ }
+
+ if err := alertsChannelReconciler.SetupWithManager(*mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "alertsChannel")
+ os.Exit(1)
+ }
+
+ alertsChannel := &nrv1.AlertsChannel{}
+ if err := alertsChannel.SetupWebhookWithManager(*mgr); err != nil {
+ setupLog.Error(err, "unable to create webhook", "webhook", "AlertsChannel")
+ os.Exit(1)
+ }
+
// alertspolicy
alertsPolicyReconciler := &controllers.AlertsPolicyReconciler{
Client: (*mgr).GetClient(),
@@ -130,7 +149,6 @@ func registerAlerts(mgr *ctrl.Manager) error {
Scheme: (*mgr).GetScheme(),
AlertClientFunc: interfaces.InitializeAlertsClient,
}
-
if err := alertsPolicyReconciler.SetupWithManager(*mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "AlertsPolicy")
os.Exit(1)
diff --git a/configs/crd/bases/nr.k8s.newrelic.com_alertschannels.yaml b/configs/crd/bases/nr.k8s.newrelic.com_alertschannels.yaml
new file mode 100644
index 0000000..c1a8853
--- /dev/null
+++ b/configs/crd/bases/nr.k8s.newrelic.com_alertschannels.yaml
@@ -0,0 +1,218 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.3.0
+ creationTimestamp: null
+ name: alertschannels.nr.k8s.newrelic.com
+spec:
+ additionalPrinterColumns:
+ - JSONPath: .status.created
+ name: Created
+ type: boolean
+ group: nr.k8s.newrelic.com
+ names:
+ kind: AlertsChannel
+ listKind: AlertsChannelList
+ plural: alertschannels
+ singular: alertschannel
+ scope: Namespaced
+ subresources: {}
+ validation:
+ openAPIV3Schema:
+ description: AlertsChannel is the Schema for the AlertsChannel API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: AlertsChannelSpec defines the desired state of AlertsChannel
+ properties:
+ api_key:
+ type: string
+ api_key_secret:
+ properties:
+ key_name:
+ type: string
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ configuration:
+ description: AlertsChannelConfiguration - copy of alerts.ChannelConfiguration
+ properties:
+ api_key:
+ type: string
+ auth_password:
+ type: string
+ auth_token:
+ type: string
+ auth_username:
+ type: string
+ base_url:
+ type: string
+ channel:
+ type: string
+ include_json_attachment:
+ type: string
+ key:
+ type: string
+ payload_type:
+ type: string
+ recipients:
+ type: string
+ region:
+ type: string
+ route_key:
+ type: string
+ service_key:
+ type: string
+ tags:
+ type: string
+ teams:
+ type: string
+ url:
+ type: string
+ user_id:
+ type: string
+ type: object
+ id:
+ type: integer
+ links:
+ description: ChannelLinks - copy of alerts.ChannelLinks
+ properties:
+ policy_ids:
+ items:
+ type: integer
+ type: array
+ policy_kubernetes_objects:
+ items:
+ type: object
+ type: array
+ policy_names:
+ items:
+ type: string
+ type: array
+ type: object
+ name:
+ type: string
+ region:
+ type: string
+ type:
+ type: string
+ required:
+ - name
+ type: object
+ status:
+ description: AlertsChannelStatus defines the observed state of AlertsChannel
+ properties:
+ applied_spec:
+ description: AlertsChannelSpec defines the desired state of AlertsChannel
+ properties:
+ api_key:
+ type: string
+ api_key_secret:
+ properties:
+ key_name:
+ type: string
+ name:
+ type: string
+ namespace:
+ type: string
+ type: object
+ configuration:
+ description: AlertsChannelConfiguration - copy of alerts.ChannelConfiguration
+ properties:
+ api_key:
+ type: string
+ auth_password:
+ type: string
+ auth_token:
+ type: string
+ auth_username:
+ type: string
+ base_url:
+ type: string
+ channel:
+ type: string
+ include_json_attachment:
+ type: string
+ key:
+ type: string
+ payload_type:
+ type: string
+ recipients:
+ type: string
+ region:
+ type: string
+ route_key:
+ type: string
+ service_key:
+ type: string
+ tags:
+ type: string
+ teams:
+ type: string
+ url:
+ type: string
+ user_id:
+ type: string
+ type: object
+ id:
+ type: integer
+ links:
+ description: ChannelLinks - copy of alerts.ChannelLinks
+ properties:
+ policy_ids:
+ items:
+ type: integer
+ type: array
+ policy_kubernetes_objects:
+ items:
+ type: object
+ type: array
+ policy_names:
+ items:
+ type: string
+ type: array
+ type: object
+ name:
+ type: string
+ region:
+ type: string
+ type:
+ type: string
+ required:
+ - name
+ type: object
+ appliedPolicyIDs:
+ items:
+ type: integer
+ type: array
+ channel_id:
+ type: integer
+ required:
+ - appliedPolicyIDs
+ - applied_spec
+ - channel_id
+ type: object
+ type: object
+ version: v1
+ versions:
+ - name: v1
+ served: true
+ storage: true
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: []
+ storedVersions: []
diff --git a/configs/crd/kustomization.yaml b/configs/crd/kustomization.yaml
index 77592e7..505c94e 100644
--- a/configs/crd/kustomization.yaml
+++ b/configs/crd/kustomization.yaml
@@ -5,6 +5,7 @@ resources:
- bases/nr.k8s.newrelic.com_nrqlalertconditions.yaml
- bases/nr.k8s.newrelic.com_policies.yaml
- bases/nr.k8s.newrelic.com_apmalertconditions.yaml
+- bases/nr.k8s.newrelic.com_alertschannels.yaml
- bases/nr.k8s.newrelic.com_alertsnrqlconditions.yaml
- bases/nr.k8s.newrelic.com_alertspolicies.yaml
- bases/nr.k8s.newrelic.com_alertsapmconditions.yaml
diff --git a/configs/crd/patches/cainjection_in_alertschannels.yaml b/configs/crd/patches/cainjection_in_alertschannels.yaml
new file mode 100644
index 0000000..ccc8ce2
--- /dev/null
+++ b/configs/crd/patches/cainjection_in_alertschannels.yaml
@@ -0,0 +1,8 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: alertschannels.nr.k8s.newrelic.com
diff --git a/configs/crd/patches/webhook_in_alertschannels.yaml b/configs/crd/patches/webhook_in_alertschannels.yaml
new file mode 100644
index 0000000..0c45cef
--- /dev/null
+++ b/configs/crd/patches/webhook_in_alertschannels.yaml
@@ -0,0 +1,17 @@
+# The following patch enables conversion webhook for CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+ name: alertschannels.nr.k8s.newrelic.com
+spec:
+ conversion:
+ strategy: Webhook
+ webhookClientConfig:
+ # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
+ # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
+ caBundle: Cg==
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
diff --git a/configs/manifests.yaml b/configs/manifests.yaml
index af01720..f04ff75 100644
--- a/configs/manifests.yaml
+++ b/configs/manifests.yaml
@@ -63,6 +63,25 @@ webhooks:
resources:
- alertspolicies
sideEffects: None
+- clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-alertschannel
+ failurePolicy: Fail
+ name: malertschannel.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertschannels
+ sideEffects: None
- clientConfig:
caBundle: Cg==
service:
@@ -185,6 +204,25 @@ webhooks:
resources:
- alertspolicies
sideEffects: None
+- clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-alertschannel
+ failurePolicy: Fail
+ name: valertschannel.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertschannels
+ sideEffects: None
- clientConfig:
caBundle: Cg==
service:
diff --git a/configs/rbac/alertschannel_editor_role.yaml b/configs/rbac/alertschannel_editor_role.yaml
new file mode 100644
index 0000000..c7fb552
--- /dev/null
+++ b/configs/rbac/alertschannel_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit alertschannels.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: alertschannel-editor-role
+rules:
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannels
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannels/status
+ verbs:
+ - get
diff --git a/configs/rbac/alertschannel_viewer_role.yaml b/configs/rbac/alertschannel_viewer_role.yaml
new file mode 100644
index 0000000..8ca0a0e
--- /dev/null
+++ b/configs/rbac/alertschannel_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view alertschannels.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: alertschannel-viewer-role
+rules:
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannels
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannels/status
+ verbs:
+ - get
diff --git a/configs/rbac/role.yaml b/configs/rbac/role.yaml
index 404e734..d23d3d6 100644
--- a/configs/rbac/role.yaml
+++ b/configs/rbac/role.yaml
@@ -69,6 +69,26 @@ rules:
- get
- patch
- update
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannels
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannels/status
+ verbs:
+ - get
+ - patch
+ - update
- apiGroups:
- nr.k8s.newrelic.com
resources:
@@ -129,4 +149,3 @@ rules:
- get
- patch
- update
-
diff --git a/configs/role.yaml b/configs/role.yaml
index b41e666..85af48f 100644
--- a/configs/role.yaml
+++ b/configs/role.yaml
@@ -26,6 +26,26 @@ rules:
- get
- patch
- update
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannel
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - nr.k8s.newrelic.com
+ resources:
+ - alertschannel/status
+ verbs:
+ - get
+ - patch
+ - update
- apiGroups:
- nr.k8s.newrelic.com
resources:
diff --git a/configs/webhook/manifests.yaml b/configs/webhook/manifests.yaml
index 0e8994c..b623cb1 100644
--- a/configs/webhook/manifests.yaml
+++ b/configs/webhook/manifests.yaml
@@ -1,233 +1,283 @@
---
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: MutatingWebhookConfiguration
-metadata:
- creationTimestamp: null
- name: mutating-webhook-configuration
-webhooks:
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /mutate-nr-k8s-newrelic-com-v1-apmalertcondition
- failurePolicy: Fail
- name: mapmalertcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - apmalertconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /mutate-nr-k8s-newrelic-com-v1-nrqlalertcondition
- failurePolicy: Fail
- name: mnrqlalertcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - nrqlalertconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /mutate-nr-k8s-newrelic-com-v1-policy
- failurePolicy: Fail
- name: mpolicy.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - policies
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /mutate-nr-k8s-newrelic-com-v1-alertsapmcondition
- failurePolicy: Fail
- name: malertsapmcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - alertsapmconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /mutate-nr-k8s-newrelic-com-v1-alertsnrqlcondition
- failurePolicy: Fail
- name: malertsnrqlcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - alertsnrqlconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /mutate-nr-k8s-newrelic-com-v1-alertspolicy
- failurePolicy: Fail
- name: malertspolicy.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - alertspolicies
-
-
----
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: ValidatingWebhookConfiguration
-metadata:
- creationTimestamp: null
- name: validating-webhook-configuration
-webhooks:
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /validate-nr-k8s-newrelic-com-v1-apmalertcondition
- failurePolicy: Fail
- name: vapmalertcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - apmalertconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /validate-nr-k8s-newrelic-com-v1-nrqlalertcondition
- failurePolicy: Fail
- name: vnrqlalertcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - nrqlalertconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /validate-nr-k8s-newrelic-com-v1-policy
- failurePolicy: Fail
- name: vpolicy.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - policies
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /validate-nr-k8s-newrelic-com-v1-alertsapmcondition
- failurePolicy: Fail
- name: valertsapmcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - alertsapmconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /validate-nr-k8s-newrelic-com-v1-alertsnrqlcondition
- failurePolicy: Fail
- name: valertsnrqlcondition.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - alertsnrqlconditions
-- clientConfig:
- caBundle: Cg==
- service:
- name: webhook-service
- namespace: system
- path: /validate-nr-k8s-newrelic-com-v1-alertspolicy
- failurePolicy: Fail
- name: valertspolicy.kb.io
- rules:
- - apiGroups:
- - nr.k8s.newrelic.com
- apiVersions:
- - v1
- operations:
- - CREATE
- - UPDATE
- resources:
- - alertspolicies
+ apiVersion: admissionregistration.k8s.io/v1beta1
+ kind: MutatingWebhookConfiguration
+ metadata:
+ creationTimestamp: null
+ name: mutating-webhook-configuration
+ webhooks:
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-alertsapmcondition
+ failurePolicy: Fail
+ name: malertsapmcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertsapmconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-alertsnrqlcondition
+ failurePolicy: Fail
+ name: malertsnrqlcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertsnrqlconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-alertspolicy
+ failurePolicy: Fail
+ name: malertspolicy.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertspolicies
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-alertschannel
+ failurePolicy: Fail
+ name: malertschannel.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertschannels
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-apmalertcondition
+ failurePolicy: Fail
+ name: mapmalertcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - apmalertconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-nrqlalertcondition
+ failurePolicy: Fail
+ name: mnrqlalertcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - nrqlalertconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /mutate-nr-k8s-newrelic-com-v1-policy
+ failurePolicy: Fail
+ name: mpolicy.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - policies
+ sideEffects: None
+
+ ---
+ apiVersion: admissionregistration.k8s.io/v1beta1
+ kind: ValidatingWebhookConfiguration
+ metadata:
+ creationTimestamp: null
+ name: validating-webhook-configuration
+ webhooks:
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-alertsapmcondition
+ failurePolicy: Fail
+ name: valertsapmcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertsapmconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-alertsnrqlcondition
+ failurePolicy: Fail
+ name: valertsnrqlcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertsnrqlconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-alertspolicy
+ failurePolicy: Fail
+ name: valertspolicy.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertspolicies
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-alertschannel
+ failurePolicy: Fail
+ name: valertschannel.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - alertschannels
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-apmalertcondition
+ failurePolicy: Fail
+ name: vapmalertcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - apmalertconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-nrqlalertcondition
+ failurePolicy: Fail
+ name: vnrqlalertcondition.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - nrqlalertconditions
+ sideEffects: None
+ - clientConfig:
+ caBundle: Cg==
+ service:
+ name: webhook-service
+ namespace: system
+ path: /validate-nr-k8s-newrelic-com-v1-policy
+ failurePolicy: Fail
+ name: vpolicy.kb.io
+ rules:
+ - apiGroups:
+ - nr.k8s.newrelic.com
+ apiVersions:
+ - v1
+ operations:
+ - CREATE
+ - UPDATE
+ resources:
+ - policies
+ sideEffects: None
+
\ No newline at end of file
diff --git a/controllers/alerts_apmcondition_controller.go b/controllers/alerts_apmcondition_controller.go
index cb337fb..a210e1b 100644
--- a/controllers/alerts_apmcondition_controller.go
+++ b/controllers/alerts_apmcondition_controller.go
@@ -189,6 +189,7 @@ func (r *AlertsAPMConditionReconciler) Reconcile(req ctrl.Request) (ctrl.Result,
func (r *AlertsAPMConditionReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.AlertClientFunc = interfaces.InitializeAlertsClient
+
return ctrl.NewControllerManagedBy(mgr).
For(&nralertsv1.AlertsAPMCondition{}).
Complete(r)
@@ -197,6 +198,7 @@ func (r *AlertsAPMConditionReconciler) SetupWithManager(mgr ctrl.Manager) error
func (r *AlertsAPMConditionReconciler) checkForExistingCondition(condition *nralertsv1.AlertsAPMCondition) {
if condition.Status.ConditionID == 0 {
r.Log.Info("Checking for existing condition", "conditionName", condition.Name)
+
//if no conditionId, get list of conditions and compare name
existingPolicyIDInt, err := strconv.Atoi(condition.Spec.ExistingPolicyID)
if err != nil {
@@ -225,6 +227,7 @@ func (r *AlertsAPMConditionReconciler) checkForExistingCondition(condition *nral
func (r *AlertsAPMConditionReconciler) deleteNewRelicAlertCondition(condition nralertsv1.AlertsAPMCondition) error {
r.Log.Info("Deleting condition", "conditionName", condition.Spec.Name)
+
_, err := r.Alerts.DeleteCondition(condition.Status.ConditionID)
if err != nil {
r.Log.Error(err, "Error deleting condition",
@@ -232,24 +235,30 @@ func (r *AlertsAPMConditionReconciler) deleteNewRelicAlertCondition(condition nr
"region", condition.Spec.Region,
"Api Key", interfaces.PartialAPIKey(r.apiKey),
)
+
return err
}
+
return nil
}
func (r *AlertsAPMConditionReconciler) getAPIKeyOrSecret(condition nralertsv1.AlertsAPMCondition) string {
-
if condition.Spec.APIKey != "" {
return condition.Spec.APIKey
}
+
if condition.Spec.APIKeySecret != (nralertsv1.NewRelicAPIKeySecret{}) {
- key := types.NamespacedName{Namespace: condition.Spec.APIKeySecret.Namespace, Name: condition.Spec.APIKeySecret.Name}
var apiKeySecret v1.Secret
+
+ key := types.NamespacedName{Namespace: condition.Spec.APIKeySecret.Namespace, Name: condition.Spec.APIKeySecret.Name}
+
if getErr := r.Client.Get(context.Background(), key, &apiKeySecret); getErr != nil {
r.Log.Error(getErr, "Error retrieving secret", "secret", apiKeySecret)
return ""
}
+
return string(apiKeySecret.Data[condition.Spec.APIKeySecret.KeyName])
}
+
return ""
}
diff --git a/controllers/alerts_apmcondition_controller_integration_test.go b/controllers/alerts_apmcondition_controller_integration_test.go
index 2c54c4a..bedc6e8 100644
--- a/controllers/alerts_apmcondition_controller_integration_test.go
+++ b/controllers/alerts_apmcondition_controller_integration_test.go
@@ -23,14 +23,6 @@ import (
)
var _ = Describe("ApmCondition reconciliation", func() {
- BeforeEach(func() {
- err := ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "my-namespace",
- },
- }))
- Expect(err).ToNot(HaveOccurred())
- })
var (
ctx context.Context
r *AlertsAPMConditionReconciler
@@ -40,7 +32,15 @@ var _ = Describe("ApmCondition reconciliation", func() {
secret *v1.Secret
fakeAlertFunc func(string, string) (interfaces.NewRelicAlertsClient, error)
)
+
BeforeEach(func() {
+ err := ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-namespace",
+ },
+ }))
+ Expect(err).ToNot(HaveOccurred())
+
ctx = context.Background()
alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
@@ -49,10 +49,12 @@ var _ = Describe("ApmCondition reconciliation", func() {
a.ID = 111
return &a, nil
}
+
alertsClient.UpdateConditionStub = func(a alerts.Condition) (*alerts.Condition, error) {
a.ID = 112
return &a, nil
}
+
alertsClient.ListConditionsStub = func(int) ([]*alerts.Condition, error) {
var a []*alerts.Condition
a = append(a, &alerts.Condition{
@@ -118,16 +120,16 @@ var _ = Describe("ApmCondition reconciliation", func() {
ConditionID: 0,
},
}
+
namespacedName = types.NamespacedName{
Namespace: "default",
Name: "test-condition",
}
- request = ctrl.Request{NamespacedName: namespacedName}
+ request = ctrl.Request{NamespacedName: namespacedName}
})
Context("When starting with no conditions", func() {
-
Context("and given a new AlertsAPMCondition", func() {
Context("with a valid condition", func() {
It("should create that condition via the AlertClient", func() {
@@ -193,8 +195,8 @@ var _ = Describe("ApmCondition reconciliation", func() {
}
Expect(ignoreAlreadyExists(k8sClient.Create(ctx, secret))).To(Succeed())
})
- It("should create that condition via the AlertClient", func() {
+ It("should create that condition via the AlertClient", func() {
err := k8sClient.Create(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -283,7 +285,6 @@ var _ = Describe("ApmCondition reconciliation", func() {
})
Context("with a valid condition", func() {
-
It("does not create a new condition", func() {
err := k8sClient.Create(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -322,7 +323,6 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Status.AppliedSpec).To(Equal(&condition.Spec))
})
-
})
})
@@ -419,7 +419,6 @@ var _ = Describe("ApmCondition reconciliation", func() {
})
Context("When starting with an existing condition", func() {
-
Context("and deleting a AlertsAPMCondition", func() {
BeforeEach(func() {
err := k8sClient.Create(ctx, condition)
@@ -434,6 +433,7 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).ToNot(HaveOccurred())
})
+
Context("with a valid condition", func() {
It("should delete that condition via the AlertClient", func() {
err := k8sClient.Delete(ctx, condition)
@@ -462,14 +462,15 @@ var _ = Describe("ApmCondition reconciliation", func() {
})
})
- Context("with a condition with no condition ID", func() {
+ Context("with a condition with no condition ID", func() {
BeforeEach(func() {
condition.Status.ConditionID = 0
err := k8sClient.Update(ctx, condition)
Expect(err).ToNot(HaveOccurred())
})
+
It("should just remove the finalizer and delete", func() {
err := k8sClient.Delete(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -487,17 +488,15 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
Context("when the Alerts API reports no condition found ", func() {
-
BeforeEach(func() {
alertsClient.DeleteConditionStub = func(int) (*alerts.Condition, error) {
return &alerts.Condition{}, errors.New("resource not found")
}
-
})
+
It("should just remove the finalizer and delete", func() {
err := k8sClient.Delete(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -515,9 +514,7 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
-
})
})
})
diff --git a/controllers/alerts_nrqlcondition_controller.go b/controllers/alerts_nrqlcondition_controller.go
index 3da4f36..430d122 100644
--- a/controllers/alerts_nrqlcondition_controller.go
+++ b/controllers/alerts_nrqlcondition_controller.go
@@ -60,6 +60,7 @@ func (r *AlertsNrqlConditionReconciler) Reconcile(req ctrl.Request) (ctrl.Result
r.Log.Info("starting reconcile action")
var condition nrv1.AlertsNrqlCondition
+
err := r.Client.Get(ctx, req.NamespacedName, &condition)
if err != nil {
if strings.Contains(err.Error(), " not found") {
@@ -213,6 +214,7 @@ func (r *AlertsNrqlConditionReconciler) checkForExistingCondition(condition *nrv
func (r *AlertsNrqlConditionReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.AlertClientFunc = interfaces.InitializeAlertsClient
+
return ctrl.NewControllerManagedBy(mgr).
For(&nrv1.AlertsNrqlCondition{}).
Complete(r)
diff --git a/controllers/alerts_nrqlcondition_controller_integration_test.go b/controllers/alerts_nrqlcondition_controller_integration_test.go
index d1c2131..0aa9911 100644
--- a/controllers/alerts_nrqlcondition_controller_integration_test.go
+++ b/controllers/alerts_nrqlcondition_controller_integration_test.go
@@ -235,7 +235,6 @@ var _ = Describe("AlertsNrqlCondition reconciliation", func() {
ConditionID: "0",
},
}
-
})
Context("with a valid condition", func() {
@@ -441,7 +440,6 @@ var _ = Describe("AlertsNrqlCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
})
})
diff --git a/controllers/alerts_policy_controller.go b/controllers/alerts_policy_controller.go
index c753301..67e3d64 100644
--- a/controllers/alerts_policy_controller.go
+++ b/controllers/alerts_policy_controller.go
@@ -133,6 +133,7 @@ func (r *AlertsPolicyReconciler) createAlertsPolicy(policy *nrv1.AlertsPolicy) e
"region", policy.Spec.Region,
"apiKey", interfaces.PartialAPIKey(r.apiKey),
)
+
return err
}
@@ -141,6 +142,7 @@ func (r *AlertsPolicyReconciler) createAlertsPolicy(policy *nrv1.AlertsPolicy) e
errConditions := r.createConditions(policy)
if errConditions != nil {
r.Log.Error(errConditions, "error creating or updating conditions")
+
return errConditions
}
r.Log.Info("policy after condition creation", "policyCondition", policy.Spec.Conditions, "pointer", &policy)
@@ -258,6 +260,7 @@ func (r *AlertsPolicyReconciler) updateNrqlCondition(policy *nrv1.AlertsPolicy,
nrqlCondition.Spec.AccountID = policy.Spec.AccountID
err := r.Client.Update(r.ctx, &nrqlCondition)
+
return err
}
@@ -290,6 +293,7 @@ func (r *AlertsPolicyReconciler) updateApmCondition(policy *nrv1.AlertsPolicy, c
r.Log.Info("updating existing condition", "alertsAPMCondition", apmCondition)
err := r.Client.Update(r.ctx, &apmCondition)
+
return err
}
@@ -450,6 +454,7 @@ func (r *AlertsPolicyReconciler) getApmConditionFromAlertsPolicyCondition(condit
//throw away the error since empty conditions are expected
_ = r.Client.Get(r.ctx, condition.GetNamespace(), &apmCondition)
r.Log.Info("retrieved condition", "alertsAPMCondition", apmCondition, "namespace", condition.GetNamespace())
+
return
}
@@ -573,6 +578,7 @@ func (r *AlertsPolicyReconciler) checkForExistingAlertsPolicy(policy *nrv1.Alert
if existingAlertsPolicy.Name == policy.Spec.Name {
r.Log.Info("matched on existing policy, updating PolicyId", "policyId", existingAlertsPolicy.ID)
policy.Status.PolicyID = existingAlertsPolicy.ID
+
break
}
}
@@ -607,8 +613,10 @@ func (r *AlertsPolicyReconciler) getAPIKeyOrSecret(policy nrv1.AlertsPolicy) str
getErr := r.Client.Get(context.Background(), key, &apiKeySecret)
if getErr != nil {
r.Log.Error(getErr, "Failed to retrieve secret", "secret", apiKeySecret)
+
return ""
}
+
return string(apiKeySecret.Data[policy.Spec.APIKeySecret.KeyName])
}
diff --git a/controllers/alerts_policy_controller_integration_test.go b/controllers/alerts_policy_controller_integration_test.go
index ac967c7..56fb000 100644
--- a/controllers/alerts_policy_controller_integration_test.go
+++ b/controllers/alerts_policy_controller_integration_test.go
@@ -68,7 +68,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Name: "test-alertspolicy",
}
request = ctrl.Request{NamespacedName: namespacedName}
-
})
Context("When starting with no policies", func() {
@@ -155,8 +154,8 @@ var _ = Describe("alertspolicy reconciliation", func() {
Name: "test-alertspolicy",
}
request = ctrl.Request{NamespacedName: namespacedName}
-
})
+
Context("when creating a valid alertspolicy", func() {
It("should create that alertspolicy", func() {
@@ -184,6 +183,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateAlertsPolicy.Status.PolicyID).To(Equal("333"))
})
+
It("creates the NRQL condition with attributes from the AlertsPolicy", func() {
err := k8sClient.Create(ctx, alertspolicy)
Expect(err).ToNot(HaveOccurred())
@@ -205,11 +205,9 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Spec.Nrql.Query).To(Equal("SELECT 1 FROM MyEvents"))
Expect(string(endStateCondition.Spec.Terms[0].Priority)).To(Equal("critical"))
Expect(endStateCondition.Spec.Enabled).To(BeTrue())
-
})
It("creates the NRQL condition with inherited attributes from the AlertsPolicy resource", func() {
-
err := k8sClient.Create(ctx, alertspolicy)
Expect(err).ToNot(HaveOccurred())
@@ -231,9 +229,9 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Spec.ExistingPolicyID).To(Equal("333"))
Expect(endStateCondition.Spec.Region).To(Equal(alertspolicy.Spec.Region))
Expect(endStateCondition.Spec.APIKey).To(Equal(alertspolicy.Spec.APIKey))
-
})
})
+
Context("when the New Relic API returns an error", func() {
BeforeEach(func() {
mockAlertsClient.CreatePolicyMutationStub = func(int, alerts.AlertsPolicyInput) (*alerts.AlertsPolicy, error) {
@@ -309,12 +307,10 @@ var _ = Describe("alertspolicy reconciliation", func() {
err = k8sClient.Get(ctx, conditionNameType, &endStateCondition)
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Name).To(Equal("APM Condition"))
-
})
Context("when creating a valid alertspolicy with conditions with k8 resource name set", func() {
It("should create the conditions with an auto-generated name ignoring the manual name", func() {
-
alertspolicy.Spec.Conditions[0].Name = "my custom name"
err := k8sClient.Create(ctx, alertspolicy)
@@ -340,7 +336,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Name).ToNot(Equal("my custom name"))
})
})
-
})
AfterEach(func() {
@@ -352,12 +347,10 @@ var _ = Describe("alertspolicy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
Context("When starting with an existing alertspolicy with a NRQL condition", func() {
BeforeEach(func() {
-
conditionSpec = &nrv1.AlertsPolicyConditionSpec{
nrv1.AlertsGenericConditionSpec{
Terms: []nrv1.AlertsNrqlConditionTerm{
@@ -422,6 +415,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, alertspolicy)
Expect(err).ToNot(HaveOccurred())
})
+
Context("and deleting that alertspolicy", func() {
It("should successfully delete", func() {
err := k8sClient.Delete(ctx, alertspolicy)
@@ -434,8 +428,8 @@ var _ = Describe("alertspolicy reconciliation", func() {
var endStateAlertsPolicy nrv1.AlertsPolicy
err = k8sClient.Get(ctx, namespacedName, &endStateAlertsPolicy)
Expect(err).NotTo(BeNil())
-
})
+
It("should delete the condition", func() {
err := k8sClient.Delete(ctx, alertspolicy)
Expect(err).ToNot(HaveOccurred())
@@ -449,17 +443,19 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Spec.Name).ToNot(Equal(alertspolicy.Spec.Conditions[0].Spec.Name))
-
})
})
+
Context("and New Relic API returns a 404", func() {
BeforeEach(func() {
mockAlertsClient.DeletePolicyMutationStub = func(int, string) (*alerts.AlertsPolicy, error) {
return &alerts.AlertsPolicy{}, errors.New("Imaginary 404 Failure")
}
})
+
It("should succeed as if a previous reconcile already deleted the alertspolicy", func() {
})
+
AfterEach(func() {
mockAlertsClient.DeletePolicyMutationStub = func(int, string) (*alerts.AlertsPolicy, error) {
return &alerts.AlertsPolicy{}, nil
@@ -571,7 +567,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Name).To(Equal(initialConditionName))
})
-
})
Context("and updating that alertspolicy", func() {
@@ -593,7 +588,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(mockAlertsClient.UpdatePolicyMutationCallCount()).To(Equal(1))
})
-
})
Context("and updating a condition name", func() {
@@ -651,7 +645,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).ToNot(BeNil())
Expect(originalCondition.Spec.Name).To(Equal(""))
})
-
})
Context("and updating a condition ", func() {
@@ -684,7 +677,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Nrql.Query).To(Equal("SELECT count(*) FROM MyEvent"))
Expect(endStateCondition.Name).To(Equal(originalConditionName))
-
})
It("should set the inherited values on the updated condition", func() {
@@ -711,9 +703,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Name).To(Equal(originalConditionName))
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
-
})
-
})
Context("and adding another condition ", func() {
@@ -775,7 +765,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
})
-
})
Context("and when the alerts client returns an error", func() {
@@ -785,6 +774,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
}
alertspolicy.Spec.IncidentPreference = "PER_CONDITION_AND_TARGET"
})
+
It("should return an error", func() {
err := k8sClient.Update(ctx, alertspolicy)
Expect(err).ToNot(HaveOccurred())
@@ -803,7 +793,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
Context("When starting with an existing alertspolicy with an APM condition", func() {
@@ -872,7 +861,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
})
Context("and making no changes ", func() {
-
It("should not try to update or create new conditions", func() {
initialConditionName := alertspolicy.Spec.Conditions[0].Name
alertspolicy.Spec.Conditions[0].Name = ""
@@ -902,7 +890,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Name).To(Equal(initialConditionName))
Expect(endStateCondition.Spec.Name).To(Equal("APM Condition"))
})
-
})
Context("and updating that alertspolicy", func() {
@@ -922,9 +909,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, &endStateAlertsPolicy)
Expect(err).To(BeNil())
Expect(mockAlertsClient.UpdatePolicyMutationCallCount()).To(Equal(1))
-
})
-
})
Context("and updating a condition name", func() {
@@ -982,7 +967,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).ToNot(BeNil())
Expect(originalCondition.Spec.Name).To(Equal(""))
})
-
})
Context("and updating a condition ", func() {
@@ -1015,7 +999,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Metric).To(Equal("Custom/bar"))
Expect(endStateCondition.Name).To(Equal(originalConditionName))
-
})
It("should set the inherited values on the updated condition", func() {
@@ -1041,9 +1024,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Name).To(Equal(originalConditionName))
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
-
})
-
})
Context("and adding another apm condition ", func() {
@@ -1103,7 +1084,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
})
-
})
Context("and when the alerts client returns an error", func() {
@@ -1113,6 +1093,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
}
alertspolicy.Spec.IncidentPreference = "PER_CONDITION_AND_TARGET"
})
+
It("should return an error", func() {
err := k8sClient.Update(ctx, alertspolicy)
Expect(err).ToNot(HaveOccurred())
@@ -1131,7 +1112,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
Context("When starting with an existing alertspolicy with two NRQL conditions", func() {
@@ -1242,9 +1222,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
Context("and removing the second condition ", func() {
BeforeEach(func() {
-
alertspolicy.Spec.Conditions = []nrv1.AlertsPolicyCondition{alertspolicy.Spec.Conditions[0]}
-
})
It("should remove second condition ", func() {
@@ -1275,7 +1253,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
err = k8sClient.Get(ctx, deletedConditionNamespace, &deletedCondition)
Expect(err).ToNot(BeNil())
Expect(deletedCondition.Name).To(Equal(""))
-
})
It("should not call the alerts API ", func() {
@@ -1287,7 +1264,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).ToNot(HaveOccurred())
Expect(mockAlertsClient.UpdatePolicyMutationCallCount()).To(Equal(0))
})
-
})
Context("and removing the first condition ", func() {
@@ -1316,7 +1292,6 @@ var _ = Describe("alertspolicy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Name).To(Equal("second alert condition"))
})
-
})
Context("and when the alerts client returns an error", func() {
@@ -1326,6 +1301,7 @@ var _ = Describe("alertspolicy reconciliation", func() {
}
alertspolicy.Spec.IncidentPreference = "PER_CONDITION_AND_TARGET"
})
+
It("should return an error", func() {
err := k8sClient.Update(ctx, alertspolicy)
Expect(err).ToNot(HaveOccurred())
@@ -1344,7 +1320,5 @@ var _ = Describe("alertspolicy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
-
})
diff --git a/controllers/alertschannel_controller.go b/controllers/alertschannel_controller.go
new file mode 100644
index 0000000..5ace8dd
--- /dev/null
+++ b/controllers/alertschannel_controller.go
@@ -0,0 +1,405 @@
+/*
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "errors"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+
+ v1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/types"
+
+ "github.com/go-logr/logr"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/newrelic/newrelic-client-go/pkg/alerts"
+
+ nrv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
+ "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
+)
+
+// AlertsChannelReconciler reconciles a AlertsChannel object
+type AlertsChannelReconciler struct {
+ client.Client
+ Log logr.Logger
+ Scheme *runtime.Scheme
+ AlertClientFunc func(string, string) (interfaces.NewRelicAlertsClient, error)
+ apiKey string
+ Alerts interfaces.NewRelicAlertsClient
+ ctx context.Context
+}
+
+// +kubebuilder:rbac:groups=nr.k8s.newrelic.com,resources=alertschannel,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=nr.k8s.newrelic.com,resources=alertschannel/status,verbs=get;update;patch
+
+//Reconcile - Main processing loop for AlertsChannel reconciliation
+func (r *AlertsChannelReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
+ var alertsChannel nrv1.AlertsChannel
+
+ r.ctx = context.Background()
+ r.Log.WithValues("alertsChannel", req.NamespacedName)
+
+ err := r.Client.Get(r.ctx, req.NamespacedName, &alertsChannel)
+ if err != nil {
+ if strings.Contains(err.Error(), " not found") {
+ r.Log.Info("AlertsChannel 'not found' after being deleted. This is expected and no cause for alarm", "error", err)
+ return ctrl.Result{}, nil
+ }
+ r.Log.Error(err, "Failed to GET alertsChannel", "name", req.NamespacedName.String())
+ return ctrl.Result{}, nil
+ }
+
+ r.Log.Info("alertsChannel", "alertsChannel.Spec", alertsChannel.Spec, "alertsChannel.status.applied", alertsChannel.Status.AppliedSpec)
+
+ r.apiKey = r.getAPIKeyOrSecret(alertsChannel)
+ if r.apiKey == "" {
+ return ctrl.Result{}, errors.New("api key is blank")
+ }
+
+ //initial alertsClient
+ alertsClient, errAlertsClient := r.AlertClientFunc(r.apiKey, alertsChannel.Spec.Region)
+
+ if errAlertsClient != nil {
+ r.Log.Error(errAlertsClient, "Failed to create AlertsClient")
+ return ctrl.Result{}, errAlertsClient
+ }
+ r.Alerts = alertsClient
+
+ deleteFinalizer := "alertschannels.finalizers.nr.k8s.newrelic.com"
+
+ //examine DeletionTimestamp to determine if object is under deletion
+ if alertsChannel.DeletionTimestamp.IsZero() {
+ if !containsString(alertsChannel.Finalizers, deleteFinalizer) {
+ alertsChannel.Finalizers = append(alertsChannel.Finalizers, deleteFinalizer)
+ }
+ } else {
+ err := r.deleteAlertsChannel(&alertsChannel, deleteFinalizer)
+ if err != nil {
+ r.Log.Error(err, "error deleting channel", "name", alertsChannel.Name)
+ return ctrl.Result{}, err
+ }
+ return ctrl.Result{}, nil
+ }
+
+ if reflect.DeepEqual(&alertsChannel.Spec, alertsChannel.Status.AppliedSpec) {
+ return ctrl.Result{}, nil
+ }
+
+ r.Log.Info("Reconciling", "alertsChannel", alertsChannel.Name)
+
+ r.checkForExistingAlertsChannel(&alertsChannel)
+
+ if alertsChannel.Status.ChannelID != 0 {
+ err := r.updateAlertsChannel(&alertsChannel)
+ if err != nil {
+ r.Log.Error(err, "error updating alertsChannel")
+ return ctrl.Result{}, err
+ }
+ } else {
+ err := r.createAlertsChannel(&alertsChannel)
+ if err != nil {
+ r.Log.Error(err, "Error creating alertsChannel")
+ return ctrl.Result{}, err
+ }
+ }
+
+ return ctrl.Result{}, nil
+}
+
+//SetupWithManager - Sets up Controller for AlertsChannel
+func (r *AlertsChannelReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&nrv1.AlertsChannel{}).
+ Complete(r)
+}
+
+func (r *AlertsChannelReconciler) getAPIKeyOrSecret(alertschannel nrv1.AlertsChannel) string {
+ if alertschannel.Spec.APIKey != "" {
+ return alertschannel.Spec.APIKey
+ }
+
+ if alertschannel.Spec.APIKeySecret != (nrv1.NewRelicAPIKeySecret{}) {
+ var apiKeySecret v1.Secret
+
+ key := types.NamespacedName{Namespace: alertschannel.Spec.APIKeySecret.Namespace, Name: alertschannel.Spec.APIKeySecret.Name}
+
+ getErr := r.Client.Get(context.Background(), key, &apiKeySecret)
+ if getErr != nil {
+ r.Log.Error(getErr, "Failed to retrieve secret", "secret", apiKeySecret)
+ return ""
+ }
+
+ return string(apiKeySecret.Data[alertschannel.Spec.APIKeySecret.KeyName])
+ }
+
+ return ""
+}
+
+func (r *AlertsChannelReconciler) deleteAlertsChannel(alertsChannel *nrv1.AlertsChannel, deleteFinalizer string) (err error) {
+ r.Log.Info("Deleting AlertsChannel", "name", alertsChannel.Name, "ChannelName", alertsChannel.Spec.Name)
+
+ if alertsChannel.Status.ChannelID != 0 {
+ _, err = r.Alerts.DeleteChannel(alertsChannel.Status.ChannelID)
+ if err != nil {
+ r.Log.Error(err, "error deleting AlertsChannel", "name", alertsChannel.Name, "ChannelName", alertsChannel.Spec.Name)
+ }
+
+ }
+
+ // Now remove finalizer
+ alertsChannel.Finalizers = removeString(alertsChannel.Finalizers, deleteFinalizer)
+
+ err = r.Client.Update(r.ctx, alertsChannel)
+ if err != nil {
+ r.Log.Error(err, "tried updating condition status", "name", alertsChannel.Name, "Namespace", alertsChannel.Namespace)
+ return err
+ }
+
+ return nil
+}
+
+func (r *AlertsChannelReconciler) createAlertsChannel(alertsChannel *nrv1.AlertsChannel) error {
+ r.Log.Info("Creating AlertsChannel", "name", alertsChannel.Name, "ChannelName", alertsChannel.Spec.Name)
+ APIChannel := alertsChannel.Spec.APIChannel()
+
+ r.Log.Info("API Payload before calling NR API", "APIChannel", APIChannel)
+
+ createdChannel, err := r.Alerts.CreateChannel(APIChannel)
+ if err != nil {
+ r.Log.Error(err, "Error creating AlertsChannel"+alertsChannel.Name)
+ return err
+ }
+
+ alertsChannel.Status.ChannelID = createdChannel.ID
+
+ // Now create the links to policies
+ allPolicyIDs, err := r.getAllPolicyIDs(&alertsChannel.Spec)
+
+ if err != nil {
+ r.Log.Error(err, "Error getting list of policyIds")
+ return err
+ }
+
+ for _, policyID := range allPolicyIDs {
+ policyChannels, errUpdatePolicies := r.Alerts.UpdatePolicyChannels(policyID, []int{createdChannel.ID})
+ if errUpdatePolicies != nil {
+ r.Log.Error(errUpdatePolicies, "error updating policyAlertsChannels", "policyID", policyID, "conditionID", createdChannel.ID, "policyChannels", policyChannels)
+ } else {
+ alertsChannel.Status.AppliedPolicyIDs = append(alertsChannel.Status.AppliedPolicyIDs, policyID)
+ }
+ }
+
+ alertsChannel.Status.AppliedSpec = &alertsChannel.Spec
+ errClientUpdate := r.Client.Update(r.ctx, alertsChannel)
+
+ if errClientUpdate != nil {
+ r.Log.Error(errClientUpdate, "Error updating channel status", "name", alertsChannel.Name, "Namespace", alertsChannel.Namespace)
+ return errClientUpdate
+ }
+
+ return nil
+}
+
+func (r *AlertsChannelReconciler) updateAlertsChannel(alertsChannel *nrv1.AlertsChannel) error {
+ r.Log.Info("Updating AlertsChannel", "name", alertsChannel.Name, "ChannelName", alertsChannel.Spec.Name)
+
+ //Check to see if update is needed
+ AppliedPolicyIDs, AppliedErr := r.getAllPolicyIDs(alertsChannel.Status.AppliedSpec)
+
+ if AppliedErr != nil {
+ r.Log.Error(AppliedErr, "Error getting list of AppliedPolicyIds")
+ return AppliedErr
+ }
+
+ IncomingPolicyIDs, incomingErr := r.getAllPolicyIDs(&alertsChannel.Spec)
+
+ if incomingErr != nil {
+ r.Log.Error(incomingErr, "Error getting list of AppliedPolicyIds")
+ return incomingErr
+ }
+
+ r.Log.Info("Updating list of policies attached to AlertsChannel",
+ "policyIDs", IncomingPolicyIDs,
+ "AppliedPolicyIDs", AppliedPolicyIDs,
+ )
+
+ processedPolicyIDs := make(map[int]bool)
+
+ for _, incomingPolicyID := range IncomingPolicyIDs {
+ processedPolicyIDs[incomingPolicyID] = false
+ }
+
+ for _, appliedPolicyID := range AppliedPolicyIDs {
+ if _, ok := processedPolicyIDs[appliedPolicyID]; ok {
+ processedPolicyIDs[appliedPolicyID] = true
+ } else {
+ r.Log.Info("Need to delete link to", "policyId", appliedPolicyID)
+ PolicyChannels, err := r.Alerts.DeletePolicyChannel(appliedPolicyID, alertsChannel.Status.ChannelID)
+ if err != nil {
+ r.Log.Error(err, "error updating policyAlertsChannels",
+ "policyID", appliedPolicyID,
+ "conditionID", alertsChannel.Status.ChannelID,
+ "PolicyChannels", PolicyChannels,
+ )
+ }
+ }
+ }
+
+ //Clear out AppliedPolicyIds
+ alertsChannel.Status.AppliedPolicyIDs = []int{}
+
+ for policyID, processed := range processedPolicyIDs {
+ r.Log.Info("processing ", "policyID", policyID, ":processed", processed)
+ alertsChannel.Status.AppliedPolicyIDs = append(alertsChannel.Status.AppliedPolicyIDs, policyID)
+
+ if !processed {
+ r.Log.Info("need to add ", "policyID", policyID)
+
+ policyChannels, err := r.Alerts.UpdatePolicyChannels(policyID, []int{alertsChannel.Status.ChannelID})
+ if err != nil {
+ r.Log.Error(err, "error updating policyAlertsChannels",
+ "policyID", policyID,
+ "conditionID", alertsChannel.Status.ChannelID,
+ "policyChannels", policyChannels,
+ )
+ r.Log.Info("policyChannels", "", policyChannels)
+ }
+ }
+ }
+
+ // Now update the AppliedSpec and the k8s object
+ alertsChannel.Status.AppliedSpec = &alertsChannel.Spec
+
+ err := r.Client.Update(r.ctx, alertsChannel)
+ if err != nil {
+ r.Log.Error(err, "Tried updating channel status", "name", alertsChannel.Name, "Namespace", alertsChannel.Namespace)
+ return err
+ }
+
+ return nil
+}
+
+func (r *AlertsChannelReconciler) checkForExistingAlertsChannel(alertsChannel *nrv1.AlertsChannel) {
+ r.Log.Info("Checking for existing Channels matching name: " + alertsChannel.Spec.Name)
+ retrievedChannels, err := r.Alerts.ListChannels()
+
+ if err != nil {
+ r.Log.Error(err, "error retrieving list of Channels")
+ return
+ }
+
+ // need to delete all non-matching spec channels
+ for _, channel := range retrievedChannels {
+ if channel.Name == alertsChannel.Spec.Name {
+ channelID := channel.ID
+ channel.ID = 0
+ APIChannel := alertsChannel.Spec.APIChannel()
+
+ if reflect.DeepEqual(&APIChannel, channel) {
+ r.Log.Info("Found matching Alerts Channel name from the New Relic API", "ID", channel.ID)
+ alertsChannel.Status.ChannelID = channelID
+
+ alertsChannel.Status.AppliedSpec = &alertsChannel.Spec
+ }
+
+ r.Log.Info("Found non matching channel so need to delete and create channel")
+
+ _, err = r.Alerts.DeleteChannel(channelID)
+ if err != nil {
+ r.Log.Error(err, "Error deleting non-matching AlertsChannel via New Relic API")
+ continue
+ }
+ }
+ }
+}
+
+func (r *AlertsChannelReconciler) getAllPolicyIDs(alertsChannelSpec *nrv1.AlertsChannelSpec) (policyIDs []int, err error) {
+ var retrievedPolicies []alerts.Policy
+ policyIDMap := make(map[int]bool)
+
+ for _, policyID := range alertsChannelSpec.Links.PolicyIDs {
+ policyIDMap[policyID] = true
+ }
+
+ if len(alertsChannelSpec.Links.PolicyNames) > 0 {
+ for _, policyName := range alertsChannelSpec.Links.PolicyNames {
+ alertParams := &alerts.ListPoliciesParams{
+ Name: policyName,
+ }
+
+ retrievedPolicies, err = r.Alerts.ListPolicies(alertParams)
+ if err != nil {
+ r.Log.Error(err, "Error getting list of policies")
+ return
+ }
+
+ for _, APIPolicy := range retrievedPolicies {
+ if policyName == APIPolicy.Name {
+ r.Log.Info("Found match of "+policyName, "policyId", APIPolicy.ID)
+ policyIDMap[APIPolicy.ID] = true
+ }
+ }
+ }
+ }
+
+ if len(alertsChannelSpec.Links.PolicyKubernetesObjects) > 0 {
+ r.Log.Info("Getting PolicyIds from PolicyKubernetesObjects",
+ "PolicyKubernetesObjects", alertsChannelSpec.Links.PolicyKubernetesObjects,
+ )
+
+ for _, policyK8s := range alertsChannelSpec.Links.PolicyKubernetesObjects {
+ key := types.NamespacedName{
+ Namespace: policyK8s.Namespace,
+ Name: policyK8s.Name,
+ }
+
+ var k8sPolicy nrv1.AlertsPolicy
+
+ err = r.Client.Get(context.Background(), key, &k8sPolicy)
+ if err != nil {
+ r.Log.Error(err, "Failed to retrieve policy", "k8sPolicy", key)
+ return
+ }
+
+ policyID, errInt := strconv.Atoi(k8sPolicy.Status.PolicyID)
+ if errInt != nil {
+ r.Log.Error(errInt, "Failed to parse policyID as an int")
+ err = errInt
+ }
+
+ if policyID != 0 {
+ policyIDMap[policyID] = true
+ } else {
+ r.Log.Info("Retrieved policy " + policyK8s.Name + " but ID was blank")
+ err = errors.New("Retrieved policy " + policyK8s.Name + " but ID was blank")
+ return
+ }
+ }
+ }
+
+ for policyID := range policyIDMap {
+ policyIDs = append(policyIDs, policyID)
+ }
+ sort.Ints(policyIDs)
+
+ return
+}
diff --git a/controllers/alertschannel_controller_test.go b/controllers/alertschannel_controller_test.go
new file mode 100644
index 0000000..72c97e0
--- /dev/null
+++ b/controllers/alertschannel_controller_test.go
@@ -0,0 +1,523 @@
+package controllers
+
+import (
+ "context"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+
+ "github.com/newrelic/newrelic-client-go/pkg/alerts"
+
+ nrv1 "github.com/newrelic/newrelic-kubernetes-operator/api/v1"
+ "github.com/newrelic/newrelic-kubernetes-operator/interfaces"
+ "github.com/newrelic/newrelic-kubernetes-operator/interfaces/interfacesfakes"
+)
+
+var _ = Describe("AlertsChannel reconciliation", func() {
+ var (
+ ctx context.Context
+ r *AlertsChannelReconciler
+ alertsChannel *nrv1.AlertsChannel
+ request ctrl.Request
+ namespacedName types.NamespacedName
+ err error
+ // secret *v1.Secret
+ fakeAlertFunc func(string, string) (interfaces.NewRelicAlertsClient, error)
+ testPolicy nrv1.AlertsPolicy
+ )
+
+ BeforeEach(func() {
+ err = ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-namespace",
+ },
+ }))
+ Expect(err).ToNot(HaveOccurred())
+
+ ctx = context.Background()
+
+ alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
+
+ alertsClient.CreateChannelStub = func(a alerts.Channel) (*alerts.Channel, error) {
+ a.ID = 543
+ return &a, nil
+ }
+
+ alertsClient.ListChannelsStub = func() ([]*alerts.Channel, error) {
+ return []*alerts.Channel{}, nil
+ }
+
+ alertsClient.ListPoliciesStub = func(*alerts.ListPoliciesParams) ([]alerts.Policy, error) {
+ return []alerts.Policy{
+ {
+ ID: 1122,
+ Name: "my-policy-name",
+ },
+ }, nil
+ }
+
+ fakeAlertFunc = func(string, string) (interfaces.NewRelicAlertsClient, error) {
+ return alertsClient, nil
+ }
+
+ r = &AlertsChannelReconciler{
+ Client: k8sClient,
+ Log: logf.Log,
+ AlertClientFunc: fakeAlertFunc,
+ }
+
+ namespacedName = types.NamespacedName{
+ Namespace: "default",
+ Name: "myalertschannel",
+ }
+
+ alertsChannel = &nrv1.AlertsChannel{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "myalertschannel",
+ Namespace: "default",
+ },
+ Spec: nrv1.AlertsChannelSpec{
+ Name: "my alert channel",
+ APIKey: "api-key",
+ APIKeySecret: nrv1.NewRelicAPIKeySecret{},
+ Region: "US",
+ Type: "email",
+ Links: nrv1.ChannelLinks{
+ PolicyIDs: []int{
+ 1,
+ 2,
+ },
+ PolicyNames: []string{
+ "my-policy-name",
+ },
+ PolicyKubernetesObjects: []metav1.ObjectMeta{
+ {
+ Name: "my-policy",
+ Namespace: "default",
+ },
+ },
+ },
+ Configuration: nrv1.AlertsChannelConfiguration{
+ Recipients: "me@email.com",
+ },
+ },
+
+ Status: nrv1.AlertsChannelStatus{
+ AppliedSpec: &nrv1.AlertsChannelSpec{},
+ ChannelID: 0,
+ AppliedPolicyIDs: []int{},
+ },
+ }
+ namespacedName = types.NamespacedName{
+ Namespace: "default",
+ Name: "myalertschannel",
+ }
+ request = ctrl.Request{NamespacedName: namespacedName}
+
+ testPolicy = nrv1.AlertsPolicy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-policy",
+ Namespace: "default",
+ },
+ Status: nrv1.AlertsPolicyStatus{
+ AppliedSpec: &nrv1.AlertsPolicySpec{},
+ PolicyID: "665544",
+ },
+ }
+ err = ignoreAlreadyExists(k8sClient.Create(ctx, &testPolicy))
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ Context("When starting with no alertsChannels", func() {
+ Context("and given a new alertsChannel", func() {
+ Context("with a valid alertsChannelSpec", func() {
+ BeforeEach(func() {
+ err = k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ It("should create that alertsChannel via the AlertClient", func() {
+ Expect(alertsClient.CreateChannelCallCount()).To(Equal(1))
+ })
+
+ It("updates the ChannelID on the kubernetes object", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.ChannelID).To(Equal(543))
+ })
+
+ It("updates the AppliedSpec on the kubernetes object for later comparison", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.AppliedSpec).To(Equal(&alertsChannel.Spec))
+ })
+
+ It("adds a policy by policy name to the alertsChannel", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.AppliedPolicyIDs).To(ContainElement(1122))
+ })
+
+ It("adds a policy by k8s object reference to the alertsChannel", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.AppliedPolicyIDs).To(ContainElement(665544))
+ })
+ })
+
+ Context("and given the same policy via policyID and policy name", func() {
+ BeforeEach(func() {
+ alertsChannel.Spec.Links = nrv1.ChannelLinks{
+ PolicyIDs: []int{
+ 1122,
+ },
+ PolicyNames: []string{
+ "my-policy-name",
+ },
+ }
+ })
+
+ It("should only create a single link object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(alertsClient.UpdatePolicyChannelsCallCount()).To(Equal(1))
+ })
+ })
+
+ Context("and given a AlertsChannel with k8s policy reference that has no policyID", func() {
+ var existingPolicyID string
+
+ BeforeEach(func() {
+ key := types.NamespacedName{Name: "my-policy",
+ Namespace: "default"}
+ err := k8sClient.Get(ctx, key, &testPolicy)
+ Expect(err).ToNot(HaveOccurred())
+ existingPolicyID = testPolicy.Status.PolicyID
+ testPolicy.Status.PolicyID = ""
+ err = k8sClient.Update(ctx, &testPolicy)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ It("Should fail the reconcile loop", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ _, err = r.Reconcile(request)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring("Retrieved policy " + testPolicy.Name + " but ID was blank"))
+ })
+
+ AfterEach(func() {
+ key := types.NamespacedName{Name: "my-policy",
+ Namespace: "default"}
+ err := k8sClient.Get(ctx, key, &testPolicy)
+ Expect(err).ToNot(HaveOccurred())
+ testPolicy.Status.PolicyID = existingPolicyID
+ err = k8sClient.Update(ctx, &testPolicy)
+ Expect(err).ToNot(HaveOccurred())
+ })
+ })
+ })
+
+ Context("and given as new alertsChannel that exists in New Relic", func() {
+ Context("when the existing Channel is the same as the configuration", func() {
+ BeforeEach(func() {
+ alertsClient.ListChannelsStub = func() ([]*alerts.Channel, error) {
+ return []*alerts.Channel{
+ {
+ ID: 112233,
+ Name: "my alert channel",
+ Type: "email",
+ Configuration: alerts.ChannelConfiguration{
+ Recipients: "me@email.com",
+ },
+ },
+ }, nil
+ }
+ })
+
+ It("Should not create a new AlertsChannel in New Relic", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(alertsClient.ListChannelsCallCount()).To(Equal(1))
+ Expect(alertsClient.CreateChannelCallCount()).To(Equal(0))
+ })
+
+ It("Should update the ChannelId on the kubernetes object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.ChannelID).To(Equal(112233))
+ })
+
+ It("Should update the AppliedSpec on the kubernetes object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.AppliedSpec).To(Equal(&alertsChannel.Spec))
+ })
+ })
+
+ Context("when the existing Channel is different from the configuration", func() {
+ BeforeEach(func() {
+ alertsClient.ListChannelsStub = func() ([]*alerts.Channel, error) {
+ return []*alerts.Channel{
+ {
+ ID: 112233,
+ Name: "my alert channel",
+ Type: "email",
+ Configuration: alerts.ChannelConfiguration{
+ Recipients: "me@stuff.com",
+ },
+ },
+ }, nil
+ }
+ alertsClient.CreateChannelStub = func(a alerts.Channel) (*alerts.Channel, error) {
+ a.ID = 112244
+ return &a, nil
+ }
+
+ })
+
+ It("Should delete and create a new AlertsChannel in New Relic", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(alertsClient.ListChannelsCallCount()).To(Equal(1))
+ Expect(alertsClient.CreateChannelCallCount()).To(Equal(1))
+ Expect(alertsClient.DeleteChannelCallCount()).To(Equal(1))
+ })
+
+ It("Should update the ChannelId on the kubernetes object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.ChannelID).To(Equal(112244))
+ })
+
+ It("Should update the AppliedSpec on the kubernetes object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.AppliedSpec).To(Equal(&alertsChannel.Spec))
+ })
+ })
+
+ Context("when multiple existing Channels are returned from the alerts API", func() {
+ BeforeEach(func() {
+ alertsClient.ListChannelsStub = func() ([]*alerts.Channel, error) {
+ return []*alerts.Channel{
+ {
+ ID: 112233,
+ Name: "my alert channel",
+ Type: "email",
+ Configuration: alerts.ChannelConfiguration{
+ Recipients: "me@stuff.com",
+ },
+ },
+ {
+ ID: 112245,
+ Name: "my alert channel",
+ Type: "email",
+ Configuration: alerts.ChannelConfiguration{
+ Recipients: "me2@stuff.com",
+ },
+ },
+ }, nil
+ }
+ alertsClient.CreateChannelStub = func(a alerts.Channel) (*alerts.Channel, error) {
+ a.ID = 112234
+ return &a, nil
+ }
+ })
+
+ It("Should delete both and create a new AlertsChannel in New Relic", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(alertsClient.ListChannelsCallCount()).To(Equal(1))
+ Expect(alertsClient.CreateChannelCallCount()).To(Equal(1))
+ Expect(alertsClient.DeleteChannelCallCount()).To(Equal(2))
+ })
+
+ It("Should update the ChannelId on the kubernetes object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.ChannelID).To(Equal(112234))
+ })
+
+ It("Should update the AppliedSpec on the kubernetes object", func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err = k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(BeNil())
+ Expect(endStateAlertsChannel.Status.AppliedSpec).To(Equal(&alertsChannel.Spec))
+ })
+ })
+ })
+
+ AfterEach(func() {
+ // Delete the alertsChannel
+ err := k8sClient.Delete(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // Need to call reconcile to delete finalizer
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+ })
+
+ Context("When starting with an existing alertsChannel", func() {
+ BeforeEach(func() {
+ err := k8sClient.Create(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ Context("and deleting that alertsChannel", func() {
+ BeforeEach(func() {
+ err := k8sClient.Delete(ctx, alertsChannel)
+
+ Expect(err).ToNot(HaveOccurred())
+
+ // call reconcile
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ It("Should delete via the NR API", func() {
+ Expect(alertsClient.DeleteChannelCallCount()).To(Equal(1))
+ })
+
+ It("Should delete the k8s object", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err := k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).To(HaveOccurred())
+ Expect(err.Error()).To(ContainSubstring(" \"myalertschannel\" not found"))
+ Expect(endStateAlertsChannel.Name).To(Equal(""))
+ })
+ })
+
+ Context("and updating that alertsChannel", func() {
+ BeforeEach(func() {
+ //Get the object again after creation
+ err := k8sClient.Get(ctx, namespacedName, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ Context("When adding a new policyID to the list of policyIds", func() {
+ BeforeEach(func() {
+ alertsChannel.Spec.Links.PolicyIDs = append(alertsChannel.Spec.Links.PolicyIDs, 4)
+ err := k8sClient.Update(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ It("Should call the NR API", func() {
+ Expect(alertsClient.UpdatePolicyChannelsCallCount()).To(Equal(5)) //4 existing plus 1 new policy
+ policyID, _ := alertsClient.UpdatePolicyChannelsArgsForCall(4)
+ Expect(policyID).To(Equal(4))
+ })
+
+ It("Should update the appliedPolicyIDs", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err := k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(endStateAlertsChannel.Status.AppliedPolicyIDs).To(ContainElement(4))
+ })
+ })
+
+ Context("When removing a policyID to the list of policyIds", func() {
+ BeforeEach(func() {
+ alertsChannel.Spec.Links.PolicyIDs = []int{1}
+ err := k8sClient.Update(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+
+ It("Should call the NR API", func() {
+ Expect(alertsClient.DeletePolicyChannelCallCount()).To(Equal(1))
+ })
+
+ It("Should update the appliedPolicyIDs", func() {
+ var endStateAlertsChannel nrv1.AlertsChannel
+ err := k8sClient.Get(ctx, namespacedName, &endStateAlertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ Expect(endStateAlertsChannel.Status.AppliedPolicyIDs).ToNot(ContainElement(2))
+ })
+ })
+
+ AfterEach(func() {
+ err := k8sClient.Delete(ctx, alertsChannel)
+ Expect(err).ToNot(HaveOccurred())
+ _, err = r.Reconcile(request)
+ Expect(err).ToNot(HaveOccurred())
+ })
+ })
+ })
+})
diff --git a/controllers/apmalertcondition_controller.go b/controllers/apmalertcondition_controller.go
index a9bd06a..7418836 100644
--- a/controllers/apmalertcondition_controller.go
+++ b/controllers/apmalertcondition_controller.go
@@ -181,6 +181,7 @@ func (r *ApmAlertConditionReconciler) Reconcile(req ctrl.Request) (ctrl.Result,
func (r *ApmAlertConditionReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.AlertClientFunc = interfaces.InitializeAlertsClient
+
return ctrl.NewControllerManagedBy(mgr).
For(&nralertsv1.ApmAlertCondition{}).
Complete(r)
@@ -220,14 +221,15 @@ func (r *ApmAlertConditionReconciler) deleteNewRelicAlertCondition(condition nra
)
return err
}
+
return nil
}
func (r *ApmAlertConditionReconciler) getAPIKeyOrSecret(condition nralertsv1.ApmAlertCondition) string {
-
if condition.Spec.APIKey != "" {
return condition.Spec.APIKey
}
+
if condition.Spec.APIKeySecret != (nralertsv1.NewRelicAPIKeySecret{}) {
key := types.NamespacedName{Namespace: condition.Spec.APIKeySecret.Namespace, Name: condition.Spec.APIKeySecret.Name}
var apiKeySecret v1.Secret
@@ -235,7 +237,9 @@ func (r *ApmAlertConditionReconciler) getAPIKeyOrSecret(condition nralertsv1.Apm
r.Log.Error(getErr, "Error retrieving secret", "secret", apiKeySecret)
return ""
}
+
return string(apiKeySecret.Data[condition.Spec.APIKeySecret.KeyName])
}
+
return ""
}
diff --git a/controllers/apmalertcondition_controller_integration_test.go b/controllers/apmalertcondition_controller_integration_test.go
index 204cb48..9039196 100644
--- a/controllers/apmalertcondition_controller_integration_test.go
+++ b/controllers/apmalertcondition_controller_integration_test.go
@@ -23,14 +23,6 @@ import (
)
var _ = Describe("ApmCondition reconciliation", func() {
- BeforeEach(func() {
- err := ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "my-namespace",
- },
- }))
- Expect(err).ToNot(HaveOccurred())
- })
var (
ctx context.Context
r *ApmAlertConditionReconciler
@@ -40,7 +32,15 @@ var _ = Describe("ApmCondition reconciliation", func() {
secret *v1.Secret
fakeAlertFunc func(string, string) (interfaces.NewRelicAlertsClient, error)
)
+
BeforeEach(func() {
+ err := ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-namespace",
+ },
+ }))
+ Expect(err).ToNot(HaveOccurred())
+
ctx = context.Background()
alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
@@ -49,10 +49,12 @@ var _ = Describe("ApmCondition reconciliation", func() {
a.ID = 111
return &a, nil
}
+
alertsClient.UpdateConditionStub = func(a alerts.Condition) (*alerts.Condition, error) {
a.ID = 112
return &a, nil
}
+
alertsClient.ListConditionsStub = func(int) ([]*alerts.Condition, error) {
var a []*alerts.Condition
a = append(a, &alerts.Condition{
@@ -123,11 +125,9 @@ var _ = Describe("ApmCondition reconciliation", func() {
Name: "test-condition",
}
request = ctrl.Request{NamespacedName: namespacedName}
-
})
Context("When starting with no conditions", func() {
-
Context("and given a new ApmAlertCondition", func() {
Context("with a valid condition", func() {
It("should create that condition via the AlertClient", func() {
@@ -193,6 +193,7 @@ var _ = Describe("ApmCondition reconciliation", func() {
}
Expect(ignoreAlreadyExists(k8sClient.Create(ctx, secret))).To(Succeed())
})
+
It("should create that condition via the AlertClient", func() {
err := k8sClient.Create(ctx, condition)
@@ -283,7 +284,6 @@ var _ = Describe("ApmCondition reconciliation", func() {
})
Context("with a valid condition", func() {
-
It("does not create a new condition", func() {
err := k8sClient.Create(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -322,7 +322,6 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Status.AppliedSpec).To(Equal(&condition.Spec))
})
-
})
})
@@ -419,7 +418,6 @@ var _ = Describe("ApmCondition reconciliation", func() {
})
Context("When starting with an existing condition", func() {
-
Context("and deleting a ApmAlertCondition", func() {
BeforeEach(func() {
err := k8sClient.Create(ctx, condition)
@@ -432,8 +430,8 @@ var _ = Describe("ApmCondition reconciliation", func() {
// change the event after creation via reconciliation
err = k8sClient.Get(ctx, namespacedName, condition)
Expect(err).ToNot(HaveOccurred())
-
})
+
Context("with a valid condition", func() {
It("should delete that condition via the AlertClient", func() {
err := k8sClient.Delete(ctx, condition)
@@ -460,16 +458,16 @@ var _ = Describe("ApmCondition reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, &endStateCondition)
Expect(err).To(HaveOccurred())
})
-
})
- Context("with a condition with no condition ID", func() {
+ Context("with a condition with no condition ID", func() {
BeforeEach(func() {
condition.Status.ConditionID = 0
err := k8sClient.Update(ctx, condition)
Expect(err).ToNot(HaveOccurred())
})
+
It("should just remove the finalizer and delete", func() {
err := k8sClient.Delete(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -487,17 +485,16 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
Context("when the Alerts API reports no condition found ", func() {
-
BeforeEach(func() {
alertsClient.DeleteConditionStub = func(int) (*alerts.Condition, error) {
return &alerts.Condition{}, errors.New("resource not found")
}
})
+
It("should just remove the finalizer and delete", func() {
err := k8sClient.Delete(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -515,9 +512,7 @@ var _ = Describe("ApmCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
-
})
})
})
diff --git a/controllers/nrqlalertcondition_controller.go b/controllers/nrqlalertcondition_controller.go
index 5b84068..ddae09d 100644
--- a/controllers/nrqlalertcondition_controller.go
+++ b/controllers/nrqlalertcondition_controller.go
@@ -205,6 +205,7 @@ func (r *NrqlAlertConditionReconciler) checkForExistingCondition(condition *nral
func (r *NrqlAlertConditionReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.AlertClientFunc = interfaces.InitializeAlertsClient
+
return ctrl.NewControllerManagedBy(mgr).
For(&nralertsv1.NrqlAlertCondition{}).
Complete(r)
@@ -216,6 +217,7 @@ func containsString(slice []string, s string) bool {
return true
}
}
+
return false
}
@@ -228,8 +230,10 @@ func (r *NrqlAlertConditionReconciler) deleteNewRelicAlertCondition(condition nr
"region", condition.Spec.Region,
"Api Key", interfaces.PartialAPIKey(r.apiKey),
)
+
return err
}
+
return nil
}
@@ -240,22 +244,26 @@ func removeString(slice []string, s string) (result []string) {
}
result = append(result, item)
}
+
return
}
func (r *NrqlAlertConditionReconciler) getAPIKeyOrSecret(condition nralertsv1.NrqlAlertCondition) string {
-
if condition.Spec.APIKey != "" {
return condition.Spec.APIKey
}
+
if condition.Spec.APIKeySecret != (nralertsv1.NewRelicAPIKeySecret{}) {
key := types.NamespacedName{Namespace: condition.Spec.APIKeySecret.Namespace, Name: condition.Spec.APIKeySecret.Name}
var apiKeySecret v1.Secret
if getErr := r.Client.Get(context.Background(), key, &apiKeySecret); getErr != nil {
r.Log.Error(getErr, "Error retrieving secret", "secret", apiKeySecret)
+
return ""
}
+
return string(apiKeySecret.Data[condition.Spec.APIKeySecret.KeyName])
}
+
return ""
}
diff --git a/controllers/nrqlalertcondition_controller_integration_test.go b/controllers/nrqlalertcondition_controller_integration_test.go
index 4c8a38e..da45466 100644
--- a/controllers/nrqlalertcondition_controller_integration_test.go
+++ b/controllers/nrqlalertcondition_controller_integration_test.go
@@ -22,15 +22,6 @@ import (
)
var _ = Describe("NrqlCondition reconciliation", func() {
- BeforeEach(func() {
- err := ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: "my-namespace",
- },
- }))
- Expect(err).ToNot(HaveOccurred())
- })
-
var (
ctx context.Context
r *NrqlAlertConditionReconciler
@@ -40,7 +31,15 @@ var _ = Describe("NrqlCondition reconciliation", func() {
secret *v1.Secret
fakeAlertFunc func(string, string) (interfaces.NewRelicAlertsClient, error)
)
+
BeforeEach(func() {
+ err := ignoreAlreadyExists(k8sClient.Create(context.Background(), &v1.Namespace{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "my-namespace",
+ },
+ }))
+ Expect(err).ToNot(HaveOccurred())
+
ctx = context.Background()
alertsClient = &interfacesfakes.FakeNewRelicAlertsClient{}
@@ -49,10 +48,12 @@ var _ = Describe("NrqlCondition reconciliation", func() {
a.ID = 111
return &a, nil
}
+
alertsClient.UpdateNrqlConditionStub = func(a alerts.NrqlCondition) (*alerts.NrqlCondition, error) {
a.ID = 112
return &a, nil
}
+
alertsClient.ListNrqlConditionsStub = func(int) ([]*alerts.NrqlCondition, error) {
var a []*alerts.NrqlCondition
a = append(a, &alerts.NrqlCondition{
@@ -118,16 +119,16 @@ var _ = Describe("NrqlCondition reconciliation", func() {
ConditionID: 0,
},
}
+
namespacedName = types.NamespacedName{
Namespace: "default",
Name: "test-condition",
}
- request = ctrl.Request{NamespacedName: namespacedName}
+ request = ctrl.Request{NamespacedName: namespacedName}
})
Context("When starting with no conditions", func() {
-
Context("and given a new NrqlAlertCondition", func() {
Context("with a valid condition", func() {
It("should create that condition via the AlertClient", func() {
@@ -193,6 +194,7 @@ var _ = Describe("NrqlCondition reconciliation", func() {
}
Expect(ignoreAlreadyExists(k8sClient.Create(ctx, secret))).To(Succeed())
})
+
It("should create that condition via the AlertClient", func() {
err := k8sClient.Create(ctx, condition)
@@ -205,9 +207,9 @@ var _ = Describe("NrqlCondition reconciliation", func() {
Expect(alertsClient.CreateNrqlConditionCallCount()).To(Equal(1))
Expect(alertsClient.UpdateNrqlConditionCallCount()).To(Equal(0))
})
+
AfterEach(func() {
//k8sClient.Delete(ctx, secret)
-
})
It("updates the ConditionID on the kubernetes object", func() {
@@ -283,11 +285,9 @@ var _ = Describe("NrqlCondition reconciliation", func() {
ConditionID: 0,
},
}
-
})
Context("with a valid condition", func() {
-
It("does not create a new condition", func() {
err := k8sClient.Create(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -326,7 +326,6 @@ var _ = Describe("NrqlCondition reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Status.AppliedSpec).To(Equal(&condition.Spec))
})
-
})
})
@@ -422,7 +421,6 @@ var _ = Describe("NrqlCondition reconciliation", func() {
})
Context("When starting with an existing condition", func() {
-
Context("and deleting a NrqlAlertCondition", func() {
BeforeEach(func() {
err := k8sClient.Create(ctx, condition)
@@ -437,6 +435,7 @@ var _ = Describe("NrqlCondition reconciliation", func() {
Expect(err).ToNot(HaveOccurred())
})
+
Context("with a valid condition", func() {
It("should delete that condition via the AlertClient", func() {
err := k8sClient.Delete(ctx, condition)
@@ -463,16 +462,16 @@ var _ = Describe("NrqlCondition reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, &endStateCondition)
Expect(err).To(HaveOccurred())
})
-
})
- Context("with a condition with no condition ID", func() {
+ Context("with a condition with no condition ID", func() {
BeforeEach(func() {
condition.Status.ConditionID = 0
err := k8sClient.Update(ctx, condition)
Expect(err).ToNot(HaveOccurred())
})
+
It("should just remove the finalizer and delete", func() {
err := k8sClient.Delete(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -490,17 +489,15 @@ var _ = Describe("NrqlCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
Context("when the Alerts API reports no condition found ", func() {
-
BeforeEach(func() {
alertsClient.DeleteNrqlConditionStub = func(int) (*alerts.NrqlCondition, error) {
return &alerts.NrqlCondition{}, errors.New("resource not found")
}
-
})
+
It("should just remove the finalizer and delete", func() {
err := k8sClient.Delete(ctx, condition)
Expect(err).ToNot(HaveOccurred())
@@ -518,10 +515,7 @@ var _ = Describe("NrqlCondition reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Name).To(Equal(""))
})
-
})
-
})
})
-
})
diff --git a/controllers/policy_controller.go b/controllers/policy_controller.go
index a2780ed..078cbdb 100644
--- a/controllers/policy_controller.go
+++ b/controllers/policy_controller.go
@@ -125,6 +125,7 @@ func (r *PolicyReconciler) createPolicy(policy *nrv1.Policy) error {
"region", policy.Spec.Region,
"Api Key", interfaces.PartialAPIKey(r.apiKey),
)
+
return err
}
policy.Status.PolicyID = createdPolicy.ID
@@ -132,6 +133,7 @@ func (r *PolicyReconciler) createPolicy(policy *nrv1.Policy) error {
errConditions := r.createConditions(policy)
if errConditions != nil {
r.Log.Error(errConditions, "error creating or updating conditions")
+
return errConditions
}
r.Log.Info("policy after condition creation", "policyCondition", policy.Spec.Conditions, "pointer", &policy)
@@ -141,13 +143,14 @@ func (r *PolicyReconciler) createPolicy(policy *nrv1.Policy) error {
err = r.Client.Update(r.ctx, policy)
if err != nil {
r.Log.Error(err, "tried updating policy status", "name", policy.Name)
+
return err
}
+
return nil
}
func (r *PolicyReconciler) createConditions(policy *nrv1.Policy) error {
-
r.Log.Info("initial policy creation so create all policies")
collectedErrors := new(customErrors.ErrorCollector)
for i, condition := range policy.Spec.Conditions {
@@ -166,12 +169,13 @@ func (r *PolicyReconciler) createConditions(policy *nrv1.Policy) error {
} else {
policy.Spec.Conditions[i] = condition
}
-
}
+
if len(*collectedErrors) > 0 {
r.Log.Info("errors encountered creating conditions", "collectoredErrors", collectedErrors)
return collectedErrors
}
+
return nil
}
@@ -234,6 +238,7 @@ func (r *PolicyReconciler) updateNrqlCondition(policy *nrv1.Policy, condition *n
if retrievedPolicyCondition.SpecHash() == condition.SpecHash() {
r.Log.Info("existing NrqlCondition matches going to next")
+
return nil
}
@@ -246,8 +251,7 @@ func (r *PolicyReconciler) updateNrqlCondition(policy *nrv1.Policy, condition *n
nrqlAlertCondition.Spec.APIKey = policy.Spec.APIKey
nrqlAlertCondition.Spec.APIKeySecret = policy.Spec.APIKeySecret
- err := r.Client.Update(r.ctx, &nrqlAlertCondition)
- return err
+ return r.Client.Update(r.ctx, &nrqlAlertCondition)
}
func (r *PolicyReconciler) updateApmCondition(policy *nrv1.Policy, condition *nrv1.PolicyCondition) error {
@@ -276,8 +280,7 @@ func (r *PolicyReconciler) updateApmCondition(policy *nrv1.Policy, condition *nr
r.Log.Info("updating existing condition", "apmAlertCondition", apmAlertCondition)
- err := r.Client.Update(r.ctx, &apmAlertCondition)
- return err
+ return r.Client.Update(r.ctx, &apmAlertCondition)
}
func (r *PolicyReconciler) createOrUpdateConditions(policy *nrv1.Policy) error {
@@ -415,8 +418,10 @@ func (r *PolicyReconciler) deleteCondition(condition *nrv1.PolicyCondition) erro
err := r.Delete(r.ctx, retrievedCondition)
if err != nil {
r.Log.Error(err, "error deleting condition resource")
+
return err
}
+
return nil
}
@@ -425,6 +430,7 @@ func (r *PolicyReconciler) getNrqlConditionFromPolicyCondition(condition *nrv1.P
//throw away the error since empty conditions are expected
_ = r.Client.Get(r.ctx, condition.GetNamespace(), &nrqlAlertCondition)
r.Log.Info("retrieved condition", "nrqlAlertCondition", nrqlAlertCondition, "namespace", condition.GetNamespace())
+
return
}
@@ -433,6 +439,7 @@ func (r *PolicyReconciler) getApmConditionFromPolicyCondition(condition *nrv1.Po
//throw away the error since empty conditions are expected
_ = r.Client.Get(r.ctx, condition.GetNamespace(), &apmAlertCondition)
r.Log.Info("retrieved condition", "apmAlertCondition", apmAlertCondition, "namespace", condition.GetNamespace())
+
return
}
@@ -457,6 +464,7 @@ func (r *PolicyReconciler) updatePolicy(policy *nrv1.Policy) error {
"region", policy.Spec.Region,
"Api Key", interfaces.PartialAPIKey(r.apiKey),
)
+
return err
}
policy.Status.PolicyID = updatedPolicy.ID
@@ -465,6 +473,7 @@ func (r *PolicyReconciler) updatePolicy(policy *nrv1.Policy) error {
errConditions := r.createOrUpdateConditions(policy)
if errConditions != nil {
r.Log.Error(errConditions, "error creating or updating conditions")
+
return errConditions
}
r.Log.Info("policySpecx before update", "policy.Spec", policy.Spec)
@@ -474,8 +483,10 @@ func (r *PolicyReconciler) updatePolicy(policy *nrv1.Policy) error {
err = r.Client.Update(r.ctx, policy)
if err != nil {
r.Log.Error(err, "failed to update policy status", "name", policy.Name)
+
return err
}
+
return nil
}
@@ -516,6 +527,7 @@ func (r *PolicyReconciler) deletePolicy(ctx context.Context, policy *nrv1.Policy
policy.Finalizers = removeString(policy.Finalizers, deleteFinalizer)
if err := r.Client.Update(ctx, policy); err != nil {
r.Log.Error(err, "Failed to update k8s records for this policy after successfully deleting the policy via New Relic Alert API")
+
return ctrl.Result{}, err
}
}
@@ -568,16 +580,18 @@ func (r *PolicyReconciler) deleteNewRelicAlertPolicy(policy *nrv1.Policy) error
"region", policy.Spec.Region,
"Api Key", interfaces.PartialAPIKey(r.apiKey),
)
+
return err
}
+
return nil
}
func (r *PolicyReconciler) getAPIKeyOrSecret(policy nrv1.Policy) string {
-
if policy.Spec.APIKey != "" {
return policy.Spec.APIKey
}
+
if policy.Spec.APIKeySecret != (nrv1.NewRelicAPIKeySecret{}) {
key := types.NamespacedName{Namespace: policy.Spec.APIKeySecret.Namespace, Name: policy.Spec.APIKeySecret.Name}
var apiKeySecret v1.Secret
@@ -586,7 +600,9 @@ func (r *PolicyReconciler) getAPIKeyOrSecret(policy nrv1.Policy) string {
r.Log.Error(getErr, "Failed to retrieve secret", "secret", apiKeySecret)
return ""
}
+
return string(apiKeySecret.Data[policy.Spec.APIKeySecret.KeyName])
}
+
return ""
}
diff --git a/controllers/policy_controller_integration_api_test.go b/controllers/policy_controller_integration_api_test.go
index d3fff11..ed4ea30 100644
--- a/controllers/policy_controller_integration_api_test.go
+++ b/controllers/policy_controller_integration_api_test.go
@@ -37,6 +37,7 @@ func newIntegrationTestClient(t *testing.T) newrelic.NewRelic {
}
client, _ := interfaces.NewClient(envAPIKey, envRegion)
+
return *client
}
diff --git a/controllers/policy_controller_integration_test.go b/controllers/policy_controller_integration_test.go
index d25cb1b..9ce8aa6 100644
--- a/controllers/policy_controller_integration_test.go
+++ b/controllers/policy_controller_integration_test.go
@@ -65,8 +65,8 @@ var _ = Describe("policy reconciliation", func() {
Namespace: "default",
Name: "test-policy",
}
- request = ctrl.Request{NamespacedName: namespacedName}
+ request = ctrl.Request{NamespacedName: namespacedName}
})
Context("When starting with no policies", func() {
@@ -151,23 +151,21 @@ var _ = Describe("policy reconciliation", func() {
Namespace: "default",
Name: "test-policy",
}
- request = ctrl.Request{NamespacedName: namespacedName}
+ request = ctrl.Request{NamespacedName: namespacedName}
})
+
Context("when creating a valid policy", func() {
It("should create that policy", func() {
-
err := k8sClient.Create(ctx, policy)
Expect(err).ToNot(HaveOccurred())
// call reconcile
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
-
})
It("updates the policyId on the Policy resource", func() {
-
err := k8sClient.Create(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -179,10 +177,9 @@ var _ = Describe("policy reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, &endStatePolicy)
Expect(err).To(BeNil())
Expect(endStatePolicy.Status.PolicyID).To(Equal(333))
-
})
- It("creates the NRQL condition with attributes from the Policy", func() {
+ It("creates the NRQL condition with attributes from the Policy", func() {
err := k8sClient.Create(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -203,11 +200,9 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Spec.Nrql.Query).To(Equal("SELECT 1 FROM MyEvents"))
Expect(endStateCondition.Spec.Terms[0].Priority).To(Equal("critical"))
Expect(endStateCondition.Spec.Enabled).To(BeTrue())
-
})
It("creates the NRQL condition with inherited attributes from the Policy resource", func() {
-
err := k8sClient.Create(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -229,9 +224,9 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Spec.ExistingPolicyID).To(Equal(333))
Expect(endStateCondition.Spec.Region).To(Equal(policy.Spec.Region))
Expect(endStateCondition.Spec.APIKey).To(Equal(policy.Spec.APIKey))
-
})
})
+
Context("when the New Relic API returns an error", func() {
BeforeEach(func() {
alertsClient.CreatePolicyStub = func(alerts.Policy) (*alerts.Policy, error) {
@@ -240,7 +235,6 @@ var _ = Describe("policy reconciliation", func() {
})
It("should not update the PolicyID", func() {
-
createErr := k8sClient.Create(ctx, policy)
Expect(createErr).ToNot(HaveOccurred())
@@ -253,14 +247,12 @@ var _ = Describe("policy reconciliation", func() {
getErr := k8sClient.Get(ctx, namespacedName, &endStatePolicy)
Expect(getErr).ToNot(HaveOccurred())
Expect(endStatePolicy.Status.PolicyID).To(Equal(0))
-
})
})
Context("when creating a valid policy with apm conditions", func() {
It("should create the conditions", func() {
conditionSpec = &nrv1.ConditionSpec{
-
GenericConditionSpec: nrv1.GenericConditionSpec{
Terms: []nrv1.AlertConditionTerm{
{
@@ -286,6 +278,7 @@ var _ = Describe("policy reconciliation", func() {
ViolationCloseTimer: 60,
},
}
+
policy.Spec.Conditions[0] = nrv1.PolicyCondition{
Spec: *conditionSpec,
}
@@ -310,12 +303,10 @@ var _ = Describe("policy reconciliation", func() {
err = k8sClient.Get(ctx, conditionNameType, &endStateCondition)
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Name).To(Equal("APM Condition"))
-
})
Context("when creating a valid policy with conditions with k8 resource name set", func() {
It("should create the conditions with an auto-generated name ignoring the manual name", func() {
-
policy.Spec.Conditions[0].Name = "my custom name"
err := k8sClient.Create(ctx, policy)
@@ -341,7 +332,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Name).ToNot(Equal("my custom name"))
})
})
-
})
AfterEach(func() {
@@ -353,12 +343,10 @@ var _ = Describe("policy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
Context("When starting with an existing policy with a NRQL condition", func() {
BeforeEach(func() {
-
conditionSpec = &nrv1.ConditionSpec{
nrv1.GenericConditionSpec{
Terms: []nrv1.AlertConditionTerm{
@@ -423,6 +411,7 @@ var _ = Describe("policy reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, policy)
Expect(err).ToNot(HaveOccurred())
})
+
Context("and deleting that policy", func() {
It("should successfully delete", func() {
err := k8sClient.Delete(ctx, policy)
@@ -435,8 +424,8 @@ var _ = Describe("policy reconciliation", func() {
var endStatePolicy nrv1.Policy
err = k8sClient.Get(ctx, namespacedName, &endStatePolicy)
Expect(err).NotTo(BeNil())
-
})
+
It("should delete the condition", func() {
err := k8sClient.Delete(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -450,17 +439,19 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).To(HaveOccurred())
Expect(endStateCondition.Spec.Name).ToNot(Equal(policy.Spec.Conditions[0].Spec.Name))
-
})
})
+
Context("and New Relic API returns a 404", func() {
BeforeEach(func() {
alertsClient.DeletePolicyStub = func(int) (*alerts.Policy, error) {
return &alerts.Policy{}, errors.New("Imaginary 404 Failure")
}
})
+
It("should succeed as if a previous reconcile already deleted the policy", func() {
})
+
AfterEach(func() {
alertsClient.DeletePolicyStub = func(int) (*alerts.Policy, error) {
return &alerts.Policy{}, nil
@@ -544,7 +535,6 @@ var _ = Describe("policy reconciliation", func() {
})
Context("and making no changes ", func() {
-
It("should not try to update or create new conditions", func() {
initialConditionName := policy.Spec.Conditions[0].Name
policy.Spec.Conditions[0].Name = ""
@@ -573,7 +563,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Name).To(Equal(initialConditionName))
})
-
})
Context("and updating that policy", func() {
@@ -593,9 +582,7 @@ var _ = Describe("policy reconciliation", func() {
err = k8sClient.Get(ctx, namespacedName, &endStatePolicy)
Expect(err).To(BeNil())
Expect(alertsClient.UpdatePolicyCallCount()).To(Equal(1))
-
})
-
})
Context("and updating a condition name", func() {
@@ -653,7 +640,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).ToNot(BeNil())
Expect(originalCondition.Spec.Name).To(Equal(""))
})
-
})
Context("and updating a condition ", func() {
@@ -686,7 +672,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Nrql.Query).To(Equal("SELECT count(*) FROM MyEvent"))
Expect(endStateCondition.Name).To(Equal(originalConditionName))
-
})
It("should set the inherited values on the updated condition", func() {
@@ -713,9 +698,7 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Name).To(Equal(originalConditionName))
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
-
})
-
})
Context("and adding another condition ", func() {
@@ -777,7 +760,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
})
-
})
Context("and when the alerts client returns an error", func() {
@@ -787,6 +769,7 @@ var _ = Describe("policy reconciliation", func() {
}
policy.Spec.IncidentPreference = "PER_CONDITION_AND_TARGET"
})
+
It("should return an error", func() {
err := k8sClient.Update(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -805,7 +788,6 @@ var _ = Describe("policy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
Context("When starting with an existing policy with an APM condition", func() {
@@ -875,7 +857,6 @@ var _ = Describe("policy reconciliation", func() {
})
Context("and making no changes ", func() {
-
It("should not try to update or create new conditions", func() {
initialConditionName := policy.Spec.Conditions[0].Name
policy.Spec.Conditions[0].Name = ""
@@ -905,7 +886,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Name).To(Equal(initialConditionName))
Expect(endStateCondition.Spec.Name).To(Equal("APM Condition"))
})
-
})
Context("and updating that policy", func() {
@@ -927,7 +907,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(alertsClient.UpdatePolicyCallCount()).To(Equal(1))
})
-
})
Context("and updating a condition name", func() {
@@ -985,7 +964,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).ToNot(BeNil())
Expect(originalCondition.Spec.Name).To(Equal(""))
})
-
})
Context("and updating a condition ", func() {
@@ -1018,7 +996,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Metric).To(Equal("Custom/bar"))
Expect(endStateCondition.Name).To(Equal(originalConditionName))
-
})
It("should set the inherited values on the updated condition", func() {
@@ -1044,15 +1021,12 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Name).To(Equal(originalConditionName))
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
-
})
-
})
Context("and adding another apm condition ", func() {
BeforeEach(func() {
secondConditionSpec := nrv1.ConditionSpec{
-
GenericConditionSpec: nrv1.GenericConditionSpec{
Terms: []nrv1.AlertConditionTerm{
{
@@ -1078,6 +1052,7 @@ var _ = Describe("policy reconciliation", func() {
ViolationCloseTimer: 60,
},
}
+
secondCondition := nrv1.PolicyCondition{
Spec: secondConditionSpec,
}
@@ -1107,7 +1082,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(endStateCondition.Spec.Region).To(Equal("us"))
Expect(endStateCondition.Spec.APIKey).To(Equal("112233"))
})
-
})
Context("and when the alerts client returns an error", func() {
@@ -1117,6 +1091,7 @@ var _ = Describe("policy reconciliation", func() {
}
policy.Spec.IncidentPreference = "PER_CONDITION_AND_TARGET"
})
+
It("should return an error", func() {
err := k8sClient.Update(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -1135,7 +1110,6 @@ var _ = Describe("policy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
Context("When starting with an existing policy with two NRQL conditions", func() {
@@ -1246,9 +1220,7 @@ var _ = Describe("policy reconciliation", func() {
Context("and removing the second condition ", func() {
BeforeEach(func() {
-
policy.Spec.Conditions = []nrv1.PolicyCondition{policy.Spec.Conditions[0]}
-
})
It("should remove second condition ", func() {
@@ -1279,7 +1251,6 @@ var _ = Describe("policy reconciliation", func() {
err = k8sClient.Get(ctx, deletedConditionNamespace, &deletedCondition)
Expect(err).ToNot(BeNil())
Expect(deletedCondition.Name).To(Equal(""))
-
})
It("should not call the alerts API ", func() {
@@ -1291,7 +1262,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).ToNot(HaveOccurred())
Expect(alertsClient.UpdatePolicyCallCount()).To(Equal(0))
})
-
})
Context("and removing the first condition ", func() {
@@ -1320,7 +1290,6 @@ var _ = Describe("policy reconciliation", func() {
Expect(err).To(BeNil())
Expect(endStateCondition.Spec.Name).To(Equal("second alert condition"))
})
-
})
Context("and when the alerts client returns an error", func() {
@@ -1330,6 +1299,7 @@ var _ = Describe("policy reconciliation", func() {
}
policy.Spec.IncidentPreference = "PER_CONDITION_AND_TARGET"
})
+
It("should return an error", func() {
err := k8sClient.Update(ctx, policy)
Expect(err).ToNot(HaveOccurred())
@@ -1348,7 +1318,5 @@ var _ = Describe("policy reconciliation", func() {
_, err = r.Reconcile(request)
Expect(err).ToNot(HaveOccurred())
})
-
})
-
})
diff --git a/controllers/suite_integration_test.go b/controllers/suite_integration_test.go
index 842e178..e6fd6fe 100644
--- a/controllers/suite_integration_test.go
+++ b/controllers/suite_integration_test.go
@@ -1,5 +1,3 @@
-// +build integration
-
/*
Licensed under the Apache License, Version 2.0 (the "License");
@@ -90,5 +88,6 @@ func ignoreAlreadyExists(err error) error {
if apierrors.IsAlreadyExists(err) {
return nil
}
+
return err
}
diff --git a/examples/example_alerts_channel.yaml b/examples/example_alerts_channel.yaml
new file mode 100644
index 0000000..3437d8e
--- /dev/null
+++ b/examples/example_alerts_channel.yaml
@@ -0,0 +1,24 @@
+apiVersion: nr.k8s.newrelic.com/v1
+kind: AlertsChannel
+metadata:
+ name: my-channel1
+spec:
+ api_key:
+ # api_key_secret:
+ # name: nr-api-key
+ # namespace: default
+ # key_name: api-key
+ name: "my alert channel"
+ region: "US"
+ type: "email"
+ links:
+ # Policy links can be by NR PolicyID, NR PolicyName OR K8s AlertPolicy object reference
+ policy_ids:
+ - 1
+ policy_names:
+ - "k8s created policy"
+ policy_kubernetes_objects:
+ - name: "my-policy"
+ namespace: "default"
+ configuration:
+ recipients: "me@email.com"
diff --git a/go.sum b/go.sum
index 0ddbaa3..dcebe75 100644
--- a/go.sum
+++ b/go.sum
@@ -551,6 +551,7 @@ github.com/newrelic/newrelic-client-go v0.28.0 h1:+9eGU+QSA48ebmtJdOOWk8UcrXqE5e
github.com/newrelic/newrelic-client-go v0.28.0/go.mod h1:6ulrfoZ6BqLGB4AMq0vutrXLmjo6SOYen4oNwatorEY=
github.com/newrelic/newrelic-client-go v0.28.1 h1:kr1M8XGLjb4exzbl1NIa67Q4LAYalr/EWeKKd2wpbw8=
github.com/newrelic/newrelic-client-go v0.28.1/go.mod h1:6ulrfoZ6BqLGB4AMq0vutrXLmjo6SOYen4oNwatorEY=
+github.com/newrelic/newrelic-client-go v0.29.0 h1:iF5RHhoyB4pqh1IbIs/nlpc79gT71apyZfe4RwjS4Nk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
diff --git a/interfaces/interfacesfakes/fake_new_relic_alerts_client.go b/interfaces/interfacesfakes/fake_new_relic_alerts_client.go
index fcdebd1..1dd3711 100644
--- a/interfaces/interfacesfakes/fake_new_relic_alerts_client.go
+++ b/interfaces/interfacesfakes/fake_new_relic_alerts_client.go
@@ -10,6 +10,19 @@ import (
)
type FakeNewRelicAlertsClient struct {
+ CreateChannelStub func(alerts.Channel) (*alerts.Channel, error)
+ createChannelMutex sync.RWMutex
+ createChannelArgsForCall []struct {
+ arg1 alerts.Channel
+ }
+ createChannelReturns struct {
+ result1 *alerts.Channel
+ result2 error
+ }
+ createChannelReturnsOnCall map[int]struct {
+ result1 *alerts.Channel
+ result2 error
+ }
CreateConditionStub func(int, alerts.Condition) (*alerts.Condition, error)
createConditionMutex sync.RWMutex
createConditionArgsForCall []struct {
@@ -80,6 +93,19 @@ type FakeNewRelicAlertsClient struct {
result1 *alerts.AlertsPolicy
result2 error
}
+ DeleteChannelStub func(int) (*alerts.Channel, error)
+ deleteChannelMutex sync.RWMutex
+ deleteChannelArgsForCall []struct {
+ arg1 int
+ }
+ deleteChannelReturns struct {
+ result1 *alerts.Channel
+ result2 error
+ }
+ deleteChannelReturnsOnCall map[int]struct {
+ result1 *alerts.Channel
+ result2 error
+ }
DeleteConditionStub func(int) (*alerts.Condition, error)
deleteConditionMutex sync.RWMutex
deleteConditionArgsForCall []struct {
@@ -133,6 +159,20 @@ type FakeNewRelicAlertsClient struct {
result1 *alerts.Policy
result2 error
}
+ DeletePolicyChannelStub func(int, int) (*alerts.Channel, error)
+ deletePolicyChannelMutex sync.RWMutex
+ deletePolicyChannelArgsForCall []struct {
+ arg1 int
+ arg2 int
+ }
+ deletePolicyChannelReturns struct {
+ result1 *alerts.Channel
+ result2 error
+ }
+ deletePolicyChannelReturnsOnCall map[int]struct {
+ result1 *alerts.Channel
+ result2 error
+ }
DeletePolicyMutationStub func(int, string) (*alerts.AlertsPolicy, error)
deletePolicyMutationMutex sync.RWMutex
deletePolicyMutationArgsForCall []struct {
@@ -174,6 +214,18 @@ type FakeNewRelicAlertsClient struct {
result1 *alerts.Policy
result2 error
}
+ ListChannelsStub func() ([]*alerts.Channel, error)
+ listChannelsMutex sync.RWMutex
+ listChannelsArgsForCall []struct {
+ }
+ listChannelsReturns struct {
+ result1 []*alerts.Channel
+ result2 error
+ }
+ listChannelsReturnsOnCall map[int]struct {
+ result1 []*alerts.Channel
+ result2 error
+ }
ListConditionsStub func(int) ([]*alerts.Condition, error)
listConditionsMutex sync.RWMutex
listConditionsArgsForCall []struct {
@@ -309,6 +361,20 @@ type FakeNewRelicAlertsClient struct {
result1 *alerts.Policy
result2 error
}
+ UpdatePolicyChannelsStub func(int, []int) (*alerts.PolicyChannels, error)
+ updatePolicyChannelsMutex sync.RWMutex
+ updatePolicyChannelsArgsForCall []struct {
+ arg1 int
+ arg2 []int
+ }
+ updatePolicyChannelsReturns struct {
+ result1 *alerts.PolicyChannels
+ result2 error
+ }
+ updatePolicyChannelsReturnsOnCall map[int]struct {
+ result1 *alerts.PolicyChannels
+ result2 error
+ }
UpdatePolicyMutationStub func(int, string, alerts.AlertsPolicyUpdateInput) (*alerts.AlertsPolicy, error)
updatePolicyMutationMutex sync.RWMutex
updatePolicyMutationArgsForCall []struct {
@@ -328,6 +394,69 @@ type FakeNewRelicAlertsClient struct {
invocationsMutex sync.RWMutex
}
+func (fake *FakeNewRelicAlertsClient) CreateChannel(arg1 alerts.Channel) (*alerts.Channel, error) {
+ fake.createChannelMutex.Lock()
+ ret, specificReturn := fake.createChannelReturnsOnCall[len(fake.createChannelArgsForCall)]
+ fake.createChannelArgsForCall = append(fake.createChannelArgsForCall, struct {
+ arg1 alerts.Channel
+ }{arg1})
+ fake.recordInvocation("CreateChannel", []interface{}{arg1})
+ fake.createChannelMutex.Unlock()
+ if fake.CreateChannelStub != nil {
+ return fake.CreateChannelStub(arg1)
+ }
+ if specificReturn {
+ return ret.result1, ret.result2
+ }
+ fakeReturns := fake.createChannelReturns
+ return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeNewRelicAlertsClient) CreateChannelCallCount() int {
+ fake.createChannelMutex.RLock()
+ defer fake.createChannelMutex.RUnlock()
+ return len(fake.createChannelArgsForCall)
+}
+
+func (fake *FakeNewRelicAlertsClient) CreateChannelCalls(stub func(alerts.Channel) (*alerts.Channel, error)) {
+ fake.createChannelMutex.Lock()
+ defer fake.createChannelMutex.Unlock()
+ fake.CreateChannelStub = stub
+}
+
+func (fake *FakeNewRelicAlertsClient) CreateChannelArgsForCall(i int) alerts.Channel {
+ fake.createChannelMutex.RLock()
+ defer fake.createChannelMutex.RUnlock()
+ argsForCall := fake.createChannelArgsForCall[i]
+ return argsForCall.arg1
+}
+
+func (fake *FakeNewRelicAlertsClient) CreateChannelReturns(result1 *alerts.Channel, result2 error) {
+ fake.createChannelMutex.Lock()
+ defer fake.createChannelMutex.Unlock()
+ fake.CreateChannelStub = nil
+ fake.createChannelReturns = struct {
+ result1 *alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
+func (fake *FakeNewRelicAlertsClient) CreateChannelReturnsOnCall(i int, result1 *alerts.Channel, result2 error) {
+ fake.createChannelMutex.Lock()
+ defer fake.createChannelMutex.Unlock()
+ fake.CreateChannelStub = nil
+ if fake.createChannelReturnsOnCall == nil {
+ fake.createChannelReturnsOnCall = make(map[int]struct {
+ result1 *alerts.Channel
+ result2 error
+ })
+ }
+ fake.createChannelReturnsOnCall[i] = struct {
+ result1 *alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
func (fake *FakeNewRelicAlertsClient) CreateCondition(arg1 int, arg2 alerts.Condition) (*alerts.Condition, error) {
fake.createConditionMutex.Lock()
ret, specificReturn := fake.createConditionReturnsOnCall[len(fake.createConditionArgsForCall)]
@@ -648,6 +777,69 @@ func (fake *FakeNewRelicAlertsClient) CreatePolicyMutationReturnsOnCall(i int, r
}{result1, result2}
}
+func (fake *FakeNewRelicAlertsClient) DeleteChannel(arg1 int) (*alerts.Channel, error) {
+ fake.deleteChannelMutex.Lock()
+ ret, specificReturn := fake.deleteChannelReturnsOnCall[len(fake.deleteChannelArgsForCall)]
+ fake.deleteChannelArgsForCall = append(fake.deleteChannelArgsForCall, struct {
+ arg1 int
+ }{arg1})
+ fake.recordInvocation("DeleteChannel", []interface{}{arg1})
+ fake.deleteChannelMutex.Unlock()
+ if fake.DeleteChannelStub != nil {
+ return fake.DeleteChannelStub(arg1)
+ }
+ if specificReturn {
+ return ret.result1, ret.result2
+ }
+ fakeReturns := fake.deleteChannelReturns
+ return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeNewRelicAlertsClient) DeleteChannelCallCount() int {
+ fake.deleteChannelMutex.RLock()
+ defer fake.deleteChannelMutex.RUnlock()
+ return len(fake.deleteChannelArgsForCall)
+}
+
+func (fake *FakeNewRelicAlertsClient) DeleteChannelCalls(stub func(int) (*alerts.Channel, error)) {
+ fake.deleteChannelMutex.Lock()
+ defer fake.deleteChannelMutex.Unlock()
+ fake.DeleteChannelStub = stub
+}
+
+func (fake *FakeNewRelicAlertsClient) DeleteChannelArgsForCall(i int) int {
+ fake.deleteChannelMutex.RLock()
+ defer fake.deleteChannelMutex.RUnlock()
+ argsForCall := fake.deleteChannelArgsForCall[i]
+ return argsForCall.arg1
+}
+
+func (fake *FakeNewRelicAlertsClient) DeleteChannelReturns(result1 *alerts.Channel, result2 error) {
+ fake.deleteChannelMutex.Lock()
+ defer fake.deleteChannelMutex.Unlock()
+ fake.DeleteChannelStub = nil
+ fake.deleteChannelReturns = struct {
+ result1 *alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
+func (fake *FakeNewRelicAlertsClient) DeleteChannelReturnsOnCall(i int, result1 *alerts.Channel, result2 error) {
+ fake.deleteChannelMutex.Lock()
+ defer fake.deleteChannelMutex.Unlock()
+ fake.DeleteChannelStub = nil
+ if fake.deleteChannelReturnsOnCall == nil {
+ fake.deleteChannelReturnsOnCall = make(map[int]struct {
+ result1 *alerts.Channel
+ result2 error
+ })
+ }
+ fake.deleteChannelReturnsOnCall[i] = struct {
+ result1 *alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
func (fake *FakeNewRelicAlertsClient) DeleteCondition(arg1 int) (*alerts.Condition, error) {
fake.deleteConditionMutex.Lock()
ret, specificReturn := fake.deleteConditionReturnsOnCall[len(fake.deleteConditionArgsForCall)]
@@ -901,6 +1093,70 @@ func (fake *FakeNewRelicAlertsClient) DeletePolicyReturnsOnCall(i int, result1 *
}{result1, result2}
}
+func (fake *FakeNewRelicAlertsClient) DeletePolicyChannel(arg1 int, arg2 int) (*alerts.Channel, error) {
+ fake.deletePolicyChannelMutex.Lock()
+ ret, specificReturn := fake.deletePolicyChannelReturnsOnCall[len(fake.deletePolicyChannelArgsForCall)]
+ fake.deletePolicyChannelArgsForCall = append(fake.deletePolicyChannelArgsForCall, struct {
+ arg1 int
+ arg2 int
+ }{arg1, arg2})
+ fake.recordInvocation("DeletePolicyChannel", []interface{}{arg1, arg2})
+ fake.deletePolicyChannelMutex.Unlock()
+ if fake.DeletePolicyChannelStub != nil {
+ return fake.DeletePolicyChannelStub(arg1, arg2)
+ }
+ if specificReturn {
+ return ret.result1, ret.result2
+ }
+ fakeReturns := fake.deletePolicyChannelReturns
+ return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeNewRelicAlertsClient) DeletePolicyChannelCallCount() int {
+ fake.deletePolicyChannelMutex.RLock()
+ defer fake.deletePolicyChannelMutex.RUnlock()
+ return len(fake.deletePolicyChannelArgsForCall)
+}
+
+func (fake *FakeNewRelicAlertsClient) DeletePolicyChannelCalls(stub func(int, int) (*alerts.Channel, error)) {
+ fake.deletePolicyChannelMutex.Lock()
+ defer fake.deletePolicyChannelMutex.Unlock()
+ fake.DeletePolicyChannelStub = stub
+}
+
+func (fake *FakeNewRelicAlertsClient) DeletePolicyChannelArgsForCall(i int) (int, int) {
+ fake.deletePolicyChannelMutex.RLock()
+ defer fake.deletePolicyChannelMutex.RUnlock()
+ argsForCall := fake.deletePolicyChannelArgsForCall[i]
+ return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeNewRelicAlertsClient) DeletePolicyChannelReturns(result1 *alerts.Channel, result2 error) {
+ fake.deletePolicyChannelMutex.Lock()
+ defer fake.deletePolicyChannelMutex.Unlock()
+ fake.DeletePolicyChannelStub = nil
+ fake.deletePolicyChannelReturns = struct {
+ result1 *alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
+func (fake *FakeNewRelicAlertsClient) DeletePolicyChannelReturnsOnCall(i int, result1 *alerts.Channel, result2 error) {
+ fake.deletePolicyChannelMutex.Lock()
+ defer fake.deletePolicyChannelMutex.Unlock()
+ fake.DeletePolicyChannelStub = nil
+ if fake.deletePolicyChannelReturnsOnCall == nil {
+ fake.deletePolicyChannelReturnsOnCall = make(map[int]struct {
+ result1 *alerts.Channel
+ result2 error
+ })
+ }
+ fake.deletePolicyChannelReturnsOnCall[i] = struct {
+ result1 *alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
func (fake *FakeNewRelicAlertsClient) DeletePolicyMutation(arg1 int, arg2 string) (*alerts.AlertsPolicy, error) {
fake.deletePolicyMutationMutex.Lock()
ret, specificReturn := fake.deletePolicyMutationReturnsOnCall[len(fake.deletePolicyMutationArgsForCall)]
@@ -1092,6 +1348,61 @@ func (fake *FakeNewRelicAlertsClient) GetPolicyReturnsOnCall(i int, result1 *ale
}{result1, result2}
}
+func (fake *FakeNewRelicAlertsClient) ListChannels() ([]*alerts.Channel, error) {
+ fake.listChannelsMutex.Lock()
+ ret, specificReturn := fake.listChannelsReturnsOnCall[len(fake.listChannelsArgsForCall)]
+ fake.listChannelsArgsForCall = append(fake.listChannelsArgsForCall, struct {
+ }{})
+ fake.recordInvocation("ListChannels", []interface{}{})
+ fake.listChannelsMutex.Unlock()
+ if fake.ListChannelsStub != nil {
+ return fake.ListChannelsStub()
+ }
+ if specificReturn {
+ return ret.result1, ret.result2
+ }
+ fakeReturns := fake.listChannelsReturns
+ return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeNewRelicAlertsClient) ListChannelsCallCount() int {
+ fake.listChannelsMutex.RLock()
+ defer fake.listChannelsMutex.RUnlock()
+ return len(fake.listChannelsArgsForCall)
+}
+
+func (fake *FakeNewRelicAlertsClient) ListChannelsCalls(stub func() ([]*alerts.Channel, error)) {
+ fake.listChannelsMutex.Lock()
+ defer fake.listChannelsMutex.Unlock()
+ fake.ListChannelsStub = stub
+}
+
+func (fake *FakeNewRelicAlertsClient) ListChannelsReturns(result1 []*alerts.Channel, result2 error) {
+ fake.listChannelsMutex.Lock()
+ defer fake.listChannelsMutex.Unlock()
+ fake.ListChannelsStub = nil
+ fake.listChannelsReturns = struct {
+ result1 []*alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
+func (fake *FakeNewRelicAlertsClient) ListChannelsReturnsOnCall(i int, result1 []*alerts.Channel, result2 error) {
+ fake.listChannelsMutex.Lock()
+ defer fake.listChannelsMutex.Unlock()
+ fake.ListChannelsStub = nil
+ if fake.listChannelsReturnsOnCall == nil {
+ fake.listChannelsReturnsOnCall = make(map[int]struct {
+ result1 []*alerts.Channel
+ result2 error
+ })
+ }
+ fake.listChannelsReturnsOnCall[i] = struct {
+ result1 []*alerts.Channel
+ result2 error
+ }{result1, result2}
+}
+
func (fake *FakeNewRelicAlertsClient) ListConditions(arg1 int) ([]*alerts.Condition, error) {
fake.listConditionsMutex.Lock()
ret, specificReturn := fake.listConditionsReturnsOnCall[len(fake.listConditionsArgsForCall)]
@@ -1727,6 +2038,75 @@ func (fake *FakeNewRelicAlertsClient) UpdatePolicyReturnsOnCall(i int, result1 *
}{result1, result2}
}
+func (fake *FakeNewRelicAlertsClient) UpdatePolicyChannels(arg1 int, arg2 []int) (*alerts.PolicyChannels, error) {
+ var arg2Copy []int
+ if arg2 != nil {
+ arg2Copy = make([]int, len(arg2))
+ copy(arg2Copy, arg2)
+ }
+ fake.updatePolicyChannelsMutex.Lock()
+ ret, specificReturn := fake.updatePolicyChannelsReturnsOnCall[len(fake.updatePolicyChannelsArgsForCall)]
+ fake.updatePolicyChannelsArgsForCall = append(fake.updatePolicyChannelsArgsForCall, struct {
+ arg1 int
+ arg2 []int
+ }{arg1, arg2Copy})
+ fake.recordInvocation("UpdatePolicyChannels", []interface{}{arg1, arg2Copy})
+ fake.updatePolicyChannelsMutex.Unlock()
+ if fake.UpdatePolicyChannelsStub != nil {
+ return fake.UpdatePolicyChannelsStub(arg1, arg2)
+ }
+ if specificReturn {
+ return ret.result1, ret.result2
+ }
+ fakeReturns := fake.updatePolicyChannelsReturns
+ return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeNewRelicAlertsClient) UpdatePolicyChannelsCallCount() int {
+ fake.updatePolicyChannelsMutex.RLock()
+ defer fake.updatePolicyChannelsMutex.RUnlock()
+ return len(fake.updatePolicyChannelsArgsForCall)
+}
+
+func (fake *FakeNewRelicAlertsClient) UpdatePolicyChannelsCalls(stub func(int, []int) (*alerts.PolicyChannels, error)) {
+ fake.updatePolicyChannelsMutex.Lock()
+ defer fake.updatePolicyChannelsMutex.Unlock()
+ fake.UpdatePolicyChannelsStub = stub
+}
+
+func (fake *FakeNewRelicAlertsClient) UpdatePolicyChannelsArgsForCall(i int) (int, []int) {
+ fake.updatePolicyChannelsMutex.RLock()
+ defer fake.updatePolicyChannelsMutex.RUnlock()
+ argsForCall := fake.updatePolicyChannelsArgsForCall[i]
+ return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeNewRelicAlertsClient) UpdatePolicyChannelsReturns(result1 *alerts.PolicyChannels, result2 error) {
+ fake.updatePolicyChannelsMutex.Lock()
+ defer fake.updatePolicyChannelsMutex.Unlock()
+ fake.UpdatePolicyChannelsStub = nil
+ fake.updatePolicyChannelsReturns = struct {
+ result1 *alerts.PolicyChannels
+ result2 error
+ }{result1, result2}
+}
+
+func (fake *FakeNewRelicAlertsClient) UpdatePolicyChannelsReturnsOnCall(i int, result1 *alerts.PolicyChannels, result2 error) {
+ fake.updatePolicyChannelsMutex.Lock()
+ defer fake.updatePolicyChannelsMutex.Unlock()
+ fake.UpdatePolicyChannelsStub = nil
+ if fake.updatePolicyChannelsReturnsOnCall == nil {
+ fake.updatePolicyChannelsReturnsOnCall = make(map[int]struct {
+ result1 *alerts.PolicyChannels
+ result2 error
+ })
+ }
+ fake.updatePolicyChannelsReturnsOnCall[i] = struct {
+ result1 *alerts.PolicyChannels
+ result2 error
+ }{result1, result2}
+}
+
func (fake *FakeNewRelicAlertsClient) UpdatePolicyMutation(arg1 int, arg2 string, arg3 alerts.AlertsPolicyUpdateInput) (*alerts.AlertsPolicy, error) {
fake.updatePolicyMutationMutex.Lock()
ret, specificReturn := fake.updatePolicyMutationReturnsOnCall[len(fake.updatePolicyMutationArgsForCall)]
@@ -1795,6 +2175,8 @@ func (fake *FakeNewRelicAlertsClient) UpdatePolicyMutationReturnsOnCall(i int, r
func (fake *FakeNewRelicAlertsClient) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
+ fake.createChannelMutex.RLock()
+ defer fake.createChannelMutex.RUnlock()
fake.createConditionMutex.RLock()
defer fake.createConditionMutex.RUnlock()
fake.createNrqlConditionMutex.RLock()
@@ -1805,6 +2187,8 @@ func (fake *FakeNewRelicAlertsClient) Invocations() map[string][][]interface{} {
defer fake.createPolicyMutex.RUnlock()
fake.createPolicyMutationMutex.RLock()
defer fake.createPolicyMutationMutex.RUnlock()
+ fake.deleteChannelMutex.RLock()
+ defer fake.deleteChannelMutex.RUnlock()
fake.deleteConditionMutex.RLock()
defer fake.deleteConditionMutex.RUnlock()
fake.deleteConditionMutationMutex.RLock()
@@ -1813,12 +2197,16 @@ func (fake *FakeNewRelicAlertsClient) Invocations() map[string][][]interface{} {
defer fake.deleteNrqlConditionMutex.RUnlock()
fake.deletePolicyMutex.RLock()
defer fake.deletePolicyMutex.RUnlock()
+ fake.deletePolicyChannelMutex.RLock()
+ defer fake.deletePolicyChannelMutex.RUnlock()
fake.deletePolicyMutationMutex.RLock()
defer fake.deletePolicyMutationMutex.RUnlock()
fake.getNrqlConditionQueryMutex.RLock()
defer fake.getNrqlConditionQueryMutex.RUnlock()
fake.getPolicyMutex.RLock()
defer fake.getPolicyMutex.RUnlock()
+ fake.listChannelsMutex.RLock()
+ defer fake.listChannelsMutex.RUnlock()
fake.listConditionsMutex.RLock()
defer fake.listConditionsMutex.RUnlock()
fake.listNrqlConditionsMutex.RLock()
@@ -1839,6 +2227,8 @@ func (fake *FakeNewRelicAlertsClient) Invocations() map[string][][]interface{} {
defer fake.updateNrqlConditionStaticMutationMutex.RUnlock()
fake.updatePolicyMutex.RLock()
defer fake.updatePolicyMutex.RUnlock()
+ fake.updatePolicyChannelsMutex.RLock()
+ defer fake.updatePolicyChannelsMutex.RUnlock()
fake.updatePolicyMutationMutex.RLock()
defer fake.updatePolicyMutationMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
diff --git a/interfaces/new_relic_alert_client.go b/interfaces/new_relic_alert_client.go
index 8f61016..78bf52c 100644
--- a/interfaces/new_relic_alert_client.go
+++ b/interfaces/new_relic_alert_client.go
@@ -26,6 +26,11 @@ type NewRelicAlertsClient interface {
UpdatePolicy(alerts.Policy) (*alerts.Policy, error)
DeletePolicy(int) (*alerts.Policy, error)
ListPolicies(*alerts.ListPoliciesParams) ([]alerts.Policy, error)
+ CreateChannel(channel alerts.Channel) (*alerts.Channel, error)
+ DeleteChannel(id int) (*alerts.Channel, error)
+ ListChannels() ([]*alerts.Channel, error)
+ UpdatePolicyChannels(policyID int, channelIDs []int) (*alerts.PolicyChannels, error)
+ DeletePolicyChannel(policyID int, ChannelID int) (*alerts.Channel, error)
// NerdGraph
CreatePolicyMutation(accountID int, policy alerts.AlertsPolicyInput) (*alerts.AlertsPolicy, error)