Skip to content

Commit

Permalink
AWS SQS Service Added (#144)
Browse files Browse the repository at this point in the history
Signed-off-by: Denis Shatilov <[email protected]>
Co-authored-by: pasha-codefresh <[email protected]>
  • Loading branch information
Rakhmanov and pasha-codefresh authored Aug 9, 2023
1 parent ba73131 commit 8570c23
Show file tree
Hide file tree
Showing 8 changed files with 595 additions and 8 deletions.
106 changes: 106 additions & 0 deletions docs/services/awssqs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# AWS SQS

## Parameters

This notification service is capable of sending simple messages to AWS SQS queue.

* `queue` - name of the queue you are intending to send messages to. Can be overwriten with target destination annotation.
* `region` - region of the sqs queue can be provided via env variable AWS_DEFAULT_REGION
* `key` - optional, aws access key must be either referenced from a secret via variable or via env variable AWS_ACCESS_KEY_ID
* `secret` - optional, aws access secret must be either referenced from a secret via variableor via env variable AWS_SECRET_ACCESS_KEY
* `account` optional, external accountId of the queue
* `endpointUrl` optional, useful for development with localstack

## Example

### Using Secret for credential retrieval:

Resource Annotation:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
notifications.argoproj.io/subscribe.on-deployment-ready.awssqs: "overwrite-myqueue"
```
* ConfigMap
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: <config-map-name>
data:
service.awssqs: |
region: "us-east-2"
queue: "myqueue"
account: "1234567"
key: "$awsaccess_key"
secret: "$awsaccess_secret"
template.deployment-ready: |
message: |
Deployment {{.obj.metadata.name}} is ready!
trigger.on-deployment-ready: |
- when: any(obj.status.conditions, {.type == 'Available' && .status == 'True'})
send: [deployment-ready]
- oncePer: obj.metadata.annotations["generation"]
```
Secret
```yaml
apiVersion: v1
kind: Secret
metadata:
name: <secret-name>
stringData:
awsaccess_key: test
awsaccess_secret: test
```
### Minimal configuration using AWS Env variables
Ensure following list of enviromental variable is injected via OIDC, or other method. And assuming SQS is local to the account.
You may skip usage of secret for sensitive data and omit other parameters. (Setting parameters via ConfigMap takes precedent.)
Variables:
```bash
export AWS_ACCESS_KEY_ID="test"
export AWS_SECRET_ACCESS_KEY="test"
export AWS_DEFAULT_REGION="us-east-1"
```

Resource Annotation:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
annotations:
notifications.argoproj.io/subscribe.on-deployment-ready.awssqs: ""
```
* ConfigMap
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: <config-map-name>
data:
service.awssqs: |
queue: "myqueue"
template.deployment-ready: |
message: |
Deployment {{.obj.metadata.name}} is ready!
trigger.on-deployment-ready: |
- when: any(obj.status.conditions, {.type == 'Available' && .status == 'True'})
send: [deployment-ready]
- oncePer: obj.metadata.annotations["generation"]
```
1 change: 1 addition & 0 deletions docs/services/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ metadata:

## Service Types

* [AwsSqs](./awssqs.md)
* [Email](./email.md)
* [GitHub](./github.md)
* [Slack](./slack.md)
Expand Down
2 changes: 2 additions & 0 deletions examples/certmanager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,5 @@ go run examples/certmanager/cli/main.go trigger run on-cert-ready <MY-CERT> --co
```
go run examples/certmanager/cli/main.go --help
```


19 changes: 18 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ require (
github.com/PagerDuty/go-pagerduty v1.7.0
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60
github.com/antonmedv/expr v1.12.6
github.com/aws/aws-sdk-go-v2/credentials v1.13.8
github.com/aws/aws-sdk-go-v2/service/sqs v1.20.0
github.com/bradleyfalzon/ghinstallation/v2 v2.5.0
github.com/ghodss/yaml v1.0.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/golang/mock v1.6.0
github.com/google/go-github/v41 v41.0.0
Expand All @@ -30,11 +33,25 @@ require (
sigs.k8s.io/yaml v1.3.0
)

require (
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
)

require (
cloud.google.com/go v0.99.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.3
github.com/aws/aws-sdk-go-v2/config v1.18.8
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
Expand Down
28 changes: 28 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,32 @@ github.com/antonmedv/expr v1.12.6/go.mod h1:FPC8iWArxls7axbVLsW+kpg1mz29A1b2M6jt
github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/config v1.18.8 h1:lDpy0WM8AHsywOnVrOHaSMfpaiV2igOw8D7svkFkXVA=
github.com/aws/aws-sdk-go-v2/config v1.18.8/go.mod h1:5XCmmyutmzzgkpk/6NYTjeWb6lgo9N170m1j6pQkIBs=
github.com/aws/aws-sdk-go-v2/credentials v1.13.8 h1:vTrwTvv5qAwjWIGhZDSBH/oQHuIQjGmD232k01FUh6A=
github.com/aws/aws-sdk-go-v2/credentials v1.13.8/go.mod h1:lVa4OHbvgjVot4gmh1uouF1ubgexSCN92P6CJQpT0t8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k=
github.com/aws/aws-sdk-go-v2/service/sqs v1.20.0 h1:tQoMg8i4nFAB70cJ4wiAYEiZRYo2P6uDmU2D6ys/igo=
github.com/aws/aws-sdk-go-v2/service/sqs v1.20.0/go.mod h1:jQhN5f4p3PALMNlUtfb/0wGIFlV7vGtJlPDVfxfNfPY=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 h1:Jfly6mRxk2ZOSlbCvZfKNS7TukSx1mIzhSsqZ/IGSZI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 h1:kOO++CYo50RcTFISESluhWEi5Prhg+gaSs4whWabiZU=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
Expand Down Expand Up @@ -293,6 +319,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
Expand Down
178 changes: 178 additions & 0 deletions pkg/services/awssqs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package services

import (
"bytes"
"context"
"os"
texttemplate "text/template"

log "github.com/sirupsen/logrus"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)

type AwsSqsNotification struct {
MessageAttributes map[string]string `json:"messageAttributes"`
}

type AwsSqsOptions struct {
Queue string `json:"queue"`
Account string `json:"account"`
Region string `json:"region"`
EndpointUrl string `json:"endpointUrl,omitempty"`
AwsAccess
}

type AwsAccess struct {
Key string `json:"key"`
Secret string `json:"secret"`
}

func NewAwsSqsService(opts AwsSqsOptions) NotificationService {
return &awsSqsService{opts: opts}
}

type awsSqsService struct {
opts AwsSqsOptions
}

func (s awsSqsService) Send(notif Notification, dest Destination) error {
options := s.setOptions()
cfg, err := config.LoadDefaultConfig(context.TODO(), options...)
if err != nil {
log.Fatalf("failed to load configuration, %v", err)
}

client := sqs.NewFromConfig(cfg)

queueUrl, err := GetQueueURL(context.TODO(), client, s.getQueueInput(dest))
if err != nil {
log.Error("Got an error getting the queue URL: ", err)
return err
}

sendMessage, err := SendMsg(context.TODO(), client, s.sendMessageInput(queueUrl.QueueUrl, notif))
if err != nil {
log.Error("Got an error sending the message: ", err)
return err
}
log.Debug("Message Sent with Id: ", *sendMessage.MessageId)

return nil
}

func (s awsSqsService) sendMessageInput(queueUrl *string, notif Notification) *sqs.SendMessageInput {
return &sqs.SendMessageInput{
QueueUrl: queueUrl,
MessageBody: aws.String(notif.Message),
DelaySeconds: 10,
}

}
func (s awsSqsService) getQueueInput(dest Destination) *sqs.GetQueueUrlInput {
result := &sqs.GetQueueUrlInput{}
result.QueueName = &s.opts.Queue

// Recipient in annotations takes precedent
if dest.Recipient != "" {
result.QueueName = &dest.Recipient
}

// Fill Account from configuration
if s.opts.Account != "" {
result.QueueOwnerAWSAccountId = &s.opts.Account
}
return result
}

func (s awsSqsService) setOptions() []func(*config.LoadOptions) error {
// Slice for AWS config options
var options []func(*config.LoadOptions) error

// When Credentials Are provided in service configuration - use them.
if (s.opts != AwsSqsOptions{} && s.opts.AwsAccess.Key != "" && s.opts.AwsAccess.Secret != "") {
options = append(options, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(s.opts.AwsAccess.Key, s.opts.AwsAccess.Secret, "default")))
}

// Fill Region from configuration
if s.opts.Region != "" {
options = append(options, config.WithRegion(s.opts.Region))
}

// Useful for testing with localstack
if s.opts.EndpointUrl != "" {
endpointRegion := os.Getenv("AWS_DEFAULT_REGION")
if s.opts.Region != "" {
endpointRegion = s.opts.Region
}
customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
if service == sqs.ServiceID {
return aws.Endpoint{
PartitionID: "aws",
URL: s.opts.EndpointUrl,
SigningRegion: endpointRegion,
}, nil
}
// returning EndpointNotFoundError will allow the service to fallback to it's default resolution
return aws.Endpoint{}, &aws.EndpointNotFoundError{}
})
options = append(options, config.WithEndpointResolverWithOptions(customResolver))
}
return options
}

