diff --git a/pkg/log/dummy.go b/pkg/log/dummy.go index c8bfc94..1ce93d0 100644 --- a/pkg/log/dummy.go +++ b/pkg/log/dummy.go @@ -70,6 +70,9 @@ func (d *DummyLogger) InfoFWithoutLn(format string, a ...interface{}) { fmt.Printf(format, a...) } +// InfoLn +// Deprecated: +// Use InfoF(string) it add \n to end func (d *DummyLogger) InfoLn(a ...interface{}) { fmt.Println(a...) } @@ -78,6 +81,9 @@ func (d *DummyLogger) ErrorFWithoutLn(format string, a ...interface{}) { fmt.Printf(format, a...) } +// ErrorLn +// Deprecated: +// Use ErrorF(string) it add \n to end func (d *DummyLogger) ErrorLn(a ...interface{}) { fmt.Println(a...) } @@ -88,6 +94,9 @@ func (d *DummyLogger) DebugFWithoutLn(format string, a ...interface{}) { } } +// DebugLn +// Deprecated: +// Use DebugF(string) it add \n to end func (d *DummyLogger) DebugLn(a ...interface{}) { if d.isDebug { fmt.Println(a...) @@ -106,6 +115,9 @@ func (d *DummyLogger) FailRetry(l string) { d.Fail(l) } +// WarnLn +// Deprecated: +// Use WarnF(string) it add \n to end func (d *DummyLogger) WarnLn(a ...interface{}) { fmt.Println(a...) } diff --git a/pkg/log/in_memory.go b/pkg/log/in_memory.go index a83539f..48cbbd7 100644 --- a/pkg/log/in_memory.go +++ b/pkg/log/in_memory.go @@ -172,6 +172,9 @@ func (l *InMemoryLogger) InfoFWithoutLn(format string, a ...interface{}) { l.parent.InfoFWithoutLn(format, a...) } +// InfoLn +// Deprecated: +// Use InfoF(string) it add \n to end func (l *InMemoryLogger) InfoLn(a ...interface{}) { l.writeEntityFormatted(listToString(a)) l.parent.InfoLn(a...) @@ -182,6 +185,9 @@ func (l *InMemoryLogger) ErrorFWithoutLn(format string, a ...interface{}) { l.parent.ErrorFWithoutLn(format, a...) } +// ErrorLn +// Deprecated: +// Use ErrorF(string) it add \n to end func (l *InMemoryLogger) ErrorLn(a ...interface{}) { l.writeEntityWithPrefix(l.errorPrefix, listToString(a)) l.parent.ErrorLn(a...) @@ -196,6 +202,9 @@ func (l *InMemoryLogger) DebugFWithoutLn(format string, a ...interface{}) { l.parent.DebugFWithoutLn(format, a...) } +// DebugLn +// Deprecated: +// Use DebugF(string) it add \n to end func (l *InMemoryLogger) DebugLn(a ...interface{}) { if l.notDebug { return @@ -210,6 +219,9 @@ func (l *InMemoryLogger) WarnFWithoutLn(format string, a ...interface{}) { l.parent.WarnFWithoutLn(format, a...) } +// WarnLn +// Deprecated: +// Use WarnF(string) it add \n to end func (l *InMemoryLogger) WarnLn(a ...interface{}) { l.writeEntityFormatted(listToString(a)) l.parent.WarnLn(a...) diff --git a/pkg/log/ln_logger_wrapper.go b/pkg/log/ln_logger_wrapper.go index fc55d3a..67181fe 100644 --- a/pkg/log/ln_logger_wrapper.go +++ b/pkg/log/ln_logger_wrapper.go @@ -14,6 +14,8 @@ package log +import "strings" + // formatWithNewLineLogger // we often use *F function, but for pretty log we use "\n" in end of string // this interface and wrapper help us for get rit of this @@ -42,5 +44,6 @@ func (w *formatWithNewLineLoggerWrapper) WarnF(format string, a ...any) { } func addLnToFormat(format string) string { - return format + "\n" + // remove last new line to avoid add double new lines + return strings.TrimSuffix(format, "\n") + "\n" } diff --git a/pkg/log/ln_logger_wrapper_test.go b/pkg/log/ln_logger_wrapper_test.go index 0e469a7..2edd790 100644 --- a/pkg/log/ln_logger_wrapper_test.go +++ b/pkg/log/ln_logger_wrapper_test.go @@ -17,21 +17,80 @@ package log import ( "errors" "fmt" + "strings" "testing" "github.com/stretchr/testify/require" ) func TestLnLoggerWrapper(t *testing.T) { - logger := NewInMemoryLoggerWithParent(NewSimpleLogger(LoggerOptions{IsDebug: true})) + emptyStringTests := []struct { + name string + do func(w formatWithNewLineLogger) + }{ + { + name: "ErrorF", + do: func(w formatWithNewLineLogger) { + w.ErrorF("") + }, + }, - assertAddNewLine := func(t *testing.T, msg string) { + { + name: "WarnF", + do: func(w formatWithNewLineLogger) { + w.WarnF("") + }, + }, + + { + name: "InfoF", + do: func(w formatWithNewLineLogger) { + w.InfoF("") + }, + }, + + { + name: "DebugF", + do: func(w formatWithNewLineLogger) { + w.DebugF("") + }, + }, + } + + for _, test := range emptyStringTests { + t.Run(fmt.Sprintf("Log empty line for %s", test.name), func(t *testing.T) { + logger := NewInMemoryLoggerWithParent(NewPrettyLogger(LoggerOptions{IsDebug: true})) + wrapper := newFormatWithNewLineLoggerWrapper(logger) + + test.do(wrapper) + + matches, err := logger.AllMatches(&Match{ + Prefix: []string{"\n"}, + }) + + require.NoError(t, err) + require.Len(t, matches, 1, "should one match") + require.Equal(t, "\n", matches[0], "should produce new line") + }) + } + + logger := NewInMemoryLoggerWithParent(NewPrettyLogger(LoggerOptions{IsDebug: true})) + + assertAddNewLine := func(t *testing.T, msg string) string { matches, err := logger.AllMatches(&Match{ Prefix: []string{fmt.Sprintf("%s\n", msg)}, }) require.NoError(t, err) require.Len(t, matches, 1, msg) + + return matches[0] + } + + assertCountNewLines := func(t *testing.T, msg string, expected int) { + match := assertAddNewLine(t, msg) + count := strings.Count(match, "\n") + require.Equal(t, expected, count, "should contain %d trailing new lines", expected) } wrapper := newFormatWithNewLineLoggerWrapper(logger) @@ -42,18 +101,49 @@ func TestLnLoggerWrapper(t *testing.T) { wrapper.ErrorF("VariablesError %s %v", "msg", true) assertAddNewLine(t, "VariablesError msg true") + // trim one new line + wrapper.ErrorF("ErrorOneLn\n") + assertCountNewLines(t, "ErrorOneLn", 1) + // save multiple new lines expected one + wrapper.ErrorF("ErrorMultiLn\n\n\n") + assertCountNewLines(t, "ErrorMultiLn\n\n", 3) + wrapper.WarnF("Warn") assertAddNewLine(t, "Warn") wrapper.WarnF("VariablesWarn %s %v", "msg", true) assertAddNewLine(t, "VariablesWarn msg true") + // trim one new line + wrapper.WarnF("WarnOneLn\n") + assertCountNewLines(t, "WarnOneLn", 1) + // save multiple new lines expected one + wrapper.WarnF("WarnMultiLn\n\n") + assertCountNewLines(t, "WarnMultiLn\n", 2) + wrapper.InfoF("Info") assertAddNewLine(t, "Info") wrapper.InfoF("VariablesInfo %s %v", "msg", errors.New("error")) assertAddNewLine(t, "VariablesInfo msg error") + // trim one new line + wrapper.InfoF("InfoOneLn\n") + assertCountNewLines(t, "InfoOneLn", 1) + // save multiple new lines expected one + wrapper.InfoF("InfoMultiLn\n\n\n\n") + assertCountNewLines(t, "InfoMultiLn\n\n\n", 4) + + wrapper.DebugF("Debug") + assertAddNewLine(t, "Debug") + wrapper.DebugF("VariablesDebug %v %s", 42, "msg") assertAddNewLine(t, "VariablesDebug 42 msg") + + // trim one new line + wrapper.DebugF("DebugOneLn\n") + assertCountNewLines(t, "DebugOneLn", 1) + // save multiple new lines expected one + wrapper.DebugF("DebugMultiLn\n\n\n\n\n") + assertCountNewLines(t, "DebugMultiLn\n\n\n\n", 5) } diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 08d09e3..6d3fdfd 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -108,24 +108,28 @@ type baseLogger interface { Process(Process, string, func() error) error InfoFWithoutLn(format string, a ...interface{}) + // InfoLn // Deprecated: // Use InfoF(string) it add \n to end InfoLn(a ...interface{}) ErrorFWithoutLn(format string, a ...interface{}) + // ErrorLn // Deprecated: // Use ErrorF(string) it add \n to end ErrorLn(a ...interface{}) DebugFWithoutLn(format string, a ...interface{}) + // DebugLn // Deprecated: // Use DebugF(string) it add \n to end DebugLn(a ...interface{}) WarnFWithoutLn(format string, a ...interface{}) + // WarnLn // Deprecated: // Use WarnF(string) it add \n to end @@ -149,18 +153,22 @@ type formatWithNewLineLogger interface { // InfoF // Warning! InfoF add \n to end of message. // If you do not have \n to end of message please use InfoFWithoutLn + // Also trim last new line from format InfoF(format string, a ...any) // ErrorF // Warning! ErrorF add \n to end of message. // If you do not have \n to end of message please use ErrorFWithoutLn + // Also trim last new line from format ErrorF(format string, a ...any) // DebugF // Warning! DebugF add \n to end of message. // If you do not have \n to end of message please use DebugFWithoutLn + // Also trim last new line from format DebugF(format string, a ...any) // WarnF // Warning! WarnF add \n to end of message. // If you do not have \n to end of message please use WarnFWithoutLn + // Also trim last new line from format WarnF(format string, a ...any) } diff --git a/pkg/log/pretty.go b/pkg/log/pretty.go index 4ae8708..50d537d 100644 --- a/pkg/log/pretty.go +++ b/pkg/log/pretty.go @@ -121,6 +121,9 @@ func (d *PrettyLogger) InfoFWithoutLn(format string, a ...interface{}) { d.logboekLogger.Info().LogF(format, a...) } +// InfoLn +// Deprecated: +// Use InfoF(string) it add \n to end func (d *PrettyLogger) InfoLn(a ...interface{}) { d.logboekLogger.Info().LogLn(a...) } @@ -129,6 +132,9 @@ func (d *PrettyLogger) ErrorFWithoutLn(format string, a ...interface{}) { d.logboekLogger.Error().LogF(format, a...) } +// ErrorLn +// Deprecated: +// Use ErrorF(string) it add \n to end func (d *PrettyLogger) ErrorLn(a ...interface{}) { d.logboekLogger.Error().LogLn(a...) } @@ -147,6 +153,9 @@ func (d *PrettyLogger) DebugFWithoutLn(format string, a ...interface{}) { } } +// DebugLn +// Deprecated: +// Use DebugF(string) it add \n to end func (d *PrettyLogger) DebugLn(a ...interface{}) { if d.debugLogWriter != nil { o := fmt.Sprintln(a...) @@ -173,6 +182,9 @@ func (d *PrettyLogger) FailRetry(l string) { d.Fail(l) } +// WarnLn +// Deprecated: +// Use WarnF(string) it add \n to end func (d *PrettyLogger) WarnLn(a ...interface{}) { a = append([]interface{}{"❗ ~ "}, a...) d.InfoLn(color.New(color.Bold).Sprint(a...)) @@ -184,7 +196,7 @@ func (d *PrettyLogger) WarnFWithoutLn(format string, a ...interface{}) { } func (d *PrettyLogger) JSON(content []byte) { - d.InfoLn(prettyJSON(content)) + d.InfoF(prettyJSON(content)) } func (d *PrettyLogger) Write(content []byte) (int, error) { diff --git a/pkg/log/process.go b/pkg/log/process.go index b968557..176bed4 100644 --- a/pkg/log/process.go +++ b/pkg/log/process.go @@ -105,7 +105,7 @@ func (l *wrappedProcessLogger) ProcessStart(msg string) { l.processes.push(p) - l.logger.InfoLn(msg) + l.logger.InfoF(msg) } func (l *wrappedProcessLogger) ProcessEnd() { @@ -116,7 +116,7 @@ func (l *wrappedProcessLogger) ProcessEnd() { msg = fmt.Sprintf("%s (%s)", p.Msg, p.formatTime()) } - l.logger.InfoLn(msg) + l.logger.InfoF(msg) } func (l *wrappedProcessLogger) ProcessFail() { @@ -127,5 +127,5 @@ func (l *wrappedProcessLogger) ProcessFail() { msg = fmt.Sprintf("%s FAILED (%s)", p.Msg, p.formatTime()) } - l.logger.ErrorLn(msg) + l.logger.ErrorF(msg) } diff --git a/pkg/log/process_test.go b/pkg/log/process_test.go index 457d8c9..fd8b7a4 100644 --- a/pkg/log/process_test.go +++ b/pkg/log/process_test.go @@ -99,4 +99,37 @@ func TestProcessLoggers(t *testing.T) { }) }) } + + t.Run("Add new line to process messages", func(t *testing.T) { + assertNewLine := func(t *testing.T, logger *InMemoryLogger, count int) { + startMatches, err := logger.AllMatches(&Match{ + Suffix: []string{"\n"}, + }) + + require.NoError(t, err) + require.Len(t, startMatches, count) + + for i := 0; i < count; i++ { + require.NotEmpty(t, startMatches[i], "should contains new line") + } + } + + expectedLoggerSuccess := NewInMemoryLoggerWithParent(NewPrettyLogger(LoggerOptions{})) + successLogger := newWrappedProcessLogger(expectedLoggerSuccess) + + successLogger.ProcessStart("Process start") + assertNewLine(t, expectedLoggerSuccess, 1) + + successLogger.ProcessEnd() + assertNewLine(t, expectedLoggerSuccess, 2) + + expectedLoggerFail := NewInMemoryLoggerWithParent(NewPrettyLogger(LoggerOptions{})) + failLogger := newWrappedProcessLogger(expectedLoggerFail) + + failLogger.ProcessStart("Process fail") + assertNewLine(t, expectedLoggerFail, 1) + + failLogger.ProcessFail() + assertNewLine(t, expectedLoggerFail, 2) + }) } diff --git a/pkg/log/silent.go b/pkg/log/silent.go index fa31259..42b8a5a 100644 --- a/pkg/log/silent.go +++ b/pkg/log/silent.go @@ -74,6 +74,9 @@ func (d *SilentLogger) InfoFWithoutLn(format string, a ...interface{}) { } } +// InfoLn +// Deprecated: +// Use InfoF(string) it add \n to end func (d *SilentLogger) InfoLn(a ...interface{}) { if d.t != nil { d.t.writeToFile(fmt.Sprintln(a...)) @@ -86,6 +89,9 @@ func (d *SilentLogger) ErrorFWithoutLn(format string, a ...interface{}) { } } +// ErrorLn +// Deprecated: +// Use ErrorF(string) it add \n to end func (d *SilentLogger) ErrorLn(a ...interface{}) { if d.t != nil { d.t.writeToFile(fmt.Sprintln(a...)) @@ -98,6 +104,9 @@ func (d *SilentLogger) DebugFWithoutLn(format string, a ...interface{}) { } } +// DebugLn +// Deprecated: +// Use DebugF(string) it add \n to end func (d *SilentLogger) DebugLn(a ...interface{}) { if d.t != nil { d.t.writeToFile(fmt.Sprintln(a...)) @@ -122,6 +131,9 @@ func (d *SilentLogger) FailRetry(l string) { } } +// WarnLn +// Deprecated: +// Use WarnF(string) it add \n to end func (d *SilentLogger) WarnLn(a ...interface{}) { if d.t != nil { d.t.writeToFile(fmt.Sprintln(a...)) diff --git a/pkg/log/simple.go b/pkg/log/simple.go index c68daa2..66ad5b5 100644 --- a/pkg/log/simple.go +++ b/pkg/log/simple.go @@ -85,6 +85,9 @@ func (d *SimpleLogger) InfoFWithoutLn(format string, a ...interface{}) { d.logger.Info(fmt.Sprintf(format, a...)) } +// InfoLn +// Deprecated: +// Use InfoF(string) it add \n to end func (d *SimpleLogger) InfoLn(a ...interface{}) { d.logger.Info(listToString(a)) } @@ -93,6 +96,9 @@ func (d *SimpleLogger) ErrorFWithoutLn(format string, a ...interface{}) { d.logger.Error(fmt.Sprintf(format, a...)) } +// ErrorLn +// Deprecated: +// Use ErrorF(string) it add \n to end func (d *SimpleLogger) ErrorLn(a ...interface{}) { d.logger.Error(listToString(a)) } @@ -103,6 +109,9 @@ func (d *SimpleLogger) DebugFWithoutLn(format string, a ...interface{}) { } } +// DebugLn +// Deprecated: +// Use DebugF(string) it add \n to end func (d *SimpleLogger) DebugLn(a ...interface{}) { if d.isDebug { d.logger.Debug(listToString(a)) @@ -126,6 +135,9 @@ func (d *SimpleLogger) WarnFWithoutLn(format string, a ...interface{}) { d.logger.Warn(fmt.Sprintf(format, a...)) } +// WarnLn +// Deprecated: +// Use WarnF(string) it add \n to end func (d *SimpleLogger) WarnLn(a ...interface{}) { d.logger.Warn(listToString(a)) } diff --git a/pkg/log/tee.go b/pkg/log/tee.go index e0766aa..d0d2d3f 100644 --- a/pkg/log/tee.go +++ b/pkg/log/tee.go @@ -125,6 +125,9 @@ func (d *TeeLogger) InfoFWithoutLn(format string, a ...interface{}) { d.writeToFile(fmt.Sprintf(format, a...)) } +// InfoLn +// Deprecated: +// Use InfoF(string) it add \n to end func (d *TeeLogger) InfoLn(a ...interface{}) { d.l.InfoLn(a...) @@ -137,6 +140,9 @@ func (d *TeeLogger) ErrorFWithoutLn(format string, a ...interface{}) { d.writeToFile(fmt.Sprintf(format, a...)) } +// ErrorLn +// Deprecated: +// Use ErrorF(string) it add \n to end func (d *TeeLogger) ErrorLn(a ...interface{}) { d.l.ErrorLn(a...) @@ -149,6 +155,9 @@ func (d *TeeLogger) DebugFWithoutLn(format string, a ...interface{}) { d.writeToFile(fmt.Sprintf(format, a...)) } +// DebugLn +// Deprecated: +// Use DebugF(string) it add \n to end func (d *TeeLogger) DebugLn(a ...interface{}) { d.l.DebugLn(a...) @@ -173,6 +182,9 @@ func (d *TeeLogger) FailRetry(l string) { d.writeToFile(l) } +// WarnLn +// Deprecated: +// Use WarnF(string) it add \n to end func (d *TeeLogger) WarnLn(a ...interface{}) { d.l.WarnLn(a...) diff --git a/pkg/yaml/validation/index.go b/pkg/yaml/validation/index.go index 9c02885..ebd4f6f 100644 --- a/pkg/yaml/validation/index.go +++ b/pkg/yaml/validation/index.go @@ -15,8 +15,10 @@ package validation import ( + "bytes" "fmt" "io" + "regexp" "strings" "sigs.k8s.io/yaml" @@ -62,7 +64,14 @@ func ParseIndex(reader io.Reader, opts ...ParseIndexOption) (*SchemaIndex, error return nil, fmt.Errorf("%w: %w", ErrRead, err) } + // we cannot use yaml.UnmarshalStrict here + // because strict unmarshal also verify that another keys not present + if err := contentHasMultipleSchemaKeys(content); err != nil { + return nil, err + } + index := SchemaIndex{} + err = yaml.Unmarshal(content, &index) if err != nil { return nil, fmt.Errorf("%w %w: schema index unmarshal failed: %w", ErrKindValidationFailed, ErrKindInvalidYAML, err) @@ -137,3 +146,26 @@ func (i *SchemaIndex) invalidIndexErr(doc []byte) error { ErrKindValidationFailed, i.Version, i.Kind, string(doc), ) } + +var ( + apiVersionRegex = regexp.MustCompile(`(?m)^apiVersion:.*$`) + kindRegex = regexp.MustCompile(`(?m)^kind:.*$`) + errSeparator = []byte(" ") +) + +func multipleKeysErr(keyName string, keys [][]byte) error { + joinedKeys := bytes.Join(keys, errSeparator) + return fmt.Errorf("%w: multiple %s keys found: %s", ErrKindValidationFailed, keyName, string(joinedKeys)) +} + +func contentHasMultipleSchemaKeys(content []byte) error { + if res := apiVersionRegex.FindAll(content, 2); len(res) > 1 { + return multipleKeysErr("apiVersion", res) + } + + if res := kindRegex.FindAll(content, 2); len(res) > 1 { + return multipleKeysErr("kind", res) + } + + return nil +} diff --git a/pkg/yaml/validation/index_test.go b/pkg/yaml/validation/index_test.go index d2f7d01..b9dfda7 100644 --- a/pkg/yaml/validation/index_test.go +++ b/pkg/yaml/validation/index_test.go @@ -142,6 +142,66 @@ value: 1 opts: []ParseIndexOption{ParseIndexWithoutCheckValid()}, }, + { + name: "multiple api versions", + reader: strings.NewReader(` +apiVersion: deckhouse.io/v1 +kind: TestKind +key: key +value: 1 +apiVersion: deckhouse.io/v1 +kkey: vval +`), + errs: []error{ErrKindValidationFailed}, + opts: []ParseIndexOption{ParseIndexWithoutCheckValid()}, + }, + + { + name: "multiple kinds", + reader: strings.NewReader(` +apiVersion: deckhouse.io/v1 +kind: TestKind +key: key +value: 1 +kind: AnotherKind +kkey: vval +`), + errs: []error{ErrKindValidationFailed}, + opts: []ParseIndexOption{ParseIndexWithoutCheckValid()}, + }, + + { + name: "multiple api versions and kinds", + reader: strings.NewReader(` +# apiVersion here +apiVersion: deckhouse.io/v1 +kind: TestKind +key: key +value: 1 +apiVersion: deckhouse.io/v1 +kind: AnotherKind +kkey: vval +`), + errs: []error{ErrKindValidationFailed}, + opts: []ParseIndexOption{ParseIndexWithoutCheckValid()}, + }, + + { + name: "multiple api versions and kinds", + reader: strings.NewReader(` +# apiVersion here +apiVersion: deckhouse.io/v1 +kind: TestKind +key: key +value: 1 +apiVersion: deckhouse.io/v1 +kind: AnotherKind +kkey: vval +`), + errs: []error{ErrKindValidationFailed}, + opts: []ParseIndexOption{ParseIndexWithoutCheckValid()}, + }, + { name: "happy case", reader: strings.NewReader(` @@ -149,6 +209,19 @@ apiVersion: deckhouse.io/v1 kind: TestKind sshUser: ubuntu sshPort: 2200 +`), + errs: nil, + }, + + { + name: "not fail if apiVersion and kind string presents but not keys", + reader: strings.NewReader(` +# apiVersion here +apiVersion: deckhouse.io/v1 # apiVersion: here +# kind here +kind: TestKind # kind: here +key: apiVersion +value: kind `), errs: nil, }, diff --git a/pkg/yaml/validation/validator_test.go b/pkg/yaml/validation/validator_test.go index c1384e7..a9d0474 100644 --- a/pkg/yaml/validation/validator_test.go +++ b/pkg/yaml/validation/validator_test.go @@ -339,6 +339,21 @@ sshAgentPrivateKeys: {"a": "b"} errSubstring: fmt.Sprintf(`"TestKind, deckhouse.io/v1": %s:`, ErrDocumentValidationFailed.Error()), opts: []ValidateOption{ValidateWithNoPrettyError(true)}, }, + { + name: "multiple api versions and kinds", + doc: ` +apiVersion: deckhouse.io/v1 +kind: TestKind +sshUser: ubuntu +sshPort: "port" +sshAgentPrivateKeys: {"a": "b"} +apiVersion: deckhouse.io/v1 +kind: AnotherKind +key: key +`, + errSubstring: "DocumentKindValidationFailed: multiple apiVersion keys found: apiVersion: deckhouse.io/v1 apiVersion: deckhouse.io/v1", + errKind: ErrKindValidationFailed, + }, } for _, test := range tests {