mirror of
https://github.com/gravitl/netmaker.git
synced 2025-10-05 08:47:35 +08:00

* New Docs CSS update and Dockerfile to include docs folder flash of unrendered text fix markdown docs ignore docs/docs.go improving the docs generation github actions for docs generation go runner version fix updated docs.yml update repo action updated updated actions and dns docs dns complete More docs update Complete docs and updated workflow Update documentation Tue Aug 6 11:17:42 UTC 2024 Update documentation Thu Aug 8 12:26:57 UTC 2024 clean up clean up Dockerfile clean up Updated workflow Updated workflow Update docs.yml Update docs.yml * requested changes * changed ingress gateway to remote access gateway
573 lines
18 KiB
Go
573 lines
18 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
"golang.org/x/exp/slog"
|
|
|
|
"github.com/gravitl/netmaker/database"
|
|
"github.com/gravitl/netmaker/logger"
|
|
"github.com/gravitl/netmaker/logic"
|
|
"github.com/gravitl/netmaker/logic/acls"
|
|
"github.com/gravitl/netmaker/models"
|
|
"github.com/gravitl/netmaker/mq"
|
|
"github.com/gravitl/netmaker/servercfg"
|
|
)
|
|
|
|
func networkHandlers(r *mux.Router) {
|
|
r.HandleFunc("/api/networks", logic.SecurityCheck(true, http.HandlerFunc(getNetworks))).
|
|
Methods(http.MethodGet)
|
|
r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceNetworks, http.HandlerFunc(createNetwork)))).
|
|
Methods(http.MethodPost)
|
|
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(getNetwork))).
|
|
Methods(http.MethodGet)
|
|
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).
|
|
Methods(http.MethodDelete)
|
|
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).
|
|
Methods(http.MethodPut)
|
|
// ACLs
|
|
r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).
|
|
Methods(http.MethodPut)
|
|
r.HandleFunc("/api/networks/{networkname}/acls/v2", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACLv2))).
|
|
Methods(http.MethodPut)
|
|
r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).
|
|
Methods(http.MethodGet)
|
|
}
|
|
|
|
// @Summary Lists all networks
|
|
// @Router /api/networks [get]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Produce json
|
|
// @Success 200 {object} models.Network
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func getNetworks(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var err error
|
|
|
|
allnetworks, err := logic.GetNetworks()
|
|
if err != nil && !database.IsEmptyRecord(err) {
|
|
slog.Error("failed to fetch networks", "error", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
|
|
logger.Log(2, r.Header.Get("user"), "fetched networks.")
|
|
logic.SortNetworks(allnetworks[:])
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(allnetworks)
|
|
}
|
|
|
|
// @Summary Get a network
|
|
// @Router /api/networks/{networkname} [get]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param networkname path string true "Network name"
|
|
// @Produce json
|
|
// @Success 200 {object} models.Network
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func getNetwork(w http.ResponseWriter, r *http.Request) {
|
|
// set header.
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var params = mux.Vars(r)
|
|
netname := params["networkname"]
|
|
network, err := logic.GetNetwork(netname)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch network [%s] info: %v",
|
|
netname, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
|
|
logger.Log(2, r.Header.Get("user"), "fetched network", netname)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(network)
|
|
}
|
|
|
|
// @Summary Update a network ACL (Access Control List)
|
|
// @Router /api/networks/{networkname}/acls [put]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param networkname path string true "Network name"
|
|
// @Param body body acls.ACLContainer true "ACL container"
|
|
// @Produce json
|
|
// @Success 200 {object} acls.ACLContainer
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var params = mux.Vars(r)
|
|
netname := params["networkname"]
|
|
var networkACLChange acls.ACLContainer
|
|
networkACLChange, err := networkACLChange.Get(acls.ContainerID(netname))
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"),
|
|
fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
err = json.NewDecoder(r.Body).Decode(&networkACLChange)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
newNetACL, err := networkACLChange.Save(acls.ContainerID(netname))
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"),
|
|
fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname)
|
|
|
|
// send peer updates
|
|
go func() {
|
|
if err = mq.PublishPeerUpdate(false); err != nil {
|
|
logger.Log(0, "failed to publish peer update after ACL update on network:", netname)
|
|
}
|
|
}()
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(newNetACL)
|
|
}
|
|
|
|
// @Summary Update a network ACL (Access Control List)
|
|
// @Router /api/networks/{networkname}/acls/v2 [put]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param networkname path string true "Network name"
|
|
// @Param body body acls.ACLContainer true "ACL container"
|
|
// @Produce json
|
|
// @Success 200 {object} acls.ACLContainer
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func updateNetworkACLv2(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var params = mux.Vars(r)
|
|
netname := params["networkname"]
|
|
var networkACLChange acls.ACLContainer
|
|
networkACLChange, err := networkACLChange.Get(acls.ContainerID(netname))
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"),
|
|
fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
err = json.NewDecoder(r.Body).Decode(&networkACLChange)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
// clone req body to use as return data successful update
|
|
retData := make(acls.ACLContainer)
|
|
data, err := json.Marshal(networkACLChange)
|
|
if err != nil {
|
|
slog.Error("failed to marshal networkACLChange whiles cloning", "error", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
err = json.Unmarshal(data, &retData)
|
|
if err != nil {
|
|
slog.Error("failed to unmarshal networkACLChange whiles cloning", "error", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
|
|
allNodes, err := logic.GetAllNodes()
|
|
if err != nil {
|
|
slog.Error("failed to fetch all nodes", "error", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
networkNodes := make([]models.Node, 0)
|
|
for _, node := range allNodes {
|
|
if node.Network == netname {
|
|
networkNodes = append(networkNodes, node)
|
|
}
|
|
}
|
|
networkNodesIdMap := make(map[string]models.Node)
|
|
for _, node := range networkNodes {
|
|
networkNodesIdMap[node.ID.String()] = node
|
|
}
|
|
networkClients, err := logic.GetNetworkExtClients(netname)
|
|
if err != nil {
|
|
slog.Error("failed to fetch network clients", "error", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
networkClientsMap := make(map[string]models.ExtClient)
|
|
for _, client := range networkClients {
|
|
networkClientsMap[client.ClientID] = client
|
|
}
|
|
|
|
// keep track of ingress gateways to disconnect from their clients
|
|
// this is required because PublishPeerUpdate only somehow does not stop communication
|
|
// between blocked clients and their ingress
|
|
assocClientsToDisconnectPerHost := make(map[uuid.UUID][]models.ExtClient)
|
|
|
|
// update client acls and then, remove client acls from req data to pass to existing functions
|
|
for id, acl := range networkACLChange {
|
|
// for node acls
|
|
if _, ok := networkNodesIdMap[string(id)]; ok {
|
|
nodeId := string(id)
|
|
// check acl update, then remove client entries
|
|
for id2 := range acl {
|
|
if _, ok := networkNodesIdMap[string(id2)]; !ok {
|
|
// update client acl
|
|
clientId := string(id2)
|
|
if client, ok := networkClientsMap[clientId]; ok {
|
|
if client.DeniedACLs == nil {
|
|
client.DeniedACLs = make(map[string]struct{})
|
|
}
|
|
if acl[acls.AclID(clientId)] == acls.NotAllowed {
|
|
client.DeniedACLs[nodeId] = struct{}{}
|
|
} else {
|
|
delete(client.DeniedACLs, string(nodeId))
|
|
}
|
|
networkClientsMap[clientId] = client
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// for client acls
|
|
clientId := string(id)
|
|
for id2 := range acl {
|
|
if _, ok := networkNodesIdMap[string(id2)]; !ok {
|
|
// update client acl
|
|
clientId2 := string(id2)
|
|
if client, ok := networkClientsMap[clientId]; ok {
|
|
if client.DeniedACLs == nil {
|
|
client.DeniedACLs = make(map[string]struct{})
|
|
}
|
|
{
|
|
// TODO: review this when client-to-client acls are supported
|
|
// if acl[acls.AclID(clientId2)] == acls.NotAllowed {
|
|
// client.DeniedACLs[clientId2] = struct{}{}
|
|
// } else {
|
|
// delete(client.DeniedACLs, clientId2)
|
|
// }
|
|
delete(client.DeniedACLs, clientId2)
|
|
}
|
|
networkClientsMap[clientId] = client
|
|
}
|
|
} else {
|
|
nodeId2 := string(id2)
|
|
if networkClientsMap[clientId].IngressGatewayID == nodeId2 && acl[acls.AclID(nodeId2)] == acls.NotAllowed {
|
|
assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID] = append(assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID], networkClientsMap[clientId])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// update each client in db for pro servers
|
|
if servercfg.IsPro {
|
|
for _, client := range networkClientsMap {
|
|
client := client
|
|
err := logic.DeleteExtClient(client.Network, client.ClientID)
|
|
if err != nil {
|
|
slog.Error(
|
|
"failed to delete client during update",
|
|
"client",
|
|
client.ClientID,
|
|
"error",
|
|
err.Error(),
|
|
)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
err = logic.SaveExtClient(&client)
|
|
if err != nil {
|
|
slog.Error(
|
|
"failed to save client during update",
|
|
"client",
|
|
client.ClientID,
|
|
"error",
|
|
err.Error(),
|
|
)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err = networkACLChange.Save(acls.ContainerID(netname))
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"),
|
|
fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname)
|
|
|
|
// send peer updates
|
|
go func() {
|
|
if err = mq.PublishPeerUpdate(false); err != nil {
|
|
logger.Log(0, "failed to publish peer update after ACL update on network:", netname)
|
|
}
|
|
|
|
// update ingress gateways of associated clients
|
|
hosts, err := logic.GetAllHosts()
|
|
if err != nil {
|
|
slog.Error(
|
|
"failed to fetch hosts after network ACL update. skipping publish extclients ACL",
|
|
"network",
|
|
netname,
|
|
)
|
|
return
|
|
}
|
|
hostsMap := make(map[uuid.UUID]models.Host)
|
|
for _, host := range hosts {
|
|
hostsMap[host.ID] = host
|
|
}
|
|
for hostId, clients := range assocClientsToDisconnectPerHost {
|
|
if host, ok := hostsMap[hostId]; ok {
|
|
if err = mq.PublishSingleHostPeerUpdate(&host, allNodes, nil, clients, false); err != nil {
|
|
slog.Error(
|
|
"failed to publish peer update to ingress after ACL update on network",
|
|
"network",
|
|
netname,
|
|
"host",
|
|
hostId,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(networkACLChange)
|
|
}
|
|
|
|
// @Summary Get a network ACL (Access Control List)
|
|
// @Router /api/networks/{networkname}/acls [get]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param networkname path string true "Network name"
|
|
// @Produce json
|
|
// @Success 200 {object} acls.ACLContainer
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func getNetworkACL(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var params = mux.Vars(r)
|
|
netname := params["networkname"]
|
|
var networkACL acls.ACLContainer
|
|
networkACL, err := networkACL.Get(acls.ContainerID(netname))
|
|
if err != nil {
|
|
if database.IsEmptyRecord(err) {
|
|
networkACL = acls.ACLContainer{}
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(networkACL)
|
|
return
|
|
}
|
|
logger.Log(0, r.Header.Get("user"),
|
|
fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
logger.Log(2, r.Header.Get("user"), "fetched acl for network", netname)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(networkACL)
|
|
}
|
|
|
|
// @Summary Delete a network
|
|
// @Router /api/networks/{networkname} [delete]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param networkname path string true "Network name"
|
|
// @Produce json
|
|
// @Success 200 {object} models.SuccessResponse
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
// @Failure 403 {object} models.ErrorResponse
|
|
func deleteNetwork(w http.ResponseWriter, r *http.Request) {
|
|
// Set header
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
var params = mux.Vars(r)
|
|
network := params["networkname"]
|
|
err := logic.DeleteNetwork(network)
|
|
if err != nil {
|
|
errtype := "badrequest"
|
|
if strings.Contains(err.Error(), "Node check failed") {
|
|
errtype = "forbidden"
|
|
}
|
|
logger.Log(0, r.Header.Get("user"),
|
|
fmt.Sprintf("failed to delete network [%s]: %v", network, err))
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype))
|
|
return
|
|
}
|
|
|
|
logger.Log(1, r.Header.Get("user"), "deleted network", network)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode("success")
|
|
}
|
|
|
|
// @Summary Create a network
|
|
// @Router /api/networks [post]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param body body models.Network true "Network details"
|
|
// @Produce json
|
|
// @Success 200 {object} models.Network
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
func createNetwork(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
var network models.Network
|
|
|
|
// we decode our body request params
|
|
err := json.NewDecoder(r.Body).Decode(&network)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
if len(network.NetID) > 32 {
|
|
err := errors.New("network name shouldn't exceed 32 characters")
|
|
logger.Log(0, r.Header.Get("user"), "failed to create network: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
if network.AddressRange == "" && network.AddressRange6 == "" {
|
|
err := errors.New("IPv4 or IPv6 CIDR required")
|
|
logger.Log(0, r.Header.Get("user"), "failed to create network: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
// validate address ranges: must be private
|
|
if network.AddressRange != "" {
|
|
_, _, err := net.ParseCIDR(network.AddressRange)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "failed to create network: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
}
|
|
if network.AddressRange6 != "" {
|
|
_, _, err := net.ParseCIDR(network.AddressRange6)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "failed to create network: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
}
|
|
|
|
network, err = logic.CreateNetwork(network)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "failed to create network: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
go func() {
|
|
defaultHosts := logic.GetDefaultHosts()
|
|
for i := range defaultHosts {
|
|
currHost := &defaultHosts[i]
|
|
newNode, err := logic.UpdateHostNetwork(currHost, network.NetID, true)
|
|
if err != nil {
|
|
logger.Log(
|
|
0,
|
|
r.Header.Get("user"),
|
|
"failed to add host to network:",
|
|
currHost.ID.String(),
|
|
network.NetID,
|
|
err.Error(),
|
|
)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
|
|
if err = mq.HostUpdate(&models.HostUpdate{
|
|
Action: models.JoinHostToNetwork,
|
|
Host: *currHost,
|
|
Node: *newNode,
|
|
}); err != nil {
|
|
logger.Log(
|
|
0,
|
|
r.Header.Get("user"),
|
|
"failed to add host to network:",
|
|
currHost.ID.String(),
|
|
network.NetID,
|
|
err.Error(),
|
|
)
|
|
}
|
|
// make host failover
|
|
logic.CreateFailOver(*newNode)
|
|
// make host remote access gateway
|
|
logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
|
|
}
|
|
// send peer updates
|
|
if err = mq.PublishPeerUpdate(false); err != nil {
|
|
logger.Log(1, "failed to publish peer update for default hosts after network is added")
|
|
}
|
|
}()
|
|
|
|
logger.Log(1, r.Header.Get("user"), "created network", network.NetID)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(network)
|
|
}
|
|
|
|
// @Summary Update network settings
|
|
// @Router /api/networks/{networkname} [put]
|
|
// @Tags Networks
|
|
// @Security oauth
|
|
// @Param networkname path string true "Network name"
|
|
// @Param body body models.Network true "Network details"
|
|
// @Produce json
|
|
// @Success 200 {object} models.Network
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
func updateNetwork(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
var payload models.Network
|
|
|
|
// we decode our body request params
|
|
err := json.NewDecoder(r.Body).Decode(&payload)
|
|
if err != nil {
|
|
slog.Info("error decoding request body", "user", r.Header.Get("user"), "err", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
netOld1, err := logic.GetNetwork(payload.NetID)
|
|
if err != nil {
|
|
slog.Info("error fetching network", "user", r.Header.Get("user"), "err", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
// partial update
|
|
netOld2 := netOld1
|
|
_, _, _, err = logic.UpdateNetwork(&netOld1, &netOld2)
|
|
if err != nil {
|
|
slog.Info("failed to update network", "user", r.Header.Get("user"), "err", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
slog.Info("updated network", "network", payload.NetID, "user", r.Header.Get("user"))
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(payload)
|
|
}
|