func (n *AwsSqsNotification) GetTemplater(name string, f texttemplate.FuncMap) (Templater, error) {
return func(notification *Notification, vars map[string]interface{}) error {
if notification.AwsSqs == nil {
notification.AwsSqs = &AwsSqsNotification{}
}

if len(n.MessageAttributes) > 0 {
notification.AwsSqs.MessageAttributes = n.MessageAttributes
if err := notification.AwsSqs.parseMessageAttributes(name, f, vars); err != nil {
return err
}
}

return nil
}, nil
}

func (n *AwsSqsNotification) parseMessageAttributes(name string, f texttemplate.FuncMap, vars map[string]interface{}) error {
for k, v := range n.MessageAttributes {
var tempData bytes.Buffer

tmpl, err := texttemplate.New(name).Funcs(f).Parse(v)
if err != nil {
continue
}
if err := tmpl.Execute(&tempData, vars); err != nil {
return err
}
if val := tempData.String(); val != "" {
n.MessageAttributes[k] = val
}
}
return nil
}

type SQSSendMessageAPI interface {
GetQueueUrl(ctx context.Context,
params *sqs.GetQueueUrlInput,
optFns ...func(*sqs.Options)) (*sqs.GetQueueUrlOutput, error)

SendMessage(ctx context.Context,
params *sqs.SendMessageInput,
optFns ...func(*sqs.Options)) (*sqs.SendMessageOutput, error)
}

var GetQueueURL = func(c context.Context, api SQSSendMessageAPI, input *sqs.GetQueueUrlInput) (*sqs.GetQueueUrlOutput, error) {
return api.GetQueueUrl(c, input)
}

var SendMsg = func(c context.Context, api SQSSendMessageAPI, input *sqs.SendMessageInput) (*sqs.SendMessageOutput, error) {
return api.SendMessage(c, input)
}
Loading

0 comments on commit 8570c23

Please sign in to comment.