Skip to content

Commit 20bae15

Browse files
committed
Enhance chart apply events to record source/version/confighash
Also switches to using structured logging, which requires wrapping logrus as a klog/logr log sink. Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
1 parent b8ab5b8 commit 20bae15

File tree

5 files changed

+171
-52
lines changed

5 files changed

+171
-52
lines changed

pkg/cmd/cmd.go

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package cmd
22

33
import (
4-
"flag"
54
"fmt"
65
"log"
76
"net/http"
87
"time"
98

9+
"github.com/go-logr/logr"
1010
"github.com/k3s-io/helm-controller/pkg/controllers"
1111
"github.com/k3s-io/helm-controller/pkg/controllers/common"
1212
"github.com/k3s-io/helm-controller/pkg/crds"
@@ -39,25 +39,12 @@ type HelmController struct {
3939
PprofPort int
4040
}
4141

42-
func (hc *HelmController) SetupDebug() error {
43-
logging := flag.NewFlagSet("", flag.PanicOnError)
44-
klog.InitFlags(logging)
42+
func (hc *HelmController) SetupLogging() (logr.Logger, error) {
43+
klog.EnableContextualLogging(true)
4544
if hc.Debug {
4645
logrus.SetLevel(logrus.DebugLevel)
47-
if err := logging.Parse([]string{
48-
fmt.Sprintf("-v=%d", hc.DebugLevel),
49-
}); err != nil {
50-
return err
51-
}
52-
} else {
53-
if err := logging.Parse([]string{
54-
"-v=0",
55-
}); err != nil {
56-
return err
57-
}
5846
}
59-
60-
return nil
47+
return common.NewLogrusSink(nil).AsLogr(), nil
6148
}
6249

6350
func (hc *HelmController) Run(app *cli.Context) error {
@@ -69,9 +56,9 @@ func (hc *HelmController) Run(app *cli.Context) error {
6956
log.Println(http.ListenAndServe(fmt.Sprintf("localhost:%d", hc.PprofPort), nil))
7057
}()
7158
}
72-
err := hc.SetupDebug()
59+
logger, err := hc.SetupLogging()
7360
if err != nil {
74-
panic("failed to setup debug logging: " + err.Error())
61+
return err
7562
}
7663

7764
cfg := hc.GetNonInteractiveClientConfig()
@@ -84,7 +71,7 @@ func (hc *HelmController) Run(app *cli.Context) error {
8471
return err
8572
}
8673

87-
ctx := app.Context
74+
ctx := klog.NewContext(app.Context, logger)
8875

8976
crds, err := crds.List()
9077
if err != nil {

pkg/controllers/chart/chart.go

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,21 @@ import (
3838
)
3939

4040
const (
41-
Label = "helmcharts.helm.cattle.io/chart"
42-
Annotation = "helmcharts.helm.cattle.io/configHash"
43-
Unmanaged = "helmcharts.helm.cattle.io/unmanaged"
4441
SecretType = "helmcharts.helm.cattle.io/values"
45-
ManagedBy = "helmcharts.cattle.io/managed-by"
4642
CRDName = "helmcharts.helm.cattle.io"
4743
ConfigCRDName = "helmchartconfigs.helm.cattle.io"
4844

4945
TaintExternalCloudProvider = "node.cloudprovider.kubernetes.io/uninitialized"
50-
LabelNodeRolePrefix = "node-role.kubernetes.io/"
51-
LabelControlPlaneSuffix = "control-plane"
52-
LabelEtcdSuffix = "etcd"
46+
47+
AnnotationChartURL = "helm.cattle.io/chart-url"
48+
AnnotationConfigHash = "helmcharts.helm.cattle.io/configHash"
49+
AnnotationManagedBy = "helmcharts.cattle.io/managed-by"
50+
AnnotationUnmanaged = "helmcharts.helm.cattle.io/unmanaged"
51+
52+
LabelChartName = "helmcharts.helm.cattle.io/chart"
53+
LabelNodeRolePrefix = "node-role.kubernetes.io/"
54+
LabelControlPlaneSuffix = "control-plane"
55+
LabelEtcdSuffix = "etcd"
5356

5457
FailurePolicyReinstall = "reinstall"
5558
FailurePolicyAbort = "abort"
@@ -320,7 +323,8 @@ func (c *Controller) OnChange(chart *v1.HelmChart, chartStatus v1.HelmChartStatu
320323
}
321324

322325
// emit an event to indicate that this Helm chart is being applied
323-
c.recorder.Eventf(chart, corev1.EventTypeNormal, "ApplyJob", "Applying HelmChart using Job %s/%s", job.Namespace, job.Name)
326+
annotations := map[string]string{AnnotationConfigHash: job.Spec.Template.ObjectMeta.Annotations[AnnotationConfigHash]}
327+
c.recorder.AnnotatedEventf(chart, annotations, corev1.EventTypeNormal, "ApplyJob", "Applying HelmChart from %s using Job %s/%s ", chartSource(chart), job.Namespace, job.Name)
324328

325329
return append(objs, job), chartStatus, nil
326330
}
@@ -430,10 +434,10 @@ func (c *Controller) shouldManage(chart *v1.HelmChart) (bool, error) {
430434
return false, nil
431435
}
432436
if chart.Annotations != nil {
433-
if _, ok := chart.Annotations[Unmanaged]; ok {
437+
if _, ok := chart.Annotations[AnnotationUnmanaged]; ok {
434438
return false, nil
435439
}
436-
managedBy, ok := chart.Annotations[ManagedBy]
440+
managedBy, ok := chart.Annotations[AnnotationManagedBy]
437441
if ok {
438442
// if the label exists, only handle this if the managedBy label matches that of this controller
439443
return managedBy == c.managedBy, nil
@@ -444,10 +448,10 @@ func (c *Controller) shouldManage(chart *v1.HelmChart) (bool, error) {
444448
chartCopy := chart.DeepCopy()
445449
if chartCopy.Annotations == nil {
446450
chartCopy.SetAnnotations(map[string]string{
447-
ManagedBy: c.managedBy,
451+
AnnotationManagedBy: c.managedBy,
448452
})
449453
} else {
450-
chartCopy.Annotations[ManagedBy] = c.managedBy
454+
chartCopy.Annotations[AnnotationManagedBy] = c.managedBy
451455
}
452456
_, err := c.helms.Update(chartCopy)
453457
return false, err
@@ -573,15 +577,15 @@ func job(chart *v1.HelmChart, apiServerPort string) (*batch.Job, *corev1.Secret,
573577
Name: fmt.Sprintf("helm-%s-%s", action, chart.Name),
574578
Namespace: chart.Namespace,
575579
Labels: map[string]string{
576-
Label: chart.Name,
580+
LabelChartName: chart.Name,
577581
},
578582
},
579583
Spec: batch.JobSpec{
580584
Template: corev1.PodTemplateSpec{
581585
ObjectMeta: metav1.ObjectMeta{
582586
Annotations: map[string]string{},
583587
Labels: map[string]string{
584-
Label: chart.Name,
588+
LabelChartName: chart.Name,
585589
},
586590
},
587591
Spec: corev1.PodSpec{
@@ -1174,7 +1178,7 @@ func hashObjects(job *batch.Job, objs ...metav1.Object) {
11741178
}
11751179
}
11761180

1177-
job.Spec.Template.ObjectMeta.Annotations[Annotation] = fmt.Sprintf("SHA256=%X", hash.Sum(nil))
1181+
job.Spec.Template.ObjectMeta.Annotations[AnnotationConfigHash] = fmt.Sprintf("SHA256=%X", hash.Sum(nil))
11781182
}
11791183

11801184
func setBackOffLimit(job *batch.Job, backOffLimit *int32) {
@@ -1190,3 +1194,35 @@ func setSecurityContext(job *batch.Job, chart *v1.HelmChart) {
11901194
job.Spec.Template.Spec.Containers[0].SecurityContext = chart.Spec.SecurityContext
11911195
}
11921196
}
1197+
1198+
// chartSource returns a string describing the source of the chart:
1199+
// chartContent, chart URL, or repo+version
1200+
func chartSource(chart *v1.HelmChart) string {
1201+
if chart == nil {
1202+
return "<unknown>"
1203+
}
1204+
1205+
if chart.Spec.ChartContent != "" {
1206+
if url := chart.Annotations[AnnotationChartURL]; url != "" {
1207+
return fmt.Sprintf("inline spec.chartContent from %s", url)
1208+
}
1209+
return "inline spec.chartContent"
1210+
}
1211+
1212+
if strings.HasPrefix(chart.Spec.Chart, "oci://") {
1213+
if chart.Spec.Version != "" {
1214+
return fmt.Sprintf("version %s from OCI registry %s", chart.Spec.Version, chart.Spec.Chart)
1215+
}
1216+
return fmt.Sprintf("latest stable version from OCI registry %s", chart.Spec.Chart)
1217+
}
1218+
1219+
if strings.Contains(chart.Spec.Chart, "://") {
1220+
return chart.Spec.Chart
1221+
}
1222+
1223+
if chart.Spec.Version != "" {
1224+
return fmt.Sprintf("%s version %s from chart repository %s", chart.Spec.Chart, chart.Spec.Version, chart.Spec.Repo)
1225+
}
1226+
1227+
return fmt.Sprintf("latest stable version of %s from chart repository %s", chart.Spec.Chart, chart.Spec.Repo)
1228+
}

pkg/controllers/chart/chart_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func TestHashObjects(t *testing.T) {
9292
b, _ := yaml.ToBytes([]runtime.Object{job})
9393
t.Logf("Generated Job:\n%s", b)
9494

95-
assert.Equalf(test.hash, job.Spec.Template.ObjectMeta.Annotations[Annotation], "%s annotation value does not match", Annotation)
95+
assert.Equalf(test.hash, job.Spec.Template.ObjectMeta.Annotations[AnnotationConfigHash], "%s annotation value does not match", AnnotationConfigHash)
9696
})
9797
}
9898
}

pkg/controllers/common/logger.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/go-logr/logr"
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
// implicit interface check
11+
var _ logr.LogSink = &LogrusSink{}
12+
13+
// mapLevel maps logr log verbosities to logrus log levels
14+
// logr does not have "log levels", but Info prints at verbosity 0
15+
// while logrus's LevelInfo is unit32(4). This means:
16+
// * panic/fatal/warn are unused,
17+
// * 0 is info
18+
// * 1 is debug
19+
// * >=2 are trace
20+
func mapLevel(level int) logrus.Level {
21+
if level >= 2 {
22+
return logrus.TraceLevel
23+
}
24+
return logrus.Level(level + 4)
25+
}
26+
27+
// mapKV maps a list of keys and values to logrus Fields
28+
func mapKV(kvs []any) logrus.Fields {
29+
fields := logrus.Fields{}
30+
for i := 0; i < len(kvs); i += 2 {
31+
k, ok := kvs[i].(string)
32+
if !ok {
33+
k = fmt.Sprint(kvs[i])
34+
}
35+
if len(kvs) > i+1 {
36+
fields[k] = kvs[i+1]
37+
} else {
38+
fields[k] = ""
39+
}
40+
}
41+
return fields
42+
}
43+
44+
// LogrusSink wraps logrus the Logger/Entry types for use as a logr LogSink.
45+
type LogrusSink struct {
46+
e *logrus.Entry
47+
ri logr.RuntimeInfo
48+
}
49+
50+
func NewLogrusSink(l *logrus.Logger) *LogrusSink {
51+
if l == nil {
52+
l = logrus.StandardLogger()
53+
}
54+
return &LogrusSink{e: logrus.NewEntry(l)}
55+
}
56+
57+
func (ls *LogrusSink) AsLogr() logr.Logger {
58+
return logr.New(ls)
59+
}
60+
61+
func (ls *LogrusSink) Init(ri logr.RuntimeInfo) {
62+
ls.ri = ri
63+
}
64+
65+
func (ls *LogrusSink) Enabled(level int) bool {
66+
return ls.e.Logger.IsLevelEnabled(mapLevel(level))
67+
}
68+
69+
func (ls *LogrusSink) Info(level int, msg string, kvs ...any) {
70+
ls.e.WithFields(mapKV(kvs)).Log(mapLevel(level), msg)
71+
}
72+
73+
func (ls *LogrusSink) Error(err error, msg string, kvs ...any) {
74+
ls.e.WithError(err).WithFields(mapKV(kvs)).Error(msg)
75+
}
76+
77+
func (ls *LogrusSink) WithValues(kvs ...any) logr.LogSink {
78+
return &LogrusSink{
79+
e: ls.e.WithFields(mapKV(kvs)),
80+
ri: ls.ri,
81+
}
82+
}
83+
84+
func (ls *LogrusSink) WithName(name string) logr.LogSink {
85+
if base, ok := ls.e.Data["logger"]; ok {
86+
name = fmt.Sprintf("%s/%s", base, name)
87+
}
88+
return ls.WithValues("logger", name)
89+
}

pkg/controllers/controllers.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package controllers
22

33
import (
44
"context"
5+
"os"
56
"time"
67

78
"github.com/k3s-io/helm-controller/pkg/controllers/chart"
@@ -23,7 +24,6 @@ import (
2324
"github.com/rancher/wrangler/v3/pkg/ratelimit"
2425
"github.com/rancher/wrangler/v3/pkg/schemes"
2526
"github.com/rancher/wrangler/v3/pkg/start"
26-
"github.com/sirupsen/logrus"
2727
corev1 "k8s.io/api/core/v1"
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929
"k8s.io/client-go/kubernetes"
@@ -35,6 +35,10 @@ import (
3535
"k8s.io/klog/v2"
3636
)
3737

38+
const (
39+
eventLogLevel klog.Level = 0
40+
)
41+
3842
type appContext struct {
3943
helmcontroller.Interface
4044

@@ -55,16 +59,17 @@ func (a *appContext) start(ctx context.Context) error {
5559
}
5660

5761
func Register(ctx context.Context, systemNamespace, controllerName string, cfg clientcmd.ClientConfig, opts common.Options) error {
58-
appCtx, err := newContext(cfg, systemNamespace, opts)
59-
if err != nil {
60-
return err
61-
}
62-
6362
if len(controllerName) == 0 {
6463
controllerName = "helm-controller"
6564
}
6665

67-
appCtx.EventBroadcaster.StartLogging(logrus.Infof)
66+
ctx = klog.NewContext(ctx, klog.FromContext(ctx).WithName(controllerName))
67+
appCtx, err := newContext(ctx, cfg, systemNamespace, opts)
68+
if err != nil {
69+
return err
70+
}
71+
72+
appCtx.EventBroadcaster.StartStructuredLogging(eventLogLevel)
6873
appCtx.EventBroadcaster.StartRecordingToSink(&typedv1.EventSinkImpl{
6974
Interface: appCtx.K8s.CoreV1().Events(systemNamespace),
7075
})
@@ -99,21 +104,23 @@ func Register(ctx context.Context, systemNamespace, controllerName string, cfg c
99104
appCtx.Core.Secret().Cache(),
100105
)
101106

102-
klog.Infof("Starting helm controller with %d threads", opts.Threadiness)
103-
klog.Infof("Using cluster role '%s' for jobs managing helm charts", opts.JobClusterRole)
104-
klog.Infof("Using default image '%s' for jobs managing helm charts", chart.DefaultJobImage)
107+
logger := klog.FromContext(ctx)
108+
logger.Info("Starting helm controller", "threads", opts.Threadiness)
109+
logger.Info("Using cluster role for jobs managing helm charts", "jobClusterRole", opts.JobClusterRole)
110+
logger.Info("Using default image for jobs managing helm charts", "defaultJobImage", chart.DefaultJobImage)
105111

106112
if len(systemNamespace) == 0 {
107113
systemNamespace = metav1.NamespaceSystem
108-
klog.Infof("Starting %s for all namespaces with lock in %s", controllerName, systemNamespace)
114+
logger.Info("Starting global controller", "leaseNamespace", systemNamespace)
109115
} else {
110-
klog.Infof("Starting %s for namespace %s", controllerName, systemNamespace)
116+
logger.Info("Starting namespaced controller", "namespace", systemNamespace)
111117
}
112118

113119
controllerLockName := controllerName + "-lock"
114120
leader.RunOrDie(ctx, systemNamespace, controllerLockName, appCtx.K8s, func(ctx context.Context) {
115121
if err := appCtx.start(ctx); err != nil {
116-
klog.Fatal(err)
122+
klog.Error(err, "failed to start controllers")
123+
os.Exit(1)
117124
}
118125
klog.Info("All controllers have been started")
119126
})
@@ -135,7 +142,7 @@ func controllerFactory(rest *rest.Config) (controller.SharedControllerFactory, e
135142
}), nil
136143
}
137144

138-
func newContext(cfg clientcmd.ClientConfig, systemNamespace string, opts common.Options) (*appContext, error) {
145+
func newContext(ctx context.Context, cfg clientcmd.ClientConfig, systemNamespace string, opts common.Options) (*appContext, error) {
139146
client, err := cfg.ClientConfig()
140147
if err != nil {
141148
return nil, err
@@ -146,7 +153,7 @@ func newContext(cfg clientcmd.ClientConfig, systemNamespace string, opts common.
146153
if err != nil {
147154
return nil, err
148155
}
149-
apply = apply.WithSetOwnerReference(false, false)
156+
apply = apply.WithSetOwnerReference(false, false).WithContext(ctx)
150157

151158
k8s, err := kubernetes.NewForConfig(client)
152159
if err != nil {
@@ -203,7 +210,7 @@ func newContext(cfg clientcmd.ClientConfig, systemNamespace string, opts common.
203210
RBAC: rbacv,
204211

205212
Apply: apply,
206-
EventBroadcaster: record.NewBroadcaster(),
213+
EventBroadcaster: record.NewBroadcaster(record.WithContext(ctx)),
207214

208215
ClientConfig: cfg,
209216
starters: []start.Starter{

0 commit comments

Comments
 (0)