Skip to content

Commit

Permalink
Merged automatically by CI pipeline
Browse files Browse the repository at this point in the history
SCALRCORE-25214 - Provider > Add Slack Integration
  • Loading branch information
emocharnik authored Jun 13, 2023
2 parents 4ea986e + 84e1dfe commit b79ba53
Show file tree
Hide file tree
Showing 5 changed files with 398 additions and 0 deletions.
30 changes: 30 additions & 0 deletions helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,3 +513,33 @@ func createWebhookIntegration(
}
}
}

func createSlackIntegration(
t *testing.T, client *Client, slackConnection *SlackConnection, environment *Environment,
) (*SlackIntegration, func()) {
ctx := context.Background()
options := SlackIntegrationCreateOptions{
Name: String("test-" + randomString(t)),
Events: []string{
SlackIntegrationEventRunApprovalRequired,
SlackIntegrationEventRunSuccess,
SlackIntegrationEventRunErrored,
},
ChannelId: String("C123"),
Account: &Account{ID: defaultAccountID},
Connection: slackConnection,
Environments: []*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)
}
}
}
9 changes: 9 additions & 0 deletions integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package scalr

type IntegrationStatus string

const (
IntegrationStatusActive IntegrationStatus = "active"
IntegrationStatusDisabled IntegrationStatus = "disabled"
IntegrationStatusFailed IntegrationStatus = "failed"
)
2 changes: 2 additions & 0 deletions scalr.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ type Client struct {
Runs Runs
ServiceAccountTokens ServiceAccountTokens
ServiceAccounts ServiceAccounts
SlackIntegrations SlackIntegrations
Tags Tags
Teams Teams
Users Users
Expand Down Expand Up @@ -237,6 +238,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
217 changes: 217 additions & 0 deletions slack_integration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package scalr

import (
"context"
"errors"
"fmt"
"net/url"
"strings"
)

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

// slackIntegrations implements SlackIntegrations.
type slackIntegrations struct {
client *Client
}

const (
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 IntegrationStatus `jsonapi:"attr,status"`
ChannelId string `jsonapi:"attr,channel-id"`
Events []string `jsonapi:"attr,events"`

// Relations
Account *Account `jsonapi:"relation,account"`
Environments []*Environment `jsonapi:"relation,environments"`
Workspaces []*Workspace `jsonapi:"relation,workspaces"`
}

type SlackIntegrationList struct {
*Pagination
Items []*SlackIntegration
}

type SlackIntegrationListOptions struct {
ListOptions

Filter *SlackIntegrationFilter `url:"filter,omitempty"`
}

// SlackIntegrationFilter represents the options for filtering Slack integrations.
type SlackIntegrationFilter struct {
Account *string `url:"account,omitempty"`
}

type SlackIntegrationCreateOptions struct {
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"`
Environments []*Environment `jsonapi:"relation,environments"`
Workspaces []*Workspace `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 *IntegrationStatus `jsonapi:"attr,status,omitempty"`
Events []string `jsonapi:"attr,events,omitempty"`

Environments []*Environment `jsonapi:"relation,environments,omitempty"`
Workspaces []*Workspace `jsonapi:"relation,workspaces"`
}

type SlackConnection struct {
ID string `jsonapi:"primary,slack-connections"`
SlackWorkspaceName string `jsonapi:"attr,slack-workspace-name"`

// Relations
Account *Account `jsonapi:"relation,account"`
}

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

return c, nil
}
Loading

0 comments on commit b79ba53

Please sign in to comment.