Some improvements to postfix config tests

This commit is contained in:
Melon 2023-08-22 13:38:01 +01:00
parent 7698bc9d50
commit d6048ccc6e
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
23 changed files with 156 additions and 47 deletions

7
.idea/discord.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@ -1,9 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
<component name="MarkdownSettingsMigration"> <component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" /> <option name="stateVersion" value="1" />
</component> </component>

View File

@ -3,6 +3,7 @@ package comma_list_scanner
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"fmt"
"io" "io"
) )
@ -18,8 +19,15 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner {
if atEOF && len(data) == 0 { if atEOF && len(data) == 0 {
return 0, nil, nil return 0, nil, nil
} }
if i := bytes.IndexByte(data, ','); i >= 0 { println("data", fmt.Sprintf("%s", data))
return i + 1, bytes.TrimSpace(data[0:i]), nil println("index", bytes.IndexAny(data, " ,"))
if i := bytes.IndexAny(data, " ,"); i >= 0 {
// consume all spaces after the comma
j := i + 1
for j < len(data) && data[j] == ' ' {
j++
}
return j, bytes.TrimSpace(data[0:i]), nil
} }
// If we're at EOF, we have a final non-terminated line. Return it. // If we're at EOF, we have a final non-terminated line. Return it.
if atEOF { if atEOF {
@ -34,6 +42,7 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner {
func (c *CommaListScanner) Scan() bool { func (c *CommaListScanner) Scan() bool {
if c.r.Scan() { if c.r.Scan() {
c.text = c.r.Text() c.text = c.r.Text()
return true
} }
c.err = c.r.Err() c.err = c.r.Err()
return false return false
@ -42,3 +51,7 @@ func (c *CommaListScanner) Scan() bool {
func (c *CommaListScanner) Text() string { func (c *CommaListScanner) Text() string {
return c.text return c.text
} }
func (c *CommaListScanner) Err() error {
return c.err
}

View File

@ -6,22 +6,40 @@ import (
"testing" "testing"
) )
var testCommaList = []struct { var testCommaList = []string{
text string "hello, wow-this-is-cool, amazing",
out []string "hello, wow-this-is-cool",
}{ "hello, wow-this-is-cool, ",
{"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}}, "hello, wow-this-is-cool,",
{"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}}, ",hello, wow-this-is-cool",
",hello, wow-this-is-cool,",
"hello, wow-this-is-cool,,,",
} }
func TestNewCommaListScanner(t *testing.T) { func TestNewCommaListScanner(t *testing.T) {
for _, i := range testCommaList { for _, i := range testCommaList {
t.Run(i.text, func(t *testing.T) { t.Run(i, func(t *testing.T) {
s := NewCommaListScanner(strings.NewReader(i.text)) // use comma list scanner
n := 0 s := NewCommaListScanner(strings.NewReader(i))
n := strings.Count(i, ",")
a := make([]string, 0, n+1)
for s.Scan() { for s.Scan() {
assert.Equal(t, i.out[n], s.Text()) a = append(a, s.Text())
} }
assert.NoError(t, s.Err())
// test against splitting and trimming strings
b := strings.Split(i, ",")
for i := 0; i < len(b); i++ {
c := strings.TrimSpace(b[i])
if c == "" {
b = append(b[0:i], b[i+1:]...)
i--
} else {
b[i] = c
}
}
assert.Equal(t, b, a)
}) })
} }
} }

View File

@ -38,7 +38,7 @@ scanAgain:
} }
func (c *ConfigParser) Pair() (string, string) { func (c *ConfigParser) Pair() (string, string) {
return c.pair[0], c.pair[1] return strings.TrimSpace(c.pair[0]), strings.TrimSpace(c.pair[1])
} }
func (c *ConfigParser) Err() error { func (c *ConfigParser) Err() error {

View File

@ -9,7 +9,20 @@ type Config struct {
VirtualMailboxMaps mapProvider.MapProvider VirtualMailboxMaps mapProvider.MapProvider
AliasMaps mapProvider.MapProvider AliasMaps mapProvider.MapProvider
LocalRecipientMaps mapProvider.MapProvider LocalRecipientMaps mapProvider.MapProvider
SmtpdSenderLoginMaps string // TODO(melon): union map? SmtpdSenderLoginMaps mapProvider.MapProvider
}
var parseProviderData = map[string]string{
"virtual_mailbox_domains": "comma",
"virtual_alias_maps": "comma",
"virtual_mailbox_maps": "comma",
"alias_maps": "comma",
"local_recipient_maps": "comma",
"smtpd_sender_login_maps": "union",
}
func (c *Config) ParseProvider(k string) string {
return parseProviderData[k]
} }
func (c *Config) SetKey(k string, m mapProvider.MapProvider) { func (c *Config) SetKey(k string, m mapProvider.MapProvider) {
@ -25,14 +38,6 @@ func (c *Config) SetKey(k string, m mapProvider.MapProvider) {
case "local_recipient_maps": case "local_recipient_maps":
c.LocalRecipientMaps = m c.LocalRecipientMaps = m
case "smtpd_sender_login_maps": case "smtpd_sender_login_maps":
c.SmtpdSenderLoginMaps = "<ERROR>" c.SmtpdSenderLoginMaps = m
} }
} }
func (c *Config) NeedsMapProvider(k string) bool {
switch k {
case "virtual_mailbox_domains", "virtual_alias_maps", "virtual_mailbox_maps", "alias_maps", "local_recipient_maps", "smtpd_sender_login_maps":
return true
}
return false
}

View File

@ -1,18 +1,20 @@
package postfix_config package postfix_config
import ( import (
"bufio" "errors"
"fmt" "fmt"
commaListScanner "github.com/1f349/lotus/postfix-config/comma-list-scanner"
configParser "github.com/1f349/lotus/postfix-config/config-parser" configParser "github.com/1f349/lotus/postfix-config/config-parser"
mapProvider "github.com/1f349/lotus/postfix-config/map-provider" mapProvider "github.com/1f349/lotus/postfix-config/map-provider"
"io" "io"
"path/filepath"
"strings" "strings"
) )
type Decoder struct { type Decoder struct {
r *configParser.ConfigParser r *configParser.ConfigParser
v *Config temp map[string]string
t map[string]string basePath string
} }
func NewDecoder(r io.Reader) *Decoder { func NewDecoder(r io.Reader) *Decoder {
@ -20,30 +22,87 @@ func NewDecoder(r io.Reader) *Decoder {
} }
func (d *Decoder) Load() error { func (d *Decoder) Load() error {
d.v = &Config{}
for d.r.Scan() { for d.r.Scan() {
k, v := d.r.Pair() k, v := d.r.Pair()
if d.v.NeedsMapProvider(k) { d.temp[k] = v
}
if err := d.r.Err(); err != nil {
return err
}
switch d.value.ParseProvider(k) {
case "comma":
m := mapProvider.SequenceMapProvider{} m := mapProvider.SequenceMapProvider{}
s := bufio.NewScanner(strings.NewReader(v)) s := commaListScanner.NewCommaListScanner(strings.NewReader(v))
s.Split(bufio.ScanWords)
for s.Scan() { for s.Scan() {
a := s.Text() a := s.Text()
println("a", a) println("a", a)
if strings.HasPrefix(a, "$") { if strings.HasPrefix(a, "$") {
// is variable m = append(m, &mapProvider.Variable{Name: a[1:]})
continue
} }
n := strings.IndexByte(a, ':')
if n == -1 { v2, err := d.createValue(a)
return fmt.Errorf("missing prefix") if err != nil {
return err
} }
m = append(m, v2)
} }
if err := s.Err(); err != nil { if err := s.Err(); err != nil {
return err return err
} }
d.v.SetKey(k, m) d.value.SetKey(k, m)
case "union":
if !strings.HasPrefix(v, "unionmap:{") || !strings.HasSuffix(v, "}") {
return errors.New("key requires a union map")
}
v = v[len("unionmap:{") : len(v)-1]
m := mapProvider.SequenceMapProvider{}
s := commaListScanner.NewCommaListScanner(strings.NewReader(v))
for s.Scan() {
a := s.Text()
v2, err := d.createValue(a)
if err != nil {
return err
}
m = append(m, v2)
}
default:
return fmt.Errorf("key '%s' has no defined parse provider", k)
} }
} }
return d.r.Err() return d.r.Err()
} }
func (d *Decoder) createValue(a string) (mapProvider.MapProvider, error) {
n := strings.IndexByte(a, ':')
if n == -1 {
return nil, fmt.Errorf("missing prefix")
}
namespace := a[:n]
value := a[n+1:]
switch namespace {
case "mysql":
if !filepath.IsAbs(value) {
value = filepath.Join(d.basePath, value)
}
provider, err := mapProvider.NewMySqlMapProvider(value)
if err != nil {
return nil, err
}
return provider, nil
case "hash":
if !filepath.IsAbs(value) {
value = filepath.Join(d.basePath, value)
}
provider, err := mapProvider.NewHashMapProvider(value)
if err != nil {
return nil, err
}
return provider, nil
}
return nil, errors.New("invalid provider namespace")
}

View File

@ -5,6 +5,8 @@ import (
_ "embed" _ "embed"
configParser "github.com/1f349/lotus/postfix-config/config-parser" configParser "github.com/1f349/lotus/postfix-config/config-parser"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing" "testing"
) )
@ -12,7 +14,15 @@ import (
var exampleConfig []byte var exampleConfig []byte
func TestDecoder_Load(t *testing.T) { func TestDecoder_Load(t *testing.T) {
// get working directory
wd, err := os.Getwd()
assert.NoError(t, err)
// read example config
b := bytes.NewReader(exampleConfig) b := bytes.NewReader(exampleConfig)
d := &Decoder{r: configParser.NewConfigParser(b)} d := &Decoder{
r: configParser.NewConfigParser(b),
basePath: filepath.Join(wd, "test-data"),
}
assert.NoError(t, d.Load()) assert.NoError(t, d.Load())
} }

View File

@ -1,10 +1,10 @@
# this only contains the relevant config properties # this only contains the relevant config properties
recipient_delimiter = + #recipient_delimiter = +
virtual_mailbox_domains = mysql:/etc/postfix/sql/mysql_virtual_domains_maps.cf virtual_mailbox_domains = mysql:mysql_virtual_domains_maps.cf
virtual_alias_maps = mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_wildcard_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_catchall_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_catchall_maps.cf virtual_alias_maps = mysql:mysql_virtual_alias_maps.cf, mysql:mysql_virtual_alias_wildcard_maps.cf, mysql:mysql_virtual_alias_domain_maps.cf, mysql:mysql_virtual_alias_user_maps.cf, mysql:mysql_virtual_alias_userdomain_maps.cf, mysql:mysql_virtual_alias_domain_catchall_maps.cf, mysql:mysql_virtual_alias_user_catchall_maps.cf
virtual_mailbox_maps = mysql:/etc/postfix/sql/mysql_virtual_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_mailbox_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_mailbox_maps.cf virtual_mailbox_maps = mysql:mysql_virtual_mailbox_maps.cf, mysql:mysql_virtual_alias_domain_mailbox_maps.cf, mysql:mysql_virtual_alias_user_mailbox_maps.cf, mysql:mysql_virtual_alias_userdomain_mailbox_maps.cf
alias_maps = hash:/etc/aliases $virtual_alias_maps alias_maps = hash:aliases.txt $virtual_alias_maps
local_recipient_maps = $virtual_mailbox_maps $alias_maps local_recipient_maps = $virtual_mailbox_maps $alias_maps
smtpd_sender_login_maps = unionmap:{ hash:/etc/aliases, mysql:/etc/postfix/sql/mysql_sender_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_domain_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_user_maps.cf, mysql:/etc/postfix/sql/mysql_virtual_alias_userdomain_maps.cf } smtpd_sender_login_maps = unionmap:{ hash:aliases.txt, mysql:mysql_sender_alias_maps.cf, mysql:mysql_virtual_alias_maps.cf, mysql:mysql_virtual_alias_domain_maps.cf, mysql:mysql_virtual_alias_user_maps.cf, mysql:mysql_virtual_alias_userdomain_maps.cf }

View File

@ -0,0 +1 @@
a: a b