github.com/google/cloudprober@v0.11.3/config/config.go (about) 1 // Copyright 2017-2020 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 /* 16 Package config provides parser for cloudprober configs. 17 18 Example Usage: 19 c, err := config.Parse(*configFile, sysvars.SysVars()) 20 21 Parse processes a config file as a Go text template and parses it into a ProberConfig proto. 22 Config file is processed using the provided variable map (usually GCP metadata variables) 23 and some predefined macros. 24 25 Macros 26 27 Cloudprober configs support some macros to make configs construction easier: 28 29 env 30 Get the value of an environment variable. 31 32 Example: 33 34 probe { 35 name: "dns_google_jp" 36 type: DNS 37 targets { 38 host_names: "1.1.1.1" 39 } 40 dns_probe { 41 resolved_domain: "{{env "TEST_DOM"}}" 42 } 43 } 44 45 # Then run cloudprober as: 46 TEST_DOM=google.co.jp ./cloudprober --config_file=cloudprober.cfg 47 48 gceCustomMetadata 49 Get value of a GCE custom metadata key. It first looks for the given key in 50 the instance's custom metadata and if it is not found there, it looks for it 51 in the project's custom metaata. 52 53 # Get load balancer IP from metadata. 54 probe { 55 name: "http_lb" 56 type: HTTP 57 targets { 58 host_names: "{{gceCustomMetadata "lb_ip"}}" 59 } 60 } 61 62 extractSubstring 63 Extract substring from a string using regex. Example use in config: 64 65 # Sharded VM-to-VM connectivity checks over internal IP 66 # Instance name format: ig-<zone>-<shard>-<random-characters>, e.g. ig-asia-east1-a-00-ftx1 67 {{$shard := .instance | extractSubstring "[^-]+-[^-]+-[^-]+-[^-]+-([^-]+)-.*" 1}} 68 probe { 69 name: "vm-to-vm-{{$shard}}" 70 type: PING 71 targets { 72 gce_targets { 73 instances {} 74 } 75 regex: "{{$targets}}" 76 } 77 run_on: "{{$run_on}}" 78 } 79 80 mkMap 81 Returns a map built from the arguments. It's useful as Go templates take only 82 one argument. With this function, we can create a map of multiple values and 83 pass it to a template. Example use in config: 84 85 {{define "probeTmpl"}} 86 probe { 87 type: {{.typ}} 88 name: "{{.name}}" 89 targets { 90 host_names: "www.google.com" 91 } 92 } 93 {{end}} 94 95 {{template "probeTmpl" mkMap "typ" "PING" "name" "ping_google"}} 96 {{template "probeTmpl" mkMap "typ" "HTTP" "name" "http_google"}} 97 98 99 mkSlice 100 Returns a slice consisting of the arguments. It can be used with the built-in 101 'range' function to replicate text. 102 103 104 {{with $regions := mkSlice "us=central1" "us-east1"}} 105 {{range $_, $region := $regions}} 106 107 probe { 108 name: "service-a-{{$region}}" 109 type: HTTP 110 targets { 111 host_names: "service-a.{{$region}}.corp.xx.com" 112 } 113 } 114 115 {{end}} 116 {{end}} 117 */ 118 package config 119 120 import ( 121 "bytes" 122 "errors" 123 "fmt" 124 "os" 125 "regexp" 126 "text/template" 127 128 "cloud.google.com/go/compute/metadata" 129 "github.com/golang/protobuf/proto" 130 configpb "github.com/google/cloudprober/config/proto" 131 ) 132 133 // ReadFromGCEMetadata returns the value of GCE custom metadata variables. To 134 // allow for instance level as project level variables, it looks for metadata 135 // variable in the following order: 136 // a. If the given key is set in the instance's custom metadata, its value is 137 // returned. 138 // b. If (and only if), the key is not found in the step above, we look for 139 // the same key in the project's custom metadata. 140 var ReadFromGCEMetadata = func(metadataKeyName string) (string, error) { 141 val, err := metadata.InstanceAttributeValue(metadataKeyName) 142 // If instance level config found, return 143 if _, notFound := err.(metadata.NotDefinedError); !notFound { 144 return val, err 145 } 146 // Check project level config next 147 return metadata.ProjectAttributeValue(metadataKeyName) 148 } 149 150 // DefaultConfig returns the default config string. 151 func DefaultConfig() string { 152 return proto.MarshalTextString(&configpb.ProberConfig{}) 153 } 154 155 // ParseTemplate processes a config file as a Go text template. 156 func ParseTemplate(config string, sysVars map[string]string) (string, error) { 157 return parseTemplate(config, sysVars, false) 158 } 159 160 // parseTemplate processes a config file as a Go text template. 161 func parseTemplate(config string, sysVars map[string]string, testMode bool) (string, error) { 162 gceCustomMetadataFunc := func(v string) (string, error) { 163 if testMode { 164 return v + "-test-value", nil 165 } 166 167 // We return "undefined" if metadata variable is not defined. 168 val, err := ReadFromGCEMetadata(v) 169 if err != nil { 170 if _, notFound := err.(metadata.NotDefinedError); notFound { 171 return "undefined", nil 172 } 173 return "", err 174 } 175 return val, nil 176 } 177 178 funcMap := map[string]interface{}{ 179 // env allows a user to lookup the value of a environment variable in 180 // the configuration 181 "env": func(key string) string { 182 value, ok := os.LookupEnv(key) 183 if !ok { 184 return "" 185 } 186 return value 187 }, 188 189 "gceCustomMetadata": gceCustomMetadataFunc, 190 191 // extractSubstring allows us to extract substring from a string using 192 // regex matching groups. 193 "extractSubstring": func(re string, n int, s string) (string, error) { 194 r, err := regexp.Compile(re) 195 if err != nil { 196 return "", err 197 } 198 matches := r.FindStringSubmatch(s) 199 if len(matches) <= n { 200 return "", fmt.Errorf("Match number %d not found. Regex: %s, String: %s", n, re, s) 201 } 202 return matches[n], nil 203 }, 204 // mkMap makes a map from its argume 205 "mkMap": func(values ...interface{}) (map[string]interface{}, error) { 206 if len(values)%2 != 0 { 207 return nil, errors.New("invalid mkMap call, need even number of args") 208 } 209 m := make(map[string]interface{}, len(values)/2) 210 for i := 0; i < len(values); i += 2 { 211 key, ok := values[i].(string) 212 if !ok { 213 return nil, errors.New("map keys must be strings") 214 } 215 m[key] = values[i+1] 216 } 217 return m, nil 218 }, 219 220 // mkSlice makes a slice from its arguments. 221 "mkSlice": func(args ...interface{}) []interface{} { 222 return args 223 }, 224 } 225 configTmpl, err := template.New("cloudprober_cfg").Funcs(funcMap).Parse(config) 226 if err != nil { 227 return "", err 228 } 229 var b bytes.Buffer 230 if err := configTmpl.Execute(&b, sysVars); err != nil { 231 return "", err 232 } 233 return b.String(), nil 234 } 235 236 // Parse processes a config file as a Go text template and parses it into a 237 // ProberConfig proto. 238 func Parse(config string, sysVars map[string]string) (*configpb.ProberConfig, error) { 239 textConfig, err := ParseTemplate(config, sysVars) 240 if err != nil { 241 return nil, err 242 } 243 cfg := &configpb.ProberConfig{} 244 if err = proto.UnmarshalText(textConfig, cfg); err != nil { 245 return nil, err 246 } 247 return cfg, nil 248 } 249 250 // ParseForTest processes a config file as a Go text template in test mode and 251 // parses it into a ProberConfig proto. This function is useful for testing 252 // configs. 253 func ParseForTest(config string, sysVars map[string]string) (*configpb.ProberConfig, error) { 254 textConfig, err := parseTemplate(config, sysVars, true) 255 if err != nil { 256 return nil, err 257 } 258 cfg := &configpb.ProberConfig{} 259 if err = proto.UnmarshalText(textConfig, cfg); err != nil { 260 return nil, err 261 } 262 return cfg, nil 263 }