pkg/controller/helpers.go (132 lines of code) (raw):

// ------------------------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // -------------------------------------------------------------------------------------------- package controller import ( "bytes" "encoding/json" "fmt" "reflect" "strings" "time" n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network" "k8s.io/klog/v2" "github.com/Azure/application-gateway-kubernetes-ingress/pkg/utils" ) // Keys that act as cache-busters should be removed from the JSON stored in cache. var keysToDeleteForCache = []string{ "etag", "tags", // In the Tags of App Gwy we store the timestamp of the most recent update. "provisioningState", "resourceGuid", "location", } func (c *AppGwIngressController) updateCache(appGw *n.ApplicationGateway) { jsonConfig, err := appGw.MarshalJSON() if err != nil { klog.Error("Could not marshal App Gwy to update cache; Wiping cache.", err) c.configCache = nil return } var sanitized []byte if sanitized, err = deleteKeyFromJSON(jsonConfig, keysToDeleteForCache...); err != nil { // Ran into an error; Wipe the existing cache klog.Error("Failed stripping ETag key from App Gwy config. Wiping cache.", err) c.configCache = nil return } *c.configCache = sanitized } // configIsSame compares the newly created App Gwy configuration with a cache to determine whether anything has changed. func (c *AppGwIngressController) configIsSame(appGw *n.ApplicationGateway) bool { if c.configCache == nil { return false } sanitizedInput := resetBackReference(appGw) jsonConfig, err := sanitizedInput.MarshalJSON() if err != nil { klog.Error("Could not marshal App Gwy to compare w/ cache; Will not use cache.", err) return false } // The JSON stored in the cache and the newly marshaled JSON will have different ETags even if configs are the same. // We need to strip ETags from all nested structures in order to have a fair comparison. var sanitized []byte if sanitized, err = deleteKeyFromJSON(jsonConfig, keysToDeleteForCache...); err != nil { // Ran into an error; Don't use cache; Refresh cache w/ new JSON klog.Error("Failed stripping ETag key from App Gwy config. Will not use cache.", err) return false } klog.V(9).Info("input state = ", string(sanitized)) klog.V(9).Info("cached state = ", string(*c.configCache)) // The result will be 0 if a==b, -1 if a < b, and +1 if a > b. return c.configCache != nil && bytes.Compare(*c.configCache, sanitized) == 0 } // resetBackReference removes the back references in app gateway. // Currently, This is an issue in redirectConfigurations func resetBackReference(appGw *n.ApplicationGateway) *n.ApplicationGateway { if appGw != nil && appGw.ApplicationGatewayPropertiesFormat != nil { if appGw.RedirectConfigurations != nil { for idx := range *appGw.RedirectConfigurations { (*appGw.RedirectConfigurations)[idx].RequestRoutingRules = nil (*appGw.RedirectConfigurations)[idx].URLPathMaps = nil (*appGw.RedirectConfigurations)[idx].PathRules = nil } } } return appGw } func dumpSanitizedJSON(appGw *n.ApplicationGateway, logToFile bool, overwritePrefix *string) ([]byte, error) { jsonConfig, err := appGw.MarshalJSON() if err != nil { return nil, err } prefix := "-- App Gwy config --" if overwritePrefix != nil { prefix = *overwritePrefix } // Remove sensitive data from the JSON config to be logged keysToDelete := []string{ "sslCertificates", } var sanitized []byte if sanitized, err = deleteKeyFromJSON(jsonConfig, keysToDelete...); err != nil { return nil, err } prettyJSON, err := utils.PrettyJSON(sanitized, prefix) if logToFile { fileName := fmt.Sprintf("app-gateway-config-%d.json", time.Now().UnixNano()) if filePath, err := utils.SaveToFile(fileName, prettyJSON); err != nil { klog.Error("Could not log to file: ", filePath, err) } } return prettyJSON, err } func (c *AppGwIngressController) isApplicationGatewayMutable(appGw *n.ApplicationGateway) bool { return appGw.OperationalState == "Running" || appGw.OperationalState == "Starting" } func isMap(v interface{}) bool { return v != nil && reflect.ValueOf(v).Type().Kind() == reflect.Map } func isSlice(v interface{}) bool { return v != nil && reflect.ValueOf(v).Type().Kind() == reflect.Slice } // deleteKey recursively deletes the given key from the map. This is NOT cap sensitive. func deleteKey(m *map[string]interface{}, keyToDelete string) { // Recursively search for the given keyToDelete for k, v := range *m { if strings.EqualFold(k, keyToDelete) { delete(*m, k) continue } // Recurse into other maps we find if isMap(v) { subMap := v.(map[string]interface{}) deleteKey(&subMap, keyToDelete) continue } // JSON blob could have a list for a value; Iterate and find maps; Delete matching keys if isSlice(v) { slice := v.([]interface{}) for idx := range slice { // We are not interested in any other element type but maps if isMap(slice[idx]) { subMap := slice[idx].(map[string]interface{}) deleteKey(&subMap, keyToDelete) } } } } } // deleteKeyFromJSON assumes the []byte passed is JSON. It unmarshalls, deletes the given key, and marshalls again. func deleteKeyFromJSON(jsonWithEtag []byte, keysToDelete ...string) ([]byte, error) { var m map[string]interface{} if err := json.Unmarshal([]byte(jsonWithEtag), &m); err != nil { klog.Error("Could not unmarshal config App Gwy JSON to delete Etag.", err) return nil, err } for _, keyToDelete := range keysToDelete { deleteKey(&m, keyToDelete) } return json.Marshal(m) }