github.com/google/cloudprober@v0.11.3/sysvars/sysvars_gce.go (about)

     1  // Copyright 2017 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 sysvars
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"strings"
    22  
    23  	"cloud.google.com/go/compute/metadata"
    24  	md "github.com/google/cloudprober/common/metadata"
    25  	"github.com/google/cloudprober/logger"
    26  	compute "google.golang.org/api/compute/v1"
    27  )
    28  
    29  // maxNICs is the number of NICs allowed on a VM. Used by addGceNicInfo.
    30  var maxNICs = 8
    31  
    32  // commonGCEGKEVars sets the variables that are available on both - GCE and GKE
    33  // metadata. This list is based on
    34  // https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#gke_mds
    35  func commonGCEGKEVars(vars map[string]string) error {
    36  	metadataFuncs := map[string]func() (string, error){
    37  		"project":     metadata.ProjectID,
    38  		"project_id":  metadata.NumericProjectID,
    39  		"instance_id": metadata.InstanceID,
    40  	}
    41  
    42  	for k, fn := range metadataFuncs {
    43  		v, err := fn()
    44  		if err != nil {
    45  			return fmt.Errorf("sysvars_gce: error while getting %s from metadata: %v", k, err)
    46  		}
    47  		vars[k] = v
    48  	}
    49  
    50  	return nil
    51  }
    52  
    53  var gceVars = func(vars map[string]string, l *logger.Logger) (bool, error) {
    54  	onGCE := metadata.OnGCE()
    55  	if !onGCE {
    56  		return false, nil
    57  	}
    58  
    59  	if err := commonGCEGKEVars(vars); err != nil {
    60  		return onGCE, err
    61  	}
    62  
    63  	// If running on Kubernetes, don't bother setting rest of the variables. They
    64  	// may or may not be available. Also, they may not be very relevant for
    65  	// Kubernetes use case.
    66  	if md.IsKubernetes() {
    67  		// TODO(manugarg): See if we want to try setting variables on Kubernetes,
    68  		// e.g. pod-name, cluster-name, cluser-location, etc.
    69  		vars["namespace"] = md.KubernetesNamespace()
    70  		return onGCE, nil
    71  	}
    72  
    73  	// Helper function we use below.
    74  	getLastToken := func(value string) string {
    75  		tokens := strings.Split(value, "/")
    76  		return tokens[len(tokens)-1]
    77  	}
    78  
    79  	for _, k := range []string{
    80  		"zone",
    81  		"internal_ip",
    82  		"external_ip",
    83  		"instance_template",
    84  		"machine_type",
    85  	} {
    86  		var v string
    87  		var err error
    88  		switch k {
    89  		case "zone":
    90  			v, err = metadata.Zone()
    91  		case "internal_ip":
    92  			v, err = metadata.InternalIP()
    93  		case "external_ip":
    94  			v, err = metadata.ExternalIP()
    95  		case "instance_template":
    96  			// `instance_template` may not be defined, depending on how cloudprober
    97  			// was deployed. If an error is returned when fetching the metadata,
    98  			// just fall back "undefined".
    99  			v, err = metadata.InstanceAttributeValue("instance-template")
   100  			if err != nil {
   101  				l.Infof("No instance_template found. Defaulting to undefined.")
   102  				v = "undefined"
   103  				err = nil
   104  			} else {
   105  				v = getLastToken(v)
   106  			}
   107  		case "machine_type":
   108  			v, err = metadata.Get("instance/machine-type")
   109  			if err != nil {
   110  				l.Infof("Could not fetch machine type. Defaulting to undefined.")
   111  				v = "undefined"
   112  				err = nil
   113  			} else {
   114  				v = getLastToken(v)
   115  			}
   116  		default:
   117  			return onGCE, fmt.Errorf("sysvars_gce: unknown variable key %q", k)
   118  		}
   119  		if err != nil {
   120  			return onGCE, fmt.Errorf("sysvars_gce: error while getting %s from metadata: %v", k, err)
   121  		}
   122  		vars[k] = v
   123  	}
   124  
   125  	zoneParts := strings.Split(vars["zone"], "-")
   126  	vars["region"] = strings.Join(zoneParts[0:len(zoneParts)-1], "-")
   127  	addGceNicInfo(vars, l)
   128  
   129  	// Fetching instance name fails on some versions of GKE.
   130  	if instance, err := metadata.InstanceName(); err != nil {
   131  		l.Warningf("Error getting instance name on GCE, using HOSTNAME environment variable: %v", err)
   132  		vars["instance"] = os.Getenv("HOSTNAME")
   133  	} else {
   134  		vars["instance"] = instance
   135  	}
   136  
   137  	labels, err := labelsFromGCE(vars["project"], vars["zone"], vars["instance"])
   138  	if err != nil {
   139  		return onGCE, err
   140  	}
   141  
   142  	for k, v := range labels {
   143  		// Adds GCE labels to the dictionary with a 'label_' prefix so they can be
   144  		// referenced in the cfg file.
   145  		vars["label_"+k] = v
   146  
   147  	}
   148  	return onGCE, nil
   149  }
   150  
   151  func labelsFromGCE(project, zone, instance string) (map[string]string, error) {
   152  	ctx := context.Background()
   153  	computeService, err := compute.NewService(ctx)
   154  	if err != nil {
   155  		return nil, fmt.Errorf("error creating compute service to get instance labels: %v", err)
   156  	}
   157  
   158  	// Following call requires read-only access to compute API. We don't want to
   159  	// fail initialization if that happens.
   160  	i, err := computeService.Instances.Get(project, zone, instance).Context(ctx).Do()
   161  	if err != nil {
   162  		l.Warningf("sysvars_gce: Error while fetching the instance resource using GCE API: %v. Continuing without labels info.", err)
   163  		return nil, nil
   164  	}
   165  
   166  	return i.Labels, nil
   167  }
   168  
   169  // addGceNicInfo adds nic information to vars.
   170  // The following information is added for each nic.
   171  // - Primary IP, if one is assigned to nic.
   172  //	 If no primary IP is found, assume that NIC doesn't exist.
   173  // - IPv6 IP, if one is assigned to nic.
   174  //   If nic0 has IPv6 IP, then assign ip to key: "internal_ipv6_ip"
   175  // - External IP, if one is assigned to nic.
   176  // - An IP alias, if any IP alias ranges are assigned to nic.
   177  //
   178  // See the following document for more information on metadata.
   179  // https://cloud.google.com/compute/docs/storing-retrieving-metadata
   180  func addGceNicInfo(vars map[string]string, l *logger.Logger) {
   181  	for i := 0; i < maxNICs; i++ {
   182  		k := fmt.Sprintf("instance/network-interfaces/%v/ip", i)
   183  		v, err := metadata.Get(k)
   184  		// If there is no private IP for NIC, NIC doesn't exist.
   185  		if err != nil {
   186  			continue
   187  		}
   188  		vars[fmt.Sprintf("nic_%d_ip", i)] = v
   189  
   190  		k = fmt.Sprintf("instance/network-interfaces/%v/ipv6s", i)
   191  		v, err = metadata.Get(k)
   192  		if err != nil {
   193  			l.Debugf("VM does not have ipv6 ip on interface# %d", i)
   194  		} else {
   195  			v = strings.TrimSpace(v)
   196  			vars[k] = v
   197  			if i == 0 {
   198  				vars["internal_ipv6_ip"] = v
   199  			}
   200  		}
   201  	}
   202  }