Refactor: Moved all under internal.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

FEAT: Directadmin provider is now working
This commit is contained in:
2024-03-18 09:55:01 +13:00
parent a52034216b
commit e1bb5adf36
19 changed files with 163 additions and 73 deletions

View File

@@ -0,0 +1,55 @@
package public_resolvers
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
)
var NoIPInResponseError = errors.New("no ip found in response")
type Doer interface {
Do(*http.Request) (*http.Response, error)
}
type ipParserFunc func(reader io.Reader) (string, error)
func defaultIpParser(reader io.Reader) (string, error) {
out, err := io.ReadAll(reader)
return string(out), err
}
type baseResolver struct {
client Doer
url string
ipParser ipParserFunc
}
func (i baseResolver) ResolvePublicIp(ctx context.Context) (net.IP, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, i.url, nil)
if err != nil {
return net.IP{}, fmt.Errorf("error creating ifconfig request: %w", err)
}
resp, err := i.client.Do(req)
if err != nil {
return net.IP{}, err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return net.IP{}, fmt.Errorf("unexpected response code %d", resp.StatusCode)
}
ipText, err := i.ipParser(resp.Body)
if err != nil {
return net.IP{}, fmt.Errorf("error reading body: %w", err)
}
return net.ParseIP(ipText), nil
}

View File

@@ -0,0 +1,100 @@
package public_resolvers
import (
"bytes"
"context"
"io"
"net"
"net/http"
"reflect"
"testing"
)
// RoundTripFunc .
type RoundTripFunc func(req *http.Request) *http.Response
// RoundTrip .
func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
// NewTestClient returns *http.Client with Transport replaced to avoid making real calls
func NewTestClient(fn RoundTripFunc) *http.Client {
return &http.Client{
Transport: fn,
}
}
func Test_baseResolver_ResolvePublicIp(t *testing.T) {
testUrl := "http://my-test-url.url"
testIp := `192.168.0.100`
client := NewTestClient(func(req *http.Request) *http.Response {
if req.URL.String() != testUrl {
return &http.Response{
StatusCode: 500,
// Send response to be tested
Body: io.NopCloser(bytes.NewBufferString(`invalid url`)),
// Must be set to non-nil value or it panics
Header: make(http.Header),
}
}
return &http.Response{
StatusCode: 200,
// Send response to be tested
Body: io.NopCloser(bytes.NewBufferString(testIp)),
// Must be set to non-nil value or it panics
Header: make(http.Header),
}
})
type fields struct {
client Doer
url string
fn ipParserFunc
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want net.IP
wantErr bool
}{
{
name: "check parse ip4",
fields: fields{
client: client,
url: testUrl,
fn: defaultIpParser,
},
args: args{
ctx: context.Background(),
},
want: net.ParseIP(testIp),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i := baseResolver{
client: tt.fields.client,
url: tt.fields.url,
ipParser: tt.fields.fn,
}
got, err := i.ResolvePublicIp(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("ResolvePublicIp() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ResolvePublicIp() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,49 @@
package public_resolvers
import (
"io"
"net/http"
"strings"
"time"
)
const (
CloudflareTraceTag = "1.1.1.1"
CloudflareTraceUrl = "https://1.1.1.1/cdn-cgi/trace"
ipPrefix = "ip="
)
type CloudflareTrace struct {
baseResolver
}
func NewDefaultCloudflareTrace() *CloudflareTrace {
return NewCloudflareTrace(&http.Client{
Timeout: 10 * time.Second,
})
}
func cloudflareTraceResponseParser(reader io.Reader) (string, error) {
data, err := io.ReadAll(reader)
if err != nil {
return "", err
}
for _, row := range strings.Split(string(data), "\n") {
if strings.Index(row, ipPrefix) == 0 {
return strings.TrimSpace(strings.ReplaceAll(row, ipPrefix, "")), nil
}
}
return "", NoIPInResponseError
}
func NewCloudflareTrace(client Doer) *CloudflareTrace {
return &CloudflareTrace{
baseResolver: baseResolver{
client: client,
url: CloudflareTraceUrl,
ipParser: cloudflareTraceResponseParser,
},
}
}

View File

@@ -0,0 +1,78 @@
package public_resolvers
import (
"bytes"
"io"
"testing"
)
func Test_cloudflareTraceResponseParser(t *testing.T) {
type args struct {
reader io.Reader
}
tests := []struct {
name string
args args
want string
wantErr bool
}{
{
name: "ok",
args: args{
reader: bytes.NewBuffer([]byte(`fl=31f118
h=1.1.1.1
ip=94.113.142.206
ts=1683145336.383
visit_scheme=https
uag=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
colo=PRG
sliver=none
http=http/2
loc=CZ
tls=TLSv1.3
sni=off
warp=off
gateway=off
rbi=off
kex=X25519`)),
},
want: "94.113.142.206",
wantErr: false,
},
{
name: "no ip in response",
args: args{
reader: bytes.NewBuffer([]byte(`fl=31f118
h=1.1.1.1
ts=1683145336.383
visit_scheme=https
uag=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
colo=PRG
sliver=none
http=http/2
loc=CZ
tls=TLSv1.3
sni=off
warp=off
gateway=off
rbi=off
kex=X25519`)),
},
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := cloudflareTraceResponseParser(tt.args.reader)
if (err != nil) != tt.wantErr {
t.Errorf("cloudflareTraceResponseParser() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("cloudflareTraceResponseParser() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,31 @@
package public_resolvers
import (
"net/http"
"time"
)
const (
IcanhazipTag = "icanhazip"
IcanhazipUrl = "https://v4.icanhazip.com/"
)
type Icanhazip struct {
baseResolver
}
func NewIcanhazipDefault() *Icanhazip {
return NewIcanhazip(&http.Client{
Timeout: 10 * time.Second,
})
}
func NewIcanhazip(client Doer) *Icanhazip {
return &Icanhazip{
baseResolver: baseResolver{
client: client,
url: v4IdentMeUrl,
ipParser: defaultIpParser,
},
}
}

View File

@@ -0,0 +1,31 @@
package public_resolvers
import (
"net/http"
"time"
)
const (
IfConfigMeTag = "ifconfig.me"
ifConfigMeUrl = "https://ifconfig.me"
)
type IfConfigMe struct {
baseResolver
}
func NewDefaultIfConfigMe() *IfConfigMe {
return NewIfConfigMe(&http.Client{
Timeout: 10 * time.Second,
})
}
func NewIfConfigMe(client Doer) *IfConfigMe {
return &IfConfigMe{
baseResolver: baseResolver{
client: client,
url: ifConfigMeUrl,
ipParser: defaultIpParser,
},
}
}

View File

@@ -0,0 +1,31 @@
package public_resolvers
import (
"net/http"
"time"
)
const (
V4IdentMeTag = "v4.ident.me"
v4IdentMeUrl = "https://v4.ident.me/"
)
type V4IdentMe struct {
baseResolver
}
func NewV4IdentMeDefault() *V4IdentMe {
return NewV4IdentMe(&http.Client{
Timeout: 10 * time.Second,
})
}
func NewV4IdentMe(client Doer) *V4IdentMe {
return &V4IdentMe{
baseResolver: baseResolver{
client: client,
url: v4IdentMeUrl,
ipParser: defaultIpParser,
},
}
}