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

chore: Add tests for Mikrotik API client code #91

Merged
merged 15 commits into from
Oct 26, 2024
Merged
2 changes: 1 addition & 1 deletion internal/dnsprovider/dnsprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Init(config configuration.Config) (provider.Provider, error) {
}
log.Info(createMsg)

mikrotikConfig := mikrotik.Config{}
mikrotikConfig := mikrotik.MikrotikConnectionConfig{}
if err := env.Parse(&mikrotikConfig); err != nil {
return nil, fmt.Errorf("reading mikrotik configuration failed: %v", err)
}
Expand Down
71 changes: 36 additions & 35 deletions internal/mikrotik/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,23 @@ import (
"sigs.k8s.io/external-dns/endpoint"
)

// Config holds the connection details for the API client
type Config struct {
Host string `env:"MIKROTIK_HOST,notEmpty"`
Port string `env:"MIKROTIK_PORT,notEmpty" envDefault:"443"`
// MikrotikConnectionConfig holds the connection details for the API client
type MikrotikConnectionConfig struct {
BaseUrl string `env:"MIKROTIK_BASEURL,notEmpty"`
Username string `env:"MIKROTIK_USERNAME,notEmpty"`
Password string `env:"MIKROTIK_PASSWORD,notEmpty"`
SkipTLSVerify bool `env:"MIKROTIK_SKIP_TLS_VERIFY" envDefault:"false"`
}

// MikrotikApiClient encapsulates the client configuration and HTTP client
type MikrotikApiClient struct {
*Config
*MikrotikConnectionConfig
*http.Client
}

// SystemInfo represents MikroTik system information
// MikrotikSystemInfo represents MikroTik system information
// https://help.mikrotik.com/docs/display/ROS/Resource
type SystemInfo struct {
type MikrotikSystemInfo struct {
ArchitectureName string `json:"architecture-name"`
BadBlocks string `json:"bad-blocks"`
BoardName string `json:"board-name"`
Expand All @@ -56,7 +55,7 @@ type SystemInfo struct {
}

// NewMikrotikClient creates a new instance of MikrotikApiClient
func NewMikrotikClient(config *Config) (*MikrotikApiClient, error) {
func NewMikrotikClient(config *MikrotikConnectionConfig) (*MikrotikApiClient, error) {
log.Infof("creating a new Mikrotik API Client")

jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
Expand All @@ -66,7 +65,7 @@ func NewMikrotikClient(config *Config) (*MikrotikApiClient, error) {
}

client := &MikrotikApiClient{
Config: config,
MikrotikConnectionConfig: config,
Client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
Expand All @@ -77,29 +76,23 @@ func NewMikrotikClient(config *Config) (*MikrotikApiClient, error) {
},
}

info, err := client.GetSystemInfo()
if err != nil {
log.Errorf("failed to connect to the MikroTik RouterOS API Endpoint: %v", err)
return nil, err
}

log.Infof("connected to board %s running RouterOS version %s (%s)", info.BoardName, info.Version, info.ArchitectureName)
return client, nil
}

// GetSystemInfo fetches system information from the MikroTik API
func (c *MikrotikApiClient) GetSystemInfo() (*SystemInfo, error) {
func (c *MikrotikApiClient) GetSystemInfo() (*MikrotikSystemInfo, error) {
log.Debugf("fetching system information.")

resp, err := c._doRequest(http.MethodGet, "system/resource", nil)
// Send the request
resp, err := c.doRequest(http.MethodGet, "system/resource", nil)
if err != nil {
log.Errorf("error getching system info: %v", err)
log.Errorf("error fetching system info: %v", err)
return nil, err
}

defer resp.Body.Close()

var info SystemInfo
// Parse the response
var info MikrotikSystemInfo
if err = json.NewDecoder(resp.Body).Decode(&info); err != nil {
log.Errorf("error decoding response body: %v", err)
return nil, err
Expand All @@ -113,26 +106,29 @@ func (c *MikrotikApiClient) GetSystemInfo() (*SystemInfo, error) {
func (c *MikrotikApiClient) CreateDNSRecord(endpoint *endpoint.Endpoint) (*DNSRecord, error) {
log.Infof("creating DNS record: %+v", endpoint)

// Convert ExternalDNS to Mikrotik DNS
record, err := NewDNSRecord(endpoint)
if err != nil {
log.Errorf("error converting ExternalDNS endpoint to Mikrotik DNS Record: %v", err)
return nil, err
}

// Serialize the data to JSON to be sent to the API
jsonBody, err := json.Marshal(record)
if err != nil {
log.Errorf("error marshalling DNS record: %v", err)
return nil, err
}

resp, err := c._doRequest(http.MethodPut, "ip/dns/static", bytes.NewReader(jsonBody))
// Send the request
resp, err := c.doRequest(http.MethodPut, "ip/dns/static", bytes.NewReader(jsonBody))
if err != nil {
log.Errorf("error creating DNS record: %v", err)
return nil, err
}

defer resp.Body.Close()

// Parse the response
if err = json.NewDecoder(resp.Body).Decode(&record); err != nil {
log.Errorf("Error decoding response body: %v", err)
return nil, err
Expand All @@ -146,14 +142,15 @@ func (c *MikrotikApiClient) CreateDNSRecord(endpoint *endpoint.Endpoint) (*DNSRe
func (c *MikrotikApiClient) GetAllDNSRecords() ([]DNSRecord, error) {
log.Infof("fetching all DNS records")

resp, err := c._doRequest(http.MethodGet, "ip/dns/static", nil)
// Send the request
resp, err := c.doRequest(http.MethodGet, "ip/dns/static", nil)
if err != nil {
log.Errorf("error fetching DNS records: %v", err)
return nil, err
}

defer resp.Body.Close()

// Parse the response
var records []DNSRecord
if err = json.NewDecoder(resp.Body).Decode(&records); err != nil {
log.Errorf("error decoding response body: %v", err)
Expand All @@ -168,24 +165,27 @@ func (c *MikrotikApiClient) GetAllDNSRecords() ([]DNSRecord, error) {
func (c *MikrotikApiClient) DeleteDNSRecord(endpoint *endpoint.Endpoint) error {
log.Infof("deleting DNS record: %+v", endpoint)

record, err := c._lookupDNSRecord(endpoint.DNSName, endpoint.RecordType)
// Send the request
record, err := c.lookupDNSRecord(endpoint.DNSName, endpoint.RecordType)
if err != nil {
log.Errorf("failed lookup for DNS record: %+v", err)
return err
}

_, err = c._doRequest(http.MethodDelete, fmt.Sprintf("ip/dns/static/%s", record.ID), nil)
// Parse the response
resp, err := c.doRequest(http.MethodDelete, fmt.Sprintf("ip/dns/static/%s", record.ID), nil)
if err != nil {
log.Errorf("error deleting DNS record: %+v", err)
return err
}
defer resp.Body.Close()
log.Infof("record deleted")

return nil
}

// _lookupDNSRecord searches for a DNS record by key and type
func (c *MikrotikApiClient) _lookupDNSRecord(key, recordType string) (*DNSRecord, error) {
// lookupDNSRecord searches for a DNS record by key and type
func (c *MikrotikApiClient) lookupDNSRecord(key, recordType string) (*DNSRecord, error) {
log.Infof("Searching for DNS record: Key: %s, RecordType: %s", key, recordType)

searchParams := fmt.Sprintf("name=%s", key)
Expand All @@ -194,13 +194,14 @@ func (c *MikrotikApiClient) _lookupDNSRecord(key, recordType string) (*DNSRecord
}
log.Debugf("Search params: %s", searchParams)

resp, err := c._doRequest(http.MethodGet, fmt.Sprintf("ip/dns/static?%s", searchParams), nil)
// Send the request
resp, err := c.doRequest(http.MethodGet, fmt.Sprintf("ip/dns/static?%s", searchParams), nil)
if err != nil {
return nil, err
}

defer resp.Body.Close()

// Parse the response
var record []DNSRecord
if err = json.NewDecoder(resp.Body).Decode(&record); err != nil {
log.Errorf("Error decoding response body: %v", err)
Expand All @@ -215,9 +216,9 @@ func (c *MikrotikApiClient) _lookupDNSRecord(key, recordType string) (*DNSRecord
return &record[0], nil
}

// _doRequest sends an HTTP request to the MikroTik API with credentials
func (c *MikrotikApiClient) _doRequest(method, path string, body io.Reader) (*http.Response, error) {
endpoint_url := fmt.Sprintf("https://%s:%s/rest/%s", c.Config.Host, c.Config.Port, path)
// doRequest sends an HTTP request to the MikroTik API with credentials
func (c *MikrotikApiClient) doRequest(method, path string, body io.Reader) (*http.Response, error) {
endpoint_url := fmt.Sprintf("%s/rest/%s", c.MikrotikConnectionConfig.BaseUrl, path)
log.Debugf("sending %s request to: %s", method, endpoint_url)

req, err := http.NewRequest(method, endpoint_url, body)
Expand All @@ -226,7 +227,7 @@ func (c *MikrotikApiClient) _doRequest(method, path string, body io.Reader) (*ht
return nil, err
}

req.SetBasicAuth(c.Config.Username, c.Config.Password)
req.SetBasicAuth(c.MikrotikConnectionConfig.Username, c.MikrotikConnectionConfig.Password)

resp, err := c.Client.Do(req)
if err != nil {
Expand Down
Loading