From 005de8cf268fd2c67e5986d49a90f9fb20007a2b Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Tue, 5 Feb 2019 15:23:46 +1100 Subject: [PATCH 01/21] VYGR-391: Create OpsGenie integrations secret during sync --- cmd/synchronization/app/BUILD.bazel | 1 + .../app/controller_constructor.go | 6 ++ cmd/synchronization/app/options.go | 26 ++++++-- pkg/apis/creator/v1/types.go | 5 ++ pkg/opsgenie/it/client_manual_test.go | 2 +- pkg/synchronization/BUILD.bazel | 1 + pkg/synchronization/controller.go | 61 ++++++++++++++++--- pkg/trebuchet/server/apiserver/apiserver.go | 2 +- 8 files changed, 90 insertions(+), 14 deletions(-) diff --git a/cmd/synchronization/app/BUILD.bazel b/cmd/synchronization/app/BUILD.bazel index 892f839d..4400865b 100644 --- a/cmd/synchronization/app/BUILD.bazel +++ b/cmd/synchronization/app/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "//pkg/composition/client:go_default_library", "//pkg/k8s:go_default_library", "//pkg/k8s/updater:go_default_library", + "//pkg/opsgenie:go_default_library", "//pkg/options:go_default_library", "//pkg/releases:go_default_library", "//pkg/releases/deployinator/client:go_default_library", diff --git a/cmd/synchronization/app/controller_constructor.go b/cmd/synchronization/app/controller_constructor.go index 25258e83..fc593365 100644 --- a/cmd/synchronization/app/controller_constructor.go +++ b/cmd/synchronization/app/controller_constructor.go @@ -7,6 +7,7 @@ import ( comp_v1_client "github.com/atlassian/voyager/pkg/composition/client" "github.com/atlassian/voyager/pkg/k8s" "github.com/atlassian/voyager/pkg/k8s/updater" + "github.com/atlassian/voyager/pkg/opsgenie" "github.com/atlassian/voyager/pkg/releases" "github.com/atlassian/voyager/pkg/releases/deployinator/client" "github.com/atlassian/voyager/pkg/servicecentral" @@ -79,6 +80,10 @@ func (cc *ControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Context) (* scHTTPClient := util.HTTPClient() scClient := servicecentral.NewServiceCentralClient(config.Logger, scHTTPClient, opts.ASAPClientConfig, opts.Providers.ServiceCentralURL) + // create a client for talking to OpsGenie Integration Manager + ogHTTPClient := util.HTTPClient() + ogClient := opsgenie.New(config.Logger, ogHTTPClient, opts.ASAPClientConfig, opts.Providers.OpsgenieIntegrationsManagerURL) + scErrorCounter := prometheus.NewCounter( prometheus.CounterOpts{ Namespace: config.AppName, @@ -133,6 +138,7 @@ func (cc *ControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Context) (* ServiceCentral: servicecentral.NewStore(config.Logger, scClient), ReleaseManagement: releases.NewReleaseManagement(deployinatorHTTPClient, config.Logger), ClusterLocation: opts.Location.ClusterLocation(), + OpsGenie: ogClient, ConfigMapUpdater: configMapObjectUpdater, RoleBindingUpdater: roleBindingObjectUpdater, diff --git a/cmd/synchronization/app/options.go b/cmd/synchronization/app/options.go index 0fb9e74d..765c33c2 100644 --- a/cmd/synchronization/app/options.go +++ b/cmd/synchronization/app/options.go @@ -21,15 +21,17 @@ type Options struct { } type Providers struct { - ServiceCentralURL *url.URL // we use custom json marshalling to read it - DeployinatorURL *url.URL + ServiceCentralURL *url.URL // we use custom json marshalling to read it + DeployinatorURL *url.URL + OpsgenieIntegrationsManagerURL *url.URL } // UnmarshalJSON unmarshals our untyped config file into a typed struct including URLs func (p *Providers) UnmarshalJSON(data []byte) error { var rawProviders struct { - ServiceCentral string `json:"serviceCentral"` - Deployinator string `json:"deployinator"` + ServiceCentral string `json:"serviceCentral"` + Deployinator string `json:"deployinator"` + OpsgenieIntegrationsManager string `json:"opsgenieIntegrationsManager"` } if err := json.Unmarshal(data, &rawProviders); err != nil { @@ -43,7 +45,17 @@ func (p *Providers) UnmarshalJSON(data []byte) error { } depURL, err := url.Parse(rawProviders.Deployinator) p.DeployinatorURL = depURL - return errors.Wrap(err, "unable to parse Deployinator URL") + if err != nil { + return errors.Wrap(err, "unable to parse Deployinator URL") + } + + ogUrl, err := url.Parse(rawProviders.OpsgenieIntegrationsManager) + if err != nil { + return errors.Wrap(err, "unable to parse OpsGenie Integrations Manager URL") + } + p.OpsgenieIntegrationsManagerURL = ogUrl + + return nil } func (o *Options) DefaultAndValidate() []error { @@ -54,6 +66,10 @@ func (o *Options) DefaultAndValidate() []error { allErrors = append(allErrors, errors.New("providers.serviceCentral must be a valid URL")) } + if o.Providers.OpsgenieIntegrationsManagerURL == nil { + allErrors = append(allErrors, errors.New("providers.OpsgenieIntegrationsManagerURL must be a valid URL")) + } + return allErrors } diff --git a/pkg/apis/creator/v1/types.go b/pkg/apis/creator/v1/types.go index 9ed00d26..25bb14f7 100644 --- a/pkg/apis/creator/v1/types.go +++ b/pkg/apis/creator/v1/types.go @@ -59,6 +59,7 @@ func (ss *ServiceSpec) EmailAddress() string { // +k8s:deepcopy-gen=true type ServiceMetadata struct { PagerDuty *PagerDutyMetadata `json:"pagerDuty,omitempty"` + OpsGenie *OpsGenieMetadata `json:"opsGenie,omitempty"` Bamboo *BambooMetadata `json:"bamboo,omitempty"` } @@ -100,6 +101,10 @@ type PagerDutyIntegrationMetadata struct { IntegrationKey string `json:"integrationKey,omitempty"` } +type OpsGenieMetadata struct { + Team string `json:"team,omitempty"` +} + // +k8s:deepcopy-gen=true type Compliance struct { PRGBControl *bool `json:"prgbControl,omitempty"` diff --git a/pkg/opsgenie/it/client_manual_test.go b/pkg/opsgenie/it/client_manual_test.go index e30de768..0a862ace 100644 --- a/pkg/opsgenie/it/client_manual_test.go +++ b/pkg/opsgenie/it/client_manual_test.go @@ -12,7 +12,7 @@ import ( "github.com/atlassian/voyager/pkg/util/pkiutil" "github.com/atlassian/voyager/pkg/util/testutil" "github.com/stretchr/testify/require" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes/scheme" ) diff --git a/pkg/synchronization/BUILD.bazel b/pkg/synchronization/BUILD.bazel index dc0ba44c..6018ac26 100644 --- a/pkg/synchronization/BUILD.bazel +++ b/pkg/synchronization/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "//pkg/composition/client:go_default_library", "//pkg/k8s:go_default_library", "//pkg/k8s/updater:go_default_library", + "//pkg/opsgenie:go_default_library", "//pkg/orchestration/wiring/k8scompute/api:go_default_library", "//pkg/pagerduty:go_default_library", "//pkg/releases:go_default_library", diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index 2a6e0c8c..291de4ff 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -17,6 +17,7 @@ import ( compClient "github.com/atlassian/voyager/pkg/composition/client" "github.com/atlassian/voyager/pkg/k8s" "github.com/atlassian/voyager/pkg/k8s/updater" + "github.com/atlassian/voyager/pkg/opsgenie" apik8scompute "github.com/atlassian/voyager/pkg/orchestration/wiring/k8scompute/api" "github.com/atlassian/voyager/pkg/pagerduty" "github.com/atlassian/voyager/pkg/releases" @@ -58,13 +59,15 @@ const ( ) const ( - serviceCentralPollPeriod = 30 * time.Second - serviceCentralListAllPeriod = 30 * time.Minute - serviceCentralListDriftCompensation = 5 * time.Second - releaseManagementPollPeriod = 5 * time.Second - releaseManagementSyncAllPeriod = 30 * time.Minute - baseDelayProcSec = 15 - rmsPollJitterFactor = 1.2 + serviceCentralPollPeriod = 30 * time.Second + serviceCentralListAllPeriod = 30 * time.Minute + serviceCentralListDriftCompensation = 5 * time.Second + releaseManagementPollPeriod = 5 * time.Second + releaseManagementSyncAllPeriod = 30 * time.Minute + baseDelayProcSec = 15 + rmsPollJitterFactor = 1.2 + secretTypeOpsGenie core_v1.SecretType = voyager.Domain + "/opsgenie" + secretNameOpsGenie = "opsgenie-integrations" ) type ServiceMetadataStore interface { @@ -73,6 +76,10 @@ type ServiceMetadataStore interface { ListModifiedServices(ctx context.Context, user auth.OptionalUser, modifiedSince time.Time) ([]creator_v1.Service, error) } +type OpsGenieIntegrationManagerClient interface { + GetOrCreateIntegrations(ctx context.Context, teamName string) (*opsgenie.IntegrationsResponse, bool /* retriable */, error) +} + type Controller struct { Logger *zap.Logger ReadyForWork func() @@ -85,6 +92,7 @@ type Controller struct { ServiceCentral ServiceMetadataStore ReleaseManagement releases.ReleaseManagementStore ClusterLocation voyager.ClusterLocation + OpsGenie OpsGenieIntegrationManagerClient RoleBindingUpdater updater.ObjectUpdater ConfigMapUpdater updater.ObjectUpdater @@ -439,6 +447,22 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ }, } + // OpsGenie team is currently optional + if serviceData.Spec.Metadata.OpsGenie != nil { + resp, retriable, err := c.OpsGenie.GetOrCreateIntegrations(context.TODO(), serviceData.Spec.Metadata.OpsGenie.Team) + if err != nil { + return retriable, err + } + spec, err := createOpsGenieSecretSpec(resp, ns.Name) + if err != nil { + return false, err // Error indicates json marshalling problem + } + retriable, err = c.createOrUpdateSecret(logger, spec) + if err != nil { + return retriable, nil + } + } + conflict, retriable, _, err := c.ConfigMapUpdater.CreateOrUpdate( logger, func(r runtime.Object) error { @@ -455,6 +479,29 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ return false, nil } +// createOpsGenieSecretSpec creates an OpsGenieIntegrations secret object suitable for Secret creation +func createOpsGenieSecretSpec(ogInt *opsgenie.IntegrationsResponse, ns string) (*core_v1.Secret, error) { + jData, err := json.Marshal(ogInt) + if err != nil { + return &core_v1.Secret{}, err + } + + return &core_v1.Secret{ + TypeMeta: meta_v1.TypeMeta{ + Kind: k8s.SecretKind, + APIVersion: core_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: secretNameOpsGenie, + Namespace: ns, + }, + Data: map[string][]byte{ + secretNameOpsGenie: jData, + }, + Type: secretTypeOpsGenie, + }, nil +} + func (c *Controller) getServiceData(user auth.OptionalUser, name voyager.ServiceName) (*creator_v1.Service, error) { return c.ServiceCentral.GetService(context.Background(), user, servicecentral.ServiceName(name)) } diff --git a/pkg/trebuchet/server/apiserver/apiserver.go b/pkg/trebuchet/server/apiserver/apiserver.go index 127b6478..42b3ad69 100644 --- a/pkg/trebuchet/server/apiserver/apiserver.go +++ b/pkg/trebuchet/server/apiserver/apiserver.go @@ -3,7 +3,7 @@ package apiserver import ( apis_trebuchet "github.com/atlassian/voyager/pkg/apis/trebuchet" "github.com/atlassian/voyager/pkg/apis/trebuchet/install" - "github.com/atlassian/voyager/pkg/apis/trebuchet/v1" + v1 "github.com/atlassian/voyager/pkg/apis/trebuchet/v1" "github.com/atlassian/voyager/pkg/trebuchet" trebuchetrest "github.com/atlassian/voyager/pkg/trebuchet/server/rest" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" From f37c6fa2b180d64f515a0c5d24e30c6ca6c00480 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Wed, 13 Feb 2019 17:30:52 +1100 Subject: [PATCH 02/21] VYGR-391: Use SecretTypeOpaque for opsgenie secret --- pkg/synchronization/controller.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index 291de4ff..0d0a28cd 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -59,15 +59,14 @@ const ( ) const ( - serviceCentralPollPeriod = 30 * time.Second - serviceCentralListAllPeriod = 30 * time.Minute - serviceCentralListDriftCompensation = 5 * time.Second - releaseManagementPollPeriod = 5 * time.Second - releaseManagementSyncAllPeriod = 30 * time.Minute - baseDelayProcSec = 15 - rmsPollJitterFactor = 1.2 - secretTypeOpsGenie core_v1.SecretType = voyager.Domain + "/opsgenie" - secretNameOpsGenie = "opsgenie-integrations" + serviceCentralPollPeriod = 30 * time.Second + serviceCentralListAllPeriod = 30 * time.Minute + serviceCentralListDriftCompensation = 5 * time.Second + releaseManagementPollPeriod = 5 * time.Second + releaseManagementSyncAllPeriod = 30 * time.Minute + baseDelayProcSec = 15 + rmsPollJitterFactor = 1.2 + secretNameOpsGenie = "opsgenie-integrations" ) type ServiceMetadataStore interface { @@ -498,7 +497,7 @@ func createOpsGenieSecretSpec(ogInt *opsgenie.IntegrationsResponse, ns string) ( Data: map[string][]byte{ secretNameOpsGenie: jData, }, - Type: secretTypeOpsGenie, + Type: core_v1.SecretTypeOpaque, }, nil } From fcb59fa0ac7dc2be8bc284647ebaa29659cd74bc Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 13:54:45 +1100 Subject: [PATCH 03/21] VYGR-391: Add opsgenie integrations into notifications Remove opsgenie secret work-in-progress --- pkg/apis/orchestration/meta/BUILD.bazel | 5 +- pkg/apis/orchestration/meta/types.go | 12 ++- pkg/opsgenie/types.go | 27 +++-- pkg/synchronization/controller.go | 137 ++++++++++++++---------- 4 files changed, 108 insertions(+), 73 deletions(-) diff --git a/pkg/apis/orchestration/meta/BUILD.bazel b/pkg/apis/orchestration/meta/BUILD.bazel index 8104cd8b..6d828eb3 100644 --- a/pkg/apis/orchestration/meta/BUILD.bazel +++ b/pkg/apis/orchestration/meta/BUILD.bazel @@ -5,5 +5,8 @@ go_library( srcs = ["types.go"], importpath = "github.com/atlassian/voyager/pkg/apis/orchestration/meta", visibility = ["//visibility:public"], - deps = ["//:go_default_library"], + deps = [ + "//:go_default_library", + "//pkg/opsgenie:go_default_library", + ], ) diff --git a/pkg/apis/orchestration/meta/types.go b/pkg/apis/orchestration/meta/types.go index b2cc2519..7fef22a4 100644 --- a/pkg/apis/orchestration/meta/types.go +++ b/pkg/apis/orchestration/meta/types.go @@ -1,6 +1,9 @@ package meta -import "github.com/atlassian/voyager" +import ( + "github.com/atlassian/voyager" + "github.com/atlassian/voyager/pkg/opsgenie" +) const ( ConfigMapConfigKey = "config" @@ -27,9 +30,10 @@ type ServiceProperties struct { // Notification is used in the ServiceProperties. type Notifications struct { - Email string `json:"email"` - LowPriorityPagerdutyEndpoint PagerDuty `json:"lowPriority"` - PagerdutyEndpoint PagerDuty `json:"main"` + Email string `json:"email"` + LowPriorityPagerdutyEndpoint PagerDuty `json:"lowPriority"` + PagerdutyEndpoint PagerDuty `json:"main"` + OpsgenieIntegrations []opsgenie.Integration `json:"opsgenieIntegrations"` } // PagerDuty is used in the ServiceProperties. diff --git a/pkg/opsgenie/types.go b/pkg/opsgenie/types.go index 32fcecbc..ae09ea74 100644 --- a/pkg/opsgenie/types.go +++ b/pkg/opsgenie/types.go @@ -5,13 +5,22 @@ type IntegrationsResponse struct { } type Integration struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - TeamID string `json:"teamId"` - TeamName string `json:"teamName"` - Priority string `json:"priority"` - APIKey string `json:"apiKey"` - Endpoint string `json:"endpoint"` - EnvType string `json:"envType"` + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + TeamID string `json:"teamId"` + TeamName string `json:"teamName"` + Priority string `json:"priority"` + APIKey string `json:"apiKey"` + Endpoint string `json:"endpoint"` + EnvType EnvType `json:"envType"` } + +type EnvType string + +const ( + EnvTypeDev EnvType = "dev" + EnvTypeStaging EnvType = "staging" + EnvTypeProd EnvType = "prod" + EnvTypeGlobal EnvType = "null" // Intentionally a string called "null" as this is the expected result from opsgenie int manager +) diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index 0d0a28cd..7da9a6f2 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -411,9 +411,9 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ tags[k] = v } - notifications, err := c.buildNotifications(serviceData.Spec) + notifications, retriable, err := c.buildNotifications(serviceData.Spec) if err != nil { - return false, err + return retriable, err } metadata := orch_meta.ServiceProperties{ @@ -446,22 +446,6 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ }, } - // OpsGenie team is currently optional - if serviceData.Spec.Metadata.OpsGenie != nil { - resp, retriable, err := c.OpsGenie.GetOrCreateIntegrations(context.TODO(), serviceData.Spec.Metadata.OpsGenie.Team) - if err != nil { - return retriable, err - } - spec, err := createOpsGenieSecretSpec(resp, ns.Name) - if err != nil { - return false, err // Error indicates json marshalling problem - } - retriable, err = c.createOrUpdateSecret(logger, spec) - if err != nil { - return retriable, nil - } - } - conflict, retriable, _, err := c.ConfigMapUpdater.CreateOrUpdate( logger, func(r runtime.Object) error { @@ -478,68 +462,69 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ return false, nil } -// createOpsGenieSecretSpec creates an OpsGenieIntegrations secret object suitable for Secret creation -func createOpsGenieSecretSpec(ogInt *opsgenie.IntegrationsResponse, ns string) (*core_v1.Secret, error) { - jData, err := json.Marshal(ogInt) - if err != nil { - return &core_v1.Secret{}, err - } - - return &core_v1.Secret{ - TypeMeta: meta_v1.TypeMeta{ - Kind: k8s.SecretKind, - APIVersion: core_v1.SchemeGroupVersion.String(), - }, - ObjectMeta: meta_v1.ObjectMeta{ - Name: secretNameOpsGenie, - Namespace: ns, - }, - Data: map[string][]byte{ - secretNameOpsGenie: jData, - }, - Type: core_v1.SecretTypeOpaque, - }, nil -} - func (c *Controller) getServiceData(user auth.OptionalUser, name voyager.ServiceName) (*creator_v1.Service, error) { return c.ServiceCentral.GetService(context.Background(), user, servicecentral.ServiceName(name)) } -func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta.Notifications, error) { +func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta.Notifications, bool /* retriable */, error) { + // Default pagerduty values re-used from Micros config.js + notifications := orch_meta.Notifications{ + Email: spec.EmailAddress(), + PagerdutyEndpoint: orch_meta.PagerDuty{ + Generic: "5d11612f25b840faaf77422edeff9c76", + CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69", + }, + LowPriorityPagerdutyEndpoint: orch_meta.PagerDuty{ + Generic: "5d11612f25b840faaf77422edeff9c76", + CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69", + }, + } + pdEnvMetadata, ok, err := pagerDutyForEnvType(spec.Metadata.PagerDuty, c.ClusterLocation.EnvType) if err != nil { - return nil, errors.Wrap(err, "error building notifications for servicemetadata") + return nil, false, errors.Wrap(err, "error building pagerduty notifications for servicemetadata") } if ok { mainPD, err := convertPagerDuty(pdEnvMetadata.Main) if err != nil { - return nil, errors.Wrap(err, "cannot convert main pagerduty entry") + return nil, false, errors.Wrap(err, "cannot convert main pagerduty entry") } lowPriPD, err := convertPagerDuty(pdEnvMetadata.LowPriority) if err != nil { - return nil, errors.Wrap(err, "cannot convert low priority pagerduty entry") + return nil, false, errors.Wrap(err, "cannot convert low priority pagerduty entry") } - return &orch_meta.Notifications{ + notifications = orch_meta.Notifications{ Email: spec.EmailAddress(), PagerdutyEndpoint: *mainPD, LowPriorityPagerdutyEndpoint: *lowPriPD, - }, nil + } + } + integrations, retriable, err := c.getOpsgenieIntegrations(spec.Metadata.OpsGenie) + if err != nil { + return nil, retriable, errors.Wrap(err, "failed to create opsgenie notifications") + } + ogNotifications, err := buildOpsgenieNotifications(integrations, c.ClusterLocation.EnvType) + if err == nil { + notifications.OpsgenieIntegrations = ogNotifications } - // Default values re-used from Micros config.js - return &orch_meta.Notifications{ - Email: spec.EmailAddress(), - PagerdutyEndpoint: orch_meta.PagerDuty{ - Generic: "5d11612f25b840faaf77422edeff9c76", - CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69", - }, - LowPriorityPagerdutyEndpoint: orch_meta.PagerDuty{ - Generic: "5d11612f25b840faaf77422edeff9c76", - CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69", - }, - }, nil + return ¬ifications, true, nil +} + +func (c *Controller) getOpsgenieIntegrations(metadata *creator_v1.OpsGenieMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { + // Opsgenie is optional + if metadata == nil { + return nil, true, nil + } + + resp, retriable, err := c.OpsGenie.GetOrCreateIntegrations(context.TODO(), metadata.Team) + if err != nil { + return nil, retriable, err + } + + return resp.Integrations, true, nil } func (c *Controller) setupDockerSecret(logger *zap.Logger, namespaceName string) (bool /* retriable */, error) { @@ -711,6 +696,40 @@ func pagerDutyForEnvType(pagerduty *creator_v1.PagerDutyMetadata, envType voyage } } +// buildOpsgenieNotifications filters a given list of integrations by the EnvType +func buildOpsgenieNotifications(integrations []opsgenie.Integration, envType voyager.EnvType) ([]opsgenie.Integration, error) { + if len(integrations) == 0 { + return nil, nil // Not an error as OpsGenie is optional + } + + filtered := make([]opsgenie.Integration, 0, 4) + + for _, integration := range integrations { + + if integration.EnvType == opsgenie.EnvTypeGlobal { + filtered = append(filtered, integration) + continue + } + + switch envType { + case voyager.EnvTypeStaging: + if integration.EnvType == opsgenie.EnvTypeStaging { + filtered = append(filtered, integration) + } + case voyager.EnvTypeProduction: + if integration.EnvType == opsgenie.EnvTypeProd { + filtered = append(filtered, integration) + } + default: + if integration.EnvType == opsgenie.EnvTypeDev { + filtered = append(filtered, integration) + } + } + } + + return filtered, nil +} + func NsServiceLabelIndexFunc(obj interface{}) ([]string, error) { ns := obj.(*core_v1.Namespace) serviceName, err := layers.ServiceNameFromNamespaceLabels(ns.Labels) From 45259db192e338d9355481122174e6649a8f6e48 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 14:32:33 +1100 Subject: [PATCH 04/21] VYGR-391: OpsGenie -> Opsgenie --- cmd/synchronization/app/controller_constructor.go | 4 ++-- cmd/synchronization/app/options.go | 2 +- pkg/apis/creator/v1/types.go | 4 ++-- pkg/opsgenie/client.go | 2 +- pkg/opsgenie/client_test.go | 10 +++++----- pkg/opsgenie/it/client_manual_test.go | 4 ++-- pkg/servicecentral/client.go | 14 +++++++------- pkg/servicecentral/client_test.go | 8 ++++---- pkg/synchronization/controller.go | 13 ++++++------- 9 files changed, 30 insertions(+), 31 deletions(-) diff --git a/cmd/synchronization/app/controller_constructor.go b/cmd/synchronization/app/controller_constructor.go index fc593365..48a7f4ec 100644 --- a/cmd/synchronization/app/controller_constructor.go +++ b/cmd/synchronization/app/controller_constructor.go @@ -80,7 +80,7 @@ func (cc *ControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Context) (* scHTTPClient := util.HTTPClient() scClient := servicecentral.NewServiceCentralClient(config.Logger, scHTTPClient, opts.ASAPClientConfig, opts.Providers.ServiceCentralURL) - // create a client for talking to OpsGenie Integration Manager + // create a client for talking to Opsgenie Integration Manager ogHTTPClient := util.HTTPClient() ogClient := opsgenie.New(config.Logger, ogHTTPClient, opts.ASAPClientConfig, opts.Providers.OpsgenieIntegrationsManagerURL) @@ -138,7 +138,7 @@ func (cc *ControllerConstructor) New(config *ctrl.Config, cctx *ctrl.Context) (* ServiceCentral: servicecentral.NewStore(config.Logger, scClient), ReleaseManagement: releases.NewReleaseManagement(deployinatorHTTPClient, config.Logger), ClusterLocation: opts.Location.ClusterLocation(), - OpsGenie: ogClient, + Opsgenie: ogClient, ConfigMapUpdater: configMapObjectUpdater, RoleBindingUpdater: roleBindingObjectUpdater, diff --git a/cmd/synchronization/app/options.go b/cmd/synchronization/app/options.go index 765c33c2..17b0e064 100644 --- a/cmd/synchronization/app/options.go +++ b/cmd/synchronization/app/options.go @@ -51,7 +51,7 @@ func (p *Providers) UnmarshalJSON(data []byte) error { ogUrl, err := url.Parse(rawProviders.OpsgenieIntegrationsManager) if err != nil { - return errors.Wrap(err, "unable to parse OpsGenie Integrations Manager URL") + return errors.Wrap(err, "unable to parse Opsgenie Integrations Manager URL") } p.OpsgenieIntegrationsManagerURL = ogUrl diff --git a/pkg/apis/creator/v1/types.go b/pkg/apis/creator/v1/types.go index 25bb14f7..5278c2c5 100644 --- a/pkg/apis/creator/v1/types.go +++ b/pkg/apis/creator/v1/types.go @@ -59,7 +59,7 @@ func (ss *ServiceSpec) EmailAddress() string { // +k8s:deepcopy-gen=true type ServiceMetadata struct { PagerDuty *PagerDutyMetadata `json:"pagerDuty,omitempty"` - OpsGenie *OpsGenieMetadata `json:"opsGenie,omitempty"` + Opsgenie *OpsgenieMetadata `json:"opsgenie,omitempty"` Bamboo *BambooMetadata `json:"bamboo,omitempty"` } @@ -101,7 +101,7 @@ type PagerDutyIntegrationMetadata struct { IntegrationKey string `json:"integrationKey,omitempty"` } -type OpsGenieMetadata struct { +type OpsgenieMetadata struct { Team string `json:"team,omitempty"` } diff --git a/pkg/opsgenie/client.go b/pkg/opsgenie/client.go index 0a5eabc7..92d854a1 100644 --- a/pkg/opsgenie/client.go +++ b/pkg/opsgenie/client.go @@ -41,7 +41,7 @@ func New(logger *zap.Logger, httpClient *http.Client, asap pkiutil.ASAP, baseURL } } -// Gets OpsGenie integrations +// Gets Opsgenie integrations // return codes: // - 400: Bad request to Opsgenie // - 401: Unauthorized diff --git a/pkg/opsgenie/client_test.go b/pkg/opsgenie/client_test.go index 204f67a2..1e6831f8 100644 --- a/pkg/opsgenie/client_test.go +++ b/pkg/opsgenie/client_test.go @@ -33,7 +33,7 @@ func TestGetIntegrations(t *testing.T) { defer ogIntManagerMockServer.Close() // when - ogIntManagerClient := mockOpsGenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) + ogIntManagerClient := mockOpsgenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) _, retriable, err := ogIntManagerClient.GetOrCreateIntegrations(context.Background(), teamName) // then @@ -58,7 +58,7 @@ func TestGetIntegrationsTeamNotFound(t *testing.T) { defer ogIntManagerMockServer.Close() // when - ogIntManagerClient := mockOpsGenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) + ogIntManagerClient := mockOpsgenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) _, retriable, err := ogIntManagerClient.GetOrCreateIntegrations(context.Background(), teamName) // then @@ -83,7 +83,7 @@ func TestGetIntegrationsRateLimited(t *testing.T) { defer ogIntManagerMockServer.Close() // when - ogIntManagerClient := mockOpsGenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) + ogIntManagerClient := mockOpsgenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) _, retriable, err := ogIntManagerClient.GetOrCreateIntegrations(context.Background(), teamName) // then @@ -108,7 +108,7 @@ func TestGetIntegrationsInternalServerError(t *testing.T) { defer ogIntManagerMockServer.Close() // when - ogIntManagerClient := mockOpsGenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) + ogIntManagerClient := mockOpsgenieIntegrationManagerClient(t, ogIntManagerMockServer.URL, pkitest.MockASAPClientConfig(t)) _, retriable, err := ogIntManagerClient.GetOrCreateIntegrations(context.Background(), teamName) // then @@ -117,7 +117,7 @@ func TestGetIntegrationsInternalServerError(t *testing.T) { require.True(t, retriable) } -func mockOpsGenieIntegrationManagerClient(t *testing.T, serverMockAddress string, asap pkiutil.ASAP) *Client { +func mockOpsgenieIntegrationManagerClient(t *testing.T, serverMockAddress string, asap pkiutil.ASAP) *Client { opsgenieIntegrationManagerURL, err := url.Parse(serverMockAddress) require.NoError(t, err) httpClient := util.HTTPClient() diff --git a/pkg/opsgenie/it/client_manual_test.go b/pkg/opsgenie/it/client_manual_test.go index 0a862ace..1c210dd0 100644 --- a/pkg/opsgenie/it/client_manual_test.go +++ b/pkg/opsgenie/it/client_manual_test.go @@ -17,7 +17,7 @@ import ( ) const ( - opsGenieIntManURL = "https://micros.prod.atl-paas.net" + opsgenieIntManURL = "https://micros.prod.atl-paas.net" ) // NOTE: THIS WILL CREATE INTEGRATIONS IF NONE EXIST @@ -32,7 +32,7 @@ func TestGetIntegrations(t *testing.T) { require.NoError(t, asapErr) client := util.HTTPClient() - c := opsgenie.New(testLogger, client, asapConfig, parseURL(t, opsGenieIntManURL)) + c := opsgenie.New(testLogger, client, asapConfig, parseURL(t, opsgenieIntManURL)) // Get Service Attributes resp, _, err := c.GetOrCreateIntegrations(ctx, "Platform SRE") diff --git a/pkg/servicecentral/client.go b/pkg/servicecentral/client.go index e48ce01c..104c7dda 100644 --- a/pkg/servicecentral/client.go +++ b/pkg/servicecentral/client.go @@ -300,9 +300,9 @@ func (c *Client) GetService(ctx context.Context, user auth.OptionalUser, service if err != nil { return nil, errors.Wrap(err, "failed to get attributes for service") } - ogTeamAttr, found, err := findOpsGenieTeamServiceAttribute(resp) + ogTeamAttr, found, err := findOpsgenieTeamServiceAttribute(resp) if err != nil { - return nil, errors.Wrap(err, "failed to get OpsGenie attributes for service") + return nil, errors.Wrap(err, "failed to get Opsgenie attributes for service") } if found { @@ -445,19 +445,19 @@ func convertV2ServiceToV1(v2Service V2Service) ServiceDataRead { return service } -func findOpsGenieTeamServiceAttribute(attributes []ServiceAttributeResponse) (ServiceAttribute, bool /*found*/, error) { - const opsGenieSchemaName = "opsgenie" +func findOpsgenieTeamServiceAttribute(attributes []ServiceAttributeResponse) (ServiceAttribute, bool /*found*/, error) { + const opsgenieSchemaName = "opsgenie" count := 0 found := false ogTeamAttr := ServiceAttribute{} for _, attr := range attributes { - if attr.Schema.Name != opsGenieSchemaName { + if attr.Schema.Name != opsgenieSchemaName { continue } team, ok := attr.Value["team"] if !ok { - return ogTeamAttr, found, errors.Errorf("expected to find team name within schema of name %q", opsGenieSchemaName) + return ogTeamAttr, found, errors.Errorf("expected to find team name within schema of name %q", opsgenieSchemaName) } ogTeamAttr = ServiceAttribute{Team: team} @@ -465,7 +465,7 @@ func findOpsGenieTeamServiceAttribute(attributes []ServiceAttributeResponse) (Se count++ } if count > 1 { - return ogTeamAttr, found, errors.New("found more than one OpsGenie service attribute") + return ogTeamAttr, found, errors.New("found more than one Opsgenie service attribute") } return ogTeamAttr, found, nil } diff --git a/pkg/servicecentral/client_test.go b/pkg/servicecentral/client_test.go index 15d99f87..d4f3e4e6 100644 --- a/pkg/servicecentral/client_test.go +++ b/pkg/servicecentral/client_test.go @@ -275,7 +275,7 @@ func TestGetService(t *testing.T) { require.Equal(t, 2, handler.RequestSnapshots.Calls()) } -func TestGetServiceWithOpsGenieAttribute(t *testing.T) { +func TestGetServiceWithOpsgenieAttribute(t *testing.T) { t.Parallel() // given handler := MockHandler(Match( @@ -306,7 +306,7 @@ func TestGetServiceWithOpsGenieAttribute(t *testing.T) { require.Equal(t, "Platform SRE", service.Attributes[0].Team) } -func TestGetServiceWithEmptyOpsGenieAttribute(t *testing.T) { +func TestGetServiceWithEmptyOpsgenieAttribute(t *testing.T) { t.Parallel() // given handler := MockHandler(Match( @@ -337,7 +337,7 @@ func TestGetServiceWithEmptyOpsGenieAttribute(t *testing.T) { require.Equal(t, "", service.Attributes[0].Team) } -func TestGetServiceWithoutOpsGenieAttribute(t *testing.T) { +func TestGetServiceWithoutOpsgenieAttribute(t *testing.T) { t.Parallel() // given handler := MockHandler(Match( @@ -393,7 +393,7 @@ func TestGetServiceWithFailedAttributesCall(t *testing.T) { require.Equal(t, 2, handler.RequestSnapshots.Calls()) } -func TestGetServiceWithMultipleOpsGenieAttribute(t *testing.T) { +func TestGetServiceWithMultipleOpsgenieAttribute(t *testing.T) { t.Parallel() // given handler := MockHandler(Match( diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index 7da9a6f2..5565ae6d 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -66,7 +66,6 @@ const ( releaseManagementSyncAllPeriod = 30 * time.Minute baseDelayProcSec = 15 rmsPollJitterFactor = 1.2 - secretNameOpsGenie = "opsgenie-integrations" ) type ServiceMetadataStore interface { @@ -75,7 +74,7 @@ type ServiceMetadataStore interface { ListModifiedServices(ctx context.Context, user auth.OptionalUser, modifiedSince time.Time) ([]creator_v1.Service, error) } -type OpsGenieIntegrationManagerClient interface { +type OpsgenieIntegrationManagerClient interface { GetOrCreateIntegrations(ctx context.Context, teamName string) (*opsgenie.IntegrationsResponse, bool /* retriable */, error) } @@ -91,7 +90,7 @@ type Controller struct { ServiceCentral ServiceMetadataStore ReleaseManagement releases.ReleaseManagementStore ClusterLocation voyager.ClusterLocation - OpsGenie OpsGenieIntegrationManagerClient + Opsgenie OpsgenieIntegrationManagerClient RoleBindingUpdater updater.ObjectUpdater ConfigMapUpdater updater.ObjectUpdater @@ -501,7 +500,7 @@ func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta LowPriorityPagerdutyEndpoint: *lowPriPD, } } - integrations, retriable, err := c.getOpsgenieIntegrations(spec.Metadata.OpsGenie) + integrations, retriable, err := c.getOpsgenieIntegrations(spec.Metadata.Opsgenie) if err != nil { return nil, retriable, errors.Wrap(err, "failed to create opsgenie notifications") } @@ -513,13 +512,13 @@ func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta return ¬ifications, true, nil } -func (c *Controller) getOpsgenieIntegrations(metadata *creator_v1.OpsGenieMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { +func (c *Controller) getOpsgenieIntegrations(metadata *creator_v1.OpsgenieMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { // Opsgenie is optional if metadata == nil { return nil, true, nil } - resp, retriable, err := c.OpsGenie.GetOrCreateIntegrations(context.TODO(), metadata.Team) + resp, retriable, err := c.Opsgenie.GetOrCreateIntegrations(context.TODO(), metadata.Team) if err != nil { return nil, retriable, err } @@ -699,7 +698,7 @@ func pagerDutyForEnvType(pagerduty *creator_v1.PagerDutyMetadata, envType voyage // buildOpsgenieNotifications filters a given list of integrations by the EnvType func buildOpsgenieNotifications(integrations []opsgenie.Integration, envType voyager.EnvType) ([]opsgenie.Integration, error) { if len(integrations) == 0 { - return nil, nil // Not an error as OpsGenie is optional + return nil, nil // Not an error as Opsgenie is optional } filtered := make([]opsgenie.Integration, 0, 4) From dd631152e4f7990475420a74c5463f16a4007387 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 16:00:05 +1100 Subject: [PATCH 05/21] VYGR-391: Set Opsgenie metadata in serviceDataToService --- pkg/servicecentral/client.go | 33 +++++++++++++------------------ pkg/servicecentral/metadata.go | 10 ++++++++++ pkg/servicecentral/store.go | 5 +++++ pkg/servicecentral/types.go | 10 +++++++--- pkg/synchronization/controller.go | 1 + 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pkg/servicecentral/client.go b/pkg/servicecentral/client.go index 104c7dda..fa2f2e11 100644 --- a/pkg/servicecentral/client.go +++ b/pkg/servicecentral/client.go @@ -300,14 +300,8 @@ func (c *Client) GetService(ctx context.Context, user auth.OptionalUser, service if err != nil { return nil, errors.Wrap(err, "failed to get attributes for service") } - ogTeamAttr, found, err := findOpsgenieTeamServiceAttribute(resp) - if err != nil { - return nil, errors.Wrap(err, "failed to get Opsgenie attributes for service") - } - if found { - service.Attributes = append(service.Attributes, ogTeamAttr) - } + service.Attributes = resp.Attributes return service, nil } @@ -344,7 +338,8 @@ func (c *Client) DeleteService(ctx context.Context, user auth.User, serviceUUID } // GetServiceAttributes queries service central for the attributes of a given service. Can return an empty array if no attributes were found -func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUser, serviceUUID string) ([]ServiceAttributeResponse, error) { +func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUser, serviceUUID string) (ServiceAttributeResponse, error) { + var svcAttrResp ServiceAttributeResponse req, err := c.rm.NewRequest( pkiutil.AuthenticateWithASAP(c.asap, asapAudience, user.NameOrElse(noUser)), restclient.Method(http.MethodGet), @@ -353,32 +348,31 @@ func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUse restclient.Header("Accept", "application/json"), ) if err != nil { - return nil, errors.Wrap(err, "failed to create get service attributes request") + return svcAttrResp, errors.Wrap(err, "failed to create get service attributes request") } response, err := c.httpClient.Do(req) if err != nil { - return nil, errors.Wrap(err, "failed to execute get service attributes request") + return svcAttrResp, errors.Wrap(err, "failed to execute get service attributes request") } defer util.CloseSilently(response.Body) respBody, err := ioutil.ReadAll(response.Body) if err != nil { - return nil, errors.Wrap(err, "failed to read response body") + return svcAttrResp, errors.Wrap(err, "failed to read response body") } if response.StatusCode != http.StatusOK { message := fmt.Sprintf("failed to get attributes for service %q. Response: %s", serviceUUID, respBody) - return nil, clientError(response.StatusCode, message) + return svcAttrResp, clientError(response.StatusCode, message) } - var parsedBody []ServiceAttributeResponse - err = json.Unmarshal(respBody, &parsedBody) + err = json.Unmarshal(respBody, &svcAttrResp) if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal response body") + return svcAttrResp, errors.Wrap(err, "failed to unmarshal response body") } - return parsedBody, nil + return svcAttrResp, nil } func clientError(statusCode int, message string) error { @@ -445,11 +439,12 @@ func convertV2ServiceToV1(v2Service V2Service) ServiceDataRead { return service } -func findOpsgenieTeamServiceAttribute(attributes []ServiceAttributeResponse) (ServiceAttribute, bool /*found*/, error) { +// findOpsgenieAttribute searches a given list of ServiceAttributes for a single OpsgenieAttribute +func findOpsgenieAttribute(attributes []ServiceAttribute) (OpsgenieAttribute, bool /*found*/, error) { const opsgenieSchemaName = "opsgenie" count := 0 found := false - ogTeamAttr := ServiceAttribute{} + ogTeamAttr := OpsgenieAttribute{} for _, attr := range attributes { if attr.Schema.Name != opsgenieSchemaName { continue @@ -460,7 +455,7 @@ func findOpsgenieTeamServiceAttribute(attributes []ServiceAttributeResponse) (Se return ogTeamAttr, found, errors.Errorf("expected to find team name within schema of name %q", opsgenieSchemaName) } - ogTeamAttr = ServiceAttribute{Team: team} + ogTeamAttr = OpsgenieAttribute{Team: team} found = true count++ } diff --git a/pkg/servicecentral/metadata.go b/pkg/servicecentral/metadata.go index de0e21a1..5b23527b 100644 --- a/pkg/servicecentral/metadata.go +++ b/pkg/servicecentral/metadata.go @@ -29,6 +29,16 @@ func SetPagerDutyMetadata(serviceCentralData *ServiceDataWrite, m *creator_v1.Pa return setMetadata(serviceCentralData, PagerDutyMetadataKey, m) } +// GetOpsGenieAttribute reads the Opsgenie team attribute out of a service +func GetOpsGenieAttribute(serviceCentralData *ServiceDataRead) (*creator_v1.OpsgenieMetadata, error) { + attributes := serviceCentralData.Attributes + ogTeamAttr, found, err := findOpsgenieAttribute(attributes) + if err != nil || !found { + return nil, err + } + return &creator_v1.OpsgenieMetadata{Team: ogTeamAttr.Team}, nil +} + // GetBambooMetadata reads the allowed builds metadata out of a service func GetBambooMetadata(serviceCentralData *ServiceDataWrite) (*creator_v1.BambooMetadata, error) { var m creator_v1.BambooMetadata diff --git a/pkg/servicecentral/store.go b/pkg/servicecentral/store.go index f7263e12..4990f6b2 100644 --- a/pkg/servicecentral/store.go +++ b/pkg/servicecentral/store.go @@ -301,6 +301,11 @@ func serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { service.Spec.Metadata.PagerDuty = pagerDutyMetadata } + ogMetadata, _ := GetOpsGenieAttribute(data) + if ogMetadata != nil { + service.Spec.Metadata.Opsgenie = ogMetadata + } + bambooMetadata, err := GetBambooMetadata(&data.ServiceDataWrite) if err != nil { return nil, err diff --git a/pkg/servicecentral/types.go b/pkg/servicecentral/types.go index a721fbe1..40615583 100644 --- a/pkg/servicecentral/types.go +++ b/pkg/servicecentral/types.go @@ -43,11 +43,11 @@ type ServiceDataRead struct { Compliance *ServiceComplianceConf `json:"compliance,omitempty"` } -type ServiceAttribute struct { - Team string `json:"team,omitempty"` +type ServiceAttributeResponse struct { + Attributes []ServiceAttribute } -type ServiceAttributeResponse struct { +type ServiceAttribute struct { ID int `json:"id"` Service ServiceAttributeService `json:"service"` Schema ServiceAttributeSchema `json:"schema"` @@ -70,6 +70,10 @@ type ServiceAttributeSchema struct { Name string `json:"name"` } +type OpsgenieAttribute struct { + Team string `json:"team,omitempty"` +} + // ServiceComplianceConf includes all service compliance related data type ServiceComplianceConf struct { // using prgb_control instead of prgbControl here to match the response field name diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index 5565ae6d..b3b3c2a0 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -512,6 +512,7 @@ func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta return ¬ifications, true, nil } +// getOpsgenieIntegrations attemps to get Opsgenie integrations from the opsgenie integration manager func (c *Controller) getOpsgenieIntegrations(metadata *creator_v1.OpsgenieMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { // Opsgenie is optional if metadata == nil { From 46e0cfbe5aaf51b67b0097b7693c461d1845e446 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 17:32:09 +1100 Subject: [PATCH 06/21] VYGR-391: Use all service attributes in service object --- pkg/servicecentral/client.go | 6 +-- pkg/servicecentral/client_test.go | 35 ++-------------- ...vice_attributes_multiple_opsgenie.rsp.json | 42 ------------------- pkg/servicecentral/types.go | 4 -- pkg/synchronization/controller.go | 8 ++-- 5 files changed, 12 insertions(+), 83 deletions(-) delete mode 100644 pkg/servicecentral/testdata/get_service_attributes_multiple_opsgenie.rsp.json diff --git a/pkg/servicecentral/client.go b/pkg/servicecentral/client.go index fa2f2e11..d8b761dc 100644 --- a/pkg/servicecentral/client.go +++ b/pkg/servicecentral/client.go @@ -301,7 +301,7 @@ func (c *Client) GetService(ctx context.Context, user auth.OptionalUser, service return nil, errors.Wrap(err, "failed to get attributes for service") } - service.Attributes = resp.Attributes + service.Attributes = resp return service, nil } @@ -338,8 +338,8 @@ func (c *Client) DeleteService(ctx context.Context, user auth.User, serviceUUID } // GetServiceAttributes queries service central for the attributes of a given service. Can return an empty array if no attributes were found -func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUser, serviceUUID string) (ServiceAttributeResponse, error) { - var svcAttrResp ServiceAttributeResponse +func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUser, serviceUUID string) ([]ServiceAttribute, error) { + var svcAttrResp []ServiceAttribute req, err := c.rm.NewRequest( pkiutil.AuthenticateWithASAP(c.asap, asapAudience, user.NameOrElse(noUser)), restclient.Method(http.MethodGet), diff --git a/pkg/servicecentral/client_test.go b/pkg/servicecentral/client_test.go index d4f3e4e6..90437cdd 100644 --- a/pkg/servicecentral/client_test.go +++ b/pkg/servicecentral/client_test.go @@ -302,8 +302,9 @@ func TestGetServiceWithOpsgenieAttribute(t *testing.T) { require.NoError(t, err) require.Equal(t, 2, handler.RequestSnapshots.Calls()) - require.Equal(t, 1, len(service.Attributes)) - require.Equal(t, "Platform SRE", service.Attributes[0].Team) + require.Equal(t, 2, len(service.Attributes)) + require.Equal(t, "Platform SRE", service.Attributes[0].Value["team"]) + require.Equal(t, "Fake News", service.Attributes[1].Value["team"]) } func TestGetServiceWithEmptyOpsgenieAttribute(t *testing.T) { @@ -334,7 +335,7 @@ func TestGetServiceWithEmptyOpsgenieAttribute(t *testing.T) { require.Equal(t, 2, handler.RequestSnapshots.Calls()) require.Equal(t, 1, len(service.Attributes)) - require.Equal(t, "", service.Attributes[0].Team) + require.Equal(t, "", service.Attributes[0].Value["Team"]) } func TestGetServiceWithoutOpsgenieAttribute(t *testing.T) { @@ -393,34 +394,6 @@ func TestGetServiceWithFailedAttributesCall(t *testing.T) { require.Equal(t, 2, handler.RequestSnapshots.Calls()) } -func TestGetServiceWithMultipleOpsgenieAttribute(t *testing.T) { - t.Parallel() - // given - handler := MockHandler(Match( - Method(http.MethodGet), - Path(fmt.Sprintf("%s/%s", v1ServicesPath, testServiceName)), - ).Respond( - Status(http.StatusOK), - JSONFromFile(t, "get_service.rsp.json"), - ), - Match( - Method(http.MethodGet), - Path(fmt.Sprintf("%s/%s/attributes", v2ServicesPath, testServiceName)), - ).Respond( - Status(http.StatusOK), - JSONFromFile(t, "get_service_attributes_multiple_opsgenie.rsp.json"), - )) - serviceCentralServerMock := httptest.NewServer(handler) - defer serviceCentralServerMock.Close() - // when - serviceCentralClient := testServiceCentralClient(t, serviceCentralServerMock.URL, pkitest.MockASAPClientConfig(t)) - _, err := serviceCentralClient.GetService(context.Background(), optionalUser, string(testServiceName)) - - // then - require.Error(t, err) - require.Equal(t, 2, handler.RequestSnapshots.Calls()) -} - func testServiceCentralClient(t *testing.T, serviceCentralServerMockAddress string, asap pkiutil.ASAP) *Client { serviceCentralURL, err := url.Parse(serviceCentralServerMockAddress) require.NoError(t, err) diff --git a/pkg/servicecentral/testdata/get_service_attributes_multiple_opsgenie.rsp.json b/pkg/servicecentral/testdata/get_service_attributes_multiple_opsgenie.rsp.json deleted file mode 100644 index f06c583f..00000000 --- a/pkg/servicecentral/testdata/get_service_attributes_multiple_opsgenie.rsp.json +++ /dev/null @@ -1,42 +0,0 @@ -[ - { - "createdBy": "fcobb", - "createdOn": "2019-01-29T04:08:15.810106Z", - "id": 23, - "modifiedBy": "fcobb", - "modifiedOn": "2019-01-29T04:08:15.810104Z", - "schema": { - "id": 6, - "name": "opsgenie", - "ref": "/api/v2/schemas/attributes/6" - }, - "service": { - "name": "slime", - "ref": "/api/v2/services/175096a2-6b18-4df4-8bd0-65fe6e0c56b4", - "uuid": "175096a2-6b18-4df4-8bd0-65fe6e0c56b4" - }, - "value": { - "team": "Platform SRE" - } - }, - { - "createdBy": "fcobb", - "createdOn": "2019-01-29T04:08:15.810106Z", - "id": 23, - "modifiedBy": "fcobb", - "modifiedOn": "2019-01-29T04:08:15.810104Z", - "schema": { - "id": 6, - "name": "opsgenie", - "ref": "/api/v2/schemas/attributes/6" - }, - "service": { - "name": "slime", - "ref": "/api/v2/services/175096a2-6b18-4df4-8bd0-65fe6e0c56b4", - "uuid": "175096a2-6b18-4df4-8bd0-65fe6e0c56b4" - }, - "value": { - "team": "Voyager" - } - } -] diff --git a/pkg/servicecentral/types.go b/pkg/servicecentral/types.go index 40615583..e42e02dc 100644 --- a/pkg/servicecentral/types.go +++ b/pkg/servicecentral/types.go @@ -43,10 +43,6 @@ type ServiceDataRead struct { Compliance *ServiceComplianceConf `json:"compliance,omitempty"` } -type ServiceAttributeResponse struct { - Attributes []ServiceAttribute -} - type ServiceAttribute struct { ID int `json:"id"` Service ServiceAttributeService `json:"service"` diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index b3b3c2a0..04e261c4 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -410,7 +410,7 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ tags[k] = v } - notifications, retriable, err := c.buildNotifications(serviceData.Spec) + notifications, retriable, err := c.buildNotifications(logger, serviceData.Spec) if err != nil { return retriable, err } @@ -505,14 +505,16 @@ func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta return nil, retriable, errors.Wrap(err, "failed to create opsgenie notifications") } ogNotifications, err := buildOpsgenieNotifications(integrations, c.ClusterLocation.EnvType) - if err == nil { + if err != nil { + logger.Error("failed to build Opsgenie Notifications") + } else { notifications.OpsgenieIntegrations = ogNotifications } return ¬ifications, true, nil } -// getOpsgenieIntegrations attemps to get Opsgenie integrations from the opsgenie integration manager +// getOpsgenieIntegrations attempts to get Opsgenie integrations from the opsgenie integration manager func (c *Controller) getOpsgenieIntegrations(metadata *creator_v1.OpsgenieMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { // Opsgenie is optional if metadata == nil { From b7e6865e97c5833c22245675870c86f58bbca161 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 17:32:41 +1100 Subject: [PATCH 07/21] VYGR-391: Use constants for default pagerduty notifications --- pkg/synchronization/controller.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index 04e261c4..f60de33f 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -55,7 +55,9 @@ const ( // see https://github.com/jtblin/kube2iam#namespace-restrictions allowedRolesAnnotation = "iam.amazonaws.com/allowed-roles" - maxSyncWorkers = 10 + maxSyncWorkers = 10 + defaultPagerdutyGeneric = "5d11612f25b840faaf77422edeff9c76" + defaultPagerdutyCloudwatch = "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69" ) const ( @@ -465,17 +467,17 @@ func (c *Controller) getServiceData(user auth.OptionalUser, name voyager.Service return c.ServiceCentral.GetService(context.Background(), user, servicecentral.ServiceName(name)) } -func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta.Notifications, bool /* retriable */, error) { +func (c *Controller) buildNotifications(logger *zap.Logger, spec creator_v1.ServiceSpec) (*orch_meta.Notifications, bool /* retriable */, error) { // Default pagerduty values re-used from Micros config.js notifications := orch_meta.Notifications{ Email: spec.EmailAddress(), PagerdutyEndpoint: orch_meta.PagerDuty{ - Generic: "5d11612f25b840faaf77422edeff9c76", - CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69", + Generic: defaultPagerdutyGeneric, + CloudWatch: defaultPagerdutyCloudwatch, }, LowPriorityPagerdutyEndpoint: orch_meta.PagerDuty{ - Generic: "5d11612f25b840faaf77422edeff9c76", - CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/124e0f010f214a9b9f30b768e7b18e69", + Generic: defaultPagerdutyGeneric, + CloudWatch: defaultPagerdutyCloudwatch, }, } From cd3ba39376dfdc343fdfba87b7e05f5995ea2b66 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 17:36:40 +1100 Subject: [PATCH 08/21] VYGR-391: Override pagerduty defaults instead of new object --- pkg/synchronization/controller.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index f60de33f..bec019de 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -496,11 +496,8 @@ func (c *Controller) buildNotifications(logger *zap.Logger, spec creator_v1.Serv return nil, false, errors.Wrap(err, "cannot convert low priority pagerduty entry") } - notifications = orch_meta.Notifications{ - Email: spec.EmailAddress(), - PagerdutyEndpoint: *mainPD, - LowPriorityPagerdutyEndpoint: *lowPriPD, - } + notifications.PagerdutyEndpoint = *mainPD + notifications.LowPriorityPagerdutyEndpoint = *lowPriPD } integrations, retriable, err := c.getOpsgenieIntegrations(spec.Metadata.Opsgenie) if err != nil { From 9022baf4147895f329f91e4b0ecd0fdd2460dc4e Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Fri, 15 Feb 2019 18:11:52 +1100 Subject: [PATCH 09/21] VYGR-391: Pull build opsgenie notifcations into separate function --- pkg/synchronization/controller.go | 32 +++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index bec019de..aafe5f8e 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -412,7 +412,7 @@ func (c *Controller) createOrUpdateServiceMetadata(logger *zap.Logger, ns *core_ tags[k] = v } - notifications, retriable, err := c.buildNotifications(logger, serviceData.Spec) + notifications, retriable, err := c.buildNotifications(serviceData.Spec) if err != nil { return retriable, err } @@ -467,7 +467,7 @@ func (c *Controller) getServiceData(user auth.OptionalUser, name voyager.Service return c.ServiceCentral.GetService(context.Background(), user, servicecentral.ServiceName(name)) } -func (c *Controller) buildNotifications(logger *zap.Logger, spec creator_v1.ServiceSpec) (*orch_meta.Notifications, bool /* retriable */, error) { +func (c *Controller) buildNotifications(spec creator_v1.ServiceSpec) (*orch_meta.Notifications, bool /* retriable */, error) { // Default pagerduty values re-used from Micros config.js notifications := orch_meta.Notifications{ Email: spec.EmailAddress(), @@ -499,20 +499,28 @@ func (c *Controller) buildNotifications(logger *zap.Logger, spec creator_v1.Serv notifications.PagerdutyEndpoint = *mainPD notifications.LowPriorityPagerdutyEndpoint = *lowPriPD } - integrations, retriable, err := c.getOpsgenieIntegrations(spec.Metadata.Opsgenie) - if err != nil { - return nil, retriable, errors.Wrap(err, "failed to create opsgenie notifications") - } - ogNotifications, err := buildOpsgenieNotifications(integrations, c.ClusterLocation.EnvType) + + ogInts, retriable, err := c.buildOpsgenieNotifications(spec.Metadata) if err != nil { - logger.Error("failed to build Opsgenie Notifications") - } else { - notifications.OpsgenieIntegrations = ogNotifications + return nil, retriable, err } + notifications.OpsgenieIntegrations = ogInts return ¬ifications, true, nil } +func (c *Controller) buildOpsgenieNotifications(metadata creator_v1.ServiceMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { + ogInts, retriable, err := c.getOpsgenieIntegrations(metadata.Opsgenie) + if err != nil { + return nil, retriable, errors.Wrap(err, "failed to get Opsgenie notifications") + } + envOgInts, err := filterOpsgenieIntegrationsByEnv(ogInts, c.ClusterLocation.EnvType) + if err != nil { + return nil, false, errors.Wrap(err, "failed to build Opsgenie notifications") + } + return envOgInts, true, nil +} + // getOpsgenieIntegrations attempts to get Opsgenie integrations from the opsgenie integration manager func (c *Controller) getOpsgenieIntegrations(metadata *creator_v1.OpsgenieMetadata) ([]opsgenie.Integration, bool /* retriable */, error) { // Opsgenie is optional @@ -697,8 +705,8 @@ func pagerDutyForEnvType(pagerduty *creator_v1.PagerDutyMetadata, envType voyage } } -// buildOpsgenieNotifications filters a given list of integrations by the EnvType -func buildOpsgenieNotifications(integrations []opsgenie.Integration, envType voyager.EnvType) ([]opsgenie.Integration, error) { +// filterOpsgenieIntegrationsByEnv filters a given list of integrations by the EnvType +func filterOpsgenieIntegrationsByEnv(integrations []opsgenie.Integration, envType voyager.EnvType) ([]opsgenie.Integration, error) { if len(integrations) == 0 { return nil, nil // Not an error as Opsgenie is optional } From 4844d763df64bd6e457b6377ca122a15cccffed8 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 11:38:42 +1100 Subject: [PATCH 10/21] VYGR-391: Comment on optional opsgenie metadata --- pkg/servicecentral/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/servicecentral/store.go b/pkg/servicecentral/store.go index 4b6d6bca..0aa76532 100644 --- a/pkg/servicecentral/store.go +++ b/pkg/servicecentral/store.go @@ -306,7 +306,7 @@ func serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { service.Spec.Metadata.PagerDuty = pagerDutyMetadata } - ogMetadata, _ := GetOpsGenieAttribute(data) + ogMetadata, _ := GetOpsGenieAttribute(data) // Error ignored as Opsgenie team is optional if ogMetadata != nil { service.Spec.Metadata.Opsgenie = ogMetadata } From 533eb33109d3f3168945db89beca19f7a51e632b Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 13:16:09 +1100 Subject: [PATCH 11/21] VYGR-391: Log when unable to get Opsgenie attribute in SC store --- pkg/servicecentral/store.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/servicecentral/store.go b/pkg/servicecentral/store.go index 0aa76532..50906a4c 100644 --- a/pkg/servicecentral/store.go +++ b/pkg/servicecentral/store.go @@ -266,7 +266,7 @@ func prepareServiceToWrite(existingData ServiceDataRead, service *creator_v1.Ser return &sd, nil } -func serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { +func (c *Store) serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { var serviceUID string if data.ServiceUUID != nil { serviceUID = *data.ServiceUUID @@ -306,7 +306,10 @@ func serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { service.Spec.Metadata.PagerDuty = pagerDutyMetadata } - ogMetadata, _ := GetOpsGenieAttribute(data) // Error ignored as Opsgenie team is optional + ogMetadata, err := GetOpsGenieAttribute(data) // Error ignored as Opsgenie team is optional + if err != nil { + c.logger.Info("unable to get Opsgenie attribute - ignoring", zap.Error(err)) + } if ogMetadata != nil { service.Spec.Metadata.Opsgenie = ogMetadata } From ce4b544301f60df60ef1e5ec75352622d587769a Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 13:19:37 +1100 Subject: [PATCH 12/21] VYGR-391: Error when Opsgenie filtering encounters unexpected envType --- pkg/synchronization/controller.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/synchronization/controller.go b/pkg/synchronization/controller.go index aafe5f8e..25f2bcdb 100644 --- a/pkg/synchronization/controller.go +++ b/pkg/synchronization/controller.go @@ -729,10 +729,12 @@ func filterOpsgenieIntegrationsByEnv(integrations []opsgenie.Integration, envTyp if integration.EnvType == opsgenie.EnvTypeProd { filtered = append(filtered, integration) } - default: + case voyager.EnvTypeDev: if integration.EnvType == opsgenie.EnvTypeDev { filtered = append(filtered, integration) } + default: + return nil, errors.Errorf("unexpected envType %q when filtering Opsgenie integrations", envType) } } From 9368162ffb3d7e9d711ab171ae17e3ac2c94acc0 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 14:14:40 +1100 Subject: [PATCH 13/21] VYGR-391: Return error when we failed to get Opsgenie attr --- pkg/servicecentral/store.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/servicecentral/store.go b/pkg/servicecentral/store.go index 50906a4c..c9273ebd 100644 --- a/pkg/servicecentral/store.go +++ b/pkg/servicecentral/store.go @@ -306,9 +306,9 @@ func (c *Store) serviceDataToService(data *ServiceDataRead) (*creator_v1.Service service.Spec.Metadata.PagerDuty = pagerDutyMetadata } - ogMetadata, err := GetOpsGenieAttribute(data) // Error ignored as Opsgenie team is optional + ogMetadata, err := GetOpsGenieAttribute(data) if err != nil { - c.logger.Info("unable to get Opsgenie attribute - ignoring", zap.Error(err)) + return nil, err } if ogMetadata != nil { service.Spec.Metadata.Opsgenie = ogMetadata From d13b74282f50fe64bda198077e5650c5a4d7d54e Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 14:18:49 +1100 Subject: [PATCH 14/21] VYGR-391: Remove struct method usage for serviceDataToService --- pkg/servicecentral/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/servicecentral/store.go b/pkg/servicecentral/store.go index c9273ebd..7e65384e 100644 --- a/pkg/servicecentral/store.go +++ b/pkg/servicecentral/store.go @@ -266,7 +266,7 @@ func prepareServiceToWrite(existingData ServiceDataRead, service *creator_v1.Ser return &sd, nil } -func (c *Store) serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { +func serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { var serviceUID string if data.ServiceUUID != nil { serviceUID = *data.ServiceUUID From c811811617a426b965cd77a50e42cffbde53c31e Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 15:07:31 +1100 Subject: [PATCH 15/21] VYGR-391: Reorder url errors to return error earlier --- cmd/synchronization/app/options.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/synchronization/app/options.go b/cmd/synchronization/app/options.go index 17b0e064..671d625a 100644 --- a/cmd/synchronization/app/options.go +++ b/cmd/synchronization/app/options.go @@ -39,15 +39,16 @@ func (p *Providers) UnmarshalJSON(data []byte) error { } scURL, err := url.Parse(rawProviders.ServiceCentral) - p.ServiceCentralURL = scURL if err != nil { return errors.Wrap(err, "unable to parse Service Central URL") } + p.ServiceCentralURL = scURL + depURL, err := url.Parse(rawProviders.Deployinator) - p.DeployinatorURL = depURL if err != nil { return errors.Wrap(err, "unable to parse Deployinator URL") } + p.DeployinatorURL = depURL ogUrl, err := url.Parse(rawProviders.OpsgenieIntegrationsManager) if err != nil { From 0271befc65095d2e255e5dd5fba0efd7d1fc2615 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 15:16:12 +1100 Subject: [PATCH 16/21] VYGR-391: Return nil for error cases in GetServiceAttributes --- pkg/servicecentral/client.go | 13 ++++++------- pkg/servicecentral/metadata.go | 4 ++-- pkg/servicecentral/store.go | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/servicecentral/client.go b/pkg/servicecentral/client.go index d8b761dc..c4cd28ff 100644 --- a/pkg/servicecentral/client.go +++ b/pkg/servicecentral/client.go @@ -339,7 +339,6 @@ func (c *Client) DeleteService(ctx context.Context, user auth.User, serviceUUID // GetServiceAttributes queries service central for the attributes of a given service. Can return an empty array if no attributes were found func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUser, serviceUUID string) ([]ServiceAttribute, error) { - var svcAttrResp []ServiceAttribute req, err := c.rm.NewRequest( pkiutil.AuthenticateWithASAP(c.asap, asapAudience, user.NameOrElse(noUser)), restclient.Method(http.MethodGet), @@ -348,28 +347,28 @@ func (c *Client) GetServiceAttributes(ctx context.Context, user auth.OptionalUse restclient.Header("Accept", "application/json"), ) if err != nil { - return svcAttrResp, errors.Wrap(err, "failed to create get service attributes request") + return nil, errors.Wrap(err, "failed to create get service attributes request") } response, err := c.httpClient.Do(req) if err != nil { - return svcAttrResp, errors.Wrap(err, "failed to execute get service attributes request") + return nil, errors.Wrap(err, "failed to execute get service attributes request") } defer util.CloseSilently(response.Body) respBody, err := ioutil.ReadAll(response.Body) if err != nil { - return svcAttrResp, errors.Wrap(err, "failed to read response body") + return nil, errors.Wrap(err, "failed to read response body") } if response.StatusCode != http.StatusOK { message := fmt.Sprintf("failed to get attributes for service %q. Response: %s", serviceUUID, respBody) - return svcAttrResp, clientError(response.StatusCode, message) + return nil, clientError(response.StatusCode, message) } - + var svcAttrResp []ServiceAttribute err = json.Unmarshal(respBody, &svcAttrResp) if err != nil { - return svcAttrResp, errors.Wrap(err, "failed to unmarshal response body") + return nil, errors.Wrap(err, "failed to unmarshal response body") } return svcAttrResp, nil diff --git a/pkg/servicecentral/metadata.go b/pkg/servicecentral/metadata.go index 5b23527b..5fabe30a 100644 --- a/pkg/servicecentral/metadata.go +++ b/pkg/servicecentral/metadata.go @@ -29,8 +29,8 @@ func SetPagerDutyMetadata(serviceCentralData *ServiceDataWrite, m *creator_v1.Pa return setMetadata(serviceCentralData, PagerDutyMetadataKey, m) } -// GetOpsGenieAttribute reads the Opsgenie team attribute out of a service -func GetOpsGenieAttribute(serviceCentralData *ServiceDataRead) (*creator_v1.OpsgenieMetadata, error) { +// GetOpsgenieAttribute reads the Opsgenie team attribute out of a service +func GetOpsgenieAttribute(serviceCentralData *ServiceDataRead) (*creator_v1.OpsgenieMetadata, error) { attributes := serviceCentralData.Attributes ogTeamAttr, found, err := findOpsgenieAttribute(attributes) if err != nil || !found { diff --git a/pkg/servicecentral/store.go b/pkg/servicecentral/store.go index 7e65384e..197f37ab 100644 --- a/pkg/servicecentral/store.go +++ b/pkg/servicecentral/store.go @@ -306,7 +306,7 @@ func serviceDataToService(data *ServiceDataRead) (*creator_v1.Service, error) { service.Spec.Metadata.PagerDuty = pagerDutyMetadata } - ogMetadata, err := GetOpsGenieAttribute(data) + ogMetadata, err := GetOpsgenieAttribute(data) if err != nil { return nil, err } From 41066cbeaabbdb8a54aa2d5bb3f2517e1b693253 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Mon, 18 Feb 2019 16:36:39 +1100 Subject: [PATCH 17/21] VYGR-391: Add unit tests for opsgenie buildNotifications --- pkg/synchronization/controller_test.go | 118 +++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/pkg/synchronization/controller_test.go b/pkg/synchronization/controller_test.go index de0eb2cc..d7839271 100644 --- a/pkg/synchronization/controller_test.go +++ b/pkg/synchronization/controller_test.go @@ -18,6 +18,7 @@ import ( "github.com/atlassian/voyager/pkg/k8s" k8s_testing "github.com/atlassian/voyager/pkg/k8s/testing" "github.com/atlassian/voyager/pkg/k8s/updater" + "github.com/atlassian/voyager/pkg/opsgenie" "github.com/atlassian/voyager/pkg/pagerduty" "github.com/atlassian/voyager/pkg/releases" "github.com/atlassian/voyager/pkg/servicecentral" @@ -72,6 +73,15 @@ func (m *fakeServiceCentral) ListModifiedServices(ctx context.Context, user auth return args.Get(0).([]creator_v1.Service), args.Error(1) } +type fakeOpsgenie struct { + mock.Mock +} + +func (m *fakeOpsgenie) GetOrCreateIntegrations(ctx context.Context, teamName string) (*opsgenie.IntegrationsResponse, bool /* retriable */, error) { + args := m.Called(ctx, teamName) + return args.Get(0).(*opsgenie.IntegrationsResponse), args.Bool(1), args.Error(2) +} + type fakeReleaseManagement struct { serviceName voyager.ServiceName serviceNames []voyager.ServiceName @@ -1272,6 +1282,111 @@ func TestGenerateIamRoleGlob(t *testing.T) { } } +func TestOpsGenieWhenIntegrationManagerFails(t *testing.T) { + t.Parallel() + ns := &core_v1.Namespace{ + TypeMeta: meta_v1.TypeMeta{ + Kind: k8s.NamespaceKind, + APIVersion: core_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: namespaceName, + Labels: map[string]string{ + voyager.ServiceNameLabel: serviceName, + }, + }, + } + + tc := testCase{ + serviceName: serviceNameVoy, + ns: ns, + mainClientObjects: []runtime.Object{ns, existingDefaultDockerSecret()}, + test: func(t *testing.T, cntrlr *Controller, ctx *ctrl.ProcessContext, tc *testCase) { + service := &creator_v1.Service{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: serviceName, + }, + Spec: creator_v1.ServiceSpec{ + ResourceOwner: "somebody", + BusinessUnit: "the unit", + LoggingID: "some-logging-id", + Metadata: creator_v1.ServiceMetadata{ + PagerDuty: &creator_v1.PagerDutyMetadata{}, + Opsgenie: &creator_v1.OpsgenieMetadata{Team: "Platform SRE"}, + }, + SSAMContainerName: "some-ssam-container", + ResourceTags: map[voyager.Tag]string{ + "foo": "bar", + "baz": "blah", + }, + }, + } + tc.scFake.On("GetService", mock.Anything, auth.NoUser(), serviceNameSc).Return(service, nil) + + var nilResp *opsgenie.IntegrationsResponse + // Return error when calling Opsgenie Integration Manager + tc.ogFake.On("GetOrCreateIntegrations", mock.Anything, mock.Anything).Return(nilResp, true, errors.New("some error")) + + _, err := cntrlr.Process(ctx) + require.Error(t, err) + }, + } + tc.run(t) +} + +func TestOpsGenieWhenNoTeam(t *testing.T) { + t.Parallel() + ns := &core_v1.Namespace{ + TypeMeta: meta_v1.TypeMeta{ + Kind: k8s.NamespaceKind, + APIVersion: core_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: namespaceName, + Labels: map[string]string{ + voyager.ServiceNameLabel: serviceName, + }, + }, + } + + tc := testCase{ + serviceName: serviceNameVoy, + ns: ns, + mainClientObjects: []runtime.Object{ns, existingDefaultDockerSecret()}, + test: func(t *testing.T, cntrlr *Controller, ctx *ctrl.ProcessContext, tc *testCase) { + service := &creator_v1.Service{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: serviceName, + }, + Spec: creator_v1.ServiceSpec{ + ResourceOwner: "somebody", + BusinessUnit: "the unit", + LoggingID: "some-logging-id", + Metadata: creator_v1.ServiceMetadata{ + PagerDuty: &creator_v1.PagerDutyMetadata{}, + }, + SSAMContainerName: "some-ssam-container", + ResourceTags: map[voyager.Tag]string{ + "foo": "bar", + "baz": "blah", + }, + }, + } + tc.scFake.On("GetService", mock.Anything, auth.NoUser(), serviceNameSc).Return(service, nil) + _, err := cntrlr.Process(ctx) + require.NoError(t, err) // Expect no error as Opsgenie team is optional + }, + } + tc.run(t) +} + +// TODO: expected environments +func mockOpsgenieIntegrations() *opsgenie.IntegrationsResponse { + return &opsgenie.IntegrationsResponse{ + Integrations: []opsgenie.Integration{}, + } +} + func basicServiceProperties(s *creator_v1.Service, envType voyager.EnvType) orch_meta.ServiceProperties { return orch_meta.ServiceProperties{ ResourceOwner: s.Spec.ResourceOwner, @@ -1303,6 +1418,7 @@ type testCase struct { mainFake *kube_testing.Fake compFake *kube_testing.Fake scFake *fakeServiceCentral + ogFake *fakeOpsgenie releasesFake *fakeReleaseManagement registry *prometheus.Registry serviceName voyager.ServiceName @@ -1322,6 +1438,7 @@ func (tc *testCase) run(t *testing.T) { tc.compFake = &compClient.Fake tc.scFake = new(fakeServiceCentral) + tc.ogFake = new(fakeOpsgenie) tc.releasesFake = &fakeReleaseManagement{serviceName: tc.serviceName, serviceNames: tc.releaseDataServiceNames} tc.registry = prometheus.NewRegistry() @@ -1415,6 +1532,7 @@ func (tc *testCase) newController(t *testing.T, mainClient *k8s_fake.Clientset, ConfigMapInformer: configMapInformer, ServiceCentral: tc.scFake, + Opsgenie: tc.ogFake, ClusterLocation: tc.clusterLocation, ReleaseManagement: tc.releasesFake, From 964933cc5d508aa18a19a3a5c4a6e9978a5fbb57 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Tue, 19 Feb 2019 10:46:11 +1100 Subject: [PATCH 18/21] VYGR-391: Update sync bazel --- pkg/synchronization/BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/synchronization/BUILD.bazel b/pkg/synchronization/BUILD.bazel index 6018ac26..0e495d23 100644 --- a/pkg/synchronization/BUILD.bazel +++ b/pkg/synchronization/BUILD.bazel @@ -68,6 +68,7 @@ go_test( "//pkg/k8s:go_default_library", "//pkg/k8s/testing:go_default_library", "//pkg/k8s/updater:go_default_library", + "//pkg/opsgenie:go_default_library", "//pkg/pagerduty:go_default_library", "//pkg/releases:go_default_library", "//pkg/servicecentral:go_default_library", From e3e587b93871a83daae7b55eadd7317b5c1345e6 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Tue, 19 Feb 2019 14:31:57 +1100 Subject: [PATCH 19/21] VYGR-391: Add test for Opsgenie build notifications per env --- pkg/synchronization/controller_test.go | 150 ++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 4 deletions(-) diff --git a/pkg/synchronization/controller_test.go b/pkg/synchronization/controller_test.go index d7839271..42de0458 100644 --- a/pkg/synchronization/controller_test.go +++ b/pkg/synchronization/controller_test.go @@ -1380,10 +1380,152 @@ func TestOpsGenieWhenNoTeam(t *testing.T) { tc.run(t) } -// TODO: expected environments -func mockOpsgenieIntegrations() *opsgenie.IntegrationsResponse { - return &opsgenie.IntegrationsResponse{ - Integrations: []opsgenie.Integration{}, +func TestBuildOpsGenieNotifications(t *testing.T) { + t.Parallel() + + fullPagerDutyMetadata := fullPagerDutyMetadata() + + envTypeCases := []struct { + envType voyager.EnvType + pagerDutyEnvMetadata creator_v1.PagerDutyEnvMetadata + expectedOgInts []opsgenie.Integration + }{ + { + voyager.EnvTypeDev, + creator_v1.PagerDutyEnvMetadata{ + Main: creator_v1.PagerDutyServiceMetadata{ + Integrations: creator_v1.PagerDutyIntegrations{ + CloudWatch: creator_v1.PagerDutyIntegrationMetadata{ + IntegrationKey: "124e0f010f214a9b9f30b768e7b18e69", + }, + Generic: creator_v1.PagerDutyIntegrationMetadata{ + IntegrationKey: defaultPagerdutyGeneric, + }, + }, + }, + LowPriority: creator_v1.PagerDutyServiceMetadata{ + Integrations: creator_v1.PagerDutyIntegrations{ + CloudWatch: creator_v1.PagerDutyIntegrationMetadata{ + IntegrationKey: "124e0f010f214a9b9f30b768e7b18e69", + }, + Generic: creator_v1.PagerDutyIntegrationMetadata{ + IntegrationKey: defaultPagerdutyGeneric, + }, + }, + }, + }, + []opsgenie.Integration{ + {EnvType: opsgenie.EnvTypeDev}, + {EnvType: opsgenie.EnvTypeGlobal}, + }, + }, + { + voyager.EnvTypeStaging, + fullPagerDutyMetadata.Staging, + []opsgenie.Integration{ + {EnvType: opsgenie.EnvTypeStaging}, + {EnvType: opsgenie.EnvTypeGlobal}, + }, + }, + { + voyager.EnvTypeProduction, + fullPagerDutyMetadata.Production, + []opsgenie.Integration{ + {EnvType: opsgenie.EnvTypeProd}, + {EnvType: opsgenie.EnvTypeGlobal}, + }, + }, + } + + for _, subCase := range envTypeCases { + t.Run(string(subCase.envType), func(t *testing.T) { + + ns := &core_v1.Namespace{ + TypeMeta: meta_v1.TypeMeta{ + Kind: k8s.NamespaceKind, + APIVersion: core_v1.SchemeGroupVersion.String(), + }, + ObjectMeta: meta_v1.ObjectMeta{ + Name: namespaceName, + Labels: map[string]string{ + voyager.ServiceNameLabel: serviceName, + }, + }, + } + + tc := testCase{ + ns: ns, + mainClientObjects: []runtime.Object{ns, existingDefaultDockerSecret()}, + test: func(t *testing.T, cntrlr *Controller, ctx *ctrl.ProcessContext, tc *testCase) { + service := &creator_v1.Service{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: serviceName, + }, + Spec: creator_v1.ServiceSpec{ + ResourceOwner: "somebody", + BusinessUnit: "the unit", + Metadata: creator_v1.ServiceMetadata{ + PagerDuty: fullPagerDutyMetadata, + Opsgenie: &creator_v1.OpsgenieMetadata{Team: "Platform SRE"}, + }, + }, + } + expected := basicServiceProperties(service, subCase.envType) + //expected Pagerduty Notifications + cwURL, err := pagerduty.KeyToCloudWatchURL(subCase.pagerDutyEnvMetadata.Main.Integrations.CloudWatch.IntegrationKey) + require.NoError(t, err) + expected.Notifications.PagerdutyEndpoint = orch_meta.PagerDuty{ + Generic: subCase.pagerDutyEnvMetadata.Main.Integrations.Generic.IntegrationKey, + CloudWatch: cwURL, + } + cwURL, err = pagerduty.KeyToCloudWatchURL(subCase.pagerDutyEnvMetadata.LowPriority.Integrations.CloudWatch.IntegrationKey) + require.NoError(t, err) + expected.Notifications.LowPriorityPagerdutyEndpoint = orch_meta.PagerDuty{ + Generic: subCase.pagerDutyEnvMetadata.LowPriority.Integrations.Generic.IntegrationKey, + CloudWatch: cwURL, + } + //expected Opsgenie Notifications + expected.Notifications.OpsgenieIntegrations = subCase.expectedOgInts + + tc.scFake.On("GetService", mock.Anything, auth.NoUser(), serviceNameSc).Return(service, nil) + ogResp := &opsgenie.IntegrationsResponse{ + Integrations: []opsgenie.Integration{ + {EnvType: opsgenie.EnvTypeDev}, + {EnvType: opsgenie.EnvTypeStaging}, + {EnvType: opsgenie.EnvTypeProd}, + {EnvType: opsgenie.EnvTypeGlobal}, + }, + } + + // Return error when calling Opsgenie Integration Manager + tc.ogFake.On("GetOrCreateIntegrations", mock.Anything, mock.Anything).Return(ogResp, true, nil) + + // make sure the controller knows we are our specific environment type + cntrlr.ClusterLocation = voyager.ClusterLocation{ + EnvType: subCase.envType, + } + _, err = cntrlr.Process(ctx) + require.NoError(t, err) + + actions := tc.mainFake.Actions() + + cm, _ := findCreatedConfigMap(actions, namespaceName, apisynchronization.DefaultServiceMetadataConfigMapName) + require.NotNil(t, cm) + + assert.Equal(t, cm.Name, apisynchronization.DefaultServiceMetadataConfigMapName) + + assert.Contains(t, cm.Data, orch_meta.ConfigMapConfigKey) + data := cm.Data[orch_meta.ConfigMapConfigKey] + + var actual orch_meta.ServiceProperties + err = yaml.UnmarshalStrict([]byte(data), &actual) + require.NoError(t, err) + + assert.Equal(t, expected, actual) + }, + } + tc.run(t) + }) } } From f22999160bef84e6ceec82e075bae2ecbdb8082c Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Thu, 21 Feb 2019 15:55:57 +1100 Subject: [PATCH 20/21] VYGR-391: Add Opsgenie integrations into entangler test --- pkg/orchestration/wiring/BUILD.bazel | 1 + pkg/orchestration/wiring/entangler_test.go | 37 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/pkg/orchestration/wiring/BUILD.bazel b/pkg/orchestration/wiring/BUILD.bazel index 129ac666..7bdd16d4 100644 --- a/pkg/orchestration/wiring/BUILD.bazel +++ b/pkg/orchestration/wiring/BUILD.bazel @@ -34,6 +34,7 @@ go_test( "//cmd/smith/config:go_default_library", "//pkg/apis/orchestration/meta:go_default_library", "//pkg/apis/orchestration/v1:go_default_library", + "//pkg/opsgenie:go_default_library", "//pkg/orchestration/wiring/registry:go_default_library", "//pkg/orchestration/wiring/wiringplugin:go_default_library", "//pkg/orchestration/wiring/wiringutil/knownshapes:go_default_library", diff --git a/pkg/orchestration/wiring/entangler_test.go b/pkg/orchestration/wiring/entangler_test.go index c70921a9..cc77b1b5 100644 --- a/pkg/orchestration/wiring/entangler_test.go +++ b/pkg/orchestration/wiring/entangler_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/atlassian/voyager/pkg/opsgenie" + smith_v1 "github.com/atlassian/smith/pkg/apis/smith/v1" smith_plugin "github.com/atlassian/smith/pkg/plugin" "github.com/atlassian/voyager" @@ -303,6 +305,41 @@ func entangleTestState(t *testing.T, state *orch_v1.State, wiringPlugins map[voy CloudWatch: "https://events.pagerduty.com/adapter/cloudwatch_sns/v1/12312312312312312312312312312312", Generic: "123123123123123", }, + OpsgenieIntegrations: []opsgenie.Integration{ + { + APIKey: "SOME-API-KEY-HERE", + Endpoint: "https://api.opsgenie.com/v1/json/cloudwatch?apiKey=SOME-API-KEY-HERE", + EnvType: "dev", + ID: "6a33291e-a4d9-467e-a25d-f9d621fe2461", + Name: "micros_CloudWatch_high_dev", + Priority: "high", + TeamID: "01101000 01101001 00100000 01101101 01101111 01101101", + TeamName: "Platform SRE", + Type: "Cloudwatch", + }, + { + APIKey: "SOME-API-KEY-HERE", + Endpoint: "null", + EnvType: "dev", + ID: "6a33291e-a4d9-467e-a25d-f9d621fe2461", + Name: "micros_Platform SRE_Datadog", + Priority: "null", + TeamID: "01101000 01101001 00100000 01101101 01101111 01101101", + TeamName: "Platform SRE", + Type: "Datadog", + }, + { + APIKey: "SOME-API-KEY-HERE", + Endpoint: "null", + EnvType: "null", + ID: "6a33291e-a4d9-467e-a25d-f9d621fe2461", + Name: "micros_Platform SRE_API", + Priority: "null", + TeamID: "01101000 01101001 00100000 01101101 01101111 01101101", + TeamName: "Platform SRE", + Type: "API", + }, + }, }, SSAMAccessLevel: "access-level-from-configmap", LoggingID: "logging-id-from-configmap", From a5ac7690adc520c400baeade9a76e7b0e552f2f8 Mon Sep 17 00:00:00 2001 From: Fraser Cobb Date: Thu, 21 Feb 2019 16:11:43 +1100 Subject: [PATCH 21/21] VYGR-391: Reorder entangler_test imports --- pkg/orchestration/wiring/entangler_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/orchestration/wiring/entangler_test.go b/pkg/orchestration/wiring/entangler_test.go index cc77b1b5..f5c4a269 100644 --- a/pkg/orchestration/wiring/entangler_test.go +++ b/pkg/orchestration/wiring/entangler_test.go @@ -7,14 +7,13 @@ import ( "strings" "testing" - "github.com/atlassian/voyager/pkg/opsgenie" - smith_v1 "github.com/atlassian/smith/pkg/apis/smith/v1" smith_plugin "github.com/atlassian/smith/pkg/plugin" "github.com/atlassian/voyager" smith_config "github.com/atlassian/voyager/cmd/smith/config" orch_meta "github.com/atlassian/voyager/pkg/apis/orchestration/meta" orch_v1 "github.com/atlassian/voyager/pkg/apis/orchestration/v1" + "github.com/atlassian/voyager/pkg/opsgenie" "github.com/atlassian/voyager/pkg/orchestration/wiring/registry" "github.com/atlassian/voyager/pkg/orchestration/wiring/wiringplugin" "github.com/atlassian/voyager/pkg/orchestration/wiring/wiringutil/knownshapes"