mirror of
https://github.com/h44z/wg-portal.git
synced 2025-06-28 01:07:03 +00:00
wip: basic CRUD for interfaces (#426)
This commit is contained in:
parent
c612b5bbb1
commit
0724505ea1
@ -3,6 +3,8 @@ package wgcontroller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/h44z/wg-portal/internal/config"
|
"github.com/h44z/wg-portal/internal/config"
|
||||||
@ -97,9 +99,26 @@ func (c MikrotikController) loadInterfaceData(
|
|||||||
return nil, fmt.Errorf("failed to query interface %s: %v", deviceId, ifaceReply.Error)
|
return nil, fmt.Errorf("failed to query interface %s: %v", deviceId, ifaceReply.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipv4, ipv6, err := c.loadIpAddresses(ctx, deviceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query IP addresses for interface %s: %v", deviceId, err)
|
||||||
|
}
|
||||||
|
addresses := c.convertIpAddresses(ipv4, ipv6)
|
||||||
|
|
||||||
|
interfaceModel, err := c.convertWireGuardInterface(wireGuardObj, ifaceReply.Data, addresses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("interface convert failed for %s: %w", deviceName, err)
|
||||||
|
}
|
||||||
|
return &interfaceModel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c MikrotikController) loadIpAddresses(
|
||||||
|
ctx context.Context,
|
||||||
|
deviceName string,
|
||||||
|
) (ipv4 []lowlevel.GenericJsonObject, ipv6 []lowlevel.GenericJsonObject, err error) {
|
||||||
addrV4Reply := c.client.Query(ctx, "/ip/address", &lowlevel.MikrotikRequestOptions{
|
addrV4Reply := c.client.Query(ctx, "/ip/address", &lowlevel.MikrotikRequestOptions{
|
||||||
PropList: []string{
|
PropList: []string{
|
||||||
"address", "network",
|
".id", "address", "network",
|
||||||
},
|
},
|
||||||
Filters: map[string]string{
|
Filters: map[string]string{
|
||||||
"interface": deviceName,
|
"interface": deviceName,
|
||||||
@ -108,12 +127,13 @@ func (c MikrotikController) loadInterfaceData(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if addrV4Reply.Status != lowlevel.MikrotikApiStatusOk {
|
if addrV4Reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
return nil, fmt.Errorf("failed to query IPv4 addresses for interface %s: %v", deviceId, addrV4Reply.Error)
|
return nil, nil, fmt.Errorf("failed to query IPv4 addresses for interface %s: %v", deviceName,
|
||||||
|
addrV4Reply.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
addrV6Reply := c.client.Query(ctx, "/ipv6/address", &lowlevel.MikrotikRequestOptions{
|
addrV6Reply := c.client.Query(ctx, "/ipv6/address", &lowlevel.MikrotikRequestOptions{
|
||||||
PropList: []string{
|
PropList: []string{
|
||||||
"address", "network",
|
".id", "address", "network",
|
||||||
},
|
},
|
||||||
Filters: map[string]string{
|
Filters: map[string]string{
|
||||||
"interface": deviceName,
|
"interface": deviceName,
|
||||||
@ -122,26 +142,16 @@ func (c MikrotikController) loadInterfaceData(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if addrV6Reply.Status != lowlevel.MikrotikApiStatusOk {
|
if addrV6Reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
return nil, fmt.Errorf("failed to query IPv6 addresses for interface %s: %v", deviceId, addrV6Reply.Error)
|
return nil, nil, fmt.Errorf("failed to query IPv6 addresses for interface %s: %v", deviceName,
|
||||||
|
addrV6Reply.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
interfaceModel, err := c.convertWireGuardInterface(wireGuardObj, ifaceReply.Data, addrV4Reply.Data,
|
return addrV4Reply.Data, addrV6Reply.Data, nil
|
||||||
addrV6Reply.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("interface convert failed for %s: %w", deviceName, err)
|
|
||||||
}
|
|
||||||
return &interfaceModel, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) convertWireGuardInterface(
|
func (c MikrotikController) convertIpAddresses(
|
||||||
wg, iface lowlevel.GenericJsonObject,
|
|
||||||
ipv4, ipv6 []lowlevel.GenericJsonObject,
|
ipv4, ipv6 []lowlevel.GenericJsonObject,
|
||||||
) (
|
) []domain.Cidr {
|
||||||
domain.PhysicalInterface,
|
|
||||||
error,
|
|
||||||
) {
|
|
||||||
// read data from wgctrl interface
|
|
||||||
|
|
||||||
addresses := make([]domain.Cidr, 0, len(ipv4)+len(ipv6))
|
addresses := make([]domain.Cidr, 0, len(ipv4)+len(ipv6))
|
||||||
for _, addr := range append(ipv4, ipv6...) {
|
for _, addr := range append(ipv4, ipv6...) {
|
||||||
addrStr := addr.GetString("address")
|
addrStr := addr.GetString("address")
|
||||||
@ -156,6 +166,16 @@ func (c MikrotikController) convertWireGuardInterface(
|
|||||||
addresses = append(addresses, cidr)
|
addresses = append(addresses, cidr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c MikrotikController) convertWireGuardInterface(
|
||||||
|
wg, iface lowlevel.GenericJsonObject,
|
||||||
|
addresses []domain.Cidr,
|
||||||
|
) (
|
||||||
|
domain.PhysicalInterface,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
pi := domain.PhysicalInterface{
|
pi := domain.PhysicalInterface{
|
||||||
Identifier: domain.InterfaceIdentifier(wg.GetString("name")),
|
Identifier: domain.InterfaceIdentifier(wg.GetString("name")),
|
||||||
KeyPair: domain.KeyPair{
|
KeyPair: domain.KeyPair{
|
||||||
@ -174,6 +194,7 @@ func (c MikrotikController) convertWireGuardInterface(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pi.SetExtras(domain.MikrotikInterfaceExtras{
|
pi.SetExtras(domain.MikrotikInterfaceExtras{
|
||||||
|
Id: wg.GetString(".id"),
|
||||||
Comment: wg.GetString("comment"),
|
Comment: wg.GetString("comment"),
|
||||||
Disabled: wg.GetBool("disabled"),
|
Disabled: wg.GetBool("disabled"),
|
||||||
})
|
})
|
||||||
@ -270,16 +291,202 @@ func (c MikrotikController) convertWireGuardPeer(peer lowlevel.GenericJsonObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) SaveInterface(
|
func (c MikrotikController) SaveInterface(
|
||||||
_ context.Context,
|
ctx context.Context,
|
||||||
id domain.InterfaceIdentifier,
|
id domain.InterfaceIdentifier,
|
||||||
updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error),
|
updateFunc func(pi *domain.PhysicalInterface) (*domain.PhysicalInterface, error),
|
||||||
) error {
|
) error {
|
||||||
// TODO implement me
|
physicalInterface, err := c.getOrCreateInterface(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceId := physicalInterface.GetExtras().(domain.MikrotikInterfaceExtras).Id
|
||||||
|
if updateFunc != nil {
|
||||||
|
physicalInterface, err = updateFunc(physicalInterface)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newExtras := physicalInterface.GetExtras().(domain.MikrotikInterfaceExtras)
|
||||||
|
newExtras.Id = deviceId // ensure the ID is not changed
|
||||||
|
physicalInterface.SetExtras(newExtras)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.updateInterface(ctx, physicalInterface); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c MikrotikController) DeleteInterface(_ context.Context, id domain.InterfaceIdentifier) error {
|
func (c MikrotikController) getOrCreateInterface(
|
||||||
// TODO implement me
|
ctx context.Context,
|
||||||
|
id domain.InterfaceIdentifier,
|
||||||
|
) (*domain.PhysicalInterface, error) {
|
||||||
|
wgReply := c.client.Query(ctx, "/interface/wireguard", &lowlevel.MikrotikRequestOptions{
|
||||||
|
PropList: []string{
|
||||||
|
".id", "name", "public-key", "private-key", "listen-port", "mtu", "disabled", "running",
|
||||||
|
},
|
||||||
|
Filters: map[string]string{
|
||||||
|
"name": string(id),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if wgReply.Status == lowlevel.MikrotikApiStatusOk && len(wgReply.Data) > 0 {
|
||||||
|
return c.loadInterfaceData(ctx, wgReply.Data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new interface if it does not exist
|
||||||
|
createReply := c.client.Create(ctx, "/interface/wireguard", lowlevel.GenericJsonObject{
|
||||||
|
"name": string(id),
|
||||||
|
})
|
||||||
|
if wgReply.Status == lowlevel.MikrotikApiStatusOk {
|
||||||
|
return c.loadInterfaceData(ctx, createReply.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("failed to create interface %s: %v", id, createReply.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c MikrotikController) updateInterface(ctx context.Context, pi *domain.PhysicalInterface) error {
|
||||||
|
extras := pi.GetExtras().(domain.MikrotikInterfaceExtras)
|
||||||
|
interfaceId := extras.Id
|
||||||
|
wgReply := c.client.Update(ctx, "/interface/wireguard/"+interfaceId, lowlevel.GenericJsonObject{
|
||||||
|
"name": pi.Identifier,
|
||||||
|
"comment": extras.Comment,
|
||||||
|
"mtu": strconv.Itoa(pi.Mtu),
|
||||||
|
"listen-port": strconv.Itoa(pi.ListenPort),
|
||||||
|
"private-key": pi.KeyPair.PrivateKey,
|
||||||
|
"disabled": strconv.FormatBool(!pi.DeviceUp),
|
||||||
|
})
|
||||||
|
if wgReply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to update interface %s: %v", pi.Identifier, wgReply.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the interface's addresses
|
||||||
|
currentV4, currentV6, err := c.loadIpAddresses(ctx, string(pi.Identifier))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load current addresses for interface %s: %v", pi.Identifier, err)
|
||||||
|
}
|
||||||
|
currentAddresses := c.convertIpAddresses(currentV4, currentV6)
|
||||||
|
|
||||||
|
// get all addresses that are currently not in the interface, only in pi
|
||||||
|
newAddresses := make([]domain.Cidr, 0, len(pi.Addresses))
|
||||||
|
for _, addr := range pi.Addresses {
|
||||||
|
if slices.Contains(currentAddresses, addr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newAddresses = append(newAddresses, addr)
|
||||||
|
}
|
||||||
|
// get obsolete addresses that are in the interface, but not in pi
|
||||||
|
obsoleteAddresses := make([]domain.Cidr, 0, len(currentAddresses))
|
||||||
|
for _, addr := range currentAddresses {
|
||||||
|
if slices.Contains(pi.Addresses, addr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obsoleteAddresses = append(obsoleteAddresses, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the IP addresses for the interface
|
||||||
|
if err := c.updateIpAddresses(ctx, string(pi.Identifier), currentV4, currentV6,
|
||||||
|
newAddresses, obsoleteAddresses); err != nil {
|
||||||
|
return fmt.Errorf("failed to update IP addresses for interface %s: %v", pi.Identifier, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c MikrotikController) updateIpAddresses(
|
||||||
|
ctx context.Context,
|
||||||
|
deviceName string,
|
||||||
|
currentV4, currentV6 []lowlevel.GenericJsonObject,
|
||||||
|
new, obsolete []domain.Cidr,
|
||||||
|
) error {
|
||||||
|
// first, delete all obsolete addresses
|
||||||
|
for _, addr := range obsolete {
|
||||||
|
// find ID of the address to delete
|
||||||
|
if addr.IsV4() {
|
||||||
|
for _, a := range currentV4 {
|
||||||
|
if a.GetString("address") == addr.String() {
|
||||||
|
// delete the address
|
||||||
|
reply := c.client.Delete(ctx, "/ip/address/"+a.GetString(".id"))
|
||||||
|
if reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to delete obsolete IPv4 address %s: %v", addr, reply.Error)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, a := range currentV6 {
|
||||||
|
if a.GetString("address") == addr.String() {
|
||||||
|
// delete the address
|
||||||
|
reply := c.client.Delete(ctx, "/ipv6/address/"+a.GetString(".id"))
|
||||||
|
if reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to delete obsolete IPv6 address %s: %v", addr, reply.Error)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then, add all new addresses
|
||||||
|
for _, addr := range new {
|
||||||
|
var createPath string
|
||||||
|
if addr.IsV4() {
|
||||||
|
createPath = "/ip/address"
|
||||||
|
} else {
|
||||||
|
createPath = "/ipv6/address"
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the address
|
||||||
|
reply := c.client.Create(ctx, createPath, lowlevel.GenericJsonObject{
|
||||||
|
"address": addr.String(),
|
||||||
|
"interface": deviceName,
|
||||||
|
})
|
||||||
|
if reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to create new address %s: %v", addr, reply.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c MikrotikController) DeleteInterface(ctx context.Context, id domain.InterfaceIdentifier) error {
|
||||||
|
// delete the interface's addresses
|
||||||
|
currentV4, currentV6, err := c.loadIpAddresses(ctx, string(id))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load current addresses for interface %s: %v", id, err)
|
||||||
|
}
|
||||||
|
for _, a := range currentV4 {
|
||||||
|
// delete the address
|
||||||
|
reply := c.client.Delete(ctx, "/ip/address/"+a.GetString(".id"))
|
||||||
|
if reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to delete IPv4 address %s: %v", a.GetString("address"), reply.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, a := range currentV6 {
|
||||||
|
// delete the address
|
||||||
|
reply := c.client.Delete(ctx, "/ipv6/address/"+a.GetString(".id"))
|
||||||
|
if reply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to delete IPv6 address %s: %v", a.GetString("address"), reply.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the WireGuard interface
|
||||||
|
wgReply := c.client.Query(ctx, "/interface/wireguard", &lowlevel.MikrotikRequestOptions{
|
||||||
|
PropList: []string{".id"},
|
||||||
|
Filters: map[string]string{
|
||||||
|
"name": string(id),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if wgReply.Status != lowlevel.MikrotikApiStatusOk || len(wgReply.Data) == 0 {
|
||||||
|
return fmt.Errorf("unable to find WireGuard interface %s: %v", id, wgReply.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceId := wgReply.Data[0].GetString(".id")
|
||||||
|
deleteReply := c.client.Delete(ctx, "/interface/wireguard/"+deviceId)
|
||||||
|
if deleteReply.Status != lowlevel.MikrotikApiStatusOk {
|
||||||
|
return fmt.Errorf("failed to delete WireGuard interface %s: %v", id, deleteReply.Error)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ const (
|
|||||||
// Controller extras can be used to store additional information available for specific controllers only.
|
// Controller extras can be used to store additional information available for specific controllers only.
|
||||||
|
|
||||||
type MikrotikInterfaceExtras struct {
|
type MikrotikInterfaceExtras struct {
|
||||||
|
Id string // internal mikrotik ID
|
||||||
Comment string
|
Comment string
|
||||||
Disabled bool
|
Disabled bool
|
||||||
}
|
}
|
||||||
|
@ -227,6 +227,11 @@ func (p *PhysicalInterface) SetExtras(extras any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ConvertPhysicalInterface(pi *PhysicalInterface) *Interface {
|
func ConvertPhysicalInterface(pi *PhysicalInterface) *Interface {
|
||||||
|
networks := make([]Cidr, 0, len(pi.Addresses))
|
||||||
|
for _, addr := range pi.Addresses {
|
||||||
|
networks = append(networks, addr.NetworkAddr())
|
||||||
|
}
|
||||||
|
|
||||||
// create a new basic interface with the data from the physical interface
|
// create a new basic interface with the data from the physical interface
|
||||||
iface := &Interface{
|
iface := &Interface{
|
||||||
Identifier: pi.Identifier,
|
Identifier: pi.Identifier,
|
||||||
@ -247,11 +252,11 @@ func ConvertPhysicalInterface(pi *PhysicalInterface) *Interface {
|
|||||||
Type: InterfaceTypeAny,
|
Type: InterfaceTypeAny,
|
||||||
DriverType: pi.DeviceType,
|
DriverType: pi.DeviceType,
|
||||||
Disabled: nil,
|
Disabled: nil,
|
||||||
PeerDefNetworkStr: "",
|
PeerDefNetworkStr: CidrsToString(networks),
|
||||||
PeerDefDnsStr: "",
|
PeerDefDnsStr: "",
|
||||||
PeerDefDnsSearchStr: "",
|
PeerDefDnsSearchStr: "",
|
||||||
PeerDefEndpoint: "",
|
PeerDefEndpoint: "",
|
||||||
PeerDefAllowedIPsStr: "",
|
PeerDefAllowedIPsStr: CidrsToString(networks),
|
||||||
PeerDefMtu: pi.Mtu,
|
PeerDefMtu: pi.Mtu,
|
||||||
PeerDefPersistentKeepalive: 0,
|
PeerDefPersistentKeepalive: 0,
|
||||||
PeerDefFirewallMark: 0,
|
PeerDefFirewallMark: 0,
|
||||||
@ -291,6 +296,15 @@ func MergeToPhysicalInterface(pi *PhysicalInterface, i *Interface) {
|
|||||||
pi.FirewallMark = i.FirewallMark
|
pi.FirewallMark = i.FirewallMark
|
||||||
pi.DeviceUp = !i.IsDisabled()
|
pi.DeviceUp = !i.IsDisabled()
|
||||||
pi.Addresses = i.Addresses
|
pi.Addresses = i.Addresses
|
||||||
|
|
||||||
|
switch pi.ImportSource {
|
||||||
|
case ControllerTypeMikrotik:
|
||||||
|
extras := MikrotikInterfaceExtras{
|
||||||
|
Comment: i.DisplayName,
|
||||||
|
Disabled: i.IsDisabled(),
|
||||||
|
}
|
||||||
|
pi.SetExtras(extras)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoutingTableInfo struct {
|
type RoutingTableInfo struct {
|
||||||
|
@ -45,11 +45,15 @@ type MikrotikApiError struct {
|
|||||||
Details string `json:"details,omitempty"`
|
Details string `json:"details,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e MikrotikApiError) String() string {
|
func (e *MikrotikApiError) String() string {
|
||||||
|
if e == nil {
|
||||||
|
return "no error"
|
||||||
|
}
|
||||||
return fmt.Sprintf("API error %d: %s - %s", e.Code, e.Message, e.Details)
|
return fmt.Sprintf("API error %d: %s - %s", e.Code, e.Message, e.Details)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GenericJsonObject map[string]any
|
type GenericJsonObject map[string]any
|
||||||
|
type EmptyResponse struct{}
|
||||||
|
|
||||||
func (JsonObject GenericJsonObject) GetString(key string) string {
|
func (JsonObject GenericJsonObject) GetString(key string) string {
|
||||||
if value, ok := JsonObject[key]; ok {
|
if value, ok := JsonObject[key]; ok {
|
||||||
@ -211,8 +215,22 @@ func (m *MikrotikApiClient) prepareGetRequest(ctx context.Context, fullUrl strin
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MikrotikApiClient) preparePostRequest(
|
func (m *MikrotikApiClient) prepareDeleteRequest(ctx context.Context, fullUrl string) (*http.Request, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fullUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/json")
|
||||||
|
if m.cfg.ApiUser != "" && m.cfg.ApiPassword != "" {
|
||||||
|
req.SetBasicAuth(m.cfg.ApiUser, m.cfg.ApiPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MikrotikApiClient) preparePayloadRequest(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
method string,
|
||||||
fullUrl string,
|
fullUrl string,
|
||||||
payload GenericJsonObject,
|
payload GenericJsonObject,
|
||||||
) (*http.Request, error) {
|
) (*http.Request, error) {
|
||||||
@ -222,7 +240,7 @@ func (m *MikrotikApiClient) preparePostRequest(
|
|||||||
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
return nil, fmt.Errorf("failed to marshal payload: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, fullUrl, bytes.NewReader(payloadBytes))
|
req, err := http.NewRequestWithContext(ctx, method, fullUrl, bytes.NewReader(payloadBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
@ -261,6 +279,12 @@ func parseHttpResponse[T any](resp *http.Response, err error) MikrotikApiRespons
|
|||||||
|
|
||||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
var data T
|
var data T
|
||||||
|
|
||||||
|
// if the type of T is EmptyResponse, we can return an empty response with just the status
|
||||||
|
if _, ok := any(data).(EmptyResponse); ok {
|
||||||
|
return MikrotikApiResponse[T]{Status: MikrotikApiStatusOk, Code: resp.StatusCode}
|
||||||
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
return errToApiResponse[T](MikrotikApiErrorCodeResponseDecodeFailed, "failed to decode response", err)
|
return errToApiResponse[T](MikrotikApiErrorCodeResponseDecodeFailed, "failed to decode response", err)
|
||||||
}
|
}
|
||||||
@ -321,6 +345,74 @@ func (m *MikrotikApiClient) Get(
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MikrotikApiClient) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
command string,
|
||||||
|
payload GenericJsonObject,
|
||||||
|
) MikrotikApiResponse[GenericJsonObject] {
|
||||||
|
apiCtx, cancel := context.WithTimeout(ctx, m.cfg.ApiTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
fullUrl := m.getFullPath(command)
|
||||||
|
|
||||||
|
req, err := m.preparePayloadRequest(apiCtx, http.MethodPut, fullUrl, payload)
|
||||||
|
if err != nil {
|
||||||
|
return errToApiResponse[GenericJsonObject](MikrotikApiErrorCodeRequestPreparationFailed,
|
||||||
|
"failed to create request", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
m.debugLog("executing API put", "url", fullUrl)
|
||||||
|
response := parseHttpResponse[GenericJsonObject](m.client.Do(req))
|
||||||
|
m.debugLog("retrieved API put result", "url", fullUrl, "duration", time.Since(start).String())
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MikrotikApiClient) Update(
|
||||||
|
ctx context.Context,
|
||||||
|
command string,
|
||||||
|
payload GenericJsonObject,
|
||||||
|
) MikrotikApiResponse[GenericJsonObject] {
|
||||||
|
apiCtx, cancel := context.WithTimeout(ctx, m.cfg.ApiTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
fullUrl := m.getFullPath(command)
|
||||||
|
|
||||||
|
req, err := m.preparePayloadRequest(apiCtx, http.MethodPatch, fullUrl, payload)
|
||||||
|
if err != nil {
|
||||||
|
return errToApiResponse[GenericJsonObject](MikrotikApiErrorCodeRequestPreparationFailed,
|
||||||
|
"failed to create request", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
m.debugLog("executing API patch", "url", fullUrl)
|
||||||
|
response := parseHttpResponse[GenericJsonObject](m.client.Do(req))
|
||||||
|
m.debugLog("retrieved API patch result", "url", fullUrl, "duration", time.Since(start).String())
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MikrotikApiClient) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
command string,
|
||||||
|
) MikrotikApiResponse[EmptyResponse] {
|
||||||
|
apiCtx, cancel := context.WithTimeout(ctx, m.cfg.ApiTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
fullUrl := m.getFullPath(command)
|
||||||
|
|
||||||
|
req, err := m.prepareDeleteRequest(apiCtx, fullUrl)
|
||||||
|
if err != nil {
|
||||||
|
return errToApiResponse[EmptyResponse](MikrotikApiErrorCodeRequestPreparationFailed,
|
||||||
|
"failed to create request", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
m.debugLog("executing API delete", "url", fullUrl)
|
||||||
|
response := parseHttpResponse[EmptyResponse](m.client.Do(req))
|
||||||
|
m.debugLog("retrieved API delete result", "url", fullUrl, "duration", time.Since(start).String())
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MikrotikApiClient) ExecList(
|
func (m *MikrotikApiClient) ExecList(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
command string,
|
command string,
|
||||||
@ -331,16 +423,16 @@ func (m *MikrotikApiClient) ExecList(
|
|||||||
|
|
||||||
fullUrl := m.getFullPath(command)
|
fullUrl := m.getFullPath(command)
|
||||||
|
|
||||||
req, err := m.preparePostRequest(apiCtx, fullUrl, payload)
|
req, err := m.preparePayloadRequest(apiCtx, http.MethodPost, fullUrl, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errToApiResponse[[]GenericJsonObject](MikrotikApiErrorCodeRequestPreparationFailed,
|
return errToApiResponse[[]GenericJsonObject](MikrotikApiErrorCodeRequestPreparationFailed,
|
||||||
"failed to create request", err)
|
"failed to create request", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
m.debugLog("executing API get", "url", fullUrl)
|
m.debugLog("executing API post", "url", fullUrl)
|
||||||
response := parseHttpResponse[[]GenericJsonObject](m.client.Do(req))
|
response := parseHttpResponse[[]GenericJsonObject](m.client.Do(req))
|
||||||
m.debugLog("retrieved API get result", "url", fullUrl, "duration", time.Since(start).String())
|
m.debugLog("retrieved API post result", "url", fullUrl, "duration", time.Since(start).String())
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user