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"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>

View File

@ -3,6 +3,7 @@ package comma_list_scanner
import (
"bufio"
"bytes"
"fmt"
"io"
)
@ -18,8 +19,15 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, ','); i >= 0 {
return i + 1, bytes.TrimSpace(data[0:i]), nil
println("data", fmt.Sprintf("%s", data))
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 atEOF {
@ -34,6 +42,7 @@ func NewCommaListScanner(r io.Reader) *CommaListScanner {
func (c *CommaListScanner) Scan() bool {
if c.r.Scan() {
c.text = c.r.Text()
return true
}
c.err = c.r.Err()
return false
@ -42,3 +51,7 @@ func (c *CommaListScanner) Scan() bool {
func (c *CommaListScanner) Text() string {
return c.text
}
func (c *CommaListScanner) Err() error {
return c.err
}

View File

@ -6,22 +6,40 @@ import (
"testing"
)
var testCommaList = []struct {
text string
out []string
}{
{"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}},
{"hello, wow this is cool, amazing", []string{"hello", "wow this is cool", "amazing"}},
var testCommaList = []string{
"hello, wow-this-is-cool, amazing",
"hello, wow-this-is-cool",
"hello, wow-this-is-cool, ",
"hello, wow-this-is-cool,",
",hello, wow-this-is-cool",
",hello, wow-this-is-cool,",
"hello, wow-this-is-cool,,,",
}
func TestNewCommaListScanner(t *testing.T) {
for _, i := range testCommaList {
t.Run(i.text, func(t *testing.T) {
s := NewCommaListScanner(strings.NewReader(i.text))
n := 0
t.Run(i, func(t *testing.T) {
// use comma list scanner
s := NewCommaListScanner(strings.NewReader(i))
n := strings.Count(i, ",")
a := make([]string, 0, n+1)
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) {
return c.pair[0], c.pair[1]
return strings.TrimSpace(c.pair[0]), strings.TrimSpace(c.pair[1])
}
func (c *ConfigParser) Err() error {

View File

@ -9,7 +9,20 @@ type Config struct {
VirtualMailboxMaps mapProvider.MapProvider
AliasMaps 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) {
@ -25,14 +38,6 @@ func (c *Config) SetKey(k string, m mapProvider.MapProvider) {
case "local_recipient_maps":
c.LocalRecipientMaps = m
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
import (
"bufio"
"errors"
"fmt"
commaListScanner "github.com/1f349/lotus/postfix-config/comma-list-scanner"
configParser "github.com/1f349/lotus/postfix-config/config-parser"
mapProvider "github.com/1f349/lotus/postfix-config/map-provider"
"io"
"path/filepath"
"strings"
)
type Decoder struct {
r *configParser.ConfigParser
v *Config
t map[string]string
temp map[string]string
basePath string
}
func NewDecoder(r io.Reader) *Decoder {
@ -20,30 +22,87 @@ func NewDecoder(r io.Reader) *Decoder {
}
func (d *Decoder) Load() error {
d.v = &Config{}
for d.r.Scan() {
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{}
s := bufio.NewScanner(strings.NewReader(v))
s.Split(bufio.ScanWords)
s := commaListScanner.NewCommaListScanner(strings.NewReader(v))
for s.Scan() {
a := s.Text()
println("a", a)
if strings.HasPrefix(a, "$") {
// is variable
m = append(m, &mapProvider.Variable{Name: a[1:]})
continue
}
n := strings.IndexByte(a, ':')
if n == -1 {
return fmt.Errorf("missing prefix")
v2, err := d.createValue(a)
if err != nil {
return err
}
m = append(m, v2)
}
if err := s.Err(); err != nil {
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()
}
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"
configParser "github.com/1f349/lotus/postfix-config/config-parser"
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)
@ -12,7 +14,15 @@ import (
var exampleConfig []byte
func TestDecoder_Load(t *testing.T) {
// get working directory
wd, err := os.Getwd()
assert.NoError(t, err)
// read example config
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())
}

View File

@ -1,10 +1,10 @@
# this only contains the relevant config properties
recipient_delimiter = +
#recipient_delimiter = +
virtual_mailbox_domains = mysql:/etc/postfix/sql/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_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
alias_maps = hash:/etc/aliases $virtual_alias_maps
virtual_mailbox_domains = mysql:mysql_virtual_domains_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: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:aliases.txt $virtual_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