• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Azure / aks-app-routing-operator / 23320258224

19 Mar 2026 10:37PM UTC coverage: 79.386% (+0.1%) from 79.258%
23320258224

push

github

jaiveerk
Merge branch 'jkatariya/approuting-istio' of https://github.com/jaiveerk/aks-app-routing-operator into jkatariya/approuting-istio

23 of 29 new or added lines in 2 files covered. (79.31%)

68 existing lines in 3 files now uncovered.

4344 of 5472 relevant lines covered (79.39%)

54.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.39
/pkg/controller/controller.go
1
// Copyright (c) Microsoft Corporation.
2
// Licensed under the MIT License.
3

4
package controller
5

6
import (
7
        "context"
8
        "fmt"
9
        "net/http"
10

11
        approutingv1alpha1 "github.com/Azure/aks-app-routing-operator/api/v1alpha1"
12
        defaultdomain "github.com/Azure/aks-app-routing-operator/pkg/clients/default-domain"
13
        "github.com/Azure/aks-app-routing-operator/pkg/controller/defaultdomaincert"
14
        placeholderpod "github.com/Azure/aks-app-routing-operator/pkg/controller/keyvault/placeholderpod"
15
        "github.com/Azure/aks-app-routing-operator/pkg/controller/keyvault/spc"
16
        "github.com/Azure/aks-app-routing-operator/pkg/controller/nginxingress"
17
        "github.com/Azure/aks-app-routing-operator/pkg/controller/service"
18
        "github.com/Azure/aks-app-routing-operator/pkg/store"
19
        "github.com/Azure/aks-app-routing-operator/pkg/util"
20
        "github.com/go-logr/logr"
21
        cfgv1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
22
        policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
23
        ubzap "go.uber.org/zap"
24
        appsv1 "k8s.io/api/apps/v1"
25
        corev1 "k8s.io/api/core/v1"
26
        netv1 "k8s.io/api/networking/v1"
27
        apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28
        "k8s.io/apimachinery/pkg/api/errors"
29
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30
        "k8s.io/apimachinery/pkg/runtime"
31
        utilruntime "k8s.io/apimachinery/pkg/util/runtime"
32
        "k8s.io/client-go/kubernetes"
33
        clientgoscheme "k8s.io/client-go/kubernetes/scheme"
34
        "k8s.io/client-go/rest"
35
        "k8s.io/klog/v2"
36
        ctrl "sigs.k8s.io/controller-runtime"
37
        "sigs.k8s.io/controller-runtime/pkg/cache"
38
        "sigs.k8s.io/controller-runtime/pkg/client"
39
        "sigs.k8s.io/controller-runtime/pkg/log/zap"
40
        metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
41
        gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
42
        secv1 "sigs.k8s.io/secrets-store-csi-driver/apis/v1"
43

44
        "github.com/Azure/aks-app-routing-operator/pkg/config"
45
        "github.com/Azure/aks-app-routing-operator/pkg/controller/dns"
46
        "github.com/Azure/aks-app-routing-operator/pkg/controller/ingress"
47
        "github.com/Azure/aks-app-routing-operator/pkg/controller/osm"
48
)
49

50
var scheme = runtime.NewScheme()
51

52
const (
53
        nicIngressClassIndex     = "spec.ingressClassName"
54
        gatewayListenerIndexName = "spec.listeners.tls.options.kubernetes.azure.com/tls-cert-service-account"
55
)
56

57
func init() {
2✔
58
        registerSchemes(scheme)
2✔
59
        ctrl.SetLogger(getLogger())
2✔
60
        // need to set klog logger to same logger to get consistent logging format for all logs.
2✔
61
        // without this things like leader election that use klog will not have the same format.
2✔
62
        // https://github.com/kubernetes/client-go/blob/560efb3b8995da3adcec09865ca78c1ddc917cc9/tools/leaderelection/leaderelection.go#L250
2✔
63
        klog.SetLogger(getLogger())
2✔
64
}
2✔
65

