pkg/controller/mutate_app_gateway.go (136 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 (
"encoding/json"
"fmt"
"strings"
"time"
n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network"
"github.com/Azure/go-autorest/autorest/to"
v1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/appgw"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/brownfield"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/controllererrors"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/environment"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/events"
)
type realClock struct{}
func (realClock) Now() time.Time { return time.Now() }
// GetAppGw gets App Gateway config.
func (c AppGwIngressController) GetAppGw() (*n.ApplicationGateway, *appgw.ConfigBuilderContext, error) {
// Get current application gateway config
appGw, err := c.azClient.GetGateway()
c.MetricStore.IncArmAPICallCounter()
if err != nil {
e := controllererrors.NewErrorWithInnerErrorf(
controllererrors.ErrorFetchingAppGatewayConfig,
err,
"unable to get specified AppGateway [%v], check AppGateway identifier", c.appGwIdentifier.AppGwName,
)
klog.Errorf(e.Error())
if c.agicPod != nil {
c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonUnableToFetchAppGw, e.Error())
}
return nil, nil, e
}
cbCtx := &appgw.ConfigBuilderContext{
ServiceList: c.k8sContext.ListServices(),
IngressList: c.k8sContext.ListHTTPIngresses(),
EnvVariables: environment.GetEnv(),
DefaultAddressPoolID: to.StringPtr(c.appGwIdentifier.AddressPoolID(appgw.DefaultBackendAddressPoolName)),
DefaultHTTPSettingsID: to.StringPtr(c.appGwIdentifier.HTTPSettingsID(appgw.DefaultBackendHTTPSettingsName)),
ExistingPortsByNumber: make(map[appgw.Port]n.ApplicationGatewayFrontendPort),
}
for _, port := range *appGw.FrontendPorts {
cbCtx.ExistingPortsByNumber[appgw.Port(*port.Port)] = port
}
return &appGw, cbCtx, nil
}
// MutateAppGateway applies App Gateway config.
func (c AppGwIngressController) MutateAppGateway(event events.Event, appGw *n.ApplicationGateway, cbCtx *appgw.ConfigBuilderContext) error {
var err error
existingConfigJSON, _ := dumpSanitizedJSON(appGw, false, to.StringPtr("-- Existing App Gwy Config --"))
klog.V(3).Info("Existing App Gateway config: ", string(existingConfigJSON))
// Prepare k8s resources Phase //
// --------------------------- //
if cbCtx.EnvVariables.EnableBrownfieldDeployment {
prohibitedTargets := c.k8sContext.ListAzureProhibitedTargets()
if len(prohibitedTargets) > 0 {
cbCtx.ProhibitedTargets = prohibitedTargets
var prohibitedTargetsList []string
for _, target := range *brownfield.GetTargetBlacklist(prohibitedTargets) {
targetJSON, _ := json.Marshal(target)
prohibitedTargetsList = append(prohibitedTargetsList, string(targetJSON))
}
klog.V(3).Infof("[brownfield] Prohibited targets: %s", strings.Join(prohibitedTargetsList, ", "))
} else {
klog.Warning("Brownfield Deployment is enabled, but AGIC did not find any AzureProhibitedTarget CRDs; Disabling brownfield deployment feature.")
cbCtx.EnvVariables.EnableBrownfieldDeployment = false
}
}
if cbCtx.EnvVariables.EnableIstioIntegration {
istioServices := c.k8sContext.ListIstioVirtualServices()
istioGateways := c.k8sContext.ListIstioGateways()
if len(istioGateways) > 0 && len(istioServices) > 0 {
cbCtx.IstioGateways = istioGateways
cbCtx.IstioVirtualServices = istioServices
} else {
klog.Warning("Istio Integration is enabled, but AGIC needs Istio Gateways and Virtual Services; Disabling Istio integration.")
cbCtx.EnvVariables.EnableIstioIntegration = false
}
}
cbCtx.IngressList = c.PruneIngress(appGw, cbCtx)
if cbCtx.EnvVariables.EnableIstioIntegration {
var gatewaysInfo []string
for _, gateway := range cbCtx.IstioGateways {
gatewaysInfo = append(gatewaysInfo, fmt.Sprintf("%s/%s", gateway.Namespace, gateway.Name))
}
klog.V(3).Infof("Istio Gateways: %+v", strings.Join(gatewaysInfo, ","))
}
// Generate App Gateway Phase //
// -------------------------- //
// Create a configbuilder based on current appgw config
configBuilder := appgw.NewConfigBuilder(c.k8sContext, &c.appGwIdentifier, appGw, c.recorder, realClock{})
// Run validations on the Kubernetes resources which can suggest misconfiguration.
if err = configBuilder.PreBuildValidate(cbCtx); err != nil {
errorLine := fmt.Sprint("ConfigBuilder PostBuildValidate returned error:", err)
klog.Error(errorLine)
if c.agicPod != nil {
c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonValidatonError, errorLine)
}
}
var generatedAppGw *n.ApplicationGateway
// Replace the current appgw config with the generated one
if generatedAppGw, err = configBuilder.Build(cbCtx); err != nil {
errorLine := fmt.Sprint("ConfigBuilder Build returned error:", err)
klog.Error(errorLine)
if c.agicPod != nil {
c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonValidatonError, errorLine)
}
return err
}
// Run post validations to report errors in the config generation.
if err = configBuilder.PostBuildValidate(cbCtx); err != nil {
errorLine := fmt.Sprint("ConfigBuilder PostBuildValidate returned error:", err)
klog.Error(errorLine)
if c.agicPod != nil {
c.recorder.Event(c.agicPod, v1.EventTypeWarning, events.ReasonValidatonError, errorLine)
}
}
// -------------------------- //
// Post Compare Phase //
// ------------------ //
// if this is not a reconciliation task
// then compare the generated state with cached state
if event.Type != events.PeriodicReconcile {
if c.configIsSame(appGw) {
klog.V(3).Info("cache: Config has NOT changed! No need to connect to ARM.")
return nil
}
}
// ------------------ //
// Deployment Phase //
// ---------------- //
configJSON, _ := dumpSanitizedJSON(appGw, cbCtx.EnvVariables.EnableSaveConfigToFile, nil)
klog.V(3).Infof("Generated config:\n%s", string(configJSON))
// Initiate deployment
klog.V(3).Info("BEGIN AppGateway deployment")
defer klog.V(3).Info("END AppGateway deployment")
err = c.azClient.UpdateGateway(generatedAppGw)
if err != nil {
// Reset cache
c.configCache = nil
return err
}
klog.V(1).Infof("Applied generated Application Gateway configuration")
// ----------------- //
// Cache Phase //
// ----------- //
if err != nil {
// Reset cache
c.configCache = nil
return controllererrors.NewErrorWithInnerErrorf(
controllererrors.ErrorDeployingAppGatewayConfig,
err,
"unable to get specified AppGateway %s", c.appGwIdentifier.AppGwName,
)
}
klog.V(3).Info("cache: Updated with latest applied config.")
c.updateCache(appGw)
// ----------- //
return nil
}