Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(provider): add undocumented debug.const provider #966

Merged
merged 1 commit into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions internal/config/env_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ func ReadProvider(ppfmt pp.PP, key, keyDeprecated string, field *provider.Provid
return false
}
ppfmt.Hintf(pp.HintExperimentalLocalWithInterface,
`You are using the experimental provider "local.iface:%s" added in version 1.15.0`,
parts[1])
`You are using the experimental "local.iface" provider added in version 1.15.0`)
*field = provider.NewLocalWithInterface(parts[1])
return true
case len(parts) == 2 && parts[0] == "url":
Expand All @@ -136,6 +135,21 @@ func ReadProvider(ppfmt pp.PP, key, keyDeprecated string, field *provider.Provid
case len(parts) == 1 && parts[0] == "none":
*field = nil
return true
case len(parts) == 2 && parts[0] == "debug.const":
ppfmt.Hintf(pp.HintDebugConstProvider, `You are using the undocumented "debug.const" provider`)
if parts[1] == "" {
ppfmt.Noticef(
pp.EmojiUserError,
`%s=debug.const: must be followed by an IP address`,
key,
)
return false
}
p, ok := provider.NewDebugConst(ppfmt, parts[1])
if ok {
*field = p
}
return ok
default:
ppfmt.Noticef(pp.EmojiUserError, "%s (%q) is not a valid provider", key, val)
return false
Expand Down
18 changes: 17 additions & 1 deletion internal/config/env_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func TestReadProvider(t *testing.T) {
localLoopback = provider.NewLocalWithInterface("lo")
ipify = provider.NewIpify()
custom = provider.MustNewCustomURL("https://url.io")
debugConst = provider.MustNewDebugConst("1.1.1.1")
)

for name, tc := range map[string]struct {
Expand Down Expand Up @@ -118,7 +119,7 @@ func TestReadProvider(t *testing.T) {
"local.iface:lo": {
true, " local.iface : lo ", false, "", trace, localLoopback, true,
func(m *mocks.MockPP) {
m.EXPECT().Hintf(pp.HintExperimentalLocalWithInterface, `You are using the experimental provider "local.iface:%s" added in version 1.15.0`, "lo")
m.EXPECT().Hintf(pp.HintExperimentalLocalWithInterface, `You are using the experimental "local.iface" provider added in version 1.15.0`)
},
},
"local.iface:": {
Expand All @@ -140,6 +141,21 @@ func TestReadProvider(t *testing.T) {
m.EXPECT().Noticef(pp.EmojiUserError, "%s (%q) is not a valid provider", key, "something-else")
},
},
"debug.const:1.1.1.1": {
true, " debug.const : 1.1.1.1 ", false, "", trace, debugConst, true,
func(m *mocks.MockPP) {
m.EXPECT().Hintf(pp.HintDebugConstProvider, `You are using the undocumented "debug.const" provider`)
},
},
"debug.const": {
true, " debug.const: ", false, "", trace, trace, false,
func(m *mocks.MockPP) {
gomock.InOrder(
m.EXPECT().Hintf(pp.HintDebugConstProvider, `You are using the undocumented "debug.const" provider`),
m.EXPECT().Noticef(pp.EmojiUserError, `%s=debug.const: must be followed by an IP address`, key),
)
},
},
} {
t.Run(name, func(t *testing.T) {
set(t, key, tc.set, tc.val)
Expand Down
1 change: 1 addition & 0 deletions internal/pp/hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ const (
HintExperimentalShoutrrr // New feature introduced in 1.12.0 on 2024/6/28
HintExperimentalWAF // New feature introduced in 1.14.0 on 2024/8/25
HintExperimentalLocalWithInterface // New feature introduced in 1.15.0
HintDebugConstProvider // Undocumented feature
)
33 changes: 33 additions & 0 deletions internal/provider/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package provider

import (
"net/netip"
"strings"

"github.com/favonia/cloudflare-ddns/internal/pp"
"github.com/favonia/cloudflare-ddns/internal/provider/protocol"
)

// NewDebugConst creates a [protocol.Const] provider.
func NewDebugConst(ppfmt pp.PP, raw string) (Provider, bool) {
ip, err := netip.ParseAddr(raw)
if err != nil {
ppfmt.Noticef(pp.EmojiUserError, `Failed to parse the IP address %q following "const:"`, raw)
return nil, false
}

return protocol.Const{
ProviderName: "debug.const:" + ip.String(),
IP: ip,
}, true
}

// MustNewDebugConst creates a [protocol.Const] provider and panics if it fails.
func MustNewDebugConst(raw string) Provider {
var buf strings.Builder
p, ok := NewDebugConst(pp.NewDefault(&buf), raw)
if !ok {
panic(buf.String())
}
return p
}
39 changes: 39 additions & 0 deletions internal/provider/const_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package provider_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/favonia/cloudflare-ddns/internal/provider"
)

func TestDebugConstName(t *testing.T) {
t.Parallel()

require.Equal(t, "debug.const:1.1.1.1", provider.Name(provider.MustNewDebugConst("1.1.1.1")))
}

func TestMustDebugConst(t *testing.T) {
t.Parallel()

for _, tc := range []struct {
input string
ok bool
}{
{"1.1.1.1", true},
{"1::1%1", true},
{"", false},
{"blah", false},
} {
t.Run(tc.input, func(t *testing.T) {
t.Parallel()

if tc.ok {
require.NotPanics(t, func() { provider.MustNewDebugConst(tc.input) })
} else {
require.Panics(t, func() { provider.MustNewDebugConst(tc.input) })
}
})
}
}
29 changes: 29 additions & 0 deletions internal/provider/protocol/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package protocol

import (
"context"
"net/netip"

"github.com/favonia/cloudflare-ddns/internal/ipnet"
"github.com/favonia/cloudflare-ddns/internal/pp"
)

// Const returns the same IP.
type Const struct {
// Name of the detection protocol.
ProviderName string

// The IP.
IP netip.Addr
}

// Name of the detection protocol.
func (p Const) Name() string {
return p.ProviderName
}

// GetIP returns the IP.
func (p Const) GetIP(_ context.Context, ppfmt pp.PP, ipNet ipnet.Type) (netip.Addr, Method, bool) {
normalizedIP, ok := ipNet.NormalizeDetectedIP(ppfmt, p.IP)
return normalizedIP, MethodPrimary, ok
}
85 changes: 85 additions & 0 deletions internal/provider/protocol/const_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// vim: nowrap
//go:build linux

package protocol_test

import (
"context"
"net/netip"
"testing"

"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"

"github.com/favonia/cloudflare-ddns/internal/ipnet"
"github.com/favonia/cloudflare-ddns/internal/mocks"
"github.com/favonia/cloudflare-ddns/internal/pp"
"github.com/favonia/cloudflare-ddns/internal/provider/protocol"
)

func TestConstName(t *testing.T) {
t.Parallel()

p := &protocol.Const{
ProviderName: "very secret name",
IP: netip.Addr{},
}

require.Equal(t, "very secret name", p.Name())
}

func TestConstGetIP(t *testing.T) {
t.Parallel()

var invalidIP netip.Addr

for name, tc := range map[string]struct {
savedIP netip.Addr
ipNet ipnet.Type
ok bool
expected netip.Addr
prepareMockPP func(*mocks.MockPP)
}{
"valid/4": {
netip.MustParseAddr("1.1.1.1"), ipnet.IP4,
true, netip.MustParseAddr("1.1.1.1"), nil,
},
"valid/6": {
netip.MustParseAddr("1::1%1"), ipnet.IP6,
true, netip.MustParseAddr("1::1%1"), nil,
},
"error/invalid": {
invalidIP, ipnet.IP6,
false, invalidIP,
func(ppfmt *mocks.MockPP) {
ppfmt.EXPECT().Noticef(pp.EmojiImpossible, "Detected IP address is not valid; this should not happen and please report it at %s", pp.IssueReportingURL)
},
},
"error/6-as-4": {
netip.MustParseAddr("1::1%1"), ipnet.IP4,
false, invalidIP,
func(ppfmt *mocks.MockPP) {
ppfmt.EXPECT().Noticef(pp.EmojiError, "Detected IP address %s is not a valid IPv4 address", "1::1%1")
},
},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()

mockCtrl := gomock.NewController(t)
mockPP := mocks.NewMockPP(mockCtrl)
if tc.prepareMockPP != nil {
tc.prepareMockPP(mockPP)
}

provider := &protocol.Const{
ProviderName: "",
IP: tc.savedIP,
}
ip, method, ok := provider.GetIP(context.Background(), mockPP, tc.ipNet)
require.Equal(t, tc.ok, ok)
require.NotEqual(t, protocol.MethodAlternative, method)
require.Equal(t, tc.expected, ip)
})
}
}
Loading