feat(notification/endpoint): add endpoint struct
Co-authored-by: Jade McGough <jade@thezets.com>pull/14639/head
parent
a8d7870689
commit
f683f0dc46
2
check.go
2
check.go
|
@ -21,7 +21,7 @@ type Check interface {
|
|||
GenerateFlux() (string, error)
|
||||
GetAuthID() ID
|
||||
json.Marshaler
|
||||
Updator
|
||||
Updater
|
||||
Getter
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
"encoding/json"
|
||||
)
|
||||
|
||||
// Updator is general interface to embed
|
||||
// Updater is general interface to embed
|
||||
// with any domain level interface to do crud related ops.
|
||||
type Updator interface {
|
||||
type Updater interface {
|
||||
CRUDLogSetter
|
||||
SetID(id ID)
|
||||
SetOrgID(id ID)
|
||||
|
@ -34,7 +34,7 @@ type NotificationRule interface {
|
|||
Valid() error
|
||||
Type() string
|
||||
json.Marshaler
|
||||
Updator
|
||||
Updater
|
||||
Getter
|
||||
SetOwnerID(id ID)
|
||||
GetOwnerID() ID
|
||||
|
@ -71,12 +71,14 @@ func (f NotificationRuleFilter) QueryParams() map[string][]string {
|
|||
return qp
|
||||
}
|
||||
|
||||
// NotificationRuleUpdate is the set of upgrade fields for patch request.
|
||||
type NotificationRuleUpdate struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Status *Status `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Valid will verify if the NotificationRuleUpdate is valid.
|
||||
func (n *NotificationRuleUpdate) Valid() error {
|
||||
if n.Name != nil && *n.Name == "" {
|
||||
return &Error{
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
package endpoint
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
// types of endpoints.
|
||||
const (
|
||||
SlackType = "slack"
|
||||
PagerDutyType = "pagerduty"
|
||||
WebhookType = "webhook"
|
||||
)
|
||||
|
||||
var typeToEndpoint = map[string](func() influxdb.NotificationEndpoint){
|
||||
SlackType: func() influxdb.NotificationEndpoint { return &Slack{} },
|
||||
PagerDutyType: func() influxdb.NotificationEndpoint { return &PagerDuty{} },
|
||||
WebhookType: func() influxdb.NotificationEndpoint { return &WebHook{} },
|
||||
}
|
||||
|
||||
type rawJSON struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON will convert the bytes to notification endpoint.
|
||||
func UnmarshalJSON(b []byte) (influxdb.NotificationEndpoint, error) {
|
||||
var raw rawJSON
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return nil, &influxdb.Error{
|
||||
Msg: "unable to detect the notification endpoint type from json",
|
||||
}
|
||||
}
|
||||
convertedFunc, ok := typeToEndpoint[raw.Type]
|
||||
if !ok {
|
||||
return nil, &influxdb.Error{
|
||||
Msg: fmt.Sprintf("invalid notification endpoint type %s", raw.Type),
|
||||
}
|
||||
}
|
||||
converted := convertedFunc()
|
||||
err := json.Unmarshal(b, converted)
|
||||
return converted, err
|
||||
}
|
||||
|
||||
// Base is the embed struct of every notification endpoint.
|
||||
type Base struct {
|
||||
ID influxdb.ID `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
OrgID influxdb.ID `json:"orgID,omitempty"`
|
||||
Status influxdb.Status `json:"status"`
|
||||
influxdb.CRUDLog
|
||||
}
|
||||
|
||||
func (b Base) valid() error {
|
||||
if !b.ID.Valid() {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Notification Endpoint ID is invalid",
|
||||
}
|
||||
}
|
||||
if b.Name == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Notification Endpoint Name can't be empty",
|
||||
}
|
||||
}
|
||||
if b.Status != influxdb.Active && b.Status != influxdb.Inactive {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid status",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID implements influxdb.Getter interface.
|
||||
func (b Base) GetID() influxdb.ID {
|
||||
return b.ID
|
||||
}
|
||||
|
||||
// GetName implements influxdb.Getter interface.
|
||||
func (b *Base) GetName() string {
|
||||
return b.Name
|
||||
}
|
||||
|
||||
// GetOrgID implements influxdb.Getter interface.
|
||||
func (b Base) GetOrgID() influxdb.ID {
|
||||
return b.OrgID
|
||||
}
|
||||
|
||||
// GetCRUDLog implements influxdb.Getter interface.
|
||||
func (b Base) GetCRUDLog() influxdb.CRUDLog {
|
||||
return b.CRUDLog
|
||||
}
|
||||
|
||||
// GetDescription implements influxdb.Getter interface.
|
||||
func (b *Base) GetDescription() string {
|
||||
return b.Description
|
||||
}
|
||||
|
||||
// GetStatus implements influxdb.Getter interface.
|
||||
func (b *Base) GetStatus() influxdb.Status {
|
||||
return b.Status
|
||||
}
|
||||
|
||||
// SetID will set the primary key.
|
||||
func (b *Base) SetID(id influxdb.ID) {
|
||||
b.ID = id
|
||||
}
|
||||
|
||||
// SetOrgID will set the org key.
|
||||
func (b *Base) SetOrgID(id influxdb.ID) {
|
||||
b.OrgID = id
|
||||
}
|
||||
|
||||
// SetName implements influxdb.Updator interface.
|
||||
func (b *Base) SetName(name string) {
|
||||
b.Name = name
|
||||
}
|
||||
|
||||
// SetDescription implements influxdb.Updator interface.
|
||||
func (b *Base) SetDescription(description string) {
|
||||
b.Description = description
|
||||
}
|
||||
|
||||
// SetStatus implements influxdb.Updator interface.
|
||||
func (b *Base) SetStatus(status influxdb.Status) {
|
||||
b.Status = status
|
||||
}
|
|
@ -0,0 +1,391 @@
|
|||
package endpoint_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/influxdata/influxdb"
|
||||
"github.com/influxdata/influxdb/mock"
|
||||
"github.com/influxdata/influxdb/notification/endpoint"
|
||||
influxTesting "github.com/influxdata/influxdb/testing"
|
||||
)
|
||||
|
||||
const (
|
||||
id1 = "020f755c3c082000"
|
||||
id3 = "020f755c3c082002"
|
||||
)
|
||||
|
||||
var goodBase = endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
Description: "desc1",
|
||||
}
|
||||
|
||||
func TestValidEndpoint(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src influxdb.NotificationEndpoint
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "invalid endpoint id",
|
||||
src: &endpoint.Slack{},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "Notification Endpoint ID is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid status",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
},
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid status",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slack url",
|
||||
src: &endpoint.Slack{
|
||||
Base: goodBase,
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "slack endpoint URL is empty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid slack url",
|
||||
src: &endpoint.Slack{
|
||||
Base: goodBase,
|
||||
URL: "posts://er:{DEf1=ghi@:5432/db?ssl",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "slack endpoint URL is invalid: parse posts://er:{DEf1=ghi@:5432/db?ssl: net/url: invalid userinfo",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slack token",
|
||||
src: &endpoint.Slack{
|
||||
Base: goodBase,
|
||||
URL: "localhost",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "slack endpoint token is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid slack token",
|
||||
src: &endpoint.Slack{
|
||||
Base: goodBase,
|
||||
URL: "localhost",
|
||||
Token: influxdb.SecretField{Key: "bad-key"},
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "slack endpoint token is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty pagerduty url",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: goodBase,
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "pagerduty endpoint URL is empty",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid pagerduty url",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: goodBase,
|
||||
URL: "posts://er:{DEf1=ghi@:5432/db?ssl",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "pagerduty endpoint URL is invalid: parse posts://er:{DEf1=ghi@:5432/db?ssl: net/url: invalid userinfo",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid routine key",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: goodBase,
|
||||
URL: "localhost",
|
||||
RoutingKey: influxdb.SecretField{Key: "bad-key"},
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "pagerduty routing key is invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty webhook http method",
|
||||
src: &endpoint.WebHook{
|
||||
Base: goodBase,
|
||||
URL: "localhost",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook http method",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty webhook token",
|
||||
src: &endpoint.WebHook{
|
||||
Base: goodBase,
|
||||
URL: "localhost",
|
||||
Method: "GET",
|
||||
AuthMethod: "bearer",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook token for bearer auth",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty webhook username",
|
||||
src: &endpoint.WebHook{
|
||||
Base: goodBase,
|
||||
URL: "localhost",
|
||||
Method: http.MethodGet,
|
||||
AuthMethod: "basic",
|
||||
},
|
||||
err: &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook username/password for basic auth",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got := c.src.Valid()
|
||||
influxTesting.ErrorsEqual(t, got, c.err)
|
||||
}
|
||||
}
|
||||
|
||||
var timeGen1 = mock.TimeGenerator{FakeValue: time.Date(2006, time.July, 13, 4, 19, 10, 0, time.UTC)}
|
||||
var timeGen2 = mock.TimeGenerator{FakeValue: time.Date(2006, time.July, 14, 5, 23, 53, 10, time.UTC)}
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src influxdb.NotificationEndpoint
|
||||
}{
|
||||
{
|
||||
name: "simple Slack",
|
||||
src: &endpoint.Slack{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://slack.com/api/chat.postMessage",
|
||||
Token: influxdb.SecretField{Key: "token-key-1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple pagerduty",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://events.pagerduty.com/v2/enqueue",
|
||||
RoutingKey: influxdb.SecretField{Key: "pagerduty-routing-key"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple webhook",
|
||||
src: &endpoint.WebHook{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
AuthMethod: "basic",
|
||||
URL: "http://example.com",
|
||||
Username: influxdb.SecretField{Key: "username-key"},
|
||||
Password: influxdb.SecretField{Key: "password-key"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
b, err := json.Marshal(c.src)
|
||||
if err != nil {
|
||||
t.Fatalf("%s marshal failed, err: %s", c.name, err.Error())
|
||||
}
|
||||
got, err := endpoint.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
t.Fatalf("%s unmarshal failed, err: %s", c.name, err.Error())
|
||||
}
|
||||
if diff := cmp.Diff(got, c.src); diff != "" {
|
||||
t.Errorf("failed %s, NotificationEndpoint are different -got/+want\ndiff %s", c.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackFill(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src influxdb.NotificationEndpoint
|
||||
target influxdb.NotificationEndpoint
|
||||
}{
|
||||
{
|
||||
name: "simple Slack",
|
||||
src: &endpoint.Slack{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://slack.com/api/chat.postMessage",
|
||||
Token: influxdb.SecretField{
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
target: &endpoint.Slack{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://slack.com/api/chat.postMessage",
|
||||
Token: influxdb.SecretField{
|
||||
Key: id1 + "-token",
|
||||
Value: strPtr("token-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple pagerduty",
|
||||
src: &endpoint.PagerDuty{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://events.pagerduty.com/v2/enqueue",
|
||||
RoutingKey: influxdb.SecretField{
|
||||
Value: strPtr("routing-key-value"),
|
||||
},
|
||||
},
|
||||
target: &endpoint.PagerDuty{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
URL: "https://events.pagerduty.com/v2/enqueue",
|
||||
RoutingKey: influxdb.SecretField{
|
||||
Key: id1 + "-routing-key",
|
||||
Value: strPtr("routing-key-value"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook with token",
|
||||
src: &endpoint.WebHook{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
AuthMethod: "basic",
|
||||
URL: "http://example.com",
|
||||
Username: influxdb.SecretField{
|
||||
Value: strPtr("username1"),
|
||||
},
|
||||
Password: influxdb.SecretField{
|
||||
Value: strPtr("password1"),
|
||||
},
|
||||
},
|
||||
target: &endpoint.WebHook{
|
||||
Base: endpoint.Base{
|
||||
ID: influxTesting.MustIDBase16(id1),
|
||||
Name: "name1",
|
||||
OrgID: influxTesting.MustIDBase16(id3),
|
||||
Status: influxdb.Active,
|
||||
CRUDLog: influxdb.CRUDLog{
|
||||
CreatedAt: timeGen1.Now(),
|
||||
UpdatedAt: timeGen2.Now(),
|
||||
},
|
||||
},
|
||||
AuthMethod: "basic",
|
||||
URL: "http://example.com",
|
||||
Username: influxdb.SecretField{
|
||||
Key: id1 + "-username",
|
||||
Value: strPtr("username1"),
|
||||
},
|
||||
Password: influxdb.SecretField{
|
||||
Key: id1 + "-password",
|
||||
Value: strPtr("password1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
c.src.BackfillSecretKeys()
|
||||
if diff := cmp.Diff(c.target, c.src); diff != "" {
|
||||
t.Errorf("failed %s, NotificationEndpoint are different -got/+want\ndiff %s", c.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
ss := new(string)
|
||||
*ss = s
|
||||
return ss
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package endpoint
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
var _ influxdb.NotificationEndpoint = &PagerDuty{}
|
||||
|
||||
const routingKeySuffix = "-routing-key"
|
||||
|
||||
// PagerDuty is the notification endpoint config of pagerduty.
|
||||
type PagerDuty struct {
|
||||
Base
|
||||
// Path is the PagerDuty API URL, should not need to be changed.
|
||||
URL string `json:"url"`
|
||||
// RoutingKey is a version 4 UUID expressed as a 32-digit hexadecimal number.
|
||||
// This is the Integration Key for an integration on any given service.
|
||||
RoutingKey influxdb.SecretField `json:"routing-key"`
|
||||
}
|
||||
|
||||
// BackfillSecretKeys fill back fill the secret field key during the unmarshalling
|
||||
// if value of that secret field is not nil.
|
||||
func (s *PagerDuty) BackfillSecretKeys() {
|
||||
if s.RoutingKey.Key == "" && s.RoutingKey.Value != nil {
|
||||
s.RoutingKey.Key = s.ID.String() + routingKeySuffix
|
||||
}
|
||||
}
|
||||
|
||||
// SecretFields return available secret fields.
|
||||
func (s PagerDuty) SecretFields() []influxdb.SecretField {
|
||||
return []influxdb.SecretField{
|
||||
s.RoutingKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Valid returns error if some configuration is invalid
|
||||
func (s PagerDuty) Valid() error {
|
||||
if err := s.Base.valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.URL == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "pagerduty endpoint URL is empty",
|
||||
}
|
||||
}
|
||||
if _, err := url.Parse(s.URL); err != nil {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: fmt.Sprintf("pagerduty endpoint URL is invalid: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
if s.RoutingKey.Key != s.ID.String()+routingKeySuffix {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "pagerduty routing key is invalid",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type pagerdutyAlias PagerDuty
|
||||
|
||||
// MarshalJSON implement json.Marshaler interface.
|
||||
func (s PagerDuty) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
struct {
|
||||
pagerdutyAlias
|
||||
Type string `json:"type"`
|
||||
}{
|
||||
pagerdutyAlias: pagerdutyAlias(s),
|
||||
Type: s.Type(),
|
||||
})
|
||||
}
|
||||
|
||||
// Type returns the type.
|
||||
func (s PagerDuty) Type() string {
|
||||
return PagerDutyType
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package endpoint
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
var _ influxdb.NotificationEndpoint = &Slack{}
|
||||
|
||||
const slackTokenSuffix = "-token"
|
||||
|
||||
// Slack is the notification endpoint config of slack.
|
||||
type Slack struct {
|
||||
Base
|
||||
// URL is a valid slack webhook URL
|
||||
// TODO(jm): validate this in unmarshaler
|
||||
// example: https://slack.com/api/chat.postMessage
|
||||
URL string `json:"url"`
|
||||
// Token is the bearer token for authorization
|
||||
Token influxdb.SecretField `json:"token"`
|
||||
}
|
||||
|
||||
// BackfillSecretKeys fill back fill the secret field key during the unmarshalling
|
||||
// if value of that secret field is not nil.
|
||||
func (s *Slack) BackfillSecretKeys() {
|
||||
if s.Token.Key == "" && s.Token.Value != nil {
|
||||
s.Token.Key = s.ID.String() + slackTokenSuffix
|
||||
}
|
||||
}
|
||||
|
||||
// SecretFields return available secret fields.
|
||||
func (s Slack) SecretFields() []influxdb.SecretField {
|
||||
return []influxdb.SecretField{
|
||||
s.Token,
|
||||
}
|
||||
}
|
||||
|
||||
// Valid returns error if some configuration is invalid
|
||||
func (s Slack) Valid() error {
|
||||
if err := s.Base.valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.URL == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "slack endpoint URL is empty",
|
||||
}
|
||||
}
|
||||
if _, err := url.Parse(s.URL); err != nil {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: fmt.Sprintf("slack endpoint URL is invalid: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
if s.Token.Key != s.ID.String()+slackTokenSuffix {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "slack endpoint token is invalid",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type slackAlias Slack
|
||||
|
||||
// MarshalJSON implement json.Marshaler interface.
|
||||
func (s Slack) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
struct {
|
||||
slackAlias
|
||||
Type string `json:"type"`
|
||||
}{
|
||||
slackAlias: slackAlias(s),
|
||||
Type: s.Type(),
|
||||
})
|
||||
}
|
||||
|
||||
// Type returns the type.
|
||||
func (s Slack) Type() string {
|
||||
return SlackType
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package endpoint
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/influxdata/influxdb"
|
||||
)
|
||||
|
||||
var _ influxdb.NotificationEndpoint = &WebHook{}
|
||||
|
||||
const (
|
||||
webhookTokenSuffix = "-token"
|
||||
webhookUsernameSuffix = "-username"
|
||||
webhookPasswordSuffix = "-password"
|
||||
)
|
||||
|
||||
// WebHook is the notification endpoint config of webhook.
|
||||
type WebHook struct {
|
||||
Base
|
||||
// Path is the API path of WebHook
|
||||
URL string `json:"url"`
|
||||
// Token is the bearer token for authorization
|
||||
Token influxdb.SecretField `json:"token,omitempty"`
|
||||
Username influxdb.SecretField `json:"username,omitempty"`
|
||||
Password influxdb.SecretField `json:"password,omitempty"`
|
||||
AuthMethod string `json:"authmethod"`
|
||||
Method string `json:"method"`
|
||||
ContentTemplate string `json:"contentTemplate"`
|
||||
}
|
||||
|
||||
// BackfillSecretKeys fill back fill the secret field key during the unmarshalling
|
||||
// if value of that secret field is not nil.
|
||||
func (s *WebHook) BackfillSecretKeys() {
|
||||
if s.Token.Key == "" && s.Token.Value != nil {
|
||||
s.Token.Key = s.ID.String() + webhookTokenSuffix
|
||||
}
|
||||
if s.Username.Key == "" && s.Username.Value != nil {
|
||||
s.Username.Key = s.ID.String() + webhookUsernameSuffix
|
||||
}
|
||||
if s.Password.Key == "" && s.Password.Value != nil {
|
||||
s.Password.Key = s.ID.String() + webhookPasswordSuffix
|
||||
}
|
||||
}
|
||||
|
||||
// SecretFields return available secret fields.
|
||||
func (s WebHook) SecretFields() []influxdb.SecretField {
|
||||
arr := make([]influxdb.SecretField, 0)
|
||||
if s.Token.Key != "" {
|
||||
arr = append(arr, s.Token)
|
||||
}
|
||||
if s.Username.Key != "" {
|
||||
arr = append(arr, s.Username)
|
||||
}
|
||||
if s.Password.Key != "" {
|
||||
arr = append(arr, s.Password)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
var goodWebHookAuthMethod = map[string]bool{
|
||||
"none": true,
|
||||
"basic": true,
|
||||
"bearer": true,
|
||||
}
|
||||
|
||||
var goodHTTPMethod = map[string]bool{
|
||||
http.MethodGet: true,
|
||||
http.MethodPost: true,
|
||||
http.MethodPut: true,
|
||||
}
|
||||
|
||||
// Valid returns error if some configuration is invalid
|
||||
func (s WebHook) Valid() error {
|
||||
if err := s.Base.valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.URL == "" {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "webhook endpoint URL is empty",
|
||||
}
|
||||
}
|
||||
if _, err := url.Parse(s.URL); err != nil {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: fmt.Sprintf("webhook endpoint URL is invalid: %s", err.Error()),
|
||||
}
|
||||
}
|
||||
if !goodHTTPMethod[s.Method] {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook http method",
|
||||
}
|
||||
}
|
||||
if !goodWebHookAuthMethod[s.AuthMethod] {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook auth method",
|
||||
}
|
||||
}
|
||||
if s.AuthMethod == "basic" &&
|
||||
(s.Username.Key != s.ID.String()+webhookUsernameSuffix ||
|
||||
s.Password.Key != s.ID.String()+webhookPasswordSuffix) {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook username/password for basic auth",
|
||||
}
|
||||
}
|
||||
if s.AuthMethod == "bearer" && s.Token.Key != webhookTokenSuffix {
|
||||
return &influxdb.Error{
|
||||
Code: influxdb.EInvalid,
|
||||
Msg: "invalid webhook token for bearer auth",
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type webhookAlias WebHook
|
||||
|
||||
// MarshalJSON implement json.Marshaler interface.
|
||||
func (s WebHook) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(
|
||||
struct {
|
||||
webhookAlias
|
||||
Type string `json:"type"`
|
||||
}{
|
||||
webhookAlias: webhookAlias(s),
|
||||
Type: s.Type(),
|
||||
})
|
||||
}
|
||||
|
||||
// Type returns the type.
|
||||
func (s WebHook) Type() string {
|
||||
return WebhookType
|
||||
}
|
||||
|
||||
// ParseResponse will parse the http response from webhook.
|
||||
func (s WebHook) ParseResponse(resp *http.Response) error {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return &influxdb.Error{
|
||||
Msg: string(body),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidNotificationEndpointType denotes that the provided NotificationEndpoint is not a valid type
|
||||
ErrInvalidNotificationEndpointType = errors.New("unknown notification endpoint type")
|
||||
)
|
||||
|
||||
// NotificationEndpoint is the configuration describing
|
||||
// how to call a 3rd party service. E.g. Slack, Pagerduty
|
||||
type NotificationEndpoint interface {
|
||||
Valid() error
|
||||
Type() string
|
||||
json.Marshaler
|
||||
Updater
|
||||
Getter
|
||||
// SecretFields return available secret fields.
|
||||
SecretFields() []SecretField
|
||||
// BackfillSecretKeys fill back fill the secret field key during the unmarshalling
|
||||
// if value of that secret field is not nil.
|
||||
BackfillSecretKeys()
|
||||
}
|
||||
|
||||
// ops for checks error
|
||||
var (
|
||||
OpFindNotificationEndpointByID = "FindNotificationEndpointByID"
|
||||
OpFindNotificationEndpoint = "FindNotificationEndpoint"
|
||||
OpFindNotificationEndpoints = "FindNotificationEndpoints"
|
||||
OpCreateNotificationEndpoint = "CreateNotificationEndpoint"
|
||||
OpUpdateNotificationEndpoint = "UpdateNotificationEndpoint"
|
||||
OpDeleteNotificationEndpoint = "DeleteNotificationEndpoint"
|
||||
)
|
||||
|
||||
// NotificationEndpointFilter represents a set of filter that restrict the returned notification endpoints.
|
||||
type NotificationEndpointFilter struct {
|
||||
ID *ID
|
||||
OrgID *ID
|
||||
Org *string
|
||||
}
|
||||
|
||||
// QueryParams Converts NotificationEndpointFilter fields to url query params.
|
||||
func (f NotificationEndpointFilter) QueryParams() map[string][]string {
|
||||
qp := map[string][]string{}
|
||||
|
||||
if f.OrgID != nil {
|
||||
qp["orgID"] = []string{f.OrgID.String()}
|
||||
}
|
||||
|
||||
if f.Org != nil {
|
||||
qp["org"] = []string{*f.Org}
|
||||
}
|
||||
|
||||
return qp
|
||||
}
|
||||
|
||||
// NotificationEndpointUpdate is the set of upgrade fields for patch request.
|
||||
type NotificationEndpointUpdate struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Status *Status `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// Valid will verify if the NotificationEndpointUpdate is valid.
|
||||
func (n *NotificationEndpointUpdate) Valid() error {
|
||||
if n.Name != nil && *n.Name == "" {
|
||||
return &Error{
|
||||
Code: EInvalid,
|
||||
Msg: "Notification Endpoint Name can't be empty",
|
||||
}
|
||||
}
|
||||
|
||||
if n.Description != nil && *n.Description == "" {
|
||||
return &Error{
|
||||
Code: EInvalid,
|
||||
Msg: "Notification Endpoint Description can't be empty",
|
||||
}
|
||||
}
|
||||
|
||||
if n.Status != nil {
|
||||
if err := n.Status.Valid(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotificationEndpointService represents a service for managing notification endpoints.
|
||||
type NotificationEndpointService interface {
|
||||
// UserResourceMappingService must be part of all NotificationEndpointStore service,
|
||||
// for create, delete.
|
||||
UserResourceMappingService
|
||||
// OrganizationService is needed for search filter
|
||||
OrganizationService
|
||||
// SecretService is needed to check if the secret key exists.
|
||||
SecretService
|
||||
|
||||
// FindNotificationEndpointByID returns a single notification endpoint by ID.
|
||||
FindNotificationEndpointByID(ctx context.Context, id ID) (NotificationEndpoint, error)
|
||||
|
||||
// FindNotificationEndpoints returns a list of notification endpoints that match filter and the total count of matching notification endpoints.
|
||||
// Additional options provide pagination & sorting.
|
||||
FindNotificationEndpoints(ctx context.Context, filter NotificationEndpointFilter, opt ...FindOptions) ([]NotificationEndpoint, int, error)
|
||||
|
||||
// CreateNotificationEndpoint creates a new notification endpoint and sets b.ID with the new identifier.
|
||||
CreateNotificationEndpoint(ctx context.Context, ne NotificationEndpoint, userID ID) error
|
||||
|
||||
// UpdateNotificationEndpointUpdateNotificationEndpoint updates a single notification endpoint.
|
||||
// Returns the new notification endpoint after update.
|
||||
UpdateNotificationEndpoint(ctx context.Context, id ID, nr NotificationEndpoint, userID ID) (NotificationEndpoint, error)
|
||||
|
||||
// PatchNotificationEndpoint updates a single notification endpoint with changeset.
|
||||
// Returns the new notification endpoint state after update.
|
||||
PatchNotificationEndpoint(ctx context.Context, id ID, upd NotificationEndpointUpdate) (NotificationEndpoint, error)
|
||||
|
||||
// DeleteNotificationEndpoint removes a notification endpoint by ID.
|
||||
DeleteNotificationEndpoint(ctx context.Context, id ID) error
|
||||
}
|
49
secret.go
49
secret.go
|
@ -1,6 +1,10 @@
|
|||
package influxdb
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ErrSecretNotFound is the error msg for a missing secret.
|
||||
const ErrSecretNotFound = "secret not found"
|
||||
|
@ -25,3 +29,46 @@ type SecretService interface {
|
|||
// DeleteSecret removes a single secret from the secret store.
|
||||
DeleteSecret(ctx context.Context, orgID ID, ks ...string) error
|
||||
}
|
||||
|
||||
// SecretField contains a key string, and value pointer.
|
||||
type SecretField struct {
|
||||
Key string `json:"key"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// String returns the key of the secret.
|
||||
func (s SecretField) String() string {
|
||||
if s.Key == "" {
|
||||
return ""
|
||||
}
|
||||
return "secret: " + string(s.Key)
|
||||
}
|
||||
|
||||
// MarshalJSON implement the json marshaler interface.
|
||||
func (s SecretField) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implement the json unmarshaler interface.
|
||||
func (s *SecretField) UnmarshalJSON(b []byte) error {
|
||||
var ss string
|
||||
if err := json.Unmarshal(b, &ss); err != nil {
|
||||
return err
|
||||
}
|
||||
if ss == "" {
|
||||
s.Key = ""
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(ss, "secret: ") {
|
||||
s.Key = ss[len("secret: "):]
|
||||
} else {
|
||||
s.Value = strPtr(ss)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
ss := new(string)
|
||||
*ss = s
|
||||
return ss
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package influxdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestSecretFieldJSON(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fld *SecretField
|
||||
json string
|
||||
target SecretField
|
||||
}{
|
||||
{
|
||||
name: "regular",
|
||||
fld: &SecretField{Key: "some key"},
|
||||
json: `"secret: some key"`,
|
||||
target: SecretField{Key: "some key"},
|
||||
},
|
||||
{name: "blank", fld: &SecretField{}, json: `""`},
|
||||
{
|
||||
name: "with value",
|
||||
fld: &SecretField{
|
||||
Key: "some key",
|
||||
Value: strPtr("some value"),
|
||||
},
|
||||
json: `"secret: some key"`,
|
||||
target: SecretField{
|
||||
Key: "some key",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unmarshal a post",
|
||||
json: `"some value"`,
|
||||
target: SecretField{
|
||||
Value: strPtr("some value"),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if c.fld != nil {
|
||||
serialized, err := json.Marshal(c.fld)
|
||||
if err != nil {
|
||||
t.Fatalf("%s failed, secret key marshal err: %q", c.name, err.Error())
|
||||
}
|
||||
if string(serialized) != c.json {
|
||||
t.Fatalf("%s failed, secret key marshal result is unexpected, got %q, want %q", c.name, string(serialized), c.json)
|
||||
}
|
||||
}
|
||||
var deserialized SecretField
|
||||
if err := json.Unmarshal([]byte(c.json), &deserialized); err != nil {
|
||||
t.Fatalf("%s failed, secret key unmarshal err: %q", c.name, err.Error())
|
||||
}
|
||||
if diff := cmp.Diff(deserialized, c.target); diff != "" {
|
||||
t.Fatalf("%s failed, secret key unmarshal result is unexpected, diff %s", c.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue