Multi-level logging (#185)

Closes #93

Summary of changes:

- Adds the [uber-go-/zap](https://github.com/uber-go/zap) library for leveled logging (69040996e0bcbd2f211d0d8d576147027e928e3b)
- A new CLI arg `--log-level` which can take the values `debug` `info` `warn` `error` `fatal` `panic`. The default is `info`, and the existing `--quiet` flag is treated as `--log-level warn`
- All `helm.exec` calls are preceded by a one-line summary. The current information on the full helm command line arguments `exec: helm exec ... ` is only output at `debug` level. This means sensitive command line arguments which may include passwords and `--set` arguments should be hidden by default.
This commit is contained in:
Simon Li
2018-07-19 11:33:57 +09:00
committed by KUOKA Yusuke
parent 35732f3a93
commit 06a1d245ae
75 changed files with 7929 additions and 63 deletions
+38 -10
View File
@@ -1,9 +1,11 @@
package helmexec
import (
"fmt"
"io"
"strings"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
@@ -13,16 +15,33 @@ const (
type execer struct {
helmBinary string
runner Runner
writer io.Writer
logger *zap.SugaredLogger
kubeContext string
extra []string
}
func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
var cfg zapcore.EncoderConfig
cfg.MessageKey = "message"
out := zapcore.AddSync(writer)
var level zapcore.Level
err := level.Set(logLevel)
if err != nil {
panic(err)
}
core := zapcore.NewCore(
zapcore.NewConsoleEncoder(cfg),
out,
level,
)
return zap.New(core).Sugar()
}
// New for running helm commands
func New(writer io.Writer, kubeContext string) *execer {
func New(logger *zap.SugaredLogger, kubeContext string) *execer {
return &execer{
helmBinary: command,
writer: writer,
logger: logger,
kubeContext: kubeContext,
runner: &ShellRunner{},
}
@@ -45,68 +64,77 @@ func (helm *execer) AddRepo(name, repository, certfile, keyfile, username, passw
if username != "" && password != "" {
args = append(args, "--username", username, "--password", password)
}
helm.logger.Infof("Adding repo %v %v", name, repository)
out, err := helm.exec(args...)
helm.write(out)
return err
}
func (helm *execer) UpdateRepo() error {
helm.logger.Info("Updating repo")
out, err := helm.exec("repo", "update")
helm.write(out)
return err
}
func (helm *execer) UpdateDeps(chart string) error {
helm.logger.Infof("Updating dependency %v", chart)
out, err := helm.exec("dependency", "update", chart)
helm.write(out)
return err
}
func (helm *execer) SyncRelease(name, chart string, flags ...string) error {
helm.logger.Infof("Upgrading %v", chart)
out, err := helm.exec(append([]string{"upgrade", "--install", "--reset-values", name, chart}, flags...)...)
helm.write(out)
return err
}
func (helm *execer) ReleaseStatus(name string) error {
helm.logger.Infof("Getting status %v", name)
out, err := helm.exec(append([]string{"status", name})...)
if helm.writer != nil {
helm.writer.Write(out)
}
helm.write(out)
return err
}
func (helm *execer) DecryptSecret(name string) (string, error) {
helm.logger.Infof("Decrypting secret %v", name)
out, err := helm.exec(append([]string{"secrets", "dec", name})...)
helm.write(out)
return name + ".dec", err
}
func (helm *execer) DiffRelease(name, chart string, flags ...string) error {
helm.logger.Infof("Comparing %v %v", name, chart)
out, err := helm.exec(append([]string{"diff", "upgrade", "--allow-unreleased", name, chart}, flags...)...)
helm.write(out)
return err
}
func (helm *execer) Lint(chart string, flags ...string) error {
helm.logger.Infof("Linting %v", chart)
out, err := helm.exec(append([]string{"lint", chart}, flags...)...)
helm.write(out)
return err
}
func (helm *execer) Fetch(chart string, flags ...string) error {
helm.logger.Infof("Fetching %v", chart)
out, err := helm.exec(append([]string{"fetch", chart}, flags...)...)
helm.write(out)
return err
}
func (helm *execer) DeleteRelease(name string, flags ...string) error {
helm.logger.Infof("Deleting %v", name)
out, err := helm.exec(append([]string{"delete", name}, flags...)...)
helm.write(out)
return err
}
func (helm *execer) TestRelease(name string, flags ...string) error {
helm.logger.Infof("Testing %v", name)
out, err := helm.exec(append([]string{"test", name}, flags...)...)
helm.write(out)
return err
@@ -120,12 +148,12 @@ func (helm *execer) exec(args ...string) ([]byte, error) {
if helm.kubeContext != "" {
cmdargs = append(cmdargs, "--kube-context", helm.kubeContext)
}
helm.write([]byte(fmt.Sprintf("exec: %s %s\n", helm.helmBinary, strings.Join(cmdargs, " "))))
helm.logger.Debugf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
return helm.runner.Execute(helm.helmBinary, cmdargs)
}
func (helm *execer) write(out []byte) {
if helm.writer != nil {
helm.writer.Write(out)
if len(out) > 0 {
helm.logger.Infof("%s", out)
}
}
+117 -42
View File
@@ -2,9 +2,11 @@ package helmexec
import (
"bytes"
"io"
"os"
"reflect"
"testing"
"go.uber.org/zap"
)
// Mocking the command-line runner
@@ -18,8 +20,8 @@ func (mock *mockRunner) Execute(cmd string, args []string) ([]byte, error) {
return []byte{}, nil
}
func MockExecer(writer io.Writer, kubeContext string) *execer {
execer := New(writer, kubeContext)
func MockExecer(logger *zap.SugaredLogger, kubeContext string) *execer {
execer := New(logger, kubeContext)
execer.runner = &mockRunner{}
return execer
}
@@ -28,7 +30,8 @@ func MockExecer(writer io.Writer, kubeContext string) *execer {
func TestNewHelmExec(t *testing.T) {
buffer := bytes.NewBufferString("something")
helm := New(buffer, "dev")
logger := NewLogger(buffer, "debug")
helm := New(logger, "dev")
if helm.kubeContext != "dev" {
t.Error("helmexec.New() - kubeContext")
}
@@ -41,7 +44,7 @@ func TestNewHelmExec(t *testing.T) {
}
func Test_SetExtraArgs(t *testing.T) {
helm := New(new(bytes.Buffer), "dev")
helm := New(NewLogger(os.Stdout, "info"), "dev")
helm.SetExtraArgs()
if len(helm.extra) != 0 {
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
@@ -57,7 +60,7 @@ func Test_SetExtraArgs(t *testing.T) {
}
func Test_SetHelmBinary(t *testing.T) {
helm := New(new(bytes.Buffer), "dev")
helm := New(NewLogger(os.Stdout, "info"), "dev")
if helm.helmBinary != "helm" {
t.Error("helmexec.command - default command is not helm")
}
@@ -69,23 +72,30 @@ func Test_SetHelmBinary(t *testing.T) {
func Test_AddRepo(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.AddRepo("myRepo", "https://repo.example.com/", "cert.pem", "key.pem", "", "")
expected := "exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev\n"
expected := `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "")
expected = "exec: helm repo add myRepo https://repo.example.com/ --kube-context dev\n"
expected = `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "example_user", "example_password")
expected = "exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev\n"
expected = `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -93,9 +103,12 @@ func Test_AddRepo(t *testing.T) {
func Test_UpdateRepo(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.UpdateRepo()
expected := "exec: helm repo update --kube-context dev\n"
expected := `Updating repo
exec: helm repo update --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.UpdateRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -103,16 +116,21 @@ func Test_UpdateRepo(t *testing.T) {
func Test_SyncRelease(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.SyncRelease("release", "chart", "--timeout 10", "--wait")
expected := "exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev\n"
expected := `Upgrading chart
exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.SyncRelease("release", "chart")
expected = "exec: helm upgrade --install --reset-values release chart --kube-context dev\n"
expected = `Upgrading chart
exec: helm upgrade --install --reset-values release chart --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -120,17 +138,22 @@ func Test_SyncRelease(t *testing.T) {
func Test_UpdateDeps(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.UpdateDeps("./chart/foo")
expected := "exec: helm dependency update ./chart/foo --kube-context dev\n"
expected := `Updating dependency ./chart/foo
exec: helm dependency update ./chart/foo --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
t.Errorf("helmexec.UpdateDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.SetExtraArgs("--verify")
helm.UpdateDeps("./chart/foo")
expected = "exec: helm dependency update ./chart/foo --verify --kube-context dev\n"
expected = `Updating dependency ./chart/foo
exec: helm dependency update ./chart/foo --verify --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -138,9 +161,12 @@ func Test_UpdateDeps(t *testing.T) {
func Test_DecryptSecret(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.DecryptSecret("secretName")
expected := "exec: helm secrets dec secretName --kube-context dev\n"
expected := `Decrypting secret secretName
exec: helm secrets dec secretName --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -148,16 +174,21 @@ func Test_DecryptSecret(t *testing.T) {
func Test_DiffRelease(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.DiffRelease("release", "chart", "--timeout 10", "--wait")
expected := "exec: helm diff upgrade --allow-unreleased release chart --timeout 10 --wait --kube-context dev\n"
expected := `Comparing release chart
exec: helm diff upgrade --allow-unreleased release chart --timeout 10 --wait --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.DiffRelease("release", "chart")
expected = "exec: helm diff upgrade --allow-unreleased release chart --kube-context dev\n"
expected = `Comparing release chart
exec: helm diff upgrade --allow-unreleased release chart --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -165,18 +196,24 @@ func Test_DiffRelease(t *testing.T) {
func Test_DeleteRelease(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.DeleteRelease("release")
expected := "exec: helm delete release --kube-context dev\n"
expected := `Deleting release
exec: helm delete release --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
func Test_DeleteRelease_Flags(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.DeleteRelease("release", "--purge")
expected := "exec: helm delete release --purge --kube-context dev\n"
expected := `Deleting release
exec: helm delete release --purge --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -184,18 +221,24 @@ func Test_DeleteRelease_Flags(t *testing.T) {
func Test_TestRelease(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.TestRelease("release")
expected := "exec: helm test release --kube-context dev\n"
expected := `Testing release
exec: helm test release --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
func Test_TestRelease_Flags(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.TestRelease("release", "--cleanup", "--timeout", "60")
expected := "exec: helm test release --cleanup --timeout 60 --kube-context dev\n"
expected := `Testing release
exec: helm test release --cleanup --timeout 60 --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -203,9 +246,12 @@ func Test_TestRelease_Flags(t *testing.T) {
func Test_ReleaseStatus(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.ReleaseStatus("myRelease")
expected := "exec: helm status myRelease --kube-context dev\n"
expected := `Getting status myRelease
exec: helm status myRelease --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.ReleaseStatus()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -213,21 +259,22 @@ func Test_ReleaseStatus(t *testing.T) {
func Test_exec(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "")
helm.exec("version")
expected := "exec: helm version\n"
if buffer.String() != expected {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
helm = MockExecer(nil, "dev")
helm = MockExecer(logger, "dev")
ret, _ := helm.exec("diff")
if len(ret) != 0 {
t.Error("helmexec.exec() - expected empty return value")
}
buffer.Reset()
helm = MockExecer(&buffer, "dev")
helm = MockExecer(logger, "dev")
helm.exec("diff", "release", "chart", "--timeout 10", "--wait")
expected = "exec: helm diff release chart --timeout 10 --wait --kube-context dev\n"
if buffer.String() != expected {
@@ -250,7 +297,7 @@ func Test_exec(t *testing.T) {
}
buffer.Reset()
helm = MockExecer(&buffer, "")
helm = MockExecer(logger, "")
helm.SetHelmBinary("overwritten")
helm.exec("version")
expected = "exec: overwritten version\n"
@@ -261,9 +308,12 @@ func Test_exec(t *testing.T) {
func Test_Lint(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.Lint("path/to/chart", "--values", "file.yml")
expected := "exec: helm lint path/to/chart --values file.yml --kube-context dev\n"
expected := `Linting path/to/chart
exec: helm lint path/to/chart --values file.yml --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -271,10 +321,35 @@ func Test_Lint(t *testing.T) {
func Test_Fetch(t *testing.T) {
var buffer bytes.Buffer
helm := MockExecer(&buffer, "dev")
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
expected := "exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev\n"
expected := `Fetching chart
exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev
`
if buffer.String() != expected {
t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
var logLevelTests = map[string]string{
"debug": `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password
`,
"info": `Adding repo myRepo https://repo.example.com/
`,
"warn": ``,
}
func Test_LogLevels(t *testing.T) {
var buffer bytes.Buffer
for logLevel, expected := range logLevelTests {
buffer.Reset()
logger := NewLogger(&buffer, logLevel)
helm := MockExecer(logger, "")
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "example_user", "example_password")
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
}