1package v2
2
3import (
4	"encoding/json"
5	"errors"
6	"fmt"
7	"net/url"
8	"path"
9	"sort"
10	"strconv"
11	"strings"
12
13	utilstrings "github.com/sensu/sensu-go/util/strings"
14)
15
16const (
17	// EntitiesResource is the name of this resource type
18	EntitiesResource = "entities"
19
20	// EntityAgentClass is the name of the class given to agent entities.
21	EntityAgentClass = "agent"
22
23	// EntityProxyClass is the name of the class given to proxy entities.
24	EntityProxyClass = "proxy"
25
26	// EntityBackendClass is the name of the class given to backend entities.
27	EntityBackendClass = "backend"
28
29	// Redacted is filled in for fields that contain sensitive information
30	Redacted = "REDACTED"
31)
32
33// DefaultRedactFields contains the default fields to redact
34var DefaultRedactFields = []string{"password", "passwd", "pass", "api_key",
35	"api_token", "access_key", "secret_key", "private_key", "secret"}
36
37// StorePrefix returns the path prefix to this resource in the store
38func (e *Entity) StorePrefix() string {
39	return EntitiesResource
40}
41
42// URIPath returns the path component of an entity URI.
43func (e *Entity) URIPath() string {
44	return path.Join(URLPrefix, "namespaces", url.PathEscape(e.Namespace), EntitiesResource, url.PathEscape(e.Name))
45}
46
47// Validate returns an error if the entity is invalid.
48func (e *Entity) Validate() error {
49	if err := ValidateName(e.Name); err != nil {
50		return errors.New("entity name " + err.Error())
51	}
52
53	if err := ValidateName(e.EntityClass); err != nil {
54		return errors.New("entity class " + err.Error())
55	}
56
57	if e.Namespace == "" {
58		return errors.New("namespace must be set")
59	}
60
61	return nil
62}
63
64// NewEntity creates a new Entity.
65func NewEntity(meta ObjectMeta) *Entity {
66	return &Entity{ObjectMeta: meta}
67}
68
69func redactMap(m map[string]string, redact []string) map[string]string {
70	if len(redact) == 0 {
71		redact = DefaultRedactFields
72	}
73	result := make(map[string]string, len(m))
74	for k, v := range m {
75		if utilstrings.FoundInArray(k, redact) {
76			result[k] = Redacted
77		} else {
78			result[k] = v
79		}
80	}
81	return result
82}
83
84// GetRedactedEntity redacts the entity according to the entity's Redact fields.
85// A redacted copy is returned. The copy contains pointers to the original's
86// memory, with different Labels and Annotations.
87func (e *Entity) GetRedactedEntity() *Entity {
88	if e == nil {
89		return nil
90	}
91	if e.Labels == nil && e.Annotations == nil {
92		return e
93	}
94	ent := &Entity{}
95	*ent = *e
96	ent.Annotations = redactMap(e.Annotations, e.Redact)
97	ent.Labels = redactMap(e.Labels, e.Redact)
98	return ent
99}
100
101// MarshalJSON implements the json.Marshaler interface.
102func (e *Entity) MarshalJSON() ([]byte, error) {
103	// Redact the entity before marshalling the entity so we don't leak any
104	// sensitive information
105	e = e.GetRedactedEntity()
106
107	type Clone Entity
108	clone := (*Clone)(e)
109
110	return json.Marshal(clone)
111}
112
113// GetEntitySubscription returns the entity subscription, using the format
114// "entity:entityName"
115func GetEntitySubscription(entityName string) string {
116	return fmt.Sprintf("entity:%s", entityName)
117}
118
119// FixtureEntity returns a testing fixture for an Entity object.
120func FixtureEntity(name string) *Entity {
121	return &Entity{
122		EntityClass:   "host",
123		Subscriptions: []string{"linux"},
124		ObjectMeta: ObjectMeta{
125			Namespace: "default",
126			Name:      name,
127		},
128		System: System{
129			Arch: "amd64",
130		},
131	}
132}
133
134//
135// Sorting
136
137// SortEntitiesByPredicate can be used to sort a given collection using a given
138// predicate.
139func SortEntitiesByPredicate(es []*Entity, fn func(a, b *Entity) bool) sort.Interface {
140	return &entitySorter{entities: es, byFn: fn}
141}
142
143// SortEntitiesByID can be used to sort a given collection of entities by their
144// IDs.
145func SortEntitiesByID(es []*Entity, asc bool) sort.Interface {
146	if asc {
147		return SortEntitiesByPredicate(es, func(a, b *Entity) bool {
148			return a.Name < b.Name
149		})
150	}
151	return SortEntitiesByPredicate(es, func(a, b *Entity) bool {
152		return a.Name > b.Name
153	})
154}
155
156// SortEntitiesByLastSeen can be used to sort a given collection of entities by
157// last time each was seen.
158func SortEntitiesByLastSeen(es []*Entity) sort.Interface {
159	return SortEntitiesByPredicate(es, func(a, b *Entity) bool {
160		return a.LastSeen > b.LastSeen
161	})
162}
163
164type entitySorter struct {
165	entities []*Entity
166	byFn     func(a, b *Entity) bool
167}
168
169// Len implements sort.Interface.
170func (s *entitySorter) Len() int {
171	return len(s.entities)
172}
173
174// Swap implements sort.Interface.
175func (s *entitySorter) Swap(i, j int) {
176	s.entities[i], s.entities[j] = s.entities[j], s.entities[i]
177}
178
179// Less implements sort.Interface.
180func (s *entitySorter) Less(i, j int) bool {
181	return s.byFn(s.entities[i], s.entities[j])
182}
183
184// EntityFields returns a set of fields that represent that resource
185func EntityFields(r Resource) map[string]string {
186	resource := r.(*Entity)
187	return map[string]string{
188		"entity.name":          resource.ObjectMeta.Name,
189		"entity.namespace":     resource.ObjectMeta.Namespace,
190		"entity.deregister":    strconv.FormatBool(resource.Deregister),
191		"entity.entity_class":  resource.EntityClass,
192		"entity.subscriptions": strings.Join(resource.Subscriptions, ","),
193	}
194}
195
196// SetNamespace sets the namespace of the resource.
197func (e *Entity) SetNamespace(namespace string) {
198	e.Namespace = namespace
199}
200