66
func getLogger(opts ...zap.Opts) logr.Logger {
6✔
67
        // use raw opts to add caller info to logs
6✔
68
        rawOpts := zap.RawZapOpts(ubzap.AddCaller())
6✔
69

6✔
70
        // zap is the default recommended logger for controller-runtime when wanting json structured output
6✔
71
        return zap.New(append(opts, rawOpts)...)
6✔
72
}
6✔
73

74
func registerSchemes(s *runtime.Scheme) {
2✔
75
        utilruntime.Must(clientgoscheme.AddToScheme(s))
2✔
76
        utilruntime.Must(secv1.Install(s))
2✔
77
        utilruntime.Must(cfgv1alpha2.AddToScheme(s))
2✔
78
        utilruntime.Must(policyv1alpha1.AddToScheme(s))
2✔
79
        utilruntime.Must(approutingv1alpha1.AddToScheme(s))
2✔
80
        utilruntime.Must(apiextensionsv1.AddToScheme(s))
2✔
81
        utilruntime.Must(gatewayv1.Install(s))
2✔
82
}
2✔
83

84
func NewRestConfig(conf *config.Config) *rest.Config {
×
85
        rc := ctrl.GetConfigOrDie()
×
86
        if conf.ServiceAccountTokenPath != "" {
×
87
                rc.BearerTokenFile = conf.ServiceAccountTokenPath
×
88
        }
×
89

90
        return rc
×
91
}
92

93
func NewManagerForRestConfig(conf *config.Config, rc *rest.Config) (ctrl.Manager, error) {
×
94
        // client creates ListWatch cache for all resources that are
×
95
        // GET or LISTed. this uses too much memory on large clusters.
×
96
        var clientOpts *client.CacheOptions
×
97
        cacheOpts := cache.Options{}
×
98
        if conf.DisableExpensiveCache {
×
99
                clientOpts = &client.CacheOptions{
×
NEW
100
                        // we only interact with a small subset of pods and events,
×
NEW
101
                        // but there's no good filter to only cache the ones we care about,
×
NEW
102
                        // so we disable caching them entirely and fetch live from the API server.
×
NEW
103
                        DisableFor: []client.Object{
×
NEW
104
                                &corev1.Pod{},
×
NEW
105
                                &corev1.Event{},
×
106
                        },
×
107
                }
×
108
        }
×
109

110
        m, err := ctrl.NewManager(rc, ctrl.Options{
×
111
                Metrics:                metricsserver.Options{BindAddress: conf.MetricsAddr},
×
112
                HealthProbeBindAddress: conf.ProbeAddr,
×
113
                Scheme:                 scheme,
×
114

×
115
                // we use an active-passive HA model meaning only the leader performs actions
×
116
                LeaderElection:          true,
×
117
                LeaderElectionNamespace: "kube-system",
×
118
                LeaderElectionID:        "aks-app-routing-operator-leader",
×
119

×
120
                Client: client.Options{Cache: clientOpts},
×
121
                Cache:  cacheOpts,
×
122
        })
×
UNCOV
123
        if err != nil {
×
124
                return nil, fmt.Errorf("creating manager: %w", err)
×
125
        }
×
126

127
        storeLog := m.GetLogger().WithName("store")
×
128
        store, err := store.New(storeLog, context.Background())
×
UNCOV
129
        if err != nil {
×
130
                return nil, fmt.Errorf("creating store: %w", err)
×
131
        }
×
132

133
        setupLog := m.GetLogger().WithName("setup")
×
134

×
135
        // Health checkers will be populated during controller setup
×
136
        healthCheckers := newHealthCheckers()
×
137
        if err := setupProbes(conf, m, setupLog, healthCheckers); err != nil {
×
UNCOV
138
                setupLog.Error(err, "failed to set up probes")
×
UNCOV
139
                return nil, fmt.Errorf("setting up probes: %w", err)
×
140
        }
×
141

142
        // create non-caching clients, non-caching for use before manager has started
143
        cl, err := client.New(rc, client.Options{Scheme: scheme})
×
144
        if err != nil {
×
UNCOV
145
                setupLog.Error(err, "unable to create non-caching client")
×
146
                return nil, fmt.Errorf("creating non-caching client: %w", err)
×
147
        }
×
148

149
        if err := loadCRDs(cl, conf, setupLog); err != nil {
×
UNCOV
150
                setupLog.Error(err, "failed to load CRDs")
×
151
                return nil, fmt.Errorf("loading CRDs: %w", err)
×
152
        }
×
153

154
        if err := setupIndexers(m, setupLog, conf); err != nil {
×
UNCOV
155
                setupLog.Error(err, "unable to setup indexers")
×
156
                return nil, fmt.Errorf("setting up indexers: %w", err)
×
157
        }
×
158

159
        if err := setupControllers(m, conf, setupLog, cl, store, healthCheckers); err != nil {
×
UNCOV
160
                setupLog.Error(err, "unable to setup controllers")
×
161
                return nil, fmt.Errorf("setting up controllers: %w", err)
×
UNCOV
162
        }
×
163

UNCOV
164
        return m, nil
×
165
}
166

