Skip to content

Commit

Permalink
Merge pull request #5 from jop-software/new-config
Browse files Browse the repository at this point in the history
task: initiate new config
  • Loading branch information
cngJo authored Sep 8, 2022
2 parents 27b5046 + e411030 commit 88ffd29
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 43 deletions.
18 changes: 17 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,20 @@ jobs:
- name: Run Vet & Lint
run: |
go vet .
golint -set_exit_status=1 .
golint -set_exit_status=1 .
test:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '>=1.18.0'

- name: Install dependencies
run: |
go version
- name: Test
run: go test ./...
4 changes: 3 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ jobs:
ci:
uses: './.github/workflows/ci.yaml'

test:
uses: './.github/workflows/ci.yaml'

release:
runs-on: ubuntu-22.04
needs: [ "ci" ]
needs: [ "ci", "test" ]

steps:
- name: Checkout
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
/.idea

.env
imap-mailbox-exporter
imap-mailbox-exporter
/config.yaml
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
BINARY=imap-mailbox-exporter

build:
go build -o $(BINARY) ./...
go build -o $(BINARY) main.go

run: build
./$(BINARY)
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ http://127.0.0.1:9101/probe?target=INBOX
probe_mailbox_count 0
```

### Configuration

The `imap-mailbox-exporter` can be configures with a `config.yaml` file and environment variables.

```yaml
server:
- hostname: 'hostname'
port: '1234'
accounts:
- username: '[email protected]'
password: 'env:E_AT_MAIL_COM_PASSWORD'
```
You can use environment variables with the `env:VARIABLE_NAME` directive in YAML.

The configuration file is expected in `./config.yaml` relative to the `imap-mailbox-exporter` binary.

### Example Usage

You can find a example docker compose configuration.
Expand Down
89 changes: 89 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package config

import (
"errors"
"fmt"
"log"
"os"
"regexp"
"strings"

"gopkg.in/yaml.v2"
)

type ConfigAcccount struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
}

type ConfigServer struct {
Host string `yaml:"hostname"`
Port string `yaml:"port"`

Account []ConfigAcccount `yaml:"accounts"`
}

func (configServer ConfigServer) HostPort() string {
return configServer.Host + ":" + configServer.Port
}

type Config struct {
Server []ConfigServer `yaml:"server"`
}

func NewConfig(path string) (*Config, error) {
config := &Config{}

configBytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}

configString := replaceEnvPlaceholder(string(configBytes))

err = yaml.Unmarshal([]byte(configString), &config)
if err != nil {
return nil, err
}

return config, nil
}

func replaceEnvPlaceholder(data string) string {
expr := regexp.MustCompile("env:([A-Z_]+)")
matches := expr.FindAll([]byte(data), -1)

for _, match := range matches {
variable := string(match)
variable = strings.TrimLeft(variable, "env:")

env := os.Getenv(variable)
if env == "" {
log.Printf("Environment variable %s is empty. Skipping replacement.", variable)
continue
}

data = strings.ReplaceAll(data, fmt.Sprintf("env:%s", variable), env)
}

return data
}

// Find the account and server from the given hostname and username
func (config Config) FindAccountInServer(hostname, username string) (*ConfigServer, *ConfigAcccount, error) {
for _, server := range config.Server {
if server.Host != hostname {
continue
}

for _, account := range server.Account {
if account.Username != username {
continue
}

return &server, &account, nil
}
}

return nil, nil, errors.New("cound not find user on given server in configuration")
}
18 changes: 18 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package config

import (
"testing"
)

func TestConfigServerHostPort(t *testing.T) {
server := &ConfigServer{
Host: "hostname",
Port: "0000",
}

result := server.HostPort()
if result != "hostname:0000" {
t.Logf("Expected hostname:0000, got %s", result)
t.Fail()
}
}
6 changes: 6 additions & 0 deletions examples/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
server:
- hostname: 'imap.mail.com'
port: '993'
accounts:
- username: '[email protected]'
password: 'env:E_MAIL_COM_PW'
2 changes: 2 additions & 0 deletions examples/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ services:

imap-exporter:
image: "ghcr.io/jop-software/imap-mailbox-exporter:latest"
volumes:
- ./config.yaml:/config.yaml
env_file:
- "imap-exporter.env"
ports:
Expand Down
4 changes: 1 addition & 3 deletions examples/imap-exporter.env
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
IMAP_SERVER=""
IMAP_USERNAME=""
IMAP_PASSWORD=""
E_MAIL_COM_PW="my-very-secure-password"
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ require (
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
Expand Down
62 changes: 26 additions & 36 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,29 @@
package main

import (
"errors"
"fmt"
"log"
"net/http"
"os"

"github.com/emersion/go-imap/client"
"github.com/joho/godotenv"
"github.com/jop-software/imap-mailbox-exporter/config"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Config holds the base IMAP credentialsf
type Config struct {
ImapUsername string
ImapPassword string
ImapServer string
}

func (c *Config) validate() bool {
return c.ImapUsername != "" && c.ImapServer != "" && c.ImapPassword != ""
}

// NewConfig creates and validated a new Config struct from environment variables
func NewConfig() (*Config, error) {
config := &Config{
ImapServer: os.Getenv("IMAP_SERVER"),
ImapUsername: os.Getenv("IMAP_USERNAME"),
ImapPassword: os.Getenv("IMAP_PASSWORD"),
}

if !config.validate() {
return nil, errors.New("not all needed configuration flags could be found")
}

return config, nil
}

var config *Config
var cfg *config.Config

func countMailsInMailbox(mailbox string) (uint32, error) {
c, err := client.DialTLS(config.ImapServer, nil)
func countMailsInMailbox(server config.ConfigServer, account config.ConfigAcccount, mailbox string) (uint32, error) {
c, err := client.DialTLS(server.HostPort(), nil)
if err != nil {
return 0, err
}

defer c.Logout()

// Login
if err := c.Login(config.ImapUsername, config.ImapPassword); err != nil {
if err := c.Login(account.Username, account.Password); err != nil {
return 0, err
}

Expand All @@ -68,12 +41,12 @@ func main() {
_ = godotenv.Load()

// Intialize Config
conf, err := NewConfig()
conf, err := config.NewConfig("./config.yaml")
if err != nil {
log.Fatalf("Could not load configuration: %v", err)
}

config = conf
cfg = conf

http.HandleFunc("/-/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
Expand All @@ -93,6 +66,18 @@ func main() {

mailbox := target

hostname := r.URL.Query().Get("hostname")
if hostname == "" {
http.Error(w, "Hostname parameter is missing", http.StatusBadRequest)
return
}

username := r.URL.Query().Get("username")
if username == "" {
http.Error(w, "Username parameter is missing", http.StatusBadRequest)
return
}

probeCountGauge := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "probe_mailbox_count",
Help: "Displays the count of mails found in the mailbox",
Expand All @@ -101,15 +86,20 @@ func main() {
registry := prometheus.NewRegistry()
registry.MustRegister(probeCountGauge)

server, account, err := cfg.FindAccountInServer(hostname, username)
if err != nil {
log.Fatalf("Error: %v", err)
}

// TODO: Proper error handling
count, err := countMailsInMailbox(mailbox)
count, err := countMailsInMailbox(*server, *account, mailbox)
if err != nil {
log.Printf("Cound not load mailbox data: %v", err)
http.Error(w, fmt.Sprintf("Cound not load mailbox data: %v", err), http.StatusInternalServerError)
return
}

log.Printf("Loaded mail count for mailbox %s: %d", mailbox, count)
log.Printf("Load mailbox count for %s of %s on %s: %d", mailbox, username, hostname, count)

probeCountGauge.Set(float64(count))

Expand Down

0 comments on commit 88ffd29

Please sign in to comment.