1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
15 type Vocabulary struct {
16 reservedTagKeys map[string]bool `json:"-"`
17 StrictTags bool `json:"strict_tags"`
18 Tags map[string]VocabularyTag `json:"tags"`
21 type VocabularyTag struct {
22 Strict bool `json:"strict"`
23 Labels []VocabularyLabel `json:"labels"`
24 Values map[string]VocabularyTagValue `json:"values"`
27 // Cannot have a constant map in Go, so we have to use a function
28 func (v *Vocabulary) systemTagKeys() map[string]bool {
29 return map[string]bool{
31 "template_uuid": true,
34 "image_timestamp": true,
35 "docker-image-repo-tag": true,
37 "container_request": true,
41 type VocabularyLabel struct {
42 Label string `json:"label"`
45 type VocabularyTagValue struct {
46 Labels []VocabularyLabel `json:"labels"`
49 func NewVocabulary(data []byte, managedTagKeys []string) (voc *Vocabulary, err error) {
50 if r := bytes.Compare(data, []byte("")); r == 0 {
51 return &Vocabulary{}, nil
53 err = json.Unmarshal(data, &voc)
55 return nil, fmt.Errorf("invalid JSON format error: %q", err)
57 if reflect.DeepEqual(voc, &Vocabulary{}) {
58 return nil, fmt.Errorf("JSON data provided doesn't match Vocabulary format: %q", data)
60 voc.reservedTagKeys = make(map[string]bool)
61 for _, managedKey := range managedTagKeys {
62 voc.reservedTagKeys[managedKey] = true
64 for systemKey := range voc.systemTagKeys() {
65 voc.reservedTagKeys[systemKey] = true
74 func (v *Vocabulary) Validate() error {
78 tagKeys := map[string]bool{}
79 // Checks for Vocabulary strictness
80 if v.StrictTags && len(v.Tags) == 0 {
81 return fmt.Errorf("vocabulary is strict but no tags are defined")
83 // Checks for duplicate tag keys
84 for key := range v.Tags {
85 if v.reservedTagKeys[key] {
86 return fmt.Errorf("tag key %q is reserved", key)
89 return fmt.Errorf("duplicate tag key %q", key)
92 for _, lbl := range v.Tags[key].Labels {
93 label := strings.ToLower(lbl.Label)
95 return fmt.Errorf("tag label %q for key %q already seen as a tag key or label", label, key)
99 // Checks for value strictness
100 if v.Tags[key].Strict && len(v.Tags[key].Values) == 0 {
101 return fmt.Errorf("tag key %q is configured as strict but doesn't provide values", key)
103 // Checks for value duplication within a key
104 tagValues := map[string]bool{}
105 for val := range v.Tags[key].Values {
107 return fmt.Errorf("duplicate tag value %q for tag %q", val, key)
109 tagValues[val] = true
110 for _, tagLbl := range v.Tags[key].Values[val].Labels {
111 label := strings.ToLower(tagLbl.Label)
112 if tagValues[label] {
113 return fmt.Errorf("tag value label %q for pair (%q:%q) already seen as a value key or label", label, key, val)
115 tagValues[label] = true
122 func (v *Vocabulary) getLabelsToKeys() (labels map[string]string) {
126 labels = make(map[string]string)
127 for key, val := range v.Tags {
128 for _, lbl := range val.Labels {
129 label := strings.ToLower(lbl.Label)
136 func (v *Vocabulary) getLabelsToValues(key string) (labels map[string]string) {
140 labels = make(map[string]string)
141 if _, ok := v.Tags[key]; ok {
142 for val := range v.Tags[key].Values {
143 for _, tagLbl := range v.Tags[key].Values[val].Labels {
144 label := strings.ToLower(tagLbl.Label)
152 func (v *Vocabulary) checkValue(key, val string) error {
153 if _, ok := v.Tags[key].Values[val]; !ok {
154 lcVal := strings.ToLower(val)
155 alias, ok := v.getLabelsToValues(key)[lcVal]
157 return fmt.Errorf("tag value %q for key %q is not defined but is an alias for %q", val, key, alias)
158 } else if v.Tags[key].Strict {
159 return fmt.Errorf("tag value %q for key %q is not listed as valid", val, key)
165 // Check validates the given data against the vocabulary.
166 func (v *Vocabulary) Check(data map[string]interface{}) error {
170 for key, val := range data {
171 // Checks for key validity
172 if v.reservedTagKeys[key] {
173 // Allow reserved keys to be used even if they are not defined in
174 // the vocabulary no matter its strictness.
177 if _, ok := v.Tags[key]; !ok {
178 lcKey := strings.ToLower(key)
179 alias, ok := v.getLabelsToKeys()[lcKey]
181 return fmt.Errorf("tag key %q is not defined but is an alias for %q", key, alias)
182 } else if v.StrictTags {
183 return fmt.Errorf("tag key %q is not defined", key)
185 // If the key is not defined, we don't need to check the value
188 // Checks for value validity -- key is defined
189 switch val := val.(type) {
191 return v.checkValue(key, val)
193 for _, singleVal := range val {
194 switch singleVal := singleVal.(type) {
196 err := v.checkValue(key, singleVal)
201 return fmt.Errorf("tag value %q for key %q is not a valid type (%T)", singleVal, key, singleVal)
205 return fmt.Errorf("tag value %q for key %q is not a valid type (%T)", val, key, val)