167
func setupIndexers(mgr ctrl.Manager, lgr logr.Logger, conf *config.Config) error {
2✔
168
        lgr.Info("setting up indexers")
2✔
169

2✔
170
        if !conf.DisableIngressNginx {
4✔
171
                lgr.Info("adding Nginx Ingress Controller IngressClass indexer")
2✔
172
                if err := nginxingress.AddIngressClassNameIndex(mgr.GetFieldIndexer(), nicIngressClassIndex); err != nil {
2✔
UNCOV
173
                        lgr.Error(err, "adding Nginx Ingress Controller IngressClass indexer")
×
UNCOV
174
                        return fmt.Errorf("adding Nginx Ingress Controller IngressClass indexer: %w", err)
×
UNCOV
175
                }
×
176
        }
177

178
        if conf.EnableGatewayTLS {
2✔
179
                if err := util.AddGatewayServiceAccountIndex(mgr.GetFieldIndexer(), gatewayListenerIndexName); err != nil {
×
UNCOV
180
                        lgr.Error(err, "adding Gateway Service Account indexer")
×
UNCOV
181
                        return fmt.Errorf("adding Gateway Service Account indexer: %w", err)
×
UNCOV
182
                }
×
183
        }
184

185
        lgr.Info("finished setting up indexers")
2✔
186
        return nil
2✔
187
}
188

