Skip to content

Commit

Permalink
feat: warn about incorrect TTL values (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
favonia committed Aug 14, 2022
1 parent 23c7bfb commit c6a7ea8
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 61 deletions.
20 changes: 19 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,24 @@ func ReadProviderMap(ppfmt pp.PP, field *map[ipnet.Type]provider.Provider) bool
return true
}

func ReadTTL(ppfmt pp.PP, field *api.TTL) bool {
if !ReadNonnegInt(ppfmt, "TTL", (*int)(field)) {
return false
}

// According to [API documentation], the valid range is 1 (auto) and [60, 86400].
// According to [DNS documentation], the valid range is "Auto" and [30, 86400].
// We thus accept the union of both ranges---1 (auto) and [30, 86400].
//
// [API documentation] https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record
// [DNS documentation] https://developers.cloudflare.com/dns/manage-dns-records/reference/ttl

if *field != 1 && (*field < 30 || *field > 86400) {
ppfmt.Warningf(pp.EmojiUserWarning, "TTL value (%i) should be 1 (automatic) or between 30 and 86400", int(*field))
}
return true
}

func ReadProxiedByDomain(ppfmt pp.PP, field *map[api.Domain]bool) bool {
var proxiedDomains, nonProxiedDomains []api.Domain

Expand Down Expand Up @@ -285,7 +303,7 @@ func (c *Config) ReadEnv(ppfmt pp.PP) bool { //nolint:cyclop
!ReadBool(ppfmt, "UPDATE_ON_START", &c.UpdateOnStart) ||
!ReadBool(ppfmt, "DELETE_ON_STOP", &c.DeleteOnStop) ||
!ReadNonnegDuration(ppfmt, "CACHE_EXPIRATION", &c.CacheExpiration) ||
!ReadNonnegInt(ppfmt, "TTL", (*int)(&c.TTL)) ||
!ReadTTL(ppfmt, &c.TTL) ||
!ReadBool(ppfmt, "PROXIED", &c.DefaultProxied) ||
!ReadNonnegDuration(ppfmt, "DETECTION_TIMEOUT", &c.DetectionTimeout) ||
!ReadNonnegDuration(ppfmt, "UPDATE_TIMEOUT", &c.UpdateTimeout) ||
Expand Down
194 changes: 134 additions & 60 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestDefaultConfigNotNil(t *testing.T) {
}

//nolint:paralleltest // environment vars are global
func TestReadAuthToken(t *testing.T) {
func TestReadAuth(t *testing.T) {
unset(t, "CF_API_TOKEN", "CF_API_TOKEN_FILE", "CF_ACCOUNT_ID")

for name, tc := range map[string]struct {
Expand Down Expand Up @@ -80,7 +80,7 @@ func useMemFS(memfs fstest.MapFS) {
}

//nolint:funlen,paralleltest // environment vars and file system are global
func TestReadAuthTokenWithFile(t *testing.T) {
func TestReadAuthWithFile(t *testing.T) {
unset(t, "CF_API_TOKEN", "CF_API_TOKEN_FILE", "CF_ACCOUNT_ID")

for name, tc := range map[string]struct {
Expand Down Expand Up @@ -152,6 +152,99 @@ func TestReadAuthTokenWithFile(t *testing.T) {
}
}

//nolint:funlen,paralleltest // environment vars are global
func TestReadProviderMap(t *testing.T) {
var (
none provider.Provider
cloudflareTrace = provider.NewCloudflareTrace()
cloudflareDOH = provider.NewCloudflareDOH()
local = provider.NewLocal()
ipify = provider.NewIpify()
)

for name, tc := range map[string]struct {
ip4Provider string
ip6Provider string
expected map[ipnet.Type]provider.Provider
ok bool
prepareMockPP func(*mocks.MockPP)
}{
"full": {
"cloudflare.trace", "ipify",
map[ipnet.Type]provider.Provider{
ipnet.IP4: cloudflareTrace,
ipnet.IP6: ipify,
},
true,
nil,
},
"4": {
"local", " ",
map[ipnet.Type]provider.Provider{
ipnet.IP4: local,
ipnet.IP6: local,
},
true,
func(m *mocks.MockPP) {
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP6_PROVIDER", "local")
},
},
"6": {
" ", "cloudflare.doh",
map[ipnet.Type]provider.Provider{
ipnet.IP4: none,
ipnet.IP6: cloudflareDOH,
},
true,
func(m *mocks.MockPP) {
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP4_PROVIDER", "none")
},
},
"empty": {
" ", " ",
map[ipnet.Type]provider.Provider{
ipnet.IP4: none,
ipnet.IP6: local,
},
true,
func(m *mocks.MockPP) {
gomock.InOrder(
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP4_PROVIDER", "none"),
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP6_PROVIDER", "local"),
)
},
},
"illformed": {
" flare", " ",
map[ipnet.Type]provider.Provider{
ipnet.IP4: none,
ipnet.IP6: local,
},
false,
func(m *mocks.MockPP) {
m.EXPECT().Errorf(pp.EmojiUserError, "Failed to parse %q: not a valid provider", "flare")
},
},
} {
tc := tc
t.Run(name, func(t *testing.T) {
mockCtrl := gomock.NewController(t)

store(t, "IP4_PROVIDER", tc.ip4Provider)
store(t, "IP6_PROVIDER", tc.ip6Provider)

field := map[ipnet.Type]provider.Provider{ipnet.IP4: none, ipnet.IP6: local}
mockPP := mocks.NewMockPP(mockCtrl)
if tc.prepareMockPP != nil {
tc.prepareMockPP(mockPP)
}
ok := config.ReadProviderMap(mockPP, &field)
require.Equal(t, tc.ok, ok)
require.Equal(t, tc.expected, field)
})
}
}

//nolint:paralleltest // environment vars are global
func TestReadDomainMap(t *testing.T) {
for name, tc := range map[string]struct {
Expand Down Expand Up @@ -211,95 +304,75 @@ func TestReadDomainMap(t *testing.T) {
}
}

//nolint:funlen,paralleltest // environment vars are global
func TestReadProviderMap(t *testing.T) {
var (
none provider.Provider
cloudflareTrace = provider.NewCloudflareTrace()
cloudflareDOH = provider.NewCloudflareDOH()
local = provider.NewLocal()
ipify = provider.NewIpify()
)

//nolint:paralleltest,funlen // environment vars are global
func TestReadTTL(t *testing.T) {
key := "TTL"
for name, tc := range map[string]struct {
ip4Provider string
ip6Provider string
expected map[ipnet.Type]provider.Provider
set bool
val string
oldField api.TTL
newField api.TTL
ok bool
prepareMockPP func(*mocks.MockPP)
}{
"full": {
"cloudflare.trace", "ipify",
map[ipnet.Type]provider.Provider{
ipnet.IP4: cloudflareTrace,
ipnet.IP6: ipify,
"nil": {
false, "", 100, 100, true,
func(m *mocks.MockPP) {
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%d", key, 100)
},
true,
nil,
},
"4": {
"local", " ",
map[ipnet.Type]provider.Provider{
ipnet.IP4: local,
ipnet.IP6: local,
},
true,
"empty": {
true, "", 100, 100, true,
func(m *mocks.MockPP) {
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP6_PROVIDER", "local")
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%d", key, 100)
},
},
"6": {
" ", "cloudflare.doh",
map[ipnet.Type]provider.Provider{
ipnet.IP4: none,
ipnet.IP6: cloudflareDOH,
},
true,
"0": {
true, "0 ", 100, 0, true,
func(m *mocks.MockPP) {
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP4_PROVIDER", "none")
m.EXPECT().Warningf(pp.EmojiUserWarning, "TTL value (%i) should be 1 (automatic) or between 30 and 86400", 0)
},
},
"empty": {
" ", " ",
map[ipnet.Type]provider.Provider{
ipnet.IP4: none,
ipnet.IP6: local,
"-1": {
true, " -1", 100, 100, false,
func(m *mocks.MockPP) {
m.EXPECT().Errorf(pp.EmojiUserError, "Failed to parse %q: %d is negative", "-1", gomock.Any())
},
true,
},
"1": {true, " 1 ", 100, 1, true, nil},
"20": {
true, " 20 ", 100, 20, true,
func(m *mocks.MockPP) {
gomock.InOrder(
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP4_PROVIDER", "none"),
m.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%s", "IP6_PROVIDER", "local"),
)
m.EXPECT().Warningf(pp.EmojiUserWarning, "TTL value (%i) should be 1 (automatic) or between 30 and 86400", 20)
},
},
"illformed": {
" flare", " ",
map[ipnet.Type]provider.Provider{
ipnet.IP4: none,
ipnet.IP6: local,
"words": {
true, " word ", 100, 100, false,
func(m *mocks.MockPP) {
m.EXPECT().Errorf(pp.EmojiUserError, "Failed to parse %q: %v", "word", gomock.Any())
},
false,
},
"9999999": {
true, " 9999999 ", 100, 9999999, true,
func(m *mocks.MockPP) {
m.EXPECT().Errorf(pp.EmojiUserError, "Failed to parse %q: not a valid provider", "flare")
m.EXPECT().Warningf(pp.EmojiUserWarning, "TTL value (%i) should be 1 (automatic) or between 30 and 86400", 9999999)
},
},
} {
tc := tc
t.Run(name, func(t *testing.T) {
mockCtrl := gomock.NewController(t)

store(t, "IP4_PROVIDER", tc.ip4Provider)
store(t, "IP6_PROVIDER", tc.ip6Provider)
set(t, key, tc.set, tc.val)

field := map[ipnet.Type]provider.Provider{ipnet.IP4: none, ipnet.IP6: local}
field := tc.oldField
mockPP := mocks.NewMockPP(mockCtrl)
if tc.prepareMockPP != nil {
tc.prepareMockPP(mockPP)
}
ok := config.ReadProviderMap(mockPP, &field)
ok := config.ReadTTL(mockPP, &field)
require.Equal(t, tc.ok, ok)
require.Equal(t, tc.expected, field)
require.Equal(t, tc.newField, field)
})
}
}
Expand Down Expand Up @@ -636,6 +709,7 @@ func TestReadEnvWithOnlyToken(t *testing.T) {
innerMockPP.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%t", "DELETE_ON_STOP", false),
innerMockPP.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%v", "CACHE_EXPIRATION", time.Duration(0)),
innerMockPP.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%d", "TTL", 0),
innerMockPP.EXPECT().Warningf(pp.EmojiUserWarning, "TTL value (%i) should be 1 (automatic) or between 30 and 86400", 0), //nolint:lll
innerMockPP.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%t", "PROXIED", false),
innerMockPP.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%v", "DETECTION_TIMEOUT", time.Duration(0)),
innerMockPP.EXPECT().Infof(pp.EmojiBullet, "Use default %s=%v", "UPDATE_TIMEOUT", time.Duration(0)),
Expand Down

0 comments on commit c6a7ea8

Please sign in to comment.