abhishek9686 11 місяців тому
батько
коміт
d64f098181
7 змінених файлів з 278 додано та 31 видалено
  1. 1 0
      controllers/controller.go
  2. 80 0
      controllers/tags.go
  3. 3 0
      database/database.go
  4. 32 0
      logic/hosts.go
  5. 104 0
      logic/tags.go
  6. 32 31
      models/host.go
  7. 26 0
      models/tags.go

+ 1 - 0
controllers/controller.go

@@ -34,6 +34,7 @@ var HttpHandlers = []interface{}{
 	loggerHandlers,
 	hostHandlers,
 	enrollmentKeyHandlers,
+	tagHandlers,
 	legacyHandlers,
 }
 

+ 80 - 0
controllers/tags.go

@@ -0,0 +1,80 @@
+package controller
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+)
+
+func tagHandlers(r *mux.Router) {
+	r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(getAllTags))).
+		Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(createTag))).
+		Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(updateTag))).
+		Methods(http.MethodPut)
+
+}
+
+// @Summary     Get all Tag entries
+// @Router      /api/v1/tags [get]
+// @Tags        TAG
+// @Accept      json
+// @Success     200 {array} models.SuccessResponse
+// @Failure     500 {object} models.ErrorResponse
+func getAllTags(w http.ResponseWriter, r *http.Request) {
+	tags, err := logic.ListTagsWithHosts()
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to get all DNS entries: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logic.SortTagEntrys(tags[:])
+	logic.ReturnSuccessResponseWithJson(w, r, tags, "fetched all tags")
+}
+
+// @Summary     Create Tag
+// @Router      /api/v1/tags [post]
+// @Tags        TAG
+// @Accept      json
+// @Success     200 {array} models.SuccessResponse
+// @Failure     500 {object} models.ErrorResponse
+func createTag(w http.ResponseWriter, r *http.Request) {
+	var tag models.Tag
+	err := json.NewDecoder(r.Body).Decode(&tag)
+	if err != nil {
+		logger.Log(0, "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	err = logic.InsertTag(tag)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, tag, "created tag successfully")
+}
+
+// @Summary     Update Tag
+// @Router      /api/v1/tags [put]
+// @Tags        TAG
+// @Accept      json
+// @Success     200 {array} models.SuccessResponse
+// @Failure     500 {object} models.ErrorResponse
+func updateTag(w http.ResponseWriter, r *http.Request) {
+	var updateTag models.UpdateTagReq
+	err := json.NewDecoder(r.Body).Decode(&updateTag)
+	if err != nil {
+		logger.Log(0, "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	go logic.UpdateTag(updateTag)
+	logic.ReturnSuccessResponse(w, r, "updating tags")
+}

+ 3 - 0
database/database.go

@@ -67,6 +67,8 @@ const (
 	PENDING_USERS_TABLE_NAME = "pending_users"
 	// USER_INVITES - table for user invites
 	USER_INVITES_TABLE_NAME = "user_invites"
+	// TAG_TABLE_NAME - table for tags
+	TAG_TABLE_NAME = "tags"
 	// == ERROR CONSTS ==
 	// NO_RECORD - no singular result found
 	NO_RECORD = "no result found"
@@ -152,6 +154,7 @@ func createTables() {
 	CreateTable(PENDING_USERS_TABLE_NAME)
 	CreateTable(USER_PERMISSIONS_TABLE_NAME)
 	CreateTable(USER_INVITES_TABLE_NAME)
+	CreateTable(TAG_TABLE_NAME)
 }
 
 func CreateTable(tableName string) error {

+ 32 - 0
logic/hosts.go

@@ -572,3 +572,35 @@ func SortApiHosts(unsortedHosts []models.ApiHost) {
 		return unsortedHosts[i].ID < unsortedHosts[j].ID
 	})
 }
+
+func GetTagMapWithHosts() (tagHostMap map[models.TagID][]models.Host) {
+	tagHostMap = make(map[models.TagID][]models.Host)
+	hosts, _ := GetAllHosts()
+	for _, hostI := range hosts {
+		if hostI.Tags == nil {
+			continue
+		}
+		for hostTagID := range hostI.Tags {
+			if _, ok := tagHostMap[hostTagID]; ok {
+				tagHostMap[hostTagID] = append(tagHostMap[hostTagID], hostI)
+			} else {
+				tagHostMap[hostTagID] = []models.Host{hostI}
+			}
+		}
+	}
+	return
+}
+
+func GetHostsWithTag(tagID models.TagID) map[string]models.Host {
+	hMap := make(map[string]models.Host)
+	hosts, _ := GetAllHosts()
+	for _, hostI := range hosts {
+		if hostI.Tags == nil {
+			continue
+		}
+		if _, ok := hostI.Tags[tagID]; ok {
+			hMap[hostI.ID.String()] = hostI
+		}
+	}
+	return hMap
+}

+ 104 - 0
logic/tags.go

@@ -0,0 +1,104 @@
+package logic
+
+import (
+	"encoding/json"
+	"fmt"
+	"sort"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+)
+
+func GetTag(tagID string) (models.Tag, error) {
+	data, err := database.FetchRecord(database.TAG_TABLE_NAME, tagID)
+	if err != nil && !database.IsEmptyRecord(err) {
+		return models.Tag{}, err
+	}
+	tag := models.Tag{}
+	err = json.Unmarshal([]byte(data), &tag)
+	if err != nil {
+		return tag, err
+	}
+	return tag, nil
+}
+
+func InsertTag(tag models.Tag) error {
+	_, 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)
+}
+
+func DeleteTag(tagID string) error {
+	return database.DeleteRecord(database.TAG_TABLE_NAME, tagID)
+}
+
+func ListTagsWithHosts() ([]models.TagListResp, error) {
+	tags, err := ListTags()
+	if err != nil {
+		return []models.TagListResp{}, err
+	}
+	tagsHostMap := GetTagMapWithHosts()
+	resp := []models.TagListResp{}
+	for _, tagI := range tags {
+		tagRespI := models.TagListResp{
+			Tag:         tagI,
+			UsedByCnt:   len(tagsHostMap[tagI.ID]),
+			TaggedHosts: tagsHostMap[tagI.ID],
+		}
+		resp = append(resp, tagRespI)
+	}
+	return resp, nil
+}
+
+func ListTags() ([]models.Tag, error) {
+
+	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
+}
+
+func UpdateTag(req models.UpdateTagReq) {
+	tagHostsMap := GetHostsWithTag(req.ID)
+	for _, hostI := range req.TaggedHosts {
+		hostI := hostI
+
+		if _, ok := tagHostsMap[hostI.ID.String()]; !ok {
+			if hostI.Tags == nil {
+				hostI.Tags = make(map[models.TagID]struct{})
+			}
+			hostI.Tags[req.ID] = struct{}{}
+			UpsertHost(&hostI)
+		} else {
+			delete(tagHostsMap, hostI.ID.String())
+		}
+	}
+	for _, deletedTaggedHost := range tagHostsMap {
+		deletedTaggedHost := deletedTaggedHost
+		delete(deletedTaggedHost.Tags, req.ID)
+		UpsertHost(&deletedTaggedHost)
+	}
+}
+
+// SortTagEntrys - Sorts slice of Tag entries by their id
+func SortTagEntrys(tags []models.TagListResp) {
+	sort.Slice(tags, func(i, j int) bool {
+		return tags[i].ID < tags[j].ID
+	})
+}

+ 32 - 31
models/host.go

@@ -41,37 +41,38 @@ const (
 
 // Host - represents a host on the network
 type Host struct {
-	ID                  uuid.UUID        `json:"id"                      yaml:"id"`
-	Verbosity           int              `json:"verbosity"               yaml:"verbosity"`
-	FirewallInUse       string           `json:"firewallinuse"           yaml:"firewallinuse"`
-	Version             string           `json:"version"                 yaml:"version"`
-	IPForwarding        bool             `json:"ipforwarding"            yaml:"ipforwarding"`
-	DaemonInstalled     bool             `json:"daemoninstalled"         yaml:"daemoninstalled"`
-	AutoUpdate          bool             `json:"autoupdate"              yaml:"autoupdate"`
-	HostPass            string           `json:"hostpass"                yaml:"hostpass"`
-	Name                string           `json:"name"                    yaml:"name"`
-	OS                  string           `json:"os"                      yaml:"os"`
-	Interface           string           `json:"interface"               yaml:"interface"`
-	Debug               bool             `json:"debug"                   yaml:"debug"`
-	ListenPort          int              `json:"listenport"              yaml:"listenport"`
-	WgPublicListenPort  int              `json:"wg_public_listen_port"   yaml:"wg_public_listen_port"`
-	MTU                 int              `json:"mtu"                     yaml:"mtu"`
-	PublicKey           wgtypes.Key      `json:"publickey"               yaml:"publickey"`
-	MacAddress          net.HardwareAddr `json:"macaddress"              yaml:"macaddress"`
-	TrafficKeyPublic    []byte           `json:"traffickeypublic"        yaml:"traffickeypublic"`
-	Nodes               []string         `json:"nodes"                   yaml:"nodes"`
-	Interfaces          []Iface          `json:"interfaces"              yaml:"interfaces"`
-	DefaultInterface    string           `json:"defaultinterface"        yaml:"defaultinterface"`
-	EndpointIP          net.IP           `json:"endpointip"              yaml:"endpointip"`
-	EndpointIPv6        net.IP           `json:"endpointipv6"            yaml:"endpointipv6"`
-	IsDocker            bool             `json:"isdocker"                yaml:"isdocker"`
-	IsK8S               bool             `json:"isk8s"                   yaml:"isk8s"`
-	IsStaticPort        bool             `json:"isstaticport"            yaml:"isstaticport"`
-	IsStatic            bool             `json:"isstatic"        yaml:"isstatic"`
-	IsDefault           bool             `json:"isdefault"               yaml:"isdefault"`
-	NatType             string           `json:"nat_type,omitempty"      yaml:"nat_type,omitempty"`
-	TurnEndpoint        *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
-	PersistentKeepalive time.Duration    `json:"persistentkeepalive"     yaml:"persistentkeepalive"`
+	ID                  uuid.UUID          `json:"id"                      yaml:"id"`
+	Verbosity           int                `json:"verbosity"               yaml:"verbosity"`
+	FirewallInUse       string             `json:"firewallinuse"           yaml:"firewallinuse"`
+	Version             string             `json:"version"                 yaml:"version"`
+	IPForwarding        bool               `json:"ipforwarding"            yaml:"ipforwarding"`
+	DaemonInstalled     bool               `json:"daemoninstalled"         yaml:"daemoninstalled"`
+	AutoUpdate          bool               `json:"autoupdate"              yaml:"autoupdate"`
+	HostPass            string             `json:"hostpass"                yaml:"hostpass"`
+	Name                string             `json:"name"                    yaml:"name"`
+	OS                  string             `json:"os"                      yaml:"os"`
+	Interface           string             `json:"interface"               yaml:"interface"`
+	Debug               bool               `json:"debug"                   yaml:"debug"`
+	ListenPort          int                `json:"listenport"              yaml:"listenport"`
+	WgPublicListenPort  int                `json:"wg_public_listen_port"   yaml:"wg_public_listen_port"`
+	MTU                 int                `json:"mtu"                     yaml:"mtu"`
+	PublicKey           wgtypes.Key        `json:"publickey"               yaml:"publickey"`
+	MacAddress          net.HardwareAddr   `json:"macaddress"              yaml:"macaddress"`
+	TrafficKeyPublic    []byte             `json:"traffickeypublic"        yaml:"traffickeypublic"`
+	Nodes               []string           `json:"nodes"                   yaml:"nodes"`
+	Interfaces          []Iface            `json:"interfaces"              yaml:"interfaces"`
+	DefaultInterface    string             `json:"defaultinterface"        yaml:"defaultinterface"`
+	EndpointIP          net.IP             `json:"endpointip"              yaml:"endpointip"`
+	EndpointIPv6        net.IP             `json:"endpointipv6"            yaml:"endpointipv6"`
+	IsDocker            bool               `json:"isdocker"                yaml:"isdocker"`
+	IsK8S               bool               `json:"isk8s"                   yaml:"isk8s"`
+	IsStaticPort        bool               `json:"isstaticport"            yaml:"isstaticport"`
+	IsStatic            bool               `json:"isstatic"        yaml:"isstatic"`
+	IsDefault           bool               `json:"isdefault"               yaml:"isdefault"`
+	NatType             string             `json:"nat_type,omitempty"      yaml:"nat_type,omitempty"`
+	TurnEndpoint        *netip.AddrPort    `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
+	PersistentKeepalive time.Duration      `json:"persistentkeepalive"     yaml:"persistentkeepalive"`
+	Tags                map[TagID]struct{} `json:"tags" yaml:"tags"`
 }
 
 // FormatBool converts a boolean to a [yes|no] string

+ 26 - 0
models/tags.go

@@ -0,0 +1,26 @@
+package models
+
+import "time"
+
+type TagID string
+
+func (id TagID) String() string {
+	return string(id)
+}
+
+type Tag struct {
+	ID        TagID     `json:"id"`
+	CreatedBy string    `json:"created_by"`
+	CreatedAt time.Time `json:"created_at"`
+}
+
+type TagListResp struct {
+	Tag
+	UsedByCnt   int    `json:"used_by_count"`
+	TaggedHosts []Host `json:"tagged_hosts"`
+}
+
+type UpdateTagReq struct {
+	Tag
+	TaggedHosts []Host `json:"tagged_hosts"`
+}