From 506129d5ebc947690816247be038b6071bf1ed9b Mon Sep 17 00:00:00 2001 From: soltysss Date: Fri, 12 May 2023 17:26:53 +0300 Subject: [PATCH 01/10] SCALRCORE-25214 - Provider > Add Slack Integration --- scalr.go | 2 + slack_integration.go | 257 ++++++++++++++++++++++++++++++++++++++ slack_integration_test.go | 43 +++++++ 3 files changed, 302 insertions(+) create mode 100644 slack_integration.go create mode 100644 slack_integration_test.go diff --git a/scalr.go b/scalr.go index 2724133..f7b1ab0 100644 --- a/scalr.go +++ b/scalr.go @@ -138,6 +138,7 @@ type Client struct { Runs Runs ServiceAccountTokens ServiceAccountTokens ServiceAccounts ServiceAccounts + SlackIntegrations SlackIntegrations Tags Tags Teams Teams Users Users @@ -236,6 +237,7 @@ func NewClient(cfg *Config) (*Client, error) { client.Runs = &runs{client: client} client.ServiceAccountTokens = &serviceAccountTokens{client: client} client.ServiceAccounts = &serviceAccounts{client: client} + client.SlackIntegrations = &slackIntegrations{client: client} client.Tags = &tags{client: client} client.Teams = &teams{client: client} client.Users = &users{client: client} diff --git a/slack_integration.go b/slack_integration.go new file mode 100644 index 0000000..5f858ba --- /dev/null +++ b/slack_integration.go @@ -0,0 +1,257 @@ +package scalr + +import ( + "context" + "errors" + "fmt" + "net/url" +) + +// Compile-time proof of interface implementation. +var _ SlackIntegrations = (*slackIntegrations)(nil) + +// SlackIntegrations describes all the SlackIntegration related methods that the Scalr +// IACP API supports. +// +// IACP API docs: https://www.scalr.com/docs/en/latest/api/index.html +type SlackIntegrations interface { + List(ctx context.Context, options SlackIntegrationListOptions) (*SlackIntegrationList, error) + Create(ctx context.Context, options SlackIntegrationCreateOptions) (*SlackIntegration, error) + Read(ctx context.Context, slackIntegration string) (*SlackIntegration, error) + Update(ctx context.Context, slackIntegration string, options SlackIntegrationUpdateOptions) (*SlackIntegration, error) + Delete(ctx context.Context, slackIntegration string) error + GetConnection(ctx context.Context, accID string) (*SlackConnection, error) + GetChannels(ctx context.Context, accID string, options SlackChannelListOptions) (*SlackChannelList, error) +} + +// slackIntegrations implements SlackIntegrations. +type slackIntegrations struct { + client *Client +} + +// SlackEvent represents a event slack integration subscribes to. +type SlackEvent string + +const ( + RunApprovalRequiredEvent SlackEvent = "run_approval_required" + RunSuccessEvent SlackEvent = "run_success" + RunErroredEvent SlackEvent = "run_errored" +) + +type SlackStatus string + +const ( + IntegrationActive SlackStatus = "active" + IntegrationDisabled SlackStatus = "disabled" +) + +// SlackIntegration represents a Scalr IACP slack integration. +type SlackIntegration struct { + ID string `jsonapi:"primary,slack-integrations"` + Name string `jsonapi:"attr,name"` + Status SlackStatus `jsonapi:"attr,status"` + ChannelId string `jsonapi:"attr,channel-id"` + Events []SlackEvent `jsonapi:"attr,events"` + + // Relations + Environment *Environment `jsonapi:"relation,environment"` + Account *Account `jsonapi:"relation,account"` + Workspaces []*Workspaces `jsonapi:"relation,workspaces"` +} + +type SlackIntegrationList struct { + *Pagination + Items []*SlackIntegration +} + +type SlackIntegrationListOptions struct { + ListOptions + + Account *string `url:"filter[account]"` +} + +type SlackIntegrationCreateOptions struct { + ID string `jsonapi:"primary,slack-integrations"` + Name *string `jsonapi:"attr,name"` + ChannelId *string `jsonapi:"attr,channel-id"` + Events []SlackEvent `jsonapi:"attr,events"` + + Account *Account `jsonapi:"relation,account"` + Connection *SlackConnection `jsonapi:"relation,connection"` + Environment *Environment `jsonapi:"relation,environment"` + Workspaces []*Workspaces `jsonapi:"relation,workspaces,omitempty"` +} + +type SlackIntegrationUpdateOptions struct { + ID string `jsonapi:"primary,slack-integrations"` + Name *string `jsonapi:"attr,name,omitempty"` + ChannelId *string `jsonapi:"attr,channel-id,omitempty"` + Status SlackStatus `jsonapi:"attr,status,omitempty"` + Events []SlackEvent `jsonapi:"attr,events,omitempty"` + + Environment *Environment `jsonapi:"relation,environment,omitempty"` + Workspaces []*Workspaces `jsonapi:"relation,workspaces,omitempty"` +} + +type SlackConnection struct { + ID string `jsonapi:"primary,slack-connections"` + SlackWorkspaceName string `jsonapi:"attr,slack-workspace-name"` + + // Relations + Account *Account `jsonapi:"relation,account"` +} + +type SlackChannel struct { + ID string `json:"slack-connections"` + Name string `json:"name"` + IsPrivate string `json:"is-private"` +} + +type SlackChannelList struct { + *Pagination + Items []*SlackChannel +} + +type SlackChannelListOptions struct { + ListOptions + + Query *string `url:"query,omitempty"` +} + +func (s *slackIntegrations) List( + ctx context.Context, options SlackIntegrationListOptions, +) (*SlackIntegrationList, error) { + req, err := s.client.newRequest("GET", "integrations/slack", &options) + if err != nil { + return nil, err + } + + wl := &SlackIntegrationList{} + err = s.client.do(ctx, req, wl) + if err != nil { + return nil, err + } + + return wl, nil +} + +func (s *slackIntegrations) Create( + ctx context.Context, options SlackIntegrationCreateOptions, +) (*SlackIntegration, error) { + // Make sure we don't send a user provided ID. + options.ID = "" + + req, err := s.client.newRequest("POST", "integrations/slack", &options) + if err != nil { + return nil, err + } + + w := &SlackIntegration{} + err = s.client.do(ctx, req, w) + if err != nil { + return nil, err + } + + return w, nil +} + +func (s *slackIntegrations) Read(ctx context.Context, si string) (*SlackIntegration, error) { + if !validStringID(&si) { + return nil, errors.New("invalid value for Slack integration ID") + } + + u := fmt.Sprintf("integrations/slack/%s", url.QueryEscape(si)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + w := &SlackIntegration{} + err = s.client.do(ctx, req, w) + if err != nil { + return nil, err + } + + return w, nil +} + +func (s *slackIntegrations) Update( + ctx context.Context, si string, options SlackIntegrationUpdateOptions, +) (*SlackIntegration, error) { + if !validStringID(&si) { + return nil, errors.New("invalid value for slack integration ID") + } + + // Make sure we don't send a user provided ID. + options.ID = "" + + u := fmt.Sprintf("integrations/slack/%s", url.QueryEscape(si)) + req, err := s.client.newRequest("PATCH", u, &options) + if err != nil { + return nil, err + } + + w := &SlackIntegration{} + err = s.client.do(ctx, req, w) + if err != nil { + return nil, err + } + + return w, nil +} + +func (s *slackIntegrations) Delete(ctx context.Context, si string) error { + if !validStringID(&si) { + return errors.New("invalid value for slack integration ID") + } + + u := fmt.Sprintf("integrations/slack/%s", url.QueryEscape(si)) + req, err := s.client.newRequest("DELETE", u, nil) + if err != nil { + return err + } + + return s.client.do(ctx, req, nil) +} + +func (s *slackIntegrations) GetConnection(ctx context.Context, accID string) (*SlackConnection, error) { + if !validStringID(&accID) { + return nil, errors.New("invalid value for account ID") + } + + u := fmt.Sprintf("/integrations/slack/%s/connection", url.QueryEscape(accID)) + req, err := s.client.newRequest("GET", u, nil) + if err != nil { + return nil, err + } + + c := &SlackConnection{} + err = s.client.do(ctx, req, c) + if err != nil { + return nil, err + } + + return c, nil +} + +func (s *slackIntegrations) GetChannels( + ctx context.Context, accID string, options SlackChannelListOptions, +) (*SlackChannelList, error) { + if !validStringID(&accID) { + return nil, errors.New("invalid value for account ID") + } + + u := fmt.Sprintf("/integrations/slack/%s/connection/channels", url.QueryEscape(accID)) + req, err := s.client.newRequest("GET", u, &options) + if err != nil { + return nil, err + } + + c := &SlackChannelList{} + err = s.client.do(ctx, req, c) + if err != nil { + return nil, err + } + + return c, nil +} diff --git a/slack_integration_test.go b/slack_integration_test.go new file mode 100644 index 0000000..4a54d8e --- /dev/null +++ b/slack_integration_test.go @@ -0,0 +1,43 @@ +package scalr + +import ( + "context" + "github.com/stretchr/testify/require" + "testing" +) + +func TestSlackIntegrationsCreate(t *testing.T) { + client := testClient(t) + ctx := context.Background() + env1, deleteEnv1 := createEnvironment(t, client) + defer deleteEnv1() + + slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) + if err != nil { + println(err.Error()) + return + } + if slackConnection.ID == "" { + t.Skip("Scalr instance doesn't have working slack connection.") + } + slackChannels, _ := client.SlackIntegrations.GetChannels(ctx, defaultAccountID, SlackChannelListOptions{}) + var channelId string + for _, channel := range slackChannels.Items { + channelId = channel.ID + break + } + t.Run("with valid options", func(t *testing.T) { + + options := SlackIntegrationCreateOptions{ + Name: String("test-" + randomString(t)), + Events: []SlackEvent{RunApprovalRequiredEvent, RunSuccessEvent, RunErroredEvent}, + ChannelId: &channelId, + Account: &Account{ID: defaultAccountID}, + Connection: slackConnection, + Environment: env1, + } + + _, err := client.SlackIntegrations.Create(ctx, options) + require.NoError(t, err) + }) +} From c4d2974b5086c46745ee088563df4d2ba6c83838 Mon Sep 17 00:00:00 2001 From: soltysss Date: Sun, 14 May 2023 00:50:51 +0300 Subject: [PATCH 02/10] SCALRCORE-25214 - Provider > Add Slack Integration --- helper_test.go | 32 ++++++++++++ slack_integration.go | 41 +++++++-------- slack_integration_test.go | 102 +++++++++++++++++++++++++++++++++++--- 3 files changed, 146 insertions(+), 29 deletions(-) diff --git a/helper_test.go b/helper_test.go index 5251bcd..25d9114 100644 --- a/helper_test.go +++ b/helper_test.go @@ -513,3 +513,35 @@ func createWebhookIntegration( } } } + +func createSlackIntegration( + t *testing.T, client *Client, slackConnection *SlackConnection, environment *Environment, +) (*SlackIntegration, func()) { + ctx := context.Background() + slackChannels, _ := client.SlackIntegrations.GetChannels(ctx, defaultAccountID, SlackChannelListOptions{}) + var channelId string + for _, channel := range slackChannels.Items { + channelId = channel.ID + break + } + options := SlackIntegrationCreateOptions{ + Name: String("test-" + randomString(t)), + Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, + ChannelId: &channelId, + Account: &Account{ID: defaultAccountID}, + Connection: slackConnection, + Environment: environment, + } + si, err := client.SlackIntegrations.Create(ctx, options) + if err != nil { + t.Fatal(err) + } + + return si, func() { + if err := client.SlackIntegrations.Delete(ctx, si.ID); err != nil { + t.Errorf("Error deleting slack integration! WARNING: Dangling resources\n"+ + "may exist! The full error is shown below.\n\n"+ + "Webhook: %s\nError: %s", si.ID, err) + } + } +} diff --git a/slack_integration.go b/slack_integration.go index 5f858ba..4f0eb49 100644 --- a/slack_integration.go +++ b/slack_integration.go @@ -29,13 +29,10 @@ type slackIntegrations struct { client *Client } -// SlackEvent represents a event slack integration subscribes to. -type SlackEvent string - const ( - RunApprovalRequiredEvent SlackEvent = "run_approval_required" - RunSuccessEvent SlackEvent = "run_success" - RunErroredEvent SlackEvent = "run_errored" + RunApprovalRequiredEvent string = "run_approval_required" + RunSuccessEvent string = "run_success" + RunErroredEvent string = "run_errored" ) type SlackStatus string @@ -47,11 +44,11 @@ const ( // SlackIntegration represents a Scalr IACP slack integration. type SlackIntegration struct { - ID string `jsonapi:"primary,slack-integrations"` - Name string `jsonapi:"attr,name"` - Status SlackStatus `jsonapi:"attr,status"` - ChannelId string `jsonapi:"attr,channel-id"` - Events []SlackEvent `jsonapi:"attr,events"` + ID string `jsonapi:"primary,slack-integrations"` + Name string `jsonapi:"attr,name"` + Status SlackStatus `jsonapi:"attr,status"` + ChannelId string `jsonapi:"attr,channel-id"` + Events []string `jsonapi:"attr,events"` // Relations Environment *Environment `jsonapi:"relation,environment"` @@ -71,10 +68,10 @@ type SlackIntegrationListOptions struct { } type SlackIntegrationCreateOptions struct { - ID string `jsonapi:"primary,slack-integrations"` - Name *string `jsonapi:"attr,name"` - ChannelId *string `jsonapi:"attr,channel-id"` - Events []SlackEvent `jsonapi:"attr,events"` + ID string `jsonapi:"primary,slack-integrations"` + Name *string `jsonapi:"attr,name"` + ChannelId *string `jsonapi:"attr,channel-id"` + Events []string `jsonapi:"attr,events"` Account *Account `jsonapi:"relation,account"` Connection *SlackConnection `jsonapi:"relation,connection"` @@ -83,11 +80,11 @@ type SlackIntegrationCreateOptions struct { } type SlackIntegrationUpdateOptions struct { - ID string `jsonapi:"primary,slack-integrations"` - Name *string `jsonapi:"attr,name,omitempty"` - ChannelId *string `jsonapi:"attr,channel-id,omitempty"` - Status SlackStatus `jsonapi:"attr,status,omitempty"` - Events []SlackEvent `jsonapi:"attr,events,omitempty"` + ID string `jsonapi:"primary,slack-integrations"` + Name *string `jsonapi:"attr,name,omitempty"` + ChannelId *string `jsonapi:"attr,channel-id,omitempty"` + Status SlackStatus `jsonapi:"attr,status,omitempty"` + Events []string `jsonapi:"attr,events,omitempty"` Environment *Environment `jsonapi:"relation,environment,omitempty"` Workspaces []*Workspaces `jsonapi:"relation,workspaces,omitempty"` @@ -219,7 +216,7 @@ func (s *slackIntegrations) GetConnection(ctx context.Context, accID string) (*S return nil, errors.New("invalid value for account ID") } - u := fmt.Sprintf("/integrations/slack/%s/connection", url.QueryEscape(accID)) + u := fmt.Sprintf("integrations/slack/%s/connection", url.QueryEscape(accID)) req, err := s.client.newRequest("GET", u, nil) if err != nil { return nil, err @@ -241,7 +238,7 @@ func (s *slackIntegrations) GetChannels( return nil, errors.New("invalid value for account ID") } - u := fmt.Sprintf("/integrations/slack/%s/connection/channels", url.QueryEscape(accID)) + u := fmt.Sprintf("integrations/slack/%s/connection/channels", url.QueryEscape(accID)) req, err := s.client.newRequest("GET", u, &options) if err != nil { return nil, err diff --git a/slack_integration_test.go b/slack_integration_test.go index 4a54d8e..d4dcbcb 100644 --- a/slack_integration_test.go +++ b/slack_integration_test.go @@ -2,6 +2,7 @@ package scalr import ( "context" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" ) @@ -13,11 +14,7 @@ func TestSlackIntegrationsCreate(t *testing.T) { defer deleteEnv1() slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) - if err != nil { - println(err.Error()) - return - } - if slackConnection.ID == "" { + if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } slackChannels, _ := client.SlackIntegrations.GetChannels(ctx, defaultAccountID, SlackChannelListOptions{}) @@ -30,14 +27,105 @@ func TestSlackIntegrationsCreate(t *testing.T) { options := SlackIntegrationCreateOptions{ Name: String("test-" + randomString(t)), - Events: []SlackEvent{RunApprovalRequiredEvent, RunSuccessEvent, RunErroredEvent}, + Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, ChannelId: &channelId, Account: &Account{ID: defaultAccountID}, Connection: slackConnection, Environment: env1, } - _, err := client.SlackIntegrations.Create(ctx, options) + si, err := client.SlackIntegrations.Create(ctx, options) + require.NoError(t, err) + + refreshed, err := client.SlackIntegrations.Read(ctx, si.ID) + require.NoError(t, err) + + for _, item := range []*SlackIntegration{ + si, + refreshed, + } { + assert.NotEmpty(t, item.ID) + assert.Equal(t, *options.Name, item.Name) + assert.Equal(t, options.Account, item.Account) + assert.Equal(t, *options.ChannelId, item.ChannelId) + assert.Equal(t, options.Events, item.Events) + } + + err = client.SlackIntegrations.Delete(ctx, si.ID) + require.NoError(t, err) + }) +} + +func TestSlackIntegrationsUpdate(t *testing.T) { + client := testClient(t) + ctx := context.Background() + env1, deleteEnv1 := createEnvironment(t, client) + defer deleteEnv1() + env2, deleteEnv2 := createEnvironment(t, client) + defer deleteEnv2() + + slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) + if err != nil || slackConnection.ID == "" { + t.Skip("Scalr instance doesn't have working slack connection.") + } + si, deleteSlack := createSlackIntegration(t, client, slackConnection, env1) + defer deleteSlack() + t.Run("with valid options", func(t *testing.T) { + + options := SlackIntegrationUpdateOptions{ + Name: String("test-" + randomString(t)), + Events: []string{RunApprovalRequiredEvent, RunErroredEvent}, + Environment: env2, + } + + si, err := client.SlackIntegrations.Update(ctx, si.ID, options) + require.NoError(t, err) + + refreshed, err := client.SlackIntegrations.Read(ctx, si.ID) require.NoError(t, err) + + for _, item := range []*SlackIntegration{ + si, + refreshed, + } { + assert.NotEmpty(t, item.ID) + assert.Equal(t, *options.Name, item.Name) + assert.Equal(t, options.Events, item.Events) + } + }) +} + +func TestSlackIntegrationsList(t *testing.T) { + client := testClient(t) + ctx := context.Background() + env1, deleteEnv1 := createEnvironment(t, client) + defer deleteEnv1() + env2, deleteEnv2 := createEnvironment(t, client) + defer deleteEnv2() + + slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) + if err != nil || slackConnection.ID == "" { + t.Skip("Scalr instance doesn't have working slack connection.") + } + si, deleteSlack := createSlackIntegration(t, client, slackConnection, env1) + defer deleteSlack() + si2, deleteSlack2 := createSlackIntegration(t, client, slackConnection, env2) + defer deleteSlack2() + t.Run("with valid options", func(t *testing.T) { + + options := SlackIntegrationListOptions{ + Account: String(defaultAccountID), + } + + sil, err := client.SlackIntegrations.List(ctx, options) + require.NoError(t, err) + + assert.Equal(t, 2, sil.TotalCount) + expectedIDs := []string{si.ID, si2.ID} + actualIDs := make([]string, len(sil.Items)) + for i, s := range sil.Items { + actualIDs[i] = s.ID + } + assert.ElementsMatch(t, expectedIDs, actualIDs) }) } From 0dde8be2494ab6348c5fa38a65d3a7d78610450d Mon Sep 17 00:00:00 2001 From: soltysss Date: Mon, 15 May 2023 16:53:51 +0300 Subject: [PATCH 03/10] SCALRCORE-25214 - Provider > Add Slack Integration --- helper_test.go | 10 ++------ slack_integration.go | 52 +++++---------------------------------- slack_integration_test.go | 29 ++++++++++++++-------- 3 files changed, 27 insertions(+), 64 deletions(-) diff --git a/helper_test.go b/helper_test.go index 25d9114..1a6d996 100644 --- a/helper_test.go +++ b/helper_test.go @@ -515,19 +515,13 @@ func createWebhookIntegration( } func createSlackIntegration( - t *testing.T, client *Client, slackConnection *SlackConnection, environment *Environment, + t *testing.T, client *Client, slackConnection *SlackConnection, channelId *string, environment *Environment, ) (*SlackIntegration, func()) { ctx := context.Background() - slackChannels, _ := client.SlackIntegrations.GetChannels(ctx, defaultAccountID, SlackChannelListOptions{}) - var channelId string - for _, channel := range slackChannels.Items { - channelId = channel.ID - break - } options := SlackIntegrationCreateOptions{ Name: String("test-" + randomString(t)), Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, - ChannelId: &channelId, + ChannelId: channelId, Account: &Account{ID: defaultAccountID}, Connection: slackConnection, Environment: environment, diff --git a/slack_integration.go b/slack_integration.go index 4f0eb49..2955221 100644 --- a/slack_integration.go +++ b/slack_integration.go @@ -21,7 +21,6 @@ type SlackIntegrations interface { Update(ctx context.Context, slackIntegration string, options SlackIntegrationUpdateOptions) (*SlackIntegration, error) Delete(ctx context.Context, slackIntegration string) error GetConnection(ctx context.Context, accID string) (*SlackConnection, error) - GetChannels(ctx context.Context, accID string, options SlackChannelListOptions) (*SlackChannelList, error) } // slackIntegrations implements SlackIntegrations. @@ -51,9 +50,9 @@ type SlackIntegration struct { Events []string `jsonapi:"attr,events"` // Relations - Environment *Environment `jsonapi:"relation,environment"` - Account *Account `jsonapi:"relation,account"` - Workspaces []*Workspaces `jsonapi:"relation,workspaces"` + Environment *Environment `jsonapi:"relation,environment"` + Account *Account `jsonapi:"relation,account"` + Workspaces []*Workspace `jsonapi:"relation,workspaces"` } type SlackIntegrationList struct { @@ -76,7 +75,7 @@ type SlackIntegrationCreateOptions struct { Account *Account `jsonapi:"relation,account"` Connection *SlackConnection `jsonapi:"relation,connection"` Environment *Environment `jsonapi:"relation,environment"` - Workspaces []*Workspaces `jsonapi:"relation,workspaces,omitempty"` + Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` } type SlackIntegrationUpdateOptions struct { @@ -86,8 +85,8 @@ type SlackIntegrationUpdateOptions struct { Status SlackStatus `jsonapi:"attr,status,omitempty"` Events []string `jsonapi:"attr,events,omitempty"` - Environment *Environment `jsonapi:"relation,environment,omitempty"` - Workspaces []*Workspaces `jsonapi:"relation,workspaces,omitempty"` + Environment *Environment `jsonapi:"relation,environment,omitempty"` + Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` } type SlackConnection struct { @@ -98,23 +97,6 @@ type SlackConnection struct { Account *Account `jsonapi:"relation,account"` } -type SlackChannel struct { - ID string `json:"slack-connections"` - Name string `json:"name"` - IsPrivate string `json:"is-private"` -} - -type SlackChannelList struct { - *Pagination - Items []*SlackChannel -} - -type SlackChannelListOptions struct { - ListOptions - - Query *string `url:"query,omitempty"` -} - func (s *slackIntegrations) List( ctx context.Context, options SlackIntegrationListOptions, ) (*SlackIntegrationList, error) { @@ -230,25 +212,3 @@ func (s *slackIntegrations) GetConnection(ctx context.Context, accID string) (*S return c, nil } - -func (s *slackIntegrations) GetChannels( - ctx context.Context, accID string, options SlackChannelListOptions, -) (*SlackChannelList, error) { - if !validStringID(&accID) { - return nil, errors.New("invalid value for account ID") - } - - u := fmt.Sprintf("integrations/slack/%s/connection/channels", url.QueryEscape(accID)) - req, err := s.client.newRequest("GET", u, &options) - if err != nil { - return nil, err - } - - c := &SlackChannelList{} - err = s.client.do(ctx, req, c) - if err != nil { - return nil, err - } - - return c, nil -} diff --git a/slack_integration_test.go b/slack_integration_test.go index d4dcbcb..abae4ce 100644 --- a/slack_integration_test.go +++ b/slack_integration_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "os" "testing" ) @@ -12,17 +13,15 @@ func TestSlackIntegrationsCreate(t *testing.T) { ctx := context.Background() env1, deleteEnv1 := createEnvironment(t, client) defer deleteEnv1() - + var channelId = os.Getenv("SLACK_CHANNEL_ID") + if len(channelId) == 0 { + t.Skip("Set `SLACK_CHANNEL_ID` environment variable to run this test. Container should have connection to slack.") + } slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } - slackChannels, _ := client.SlackIntegrations.GetChannels(ctx, defaultAccountID, SlackChannelListOptions{}) - var channelId string - for _, channel := range slackChannels.Items { - channelId = channel.ID - break - } + t.Run("with valid options", func(t *testing.T) { options := SlackIntegrationCreateOptions{ @@ -68,7 +67,12 @@ func TestSlackIntegrationsUpdate(t *testing.T) { if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } - si, deleteSlack := createSlackIntegration(t, client, slackConnection, env1) + var channelId = os.Getenv("SLACK_CHANNEL_ID") + if len(channelId) == 0 { + t.Skip("Set `SLACK_CHANNEL_ID` environment variable to run this test. Container should have connection to slack.") + } + + si, deleteSlack := createSlackIntegration(t, client, slackConnection, &channelId, env1) defer deleteSlack() t.Run("with valid options", func(t *testing.T) { @@ -107,9 +111,14 @@ func TestSlackIntegrationsList(t *testing.T) { if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } - si, deleteSlack := createSlackIntegration(t, client, slackConnection, env1) + var channelId = os.Getenv("SLACK_CHANNEL_ID") + if len(channelId) == 0 { + t.Skip("Set `SLACK_CHANNEL_ID` environment variable to run this test. Container should have connection to slack.") + } + + si, deleteSlack := createSlackIntegration(t, client, slackConnection, &channelId, env1) defer deleteSlack() - si2, deleteSlack2 := createSlackIntegration(t, client, slackConnection, env2) + si2, deleteSlack2 := createSlackIntegration(t, client, slackConnection, &channelId, env2) defer deleteSlack2() t.Run("with valid options", func(t *testing.T) { From 51c054b58bff4cafe050a9e96a5d9fa8be032766 Mon Sep 17 00:00:00 2001 From: soltysss Date: Fri, 26 May 2023 20:02:56 +0300 Subject: [PATCH 04/10] SCALRCORE-25214 - Provider > Add Slack Integration --- helper_test.go | 12 ++++++------ slack_integration.go | 18 +++++++++--------- slack_integration_test.go | 18 +++++++++--------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/helper_test.go b/helper_test.go index 1a6d996..cfed3b6 100644 --- a/helper_test.go +++ b/helper_test.go @@ -519,12 +519,12 @@ func createSlackIntegration( ) (*SlackIntegration, func()) { ctx := context.Background() options := SlackIntegrationCreateOptions{ - Name: String("test-" + randomString(t)), - Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, - ChannelId: channelId, - Account: &Account{ID: defaultAccountID}, - Connection: slackConnection, - Environment: environment, + Name: String("test-" + randomString(t)), + Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, + ChannelId: channelId, + Account: &Account{ID: defaultAccountID}, + Connection: slackConnection, + Environments: []*Environment{environment}, } si, err := client.SlackIntegrations.Create(ctx, options) if err != nil { diff --git a/slack_integration.go b/slack_integration.go index 2955221..10727bb 100644 --- a/slack_integration.go +++ b/slack_integration.go @@ -50,9 +50,9 @@ type SlackIntegration struct { Events []string `jsonapi:"attr,events"` // Relations - Environment *Environment `jsonapi:"relation,environment"` - Account *Account `jsonapi:"relation,account"` - Workspaces []*Workspace `jsonapi:"relation,workspaces"` + Account *Account `jsonapi:"relation,account"` + Environments []*Environment `jsonapi:"relation,environments"` + Workspaces []*Workspace `jsonapi:"relation,workspaces"` } type SlackIntegrationList struct { @@ -72,10 +72,10 @@ type SlackIntegrationCreateOptions struct { ChannelId *string `jsonapi:"attr,channel-id"` Events []string `jsonapi:"attr,events"` - Account *Account `jsonapi:"relation,account"` - Connection *SlackConnection `jsonapi:"relation,connection"` - Environment *Environment `jsonapi:"relation,environment"` - Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` + Account *Account `jsonapi:"relation,account"` + Connection *SlackConnection `jsonapi:"relation,connection"` + Environments []*Environment `jsonapi:"relation,environments"` + Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` } type SlackIntegrationUpdateOptions struct { @@ -85,8 +85,8 @@ type SlackIntegrationUpdateOptions struct { Status SlackStatus `jsonapi:"attr,status,omitempty"` Events []string `jsonapi:"attr,events,omitempty"` - Environment *Environment `jsonapi:"relation,environment,omitempty"` - Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` + Environments []*Environment `jsonapi:"relation,environments,omitempty"` + Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` } type SlackConnection struct { diff --git a/slack_integration_test.go b/slack_integration_test.go index abae4ce..9992c0a 100644 --- a/slack_integration_test.go +++ b/slack_integration_test.go @@ -25,12 +25,12 @@ func TestSlackIntegrationsCreate(t *testing.T) { t.Run("with valid options", func(t *testing.T) { options := SlackIntegrationCreateOptions{ - Name: String("test-" + randomString(t)), - Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, - ChannelId: &channelId, - Account: &Account{ID: defaultAccountID}, - Connection: slackConnection, - Environment: env1, + Name: String("test-" + randomString(t)), + Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, + ChannelId: &channelId, + Account: &Account{ID: defaultAccountID}, + Connection: slackConnection, + Environments: []*Environment{env1}, } si, err := client.SlackIntegrations.Create(ctx, options) @@ -77,9 +77,9 @@ func TestSlackIntegrationsUpdate(t *testing.T) { t.Run("with valid options", func(t *testing.T) { options := SlackIntegrationUpdateOptions{ - Name: String("test-" + randomString(t)), - Events: []string{RunApprovalRequiredEvent, RunErroredEvent}, - Environment: env2, + Name: String("test-" + randomString(t)), + Events: []string{RunApprovalRequiredEvent, RunErroredEvent}, + Environments: []*Environment{env2}, } si, err := client.SlackIntegrations.Update(ctx, si.ID, options) From e4cd75d69c2279cd9463e1ac7b29390d7caf991f Mon Sep 17 00:00:00 2001 From: soltysss Date: Wed, 31 May 2023 19:55:30 +0300 Subject: [PATCH 05/10] SCALRCORE-26173 - Terraform provider > clients > handle rate limit --- README.md | 4 ++++ scalr.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 6a85d5c..a505bfc 100644 --- a/README.md +++ b/README.md @@ -99,4 +99,8 @@ export SCALR_TOKEN= You can run the acceptance tests like this: ``` make test +``` +To run specific test: +``` +TESTARGS="-run TestAccessPoliciesList/without_list_options" make test ``` \ No newline at end of file diff --git a/scalr.go b/scalr.go index 2724133..48c6805 100644 --- a/scalr.go +++ b/scalr.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log" "net/http" "net/url" "os" @@ -265,6 +266,12 @@ func (c *Client) retryHTTPCheck(ctx context.Context, resp *http.Response, err er return c.retryServerErrors, err } if resp.StatusCode == 429 || (c.retryServerErrors && resp.StatusCode >= 500) { + if resp.StatusCode == 429 { + log.Printf( + "[DEBUG] API rate limit reached for %s%s, retrying...", + resp.Request.URL.Host, resp.Request.URL.Path, + ) + } return true, nil } return false, nil From 5b10500e4874adcd905632f3ff27407f09b0f974 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 1 Jun 2023 15:24:02 +0300 Subject: [PATCH 06/10] SCALRCORE-25911 add EnvironmentUpdateOptionsWithoutPG struct --- environment.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/environment.go b/environment.go index 206389a..165048c 100644 --- a/environment.go +++ b/environment.go @@ -18,6 +18,7 @@ type Environments interface { Read(ctx context.Context, environmentID string) (*Environment, error) Create(ctx context.Context, options EnvironmentCreateOptions) (*Environment, error) Update(ctx context.Context, environmentID string, options EnvironmentUpdateOptions) (*Environment, error) + UpdateWithoutPG(ctx context.Context, environmentID string, options EnvironmentUpdateOptionsWithoutPG) (*Environment, error) Delete(ctx context.Context, environmentID string) error } @@ -189,6 +190,15 @@ type EnvironmentUpdateOptions struct { DefaultProviderConfigurations []*ProviderConfiguration `jsonapi:"relation,default-provider-configurations"` } +type EnvironmentUpdateOptionsWithoutPG struct { + ID string `jsonapi:"primary,environments"` + Name *string `jsonapi:"attr,name,omitempty"` + CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"` + + // Relations + DefaultProviderConfigurations []*ProviderConfiguration `jsonapi:"relation,default-provider-configurations"` +} + // Update settings of an existing environment. func (s *environments) Update(ctx context.Context, environmentID string, options EnvironmentUpdateOptions) (*Environment, error) { // Make sure we don't send a user provided ID. @@ -209,6 +219,24 @@ func (s *environments) Update(ctx context.Context, environmentID string, options return env, nil } +func (s *environments) UpdateWithoutPG(ctx context.Context, environmentID string, options EnvironmentUpdateOptionsWithoutPG) (*Environment, error) { + options.ID = "" + + u := fmt.Sprintf("environments/%s", url.QueryEscape(environmentID)) + req, err := s.client.newRequest("PATCH", u, &options) + if err != nil { + return nil, err + } + + env := &Environment{} + err = s.client.do(ctx, req, env) + if err != nil { + return nil, err + } + + return env, nil +} + // Delete an environment by its ID. func (s *environments) Delete(ctx context.Context, environmentID string) error { if !validStringID(&environmentID) { From 269bd29a43a86514131a9c248cf857cd6bc542cd Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 1 Jun 2023 15:42:43 +0300 Subject: [PATCH 07/10] SCALRCORE-25911 update --- environment.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/environment.go b/environment.go index 165048c..a5f87e9 100644 --- a/environment.go +++ b/environment.go @@ -18,7 +18,7 @@ type Environments interface { Read(ctx context.Context, environmentID string) (*Environment, error) Create(ctx context.Context, options EnvironmentCreateOptions) (*Environment, error) Update(ctx context.Context, environmentID string, options EnvironmentUpdateOptions) (*Environment, error) - UpdateWithoutPG(ctx context.Context, environmentID string, options EnvironmentUpdateOptionsWithoutPG) (*Environment, error) + UpdateDefaultProviderConfigurationOnly(ctx context.Context, environmentID string, options EnvironmentUpdateOptionsDefaultProviderConfigurationOnly) (*Environment, error) Delete(ctx context.Context, environmentID string) error } @@ -190,11 +190,8 @@ type EnvironmentUpdateOptions struct { DefaultProviderConfigurations []*ProviderConfiguration `jsonapi:"relation,default-provider-configurations"` } -type EnvironmentUpdateOptionsWithoutPG struct { - ID string `jsonapi:"primary,environments"` - Name *string `jsonapi:"attr,name,omitempty"` - CostEstimationEnabled *bool `jsonapi:"attr,cost-estimation-enabled,omitempty"` - +type EnvironmentUpdateOptionsDefaultProviderConfigurationOnly struct { + ID string `jsonapi:"primary,environments"` // Relations DefaultProviderConfigurations []*ProviderConfiguration `jsonapi:"relation,default-provider-configurations"` } @@ -219,7 +216,7 @@ func (s *environments) Update(ctx context.Context, environmentID string, options return env, nil } -func (s *environments) UpdateWithoutPG(ctx context.Context, environmentID string, options EnvironmentUpdateOptionsWithoutPG) (*Environment, error) { +func (s *environments) UpdateDefaultProviderConfigurationOnly(ctx context.Context, environmentID string, options EnvironmentUpdateOptionsDefaultProviderConfigurationOnly) (*Environment, error) { options.ID = "" u := fmt.Sprintf("environments/%s", url.QueryEscape(environmentID)) From 02c39ca27b4c1d47c8f3a145da3d2292032060ae Mon Sep 17 00:00:00 2001 From: Petro Protsakh Date: Mon, 12 Jun 2023 12:13:30 +0300 Subject: [PATCH 08/10] SCALRCORE-25214 Update Slack methods --- helper_test.go | 12 +++++++---- integration.go | 9 +++++++++ slack_integration.go | 42 +++++++++++++++++++-------------------- slack_integration_test.go | 36 ++++++++++++++------------------- 4 files changed, 52 insertions(+), 47 deletions(-) create mode 100644 integration.go diff --git a/helper_test.go b/helper_test.go index cfed3b6..c081bc1 100644 --- a/helper_test.go +++ b/helper_test.go @@ -515,13 +515,17 @@ func createWebhookIntegration( } func createSlackIntegration( - t *testing.T, client *Client, slackConnection *SlackConnection, channelId *string, environment *Environment, + t *testing.T, client *Client, slackConnection *SlackConnection, environment *Environment, ) (*SlackIntegration, func()) { ctx := context.Background() options := SlackIntegrationCreateOptions{ - Name: String("test-" + randomString(t)), - Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, - ChannelId: channelId, + Name: String("test-" + randomString(t)), + Events: []string{ + SlackIntegrationEventRunApprovalRequired, + SlackIntegrationEventRunSuccess, + SlackIntegrationEventRunErrored, + }, + ChannelId: String("C123"), Account: &Account{ID: defaultAccountID}, Connection: slackConnection, Environments: []*Environment{environment}, diff --git a/integration.go b/integration.go new file mode 100644 index 0000000..4a4e3ee --- /dev/null +++ b/integration.go @@ -0,0 +1,9 @@ +package scalr + +type IntegrationStatus string + +const ( + IntegrationStatusActive IntegrationStatus = "active" + IntegrationStatusDisabled IntegrationStatus = "disabled" + IntegrationStatusFailed IntegrationStatus = "failed" +) diff --git a/slack_integration.go b/slack_integration.go index 10727bb..e3d7a2f 100644 --- a/slack_integration.go +++ b/slack_integration.go @@ -29,25 +29,18 @@ type slackIntegrations struct { } const ( - RunApprovalRequiredEvent string = "run_approval_required" - RunSuccessEvent string = "run_success" - RunErroredEvent string = "run_errored" -) - -type SlackStatus string - -const ( - IntegrationActive SlackStatus = "active" - IntegrationDisabled SlackStatus = "disabled" + SlackIntegrationEventRunApprovalRequired string = "run_approval_required" + SlackIntegrationEventRunSuccess string = "run_success" + SlackIntegrationEventRunErrored string = "run_errored" ) // SlackIntegration represents a Scalr IACP slack integration. type SlackIntegration struct { - ID string `jsonapi:"primary,slack-integrations"` - Name string `jsonapi:"attr,name"` - Status SlackStatus `jsonapi:"attr,status"` - ChannelId string `jsonapi:"attr,channel-id"` - Events []string `jsonapi:"attr,events"` + ID string `jsonapi:"primary,slack-integrations"` + Name string `jsonapi:"attr,name"` + Status IntegrationStatus `jsonapi:"attr,status"` + ChannelId string `jsonapi:"attr,channel-id"` + Events []string `jsonapi:"attr,events"` // Relations Account *Account `jsonapi:"relation,account"` @@ -63,7 +56,12 @@ type SlackIntegrationList struct { type SlackIntegrationListOptions struct { ListOptions - Account *string `url:"filter[account]"` + Filter *SlackIntegrationFilter `url:"filter,omitempty"` +} + +// SlackIntegrationFilter represents the options for filtering Slack integrations. +type SlackIntegrationFilter struct { + Account *string `url:"account,omitempty"` } type SlackIntegrationCreateOptions struct { @@ -79,14 +77,14 @@ type SlackIntegrationCreateOptions struct { } type SlackIntegrationUpdateOptions struct { - ID string `jsonapi:"primary,slack-integrations"` - Name *string `jsonapi:"attr,name,omitempty"` - ChannelId *string `jsonapi:"attr,channel-id,omitempty"` - Status SlackStatus `jsonapi:"attr,status,omitempty"` - Events []string `jsonapi:"attr,events,omitempty"` + ID string `jsonapi:"primary,slack-integrations"` + Name *string `jsonapi:"attr,name,omitempty"` + ChannelId *string `jsonapi:"attr,channel-id,omitempty"` + Status *IntegrationStatus `jsonapi:"attr,status,omitempty"` + Events []string `jsonapi:"attr,events,omitempty"` Environments []*Environment `jsonapi:"relation,environments,omitempty"` - Workspaces []*Workspace `jsonapi:"relation,workspaces,omitempty"` + Workspaces []*Workspace `jsonapi:"relation,workspaces"` } type SlackConnection struct { diff --git a/slack_integration_test.go b/slack_integration_test.go index 9992c0a..256ee02 100644 --- a/slack_integration_test.go +++ b/slack_integration_test.go @@ -4,7 +4,6 @@ import ( "context" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "os" "testing" ) @@ -13,10 +12,7 @@ func TestSlackIntegrationsCreate(t *testing.T) { ctx := context.Background() env1, deleteEnv1 := createEnvironment(t, client) defer deleteEnv1() - var channelId = os.Getenv("SLACK_CHANNEL_ID") - if len(channelId) == 0 { - t.Skip("Set `SLACK_CHANNEL_ID` environment variable to run this test. Container should have connection to slack.") - } + slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") @@ -25,9 +21,13 @@ func TestSlackIntegrationsCreate(t *testing.T) { t.Run("with valid options", func(t *testing.T) { options := SlackIntegrationCreateOptions{ - Name: String("test-" + randomString(t)), - Events: []string{string(RunApprovalRequiredEvent), string(RunSuccessEvent), string(RunErroredEvent)}, - ChannelId: &channelId, + Name: String("test-" + randomString(t)), + Events: []string{ + SlackIntegrationEventRunApprovalRequired, + SlackIntegrationEventRunSuccess, + SlackIntegrationEventRunErrored, + }, + ChannelId: String("C123"), Account: &Account{ID: defaultAccountID}, Connection: slackConnection, Environments: []*Environment{env1}, @@ -67,18 +67,15 @@ func TestSlackIntegrationsUpdate(t *testing.T) { if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } - var channelId = os.Getenv("SLACK_CHANNEL_ID") - if len(channelId) == 0 { - t.Skip("Set `SLACK_CHANNEL_ID` environment variable to run this test. Container should have connection to slack.") - } - si, deleteSlack := createSlackIntegration(t, client, slackConnection, &channelId, env1) + si, deleteSlack := createSlackIntegration(t, client, slackConnection, env1) defer deleteSlack() + t.Run("with valid options", func(t *testing.T) { options := SlackIntegrationUpdateOptions{ Name: String("test-" + randomString(t)), - Events: []string{RunApprovalRequiredEvent, RunErroredEvent}, + Events: []string{SlackIntegrationEventRunApprovalRequired, SlackIntegrationEventRunErrored}, Environments: []*Environment{env2}, } @@ -111,19 +108,16 @@ func TestSlackIntegrationsList(t *testing.T) { if err != nil || slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } - var channelId = os.Getenv("SLACK_CHANNEL_ID") - if len(channelId) == 0 { - t.Skip("Set `SLACK_CHANNEL_ID` environment variable to run this test. Container should have connection to slack.") - } - si, deleteSlack := createSlackIntegration(t, client, slackConnection, &channelId, env1) + si, deleteSlack := createSlackIntegration(t, client, slackConnection, env1) defer deleteSlack() - si2, deleteSlack2 := createSlackIntegration(t, client, slackConnection, &channelId, env2) + si2, deleteSlack2 := createSlackIntegration(t, client, slackConnection, env2) defer deleteSlack2() + t.Run("with valid options", func(t *testing.T) { options := SlackIntegrationListOptions{ - Account: String(defaultAccountID), + Filter: &SlackIntegrationFilter{Account: String(defaultAccountID)}, } sil, err := client.SlackIntegrations.List(ctx, options) From 84e1dfe6bf84b794bbde732b20de10c6b5476512 Mon Sep 17 00:00:00 2001 From: Petro Protsakh Date: Mon, 12 Jun 2023 20:27:07 +0300 Subject: [PATCH 09/10] SCALRCORE-25214 Improve slack connection fetching --- slack_integration.go | 5 +++++ slack_integration_test.go | 12 +++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/slack_integration.go b/slack_integration.go index e3d7a2f..badc9cb 100644 --- a/slack_integration.go +++ b/slack_integration.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/url" + "strings" ) // Compile-time proof of interface implementation. @@ -205,6 +206,10 @@ func (s *slackIntegrations) GetConnection(ctx context.Context, accID string) (*S c := &SlackConnection{} err = s.client.do(ctx, req, c) if err != nil { + if strings.Contains(err.Error(), "data is not a jsonapi representation") { + // workaround for jsonapi serializer that can't handle nil 'data' structure we use for missing connection + return c, nil + } return nil, err } diff --git a/slack_integration_test.go b/slack_integration_test.go index 256ee02..5fc879e 100644 --- a/slack_integration_test.go +++ b/slack_integration_test.go @@ -14,7 +14,9 @@ func TestSlackIntegrationsCreate(t *testing.T) { defer deleteEnv1() slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) - if err != nil || slackConnection.ID == "" { + require.NoError(t, err) + + if slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } @@ -64,7 +66,9 @@ func TestSlackIntegrationsUpdate(t *testing.T) { defer deleteEnv2() slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) - if err != nil || slackConnection.ID == "" { + require.NoError(t, err) + + if slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } @@ -105,7 +109,9 @@ func TestSlackIntegrationsList(t *testing.T) { defer deleteEnv2() slackConnection, err := client.SlackIntegrations.GetConnection(ctx, defaultAccountID) - if err != nil || slackConnection.ID == "" { + require.NoError(t, err) + + if slackConnection.ID == "" { t.Skip("Scalr instance doesn't have working slack connection.") } From df3ba0decf0bce739a2362c7718623ef05268643 Mon Sep 17 00:00:00 2001 From: soltysss Date: Tue, 13 Jun 2023 13:12:26 +0300 Subject: [PATCH 10/10] SCALRCORE-26173 - Terraform provider > clients > handle rate limit, fix tests --- scalr_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/scalr_test.go b/scalr_test.go index fee156e..02c0715 100644 --- a/scalr_test.go +++ b/scalr_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "net/url" "os" "testing" ) @@ -190,26 +191,38 @@ func TestClient_retryHTTPCheck(t *testing.T) { checkErr error }{ "429-no-server-errors": { - resp: &http.Response{StatusCode: 429}, + resp: &http.Response{ + StatusCode: 429, + Request: &http.Request{URL: &url.URL{Host: "scalr.test", Path: "/test/thing"}}, + }, err: nil, checkOK: true, checkErr: nil, }, "429-with-server-errors": { - resp: &http.Response{StatusCode: 429}, + resp: &http.Response{ + StatusCode: 429, + Request: &http.Request{URL: &url.URL{Host: "scalr.test", Path: "/test/thing"}}, + }, err: nil, retryServerErrors: true, checkOK: true, checkErr: nil, }, "500-no-server-errors": { - resp: &http.Response{StatusCode: 500}, + resp: &http.Response{ + StatusCode: 500, + Request: &http.Request{URL: &url.URL{Host: "scalr.test", Path: "/test/thing"}}, + }, err: nil, checkOK: false, checkErr: nil, }, "500-with-server-errors": { - resp: &http.Response{StatusCode: 500}, + resp: &http.Response{ + StatusCode: 500, + Request: &http.Request{URL: &url.URL{Host: "scalr.test", Path: "/test/thing"}}, + }, err: nil, retryServerErrors: true, checkOK: true,