| // Copyright 2021 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package configfetcher |
| |
| import ( |
| "fmt" |
| "strconv" |
| "time" |
| |
| "policy-manager/protos" |
| |
| "cloud.google.com/go/compute/metadata" |
| "github.com/golang/glog" |
| "github.com/golang/protobuf/jsonpb" |
| "github.com/golang/protobuf/proto" |
| ) |
| |
| const ( |
| // gciLegacyConfigKey is the GCE metadata Key used to specify user's preference of |
| // the InstanceConfig. The value of this key should be JSON representation |
| // of the InstanceConfig proto. |
| // DEPRECATED. Use the specific keys below. |
| gciLegacyConfigKey = "gci-instance-config" |
| |
| // Metadata keys for each InstanceConfig attribute that users can specify. |
| // Although we continue supporting metadata key with 'gci-' prefix, users |
| // should use the keys with 'cos-' prefix if possible. |
| gciKeyUpdateStrategy = "gci-update-strategy" |
| gciKeyMetricsEnabled = "gci-metrics-enabled" |
| |
| // Metadata keys with 'cos-' prefix |
| cosKeyUpdateStrategy = "cos-update-strategy" |
| cosKeyMetricsEnabled = "cos-metrics-enabled" |
| |
| // Metadata keys for GCP-related features. |
| keyGoogleLoggingEnabled = "google-logging-enabled" |
| keyGoogleMonitoringEnabled = "google-monitoring-enabled" |
| |
| // The sleep time when watching user config failed. |
| retryInterval = 5 * time.Second |
| ) |
| |
| // FetchUserConfig returns the InstanceConfig specified by user in GCE metadata. A nil is |
| // returned if there is any error. |
| func FetchUserConfig() (*protos.InstanceConfig, error) { |
| if userConfig, err := getUserConfig(); err == nil { |
| return userConfig, nil |
| } else { |
| return nil, err |
| } |
| } |
| |
| // PollUserConfig watches the update of the InstanceConfig specified by user in GCE |
| // metadata. It repeatedly sends request to wait for update of userConfig. When an update |
| // is returned, PollUserConfig compares it to the cached userConfig and outputs to the |
| // channel if it is really updated. |
| func PollUserConfig(out chan *protos.InstanceConfig) { |
| var userConfig, lastUserConfig *protos.InstanceConfig |
| var err error |
| lastUserConfig = nil |
| for { |
| userConfig, err = getUserConfig() |
| if err != nil { |
| time.Sleep(retryInterval) |
| continue // Retry forever. |
| } |
| // Check whether userConfig is updated. |
| if proto.Equal(lastUserConfig, userConfig) { |
| glog.V(1).Infof( |
| "Instance custom metadata are updated but instanceConfig stays the same: {%+v}", |
| userConfig) |
| continue |
| } |
| // To cache the original userConfig, a deep copy is necessary. |
| lastUserConfig = proto.Clone(userConfig).(*protos.InstanceConfig) |
| out <- userConfig |
| } |
| } |
| |
| // getUserConfig gets the content of the instance custom metadata keys'. |
| // Then getUserConfig parses the metadata keys and returns userConfig. |
| func getUserConfig() (*protos.InstanceConfig, error) { |
| var metadataKeys []string |
| var err error |
| if metadataKeys, err = metadata.InstanceAttributes(); err != nil { |
| return nil, fmt.Errorf("failed to fetch metadata from metadata server: %s", err) |
| } |
| glog.V(4).Infof("Current value of instance custom metadata keys: %v", metadataKeys) |
| userConfig, err := getUserConfigFromMetadata(metadataKeys) |
| if err != nil { |
| return nil, err |
| } |
| return userConfig, nil |
| } |
| |
| // GetInstanceID returns the id of the instance. |
| func GetInstanceID() (uint64, error) { |
| var resp, err = metadata.InstanceID() |
| if err != nil { |
| return 0, err |
| } |
| |
| return strconv.ParseUint(string(resp), 10, 64) |
| } |
| |
| // Helper function to get metadata value of a key with 'cos-' prefix and the |
| // corresponding key with 'gci-' prefix. For example, 'cos-update-strategy' and |
| // 'gci-update-strategy'. The value of 'gci-' key is returned only if the function |
| // fail to get the value of 'cos-' key. |
| func getCOSGCIConfigSetting(cosKey string, gciKey string, metadataKeys []string) (string, bool) { |
| var val string |
| var err error |
| |
| for _, metadataKey := range metadataKeys { |
| if metadataKey == cosKey { |
| if val, err = metadata.InstanceAttributeValue(cosKey); err == nil { |
| return val, true |
| } |
| } else if metadataKey == gciKey { |
| if val, err = metadata.InstanceAttributeValue(gciKey); err == nil { |
| return val, true |
| } |
| } |
| } |
| return "", false |
| } |
| |
| // Gets user config based on the metadata keys. An empty InstanceConfig is returned |
| // if there is no InstanceConfig-related metadata in the pairs. |
| func getUserConfigFromMetadata(metadataKeys []string) (*protos.InstanceConfig, error) { |
| var configStr string |
| var err error |
| var ok, boolean bool |
| userConfig := new(protos.InstanceConfig) |
| userConfig.HealthMonitorConfig = new(protos.HealthMonitorConfig) |
| |
| // Get legacy instance config from metadata. This is deprecated, but if this is still |
| // being specified, use it and ignore others. |
| if configStr, ok = getCOSGCIConfigSetting("", gciLegacyConfigKey, metadataKeys); ok { |
| if configStr != "" { |
| if err := jsonpb.UnmarshalString(configStr, userConfig); err != nil { |
| return nil, fmt.Errorf("failed to unmarshal InstanceConfig %s: %s", configStr, err) |
| } |
| } |
| return userConfig, nil |
| } |
| |
| // Get individual config settings. Keys with 'cos-' prefix have the priority. |
| if configStr, ok = getCOSGCIConfigSetting(cosKeyUpdateStrategy, gciKeyUpdateStrategy, metadataKeys); ok { |
| if configStr == "update_disabled" { |
| userConfig.UpdateStrategy = proto.String(configStr) |
| } else { |
| userConfig.UpdateStrategy = proto.String("") |
| } |
| } |
| |
| if configStr, ok = getCOSGCIConfigSetting(cosKeyMetricsEnabled, gciKeyMetricsEnabled, metadataKeys); ok { |
| if boolean, err = strconv.ParseBool(configStr); err == nil { |
| userConfig.MetricsEnabled = proto.Bool(boolean) |
| } |
| } |
| |
| if configStr, ok := getCOSGCIConfigSetting(keyGoogleLoggingEnabled, "", metadataKeys); ok { |
| if boolean, err = strconv.ParseBool(configStr); err == nil { |
| userConfig.HealthMonitorConfig.LoggingEnabled = proto.Bool(boolean) |
| } |
| } |
| |
| if configStr, ok := getCOSGCIConfigSetting(keyGoogleMonitoringEnabled, "", metadataKeys); ok { |
| if boolean, err = strconv.ParseBool(configStr); err == nil { |
| userConfig.HealthMonitorConfig.MonitoringEnabled = proto.Bool(boolean) |
| } |
| } |
| |
| // Set Enforced to true if user is enabling logging or monitoring. |
| if userConfig.HealthMonitorConfig.GetLoggingEnabled() || userConfig.HealthMonitorConfig.GetMonitoringEnabled() { |
| userConfig.HealthMonitorConfig.Enforced = proto.Bool(true) |
| } |
| |
| return userConfig, nil |
| } |