Skip to content

Commit

Permalink
SCALRCORE-25214 - Provider > Add Slack Integration
Browse files Browse the repository at this point in the history
  • Loading branch information
soltysss committed May 12, 2023
1 parent e257255 commit 506129d
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 0 deletions.
2 changes: 2 additions & 0 deletions scalr.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ type Client struct {
Runs Runs
ServiceAccountTokens ServiceAccountTokens
ServiceAccounts ServiceAccounts
SlackIntegrations SlackIntegrations
Tags Tags
Teams Teams
Users Users
Expand Down Expand Up @@ -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}
Expand Down
257 changes: 257 additions & 0 deletions slack_integration.go
Original file line number Diff line number Diff line change
@@ -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
}
43 changes: 43 additions & 0 deletions slack_integration_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}

0 comments on commit 506129d

Please sign in to comment.