da289017a5
* generate model from openAPI schema * implement replica status #231 * Sync "Pause service blocking schedule" #234 * Sync "Safe Search Provider" #200
145 lines
3.2 KiB
Go
145 lines
3.2 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
|
|
"github.com/bakito/adguardhome-sync/pkg/client/model"
|
|
"github.com/bakito/adguardhome-sync/pkg/log"
|
|
"github.com/bakito/adguardhome-sync/pkg/types"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var l = log.GetLogger("client")
|
|
|
|
// New create a new api client
|
|
func New(config types.AdGuardInstance) (Client, error) {
|
|
var apiURL string
|
|
if config.APIPath == "" {
|
|
apiURL = fmt.Sprintf("%s/control", config.URL)
|
|
} else {
|
|
apiURL = fmt.Sprintf("%s/%s", config.URL, config.APIPath)
|
|
}
|
|
u, err := url.Parse(apiURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
u.Path = path.Clean(u.Path)
|
|
|
|
httpClient := &http.Client{}
|
|
if config.InsecureSkipVerify {
|
|
// #nosec G402 has to be explicitly enabled
|
|
httpClient.Transport = &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
}
|
|
|
|
aghClient, err := model.NewClient(u.String(), func(client *model.AdguardHomeClient) error {
|
|
client.Client = httpClient
|
|
client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, req *http.Request) error {
|
|
if config.Username != "" && config.Password != "" {
|
|
req.Header.Add("Authorization", "Basic "+basicAuth(config.Username, config.Password))
|
|
}
|
|
return nil
|
|
})
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &apiClient{
|
|
host: u.Host,
|
|
client: aghClient,
|
|
log: l.With("host", u.Host),
|
|
}, nil
|
|
}
|
|
|
|
func basicAuth(username, password string) string {
|
|
auth := username + ":" + password
|
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
|
}
|
|
|
|
type apiClient struct {
|
|
host string
|
|
client *model.AdguardHomeClient
|
|
log *zap.SugaredLogger
|
|
}
|
|
|
|
func (a apiClient) Host(context.Context) string {
|
|
return a.host
|
|
}
|
|
|
|
func (a apiClient) GetServerStatus(ctx context.Context) (*model.ServerStatus, error) {
|
|
sr, err := read(ctx, a.client.Status, model.ParseStatusResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sr.JSON200, nil
|
|
}
|
|
|
|
func (a apiClient) GetFilteringStatus(ctx context.Context) (*model.FilterStatus, error) {
|
|
sr, err := read(ctx, a.client.FilteringStatus, model.ParseFilteringStatusResp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sr.JSON200, nil
|
|
}
|
|
|
|
func (a apiClient) SetFilteringConfig(ctx context.Context, config model.FilterConfig) error {
|
|
return write(ctx, config, a.client.FilteringConfig)
|
|
}
|
|
|
|
func write[B interface{}](
|
|
ctx context.Context,
|
|
body B,
|
|
req func(ctx context.Context, body B, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
|
) error {
|
|
resp, err := req(ctx, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return detailedError(resp)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func read[I interface{}](
|
|
ctx context.Context,
|
|
req func(ctx context.Context, reqEditors ...model.RequestEditorFn) (*http.Response, error),
|
|
parse func(rsp *http.Response) (*I, error),
|
|
) (*I, error) {
|
|
resp, err := req(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, detailedError(resp)
|
|
}
|
|
return parse(resp)
|
|
}
|
|
|
|
func detailedError(resp *http.Response) error {
|
|
e := resp.Status
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(body) > 0 {
|
|
e += fmt.Sprintf("(%s)", string(body))
|
|
}
|
|
return errors.New(e)
|
|
}
|