189
func setupControllers(mgr ctrl.Manager, conf *config.Config, lgr logr.Logger, cl client.Client, store store.Store, healthCheckers *healthCheckers) error {
2✔
190
        lgr.Info("setting up controllers")
2✔
191

2✔
192
        lgr.Info("setting up ExternalDNS controller")
2✔
193
        if err := dns.NewExternalDns(mgr, conf); err != nil {
2✔
UNCOV
194
                return fmt.Errorf("setting up external dns controller: %w", err)
×
UNCOV
195
        }
×
196

197
        var ingressManager util.IngressManager
2✔
198
        if !conf.DisableIngressNginx {
4✔
199
                lgr.Info("determining default IngressClass controller class")
2✔
200
                defaultCc, err := nginxingress.GetDefaultIngressClassControllerClass(cl)
2✔
201
                if err != nil {
2✔
UNCOV
202
                        return fmt.Errorf("determining default IngressClass controller class: %w", err)
×
UNCOV
203
                }
×
204

205
                lgr.Info("setting up Nginx Ingress Controller reconciler")
2✔
206
                if err := nginxingress.NewReconciler(conf, mgr, defaultCc); err != nil {
2✔
UNCOV
207
                        return fmt.Errorf("setting up nginx ingress controller reconciler: %w", err)
×
UNCOV
208
                }
×
209

210
                lgr.Info("setting up ingress cert config reconciler")
2✔
211
                if err = osm.NewIngressCertConfigReconciler(mgr, conf); err != nil {
2✔
UNCOV
212
                        return fmt.Errorf("setting up ingress cert config reconciler: %w", err)
×
UNCOV
213
                }
×
214

215
                defaultNic := nginxingress.GetDefaultNginxIngressController()
2✔
216
                if err := service.NewNginxIngressReconciler(mgr, nginxingress.ToNginxIngressConfig(&defaultNic, defaultCc)); err != nil {
2✔
UNCOV
217
                        return fmt.Errorf("setting up nginx ingress reconciler: %w", err)
×
UNCOV
218
                }
×
219

220
                lgr.Info("setting up default Nginx Ingress Controller reconciler")
2✔
221
                if err := nginxingress.NewDefaultReconciler(mgr, conf); err != nil {
2✔
UNCOV
222
                        return fmt.Errorf("setting up nginx ingress default controller reconciler: %w", err)
×
UNCOV
223
                }
×
224

225
                lgr.Info("setting up ingress concurrency watchdog")
2✔
226
                if err := ingress.NewConcurrencyWatchdog(mgr, conf, ingress.GetListNginxWatchdogTargets(mgr.GetClient(), defaultCc)); err != nil {
2✔
UNCOV
227
                        return fmt.Errorf("setting up ingress concurrency watchdog: %w", err)
×
228
                }
×
229

230
                ingressManager = util.NewIngressManagerFromFn(func(ing *netv1.Ingress) (bool, error) {
2✔
UNCOV
231
                        return nginxingress.IsIngressManaged(context.Background(), mgr.GetClient(), ing, nicIngressClassIndex)
×
232
                })
×
233
                lgr.Info("setting up keyvault secret provider class reconciler")
2✔
234
                if err := spc.NewIngressSecretProviderClassReconciler(mgr, conf, ingressManager); err != nil {
2✔
UNCOV
235
                        return fmt.Errorf("setting up ingress secret provider class reconciler: %w", err)
×
236
                }
×
237
                lgr.Info("setting up nginx keyvault secret provider class reconciler")
2✔
238
                if err := spc.NewNginxSecretProviderClassReconciler(mgr, conf); err != nil {
2✔
UNCOV
239
                        return fmt.Errorf("setting up nginx secret provider class reconciler: %w", err)
×
240
                }
×
241

242
                ingressSourceSpecer := osm.NewIngressControllerSourceSpecerFromFn(func(ing *netv1.Ingress) (policyv1alpha1.IngressSourceSpec, bool, error) {
2✔
UNCOV
243
                        return nginxingress.IngressSource(context.Background(), mgr.GetClient(), conf, defaultCc, ing, nicIngressClassIndex)
×
244
                })
×
245
                lgr.Info("setting up ingress backend reconciler")
2✔
246
                if err := osm.NewIngressBackendReconciler(mgr, conf, ingressSourceSpecer); err != nil {
2✔
UNCOV
247
                        return fmt.Errorf("setting up ingress backend reconciler: %w", err)
×
UNCOV
248
                }
×
249
        }
250

251
        lgr.Info("setting up keyvault placeholder pod controller")
2✔
252
        if err := placeholderpod.NewPlaceholderPodController(mgr, conf, ingressManager); err != nil {
2✔
UNCOV
253
                return fmt.Errorf("setting up placeholder pod controller: %w", err)
×
254
        }
×
255
        lgr.Info("setting up keyvault event mirror")
2✔
256
        if err := placeholderpod.NewEventMirror(mgr, conf); err != nil {
2✔
UNCOV
257
                return fmt.Errorf("setting up event mirror: %w", err)
×
258
        }
×
259

260
        if conf.EnableGatewayTLS {
2✔
261
                lgr.Info("setting up gateway reconcilers")
×
UNCOV
262
                if err := spc.NewGatewaySecretClassProviderReconciler(mgr, conf, gatewayListenerIndexName); err != nil {
×
UNCOV
263
                        return fmt.Errorf("setting up Gateway SPC reconciler: %w", err)
×
UNCOV
264
                }
×
265
        }
266

267
        if conf.EnableDefaultDomain {
2✔
268
                lgr.Info("setting up default domain reconcilers")
×
269

×
270
                // Create default domain client for fetching TLS certificates
×
271
                defaultDomainClient := defaultdomain.NewCachedClient(
×
272
                        context.Background(),
×
273
                        defaultdomain.CachedClientOpts{
×
274
                                Opts: defaultdomain.Opts{
×
275
                                        ServerAddress: conf.DefaultDomainServerAddress,
×
276
                                },
×
277
                        },
×
278
                        mgr.GetLogger().WithName("default-domain-client"),
×
279
                )
×
280

×
281
                // We no longer want to have the default domain client directly impact the health of the operator.
×
282
                // Issues calling default-domain-svc are unlikely to be operator errors and are almost always errors
×
283
                // outside the operator's control (network issues, default-domain-svc down, etc). Instead we use Prometheus
×
284
                // metrics to monitor the health of default domain calls so we can alert and view trends on them.
×
285
                // healthCheckers.addCheck(defaultDomainClient)
×
286

×
UNCOV
287
                if err := defaultdomaincert.NewReconciler(conf, mgr, defaultDomainClient); err != nil {
×
UNCOV
288
                        return fmt.Errorf("setting up default domain reconciler: %w", err)
×
UNCOV
289
                }
×
290
        }
291

292
        lgr.Info("finished setting up controllers")
2✔
293
        return nil
2✔
294
}
295

