feat(notification/endpoint): add endpoint struct

Co-authored-by: Jade McGough <jade@thezets.com>
pull/14639/head
Kelvin Wang 2019-07-26 15:38:30 -04:00
parent a8d7870689
commit f683f0dc46
10 changed files with 1081 additions and 5 deletions

View File

@ -21,7 +21,7 @@ type Check interface {
GenerateFlux() (string, error)
GetAuthID() ID
json.Marshaler
Updator
Updater
Getter
}

View File

@ -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{

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

123
notification_endpoint.go Normal file
View File

@ -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
}

View File

@ -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
}

61
secret_test.go Normal file
View File

@ -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)
}
}
}