diff --git a/db.go b/db.go index 77d4ba03..fa200604 100644 --- a/db.go +++ b/db.go @@ -100,18 +100,18 @@ func (h *Headscale) getValue(key string) (string, error) { // setValue sets value for the given key in KV. func (h *Headscale) setValue(key string, value string) error { - kv := KV{ + keyValue := KV{ Key: key, Value: value, } if _, err := h.getValue(key); err == nil { - h.db.Model(&kv).Where("key = ?", key).Update("value", value) + h.db.Model(&keyValue).Where("key = ?", key).Update("value", value) return nil } - h.db.Create(kv) + h.db.Create(keyValue) return nil } diff --git a/machine.go b/machine.go index d1f27cc8..4dc9cd99 100644 --- a/machine.go +++ b/machine.go @@ -526,7 +526,7 @@ func (machine Machine) toNode( hostname = machine.Name } - n := tailcfg.Node{ + node := tailcfg.Node{ ID: tailcfg.NodeID(machine.ID), // this is the actual ID StableID: tailcfg.StableNodeID( strconv.FormatUint(machine.ID, BASE_10), @@ -551,7 +551,7 @@ func (machine Machine) toNode( Capabilities: []string{tailcfg.CapabilityFileSharing}, } - return &n, nil + return &node, nil } func (machine *Machine) toProto() *v1.Machine { diff --git a/oidc.go b/oidc.go index e68e1122..7e24ed3f 100644 --- a/oidc.go +++ b/oidc.go @@ -76,15 +76,15 @@ func (h *Headscale) RegisterOIDC(ctx *gin.Context) { return } - b := make([]byte, RANDOM_BYTE_SIZE) - if _, err := rand.Read(b); err != nil { + randomBlob := make([]byte, RANDOM_BYTE_SIZE) + if _, err := rand.Read(randomBlob); err != nil { log.Error().Msg("could not read 16 bytes from rand") ctx.String(http.StatusInternalServerError, "could not read 16 bytes from rand") return } - stateStr := hex.EncodeToString(b)[:32] + stateStr := hex.EncodeToString(randomBlob)[:32] // place the machine key into the state cache, so it can be retrieved later h.oidcStateCache.Set(stateStr, mKeyStr, OIDC_STATE_CACHE_EXPIRATION) diff --git a/poll.go b/poll.go index 19278538..aaf5bd0b 100644 --- a/poll.go +++ b/poll.go @@ -29,20 +29,20 @@ const ( // only after their first request (marked with the ReadOnly field). // // At this moment the updates are sent in a quite horrendous way, but they kinda work. -func (h *Headscale) PollNetMapHandler(c *gin.Context) { +func (h *Headscale) PollNetMapHandler(ctx *gin.Context) { log.Trace(). Str("handler", "PollNetMap"). - Str("id", c.Param("id")). + Str("id", ctx.Param("id")). Msg("PollNetMapHandler called") - body, _ := io.ReadAll(c.Request.Body) - mKeyStr := c.Param("id") + body, _ := io.ReadAll(ctx.Request.Body) + mKeyStr := ctx.Param("id") mKey, err := wgkey.ParseHex(mKeyStr) if err != nil { log.Error(). Str("handler", "PollNetMap"). Err(err). Msg("Cannot parse client key") - c.String(http.StatusBadRequest, "") + ctx.String(http.StatusBadRequest, "") return } @@ -53,36 +53,36 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { Str("handler", "PollNetMap"). Err(err). Msg("Cannot decode message") - c.String(http.StatusBadRequest, "") + ctx.String(http.StatusBadRequest, "") return } - m, err := h.GetMachineByMachineKey(mKey.HexString()) + machine, err := h.GetMachineByMachineKey(mKey.HexString()) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { log.Warn(). Str("handler", "PollNetMap"). Msgf("Ignoring request, cannot find machine with key %s", mKey.HexString()) - c.String(http.StatusUnauthorized, "") + ctx.String(http.StatusUnauthorized, "") return } log.Error(). Str("handler", "PollNetMap"). Msgf("Failed to fetch machine from the database with Machine key: %s", mKey.HexString()) - c.String(http.StatusInternalServerError, "") + ctx.String(http.StatusInternalServerError, "") } log.Trace(). Str("handler", "PollNetMap"). - Str("id", c.Param("id")). - Str("machine", m.Name). + Str("id", ctx.Param("id")). + Str("machine", machine.Name). Msg("Found machine in database") hostinfo, _ := json.Marshal(req.Hostinfo) - m.Name = req.Hostinfo.Hostname - m.HostInfo = datatypes.JSON(hostinfo) - m.DiscoKey = wgkey.Key(req.DiscoKey).HexString() + machine.Name = req.Hostinfo.Hostname + machine.HostInfo = datatypes.JSON(hostinfo) + machine.DiscoKey = wgkey.Key(req.DiscoKey).HexString() now := time.Now().UTC() // From Tailscale client: @@ -95,20 +95,20 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { // before their first real endpoint update. if !req.ReadOnly { endpoints, _ := json.Marshal(req.Endpoints) - m.Endpoints = datatypes.JSON(endpoints) - m.LastSeen = &now + machine.Endpoints = datatypes.JSON(endpoints) + machine.LastSeen = &now } - h.db.Save(&m) + h.db.Save(&machine) - data, err := h.getMapResponse(mKey, req, m) + data, err := h.getMapResponse(mKey, req, machine) if err != nil { log.Error(). Str("handler", "PollNetMap"). - Str("id", c.Param("id")). - Str("machine", m.Name). + Str("id", ctx.Param("id")). + Str("machine", machine.Name). Err(err). Msg("Failed to get Map response") - c.String(http.StatusInternalServerError, ":(") + ctx.String(http.StatusInternalServerError, ":(") return } @@ -120,8 +120,8 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { // Details on the protocol can be found in https://github.com/tailscale/tailscale/blob/main/tailcfg/tailcfg.go#L696 log.Debug(). Str("handler", "PollNetMap"). - Str("id", c.Param("id")). - Str("machine", m.Name). + Str("id", ctx.Param("id")). + Str("machine", machine.Name). Bool("readOnly", req.ReadOnly). Bool("omitPeers", req.OmitPeers). Bool("stream", req.Stream). @@ -130,16 +130,16 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { if req.ReadOnly { log.Info(). Str("handler", "PollNetMap"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Client is starting up. Probably interested in a DERP map") - c.Data(http.StatusOK, "application/json; charset=utf-8", data) + ctx.Data(http.StatusOK, "application/json; charset=utf-8", data) return } // There has been an update to _any_ of the nodes that the other nodes would // need to know about - h.setLastStateChangeToNow(m.Namespace.Name) + h.setLastStateChangeToNow(machine.Namespace.Name) // The request is not ReadOnly, so we need to set up channels for updating // peers via longpoll @@ -147,8 +147,8 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { // Only create update channel if it has not been created log.Trace(). Str("handler", "PollNetMap"). - Str("id", c.Param("id")). - Str("machine", m.Name). + Str("id", ctx.Param("id")). + Str("machine", machine.Name). Msg("Loading or creating update channel") updateChan := make(chan struct{}) @@ -162,13 +162,13 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { if req.OmitPeers && !req.Stream { log.Info(). Str("handler", "PollNetMap"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Client sent endpoint update and is ok with a response without peer list") - c.Data(http.StatusOK, "application/json; charset=utf-8", data) + ctx.Data(http.StatusOK, "application/json; charset=utf-8", data) // It sounds like we should update the nodes when we have received a endpoint update // even tho the comments in the tailscale code dont explicitly say so. - updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "endpoint-update"). + updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "endpoint-update"). Inc() go func() { updateChan <- struct{}{} }() @@ -176,34 +176,34 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { } else if req.OmitPeers && req.Stream { log.Warn(). Str("handler", "PollNetMap"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Ignoring request, don't know how to handle it") - c.String(http.StatusBadRequest, "") + ctx.String(http.StatusBadRequest, "") return } log.Info(). Str("handler", "PollNetMap"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Client is ready to access the tailnet") log.Info(). Str("handler", "PollNetMap"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Sending initial map") go func() { pollDataChan <- data }() log.Info(). Str("handler", "PollNetMap"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Notifying peers") - updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "full-update"). + updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "full-update"). Inc() go func() { updateChan <- struct{}{} }() h.PollNetMapStream( - c, - m, + ctx, + machine, req, mKey, pollDataChan, @@ -213,8 +213,8 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { ) log.Trace(). Str("handler", "PollNetMap"). - Str("id", c.Param("id")). - Str("machine", m.Name). + Str("id", ctx.Param("id")). + Str("machine", machine.Name). Msg("Finished stream, closing PollNetMap session") } @@ -222,33 +222,40 @@ func (h *Headscale) PollNetMapHandler(c *gin.Context) { // stream logic, ensuring we communicate updates and data // to the connected clients. func (h *Headscale) PollNetMapStream( - c *gin.Context, - m *Machine, - req tailcfg.MapRequest, - mKey wgkey.Key, + ctx *gin.Context, + machine *Machine, + mapRequest tailcfg.MapRequest, + machineKey wgkey.Key, pollDataChan chan []byte, keepAliveChan chan []byte, updateChan chan struct{}, cancelKeepAlive chan struct{}, ) { - go h.scheduledPollWorker(cancelKeepAlive, updateChan, keepAliveChan, mKey, req, m) + go h.scheduledPollWorker( + cancelKeepAlive, + updateChan, + keepAliveChan, + machineKey, + mapRequest, + machine, + ) - c.Stream(func(writer io.Writer) bool { + ctx.Stream(func(writer io.Writer) bool { log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Waiting for data to stream...") log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Msgf("pollData is %#v, keepAliveChan is %#v, updateChan is %#v", pollDataChan, keepAliveChan, updateChan) select { case data := <-pollDataChan: log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "pollData"). Int("bytes", len(data)). Msg("Sending data received via pollData channel") @@ -256,7 +263,7 @@ func (h *Headscale) PollNetMapStream( if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "pollData"). Err(err). Msg("Cannot write data") @@ -265,33 +272,33 @@ func (h *Headscale) PollNetMapStream( } log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "pollData"). Int("bytes", len(data)). Msg("Data from pollData channel written successfully") // TODO(kradalby): Abstract away all the database calls, this can cause race conditions // when an outdated machine object is kept alive, e.g. db is update from // command line, but then overwritten. - err = h.UpdateMachine(m) + err = h.UpdateMachine(machine) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "pollData"). Err(err). Msg("Cannot update machine from database") } now := time.Now().UTC() - m.LastSeen = &now + machine.LastSeen = &now - lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name). + lastStateUpdate.WithLabelValues(machine.Namespace.Name, machine.Name). Set(float64(now.Unix())) - m.LastSuccessfulUpdate = &now + machine.LastSuccessfulUpdate = &now - h.db.Save(&m) + h.db.Save(&machine) log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "pollData"). Int("bytes", len(data)). Msg("Machine entry in database updated successfully after sending pollData") @@ -301,7 +308,7 @@ func (h *Headscale) PollNetMapStream( case data := <-keepAliveChan: log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "keepAlive"). Int("bytes", len(data)). Msg("Sending keep alive message") @@ -309,7 +316,7 @@ func (h *Headscale) PollNetMapStream( if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "keepAlive"). Err(err). Msg("Cannot write keep alive message") @@ -318,28 +325,28 @@ func (h *Headscale) PollNetMapStream( } log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "keepAlive"). Int("bytes", len(data)). Msg("Keep alive sent successfully") // TODO(kradalby): Abstract away all the database calls, this can cause race conditions // when an outdated machine object is kept alive, e.g. db is update from // command line, but then overwritten. - err = h.UpdateMachine(m) + err = h.UpdateMachine(machine) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "keepAlive"). Err(err). Msg("Cannot update machine from database") } now := time.Now().UTC() - m.LastSeen = &now - h.db.Save(&m) + machine.LastSeen = &now + h.db.Save(&machine) log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "keepAlive"). Int("bytes", len(data)). Msg("Machine updated successfully after sending keep alive") @@ -349,23 +356,23 @@ func (h *Headscale) PollNetMapStream( case <-updateChan: log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "update"). Msg("Received a request for update") - updateRequestsReceivedOnChannel.WithLabelValues(m.Name, m.Namespace.Name). + updateRequestsReceivedOnChannel.WithLabelValues(machine.Name, machine.Namespace.Name). Inc() - if h.isOutdated(m) { + if h.isOutdated(machine) { log.Debug(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). - Time("last_successful_update", *m.LastSuccessfulUpdate). - Time("last_state_change", h.getLastStateChange(m.Namespace.Name)). - Msgf("There has been updates since the last successful update to %s", m.Name) - data, err := h.getMapResponse(mKey, req, m) + Str("machine", machine.Name). + Time("last_successful_update", *machine.LastSuccessfulUpdate). + Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)). + Msgf("There has been updates since the last successful update to %s", machine.Name) + data, err := h.getMapResponse(machineKey, mapRequest, machine) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "update"). Err(err). Msg("Could not get the map update") @@ -374,21 +381,21 @@ func (h *Headscale) PollNetMapStream( if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "update"). Err(err). Msg("Could not write the map response") - updateRequestsSentToNode.WithLabelValues(m.Name, m.Namespace.Name, "failed"). + updateRequestsSentToNode.WithLabelValues(machine.Name, machine.Namespace.Name, "failed"). Inc() return false } log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "update"). Msg("Updated Map has been sent") - updateRequestsSentToNode.WithLabelValues(m.Name, m.Namespace.Name, "success"). + updateRequestsSentToNode.WithLabelValues(machine.Name, machine.Namespace.Name, "success"). Inc() // Keep track of the last successful update, @@ -398,64 +405,64 @@ func (h *Headscale) PollNetMapStream( // TODO(kradalby): Abstract away all the database calls, this can cause race conditions // when an outdated machine object is kept alive, e.g. db is update from // command line, but then overwritten. - err = h.UpdateMachine(m) + err = h.UpdateMachine(machine) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "update"). Err(err). Msg("Cannot update machine from database") } now := time.Now().UTC() - lastStateUpdate.WithLabelValues(m.Namespace.Name, m.Name). + lastStateUpdate.WithLabelValues(machine.Namespace.Name, machine.Name). Set(float64(now.Unix())) - m.LastSuccessfulUpdate = &now + machine.LastSuccessfulUpdate = &now - h.db.Save(&m) + h.db.Save(&machine) } else { log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). - Time("last_successful_update", *m.LastSuccessfulUpdate). - Time("last_state_change", h.getLastStateChange(m.Namespace.Name)). - Msgf("%s is up to date", m.Name) + Str("machine", machine.Name). + Time("last_successful_update", *machine.LastSuccessfulUpdate). + Time("last_state_change", h.getLastStateChange(machine.Namespace.Name)). + Msgf("%s is up to date", machine.Name) } return true - case <-c.Request.Context().Done(): + case <-ctx.Request.Context().Done(): log.Info(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("The client has closed the connection") // TODO: Abstract away all the database calls, this can cause race conditions // when an outdated machine object is kept alive, e.g. db is update from // command line, but then overwritten. - err := h.UpdateMachine(m) + err := h.UpdateMachine(machine) if err != nil { log.Error(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "Done"). Err(err). Msg("Cannot update machine from database") } now := time.Now().UTC() - m.LastSeen = &now - h.db.Save(&m) + machine.LastSeen = &now + h.db.Save(&machine) log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "Done"). Msg("Cancelling keepAlive channel") cancelKeepAlive <- struct{}{} log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "Done"). Msg("Closing update channel") // h.closeUpdateChannel(m) @@ -463,14 +470,14 @@ func (h *Headscale) PollNetMapStream( log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "Done"). Msg("Closing pollData channel") close(pollDataChan) log.Trace(). Str("handler", "PollNetMapStream"). - Str("machine", m.Name). + Str("machine", machine.Name). Str("channel", "Done"). Msg("Closing keepAliveChan channel") close(keepAliveChan) @@ -484,9 +491,9 @@ func (h *Headscale) scheduledPollWorker( cancelChan <-chan struct{}, updateChan chan<- struct{}, keepAliveChan chan<- []byte, - mKey wgkey.Key, - req tailcfg.MapRequest, - m *Machine, + machineKey wgkey.Key, + mapRequest tailcfg.MapRequest, + machine *Machine, ) { keepAliveTicker := time.NewTicker(KEEP_ALIVE_INTERVAL) updateCheckerTicker := time.NewTicker(UPDATE_CHECK_INTERVAL) @@ -497,7 +504,7 @@ func (h *Headscale) scheduledPollWorker( return case <-keepAliveTicker.C: - data, err := h.getMapKeepAliveResponse(mKey, req) + data, err := h.getMapKeepAliveResponse(machineKey, mapRequest) if err != nil { log.Error(). Str("func", "keepAlive"). @@ -509,16 +516,16 @@ func (h *Headscale) scheduledPollWorker( log.Debug(). Str("func", "keepAlive"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Sending keepalive") keepAliveChan <- data case <-updateCheckerTicker.C: log.Debug(). Str("func", "scheduledPollWorker"). - Str("machine", m.Name). + Str("machine", machine.Name). Msg("Sending update request") - updateRequestsFromNode.WithLabelValues(m.Name, m.Namespace.Name, "scheduled-update"). + updateRequestsFromNode.WithLabelValues(machine.Name, machine.Namespace.Name, "scheduled-update"). Inc() updateChan <- struct{}{} } diff --git a/preauth_keys.go b/preauth_keys.go index 1a00077a..742e06cf 100644 --- a/preauth_keys.go +++ b/preauth_keys.go @@ -39,7 +39,7 @@ func (h *Headscale) CreatePreAuthKey( ephemeral bool, expiration *time.Time, ) (*PreAuthKey, error) { - n, err := h.GetNamespace(namespaceName) + namespace, err := h.GetNamespace(namespaceName) if err != nil { return nil, err } @@ -50,29 +50,29 @@ func (h *Headscale) CreatePreAuthKey( return nil, err } - k := PreAuthKey{ + key := PreAuthKey{ Key: kstr, - NamespaceID: n.ID, - Namespace: *n, + NamespaceID: namespace.ID, + Namespace: *namespace, Reusable: reusable, Ephemeral: ephemeral, CreatedAt: &now, Expiration: expiration, } - h.db.Save(&k) + h.db.Save(&key) - return &k, nil + return &key, nil } // ListPreAuthKeys returns the list of PreAuthKeys for a namespace. func (h *Headscale) ListPreAuthKeys(namespaceName string) ([]PreAuthKey, error) { - n, err := h.GetNamespace(namespaceName) + namespace, err := h.GetNamespace(namespaceName) if err != nil { return nil, err } keys := []PreAuthKey{} - if err := h.db.Preload("Namespace").Where(&PreAuthKey{NamespaceID: n.ID}).Find(&keys).Error; err != nil { + if err := h.db.Preload("Namespace").Where(&PreAuthKey{NamespaceID: namespace.ID}).Find(&keys).Error; err != nil { return nil, err } diff --git a/routes.go b/routes.go index d6acc3a5..9845b76d 100644 --- a/routes.go +++ b/routes.go @@ -15,12 +15,12 @@ func (h *Headscale) GetAdvertisedNodeRoutes( namespace string, nodeName string, ) (*[]netaddr.IPPrefix, error) { - m, err := h.GetMachine(namespace, nodeName) + machine, err := h.GetMachine(namespace, nodeName) if err != nil { return nil, err } - hostInfo, err := m.GetHostInfo() + hostInfo, err := machine.GetHostInfo() if err != nil { return nil, err } @@ -35,12 +35,12 @@ func (h *Headscale) GetEnabledNodeRoutes( namespace string, nodeName string, ) ([]netaddr.IPPrefix, error) { - m, err := h.GetMachine(namespace, nodeName) + machine, err := h.GetMachine(namespace, nodeName) if err != nil { return nil, err } - data, err := m.EnabledRoutes.MarshalJSON() + data, err := machine.EnabledRoutes.MarshalJSON() if err != nil { return nil, err } @@ -97,7 +97,7 @@ func (h *Headscale) EnableNodeRoute( nodeName string, routeStr string, ) error { - m, err := h.GetMachine(namespace, nodeName) + machine, err := h.GetMachine(namespace, nodeName) if err != nil { return err } @@ -137,10 +137,10 @@ func (h *Headscale) EnableNodeRoute( return err } - m.EnabledRoutes = datatypes.JSON(routes) - h.db.Save(&m) + machine.EnabledRoutes = datatypes.JSON(routes) + h.db.Save(&machine) - err = h.RequestMapUpdates(m.NamespaceID) + err = h.RequestMapUpdates(machine.NamespaceID) if err != nil { return err }