296
func setupProbes(conf *config.Config, mgr ctrl.Manager, log logr.Logger, healthCheckers *healthCheckers) error {
2✔
297
        log.Info("adding probes to manager")
2✔
298

2✔
299
        check := func(req *http.Request) error { return nil }
2✔
300

301
        if err := mgr.AddReadyzCheck("readyz", check); err != nil {
2✔
UNCOV
302
                return fmt.Errorf("adding readyz check: %w", err)
×
UNCOV
303
        }
×
304

305
        // Create health check that includes all registered health checkers
306
        healthCheck := func(req *http.Request) error {
2✔
307
                if !healthCheckers.isHealthy() {
×
UNCOV
308
                        return fmt.Errorf("health checker is unhealthy")
×
UNCOV
309
                }
×
UNCOV
310
                return nil
×
311
        }
312

313
        if err := mgr.AddHealthzCheck("healthz", healthCheck); err != nil {
2✔
UNCOV
314
                return fmt.Errorf("adding healthz check: %w", err)
×
UNCOV
315
        }
×
316

317
        log.Info("added probes to manager")
2✔
318
        return nil
2✔
319
}
320

321
func getSelfDeploy(kcs kubernetes.Interface, conf *config.Config, log logr.Logger) (*appsv1.Deployment, error) {
4✔
322
        // this doesn't actually work today. operator ns is not the same as resource ns which means we can't set this operator
4✔
323
        // as the owner of any child resources. https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/#owner-references-in-object-specifications
4✔
324
        // dynamic provisioning through a crd will fix this and fix our garbage collection.
4✔
325

4✔
326
        deploy, err := kcs.AppsV1().Deployments(conf.NS).Get(context.Background(), conf.OperatorDeployment, metav1.GetOptions{})
4✔
327
        if errors.IsNotFound(err) {
6✔
328
                // It's okay if we don't find the deployment - just skip setting ownership references latter
2✔
329
                log.Info("self deploy not found")
2✔
330
                return nil, nil
2✔
331
        }
2✔
332
        if err != nil {
2✔
UNCOV
333
                return nil, err
×
UNCOV
334
        }
×
335
        return deploy, nil
2✔
336
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc