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  }