github.com/google/cloudprober@v0.11.3/probes/options/labels.go (about) 1 // Copyright 2017-2021 The Cloudprober Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package options 16 17 import ( 18 "regexp" 19 "strconv" 20 "strings" 21 "sync" 22 23 "github.com/google/cloudprober/targets/endpoint" 24 25 configpb "github.com/google/cloudprober/probes/proto" 26 ) 27 28 // targetLabelType for target based additional labels 29 type targetLabelType int 30 31 // TargetLabelType enum values. 32 const ( 33 notTargetLabel targetLabelType = iota 34 label 35 name 36 port 37 ) 38 39 var targetLabelRegex = regexp.MustCompile(`target.label.(.*)`) 40 41 type targetToken struct { 42 tokenType targetLabelType 43 labelKey string // target's label key. 44 } 45 46 // AdditionalLabel encapsulates additional labels to attach to probe results. 47 type AdditionalLabel struct { 48 mu sync.RWMutex 49 Key string 50 51 // If non-empty, additional label's value is independent of the target/ 52 staticValue string 53 54 // This map will allow for quick value lookup for a target. It will be 55 // updated by the probe while updating targets. 56 valueForTarget map[string]string 57 58 // At the time of parsing we split the label value at the delimiters ('@'). 59 // When we update an additional label for a target, we update the value 60 // parts that correspond to the substitution tokens and join them back. 61 valueParts []string 62 63 // Target based substitution tokens. 64 tokens []targetToken 65 } 66 67 // UpdateForTarget updates addtional label based on target's name and labels. 68 func (al *AdditionalLabel) UpdateForTarget(ep endpoint.Endpoint) { 69 al.mu.Lock() 70 defer al.mu.Unlock() 71 72 // Return early if this label has a static value. 73 if al.staticValue != "" { 74 return 75 } 76 77 if al.valueForTarget == nil { 78 al.valueForTarget = make(map[string]string) 79 } 80 81 parts := append([]string{}, al.valueParts...) 82 for i, tok := range al.tokens { 83 switch tok.tokenType { 84 case name: 85 parts[2*i+1] = ep.Name 86 case port: 87 parts[2*i+1] = strconv.Itoa(ep.Port) 88 case label: 89 parts[2*i+1] = ep.Labels[tok.labelKey] 90 } 91 } 92 al.valueForTarget[ep.Name] = strings.Join(parts, "") 93 } 94 95 // KeyValueForTarget returns key, value pair for the given target. 96 func (al *AdditionalLabel) KeyValueForTarget(targetName string) (key, val string) { 97 al.mu.RLock() 98 defer al.mu.RUnlock() 99 100 if al.staticValue != "" { 101 return al.Key, al.staticValue 102 } 103 return al.Key, al.valueForTarget[targetName] 104 } 105 106 // ParseAdditionalLabel parses an additional label proto message into an 107 // AdditionalLabel struct. 108 func ParseAdditionalLabel(alpb *configpb.AdditionalLabel) *AdditionalLabel { 109 al := &AdditionalLabel{ 110 Key: alpb.GetKey(), 111 } 112 113 al.valueParts = strings.Split(alpb.GetValue(), "@") 114 115 // No tokens 116 if len(al.valueParts) == 1 { 117 al.staticValue = alpb.GetValue() 118 return al 119 } 120 121 // If there are even number of parts after the split above, that means we 122 // don't have an even number of delimiters ('@'). Assume that the last 123 // token is incomplete and attach '@' to the front of the last part. 124 // e.g. @target.name@:@target.port 125 // valueParts: ["", "target.name", ":", "@target.port"] 126 lenParts := len(al.valueParts) 127 if lenParts%2 == 0 { 128 al.valueParts[lenParts-1] = "@" + al.valueParts[lenParts-1] 129 } 130 // tokens[i] -> parts[2*i+1] 131 // e.g. proto:@target.name@/@target.label.url@ --> 132 // valueParts: ["proto:", "target.name", "/", "target.label.url", ""] 133 // tokens: ["target.name", "target.label.url"] 134 numTokens := (len(al.valueParts) - 1) / 2 135 for i := 0; i < numTokens; i++ { 136 tokStr := al.valueParts[2*i+1] 137 if tokStr == "target.name" { 138 al.tokens = append(al.tokens, targetToken{tokenType: name}) 139 continue 140 } 141 if tokStr == "target.port" { 142 al.tokens = append(al.tokens, targetToken{tokenType: port}) 143 continue 144 } 145 matches := targetLabelRegex.FindStringSubmatch(tokStr) 146 if len(matches) == 2 { 147 al.tokens = append(al.tokens, targetToken{tokenType: label, labelKey: matches[1]}) 148 } 149 } 150 151 // if no valid tokens found, assign the value as it is. 152 if len(al.tokens) == 0 { 153 al.staticValue = alpb.GetValue() 154 } 155 156 return al 157 } 158 159 func parseAdditionalLabels(p *configpb.ProbeDef) []*AdditionalLabel { 160 var aLabels []*AdditionalLabel 161 162 for _, pb := range p.GetAdditionalLabel() { 163 aLabels = append(aLabels, ParseAdditionalLabel(pb)) 164 } 165 166 return aLabels 167 }