Skip to content

Commit

Permalink
feat: welcome erida!
Browse files Browse the repository at this point in the history
  • Loading branch information
fadyat committed Nov 11, 2023
0 parents commit ed7664a
Show file tree
Hide file tree
Showing 17 changed files with 1,161 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: publish

on:
push:
branches: [ 'master' ]

jobs:
publish:
name: publish
permissions: write-all
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- uses: actions/checkout@v3
- uses: ko-build/setup-ko@v0.6
- run: ko build --platform=linux/amd64 --bare
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.env
.idea
.DS_Store
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ifneq (,$(wildcard ./.env))
include .env
export
endif

run:
@go run main.go

integration:
@go test -v ./... -tags=integration


49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
### Erida: Simplifying Internal Cluster Communication

**Erida** is a straightforward SMTP relay server designed for sending internal cluster emails to an authenticated SMTP
server with seamless Slack integration.

### Email Address Flexibility

Erida supports a variety of email addresses, including common ones like `avfadeev@gmail.com` and those associated with
messaging services, particularly Slack.

For Slack integration, you can use addresses following this syntax:

- User-specific Slack address: `personal.<username>@slack`
- Channel-specific Slack address: `channel.<channelname>@slack`

It's important to note that both the `username` and `channelname` are case-insensitive, and the bot must have the
necessary
permissions to access the specified Slack channels.

### Configuration Made Easy

Configuring Erida is a breeze. All you need to do is set the following environment variables:

- `SMTP_HOST`: SMTP server host
- `SMTP_PORT`: SMTP server port
- `SMTP_USER`: SMTP server username
- `SMTP_PASSWORD`: SMTP server password
- `SLACK_TOKEN`: Slack bot token
- `SMTP_TLS` (Optional, default: true): Enable or disable Start TLS usage

For additional variables, refer to the [configuration file](internal/config.go).

### Getting Started

If you're new to configuring the bot, check out the step-by-step guide
at [Slack Quickstart](https://api.slack.com/start/quickstart).

Ensure that the bot has the necessary permissions, specifically `chat:write`.

### Example Usage

Let's walk through an example. Assume that an external SMTP server is configured to send emails to **Erida** with the
following addresses: `personal.fadyat@slack`, `channel.general@slack`, and `avfadeev@gmail.com`.

The message will be seamlessly delivered to the Slack channel `#general` and the Slack user `@fadyat`, as well as to the
email address `avfadeev@gmail.com`.

**Erida** simplifies internal communication, bridging the gap between email and Slack effortlessly.

24 changes: 24 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module github.com/fadyat/erida

go 1.21

require (
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
github.com/emersion/go-smtp v0.19.0
github.com/ilyakaznacheev/cleanenv v1.5.0
github.com/mocktools/go-smtp-mock/v2 v2.1.0
github.com/slack-go/slack v0.12.3
github.com/stretchr/testify v1.8.4
)

require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
golang.org/x/net v0.18.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
)
46 changes: 46 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.19.0 h1:iVCDtR2/JY3RpKoaZ7u6I/sb52S3EzfNHO1fAWVHgng=
github.com/emersion/go-smtp v0.19.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mocktools/go-smtp-mock/v2 v2.1.0 h1:gGiWqlaMTExk7Id38G2+sWfOelsE+OAqJWAMsAI/654=
github.com/mocktools/go-smtp-mock/v2 v2.1.0/go.mod h1:n8aNpDYncZHH/cZHtJKzQyeYT/Dut00RghVM+J1Ed94=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/slack-go/slack v0.12.3 h1:92/dfFU8Q5XP6Wp5rr5/T5JHLM5c5Smtn53fhToAP88=
github.com/slack-go/slack v0.12.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
106 changes: 106 additions & 0 deletions internal/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package internal

import (
"fmt"
"github.com/slack-go/slack"
"strings"
)

const (
emailRecipient = "email"
slackRecipient = "slack"

personalMessage = "personal."
channelMessage = "channel."
)

var (
operators = map[string]map[string]string{
slackRecipient: {
personalMessage: "@",
channelMessage: "#",
},
}
)

type client interface {

// proxify is a function, that will be used as a proxy for the data.
proxify(from string, to []string, body string) error
}

func takeUsernames(recipients []string, recipientType string) []string {
var usernames = make([]string, 0, len(recipients))

for _, r := range recipients {
at := strings.Split(r, "@")
if len(at) != 2 {
continue
}

if at[1] != recipientType {
continue
}

usernames = append(usernames, convertToRecipientWay(at[0], recipientType))
}

return usernames
}

func convertToRecipientWay(username string, recipientType string) string {
if _, ok := operators[recipientType]; !ok {
return username
}

var msgType string
switch {
case strings.HasPrefix(username, personalMessage):
msgType = personalMessage
case strings.HasPrefix(username, channelMessage):
msgType = channelMessage
}

operator := operators[recipientType][msgType]
return operator + strings.TrimPrefix(username, msgType)
}

func selectClientType(recipient string) (string, error) {
at := strings.Split(recipient, "@")
if len(at) != 2 {
return "", fmt.Errorf("invalid recipient: %s", recipient)
}

domain := at[1]
if domain == slackRecipient {
return slackRecipient, nil
}

return emailRecipient, nil
}

func selectClient(clientType string, cfg *Config) (client, error) {
switch clientType {
case emailRecipient:
return newMailClient(cfg)
case slackRecipient:
return newSlackClient(slack.New(cfg.SlackToken)), nil
default:
return nil, fmt.Errorf("unknown client type: %s", clientType)
}
}

func groupByClientType(recipients []string) (map[string][]string, error) {
groups := make(map[string][]string)

for _, recipient := range recipients {
clientType, err := selectClientType(recipient)
if err != nil {
return nil, fmt.Errorf("failed to select client type: %w", err)
}

groups[clientType] = append(groups[clientType], recipient)
}

return groups, nil
}
Loading

0 comments on commit ed7664a

Please sign in to comment.