| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 | package logicimport (	"encoding/json"	"errors"	"fmt"	"regexp"	"sort"	"sync"	"time"	"github.com/gravitl/netmaker/database"	"github.com/gravitl/netmaker/models"	"golang.org/x/exp/slog")var tagMutex = &sync.RWMutex{}// GetTag - fetches tag infofunc GetTag(tagID models.TagID) (models.Tag, error) {	data, err := database.FetchRecord(database.TAG_TABLE_NAME, tagID.String())	if err != nil {		return models.Tag{}, err	}	tag := models.Tag{}	err = json.Unmarshal([]byte(data), &tag)	if err != nil {		return tag, err	}	return tag, nil}// InsertTag - creates new tagfunc InsertTag(tag models.Tag) error {	tagMutex.Lock()	defer tagMutex.Unlock()	_, err := database.FetchRecord(database.TAG_TABLE_NAME, tag.ID.String())	if err == nil {		return fmt.Errorf("tag `%s` exists already", tag.ID)	}	d, err := json.Marshal(tag)	if err != nil {		return err	}	return database.Insert(tag.ID.String(), string(d), database.TAG_TABLE_NAME)}// DeleteTag - delete tag, will also untag hostsfunc DeleteTag(tagID models.TagID, removeFromPolicy bool) error {	tagMutex.Lock()	defer tagMutex.Unlock()	// cleanUp tags on hosts	tag, err := GetTag(tagID)	if err != nil {		return err	}	nodes, err := GetNetworkNodes(tag.Network.String())	if err != nil {		return err	}	for _, nodeI := range nodes {		nodeI := nodeI		if _, ok := nodeI.Tags[tagID]; ok {			delete(nodeI.Tags, tagID)			UpsertNode(&nodeI)		}	}	if removeFromPolicy {		// remove tag used on acl policy		go RemoveDeviceTagFromAclPolicies(tagID, tag.Network)	}	extclients, _ := GetNetworkExtClients(tag.Network.String())	for _, extclient := range extclients {		if _, ok := extclient.Tags[tagID]; ok {			delete(extclient.Tags, tagID)			SaveExtClient(&extclient)		}	}	return database.DeleteRecord(database.TAG_TABLE_NAME, tagID.String())}// ListTagsWithHosts - lists all tags with tagged hostsfunc ListTagsWithNodes(netID models.NetworkID) ([]models.TagListResp, error) {	tags, err := ListNetworkTags(netID)	if err != nil {		return []models.TagListResp{}, err	}	tagsNodeMap := GetTagMapWithNodesByNetwork(netID)	resp := []models.TagListResp{}	for _, tagI := range tags {		tagRespI := models.TagListResp{			Tag:         tagI,			UsedByCnt:   len(tagsNodeMap[tagI.ID]),			TaggedNodes: GetAllNodesAPI(tagsNodeMap[tagI.ID]),		}		resp = append(resp, tagRespI)	}	return resp, nil}// ListTags - lists all tags from DBfunc ListTags() ([]models.Tag, error) {	tagMutex.RLock()	defer tagMutex.RUnlock()	data, err := database.FetchRecords(database.TAG_TABLE_NAME)	if err != nil && !database.IsEmptyRecord(err) {		return []models.Tag{}, err	}	tags := []models.Tag{}	for _, dataI := range data {		tag := models.Tag{}		err := json.Unmarshal([]byte(dataI), &tag)		if err != nil {			continue		}		tags = append(tags, tag)	}	return tags, nil}// ListTags - lists all tags from DBfunc ListNetworkTags(netID models.NetworkID) ([]models.Tag, error) {	tagMutex.RLock()	defer tagMutex.RUnlock()	data, err := database.FetchRecords(database.TAG_TABLE_NAME)	if err != nil && !database.IsEmptyRecord(err) {		return []models.Tag{}, err	}	tags := []models.Tag{}	for _, dataI := range data {		tag := models.Tag{}		err := json.Unmarshal([]byte(dataI), &tag)		if err != nil {			continue		}		if tag.Network == netID {			tags = append(tags, tag)		}	}	return tags, nil}// UpdateTag - updates and syncs hosts with tag updatefunc UpdateTag(req models.UpdateTagReq, newID models.TagID) {	tagMutex.Lock()	defer tagMutex.Unlock()	var err error	tagNodesMap := GetNodesWithTag(req.ID)	for _, apiNode := range req.TaggedNodes {		node := models.Node{}		var nodeID string		if apiNode.IsStatic {			if apiNode.StaticNode.RemoteAccessClientID != "" {				continue			}			extclient, err := GetExtClient(apiNode.StaticNode.ClientID, apiNode.StaticNode.Network)			if err != nil {				continue			}			node.IsStatic = true			nodeID = extclient.ClientID			node.StaticNode = extclient		} else {			node, err = GetNodeByID(apiNode.ID)			if err != nil {				continue			}			nodeID = node.ID.String()		}		if _, ok := tagNodesMap[nodeID]; !ok {			if node.StaticNode.Tags == nil {				node.StaticNode.Tags = make(map[models.TagID]struct{})			}			if node.Tags == nil {				node.Tags = make(map[models.TagID]struct{})			}			if newID != "" {				if node.IsStatic {					node.StaticNode.Tags[newID] = struct{}{}					SaveExtClient(&node.StaticNode)				} else {					node.Tags[newID] = struct{}{}					UpsertNode(&node)				}			} else {				if node.IsStatic {					node.StaticNode.Tags[req.ID] = struct{}{}					SaveExtClient(&node.StaticNode)				} else {					node.Tags[req.ID] = struct{}{}					UpsertNode(&node)				}			}		} else {			if newID != "" {				delete(node.Tags, req.ID)				delete(node.StaticNode.Tags, req.ID)				if node.IsStatic {					node.StaticNode.Tags[newID] = struct{}{}					SaveExtClient(&node.StaticNode)				} else {					node.Tags[newID] = struct{}{}					UpsertNode(&node)				}			}			delete(tagNodesMap, nodeID)		}	}	for _, deletedTaggedNode := range tagNodesMap {		delete(deletedTaggedNode.Tags, req.ID)		delete(deletedTaggedNode.StaticNode.Tags, req.ID)		if deletedTaggedNode.IsStatic {			SaveExtClient(&deletedTaggedNode.StaticNode)		} else {			UpsertNode(&deletedTaggedNode)		}	}	go func(req models.UpdateTagReq) {		if newID != "" {			tagNodesMap = GetNodesWithTag(req.ID)			for _, nodeI := range tagNodesMap {				nodeI := nodeI				if nodeI.StaticNode.Tags == nil {					nodeI.StaticNode.Tags = make(map[models.TagID]struct{})				}				if nodeI.Tags == nil {					nodeI.Tags = make(map[models.TagID]struct{})				}				delete(nodeI.Tags, req.ID)				delete(nodeI.StaticNode.Tags, req.ID)				nodeI.Tags[newID] = struct{}{}				nodeI.StaticNode.Tags[newID] = struct{}{}				if nodeI.IsStatic {					SaveExtClient(&nodeI.StaticNode)				} else {					UpsertNode(&nodeI)				}			}		}	}(req)}// SortTagEntrys - Sorts slice of Tag entries by their idfunc SortTagEntrys(tags []models.TagListResp) {	sort.Slice(tags, func(i, j int) bool {		return tags[i].ID < tags[j].ID	})}func CheckIDSyntax(id string) error {	if id == "" {		return errors.New("name is required")	}	if len(id) < 3 {		return errors.New("name should have min 3 characters")	}	reg, err := regexp.Compile("^[a-zA-Z-]+$")	if err != nil {		return err	}	if !reg.MatchString(id) {		return errors.New("invalid name. allowed characters are [a-zA-Z-]")	}	return nil}func CreateDefaultTags(netID models.NetworkID) {	// create tag for remote access gws in the network	tag := models.Tag{		ID:        models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.RemoteAccessTagName)),		TagName:   models.RemoteAccessTagName,		Network:   netID,		CreatedBy: "auto",		CreatedAt: time.Now(),	}	_, err := GetTag(tag.ID)	if err == nil {		return	}	err = InsertTag(tag)	if err != nil {		slog.Error("failed to create remote access gw tag", "error", err.Error())		return	}}
 |