From bee5c4e897baacb34973b8f3943c2b0c16ac93b1 Mon Sep 17 00:00:00 2001 From: Marcus Crestani Date: Tue, 24 Mar 2020 11:50:09 +0100 Subject: [PATCH 001/143] Insert codec as a string, not as a single-quoted character literal. This enables codecs that need additional configuration settings, like `multiline` with an escaped string: ``` codec: "multiline { pattern => \"^%{YEAR}/%{MONTHNUM}/%{MONTHDAY}[T ]%{HOUR}:?%{MINUTE}:?%{SECOND}\" negate => true what => previous }" ``` --- logstash/parallel_process.go | 2 +- logstash/parallel_process_test.go | 8 ++++---- logstash/process.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/logstash/parallel_process.go b/logstash/parallel_process.go index e830c77..de5a951 100644 --- a/logstash/parallel_process.go +++ b/logstash/parallel_process.go @@ -168,7 +168,7 @@ func getSocketInOutPlugins(testStream []*TestStream) ([]string, []string, error) if err != nil { return nil, nil, err } - logstashInput[i] = fmt.Sprintf("unix { mode => \"client\" path => %q codec => %q add_field => %s }", + logstashInput[i] = fmt.Sprintf("unix { mode => \"client\" path => %q codec => %s add_field => %s }", sp.senderPath, sp.inputCodec, fieldHash) logstashOutput[i] = fmt.Sprintf("if [@metadata][__lfv_testcase] == \"%s\" { file { path => %q codec => \"json_lines\" } }", strconv.Itoa(i), sp.receiver.Name()) diff --git a/logstash/parallel_process_test.go b/logstash/parallel_process_test.go index f2b8ee7..7f1d1b7 100644 --- a/logstash/parallel_process_test.go +++ b/logstash/parallel_process_test.go @@ -97,7 +97,7 @@ func TestGetSocketInOutPlugins(t *testing.T) { }, }, []string{ - "unix { mode => \"client\" path => \"/tmp/foo\" codec => \"any_codec\" " + + "unix { mode => \"client\" path => \"/tmp/foo\" codec => any_codec " + "add_field => { \"[@metadata][__lfv_testcase]\" => \"0\" } }", }, []string{ @@ -122,9 +122,9 @@ func TestGetSocketInOutPlugins(t *testing.T) { }, }, []string{ - "unix { mode => \"client\" path => \"/tmp/foo\" codec => \"any_codec\" " + + "unix { mode => \"client\" path => \"/tmp/foo\" codec => any_codec " + "add_field => { \"[@metadata][__lfv_testcase]\" => \"0\" } }", - "unix { mode => \"client\" path => \"/tmp/bar\" codec => \"other_codec\" " + + "unix { mode => \"client\" path => \"/tmp/bar\" codec => other_codec " + "add_field => { \"[@metadata][__lfv_testcase]\" => \"1\" } }", }, []string{ @@ -148,7 +148,7 @@ func TestGetSocketInOutPlugins(t *testing.T) { }, }, []string{ - "unix { mode => \"client\" path => \"/tmp/foo\" codec => \"any_codec\" " + + "unix { mode => \"client\" path => \"/tmp/foo\" codec => any_codec " + "add_field => { \"[@metadata][__lfv_testcase]\" => \"0\" \"[@metadata][foo]\" => \"bar\" } }", }, []string{ diff --git a/logstash/process.go b/logstash/process.go index 87dfb3e..7107515 100644 --- a/logstash/process.go +++ b/logstash/process.go @@ -51,7 +51,7 @@ func NewProcess(inv *Invocation, inputCodec string, fields FieldSet, keptEnvVars _ = outputFile.Close() return nil, err } - inputs := fmt.Sprintf("input { stdin { codec => %q add_field => %s } }", inputCodec, fieldHash) + inputs := fmt.Sprintf("input { stdin { codec => %s add_field => %s } }", inputCodec, fieldHash) outputs := fmt.Sprintf("output { file { path => %q codec => \"json_lines\" } }", outputFile.Name()) env := getLimitedEnvironment(os.Environ(), keptEnvVars) From 898c1c45773b21e5d3f7584578d373be9b323a6b Mon Sep 17 00:00:00 2001 From: Filippo Giunchedi Date: Tue, 5 May 2020 14:47:56 +0200 Subject: [PATCH 002/143] README.md: quote @timestamp in yaml example Using the example file leads to an error otherwise: Error reading/unmarshalling testcases.yml: yaml: line 15: found character that cannot start any token --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d14990b..1c3138d 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ testcases: "time": "2015-10-06T20:55:29Z" } expected: - - @timestamp: "2015-10-06T20:55:29.000Z" + - "@timestamp": "2015-10-06T20:55:29.000Z" client: "localhost" clientip: "127.0.0.1" message: "This is a test message" From 2569e44a6d5e4459d35d30232eae487aa7f79387 Mon Sep 17 00:00:00 2001 From: disaster37 Date: Sun, 14 Jun 2020 21:27:14 +0200 Subject: [PATCH 003/143] Add test summary and observer pattern (#74) --- Makefile | 1 + go.mod | 3 +- go.sum | 4 ++ logstash-filter-verifier.go | 78 +++++++++++++-------- observer/data.go | 16 +++++ observer/summary.go | 91 ++++++++++++++++++++++++ testcase/testcase.go | 134 +++++++++++++++++------------------- testcase/testcase_test.go | 90 +++++++----------------- 8 files changed, 251 insertions(+), 166 deletions(-) create mode 100644 observer/data.go create mode 100644 observer/summary.go diff --git a/Makefile b/Makefile index 62fd282..09d488e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ # Copyright (c) 2015-2020 Magnus Bäck + export GOBIN := $(shell pwd)/bin export PATH := $(GOBIN):$(PATH) diff --git a/go.mod b/go.mod index 9991af6..6ce91cf 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( github.com/Masterminds/semver/v3 v3.0.1 - github.com/ahmetb/govvv v0.2.0 + github.com/ahmetb/govvv v0.3.0 github.com/alecthomas/kingpin v2.2.6+incompatible github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect @@ -12,6 +12,7 @@ require ( github.com/breml/logstash-config v0.1.0 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/hashicorp/packer v1.4.4 + github.com/imkira/go-observer v1.0.3 github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82 github.com/mattn/go-shellwords v1.0.6 github.com/mikefarah/yaml/v2 v2.4.0 diff --git a/go.sum b/go.sum index bb2c8b9..d56a13d 100644 --- a/go.sum +++ b/go.sum @@ -23,6 +23,8 @@ github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:O github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/ahmetb/govvv v0.2.0 h1:3rzpWlytDIDQfL64/Kbjs88tNJltY9E8sk1tNju6FAU= github.com/ahmetb/govvv v0.2.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= +github.com/ahmetb/govvv v0.3.0 h1:YGLGwEyiUwHFy5eh/RUhdupbuaCGBYn5T5GWXp+WJB0= +github.com/ahmetb/govvv v0.3.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= @@ -148,6 +150,8 @@ github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKe github.com/hetznercloud/hcloud-go v1.15.1/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2wrVdERJgCtxsF3Q= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs= +github.com/imkira/go-observer v1.0.3 h1:l45TYAEeAB4L2xF6PR2gRLn2NE5tYhudh33MLmC7B80= +github.com/imkira/go-observer v1.0.3/go.mod h1:zLzElv2cGTHufQG17IEILJMPDg32TD85fFgKyFv00wU= github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go index 36b0139..158f9dc 100644 --- a/logstash-filter-verifier.go +++ b/logstash-filter-verifier.go @@ -3,7 +3,6 @@ package main import ( - "errors" "fmt" "os" "path/filepath" @@ -12,8 +11,10 @@ import ( semver "github.com/Masterminds/semver/v3" "github.com/alecthomas/kingpin" + "github.com/imkira/go-observer" "github.com/magnusbaeck/logstash-filter-verifier/logging" "github.com/magnusbaeck/logstash-filter-verifier/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/observer" "github.com/magnusbaeck/logstash-filter-verifier/testcase" "github.com/mattn/go-shellwords" oplogging "github.com/op/go-logging" @@ -76,6 +77,10 @@ var ( Flag("sockets-timeout", "Timeout (duration) for the communication with Logstash via Unix domain sockets. Has no effect unless --sockets is used."). Default("60s"). Duration() + quiet = kingpin. + Flag("quiet", "Permit to disable extra messages"). + Default("false"). + Bool() // Arguments testcasePath = kingpin. @@ -115,53 +120,55 @@ func findExecutable(paths []string) (string, error) { // slice of test cases and compares the actual events against the // expected set. Returns an error if at least one test case fails or // if there's a problem running the tests. -func runTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string) error { +func runTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveObserver observer.Property) (bool, error) { ok := true for _, t := range tests { fmt.Printf("Running tests in %s...\n", filepath.Base(t.File)) p, err := logstash.NewProcess(inv, t.Codec, t.InputFields, keptEnvVars) if err != nil { - return err + return false, err } defer p.Release() if err = p.Start(); err != nil { - return err + return false, err } for _, line := range t.InputLines { _, err = p.Input.Write([]byte(line + "\n")) if err != nil { - return err + return false, err } } if err = p.Input.Close(); err != nil { - return err + return false, err } result, err := p.Wait() if err != nil || *logstashOutput { message := getLogstashOutputMessage(result.Output, result.Log) if err != nil { - return fmt.Errorf("Error running Logstash: %s.%s", err, message) + return false, fmt.Errorf("Error running Logstash: %s.%s", err, message) } userError("%s", message) } - if err = t.Compare(result.Events, false, diffCommand); err != nil { - userError("Testcase failed, continuing with the rest: %s", err) + + currentOk, err := t.Compare(result.Events, diffCommand, liveObserver) + if err != nil { + return false, err + } + if !currentOk { ok = false } } - if !ok { - return errors.New("one or more testcases failed") - } - return nil + + return ok, nil } // runParallelTests runs multiple set of configuration in a single // instance of Logstash against a slice of test cases and compares // the actual events against the expected set. Returns an error if // at least one test case fails or if there's a problem running the tests. -func runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string) error { +func runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveProducer observer.Property) (bool, error) { testStreams := make([]*logstash.TestStream, 0, len(tests)) badCodecs := map[string]string{ @@ -181,30 +188,30 @@ func runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, di ts, err := logstash.NewTestStream(t.Codec, t.InputFields, *unixSocketCommTimeout) if err != nil { logstash.CleanupTestStreams(testStreams) - return err + return false, err } testStreams = append(testStreams, ts) } p, err := logstash.NewParallelProcess(inv, testStreams, keptEnvVars) if err != nil { - return err + return false, err } defer p.Release() if err = p.Start(); err != nil { - return err + return false, err } for i, t := range tests { for _, line := range t.InputLines { _, err = testStreams[i].Write([]byte(line + "\n")) if err != nil { - return err + return false, err } } if err = testStreams[i].Close(); err != nil { - return err + return false, err } } @@ -212,22 +219,22 @@ func runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, di if err != nil || *logstashOutput { message := getLogstashOutputMessage(result.Output, result.Log) if err != nil { - return fmt.Errorf("Error running Logstash: %s.%s", err, message) + return false, fmt.Errorf("Error running Logstash: %s.%s", err, message) } userError("%s", message) } ok := true for i, t := range tests { - if err = t.Compare(result.Events[i], false, diffCommand); err != nil { + currentOk, err := t.Compare(result.Events[i], diffCommand, liveProducer) + if err != nil { userError("Testcase %s failed, continuing with the rest: %s", filepath.Base(t.File), err) + } + if !currentOk { ok = false } } - if !ok { - return errors.New("one or more testcases failed") - } - return nil + return ok, nil } // getLogstashOutputMessage examines the test result and prepares a @@ -276,6 +283,15 @@ func mainEntrypoint() int { kingpin.Version(fmt.Sprintf("%s %s", kingpin.CommandLine.Name, GitSummary)) kingpin.Parse() + var status bool + + // Create and lauch observer + liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) + + if !*quiet { + go lfvobserver.RunSummaryObserver(liveObserver) + } + level, err := oplogging.LogLevel(*loglevel) if err != nil { prefixedUserError("Bad loglevel: %s", *loglevel) @@ -330,17 +346,23 @@ func mainEntrypoint() int { return 1 } fmt.Println("Use Unix domain sockets.") - if err = runParallelTests(inv, tests, diffCmd, allKeptEnvVars); err != nil { + if status, err = runParallelTests(inv, tests, diffCmd, allKeptEnvVars, liveObserver); err != nil { userError(err.Error()) return 1 } } else { - if err = runTests(inv, tests, diffCmd, allKeptEnvVars); err != nil { + if status, err = runTests(inv, tests, diffCmd, allKeptEnvVars, liveObserver); err != nil { userError(err.Error()) return 1 } } - return 0 + + liveObserver.Update(lfvobserver.TestExecutionEnd{}) + + if status { + return 0 + } + return 1 } func main() { diff --git a/observer/data.go b/observer/data.go new file mode 100644 index 0000000..b2bdb1e --- /dev/null +++ b/observer/data.go @@ -0,0 +1,16 @@ +package observer + +// TestExecutionStart is empty struct to inform consumer that test begin +type TestExecutionStart struct{} + +// TestExecutionEnd is empty struct to inform consumer that test is finished +type TestExecutionEnd struct{} + +// ComparisonResult permit to follow the test execution +type ComparisonResult struct { + Name string + Status bool + Explain string + Path string + EventIndex int +} diff --git a/observer/summary.go b/observer/summary.go new file mode 100644 index 0000000..2b0030d --- /dev/null +++ b/observer/summary.go @@ -0,0 +1,91 @@ +package observer + +import ( + "fmt" + "sort" + + "github.com/imkira/go-observer" + "github.com/magnusbaeck/logstash-filter-verifier/logging" +) + +// Summary describe the summary of global test case. +// It count the number of success test and opposite +// the number of failed test. +type Summary struct { + NumberOk int + NumberNotOk int +} + +var log = logging.MustGetLogger() + +// RunSummaryObserver lauch consummer witch is in responsible to +// print summary at the end of all tests cases. +func RunSummaryObserver(prop observer.Property) { + var ( + results map[string]Summary + globalSummary Summary + ) + + stream := prop.Observe() + + for { + data := stream.Value() + + switch event := data.(type) { + // Init struct to store result test + case TestExecutionStart: + results = make(map[string]Summary) + globalSummary = Summary{ + NumberOk: 0, + NumberNotOk: 0, + } + // Display result on stdout + case TestExecutionEnd: + fmt.Printf("\nSummary: %s All tests : %d/%d\n", getIconStatus(globalSummary.NumberNotOk), globalSummary.NumberOk, globalSummary.NumberOk+globalSummary.NumberNotOk) + + // Ordering by keys name + keys := make([]string, len(results)) + i := 0 + for key := range results { + keys[i] = key + i++ + } + sort.Strings(keys) + for _, key := range keys { + summary := results[key] + + fmt.Printf("\t %s %s: %d/%d\n", getIconStatus(summary.NumberNotOk), key, summary.NumberOk, summary.NumberOk+summary.NumberNotOk) + } + // Store result test + case ComparisonResult: + + // Compute summary to display at the end and siplay current test status + summary := results[event.Path] + if event.Status { + summary.NumberOk++ + globalSummary.NumberOk++ + fmt.Printf("\u2611 %s from %s\n", event.Name, event.Path) + } else { + summary.NumberNotOk++ + globalSummary.NumberNotOk++ + fmt.Printf("\u2610 %s from %s:\n%s\n", event.Name, event.Path, event.Explain) + } + results[event.Path] = summary + default: + log.Debugf("Receive data that we doesn't say how to manage it %+v", data) + } + + // wait change + <-stream.Changes() + // advance to next value + stream.Next() + } +} + +func getIconStatus(numberNotOk int) string { + if numberNotOk == 0 { + return "\u2611" + } + + return "\u2610" +} diff --git a/testcase/testcase.go b/testcase/testcase.go index 11e6f3f..fe5a72b 100644 --- a/testcase/testcase.go +++ b/testcase/testcase.go @@ -17,8 +17,10 @@ import ( "strings" unjson "github.com/hashicorp/packer/common/json" + "github.com/imkira/go-observer" "github.com/magnusbaeck/logstash-filter-verifier/logging" "github.com/magnusbaeck/logstash-filter-verifier/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/observer" "github.com/mikefarah/yaml/v2" ) @@ -90,23 +92,6 @@ type TestCase struct { Description string `json:"description" yaml:"description"` } -// ComparisonError indicates that there was a mismatch when the -// results of a test case was compared against the test case -// definition. -type ComparisonError struct { - ActualCount int - ExpectedCount int - Mismatches []MismatchedEvent -} - -// MismatchedEvent holds a single tuple of actual and expected events -// for a particular index in the list of events for a test case. -type MismatchedEvent struct { - Actual logstash.Event - Expected logstash.Event - Index int -} - var ( log = logging.MustGetLogger() @@ -224,25 +209,42 @@ func NewFromFile(path string) (*TestCaseSet, error) { // Compare compares a slice of events against the expected events of // this test case. Each event is written pretty-printed to a temporary -// file and the two files are passed to "diff -u". If quiet is true, -// the progress messages normally written to stderr will be emitted -// and the output of the diff program will be discarded. -func (tcs *TestCaseSet) Compare(events []logstash.Event, quiet bool, diffCommand []string) error { - result := ComparisonError{ - ActualCount: len(events), - ExpectedCount: len(tcs.ExpectedEvents), - Mismatches: []MismatchedEvent{}, - } +// file and the two files are passed to "diff -u". The resulting of diff command +// is sended to observer throughout lfvobserver.ComparisonResult struct. +// It return true if the current test case pass, else it return false. +func (tcs *TestCaseSet) Compare(events []logstash.Event, diffCommand []string, liveProducer observer.Property) (bool, error) { + status := true // Don't even attempt to do a deep comparison of the event // lists unless their lengths are equal. - if result.ActualCount != result.ExpectedCount { - return result + if len(tcs.ExpectedEvents) != len(events) { + comparisonResult := lfvobserver.ComparisonResult{ + Status: false, + Name: "Compare actual event with expected event", + Explain: fmt.Sprintf("Expected %d event(s), got %d instead.", len(tcs.ExpectedEvents), len(events)), + Path: filepath.Base(tcs.File), + EventIndex: 0, + } + liveProducer.Update(comparisonResult) + return false, nil + } + + // Check if test consit to validate that all event are dropped and so not failed before + if len(events) == 0 { + comparisonResult := lfvobserver.ComparisonResult{ + Status: true, + Name: "Compare actual event with expected event", + Explain: "Drop all events", + Path: filepath.Base(tcs.File), + EventIndex: 0, + } + liveProducer.Update(comparisonResult) + return true, nil } tempdir, err := ioutil.TempDir("", "") if err != nil { - return nil + return false, err } defer func() { if err := os.RemoveAll(tempdir); err != nil { @@ -251,12 +253,15 @@ func (tcs *TestCaseSet) Compare(events []logstash.Event, quiet bool, diffCommand }() for i, actualEvent := range events { - if !quiet { - var description string - if len(tcs.descriptions[i]) > 0 { - description = fmt.Sprintf(" (%s)", tcs.descriptions[i]) - } - fmt.Printf("Comparing message %d of %d from %s%s...\n", i+1, len(events), filepath.Base(tcs.File), description) + comparisonResult := lfvobserver.ComparisonResult{ + Path: filepath.Base(tcs.File), + EventIndex: i, + Status: true, + } + if (len(tcs.descriptions) > i) && (len(tcs.descriptions[i]) > 0) { + comparisonResult.Name = fmt.Sprintf("Comparing message %d of %d (%s)", i+1, len(events), tcs.descriptions[i]) + } else { + comparisonResult.Name = fmt.Sprintf("Comparing message %d of %d", i+1, len(events)) } // Ignored fields can be in a sub object @@ -271,25 +276,25 @@ func (tcs *TestCaseSet) Compare(events []logstash.Event, quiet bool, diffCommand resultDir := filepath.Join(tempdir, filepath.Base(tcs.File), strconv.Itoa(i+1)) actualFilePath := filepath.Join(resultDir, "actual") if err = marshalToFile(actualEvent, actualFilePath); err != nil { - return err + return false, err } expectedFilePath := filepath.Join(resultDir, "expected") if err = marshalToFile(tcs.ExpectedEvents[i], expectedFilePath); err != nil { - return err + return false, err } - equal, err := runDiffCommand(diffCommand, expectedFilePath, actualFilePath, quiet) + comparisonResult.Status, comparisonResult.Explain, err = runDiffCommand(diffCommand, expectedFilePath, actualFilePath) if err != nil { - return err + return false, err } - if !equal { - result.Mismatches = append(result.Mismatches, MismatchedEvent{actualEvent, tcs.ExpectedEvents[i], i}) + if !comparisonResult.Status { + status = false } + + liveProducer.Update(comparisonResult) } - if len(result.Mismatches) == 0 { - return nil - } - return result + + return status, nil } // marshalToFile pretty-prints a logstash.Event and writes it to a @@ -309,34 +314,19 @@ func marshalToFile(event logstash.Event, filename string) error { // path and optional arguments) and returns whether the files were // equal, i.e. whether the diff command returned a zero exit // status. The returned error value will be set if there was a problem -// running the command. If quiet is true, the output of the diff -// command will be discarded. Otherwise the child process will inherit -// stdout and stderr from the parent. -func runDiffCommand(command []string, file1, file2 string, quiet bool) (bool, error) { +// running the command. The output of the diff command will is returned +// as string. +func runDiffCommand(command []string, file1, file2 string) (bool, string, error) { fullCommand := append(command, file1) fullCommand = append(fullCommand, file2) - c := exec.Command(fullCommand[0], fullCommand[1:]...) // nolint:gosec - if !quiet { - c.Stdout = os.Stdout - c.Stderr = os.Stderr - } - log.Infof("Starting %q with args %q.", c.Path, c.Args[1:]) - if err := c.Start(); err != nil { - return false, err - } - if err := c.Wait(); err != nil { - log.Infof("Child with pid %d failed: %s", c.Process.Pid, err) - return false, nil - } - return true, nil -} - -func (e ComparisonError) Error() string { - if e.ActualCount != e.ExpectedCount { - return fmt.Sprintf("Expected %d event(s), got %d instead.", e.ExpectedCount, e.ActualCount) - } - if len(e.Mismatches) > 0 { - return fmt.Sprintf("%d message(s) did not match the expectations.", len(e.Mismatches)) + /* #nosec */ + c := exec.Command(fullCommand[0], fullCommand[1:]...) + stdoutStderr, err := c.CombinedOutput() + + success := err == nil + if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() == 1 { + // Exit code 1 is expected when the files differ; just ignore it. + err = nil } - return "No error" + return success, string(stdoutStderr), err } diff --git a/testcase/testcase_test.go b/testcase/testcase_test.go index fd38b4d..38a517f 100644 --- a/testcase/testcase_test.go +++ b/testcase/testcase_test.go @@ -8,9 +8,9 @@ import ( "io/ioutil" "os" "path/filepath" - "reflect" "testing" + "github.com/imkira/go-observer" "github.com/magnusbaeck/logstash-filter-verifier/logstash" "github.com/stretchr/testify/assert" ) @@ -154,11 +154,14 @@ func TestCompare(t *testing.T) { } defer os.RemoveAll(tempdir) + liveObserver := observer.NewProperty(nil) + cases := []struct { testcase *TestCaseSet actualEvents []logstash.Event diffCommand []string - result error + result bool + err error }{ // Empty test case with no messages is okay. { @@ -173,6 +176,7 @@ func TestCompare(t *testing.T) { }, []logstash.Event{}, []string{"diff"}, + true, nil, }, // Too few messages received. @@ -199,11 +203,8 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, - ComparisonError{ - ActualCount: 1, - ExpectedCount: 2, - Mismatches: []MismatchedEvent{}, - }, + false, + nil, }, // Too many messages received. { @@ -229,11 +230,8 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, - ComparisonError{ - ActualCount: 2, - ExpectedCount: 1, - Mismatches: []MismatchedEvent{}, - }, + false, + nil, }, // Different fields. { @@ -256,21 +254,8 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, - ComparisonError{ - ActualCount: 1, - ExpectedCount: 1, - Mismatches: []MismatchedEvent{ - { - Actual: logstash.Event{ - "c": "d", - }, - Expected: logstash.Event{ - "a": "b", - }, - Index: 0, - }, - }, - }, + false, + nil, }, // Same field with different values. { @@ -293,21 +278,8 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, - ComparisonError{ - ActualCount: 1, - ExpectedCount: 1, - Mismatches: []MismatchedEvent{ - { - Actual: logstash.Event{ - "a": "B", - }, - Expected: logstash.Event{ - "a": "b", - }, - Index: 0, - }, - }, - }, + false, + nil, }, // Ignored fields are ignored. { @@ -332,6 +304,7 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, + true, nil, }, // Ignored fields with bracket notation are ignored @@ -367,6 +340,7 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, + true, nil, }, // Ignored fields with bracket notation are ignored (when empty hash) @@ -396,6 +370,7 @@ func TestCompare(t *testing.T) { }, }, []string{"diff"}, + true, nil, }, // Diff command execution errors are propagated correctly. @@ -419,34 +394,19 @@ func TestCompare(t *testing.T) { }, }, []string{filepath.Join(tempdir, "does-not-exist")}, + false, &os.PathError{}, }, } for i, c := range cases { - actualResult := c.testcase.Compare(c.actualEvents, true, c.diffCommand) - if actualResult == nil && c.result != nil { - t.Errorf("Test %d: Expected failure, got success.", i) - } else if actualResult != nil && c.result == nil { - t.Errorf("Test %d: Expected success, got this error instead: %#v", i, actualResult) - } else if actualResult != nil && c.result != nil { - // Check if we get the right kind of error. - actualType := reflect.TypeOf(actualResult) - expectedType := reflect.TypeOf(c.result) - if actualType == expectedType { - switch e := actualResult.(type) { - case ComparisonError: - if !reflect.DeepEqual(c.result, e) { - t.Errorf("Test %d:\nExpected:\n%#v\nGot:\n%#v", i, c.result, e) - } - default: - // Except in the explicitly - // handled cases above we just - // care that the types match. - } - } else { - t.Errorf("Test %d:\nExpected error:\n%s\nGot:\n%s (%s)", i, expectedType, actualType, actualResult) - } + actualResult, err := c.testcase.Compare(c.actualEvents, c.diffCommand, liveObserver) + if err != nil && c.err == nil { + t.Errorf("Test %d: Expected no error, got error: %s", i, err) + } else if c.err != nil && err == nil { + t.Errorf("Test %d: Expected error, got no error.", i) + } else if actualResult != c.result { + t.Errorf("Test %d: Expected %t, got %t.", i, c.result, actualResult) } } } From 97f1ddd6b8485b47970e49bc9937434c8f45b148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sun, 14 Jun 2020 21:42:08 +0200 Subject: [PATCH 004/143] Add "checktidy" make goal for checking "go mod tidy" cleanliness We don't want cruft laying around in go.{mod,sum} and the new "checktidy" make goal makes it easy to check. Since the command could modify the working tree and produces false positives if run from a tree where go.{mod,sum} already is dirty we don't run it as part of the old "check" goal. However, include it in the Travis build. --- .travis.yml | 2 +- Makefile | 4 ++++ go.sum | 2 -- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ccfc864..eb4b197 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,4 +3,4 @@ language: go go: - "1.13.5" script: - - make check test + - make check checktidy test diff --git a/Makefile b/Makefile index 09d488e..b895b12 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,10 @@ $(PROGRAM)$(EXEC_SUFFIX): .FORCE $(GOVVV) check: $(GOLANGCI_LINT) golangci-lint run +.PHONY: checktidy +checktidy: + go mod tidy && git diff --exit-code -- go.mod go.sum + .PHONY: clean clean: rm -f $(PROGRAM)$(EXEC_SUFFIX) $(GOCOV) $(GOCOV_HTML) $(GOLANGCI_LINT) $(GPM) $(OVERALLS) diff --git a/go.sum b/go.sum index d56a13d..3112172 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,6 @@ github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= -github.com/ahmetb/govvv v0.2.0 h1:3rzpWlytDIDQfL64/Kbjs88tNJltY9E8sk1tNju6FAU= -github.com/ahmetb/govvv v0.2.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= github.com/ahmetb/govvv v0.3.0 h1:YGLGwEyiUwHFy5eh/RUhdupbuaCGBYn5T5GWXp+WJB0= github.com/ahmetb/govvv v0.3.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= From fde967b49442f3c9434531173f672886956a363f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sun, 14 Jun 2020 22:11:57 +0200 Subject: [PATCH 005/143] Clean up after commit 2569e44 Mostly rewordings and languages fixups. --- Makefile | 1 - logstash-filter-verifier.go | 13 ++++++------- observer/data.go | 6 +++--- observer/summary.go | 12 ++++-------- testcase/testcase.go | 19 +++++++++++-------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index b895b12..0ed2848 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ # Copyright (c) 2015-2020 Magnus Bäck - export GOBIN := $(shell pwd)/bin export PATH := $(GOBIN):$(PATH) diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go index 158f9dc..54f3dfe 100644 --- a/logstash-filter-verifier.go +++ b/logstash-filter-verifier.go @@ -78,7 +78,7 @@ var ( Default("60s"). Duration() quiet = kingpin. - Flag("quiet", "Permit to disable extra messages"). + Flag("quiet", "Omit test progress messages and event diffs."). Default("false"). Bool() @@ -118,8 +118,8 @@ func findExecutable(paths []string) (string, error) { // runTests runs Logstash with a set of configuration files against a // slice of test cases and compares the actual events against the -// expected set. Returns an error if at least one test case fails or -// if there's a problem running the tests. +// expected set. Returns a bool that indicates whether all tests pass +// and an error that indicates a problem running the tests. func runTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveObserver observer.Property) (bool, error) { ok := true for _, t := range tests { @@ -166,8 +166,9 @@ func runTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffComman // runParallelTests runs multiple set of configuration in a single // instance of Logstash against a slice of test cases and compares -// the actual events against the expected set. Returns an error if -// at least one test case fails or if there's a problem running the tests. +// the actual events against the expected set. Returns a bool that +// indicates whether all tests pass and an error that indicates a +// problem running the tests. func runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveProducer observer.Property) (bool, error) { testStreams := make([]*logstash.TestStream, 0, len(tests)) @@ -285,9 +286,7 @@ func mainEntrypoint() int { var status bool - // Create and lauch observer liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) - if !*quiet { go lfvobserver.RunSummaryObserver(liveObserver) } diff --git a/observer/data.go b/observer/data.go index b2bdb1e..e4bf410 100644 --- a/observer/data.go +++ b/observer/data.go @@ -1,12 +1,12 @@ package observer -// TestExecutionStart is empty struct to inform consumer that test begin +// TestExecutionStart is empty struct to inform consumer that test execution has begun. type TestExecutionStart struct{} -// TestExecutionEnd is empty struct to inform consumer that test is finished +// TestExecutionEnd is empty struct to inform consumer that test execution has finished. type TestExecutionEnd struct{} -// ComparisonResult permit to follow the test execution +// ComparisonResult describes the result of the execution of a single test case. type ComparisonResult struct { Name string Status bool diff --git a/observer/summary.go b/observer/summary.go index 2b0030d..ab4fee6 100644 --- a/observer/summary.go +++ b/observer/summary.go @@ -8,9 +8,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/logging" ) -// Summary describe the summary of global test case. -// It count the number of success test and opposite -// the number of failed test. +// Summary summarizes the number of successful and failed test cases. type Summary struct { NumberOk int NumberNotOk int @@ -18,8 +16,8 @@ type Summary struct { var log = logging.MustGetLogger() -// RunSummaryObserver lauch consummer witch is in responsible to -// print summary at the end of all tests cases. +// RunSummaryObserver launches a consumer responsible for printing a summary +// at the end of the execution. func RunSummaryObserver(prop observer.Property) { var ( results map[string]Summary @@ -59,7 +57,7 @@ func RunSummaryObserver(prop observer.Property) { // Store result test case ComparisonResult: - // Compute summary to display at the end and siplay current test status + // Compute summary to display at the end and display current test status summary := results[event.Path] if event.Status { summary.NumberOk++ @@ -75,9 +73,7 @@ func RunSummaryObserver(prop observer.Property) { log.Debugf("Receive data that we doesn't say how to manage it %+v", data) } - // wait change <-stream.Changes() - // advance to next value stream.Next() } } diff --git a/testcase/testcase.go b/testcase/testcase.go index fe5a72b..8f866ce 100644 --- a/testcase/testcase.go +++ b/testcase/testcase.go @@ -209,9 +209,10 @@ func NewFromFile(path string) (*TestCaseSet, error) { // Compare compares a slice of events against the expected events of // this test case. Each event is written pretty-printed to a temporary -// file and the two files are passed to "diff -u". The resulting of diff command -// is sended to observer throughout lfvobserver.ComparisonResult struct. -// It return true if the current test case pass, else it return false. +// file and the two files are passed to the diff command. Its output is +// is sent to the observer via an lfvobserver.ComparisonResult struct. +// Returns true if the current test case passes, otherwise false. A non-nil +// error value indicates a problem executing the test. func (tcs *TestCaseSet) Compare(events []logstash.Event, diffCommand []string, liveProducer observer.Property) (bool, error) { status := true @@ -229,7 +230,8 @@ func (tcs *TestCaseSet) Compare(events []logstash.Event, diffCommand []string, l return false, nil } - // Check if test consit to validate that all event are dropped and so not failed before + // Make sure we produce a result even if there are zero events (i.e. we + // won't enter the for loop below). if len(events) == 0 { comparisonResult := lfvobserver.ComparisonResult{ Status: true, @@ -312,10 +314,11 @@ func marshalToFile(event logstash.Event, filename string) error { // runDiffCommand passes two files to the supplied command (executable // path and optional arguments) and returns whether the files were -// equal, i.e. whether the diff command returned a zero exit -// status. The returned error value will be set if there was a problem -// running the command. The output of the diff command will is returned -// as string. +// equal. The returned error value will be set if there was a problem +// running the command or if it returned an exit status other than zero +// or one. The latter is interpreted as "comparison performed successfully +// but the files were different". The output of the diff command is +// returned as a string. func runDiffCommand(command []string, file1, file2 string) (bool, string, error) { fullCommand := append(command, file1) fullCommand = append(fullCommand, file2) From 6d60b465f6176ba19bf2408e7fc1faaccd1ba9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 18 Jun 2020 20:17:30 +0200 Subject: [PATCH 006/143] Upgrade golangci-lint to 1.27.0 Because of a stricter gosec linter we had to tighten the permissions of the created tempfiles so they're not world-readable. --- Makefile | 2 +- logstash/invocation.go | 2 +- logstash/invocation_test.go | 2 +- logstash/pipelineconfigdir.go | 2 +- logstash/pipelineconfigdir_test.go | 48 +++++++++++++++--------------- testcase/discover_test.go | 4 +-- testcase/testcase_test.go | 2 +- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 0ed2848..67f8357 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ GOLANGCI_LINT := $(GOBIN)/golangci-lint$(EXEC_SUFFIX) GOVVV := $(GOBIN)/govvv$(EXEC_SUFFIX) OVERALLS := $(GOBIN)/overalls$(EXEC_SUFFIX) -GOLANGCI_LINT_VERSION := v1.22.2 +GOLANGCI_LINT_VERSION := v1.27.0 .PHONY: all all: $(PROGRAM)$(EXEC_SUFFIX) diff --git a/logstash/invocation.go b/logstash/invocation.go index 7fba284..0df53a0 100644 --- a/logstash/invocation.go +++ b/logstash/invocation.go @@ -110,7 +110,7 @@ func NewInvocation(logstashPath string, logstashArgs []string, logstashVersion * // put there. The various path settings that we need // to provide can just as well be passed as command // arguments. - err := ioutil.WriteFile(filepath.Join(configDir, "logstash.yml"), []byte{}, 0644) + err := ioutil.WriteFile(filepath.Join(configDir, "logstash.yml"), []byte{}, 0600) if err != nil { _ = logFile.Close() _ = os.RemoveAll(tempDir) diff --git a/logstash/invocation_test.go b/logstash/invocation_test.go index 1a6d7ed..c3340f4 100644 --- a/logstash/invocation_test.go +++ b/logstash/invocation_test.go @@ -197,7 +197,7 @@ func createTestInvocation(version semver.Version) (*testInvocation, error) { configFile := filepath.Join(tempdir, "configfile.conf") configContents := "" - if err = ioutil.WriteFile(configFile, []byte(configContents), 0644); err != nil { + if err = ioutil.WriteFile(configFile, []byte(configContents), 0600); err != nil { return nil, fmt.Errorf("Unexpected error when creating dummy configuration file: %s", err) } logstashPath := filepath.Join(tempdir, "bin/logstash") diff --git a/logstash/pipelineconfigdir.go b/logstash/pipelineconfigdir.go index cff97f4..4db5646 100644 --- a/logstash/pipelineconfigdir.go +++ b/logstash/pipelineconfigdir.go @@ -116,5 +116,5 @@ func removeInputOutput(path string) error { config.Input = nil config.Output = nil - return ioutil.WriteFile(path, []byte(config.String()), 0644) + return ioutil.WriteFile(path, []byte(config.String()), 0600) } diff --git a/logstash/pipelineconfigdir_test.go b/logstash/pipelineconfigdir_test.go index 0b970f5..332b66d 100644 --- a/logstash/pipelineconfigdir_test.go +++ b/logstash/pipelineconfigdir_test.go @@ -39,8 +39,8 @@ func TestFlattenFilenames(t *testing.T) { // Files only. { []testhelpers.FileWithMode{ - {"a", 0644, ""}, - {"b", 0644, ""}, + {"a", 0600, ""}, + {"b", 0600, ""}, }, []string{"a", "b"}, []string{"a", "b"}, @@ -48,9 +48,9 @@ func TestFlattenFilenames(t *testing.T) { // Files only, and only a subset of them. { []testhelpers.FileWithMode{ - {"a", 0644, ""}, - {"b", 0644, ""}, - {"c", 0644, ""}, + {"a", 0600, ""}, + {"b", 0600, ""}, + {"c", 0600, ""}, }, []string{"a", "b"}, []string{"a", "b"}, @@ -58,9 +58,9 @@ func TestFlattenFilenames(t *testing.T) { // Files and an empty subdirectory. { []testhelpers.FileWithMode{ - {"a", 0644, ""}, - {"b", 0644, ""}, - {"c", os.ModeDir | 0755, ""}, + {"a", 0600, ""}, + {"b", 0600, ""}, + {"c", os.ModeDir | 0700, ""}, }, []string{"a", "b", "c"}, []string{"a", "b"}, @@ -68,10 +68,10 @@ func TestFlattenFilenames(t *testing.T) { // Files and a file in a subdirectory. { []testhelpers.FileWithMode{ - {"a", 0644, ""}, - {"b", 0644, ""}, - {"c", os.ModeDir | 0755, ""}, - {"c/d", 0644, ""}, + {"a", 0600, ""}, + {"b", 0600, ""}, + {"c", os.ModeDir | 0700, ""}, + {"c/d", 0600, ""}, }, []string{"a", "b", "c"}, []string{"a", "b", "c/d"}, @@ -79,12 +79,12 @@ func TestFlattenFilenames(t *testing.T) { // Files and multiple levels of subdirectories. { []testhelpers.FileWithMode{ - {"a", 0644, ""}, - {"b", 0644, ""}, - {"c", os.ModeDir | 0755, ""}, - {"c/d", os.ModeDir | 0755, ""}, - {"c/d/e", 0644, ""}, - {"c/f", 0644, ""}, + {"a", 0600, ""}, + {"b", 0600, ""}, + {"c", os.ModeDir | 0700, ""}, + {"c/d", os.ModeDir | 0700, ""}, + {"c/d/e", 0600, ""}, + {"c/f", 0600, ""}, }, []string{"a", "b", "c"}, []string{"a", "b", "c/f"}, @@ -92,8 +92,8 @@ func TestFlattenFilenames(t *testing.T) { // Just as directory with files. { []testhelpers.FileWithMode{ - {"a", os.ModeDir | 0755, ""}, - {"a/b", 0644, ""}, + {"a", os.ModeDir | 0700, ""}, + {"a/b", 0600, ""}, }, []string{"a"}, []string{"a/b"}, @@ -167,12 +167,12 @@ func TestGetPipelineConfigDir(t *testing.T) { var configFiles []string for _, f := range c.files { - err = os.MkdirAll(filepath.Join(tempdir, filepath.Dir(f)), 0755) + err = os.MkdirAll(filepath.Join(tempdir, filepath.Dir(f)), 0700) if err != nil { t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) } - err = ioutil.WriteFile(filepath.Join(tempdir, f), []byte(createLogstashConfigWithString(filepath.Base(f))), 0644) + err = ioutil.WriteFile(filepath.Join(tempdir, f), []byte(createLogstashConfigWithString(filepath.Base(f))), 0600) if err != nil { t.Fatalf("Test %d: Unexpected error when writing to temp file: %s", i, err) } @@ -264,7 +264,7 @@ func TestGetFilesInDir(t *testing.T) { f.Close() } for _, filename := range c.dirs { - err := os.MkdirAll(filepath.Join(tempdir, filename), 0755) + err := os.MkdirAll(filepath.Join(tempdir, filename), 0700) if err != nil { t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) } @@ -328,7 +328,7 @@ func TestRemoveInputOutput(t *testing.T) { path := f.Name() defer os.Remove(path) - err = ioutil.WriteFile(path, []byte(c.input), 0644) + err = ioutil.WriteFile(path, []byte(c.input), 0600) if err != nil { t.Fatalf("Test %d: Unexpected error when writing to temp file: %s", i, err) } diff --git a/testcase/discover_test.go b/testcase/discover_test.go index 2ed9bf1..a669481 100644 --- a/testcase/discover_test.go +++ b/testcase/discover_test.go @@ -58,7 +58,7 @@ func TestDiscoverTests_Directory(t *testing.T) { t.Errorf("This test doesn't support subdirectories: %s", f) break } - if err = ioutil.WriteFile(filepath.Join(tempdir, f), []byte(`{"type": "test"}`), 0644); err != nil { + if err = ioutil.WriteFile(filepath.Join(tempdir, f), []byte(`{"type": "test"}`), 0600); err != nil { t.Fatalf(err.Error()) } } @@ -122,7 +122,7 @@ func TestDiscoverTests_File(t *testing.T) { // As it happens a valid JSON file is also a valid YAML file so // the file we create can have the same contents regardless of // the file format. - if err = ioutil.WriteFile(inputpath, []byte(`{"type": "test"}`), 0644); err != nil { + if err = ioutil.WriteFile(inputpath, []byte(`{"type": "test"}`), 0600); err != nil { t.Fatal(err.Error()) } diff --git a/testcase/testcase_test.go b/testcase/testcase_test.go index 38a517f..97a8bc0 100644 --- a/testcase/testcase_test.go +++ b/testcase/testcase_test.go @@ -130,7 +130,7 @@ func TestNewFromFile(t *testing.T) { // As it happens a valid JSON file is also a valid YAML file so // the file we create can have the same contents regardless of // the file format. - if err = ioutil.WriteFile(fullTestCasePath, []byte(`{"type": "test"}`), 0644); err != nil { + if err = ioutil.WriteFile(fullTestCasePath, []byte(`{"type": "test"}`), 0600); err != nil { t.Fatal(err.Error()) } From e81fcdfc0d6a2a0d8de5cadf6d6919ff539a1796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 18 Jun 2020 20:21:28 +0200 Subject: [PATCH 007/143] Enable godot checker and fix existing violations --- .golangci.yml | 1 + logstash/parallel_process.go | 10 +++++----- testcase/helper.go | 2 +- testcase/helper_test.go | 2 +- testcase/testcase.go | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 48f2589..5dc118a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,6 +4,7 @@ linters: enable: - deadcode - gocyclo + - godot - gofmt - gosec - gosimple diff --git a/logstash/parallel_process.go b/logstash/parallel_process.go index de5a951..9cde630 100644 --- a/logstash/parallel_process.go +++ b/logstash/parallel_process.go @@ -18,7 +18,7 @@ import ( "time" ) -// TestStream contains the input and output streams for one test case +// TestStream contains the input and output streams for one test case. type TestStream struct { sender *net.UnixConn senderListener *net.UnixListener @@ -82,7 +82,7 @@ func NewTestStream(inputCodec string, fields FieldSet, timeout time.Duration) (* return ts, nil } -// Write writes to the sender of the TestStream +// Write writes to the sender of the TestStream. func (ts *TestStream) Write(p []byte) (n int, err error) { timer := time.NewTimer(ts.timeout) select { @@ -93,7 +93,7 @@ func (ts *TestStream) Write(p []byte) (n int, err error) { return ts.sender.Write(p) } -// Close closes the sender of the TestStream +// Close closes the sender of the TestStream. func (ts *TestStream) Close() error { if ts.sender != nil { err := ts.sender.Close() @@ -104,7 +104,7 @@ func (ts *TestStream) Close() error { } // Cleanup closes and removes all temporary resources -// for a TestStream +// for a TestStream. func (ts *TestStream) Cleanup() { if ts.senderListener != nil { ts.senderListener.Close() @@ -120,7 +120,7 @@ func (ts *TestStream) Cleanup() { // CleanupTestStreams closes all sockets and streams as well // removes temporary file ressources for an array of -// TestStreams +// TestStreams. func CleanupTestStreams(ts []*TestStream) { for i := range ts { ts[i].Cleanup() diff --git a/testcase/helper.go b/testcase/helper.go index 1f14a16..833a26e 100644 --- a/testcase/helper.go +++ b/testcase/helper.go @@ -24,7 +24,7 @@ func parseAllBracketProperties(data map[string]interface{}) map[string]interface return result } -// extractBracketFields convert bracket notation to slice of key +// extractBracketFields convert bracket notation to slice of key. func extractBracketFields(key string) []string { rValidator := regexp.MustCompile(`^(\[\w+\])+$`) rExtractField := regexp.MustCompile(`\[(\w+)\]`) diff --git a/testcase/helper_test.go b/testcase/helper_test.go index c4b9100..3efff03 100644 --- a/testcase/helper_test.go +++ b/testcase/helper_test.go @@ -32,7 +32,7 @@ func TestExtractBracketFields(t *testing.T) { } } -// TestParseBracketProperty test keys that contain bracket notation are converted to sub structure +// TestParseBracketProperty test keys that contain bracket notation are converted to sub structure. func TestParseBracketProperty(t *testing.T) { cases := []struct { key []string diff --git a/testcase/testcase.go b/testcase/testcase.go index 8f866ce..91770a6 100644 --- a/testcase/testcase.go +++ b/testcase/testcase.go @@ -99,7 +99,7 @@ var ( ) // convertBracketFields permit to replace keys that contains bracket with sub structure. -// For example, the key `[log][file][path]` will be convert by `"log": {"file": {"path": "VALUE"}}` +// For example, the key `[log][file][path]` will be convert by `"log": {"file": {"path": "VALUE"}}`. func (tcs *TestCaseSet) convertBracketFields() error { // Convert fields in input fields tcs.InputFields = parseAllBracketProperties(tcs.InputFields) From e51eb08c6fcad0fa93b5698d95bfa99848fa5eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Tue, 30 Jun 2020 21:07:51 +0200 Subject: [PATCH 008/143] README.md: Clarify documentation of codec setting Commit bee5c4e changed the behavior of the codec setting to support giving options to the codec, but this wasn't made clear in the documentation. --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1c3138d..5739bc2 100644 --- a/README.md +++ b/README.md @@ -306,8 +306,11 @@ There are a few points to be made here: Test case files are JSON files containing a single object. That object may have the following properties: -* `codec`: A string value naming the Logstash codec that should be - used when events are read. This is normally "line" or "json_lines". +* `codec`: A string with the codec configuration of the input plugin used + when executing the tests. This string will be included verbatim in the + Logstash configuration so it could either be just the name of the codec + plugin (normally `line` or `json_lines`) or include additional codec + options like e.g. `plain { charset => "ISO-8859-1" }`. * `fields`: An object containing the fields that all input messages should have. This is vital since filters typically are configured based on the event's type and/or tags. Scalar values (strings, From 22f7b218fdd6adb3495e98f9371fa80c06904e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Tue, 30 Jun 2020 21:27:00 +0200 Subject: [PATCH 009/143] observer: Removed extra space in summary message --- observer/summary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/observer/summary.go b/observer/summary.go index ab4fee6..6dcc65f 100644 --- a/observer/summary.go +++ b/observer/summary.go @@ -39,7 +39,7 @@ func RunSummaryObserver(prop observer.Property) { } // Display result on stdout case TestExecutionEnd: - fmt.Printf("\nSummary: %s All tests : %d/%d\n", getIconStatus(globalSummary.NumberNotOk), globalSummary.NumberOk, globalSummary.NumberOk+globalSummary.NumberNotOk) + fmt.Printf("\nSummary: %s All tests: %d/%d\n", getIconStatus(globalSummary.NumberNotOk), globalSummary.NumberOk, globalSummary.NumberOk+globalSummary.NumberNotOk) // Ordering by keys name keys := make([]string, len(results)) From f895f25faa9528f35e79f8a98e2d53cee4572c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Tue, 30 Jun 2020 21:18:42 +0200 Subject: [PATCH 010/143] Add 1.6.1 changelog entry --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c9e93..e89ffd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ Logstash Filter Verifier Change Log All timestamps are in the Europe/Stockholm timezone. +1.6.1 (2020-06-30) +------------------ + + * Slightly changed output format with a summary included at the end. + * The `codec` test case file option is inserted in the configuration file + verbatim rather than as a string literal, allowing the user to provide + additional codec options. + + 1.6.0 (2020-01-02) ------------------ From 98125d8eb4fa173a03d46f8ae1c559fd5fa34568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 14 Nov 2020 20:39:03 +0100 Subject: [PATCH 011/143] Make sure observers have time to complete their processing There was a race condition when lfvobserver.TestExecutionEnd{} had been pushed to the summary observer since we never waited for it to write its output before shutting down. This was okay most of the time but occasionally the summary output lost the race. Introduce an lfvobserver.Interface interface that observers can adhere to, including a Finalize method that waits for the final event to have been processed. Fixes #92 --- logstash-filter-verifier.go | 25 +++++-- observer/data.go | 10 +++ observer/summary.go | 138 +++++++++++++++++++++--------------- 3 files changed, 112 insertions(+), 61 deletions(-) diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go index 54f3dfe..4e0d358 100644 --- a/logstash-filter-verifier.go +++ b/logstash-filter-verifier.go @@ -286,11 +286,6 @@ func mainEntrypoint() int { var status bool - liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) - if !*quiet { - go lfvobserver.RunSummaryObserver(liveObserver) - } - level, err := oplogging.LogLevel(*loglevel) if err != nil { prefixedUserError("Bad loglevel: %s", *loglevel) @@ -298,6 +293,19 @@ func mainEntrypoint() int { } logging.SetLevel(level) + // Set up observers + observers := make([]lfvobserver.Interface, 0) + liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) + if !*quiet { + observers = append(observers, lfvobserver.NewSummaryObserver(liveObserver)) + } + for _, obs := range observers { + if err := obs.Start(); err != nil { + userError("Initialization error: %s", err) + return 1 + } + } + diffCmd, err := shellwords.NewParser().Parse(*diffCommand) if err != nil { userError("Error parsing diff command %q: %s", *diffCommand, err) @@ -358,6 +366,13 @@ func mainEntrypoint() int { liveObserver.Update(lfvobserver.TestExecutionEnd{}) + for _, obs := range observers { + if err := obs.Finalize(); err != nil { + userError(err.Error()) + return 1 + } + } + if status { return 0 } diff --git a/observer/data.go b/observer/data.go index e4bf410..ddff39b 100644 --- a/observer/data.go +++ b/observer/data.go @@ -14,3 +14,13 @@ type ComparisonResult struct { Path string EventIndex int } + +// Interface defines the methods of an observer. +type Interface interface { + // Start fires up the observer in a new goroutine. + Start() error + + // Finalize waits for the observer to receive the final property value, process it, + // and shut itself down. + Finalize() error +} diff --git a/observer/summary.go b/observer/summary.go index 6dcc65f..8cb2ece 100644 --- a/observer/summary.go +++ b/observer/summary.go @@ -8,74 +8,100 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/logging" ) +var log = logging.MustGetLogger() + // Summary summarizes the number of successful and failed test cases. type Summary struct { NumberOk int NumberNotOk int } -var log = logging.MustGetLogger() +// SummaryObserver implements an LFV event observer that outputs a summary of +// the test execution when it's over. +type SummaryObserver struct { + done chan interface{} + prop observer.Property +} -// RunSummaryObserver launches a consumer responsible for printing a summary +// NewSummaryObserver initializes a new SummaryObserver struct. +func NewSummaryObserver(prop observer.Property) *SummaryObserver { + return &SummaryObserver{ + done: make(chan interface{}), + prop: prop, + } +} + +// Start launches a consumer responsible for printing a summary // at the end of the execution. -func RunSummaryObserver(prop observer.Property) { - var ( - results map[string]Summary - globalSummary Summary - ) - - stream := prop.Observe() - - for { - data := stream.Value() - - switch event := data.(type) { - // Init struct to store result test - case TestExecutionStart: - results = make(map[string]Summary) - globalSummary = Summary{ - NumberOk: 0, - NumberNotOk: 0, - } - // Display result on stdout - case TestExecutionEnd: - fmt.Printf("\nSummary: %s All tests: %d/%d\n", getIconStatus(globalSummary.NumberNotOk), globalSummary.NumberOk, globalSummary.NumberOk+globalSummary.NumberNotOk) - - // Ordering by keys name - keys := make([]string, len(results)) - i := 0 - for key := range results { - keys[i] = key - i++ - } - sort.Strings(keys) - for _, key := range keys { - summary := results[key] +func (so *SummaryObserver) Start() error { + go func() { + var ( + results map[string]Summary + globalSummary Summary + ) - fmt.Printf("\t %s %s: %d/%d\n", getIconStatus(summary.NumberNotOk), key, summary.NumberOk, summary.NumberOk+summary.NumberNotOk) - } - // Store result test - case ComparisonResult: - - // Compute summary to display at the end and display current test status - summary := results[event.Path] - if event.Status { - summary.NumberOk++ - globalSummary.NumberOk++ - fmt.Printf("\u2611 %s from %s\n", event.Name, event.Path) - } else { - summary.NumberNotOk++ - globalSummary.NumberNotOk++ - fmt.Printf("\u2610 %s from %s:\n%s\n", event.Name, event.Path, event.Explain) + stream := so.prop.Observe() + + for { + data := stream.Value() + + switch event := data.(type) { + // Init struct to store result test + case TestExecutionStart: + results = make(map[string]Summary) + globalSummary = Summary{ + NumberOk: 0, + NumberNotOk: 0, + } + // Display result on stdout + case TestExecutionEnd: + fmt.Printf("\nSummary: %s All tests: %d/%d\n", getIconStatus(globalSummary.NumberNotOk), globalSummary.NumberOk, globalSummary.NumberOk+globalSummary.NumberNotOk) + + // Ordering by keys name + keys := make([]string, len(results)) + i := 0 + for key := range results { + keys[i] = key + i++ + } + sort.Strings(keys) + for _, key := range keys { + summary := results[key] + + fmt.Printf("\t %s %s: %d/%d\n", getIconStatus(summary.NumberNotOk), key, summary.NumberOk, summary.NumberOk+summary.NumberNotOk) + } + so.done <- true + // Store result test + case ComparisonResult: + + // Compute summary to display at the end and display current test status + summary := results[event.Path] + if event.Status { + summary.NumberOk++ + globalSummary.NumberOk++ + fmt.Printf("\u2611 %s from %s\n", event.Name, event.Path) + } else { + summary.NumberNotOk++ + globalSummary.NumberNotOk++ + fmt.Printf("\u2610 %s from %s:\n%s\n", event.Name, event.Path, event.Explain) + } + results[event.Path] = summary + default: + log.Debugf("Receive data that we doesn't say how to manage it %+v", data) } - results[event.Path] = summary - default: - log.Debugf("Receive data that we doesn't say how to manage it %+v", data) + + <-stream.Changes() + stream.Next() } + }() + return nil +} - <-stream.Changes() - stream.Next() - } +// Finalize waits for the observer to receive the final property value +// and output the summary of all test executions. +func (so *SummaryObserver) Finalize() error { + <-so.done + return nil } func getIconStatus(numberNotOk int) string { From 7fbf832d69befbd51dc7451fdda6aa65315a887f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 14 Nov 2020 21:04:16 +0100 Subject: [PATCH 012/143] .gitignore: Add .vscode and *.swp --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2a5ae0b..7a7e831 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ /coverage.html /dist/ /logstash-filter-verifier +/.vscode/ *.coverprofile +*.swp From 6505b9f5390d8acac91aaa2e3d4ada3d7c6d203d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 14 Nov 2020 21:09:57 +0100 Subject: [PATCH 013/143] Bump Go version for Travis and release builds to 1.15.5 --- .travis.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index eb4b197..31c1546 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ --- language: go go: - - "1.13.5" + - "1.15.5" script: - make check checktidy test diff --git a/Makefile b/Makefile index 67f8357..8c7bbbb 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ OS_NAME := $(shell uname -s) endif # The Docker image to use when building release images. -GOLANG_DOCKER_IMAGE := golang:1.13.5 +GOLANG_DOCKER_IMAGE := golang:1.15.5 INSTALL := install From a0b2706cf3efe02dc4746654c65f345f5234f591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 14 Nov 2020 21:18:01 +0100 Subject: [PATCH 014/143] Upgrade golangci-lint to 1.32.2 --- Makefile | 2 +- logstash-filter-verifier.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8c7bbbb..bad7a56 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ GOLANGCI_LINT := $(GOBIN)/golangci-lint$(EXEC_SUFFIX) GOVVV := $(GOBIN)/govvv$(EXEC_SUFFIX) OVERALLS := $(GOBIN)/overalls$(EXEC_SUFFIX) -GOLANGCI_LINT_VERSION := v1.27.0 +GOLANGCI_LINT_VERSION := v1.32.2 .PHONY: all all: $(PROGRAM)$(EXEC_SUFFIX) diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go index 4e0d358..f9c4478 100644 --- a/logstash-filter-verifier.go +++ b/logstash-filter-verifier.go @@ -39,7 +39,7 @@ var ( "/usr/share/logstash/bin/logstash", } - // Flags + // Flags. diffCommand = kingpin. Flag("diff-command", "Set the command to run to compare two events. The command will receive the two files to compare as arguments."). Default("diff -u"). @@ -82,7 +82,7 @@ var ( Default("false"). Bool() - // Arguments + // Arguments. testcasePath = kingpin. Arg("testcases", "Test case file or a directory containing one or more test case files."). Required(). From b3f7099419b9c399cf6507989025c59c3fe49b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 21 Nov 2020 20:06:18 +0100 Subject: [PATCH 015/143] Add 1.6.2 changelog entry --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e89ffd9..6f57547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Logstash Filter Verifier Change Log All timestamps are in the Europe/Stockholm timezone. +1.6.2 (2020-11-21) +------------------ + + * Fixed race condition bug that sometimes caused the summary to not + get shown at the end of the execution. + * Upgraded the Go compiler used for release binaries and CI to 1.15.2. + + 1.6.1 (2020-06-30) ------------------ From 61a579889997bdbf308ce9f7f2795bf4c7b00bc5 Mon Sep 17 00:00:00 2001 From: Chris Wiechmann Date: Mon, 20 Jul 2020 11:05:44 +0200 Subject: [PATCH 016/143] Beats fields details added to Readme Fixes #83 --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 5739bc2..8cc51a4 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,41 @@ reading the actual event. Hence we don't need to include it in the expected event either. Additional fields can be ignored with the `ignore` array property in the test case file (see details below). +### Beats messages + +In [Beats](https://www.elastic.co/guide/en/beats/libbeat/current/beats-reference.html) +you can also specify fields to control the behavior of the Logstash pipeline. +An example in Beats config might look like this: +``` +- input_type: log + paths: ["/var/log/work/*.log"] + fields: + type: openlog +- input_type: log + paths: ["/var/log/trace/*.trc"] + fields: + type: trace +``` +The Logstash configuration would then look like this to check the +given field: +``` +if ([fields][type] == "openlog") { + Do something for type openlog +``` +But, in order to test the behavior with LFV you have to give it like so: +``` +{ + "fields": { + "[fields][type]": "openlog" + }, +``` +The reason is, that Beats is inserting by default declared fields under a +root element `fields`, while the LFV is just considering it as a configuration +option. +Alternatively you can tell Beats to insert the configured fields on root: +``` +fields_under_root: true +``` ### JSON messages From c22a4250f3783255e4a2dbcc745df25a9eab60f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Tue, 26 Jan 2021 21:12:59 +0100 Subject: [PATCH 017/143] Add /v2 suffix to package name LFV development on the master branch will now continue with a new package name with a /v2 suffix. The v2 release (issue #96) will contain a bunch of backwards incompatible changes. --- go.mod | 2 +- logstash-filter-verifier.go | 8 ++++---- logstash-filter-verifier_test.go | 2 +- logstash/copyfile_test.go | 2 +- logstash/eventreader_test.go | 2 +- logstash/invocation_test.go | 2 +- logstash/logger.go | 2 +- logstash/pipelineconfigdir_test.go | 2 +- logstash/process_test.go | 2 +- observer/summary.go | 2 +- testcase/testcase.go | 6 +++--- testcase/testcase_test.go | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 6ce91cf..b047c40 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/magnusbaeck/logstash-filter-verifier +module github.com/magnusbaeck/logstash-filter-verifier/v2 go 1.13 diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go index f9c4478..80bf96e 100644 --- a/logstash-filter-verifier.go +++ b/logstash-filter-verifier.go @@ -12,10 +12,10 @@ import ( semver "github.com/Masterminds/semver/v3" "github.com/alecthomas/kingpin" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/logging" - "github.com/magnusbaeck/logstash-filter-verifier/logstash" - lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/observer" - "github.com/magnusbaeck/logstash-filter-verifier/testcase" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/observer" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testcase" "github.com/mattn/go-shellwords" oplogging "github.com/op/go-logging" ) diff --git a/logstash-filter-verifier_test.go b/logstash-filter-verifier_test.go index 796e819..f0b5823 100644 --- a/logstash-filter-verifier_test.go +++ b/logstash-filter-verifier_test.go @@ -9,7 +9,7 @@ import ( "regexp" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" ) func TestFindExecutable(t *testing.T) { diff --git a/logstash/copyfile_test.go b/logstash/copyfile_test.go index de67ce2..37b41a0 100644 --- a/logstash/copyfile_test.go +++ b/logstash/copyfile_test.go @@ -9,7 +9,7 @@ import ( "strconv" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" ) func TestAllFilesExist(t *testing.T) { diff --git a/logstash/eventreader_test.go b/logstash/eventreader_test.go index 0d17465..223d7aa 100644 --- a/logstash/eventreader_test.go +++ b/logstash/eventreader_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" ) func TestReadEvents(t *testing.T) { diff --git a/logstash/invocation_test.go b/logstash/invocation_test.go index c3340f4..f84c1f4 100644 --- a/logstash/invocation_test.go +++ b/logstash/invocation_test.go @@ -11,7 +11,7 @@ import ( "testing" semver "github.com/Masterminds/semver/v3" - "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" ) func TestArgs(t *testing.T) { diff --git a/logstash/logger.go b/logstash/logger.go index be7fcd7..bdcddfc 100644 --- a/logstash/logger.go +++ b/logstash/logger.go @@ -3,7 +3,7 @@ package logstash import ( - "github.com/magnusbaeck/logstash-filter-verifier/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" ) var ( diff --git a/logstash/pipelineconfigdir_test.go b/logstash/pipelineconfigdir_test.go index 332b66d..b53758f 100644 --- a/logstash/pipelineconfigdir_test.go +++ b/logstash/pipelineconfigdir_test.go @@ -13,7 +13,7 @@ import ( "testing" "github.com/breml/logstash-config/ast" - "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" ) func createLogstashConfigWithString(s string) string { diff --git a/logstash/process_test.go b/logstash/process_test.go index c83eadc..512f137 100644 --- a/logstash/process_test.go +++ b/logstash/process_test.go @@ -9,7 +9,7 @@ import ( "os/exec" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" ) type closeableBuffer struct { diff --git a/observer/summary.go b/observer/summary.go index 8cb2ece..9b6b8e5 100644 --- a/observer/summary.go +++ b/observer/summary.go @@ -5,7 +5,7 @@ import ( "sort" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" ) var log = logging.MustGetLogger() diff --git a/testcase/testcase.go b/testcase/testcase.go index 91770a6..772de46 100644 --- a/testcase/testcase.go +++ b/testcase/testcase.go @@ -18,9 +18,9 @@ import ( unjson "github.com/hashicorp/packer/common/json" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/logging" - "github.com/magnusbaeck/logstash-filter-verifier/logstash" - lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/observer" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/observer" "github.com/mikefarah/yaml/v2" ) diff --git a/testcase/testcase_test.go b/testcase/testcase_test.go index 97a8bc0..cd7462b 100644 --- a/testcase/testcase_test.go +++ b/testcase/testcase_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/logstash" + "github.com/magnusbaeck/logstash-filter-verifier/v2/logstash" "github.com/stretchr/testify/assert" ) From ea01afb4552f41745fb199fb1ade6c81dfccc516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Tue, 26 Jan 2021 21:57:18 +0100 Subject: [PATCH 018/143] Make all packages private by moving them to "internal" subdirectory None of the packages in LFV are meant to be public. If needed we might want to turn one or more of them into public APIs but we can do that later without problems. --- {logging => internal/logging}/logger.go | 0 {logstash => internal/logstash}/copyfile.go | 0 {logstash => internal/logstash}/copyfile_test.go | 2 +- {logstash => internal/logstash}/deleted_tempfile.go | 0 {logstash => internal/logstash}/detectversion.go | 0 {logstash => internal/logstash}/detectversion_test.go | 0 {logstash => internal/logstash}/env.go | 0 {logstash => internal/logstash}/env_test.go | 0 {logstash => internal/logstash}/eventreader.go | 0 {logstash => internal/logstash}/eventreader_test.go | 2 +- {logstash => internal/logstash}/fieldset.go | 0 {logstash => internal/logstash}/fieldset_test.go | 0 {logstash => internal/logstash}/invocation.go | 0 {logstash => internal/logstash}/invocation_test.go | 2 +- {logstash => internal/logstash}/logger.go | 2 +- {logstash => internal/logstash}/main_test.go | 0 {logstash => internal/logstash}/parallel_process.go | 0 {logstash => internal/logstash}/parallel_process_test.go | 0 {logstash => internal/logstash}/pipelineconfigdir.go | 0 {logstash => internal/logstash}/pipelineconfigdir_test.go | 2 +- {logstash => internal/logstash}/process.go | 0 {logstash => internal/logstash}/process_test.go | 2 +- {logstash => internal/logstash}/result.go | 0 {observer => internal/observer}/data.go | 0 {observer => internal/observer}/summary.go | 2 +- {testcase => internal/testcase}/discover.go | 0 {testcase => internal/testcase}/discover_test.go | 0 {testcase => internal/testcase}/helper.go | 0 {testcase => internal/testcase}/helper_test.go | 0 {testcase => internal/testcase}/testcase.go | 6 +++--- {testcase => internal/testcase}/testcase_test.go | 2 +- {testhelpers => internal/testhelpers}/compare.go | 0 {testhelpers => internal/testhelpers}/filewithmode.go | 0 logstash-filter-verifier.go | 8 ++++---- logstash-filter-verifier_test.go | 2 +- 35 files changed, 16 insertions(+), 16 deletions(-) rename {logging => internal/logging}/logger.go (100%) rename {logstash => internal/logstash}/copyfile.go (100%) rename {logstash => internal/logstash}/copyfile_test.go (98%) rename {logstash => internal/logstash}/deleted_tempfile.go (100%) rename {logstash => internal/logstash}/detectversion.go (100%) rename {logstash => internal/logstash}/detectversion_test.go (100%) rename {logstash => internal/logstash}/env.go (100%) rename {logstash => internal/logstash}/env_test.go (100%) rename {logstash => internal/logstash}/eventreader.go (100%) rename {logstash => internal/logstash}/eventreader_test.go (94%) rename {logstash => internal/logstash}/fieldset.go (100%) rename {logstash => internal/logstash}/fieldset_test.go (100%) rename {logstash => internal/logstash}/invocation.go (100%) rename {logstash => internal/logstash}/invocation_test.go (98%) rename {logstash => internal/logstash}/logger.go (63%) rename {logstash => internal/logstash}/main_test.go (100%) rename {logstash => internal/logstash}/parallel_process.go (100%) rename {logstash => internal/logstash}/parallel_process_test.go (100%) rename {logstash => internal/logstash}/pipelineconfigdir.go (100%) rename {logstash => internal/logstash}/pipelineconfigdir_test.go (99%) rename {logstash => internal/logstash}/process.go (100%) rename {logstash => internal/logstash}/process_test.go (98%) rename {logstash => internal/logstash}/result.go (100%) rename {observer => internal/observer}/data.go (100%) rename {observer => internal/observer}/summary.go (97%) rename {testcase => internal/testcase}/discover.go (100%) rename {testcase => internal/testcase}/discover_test.go (100%) rename {testcase => internal/testcase}/helper.go (100%) rename {testcase => internal/testcase}/helper_test.go (100%) rename {testcase => internal/testcase}/testcase.go (98%) rename {testcase => internal/testcase}/testcase_test.go (99%) rename {testhelpers => internal/testhelpers}/compare.go (100%) rename {testhelpers => internal/testhelpers}/filewithmode.go (100%) diff --git a/logging/logger.go b/internal/logging/logger.go similarity index 100% rename from logging/logger.go rename to internal/logging/logger.go diff --git a/logstash/copyfile.go b/internal/logstash/copyfile.go similarity index 100% rename from logstash/copyfile.go rename to internal/logstash/copyfile.go diff --git a/logstash/copyfile_test.go b/internal/logstash/copyfile_test.go similarity index 98% rename from logstash/copyfile_test.go rename to internal/logstash/copyfile_test.go index 37b41a0..153d0d2 100644 --- a/logstash/copyfile_test.go +++ b/internal/logstash/copyfile_test.go @@ -9,7 +9,7 @@ import ( "strconv" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) func TestAllFilesExist(t *testing.T) { diff --git a/logstash/deleted_tempfile.go b/internal/logstash/deleted_tempfile.go similarity index 100% rename from logstash/deleted_tempfile.go rename to internal/logstash/deleted_tempfile.go diff --git a/logstash/detectversion.go b/internal/logstash/detectversion.go similarity index 100% rename from logstash/detectversion.go rename to internal/logstash/detectversion.go diff --git a/logstash/detectversion_test.go b/internal/logstash/detectversion_test.go similarity index 100% rename from logstash/detectversion_test.go rename to internal/logstash/detectversion_test.go diff --git a/logstash/env.go b/internal/logstash/env.go similarity index 100% rename from logstash/env.go rename to internal/logstash/env.go diff --git a/logstash/env_test.go b/internal/logstash/env_test.go similarity index 100% rename from logstash/env_test.go rename to internal/logstash/env_test.go diff --git a/logstash/eventreader.go b/internal/logstash/eventreader.go similarity index 100% rename from logstash/eventreader.go rename to internal/logstash/eventreader.go diff --git a/logstash/eventreader_test.go b/internal/logstash/eventreader_test.go similarity index 94% rename from logstash/eventreader_test.go rename to internal/logstash/eventreader_test.go index 223d7aa..d523c8c 100644 --- a/logstash/eventreader_test.go +++ b/internal/logstash/eventreader_test.go @@ -7,7 +7,7 @@ import ( "fmt" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) func TestReadEvents(t *testing.T) { diff --git a/logstash/fieldset.go b/internal/logstash/fieldset.go similarity index 100% rename from logstash/fieldset.go rename to internal/logstash/fieldset.go diff --git a/logstash/fieldset_test.go b/internal/logstash/fieldset_test.go similarity index 100% rename from logstash/fieldset_test.go rename to internal/logstash/fieldset_test.go diff --git a/logstash/invocation.go b/internal/logstash/invocation.go similarity index 100% rename from logstash/invocation.go rename to internal/logstash/invocation.go diff --git a/logstash/invocation_test.go b/internal/logstash/invocation_test.go similarity index 98% rename from logstash/invocation_test.go rename to internal/logstash/invocation_test.go index f84c1f4..b55c98f 100644 --- a/logstash/invocation_test.go +++ b/internal/logstash/invocation_test.go @@ -11,7 +11,7 @@ import ( "testing" semver "github.com/Masterminds/semver/v3" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) func TestArgs(t *testing.T) { diff --git a/logstash/logger.go b/internal/logstash/logger.go similarity index 63% rename from logstash/logger.go rename to internal/logstash/logger.go index bdcddfc..1b5ec1d 100644 --- a/logstash/logger.go +++ b/internal/logstash/logger.go @@ -3,7 +3,7 @@ package logstash import ( - "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) var ( diff --git a/logstash/main_test.go b/internal/logstash/main_test.go similarity index 100% rename from logstash/main_test.go rename to internal/logstash/main_test.go diff --git a/logstash/parallel_process.go b/internal/logstash/parallel_process.go similarity index 100% rename from logstash/parallel_process.go rename to internal/logstash/parallel_process.go diff --git a/logstash/parallel_process_test.go b/internal/logstash/parallel_process_test.go similarity index 100% rename from logstash/parallel_process_test.go rename to internal/logstash/parallel_process_test.go diff --git a/logstash/pipelineconfigdir.go b/internal/logstash/pipelineconfigdir.go similarity index 100% rename from logstash/pipelineconfigdir.go rename to internal/logstash/pipelineconfigdir.go diff --git a/logstash/pipelineconfigdir_test.go b/internal/logstash/pipelineconfigdir_test.go similarity index 99% rename from logstash/pipelineconfigdir_test.go rename to internal/logstash/pipelineconfigdir_test.go index b53758f..3e071c5 100644 --- a/logstash/pipelineconfigdir_test.go +++ b/internal/logstash/pipelineconfigdir_test.go @@ -13,7 +13,7 @@ import ( "testing" "github.com/breml/logstash-config/ast" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) func createLogstashConfigWithString(s string) string { diff --git a/logstash/process.go b/internal/logstash/process.go similarity index 100% rename from logstash/process.go rename to internal/logstash/process.go diff --git a/logstash/process_test.go b/internal/logstash/process_test.go similarity index 98% rename from logstash/process_test.go rename to internal/logstash/process_test.go index 512f137..66e73f7 100644 --- a/logstash/process_test.go +++ b/internal/logstash/process_test.go @@ -9,7 +9,7 @@ import ( "os/exec" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) type closeableBuffer struct { diff --git a/logstash/result.go b/internal/logstash/result.go similarity index 100% rename from logstash/result.go rename to internal/logstash/result.go diff --git a/observer/data.go b/internal/observer/data.go similarity index 100% rename from observer/data.go rename to internal/observer/data.go diff --git a/observer/summary.go b/internal/observer/summary.go similarity index 97% rename from observer/summary.go rename to internal/observer/summary.go index 9b6b8e5..8f38f00 100644 --- a/observer/summary.go +++ b/internal/observer/summary.go @@ -5,7 +5,7 @@ import ( "sort" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) var log = logging.MustGetLogger() diff --git a/testcase/discover.go b/internal/testcase/discover.go similarity index 100% rename from testcase/discover.go rename to internal/testcase/discover.go diff --git a/testcase/discover_test.go b/internal/testcase/discover_test.go similarity index 100% rename from testcase/discover_test.go rename to internal/testcase/discover_test.go diff --git a/testcase/helper.go b/internal/testcase/helper.go similarity index 100% rename from testcase/helper.go rename to internal/testcase/helper.go diff --git a/testcase/helper_test.go b/internal/testcase/helper_test.go similarity index 100% rename from testcase/helper_test.go rename to internal/testcase/helper_test.go diff --git a/testcase/testcase.go b/internal/testcase/testcase.go similarity index 98% rename from testcase/testcase.go rename to internal/testcase/testcase.go index 772de46..83547c9 100644 --- a/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -18,9 +18,9 @@ import ( unjson "github.com/hashicorp/packer/common/json" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" - "github.com/magnusbaeck/logstash-filter-verifier/v2/logstash" - lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/observer" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" "github.com/mikefarah/yaml/v2" ) diff --git a/testcase/testcase_test.go b/internal/testcase/testcase_test.go similarity index 99% rename from testcase/testcase_test.go rename to internal/testcase/testcase_test.go index cd7462b..b17762e 100644 --- a/testcase/testcase_test.go +++ b/internal/testcase/testcase_test.go @@ -11,7 +11,7 @@ import ( "testing" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/logstash" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" "github.com/stretchr/testify/assert" ) diff --git a/testhelpers/compare.go b/internal/testhelpers/compare.go similarity index 100% rename from testhelpers/compare.go rename to internal/testhelpers/compare.go diff --git a/testhelpers/filewithmode.go b/internal/testhelpers/filewithmode.go similarity index 100% rename from testhelpers/filewithmode.go rename to internal/testhelpers/filewithmode.go diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go index 80bf96e..35d7a39 100644 --- a/logstash-filter-verifier.go +++ b/logstash-filter-verifier.go @@ -12,10 +12,10 @@ import ( semver "github.com/Masterminds/semver/v3" "github.com/alecthomas/kingpin" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/logging" - "github.com/magnusbaeck/logstash-filter-verifier/v2/logstash" - lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testcase" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testcase" "github.com/mattn/go-shellwords" oplogging "github.com/op/go-logging" ) diff --git a/logstash-filter-verifier_test.go b/logstash-filter-verifier_test.go index f0b5823..738e951 100644 --- a/logstash-filter-verifier_test.go +++ b/logstash-filter-verifier_test.go @@ -9,7 +9,7 @@ import ( "regexp" "testing" - "github.com/magnusbaeck/logstash-filter-verifier/v2/testhelpers" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) func TestFindExecutable(t *testing.T) { From 4025bedf56efc5946a792bd60af1e12f481f1192 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 2 Feb 2021 18:44:27 +0100 Subject: [PATCH 019/143] Initial refactoring for LFV v2.0 Replace flag processing with spf13/cobra to prepare for sub commands. Add spf13/viper for support of config files. Move v1 functionality to sub-command `standalone`. Abstract logging with interface. --- Makefile | 3 +- go.mod | 8 +- go.sum | 144 ++++++- internal/app/app.go | 92 +++++ internal/app/standalone.go | 96 +++++ internal/app/standalone/standalone.go | 328 +++++++++++++++ .../app/standalone/standalone_test.go | 18 +- internal/logging/logger.go | 30 +- logstash-filter-verifier.go | 384 ------------------ main.go | 16 + 10 files changed, 716 insertions(+), 403 deletions(-) create mode 100644 internal/app/app.go create mode 100644 internal/app/standalone.go create mode 100644 internal/app/standalone/standalone.go rename logstash-filter-verifier_test.go => internal/app/standalone/standalone_test.go (78%) delete mode 100644 logstash-filter-verifier.go create mode 100644 main.go diff --git a/Makefile b/Makefile index bad7a56..c45c385 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,8 @@ checktidy: .PHONY: clean clean: - rm -f $(PROGRAM)$(EXEC_SUFFIX) $(GOCOV) $(GOCOV_HTML) $(GOLANGCI_LINT) $(GPM) $(OVERALLS) + rm -f $(PROGRAM)$(EXEC_SUFFIX) + rm -rf $(GOBIN) rm -rf dist .PHONY: install diff --git a/go.mod b/go.mod index b047c40..897628c 100644 --- a/go.mod +++ b/go.mod @@ -5,19 +5,19 @@ go 1.13 require ( github.com/Masterminds/semver/v3 v3.0.1 github.com/ahmetb/govvv v0.3.0 - github.com/alecthomas/kingpin v2.2.6+incompatible - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect - github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/axw/gocov v1.0.0 github.com/breml/logstash-config v0.1.0 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/hashicorp/packer v1.4.4 github.com/imkira/go-observer v1.0.3 - github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82 + github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b github.com/mattn/go-shellwords v1.0.6 github.com/mikefarah/yaml/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 + github.com/spf13/cobra v1.1.1 + github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.4.0 github.com/yookoala/realpath v1.0.0 // indirect + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect ) diff --git a/go.sum b/go.sum index 3112172..3a29e6f 100644 --- a/go.sum +++ b/go.sum @@ -4,31 +4,34 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE= github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591/go.mod h1:EHGzQGbwozJBj/4qj3WGrTJ0FqjgOTOxLQ0VNWvPn08= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/ahmetb/govvv v0.3.0 h1:YGLGwEyiUwHFy5eh/RUhdupbuaCGBYn5T5GWXp+WJB0= github.com/ahmetb/govvv v0.3.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= -github.com/alecthomas/kingpin v2.2.6+incompatible h1:5svnBTFgJjZvGKyYBtMB0+m5wvrbUHiqye8wRJMlnYI= -github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -47,27 +50,39 @@ github.com/aws/aws-sdk-go v1.16.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.24.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/axw/gocov v1.0.0 h1:YsqYR66hUmilVr23tu8USgnJIJvnwh3n7j5zRn7x4LU= github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/breml/logstash-config v0.1.0 h1:fym6y8WJpJxEicmwUBhdzH2VSHzaFoUf2jA2tOTV7Tw= github.com/breml/logstash-config v0.1.0/go.mod h1:ZBjsBLmordIP3WF/sMfUlRkwBhrHgw6njXx2m7P4Cmo= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437/go.mod h1:PRcPVAAma6zcLpFd4GZrjR/MRpood3TamjKI2m/z/Uw= github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8/go.mod h1:/YnlngP1PARC0SKAZx6kaAEMOp8bNTQGqS+Ka3MctNI= github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dnaeon/go-vcr v1.0.0/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/docker v0.0.0-20180422163414-57142e89befe h1:VW8TnWi0CZgg7oCv0wH6evNwkzcJg/emnw4HrVIWws4= github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= @@ -76,19 +91,28 @@ github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSY github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 h1:mKMuZuxwRig082824nGPyH0xVjaKDPjf41kI9W2aTk0= github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018/go.mod h1:UqxAgEOt89sCiXlrc/ycnx00LVvUO/eS8tMUkWX4R7w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -101,23 +125,34 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-getter v1.3.1-0.20190906090232-a0f878cb75da/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -125,6 +160,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79/go.mod h1:09jT3Y/OIsjTjQ2+3bkVNPDKqWcGIYYvjB2BEKVUdvc= github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -136,6 +172,7 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= @@ -150,13 +187,19 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs= github.com/imkira/go-observer v1.0.3 h1:l45TYAEeAB4L2xF6PR2gRLn2NE5tYhudh33MLmC7B80= github.com/imkira/go-observer v1.0.3/go.mod h1:zLzElv2cGTHufQG17IEILJMPDg32TD85fFgKyFv00wU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -167,6 +210,7 @@ github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f/go.mod h1:+ZoRqAPR github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -174,11 +218,13 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= -github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82 h1:pMb6HhXFlcC2qHIx7Z++2nRhgQ+5kQx8wLbMqBpuU6U= -github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= +github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b h1:5Wc/N1FIBnExmX0/SEdKe0A0COvdJc3rCGHQ7s1oBPQ= +github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -186,6 +232,7 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mikefarah/yaml/v2 v2.4.0 h1:eYqfooY0BnvKTJxr7+ABJs13n3dg9n347GScDaU2Lww= @@ -193,6 +240,7 @@ github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RR github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -200,6 +248,8 @@ github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557/go.mod h1:QuAqW7/z+iv6aWFJdrA8kCbsF0OOJVKCICqTcYBexuY= github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -207,8 +257,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U= github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -220,6 +272,8 @@ github.com/oracle/oci-go-sdk v1.8.0/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBc github.com/outscale/osc-go v0.0.1/go.mod h1:hJLmXzqU/t07qQYh90I0TqZzu9s85Zs6FMrxk3ukiFM= github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= @@ -229,8 +283,19 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible/go.mod h1:T3/WrziK7fYH3C8ilAFAHe99R452/IzIG3YYkqaOFeQ= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= @@ -239,10 +304,32 @@ github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:Xj github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -250,17 +337,22 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/tencentcloud/tencentcloud-sdk-go v3.0.71+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c= github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0/go.mod h1:sBh287mCRwCz6zyXHMmw7sSZGPohVpnx+o+OY4M+i3A= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854= github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ= github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -270,16 +362,26 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -295,6 +397,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -310,6 +413,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -318,11 +422,16 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -331,16 +440,25 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -352,18 +470,24 @@ google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/h2non/gock.v1 v1.0.12/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -371,7 +495,11 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..7d3b19a --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,92 @@ +package app + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +const ( + exitCodeNormal = 0 + exitCodeError = 1 +) + +func Execute(version string, stdout, stderr io.Writer) int { + log := logging.MustGetLogger() + viper.Set("logger", log) + + // Initialize config + viper.SetConfigName("logstash-filter-verifier") // name of config file (without extension) + viper.AddConfigPath("/etc/logstash-filter-verifier/") // path to look for the config file in + viper.AddConfigPath("$HOME/.logstash-filter-verifier") // call multiple times to add many search paths + viper.AddConfigPath(".") // optionally look for config in the working directory + + // Setup default values + viper.SetDefault("loglevel", "WARNING") + viper.SetDefault("socket", "/tmp/logstash-filter-verifier.sock") + viper.SetDefault("pipeline", "/etc/logstash/pipelines.yml") + viper.SetDefault("logstash.path", "") + + // Read config + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + log.Errorf("Error processing config file: %v", err) + return exitCodeError + } + } + + rootCmd := makeRootCmd(version) + rootCmd.SetOut(stdout) + rootCmd.SetErr(stderr) + rootCmd.SilenceUsage = true + + if err := rootCmd.Execute(); err != nil { + prefixedUserError("error: %v", err) + return exitCodeError + } + + return exitCodeNormal +} + +func makeRootCmd(version string) *cobra.Command { + rootCmd := &cobra.Command{ + Use: "logstash-filter-verifier", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + logging.SetLevel(viper.GetString("loglevel")) + }, + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, + SilenceErrors: true, + Version: version, + } + + rootCmd.InitDefaultVersionFlag() + + rootCmd.PersistentFlags().String("loglevel", "WARNING", "Set the desired level of logging (one of: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG).") + _ = viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel")) + + rootCmd.AddCommand(makeStandaloneCmd()) + + return rootCmd +} + +// prefixedUserError prints an error message to stderr and prefixes it +// with the name of the program file (e.g. "logstash-filter-verifier: +// something bad happened."). +func prefixedUserError(format string, a ...interface{}) { + basename := filepath.Base(os.Args[0]) + message := fmt.Sprintf(format, a...) + if strings.HasSuffix(message, "\n") { + fmt.Fprintf(os.Stderr, "%s: %s", basename, message) + } else { + fmt.Fprintf(os.Stderr, "%s: %s\n", basename, message) + } +} diff --git a/internal/app/standalone.go b/internal/app/standalone.go new file mode 100644 index 0000000..636c450 --- /dev/null +++ b/internal/app/standalone.go @@ -0,0 +1,96 @@ +package app + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/standalone" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func makeStandaloneCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "standalone [] ...", + Short: "Run logstash-filter-verifier in standalone mode", + RunE: runE, + Args: validateArgs, + SilenceErrors: true, + } + + // TODO: This flag makes sense with daemon mode test command as well. + cmd.Flags().String("diff-command", "diff -u", "Set the command to run to compare two events. The command will receive the two files to compare as arguments.") + _ = viper.BindPFlag("diff-command", cmd.Flags().Lookup("diff-command")) + + // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().StringSlice("keep-env", []string{"PATH"}, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") + _ = viper.BindPFlag("keep-envs", cmd.Flags().Lookup("keep-env")) + + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().StringSlice("logstash-arg", nil, "Command line arguments, which are passed to Logstash. Flag and value have to be provided as a flag each, e.g.: --logstash-arg=-n --logstash-arg=InstanceName") + _ = viper.BindPFlag("logstash-args", cmd.Flags().Lookup("logstash-arg")) + + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().Bool("logstash-output", false, "Print the debug output of logstash.") + _ = viper.BindPFlag("logstash-output", cmd.Flags().Lookup("logstash-output")) + + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().StringSlice("logstash-path", nil, "Add a path to the list of Logstash executable paths that will be tried in order (first match is used).") + _ = viper.BindPFlag("logstash-paths", cmd.Flags().Lookup("logstash-path")) + + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().String("logstash-version", "auto", "The version of Logstash that's being targeted.") + _ = viper.BindPFlag("logstash-version", cmd.Flags().Lookup("logstash-version")) + + cmd.Flags().Bool("sockets", false, "Use Unix domain sockets for the communication with Logstash.") + _ = viper.BindPFlag("sockets", cmd.Flags().Lookup("sockets")) + + cmd.Flags().Duration("sockets-timeout", 60*time.Second, "Timeout (duration) for the communication with Logstash via Unix domain sockets. Has no effect unless --sockets is used.") + _ = viper.BindPFlag("sockets-timeout", cmd.Flags().Lookup("sockets-timeout")) + + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().Bool("quiet", false, "Omit test progress messages and event diffs.") + _ = viper.BindPFlag("quiet", cmd.Flags().Lookup("quiet")) + + return cmd +} + +func runE(_ *cobra.Command, args []string) error { + s := standalone.New( + viper.GetBool("quiet"), + viper.GetString("diff-command"), + args[0], + viper.GetStringSlice("keep-envs"), + viper.GetStringSlice("logstash-paths"), + viper.GetString("logstash-version"), + viper.GetStringSlice("logstash-args"), + viper.GetBool("logstash-output"), + args[1:], + viper.GetBool("sockets"), + viper.GetDuration("sockets-timeout"), + viper.Get("logger").(logging.Logger), + ) + + return s.Run() +} + +func validateArgs(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("required argument 'testcases' not provided, try --help") + } + if len(args) < 2 { + return errors.New("required argument 'config' not provided, try --help") + } + for _, arg := range args { + _, err := os.Stat(arg) + if os.IsNotExist(err) { + return fmt.Errorf("path '%s' does not exist, try --help", arg) + } + } + return nil +} diff --git a/internal/app/standalone/standalone.go b/internal/app/standalone/standalone.go new file mode 100644 index 0000000..b600886 --- /dev/null +++ b/internal/app/standalone/standalone.go @@ -0,0 +1,328 @@ +package standalone + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/imkira/go-observer" + "github.com/mattn/go-shellwords" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testcase" +) + +const autoVersion = "auto" + +var ( + defaultKeptEnvVars = []string{ + "PATH", + } + defaultLogstashPaths = []string{ + "/opt/logstash/bin/logstash", + "/usr/share/logstash/bin/logstash", + } +) + +type Standalone struct { + quiet bool + diffCommand string + testcasePath string + keptEnvVars []string + logstashPaths []string + logstashVersion string + logstashArgs []string + logstashOutput bool + configPaths []string + unixSockets bool + unixSocketCommTimeout time.Duration + + log logging.Logger +} + +func New( + quiet bool, + diffCommand string, + testcasePath string, + keptEnvVars []string, + logstashPaths []string, + logstashVersion string, + logstashArgs []string, + logstashOutput bool, + configPaths []string, + unixSockets bool, + unixSocketCommTimeout time.Duration, + log logging.Logger, +) Standalone { + return Standalone{ + quiet: quiet, + diffCommand: diffCommand, + testcasePath: testcasePath, + keptEnvVars: keptEnvVars, + logstashPaths: logstashPaths, + logstashVersion: logstashVersion, + logstashArgs: logstashArgs, + logstashOutput: logstashOutput, + configPaths: configPaths, + unixSockets: unixSockets, + unixSocketCommTimeout: unixSocketCommTimeout, + log: log, + } +} + +func (s Standalone) Run() error { + var status bool + + // Set up observers + observers := make([]lfvobserver.Interface, 0) + liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) + if !s.quiet { + observers = append(observers, lfvobserver.NewSummaryObserver(liveObserver)) + } + for _, obs := range observers { + if err := obs.Start(); err != nil { + return fmt.Errorf("Initialization error: %s", err) + } + } + + diffCmd, err := shellwords.NewParser().Parse(s.diffCommand) + if err != nil { + return fmt.Errorf("Error parsing diff command %q: %s", s.diffCommand, err) + } + + tests, err := testcase.DiscoverTests(s.testcasePath) + if err != nil { + return fmt.Errorf(err.Error()) + } + + allKeptEnvVars := append(defaultKeptEnvVars, s.keptEnvVars...) + + logstashPath, err := s.findExecutable(append(s.logstashPaths, defaultLogstashPaths...)) + if err != nil { + return fmt.Errorf("Error locating Logstash: %s", err) + } + + var targetVersion *semver.Version + if s.logstashVersion == autoVersion { + targetVersion, err = logstash.DetectVersion(logstashPath, allKeptEnvVars) + if err != nil { + return fmt.Errorf("Could not auto-detect the Logstash version: %s", err) + } + } else { + targetVersion, err = semver.NewVersion(s.logstashVersion) + if err != nil { + return fmt.Errorf("The given Logstash version %q could not be parsed as a version number (%s).", s.logstashVersion, err) + } + } + + inv, err := logstash.NewInvocation(logstashPath, s.logstashArgs, targetVersion, s.configPaths...) + if err != nil { + return fmt.Errorf("An error occurred while setting up the Logstash environment: %s", err) + } + defer inv.Release() + if s.unixSockets { + if runtime.GOOS == "windows" { + return fmt.Errorf("Use of Unix domain sockets for communication with Logstash is not supported on Windows.") + } + fmt.Println("Use Unix domain sockets.") + if status, err = s.runParallelTests(inv, tests, diffCmd, allKeptEnvVars, liveObserver); err != nil { + return fmt.Errorf(err.Error()) + } + } else { + if status, err = s.runTests(inv, tests, diffCmd, allKeptEnvVars, liveObserver); err != nil { + return fmt.Errorf(err.Error()) + } + } + + liveObserver.Update(lfvobserver.TestExecutionEnd{}) + + for _, obs := range observers { + if err := obs.Finalize(); err != nil { + return fmt.Errorf(err.Error()) + } + } + + if status { + return nil + } + return errors.New("failed test cases") +} + +// findExecutable examines the passed file paths and returns the first +// one that is an existing executable file. +func (s Standalone) findExecutable(paths []string) (string, error) { + for _, p := range paths { + stat, err := os.Stat(p) + if err != nil { + s.log.Debugf("Logstash path candidate rejected: %s", err) + continue + } + if !stat.Mode().IsRegular() { + s.log.Debugf("Logstash path candidate not a regular file: %s", p) + continue + } + if runtime.GOOS != "windows" && stat.Mode().Perm()&0111 != 0111 { + s.log.Debugf("Logstash path candidate not an executable file: %s", p) + continue + } + s.log.Debugf("Logstash path candidate accepted: %s", p) + return p, nil + } + return "", fmt.Errorf("no existing executable found among candidates: %s", strings.Join(paths, ", ")) +} + +// runTests runs Logstash with a set of configuration files against a +// slice of test cases and compares the actual events against the +// expected set. Returns a bool that indicates whether all tests pass +// and an error that indicates a problem running the tests. +func (s Standalone) runTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveObserver observer.Property) (bool, error) { + ok := true + for _, t := range tests { + fmt.Printf("Running tests in %s...\n", filepath.Base(t.File)) + p, err := logstash.NewProcess(inv, t.Codec, t.InputFields, keptEnvVars) + if err != nil { + return false, err + } + defer p.Release() + if err = p.Start(); err != nil { + return false, err + } + + for _, line := range t.InputLines { + _, err = p.Input.Write([]byte(line + "\n")) + if err != nil { + return false, err + } + } + if err = p.Input.Close(); err != nil { + return false, err + } + + result, err := p.Wait() + if err != nil || s.logstashOutput { + message := getLogstashOutputMessage(result.Output, result.Log) + if err != nil { + return false, fmt.Errorf("Error running Logstash: %s.%s", err, message) + } + userError("%s", message) + } + + currentOk, err := t.Compare(result.Events, diffCommand, liveObserver) + if err != nil { + return false, err + } + if !currentOk { + ok = false + } + } + + return ok, nil +} + +// runParallelTests runs multiple set of configuration in a single +// instance of Logstash against a slice of test cases and compares +// the actual events against the expected set. Returns a bool that +// indicates whether all tests pass and an error that indicates a +// problem running the tests. +func (s Standalone) runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveProducer observer.Property) (bool, error) { + testStreams := make([]*logstash.TestStream, 0, len(tests)) + + badCodecs := map[string]string{ + "json": "json_lines", + "plain": "line", + } + for _, t := range tests { + if repl, ok := badCodecs[t.Codec]; ok { + s.log.Warning( + "The testcase file %q uses the %q codec. That codec "+ + "will most likely not work as expected when --sockets is used. Try %q instead.", + t.File, t.Codec, repl) + } + } + + for _, t := range tests { + ts, err := logstash.NewTestStream(t.Codec, t.InputFields, s.unixSocketCommTimeout) + if err != nil { + logstash.CleanupTestStreams(testStreams) + return false, err + } + testStreams = append(testStreams, ts) + } + + p, err := logstash.NewParallelProcess(inv, testStreams, keptEnvVars) + if err != nil { + return false, err + } + defer p.Release() + if err = p.Start(); err != nil { + return false, err + } + + for i, t := range tests { + for _, line := range t.InputLines { + _, err = testStreams[i].Write([]byte(line + "\n")) + if err != nil { + return false, err + } + } + + if err = testStreams[i].Close(); err != nil { + return false, err + } + } + + result, err := p.Wait() + if err != nil || s.logstashOutput { + message := getLogstashOutputMessage(result.Output, result.Log) + if err != nil { + return false, fmt.Errorf("Error running Logstash: %s.%s", err, message) + } + userError("%s", message) + } + ok := true + for i, t := range tests { + currentOk, err := t.Compare(result.Events[i], diffCommand, liveProducer) + if err != nil { + userError("Testcase %s failed, continuing with the rest: %s", filepath.Base(t.File), err) + } + if !currentOk { + ok = false + } + } + + return ok, nil +} + +// getLogstashOutputMessage examines the test result and prepares a +// message describing the process's output, log output, or neither +// (resulting in an empty string). +func getLogstashOutputMessage(output string, log string) string { + var message string + if output != "" { + message += fmt.Sprintf("\nProcess output:\n%s", output) + } else { + message += "\nThe process wrote nothing to stdout or stderr." + } + if log != "" { + message += fmt.Sprintf("\nLog:\n%s", log) + } else { + message += "\nThe process wrote nothing to its logfile." + } + return message +} + +// userError prints an error message to stderr. +func userError(format string, a ...interface{}) { + if strings.HasSuffix(format, "\n") { + fmt.Fprintf(os.Stderr, format, a...) + } else { + fmt.Fprintf(os.Stderr, format+"\n", a...) + } +} diff --git a/logstash-filter-verifier_test.go b/internal/app/standalone/standalone_test.go similarity index 78% rename from logstash-filter-verifier_test.go rename to internal/app/standalone/standalone_test.go index 738e951..446a47c 100644 --- a/logstash-filter-verifier_test.go +++ b/internal/app/standalone/standalone_test.go @@ -1,6 +1,6 @@ // Copyright (c) 2017 Magnus Bäck -package main +package standalone import ( "io/ioutil" @@ -128,7 +128,8 @@ func TestFindExecutable(t *testing.T) { absInputs[i] = filepath.Join(tempdir, p) } - result, err := findExecutable(absInputs) + standalone := New(false, "", "", nil, nil, "", nil, false, nil, false, 0, nilLogger{}) + result, err := standalone.findExecutable(absInputs) if err == nil && c.errorRegexp != nil { t.Errorf("Test %d: Expected failure, got success.", i) } else if err != nil && c.errorRegexp == nil { @@ -140,3 +141,16 @@ func TestFindExecutable(t *testing.T) { } } } + +type nilLogger struct{} + +func (n nilLogger) Debug(args ...interface{}) {} +func (n nilLogger) Debugf(format string, args ...interface{}) {} +func (n nilLogger) Error(args ...interface{}) {} +func (n nilLogger) Errorf(format string, args ...interface{}) {} +func (n nilLogger) Fatal(args ...interface{}) {} +func (n nilLogger) Fatalf(format string, args ...interface{}) {} +func (n nilLogger) Info(args ...interface{}) {} +func (n nilLogger) Infof(format string, args ...interface{}) {} +func (n nilLogger) Warning(args ...interface{}) {} +func (n nilLogger) Warningf(format string, args ...interface{}) {} diff --git a/internal/logging/logger.go b/internal/logging/logger.go index 1334ea8..fefaa4f 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -3,23 +3,45 @@ package logging import ( + "os" + oplogging "github.com/op/go-logging" ) +type Logger interface { + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) + Info(args ...interface{}) + Infof(format string, args ...interface{}) + Warning(args ...interface{}) + Warningf(format string, args ...interface{}) +} + const ( logModule = "logstash-filter-verifier" ) var ( - log = oplogging.MustGetLogger(logModule) + log = oplogging.MustGetLogger(logModule) + backend = oplogging.AddModuleLevel(oplogging.NewLogBackend(os.Stderr, "", 0)) ) // MustGetLogger returns the application's default logger. -func MustGetLogger() *oplogging.Logger { +func MustGetLogger() Logger { + log.SetBackend(backend) return log } // SetLevel sets the desired log level for the default logger. -func SetLevel(level oplogging.Level) { - oplogging.SetLevel(level, logModule) +func SetLevel(loglevel string) { + level, err := oplogging.LogLevel(loglevel) + if err != nil { + level = oplogging.WARNING + log.Warning("invalid log level, fall back to WARNING") + } + backend.SetLevel(level, logModule) } diff --git a/logstash-filter-verifier.go b/logstash-filter-verifier.go deleted file mode 100644 index 35d7a39..0000000 --- a/logstash-filter-verifier.go +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (c) 2015-2019 Magnus Bäck - -package main - -import ( - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - semver "github.com/Masterminds/semver/v3" - "github.com/alecthomas/kingpin" - "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" - lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testcase" - "github.com/mattn/go-shellwords" - oplogging "github.com/op/go-logging" -) - -var ( - // GitSummary contains "git describe" output and is automatically - // populated via linker options when building with govvv. - GitSummary = "(unknown)" - - log = logging.MustGetLogger() - - loglevels = []string{"CRITICAL", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"} - - autoVersion = "auto" - - defaultKeptEnvVars = []string{ - "PATH", - } - defaultLogstashPaths = []string{ - "/opt/logstash/bin/logstash", - "/usr/share/logstash/bin/logstash", - } - - // Flags. - diffCommand = kingpin. - Flag("diff-command", "Set the command to run to compare two events. The command will receive the two files to compare as arguments."). - Default("diff -u"). - String() - keptEnvVars = kingpin. - Flag("keep-env", fmt.Sprintf("Add this environment variable to the list of variables that will be preserved from the calling process's environment. Initial list of variables: %s", strings.Join(defaultKeptEnvVars, ", "))). - PlaceHolder("VARNAME"). - Strings() - loglevel = kingpin. - Flag("loglevel", fmt.Sprintf("Set the desired level of logging (one of: %s).", strings.Join(loglevels, ", "))). - Default("WARNING"). - Enum(loglevels...) - logstashArgs = kingpin. - Flag("logstash-arg", "Command line arguments, which are passed to Logstash. Flag and value have to be provided as a flag each, e.g.: --logstash-arg=-n --logstash-arg=InstanceName"). - PlaceHolder("ARG"). - Strings() - logstashOutput = kingpin. - Flag("logstash-output", "Print the debug output of logstash."). - Default("false"). - Bool() - logstashPaths = kingpin. - Flag("logstash-path", "Add a path to the list of Logstash executable paths that will be tried in order (first match is used)."). - PlaceHolder("PATH"). - Strings() - logstashVersion = kingpin. - Flag("logstash-version", "The version of Logstash that's being targeted."). - PlaceHolder("VERSION"). - Default(autoVersion). - String() - unixSockets = kingpin. - Flag("sockets", "Use Unix domain sockets for the communication with Logstash."). - Default("false"). - Bool() - unixSocketCommTimeout = kingpin. - Flag("sockets-timeout", "Timeout (duration) for the communication with Logstash via Unix domain sockets. Has no effect unless --sockets is used."). - Default("60s"). - Duration() - quiet = kingpin. - Flag("quiet", "Omit test progress messages and event diffs."). - Default("false"). - Bool() - - // Arguments. - testcasePath = kingpin. - Arg("testcases", "Test case file or a directory containing one or more test case files."). - Required(). - ExistingFileOrDir() - configPaths = kingpin. - Arg("config", "Logstash configuration file or a directory containing one or more configuration files."). - Required(). - ExistingFilesOrDirs() -) - -// findExecutable examines the passed file paths and returns the first -// one that is an existing executable file. -func findExecutable(paths []string) (string, error) { - for _, p := range paths { - stat, err := os.Stat(p) - if err != nil { - log.Debugf("Logstash path candidate rejected: %s", err) - continue - } - if !stat.Mode().IsRegular() { - log.Debugf("Logstash path candidate not a regular file: %s", p) - continue - } - if runtime.GOOS != "windows" && stat.Mode().Perm()&0111 != 0111 { - log.Debugf("Logstash path candidate not an executable file: %s", p) - continue - } - log.Debugf("Logstash path candidate accepted: %s", p) - return p, nil - } - return "", fmt.Errorf("no existing executable found among candidates: %s", strings.Join(paths, ", ")) -} - -// runTests runs Logstash with a set of configuration files against a -// slice of test cases and compares the actual events against the -// expected set. Returns a bool that indicates whether all tests pass -// and an error that indicates a problem running the tests. -func runTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveObserver observer.Property) (bool, error) { - ok := true - for _, t := range tests { - fmt.Printf("Running tests in %s...\n", filepath.Base(t.File)) - p, err := logstash.NewProcess(inv, t.Codec, t.InputFields, keptEnvVars) - if err != nil { - return false, err - } - defer p.Release() - if err = p.Start(); err != nil { - return false, err - } - - for _, line := range t.InputLines { - _, err = p.Input.Write([]byte(line + "\n")) - if err != nil { - return false, err - } - } - if err = p.Input.Close(); err != nil { - return false, err - } - - result, err := p.Wait() - if err != nil || *logstashOutput { - message := getLogstashOutputMessage(result.Output, result.Log) - if err != nil { - return false, fmt.Errorf("Error running Logstash: %s.%s", err, message) - } - userError("%s", message) - } - - currentOk, err := t.Compare(result.Events, diffCommand, liveObserver) - if err != nil { - return false, err - } - if !currentOk { - ok = false - } - } - - return ok, nil -} - -// runParallelTests runs multiple set of configuration in a single -// instance of Logstash against a slice of test cases and compares -// the actual events against the expected set. Returns a bool that -// indicates whether all tests pass and an error that indicates a -// problem running the tests. -func runParallelTests(inv *logstash.Invocation, tests []testcase.TestCaseSet, diffCommand []string, keptEnvVars []string, liveProducer observer.Property) (bool, error) { - testStreams := make([]*logstash.TestStream, 0, len(tests)) - - badCodecs := map[string]string{ - "json": "json_lines", - "plain": "line", - } - for _, t := range tests { - if repl, ok := badCodecs[t.Codec]; ok { - log.Warning( - "The testcase file %q uses the %q codec. That codec "+ - "will most likely not work as expected when --sockets is used. Try %q instead.", - t.File, t.Codec, repl) - } - } - - for _, t := range tests { - ts, err := logstash.NewTestStream(t.Codec, t.InputFields, *unixSocketCommTimeout) - if err != nil { - logstash.CleanupTestStreams(testStreams) - return false, err - } - testStreams = append(testStreams, ts) - } - - p, err := logstash.NewParallelProcess(inv, testStreams, keptEnvVars) - if err != nil { - return false, err - } - defer p.Release() - if err = p.Start(); err != nil { - return false, err - } - - for i, t := range tests { - for _, line := range t.InputLines { - _, err = testStreams[i].Write([]byte(line + "\n")) - if err != nil { - return false, err - } - } - - if err = testStreams[i].Close(); err != nil { - return false, err - } - } - - result, err := p.Wait() - if err != nil || *logstashOutput { - message := getLogstashOutputMessage(result.Output, result.Log) - if err != nil { - return false, fmt.Errorf("Error running Logstash: %s.%s", err, message) - } - userError("%s", message) - } - ok := true - for i, t := range tests { - currentOk, err := t.Compare(result.Events[i], diffCommand, liveProducer) - if err != nil { - userError("Testcase %s failed, continuing with the rest: %s", filepath.Base(t.File), err) - } - if !currentOk { - ok = false - } - } - - return ok, nil -} - -// getLogstashOutputMessage examines the test result and prepares a -// message describing the process's output, log output, or neither -// (resulting in an empty string). -func getLogstashOutputMessage(output string, log string) string { - var message string - if output != "" { - message += fmt.Sprintf("\nProcess output:\n%s", output) - } else { - message += "\nThe process wrote nothing to stdout or stderr." - } - if log != "" { - message += fmt.Sprintf("\nLog:\n%s", log) - } else { - message += "\nThe process wrote nothing to its logfile." - } - return message -} - -// prefixedUserError prints an error message to stderr and prefixes it -// with the name of the program file (e.g. "logstash-filter-verifier: -// something bad happened."). -func prefixedUserError(format string, a ...interface{}) { - basename := filepath.Base(os.Args[0]) - message := fmt.Sprintf(format, a...) - if strings.HasSuffix(message, "\n") { - fmt.Fprintf(os.Stderr, "%s: %s", basename, message) - } else { - fmt.Fprintf(os.Stderr, "%s: %s\n", basename, message) - } -} - -// userError prints an error message to stderr. -func userError(format string, a ...interface{}) { - if strings.HasSuffix(format, "\n") { - fmt.Fprintf(os.Stderr, format, a...) - } else { - fmt.Fprintf(os.Stderr, format+"\n", a...) - } -} - -// mainEntrypoint functions as the main function of the program and -// returns the desired exit code. -func mainEntrypoint() int { - kingpin.Version(fmt.Sprintf("%s %s", kingpin.CommandLine.Name, GitSummary)) - kingpin.Parse() - - var status bool - - level, err := oplogging.LogLevel(*loglevel) - if err != nil { - prefixedUserError("Bad loglevel: %s", *loglevel) - return 1 - } - logging.SetLevel(level) - - // Set up observers - observers := make([]lfvobserver.Interface, 0) - liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) - if !*quiet { - observers = append(observers, lfvobserver.NewSummaryObserver(liveObserver)) - } - for _, obs := range observers { - if err := obs.Start(); err != nil { - userError("Initialization error: %s", err) - return 1 - } - } - - diffCmd, err := shellwords.NewParser().Parse(*diffCommand) - if err != nil { - userError("Error parsing diff command %q: %s", *diffCommand, err) - return 1 - } - - tests, err := testcase.DiscoverTests(*testcasePath) - if err != nil { - userError(err.Error()) - return 1 - } - - allKeptEnvVars := append(defaultKeptEnvVars, *keptEnvVars...) - - logstashPath, err := findExecutable(append(*logstashPaths, defaultLogstashPaths...)) - if err != nil { - userError("Error locating Logstash: %s", err) - return 1 - } - - var targetVersion *semver.Version - if *logstashVersion == autoVersion { - targetVersion, err = logstash.DetectVersion(logstashPath, allKeptEnvVars) - if err != nil { - userError("Could not auto-detect the Logstash version: %s", err) - return 1 - } - } else { - targetVersion, err = semver.NewVersion(*logstashVersion) - if err != nil { - userError("The given Logstash version %q could not be parsed as a version number (%s).", *logstashVersion, err) - return 1 - } - } - - inv, err := logstash.NewInvocation(logstashPath, *logstashArgs, targetVersion, *configPaths...) - if err != nil { - userError("An error occurred while setting up the Logstash environment: %s", err) - return 1 - } - defer inv.Release() - if *unixSockets { - if runtime.GOOS == "windows" { - userError("Use of Unix domain sockets for communication with Logstash is not supported on Windows.") - return 1 - } - fmt.Println("Use Unix domain sockets.") - if status, err = runParallelTests(inv, tests, diffCmd, allKeptEnvVars, liveObserver); err != nil { - userError(err.Error()) - return 1 - } - } else { - if status, err = runTests(inv, tests, diffCmd, allKeptEnvVars, liveObserver); err != nil { - userError(err.Error()) - return 1 - } - } - - liveObserver.Update(lfvobserver.TestExecutionEnd{}) - - for _, obs := range observers { - if err := obs.Finalize(); err != nil { - userError(err.Error()) - return 1 - } - } - - if status { - return 0 - } - return 1 -} - -func main() { - os.Exit(mainEntrypoint()) -} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c2746b7 --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "os" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app" +) + +// GitSummary contains "git describe" output and is automatically +// populated via linker options when building with govvv. +var GitSummary = "(unknown)" + +func main() { + exitCode := app.Execute(GitSummary, os.Stdout, os.Stderr) + os.Exit(exitCode) +} From c4a4790ee7f92f3b78dc63c6c89904d6b122e20a Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 8 Feb 2021 07:40:14 +0100 Subject: [PATCH 020/143] Format quoted argument with %q --- internal/app/standalone.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/app/standalone.go b/internal/app/standalone.go index 636c450..e4267b6 100644 --- a/internal/app/standalone.go +++ b/internal/app/standalone.go @@ -89,7 +89,7 @@ func validateArgs(cmd *cobra.Command, args []string) error { for _, arg := range args { _, err := os.Stat(arg) if os.IsNotExist(err) { - return fmt.Errorf("path '%s' does not exist, try --help", arg) + return fmt.Errorf("path %q does not exist, try --help", arg) } } return nil From 9b4dfcabd668d5cbf3d017d808bd1a8e96a031c5 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 8 Feb 2021 07:40:55 +0100 Subject: [PATCH 021/143] Use passed in stdout --- internal/app/app.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 7d3b19a..c3e8a1e 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -48,7 +48,7 @@ func Execute(version string, stdout, stderr io.Writer) int { rootCmd.SilenceUsage = true if err := rootCmd.Execute(); err != nil { - prefixedUserError("error: %v", err) + prefixedUserError(stderr, "error: %v", err) return exitCodeError } @@ -81,12 +81,12 @@ func makeRootCmd(version string) *cobra.Command { // prefixedUserError prints an error message to stderr and prefixes it // with the name of the program file (e.g. "logstash-filter-verifier: // something bad happened."). -func prefixedUserError(format string, a ...interface{}) { +func prefixedUserError(out io.Writer, format string, a ...interface{}) { basename := filepath.Base(os.Args[0]) message := fmt.Sprintf(format, a...) if strings.HasSuffix(message, "\n") { - fmt.Fprintf(os.Stderr, "%s: %s", basename, message) + fmt.Fprintf(out, "%s: %s", basename, message) } else { - fmt.Fprintf(os.Stderr, "%s: %s\n", basename, message) + fmt.Fprintf(out, "%s: %s\n", basename, message) } } From 65cc9809cd06dde21c44fed991a8a4fc5457b56d Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 8 Feb 2021 07:41:46 +0100 Subject: [PATCH 022/143] Rearrange config path, first has highest priority --- internal/app/app.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index c3e8a1e..566b8e1 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -23,10 +23,10 @@ func Execute(version string, stdout, stderr io.Writer) int { viper.Set("logger", log) // Initialize config - viper.SetConfigName("logstash-filter-verifier") // name of config file (without extension) - viper.AddConfigPath("/etc/logstash-filter-verifier/") // path to look for the config file in - viper.AddConfigPath("$HOME/.logstash-filter-verifier") // call multiple times to add many search paths - viper.AddConfigPath(".") // optionally look for config in the working directory + viper.SetConfigName("logstash-filter-verifier") // name of config file (without extension) + viper.AddConfigPath(".") + viper.AddConfigPath("$HOME/.logstash-filter-verifier") + viper.AddConfigPath("/etc/logstash-filter-verifier/") // Setup default values viper.SetDefault("loglevel", "WARNING") From 2f61becceb57b24212732b39a908212bd9a79faa Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Feb 2021 07:25:25 +0100 Subject: [PATCH 023/143] Change default user config path to os.UserConfigDir --- internal/app/app.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/app/app.go b/internal/app/app.go index 566b8e1..a659d48 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "os" + "path" "path/filepath" "strings" @@ -25,7 +26,10 @@ func Execute(version string, stdout, stderr io.Writer) int { // Initialize config viper.SetConfigName("logstash-filter-verifier") // name of config file (without extension) viper.AddConfigPath(".") - viper.AddConfigPath("$HOME/.logstash-filter-verifier") + configDir, err := os.UserConfigDir() + if err == nil { + viper.AddConfigPath(path.Join(configDir, "logstash-filter-verifier")) + } viper.AddConfigPath("/etc/logstash-filter-verifier/") // Setup default values From 6d5a8fa166d9a164f6e65fd66a3bb9ad6d059634 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 16 Feb 2021 22:03:01 +0100 Subject: [PATCH 024/143] Initial version of daemon mode --- .gitignore | 2 + Makefile | 30 +- go.mod | 19 +- go.sum | 91 +++ integration_test.go | 111 +++ internal/app/app.go | 1 + internal/app/daemon.go | 22 + internal/app/daemon/daemon.go | 321 +++++++++ internal/app/daemon/shutdown/shutdown.go | 47 ++ internal/app/daemon/test/test.go | 148 ++++ internal/app/daemon_run.go | 38 ++ internal/app/daemon_shutdown.go | 25 + internal/app/daemon_start.go | 36 + internal/app/standalone.go | 13 +- internal/daemon/api/grpc/api.go | 3 + internal/daemon/api/grpc/api.pb.go | 634 ++++++++++++++++++ internal/daemon/api/grpc/api.proto | 45 ++ internal/daemon/api/grpc/api_grpc.pb.go | 209 ++++++ internal/daemon/controller/controller.go | 198 ++++++ internal/daemon/controller/controller_test.go | 218 ++++++ internal/daemon/controller/events.go | 48 ++ internal/daemon/controller/files.go | 77 +++ internal/daemon/controller/instance.go | 9 + internal/daemon/controller/pipelines.go | 44 ++ internal/daemon/controller/statemachine.go | 134 ++++ internal/daemon/file/file.go | 20 + internal/daemon/idgen/idgen.go | 14 + .../daemon/instance/logstash/dummy_reader.go | 19 + internal/daemon/instance/logstash/instance.go | 139 ++++ .../daemon/instance/logstash/processors.go | 118 ++++ .../daemon/instance/logstash/tail_logger.go | 17 + .../instance/mock/logstash_instance_mock.go | 145 ++++ internal/daemon/logstashconfig/file.go | 118 ++++ internal/daemon/logstashconfig/file_test.go | 169 +++++ internal/daemon/pipeline/pipeline.go | 107 +++ internal/daemon/pipeline/pipeline_test.go | 110 +++ .../daemon/pipeline/testdata/folder/main.conf | 7 + .../testdata/folder/subfolder/other.conf | 5 + .../pipeline/testdata/pipelines_advanced.yml | 4 + .../pipeline/testdata/pipelines_basic.yml | 4 + .../testdata/pipelines_invalid_yaml.yml | 1 + internal/daemon/session/controller.go | 111 +++ internal/daemon/session/controller_test.go | 165 +++++ internal/daemon/session/files.go | 66 ++ .../daemon/session/logstash_controller.go | 12 + .../session/logstash_controller_mock_test.go | 189 ++++++ internal/daemon/session/session.go | 246 +++++++ internal/daemon/template/template.go | 25 + internal/logging/logger.go | 15 + internal/logging/logger_mock.go | 490 ++++++++++++++ internal/logstash/pipelineconfigdir_test.go | 8 +- logstash-filter-verifier.yml.example | 22 + testdata/basic_pipeline.yml | 2 + testdata/basic_pipeline/main/dir/main2.conf | 5 + testdata/basic_pipeline/main/main.conf | 16 + testdata/conditional_output.yml | 2 + .../main/conditional_output.conf | 25 + testdata/pipeline_to_pipeline.yml | 4 + .../first_stage/first_stage.conf | 19 + .../second_stage/second_stage.conf | 19 + .../testcases/basic_pipeline/testcase1.json | 38 ++ .../testcases/basic_pipeline/testcase2.json | 29 + .../conditional_output.json | 33 + .../pipeline_to_pipeline.json | 25 + testdata/testcases/testcase_lfv.json | 25 + tools.go | 3 + 66 files changed, 5093 insertions(+), 21 deletions(-) create mode 100644 integration_test.go create mode 100644 internal/app/daemon.go create mode 100644 internal/app/daemon/daemon.go create mode 100644 internal/app/daemon/shutdown/shutdown.go create mode 100644 internal/app/daemon/test/test.go create mode 100644 internal/app/daemon_run.go create mode 100644 internal/app/daemon_shutdown.go create mode 100644 internal/app/daemon_start.go create mode 100644 internal/daemon/api/grpc/api.go create mode 100644 internal/daemon/api/grpc/api.pb.go create mode 100644 internal/daemon/api/grpc/api.proto create mode 100644 internal/daemon/api/grpc/api_grpc.pb.go create mode 100644 internal/daemon/controller/controller.go create mode 100644 internal/daemon/controller/controller_test.go create mode 100644 internal/daemon/controller/events.go create mode 100644 internal/daemon/controller/files.go create mode 100644 internal/daemon/controller/instance.go create mode 100644 internal/daemon/controller/pipelines.go create mode 100644 internal/daemon/controller/statemachine.go create mode 100644 internal/daemon/file/file.go create mode 100644 internal/daemon/idgen/idgen.go create mode 100644 internal/daemon/instance/logstash/dummy_reader.go create mode 100644 internal/daemon/instance/logstash/instance.go create mode 100644 internal/daemon/instance/logstash/processors.go create mode 100644 internal/daemon/instance/logstash/tail_logger.go create mode 100644 internal/daemon/instance/mock/logstash_instance_mock.go create mode 100644 internal/daemon/logstashconfig/file.go create mode 100644 internal/daemon/logstashconfig/file_test.go create mode 100644 internal/daemon/pipeline/pipeline.go create mode 100644 internal/daemon/pipeline/pipeline_test.go create mode 100644 internal/daemon/pipeline/testdata/folder/main.conf create mode 100644 internal/daemon/pipeline/testdata/folder/subfolder/other.conf create mode 100644 internal/daemon/pipeline/testdata/pipelines_advanced.yml create mode 100644 internal/daemon/pipeline/testdata/pipelines_basic.yml create mode 100644 internal/daemon/pipeline/testdata/pipelines_invalid_yaml.yml create mode 100644 internal/daemon/session/controller.go create mode 100644 internal/daemon/session/controller_test.go create mode 100644 internal/daemon/session/files.go create mode 100644 internal/daemon/session/logstash_controller.go create mode 100644 internal/daemon/session/logstash_controller_mock_test.go create mode 100644 internal/daemon/session/session.go create mode 100644 internal/daemon/template/template.go create mode 100644 internal/logging/logger_mock.go create mode 100644 logstash-filter-verifier.yml.example create mode 100644 testdata/basic_pipeline.yml create mode 100644 testdata/basic_pipeline/main/dir/main2.conf create mode 100644 testdata/basic_pipeline/main/main.conf create mode 100644 testdata/conditional_output.yml create mode 100644 testdata/conditional_output/main/conditional_output.conf create mode 100644 testdata/pipeline_to_pipeline.yml create mode 100644 testdata/pipeline_to_pipeline/first_stage/first_stage.conf create mode 100644 testdata/pipeline_to_pipeline/second_stage/second_stage.conf create mode 100644 testdata/testcases/basic_pipeline/testcase1.json create mode 100644 testdata/testcases/basic_pipeline/testcase2.json create mode 100644 testdata/testcases/conditional_output/conditional_output.json create mode 100644 testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json create mode 100644 testdata/testcases/testcase_lfv.json diff --git a/.gitignore b/.gitignore index 7a7e831..c747752 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/3rdparty /bin /coverage.html /dist/ @@ -5,3 +6,4 @@ /.vscode/ *.coverprofile *.swp +logstash-filter-verifier.yml diff --git a/Makefile b/Makefile index c45c385..acfcef5 100644 --- a/Makefile +++ b/Makefile @@ -35,11 +35,14 @@ TARGETS := darwin_amd64 linux_386 linux_amd64 windows_386 windows_amd64 VERSION := $(shell git describe --tags --always) -GOCOV := $(GOBIN)/gocov$(EXEC_SUFFIX) -GOCOV_HTML := $(GOBIN)/gocov-html$(EXEC_SUFFIX) -GOLANGCI_LINT := $(GOBIN)/golangci-lint$(EXEC_SUFFIX) -GOVVV := $(GOBIN)/govvv$(EXEC_SUFFIX) -OVERALLS := $(GOBIN)/overalls$(EXEC_SUFFIX) +GOCOV := $(GOBIN)/gocov$(EXEC_SUFFIX) +GOCOV_HTML := $(GOBIN)/gocov-html$(EXEC_SUFFIX) +GOLANGCI_LINT := $(GOBIN)/golangci-lint$(EXEC_SUFFIX) +GOVVV := $(GOBIN)/govvv$(EXEC_SUFFIX) +OVERALLS := $(GOBIN)/overalls$(EXEC_SUFFIX) +PROTOC_GEN_GO := $(GOBIN)/protoc-gen-go$(EXEC_SUFFIX) +PROTOC_GEN_GO_GRPC := $(GOBIN)/protoc-gen-go-grpc$(EXEC_SUFFIX) +MOQ := $(GOBIN)/moq$(EXEC_SUFFIX) GOLANGCI_LINT_VERSION := v1.32.2 @@ -66,11 +69,26 @@ $(GOVVV): $(OVERALLS): go get github.com/go-playground/overalls +# TODO: For protoc to find this dependency, I suppose, they must reside in the PATH +$(PROTOC_GEN_GO): + go get google.golang.org/protobuf/cmd/protoc-gen-go + +# TODO: For protoc to find this dependency, I suppose, they must reside in the PATH +$(PROTOC_GEN_GO_GRPC): + go get google.golang.org/grpc/cmd/protoc-gen-go-grpc + +$(MOQ): + go get github.com/matryer/moq + # The Go compiler is fast and pretty good about figuring out what to # build so we don't try to to outsmart it. -$(PROGRAM)$(EXEC_SUFFIX): .FORCE $(GOVVV) +$(PROGRAM)$(EXEC_SUFFIX): gogenerate .FORCE $(GOVVV) govvv build -o $@ +.PHONY: gogenerate +generate: $(MOQ) # TODO: go generate also depends on protobuf-compiler, which needs to be installed as well. + go generate ./... + .PHONY: check check: $(GOLANGCI_LINT) golangci-lint run diff --git a/go.mod b/go.mod index 897628c..622c002 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,29 @@ require ( github.com/Masterminds/semver/v3 v3.0.1 github.com/ahmetb/govvv v0.3.0 github.com/axw/gocov v1.0.0 - github.com/breml/logstash-config v0.1.0 + github.com/bmatcuk/doublestar/v2 v2.0.4 + github.com/breml/logstash-config v0.4.0 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 + github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 + github.com/hpcloud/tail v1.0.0 github.com/imkira/go-observer v1.0.3 + github.com/magnusbaeck/logstash-filter-verifier v0.0.0-20201128205846-61a579889997 github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b + github.com/matoous/go-nanoid v1.5.0 + github.com/matryer/is v1.4.0 + github.com/matryer/moq v0.2.1 github.com/mattn/go-shellwords v1.0.6 github.com/mikefarah/yaml/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 + github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.4.0 - github.com/yookoala/realpath v1.0.0 // indirect + github.com/stretchr/testify v1.5.1 + github.com/tidwall/gjson v1.6.8 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + google.golang.org/grpc v1.34.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 + google.golang.org/protobuf v1.25.0 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 3a29e6f..1eef112 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,11 @@ github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:O github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/ahmetb/govvv v0.3.0 h1:YGLGwEyiUwHFy5eh/RUhdupbuaCGBYn5T5GWXp+WJB0= github.com/ahmetb/govvv v0.3.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -56,16 +59,22 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= +github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/breml/logstash-config v0.1.0 h1:fym6y8WJpJxEicmwUBhdzH2VSHzaFoUf2jA2tOTV7Tw= github.com/breml/logstash-config v0.1.0/go.mod h1:ZBjsBLmordIP3WF/sMfUlRkwBhrHgw6njXx2m7P4Cmo= +github.com/breml/logstash-config v0.4.0 h1:KHzk5ePORMYD/tMQzhr3oPe7LbJjF9TRaquMV7keGvQ= +github.com/breml/logstash-config v0.4.0/go.mod h1:Opa416n2AzUie2KU8MsVze1CoLpbqdqt6sVqZjgoa9k= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -87,6 +96,10 @@ github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5N github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -118,12 +131,26 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -132,6 +159,7 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= @@ -183,6 +211,7 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hashicorp/vault v1.1.0/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hetznercloud/hcloud-go v1.15.1/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2wrVdERJgCtxsF3Q= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs= github.com/imkira/go-observer v1.0.3 h1:l45TYAEeAB4L2xF6PR2gRLn2NE5tYhudh33MLmC7B80= @@ -220,11 +249,20 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magnusbaeck/logstash-filter-verifier v0.0.0-20201128205846-61a579889997 h1:lAZRZB9Elk7diV4eYzIp4aEVEedCQuHibiDVlDlNcL8= +github.com/magnusbaeck/logstash-filter-verifier v0.0.0-20201128205846-61a579889997/go.mod h1:0FkNWNMV/zvGcRgvXIArlw5LIriH4WHClLkFX+Sd40c= github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= +github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b h1:5Wc/N1FIBnExmX0/SEdKe0A0COvdJc3rCGHQ7s1oBPQ= github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= +github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek= +github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/moq v0.2.1 h1:4roNlIsfEXb7127O3v558H+9jmV70G2FAJPYIZX84lQ= +github.com/matryer/moq v0.2.1/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -278,6 +316,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -287,6 +327,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -337,10 +378,18 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/tencentcloud/tencentcloud-sdk-go v3.0.71+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c= github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= @@ -352,6 +401,7 @@ github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1 github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854= github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ= github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -364,6 +414,8 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -382,6 +434,8 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -399,6 +453,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -407,6 +463,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -424,6 +481,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -443,6 +501,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -453,7 +512,13 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2pTvDyhZNduBUMD2q3iqs4= +golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -471,16 +536,40 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 h1:M8spwkmx0pHrPq+uMdl22w5CvJ/Y+oAJTIs9oGoCpOE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= @@ -490,6 +579,7 @@ gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -501,5 +591,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/integration_test.go b/integration_test.go new file mode 100644 index 0000000..843cd3b --- /dev/null +++ b/integration_test.go @@ -0,0 +1,111 @@ +package main_test + +import ( + "context" + "os" + "path" + "testing" + "time" + + "github.com/matryer/is" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/test" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func TestIntegration(t *testing.T) { + if os.Getenv("INTEGRATION_TEST") != "1" { + t.Skip("integration test skipped, enable with env var `INTEGRATION_TEST=1`") + } + + is := is.New(t) + + // FIXME: use test logger + testLogger := &logging.LoggerMock{ + DebugFunc: func(args ...interface{}) { t.Log(args...) }, + DebugfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, + ErrorFunc: func(args ...interface{}) { t.Log(args...) }, + ErrorfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, + FatalFunc: func(args ...interface{}) { t.Log(args...) }, + FatalfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, + InfoFunc: func(args ...interface{}) { t.Log(args...) }, + InfofFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, + WarningFunc: func(args ...interface{}) { t.Log(args...) }, + WarningfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, + } + + tempdir := t.TempDir() + // Start Daemon + socket := path.Join(tempdir, "integration_test.socket") + logstashPath := path.Join("3rdparty/logstash-7.10.0/bin/logstash") + if !file.Exists(logstashPath) { + t.Fatalf("Logstash needs to be present in %q for the integration tests to work", logstashPath) + } + + // FIXME: use test logger + log := testLogger + server := daemon.New(socket, logstashPath, log) + + go func() { + is := is.New(t) + + defer server.Cleanup() + + err := server.Run() + is.NoErr(err) + }() + + i := 0 + for { + if file.Exists(socket) { + break + } + time.Sleep(100 * time.Millisecond) + i++ + if i >= 20 { + t.Fatalf("wait for socket file failed") + } + } + + // Run tests + cases := []struct { + name string + pipeline string + basePath string + testcases string + }{ + { + name: "basic_pipeline", + }, + { + name: "conditional_output", + }, + { + name: "pipeline_to_pipeline", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + client, err := test.New( + path.Join(tempdir, "integration_test.socket"), + log, + "testdata/"+tc.name+".yml", + "testdata/"+tc.name, + "testdata/testcases/"+tc.name, + ) + is.NoErr(err) + + err = client.Run() + is.NoErr(err) + }) + } + + _, err := server.Shutdown(context.Background(), &grpc.ShutdownRequest{}) + is.NoErr(err) + + time.Sleep(3 * time.Second) +} diff --git a/internal/app/app.go b/internal/app/app.go index a659d48..81d3656 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -78,6 +78,7 @@ func makeRootCmd(version string) *cobra.Command { _ = viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel")) rootCmd.AddCommand(makeStandaloneCmd()) + rootCmd.AddCommand(makeDaemonCmd()) return rootCmd } diff --git a/internal/app/daemon.go b/internal/app/daemon.go new file mode 100644 index 0000000..d867ba2 --- /dev/null +++ b/internal/app/daemon.go @@ -0,0 +1,22 @@ +package app + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func makeDaemonCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "daemon", + Short: "Control logstash-filter-verifier daemon mode", + } + + cmd.PersistentFlags().StringP("socket", "s", "", "location of the control socket") + _ = viper.BindPFlag("socket", cmd.PersistentFlags().Lookup("socket")) + + cmd.AddCommand(makeDaemonStartCmd()) + cmd.AddCommand(makeDaemonShutdownCmd()) + cmd.AddCommand(makeDaemonRunCmd()) + + return cmd +} diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go new file mode 100644 index 0000000..33de2b6 --- /dev/null +++ b/internal/app/daemon/daemon.go @@ -0,0 +1,321 @@ +package daemon + +import ( + "archive/zip" + "bytes" + "context" + "io/ioutil" + "net" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/pkg/errors" + "google.golang.org/grpc" + "gopkg.in/yaml.v2" + + pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/controller" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/instance/logstash" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type Daemon struct { + socket string + logstashPath string + + tempdir string + + sessionController *session.Controller + + server *grpc.Server + logstashController *controller.Controller + + // This channel is closed as soon as the shutdown is in progress. + shutdownInProgress chan struct{} + + // Global shutdownLogstashInstance channel, all Go routines should listen to this channel to + // get notified and safely exit on shutdownLogstashInstance of the daemon. + // The shutdownSignalHandler() will close this channel on shutdownLogstashInstance. + shutdownLogstashInstance chan struct{} + + // shutdownSignal is sent by the Shutdown GRPC handler, when a shutdown command + // is received. The shutdownSignal channel is processed by the shutdownSignalHandler(). + shutdownSignal chan struct{} + + // Global shutdown wait group. Daemon.Run() will wait for this wait group + // before returning and exiting the main Go routine. + shutdownLogstashInstancesWG *sync.WaitGroup + + log logging.Logger + + pb.UnimplementedControlServer +} + +// New creates a new logstash filter verifier daemon. +func New(socket string, logstashPath string, log logging.Logger) Daemon { + return Daemon{ + socket: socket, + logstashPath: logstashPath, + log: log, + shutdownInProgress: make(chan struct{}), + shutdownLogstashInstance: make(chan struct{}), + shutdownSignal: make(chan struct{}), + shutdownLogstashInstancesWG: &sync.WaitGroup{}, + } +} + +// Run starts the logstash filter verifier daemon. +func (d *Daemon) Run() error { + tempdir, err := ioutil.TempDir("", "lfv-") + if err != nil { + return err + } + d.tempdir = tempdir + d.log.Debugf("Temporary directory for daemon created in %q", d.tempdir) + + // Create and start Logstash Controller + d.shutdownLogstashInstancesWG.Add(1) + instance := logstash.New(d.logstashPath, d.log, d.shutdownLogstashInstance, d.shutdownLogstashInstancesWG) + logstashController, err := controller.NewController(instance, tempdir, d.log, d.shutdownLogstashInstance) + if err != nil { + return err + } + d.logstashController = logstashController + + err = d.logstashController.Launch() + if err != nil { + return err + } + + // Create Session Handler + d.sessionController = session.NewController(d.tempdir, d.logstashController, d.log) + + // Setup signal handler and shutdown coordinator + shutdownHandlerCompleted := make(chan struct{}) + go d.shutdownSignalHandler(shutdownHandlerCompleted) + + // Create and start GRPC Server + lis, err := net.Listen("unix", d.socket) + if err != nil { + return err + } + d.server = grpc.NewServer() + pb.RegisterControlServer(d.server, d) + + d.log.Infof("Daemon listening on %s", d.socket) + err = d.server.Serve(lis) + + // This is called from the main Go routine, so we have to wait for all others + // to shutdown, before we can return and end the program/daemon. + <-shutdownHandlerCompleted + + return err +} + +func (d *Daemon) shutdownSignalHandler(shutdownHandlerCompleted chan struct{}) { + // Make sure, shutdownHandlerCompleted channel is closed and main Go routine + // exits cleanly. + defer close(shutdownHandlerCompleted) + + // Listen to shutdown signal (comming from shutdown GRPC requests) as well + // as OS signals interrupt and SIGTERM (not present on all systems). + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + + select { + case <-d.shutdownSignal: + case <-c: + } + + // Shutdown signal or OS signal received, start shutdown procedure + // Signal shutdown to all + close(d.shutdownInProgress) + close(d.shutdownSignal) + + // TODO: Make shutdown timeout configurable + t := time.NewTimer(3 * time.Second) + + // Wait for currently running sessions to finish. + select { + case <-d.sessionController.WaitFinish(): + t.Stop() + case <-t.C: + } + + // Stop accepting new connections, wait for currently running handlers to finish properly. + serverStopped := make(chan struct{}) + go func() { + d.server.GracefulStop() + close(serverStopped) + }() + + // Stop Logstash instance + logstashInstancesStopped := make(chan struct{}) + go func() { + close(d.shutdownLogstashInstance) + d.shutdownLogstashInstancesWG.Wait() + close(logstashInstancesStopped) + }() + + // TODO: Make shutdown timeout configurable + t.Reset(3 * time.Second) + + // Wait for Logstash and GRPC Server to shutdown + serverStopComplete := false + logstashInstanceStopComplete := false + for !serverStopComplete || !logstashInstanceStopComplete { + select { + case <-t.C: + d.log.Debug("Shutdown timeout reached, force shutdown.") + d.server.Stop() + serverStopComplete = true + logstashInstanceStopComplete = true + case <-serverStopped: + d.log.Debug("server successfully stopped.") + serverStopComplete = true + serverStopped = nil + case <-logstashInstancesStopped: + d.log.Debug("logstash instance successfully stopped.") + logstashInstanceStopComplete = true + logstashInstancesStopped = nil + } + } + t.Stop() +} + +// Cleanup removes the temporary files created by the daemon. +func (d *Daemon) Cleanup() { + err := os.RemoveAll(d.tempdir) + if err != nil { + d.log.Errorf("Failed to cleanup temporary directory for daemon %q: %v", d.tempdir, err) + } +} + +// Shutdown signals the daemon to shutdown. +func (d *Daemon) Shutdown(ctx context.Context, in *pb.ShutdownRequest) (*pb.ShutdownResponse, error) { + select { + case d.shutdownSignal <- struct{}{}: + default: + } + + return &pb.ShutdownResponse{}, nil +} + +func (d *Daemon) isShutdownInProgress() bool { + select { + case <-d.shutdownInProgress: + return true + default: + } + return false +} + +// SetupTest creates a new session, receives the pipeline configuration +// (zip archive), and prepares the files for the new session. +func (d *Daemon) SetupTest(ctx context.Context, in *pb.SetupTestRequest) (*pb.SetupTestResponse, error) { + if d.isShutdownInProgress() { + return nil, errors.New("daemon is shutting down, no new sessions accepted") + } + + pipelines, configFiles, err := d.extractZip(in.Pipeline) + if err != nil { + return nil, err + } + + session, err := d.sessionController.Create(pipelines, configFiles) + if err != nil { + return nil, err + } + + return &pb.SetupTestResponse{ + SessionID: session.ID(), + }, err +} + +func (d *Daemon) extractZip(in []byte) (pipeline.Pipelines, []logstashconfig.File, error) { + r, err := zip.NewReader(bytes.NewReader(in), int64(len(in))) + if err != nil { + return pipeline.Pipelines{}, nil, err + } + + pipelines := pipeline.Pipelines{} + configFiles := make([]logstashconfig.File, 0, len(r.File)) + for _, f := range r.File { + rc, err := f.Open() + if err != nil { + return pipeline.Pipelines{}, nil, err + } + + switch f.Name { + case "pipelines.yml": + pipelinesBody, err := ioutil.ReadAll(rc) + if err != nil { + return pipeline.Pipelines{}, nil, err + } + + err = yaml.Unmarshal([]byte(pipelinesBody), &pipelines) + if err != nil { + return pipeline.Pipelines{}, nil, err + } + default: + body, err := ioutil.ReadAll(rc) + if err != nil { + return pipeline.Pipelines{}, nil, err + } + configFile := logstashconfig.File{ + Name: f.Name, + Body: body, + } + configFiles = append(configFiles, configFile) + } + + err = rc.Close() + if err != nil { + return pipeline.Pipelines{}, nil, err + } + } + + return pipelines, configFiles, nil +} + +// ExecuteTest runs a test case set against the Logstash configuration, that has +// been loaded previously with SetupTest. +func (d *Daemon) ExecuteTest(ctx context.Context, in *pb.ExecuteTestRequest) (*pb.ExecuteTestResponse, error) { + session, err := d.sessionController.Get(in.SessionID) + if err != nil { + return nil, errors.Wrap(err, "invalid session ID") + } + + err = session.ExecuteTest(in.InputLines, in.Fields) + if err != nil { + return nil, err + } + + results, err := session.GetResults() + if err != nil { + d.log.Errorf("failed to wait for Logstash results: %v", err) + } + + return &pb.ExecuteTestResponse{ + Results: results, + }, nil +} + +// TeardownTest closes a test session, previously opened by SetupTest. +// After all test case sets are executed against the Logstash configuration, +// the test session needs to be closed. +func (d *Daemon) TeardownTest(ctx context.Context, in *pb.TeardownTestRequest) (*pb.TeardownTestResponse, error) { + err := d.sessionController.DestroyByID(in.SessionID) + if err != nil { + return nil, errors.Wrap(err, "destroy of session failed") + } + + result := pb.TeardownTestResponse{} + return &result, err +} diff --git a/internal/app/daemon/shutdown/shutdown.go b/internal/app/daemon/shutdown/shutdown.go new file mode 100644 index 0000000..cc9b522 --- /dev/null +++ b/internal/app/daemon/shutdown/shutdown.go @@ -0,0 +1,47 @@ +package shutdown + +import ( + "context" + "net" + "time" + + "google.golang.org/grpc" + + pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type Shutdown struct { + socket string + + log logging.Logger +} + +func New(socket string, log logging.Logger) Shutdown { + return Shutdown{ + socket: socket, + log: log, + } +} + +func (s Shutdown) Run() error { + s.log.Debug("Shutdown on socket ", s.socket) + + conn, err := grpc.Dial( + s.socket, + grpc.WithInsecure(), + grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { + if d, ok := ctx.Deadline(); ok { + return net.DialTimeout("unix", addr, time.Until(d)) + } + return net.Dial("unix", addr) + })) + if err != nil { + return err + } + defer conn.Close() + c := pb.NewControlClient(conn) + + _, err = c.Shutdown(context.Background(), &pb.ShutdownRequest{}) + return err +} diff --git a/internal/app/daemon/test/test.go b/internal/app/daemon/test/test.go new file mode 100644 index 0000000..e6f6792 --- /dev/null +++ b/internal/app/daemon/test/test.go @@ -0,0 +1,148 @@ +package test + +import ( + "context" + "encoding/json" + "fmt" + "net" + "os" + "path" + "time" + + "github.com/imkira/go-observer" + "google.golang.org/grpc" + + "github.com/magnusbaeck/logstash-filter-verifier/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/observer" + "github.com/magnusbaeck/logstash-filter-verifier/testcase" + pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type Test struct { + socket string + pipeline string + pipelineBase string + testcasePath string + + log logging.Logger +} + +func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath string) (Test, error) { + if !path.IsAbs(pipelineBase) { + cwd, err := os.Getwd() + if err != nil { + return Test{}, err + } + pipelineBase = path.Join(cwd, pipelineBase) + } + return Test{ + socket: socket, + pipeline: pipeline, + pipelineBase: pipelineBase, + testcasePath: testcasePath, + log: log, + }, nil +} + +func (s Test) Run() error { + s.log.Debug("Test on socket ", s.socket) + + conn, err := grpc.Dial( + s.socket, + grpc.WithInsecure(), + grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { + if d, ok := ctx.Deadline(); ok { + return net.DialTimeout("unix", addr, time.Until(d)) + } + return net.Dial("unix", addr) + })) + if err != nil { + return err + } + defer conn.Close() + c := pb.NewControlClient(conn) + + // FIXME: Read pipeline, find config (fix paths based on pipelineBase if necessary) + a, err := pipeline.NewArchive(s.pipeline, s.pipelineBase) + if err != nil { + return err + } + + b, err := a.ZipBytes() + if err != nil { + return err + } + + result, err := c.SetupTest(context.Background(), &pb.SetupTestRequest{ + Pipeline: b, + }) + if err != nil { + return err + } + sessionID := result.SessionID + + tests, err := testcase.DiscoverTests(s.testcasePath) + if err != nil { + return err + } + + observers := make([]lfvobserver.Interface, 0) + liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) + observers = append(observers, lfvobserver.NewSummaryObserver(liveObserver)) + for _, obs := range observers { + if err := obs.Start(); err != nil { + return err + } + } + + for _, t := range tests { + fields := make(map[string]string, len(t.InputFields)) + for k, v := range t.InputFields { + // FIXME: dirty hack to convert interface{} to string, does not work with nested Logstash fields e.g. [fields][nested_field] + fields[k] = fmt.Sprint(v) + } + result, err := c.ExecuteTest(context.Background(), &pb.ExecuteTestRequest{ + SessionID: sessionID, + InputLines: t.InputLines, + Fields: fields, + }) + if err != nil { + return err + } + + var events []logstash.Event + for _, line := range result.Results { + var event logstash.Event + err = json.Unmarshal([]byte(line), &event) + if err != nil { + return err + } + events = append(events, event) + } + + _, err = t.Compare(events, []string{"diff", "-u"}, liveObserver) + if err != nil { + return err + } + } + + _, err = c.TeardownTest(context.Background(), &pb.TeardownTestRequest{ + SessionID: sessionID, + Stats: false, + }) + if err != nil { + return err + } + + liveObserver.Update(lfvobserver.TestExecutionEnd{}) + + for _, obs := range observers { + if err := obs.Finalize(); err != nil { + return err + } + } + + return nil +} diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go new file mode 100644 index 0000000..05ad269 --- /dev/null +++ b/internal/app/daemon_run.go @@ -0,0 +1,38 @@ +package app + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/test" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func makeDaemonRunCmd() *cobra.Command { + rootCmd := &cobra.Command{ + Use: "run", + Short: "Run test suite with logstash-filter-verifier daemon", + RunE: runDaemonRun, + } + + rootCmd.Flags().StringP("pipeline", "p", "", "location of the pipelines.yml file to be processed") + _ = viper.BindPFlag("pipeline", rootCmd.Flags().Lookup("pipeline")) + rootCmd.Flags().StringP("pipeline-base", "", "", "base directory for relative paths in the pipelines.yml") + _ = viper.BindPFlag("pipeline-base", rootCmd.Flags().Lookup("pipeline-base")) + rootCmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") + _ = viper.BindPFlag("testcase-dir", rootCmd.Flags().Lookup("testcase-dir")) + + return rootCmd +} + +func runDaemonRun(_ *cobra.Command, args []string) error { + // TODO: Remove this + // logging.SetLevel(oplogging.INFO) + + t, err := test.New(viper.GetString("socket"), viper.Get("logger").(logging.Logger), viper.GetString("pipeline"), viper.GetString("pipeline-base"), viper.GetString("testcase-dir")) + if err != nil { + return err + } + + return t.Run() +} diff --git a/internal/app/daemon_shutdown.go b/internal/app/daemon_shutdown.go new file mode 100644 index 0000000..371c446 --- /dev/null +++ b/internal/app/daemon_shutdown.go @@ -0,0 +1,25 @@ +package app + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/shutdown" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func makeDaemonShutdownCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "shutdown", + Short: "Shutdown logstash-filter-verifier daemon", + RunE: runDaemonShutdown, + } + + return cmd +} + +func runDaemonShutdown(_ *cobra.Command, _ []string) error { + s := shutdown.New(viper.GetString("socket"), viper.Get("logger").(logging.Logger)) + + return s.Run() +} diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go new file mode 100644 index 0000000..765a37c --- /dev/null +++ b/internal/app/daemon_start.go @@ -0,0 +1,36 @@ +package app + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func makeDaemonStartCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "start", + Short: "Start logstash-filter-verifier daemon", + RunE: runDaemonStart, + } + + cmd.Flags().StringP("logstash-path", "", "/usr/share/logstash/bin/logstash", "location where the logstash executable is found") + _ = viper.BindPFlag("logstash.path", cmd.Flags().Lookup("logstash-path")) + + return cmd +} + +func runDaemonStart(_ *cobra.Command, _ []string) error { + socket := viper.GetString("socket") + logstashPath := viper.GetString("logstash.path") + log := viper.Get("logger").(logging.Logger) + + log.Debugf("config: socket: %s", socket) + log.Debugf("config: logstash-path: %s", logstashPath) + + s := daemon.New(socket, logstashPath, log) + defer s.Cleanup() + + return s.Run() +} diff --git a/internal/app/standalone.go b/internal/app/standalone.go index e4267b6..e46f0c2 100644 --- a/internal/app/standalone.go +++ b/internal/app/standalone.go @@ -15,11 +15,10 @@ import ( func makeStandaloneCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "standalone [] ...", - Short: "Run logstash-filter-verifier in standalone mode", - RunE: runE, - Args: validateArgs, - SilenceErrors: true, + Use: "standalone [] ...", + Short: "Run logstash-filter-verifier in standalone mode", + RunE: runStandalone, + Args: validateStandaloneArgs, } // TODO: This flag makes sense with daemon mode test command as well. @@ -60,7 +59,7 @@ func makeStandaloneCmd() *cobra.Command { return cmd } -func runE(_ *cobra.Command, args []string) error { +func runStandalone(_ *cobra.Command, args []string) error { s := standalone.New( viper.GetBool("quiet"), viper.GetString("diff-command"), @@ -79,7 +78,7 @@ func runE(_ *cobra.Command, args []string) error { return s.Run() } -func validateArgs(cmd *cobra.Command, args []string) error { +func validateStandaloneArgs(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("required argument 'testcases' not provided, try --help") } diff --git a/internal/daemon/api/grpc/api.go b/internal/daemon/api/grpc/api.go new file mode 100644 index 0000000..a529407 --- /dev/null +++ b/internal/daemon/api/grpc/api.go @@ -0,0 +1,3 @@ +package grpc + +//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative api.proto diff --git a/internal/daemon/api/grpc/api.pb.go b/internal/daemon/api/grpc/api.pb.go new file mode 100644 index 0000000..ee1c753 --- /dev/null +++ b/internal/daemon/api/grpc/api.pb.go @@ -0,0 +1,634 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.6.1 +// source: api.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ShutdownRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ShutdownRequest) Reset() { + *x = ShutdownRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShutdownRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShutdownRequest) ProtoMessage() {} + +func (x *ShutdownRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShutdownRequest.ProtoReflect.Descriptor instead. +func (*ShutdownRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +type ShutdownResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ShutdownResponse) Reset() { + *x = ShutdownResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ShutdownResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ShutdownResponse) ProtoMessage() {} + +func (x *ShutdownResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ShutdownResponse.ProtoReflect.Descriptor instead. +func (*ShutdownResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +type SetupTestRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pipeline []byte `protobuf:"bytes,1,opt,name=pipeline,proto3" json:"pipeline,omitempty"` +} + +func (x *SetupTestRequest) Reset() { + *x = SetupTestRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupTestRequest) ProtoMessage() {} + +func (x *SetupTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupTestRequest.ProtoReflect.Descriptor instead. +func (*SetupTestRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +func (x *SetupTestRequest) GetPipeline() []byte { + if x != nil { + return x.Pipeline + } + return nil +} + +type SetupTestResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` +} + +func (x *SetupTestResponse) Reset() { + *x = SetupTestResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetupTestResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetupTestResponse) ProtoMessage() {} + +func (x *SetupTestResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetupTestResponse.ProtoReflect.Descriptor instead. +func (*SetupTestResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *SetupTestResponse) GetSessionID() string { + if x != nil { + return x.SessionID + } + return "" +} + +type ExecuteTestRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` + InputLines []string `protobuf:"bytes,2,rep,name=inputLines,proto3" json:"inputLines,omitempty"` + Fields map[string]string `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ExecuteTestRequest) Reset() { + *x = ExecuteTestRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteTestRequest) ProtoMessage() {} + +func (x *ExecuteTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteTestRequest.ProtoReflect.Descriptor instead. +func (*ExecuteTestRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{4} +} + +func (x *ExecuteTestRequest) GetSessionID() string { + if x != nil { + return x.SessionID + } + return "" +} + +func (x *ExecuteTestRequest) GetInputLines() []string { + if x != nil { + return x.InputLines + } + return nil +} + +func (x *ExecuteTestRequest) GetFields() map[string]string { + if x != nil { + return x.Fields + } + return nil +} + +type ExecuteTestResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Results []string `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *ExecuteTestResponse) Reset() { + *x = ExecuteTestResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteTestResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteTestResponse) ProtoMessage() {} + +func (x *ExecuteTestResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteTestResponse.ProtoReflect.Descriptor instead. +func (*ExecuteTestResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{5} +} + +func (x *ExecuteTestResponse) GetResults() []string { + if x != nil { + return x.Results + } + return nil +} + +type TeardownTestRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` + Stats bool `protobuf:"varint,2,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *TeardownTestRequest) Reset() { + *x = TeardownTestRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TeardownTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TeardownTestRequest) ProtoMessage() {} + +func (x *TeardownTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TeardownTestRequest.ProtoReflect.Descriptor instead. +func (*TeardownTestRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{6} +} + +func (x *TeardownTestRequest) GetSessionID() string { + if x != nil { + return x.SessionID + } + return "" +} + +func (x *TeardownTestRequest) GetStats() bool { + if x != nil { + return x.Stats + } + return false +} + +type TeardownTestResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Stats string `protobuf:"bytes,1,opt,name=stats,proto3" json:"stats,omitempty"` +} + +func (x *TeardownTestResponse) Reset() { + *x = TeardownTestResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TeardownTestResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TeardownTestResponse) ProtoMessage() {} + +func (x *TeardownTestResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TeardownTestResponse.ProtoReflect.Descriptor instead. +func (*TeardownTestResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{7} +} + +func (x *TeardownTestResponse) GetStats() string { + if x != nil { + return x.Stats + } + return "" +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, + 0x63, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x75, + 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x31, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x75, + 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0xcb, 0x01, 0x0a, 0x12, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, + 0x12, 0x3c, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, + 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2f, 0x0a, 0x13, 0x45, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x54, 0x65, + 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, + 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x32, 0x95, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, + 0x3b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, + 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09, + 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, + 0x73, 0x74, 0x12, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, + 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, 0x52, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, 0x75, 0x73, + 0x62, 0x61, 0x65, 0x63, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, 0x2d, 0x66, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x76, + 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, + 0x6e, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_api_proto_goTypes = []interface{}{ + (*ShutdownRequest)(nil), // 0: grpc.ShutdownRequest + (*ShutdownResponse)(nil), // 1: grpc.ShutdownResponse + (*SetupTestRequest)(nil), // 2: grpc.SetupTestRequest + (*SetupTestResponse)(nil), // 3: grpc.SetupTestResponse + (*ExecuteTestRequest)(nil), // 4: grpc.ExecuteTestRequest + (*ExecuteTestResponse)(nil), // 5: grpc.ExecuteTestResponse + (*TeardownTestRequest)(nil), // 6: grpc.TeardownTestRequest + (*TeardownTestResponse)(nil), // 7: grpc.TeardownTestResponse + nil, // 8: grpc.ExecuteTestRequest.FieldsEntry +} +var file_api_proto_depIdxs = []int32{ + 8, // 0: grpc.ExecuteTestRequest.fields:type_name -> grpc.ExecuteTestRequest.FieldsEntry + 0, // 1: grpc.Control.Shutdown:input_type -> grpc.ShutdownRequest + 2, // 2: grpc.Control.SetupTest:input_type -> grpc.SetupTestRequest + 4, // 3: grpc.Control.ExecuteTest:input_type -> grpc.ExecuteTestRequest + 6, // 4: grpc.Control.TeardownTest:input_type -> grpc.TeardownTestRequest + 1, // 5: grpc.Control.Shutdown:output_type -> grpc.ShutdownResponse + 3, // 6: grpc.Control.SetupTest:output_type -> grpc.SetupTestResponse + 5, // 7: grpc.Control.ExecuteTest:output_type -> grpc.ExecuteTestResponse + 7, // 8: grpc.Control.TeardownTest:output_type -> grpc.TeardownTestResponse + 5, // [5:9] is the sub-list for method output_type + 1, // [1:5] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShutdownRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ShutdownResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupTestRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetupTestResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteTestRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteTestResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TeardownTestRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TeardownTestResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/internal/daemon/api/grpc/api.proto b/internal/daemon/api/grpc/api.proto new file mode 100644 index 0000000..91c81ca --- /dev/null +++ b/internal/daemon/api/grpc/api.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +option go_package = "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/daemon/api/grpc"; + +package grpc; + +// The control service definition. +service Control { + rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {} + + rpc SetupTest (SetupTestRequest) returns (SetupTestResponse) {} + rpc ExecuteTest (ExecuteTestRequest) returns (ExecuteTestResponse) {} + rpc TeardownTest (TeardownTestRequest) returns (TeardownTestResponse) {} +} + +message ShutdownRequest {} + +message ShutdownResponse {} + +message SetupTestRequest { + bytes pipeline = 1; +} + +message SetupTestResponse { + string sessionID = 1; +} + +message ExecuteTestRequest { + string sessionID = 1; + repeated string inputLines = 2; + map fields = 3; +} + +message ExecuteTestResponse { + repeated string results = 1; +} + +message TeardownTestRequest { + string sessionID = 1; + bool stats = 2; +} + +message TeardownTestResponse { + string stats = 1; +} diff --git a/internal/daemon/api/grpc/api_grpc.pb.go b/internal/daemon/api/grpc/api_grpc.pb.go new file mode 100644 index 0000000..b1dd4ac --- /dev/null +++ b/internal/daemon/api/grpc/api_grpc.pb.go @@ -0,0 +1,209 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ControlClient is the client API for Control service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ControlClient interface { + Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error) + SetupTest(ctx context.Context, in *SetupTestRequest, opts ...grpc.CallOption) (*SetupTestResponse, error) + ExecuteTest(ctx context.Context, in *ExecuteTestRequest, opts ...grpc.CallOption) (*ExecuteTestResponse, error) + TeardownTest(ctx context.Context, in *TeardownTestRequest, opts ...grpc.CallOption) (*TeardownTestResponse, error) +} + +type controlClient struct { + cc grpc.ClientConnInterface +} + +func NewControlClient(cc grpc.ClientConnInterface) ControlClient { + return &controlClient{cc} +} + +func (c *controlClient) Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error) { + out := new(ShutdownResponse) + err := c.cc.Invoke(ctx, "/grpc.Control/Shutdown", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *controlClient) SetupTest(ctx context.Context, in *SetupTestRequest, opts ...grpc.CallOption) (*SetupTestResponse, error) { + out := new(SetupTestResponse) + err := c.cc.Invoke(ctx, "/grpc.Control/SetupTest", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *controlClient) ExecuteTest(ctx context.Context, in *ExecuteTestRequest, opts ...grpc.CallOption) (*ExecuteTestResponse, error) { + out := new(ExecuteTestResponse) + err := c.cc.Invoke(ctx, "/grpc.Control/ExecuteTest", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *controlClient) TeardownTest(ctx context.Context, in *TeardownTestRequest, opts ...grpc.CallOption) (*TeardownTestResponse, error) { + out := new(TeardownTestResponse) + err := c.cc.Invoke(ctx, "/grpc.Control/TeardownTest", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ControlServer is the server API for Control service. +// All implementations must embed UnimplementedControlServer +// for forward compatibility +type ControlServer interface { + Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) + SetupTest(context.Context, *SetupTestRequest) (*SetupTestResponse, error) + ExecuteTest(context.Context, *ExecuteTestRequest) (*ExecuteTestResponse, error) + TeardownTest(context.Context, *TeardownTestRequest) (*TeardownTestResponse, error) + mustEmbedUnimplementedControlServer() +} + +// UnimplementedControlServer must be embedded to have forward compatible implementations. +type UnimplementedControlServer struct { +} + +func (UnimplementedControlServer) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Shutdown not implemented") +} +func (UnimplementedControlServer) SetupTest(context.Context, *SetupTestRequest) (*SetupTestResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetupTest not implemented") +} +func (UnimplementedControlServer) ExecuteTest(context.Context, *ExecuteTestRequest) (*ExecuteTestResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ExecuteTest not implemented") +} +func (UnimplementedControlServer) TeardownTest(context.Context, *TeardownTestRequest) (*TeardownTestResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TeardownTest not implemented") +} +func (UnimplementedControlServer) mustEmbedUnimplementedControlServer() {} + +// UnsafeControlServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ControlServer will +// result in compilation errors. +type UnsafeControlServer interface { + mustEmbedUnimplementedControlServer() +} + +func RegisterControlServer(s grpc.ServiceRegistrar, srv ControlServer) { + s.RegisterService(&Control_ServiceDesc, srv) +} + +func _Control_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ShutdownRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServer).Shutdown(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Control/Shutdown", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServer).Shutdown(ctx, req.(*ShutdownRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Control_SetupTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetupTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServer).SetupTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Control/SetupTest", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServer).SetupTest(ctx, req.(*SetupTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Control_ExecuteTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExecuteTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServer).ExecuteTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Control/ExecuteTest", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServer).ExecuteTest(ctx, req.(*ExecuteTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Control_TeardownTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TeardownTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ControlServer).TeardownTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.Control/TeardownTest", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ControlServer).TeardownTest(ctx, req.(*TeardownTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Control_ServiceDesc is the grpc.ServiceDesc for Control service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Control_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.Control", + HandlerType: (*ControlServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Shutdown", + Handler: _Control_Shutdown_Handler, + }, + { + MethodName: "SetupTest", + Handler: _Control_SetupTest_Handler, + }, + { + MethodName: "ExecuteTest", + Handler: _Control_ExecuteTest_Handler, + }, + { + MethodName: "TeardownTest", + Handler: _Control_TeardownTest_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api.proto", +} diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go new file mode 100644 index 0000000..2fd6394 --- /dev/null +++ b/internal/daemon/controller/controller.go @@ -0,0 +1,198 @@ +package controller + +import ( + "io/ioutil" + "os" + "path" + + "gopkg.in/yaml.v2" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/template" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +const LogstashInstanceDirectoryPrefix = "logstash-instance" + +type Controller struct { + id string + + workDir string + log logging.Logger + + shutdown chan struct{} + + instance Instance + + stateMachine *stateMachine + receivedEvents *events + pipelines *pipelines +} + +func NewController(instance Instance, baseDir string, log logging.Logger, shutdown chan struct{}) (*Controller, error) { + id := idgen.New() + + workDir := path.Join(baseDir, LogstashInstanceDirectoryPrefix, id) + + templateData := struct { + WorkDir string + }{ + WorkDir: workDir, + } + + err := os.MkdirAll(workDir, 0700) + if err != nil { + return nil, err + } + + templates := map[string]string{ + "logstash.yml": logstashConfig, + "log4j2.properties": log4j2Config, + "stdin.conf": stdinPipeline, + "output.conf": outputPipeline, + } + + for filename, tmpl := range templates { + err = template.ToFile(path.Join(workDir, filename), tmpl, templateData, 0600) + if err != nil { + return nil, err + } + } + + controller := Controller{ + id: id, + workDir: workDir, + log: log, + shutdown: shutdown, + instance: instance, + + stateMachine: newStateMachine(shutdown, log), + receivedEvents: newEvents(), + pipelines: newPipelines(), + } + + err = controller.writePipelines() + if err != nil { + return nil, err + } + + return &controller, nil +} + +func (c *Controller) ID() string { + return c.id +} + +func (c *Controller) Launch() error { + c.pipelines.reset("stdin", "output") + c.stateMachine.executeCommand(commandStart) + + err := c.instance.Start(c, c.workDir) + if err != nil { + c.instance.Shutdown() + return err + } + + return nil +} + +func (c *Controller) SetupTest(pipelines pipeline.Pipelines) error { + err := c.stateMachine.waitForState(stateReady) + if err != nil { + return err + } + + c.stateMachine.executeCommand(commandSetupTest) + + return c.reload(pipelines, 0) +} + +func (c *Controller) ExecuteTest(pipelines pipeline.Pipelines, expectedEvents int) error { + err := c.stateMachine.waitForState(stateReadyForTest) + if err != nil { + return err + } + + c.stateMachine.executeCommand(commandExecuteTest) + + return c.reload(pipelines, expectedEvents) +} + +func (c *Controller) GetResults() ([]string, error) { + err := c.stateMachine.waitForState(stateReadyForTest) + if err != nil { + return nil, err + } + + return c.receivedEvents.get(), nil +} + +func (c *Controller) Teardown() error { + err := c.stateMachine.waitForState(stateReadyForTest) + if err != nil { + return err + } + + c.stateMachine.executeCommand(commandTeardown) + + return c.reload(nil, 0) +} + +func (c *Controller) reload(pipelines pipeline.Pipelines, expectedEvents int) error { + err := c.writePipelines(pipelines...) + if err != nil { + return err + } + + pipelineNames := make([]string, 0, len(pipelines)) + for _, pipeline := range pipelines { + pipelineNames = append(pipelineNames, pipeline.ID) + } + + c.receivedEvents.reset(expectedEvents) + c.pipelines.reset(pipelineNames...) + + err = c.instance.ConfigReload() + return err +} + +func (c *Controller) writePipelines(pipelines ...pipeline.Pipeline) error { + basePipelines := basePipelines(c.workDir) + + pipelines = append(basePipelines, pipelines...) + + pipelinesBody, err := yaml.Marshal(pipelines) + if err != nil { + return err + } + + err = ioutil.WriteFile(path.Join(c.workDir, "pipelines.yml"), pipelinesBody, 0600) + if err != nil { + return err + } + + return nil +} + +func (c *Controller) ReceiveEvent(event string) error { + c.receivedEvents.append(event) + + if c.receivedEvents.isComplete() { + err := c.stateMachine.waitForState(stateRunningTest) + if err != nil { + return err + } + + c.stateMachine.executeCommand(commandTestComplete) + } + + return nil +} + +func (c *Controller) PipelinesReady(pipelines ...string) { + c.pipelines.setReady(pipelines...) + if c.pipelines.isReady() { + c.stateMachine.executeCommand(commandPipelineReady) + } +} diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go new file mode 100644 index 0000000..316d6a4 --- /dev/null +++ b/internal/daemon/controller/controller_test.go @@ -0,0 +1,218 @@ +package controller_test + +import ( + "errors" + "path" + "testing" + + "github.com/matryer/is" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/controller" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/instance/mock" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func TestNewController(t *testing.T) { + cases := []struct { + name string + }{ + { + name: "success", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + tempdir := t.TempDir() + + c, err := controller.NewController(nil, tempdir, logging.NoopLogger, nil) + is.NoErr(err) + + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "log4j2.properties"))) // log4j2.properties + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "stdin.conf"))) // stdin.conf + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "output.conf"))) // output.conf + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml + }) + } +} + +func TestLaunch(t *testing.T) { + cases := []struct { + name string + instanceStartErr error + + wantErr bool + }{ + { + name: "success", + }, + { + name: "instance start error", + instanceStartErr: errors.New("error"), + + wantErr: true, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + instance := &mock.InstanceMock{ + StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { + return test.instanceStartErr + }, + ShutdownFunc: func() {}, + } + + tempdir := t.TempDir() + + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, nil) + is.NoErr(err) + + err = c.Launch() + is.True(err != nil == test.wantErr) // Launch error + }) + } +} + +func TestCompleteCycle(t *testing.T) { + cases := []struct { + name string + }{ + { + name: "success", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + instance := &mock.InstanceMock{ + StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { + return nil + }, + ConfigReloadFunc: func() error { + return nil + }, + } + + tempdir := t.TempDir() + + shutdown := make(chan struct{}) + + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, shutdown) + is.NoErr(err) + + err = c.Launch() + is.NoErr(err) + + // Simulate pipelines ready from instance + c.PipelinesReady("stdin", "output") + + pipelines := pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "main", + Config: "main.conf", + Ordered: "true", + Workers: 1, + }, + } + + err = c.SetupTest(pipelines) + is.NoErr(err) + + // Simulate pipelines ready from instance + c.PipelinesReady("stdin", "output", "main") + + pipelines = append(pipelines, pipeline.Pipeline{ + ID: "input", + Config: "input.conf", + Ordered: "true", + Workers: 1, + }) + + err = c.ExecuteTest(pipelines, 2) + is.NoErr(err) + + // Simulate pipelines ready from instance + c.PipelinesReady("stdin", "output", "main", "input") + err = c.ReceiveEvent("result 1") + is.NoErr(err) + err = c.ReceiveEvent("result 2") + is.NoErr(err) + + res, err := c.GetResults() + is.NoErr(err) + is.Equal(2, len(res)) + + // Test content of pipeline.yml + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml + is.True(file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: main")) // pipelines.yml contains "id: main" + is.True(file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: input")) // pipelines.yml contains "id: input" + + err = c.Teardown() + is.NoErr(err) + + // Test if pipelines are reomved from pipeline.yml + is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml + is.True(!file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: main")) // pipelines.yml contains "id: main" + is.True(!file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: input")) // pipelines.yml contains "id: input" + }) + } +} + +func TestSetupTest_Shutdown(t *testing.T) { + cases := []struct { + name string + }{ + { + name: "success", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + instance := &mock.InstanceMock{ + StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { + return nil + }, + ConfigReloadFunc: func() error { + return nil + }, + } + + tempdir := t.TempDir() + shutdown := make(chan struct{}) + + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, shutdown) + is.NoErr(err) + + err = c.Launch() + is.NoErr(err) + + // Simulate shutdown signal + close(shutdown) + + pipelines := pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "main", + Config: "main.conf", + Ordered: "true", + Workers: 1, + }, + } + + err = c.SetupTest(pipelines) + is.True(err != nil) // expect shutdown error + }) + } +} diff --git a/internal/daemon/controller/events.go b/internal/daemon/controller/events.go new file mode 100644 index 0000000..470f85c --- /dev/null +++ b/internal/daemon/controller/events.go @@ -0,0 +1,48 @@ +package controller + +import "sync" + +type events struct { + events []string + expected int + mutex *sync.Mutex +} + +func newEvents() *events { + return &events{ + events: make([]string, 0, 100), + mutex: &sync.Mutex{}, + } +} + +func (e *events) append(event string) { + e.mutex.Lock() + defer e.mutex.Unlock() + + e.events = append(e.events, event) +} + +func (e *events) isComplete() bool { + e.mutex.Lock() + defer e.mutex.Unlock() + + return e.expected == len(e.events) +} + +func (e *events) reset(expected int) { + e.mutex.Lock() + defer e.mutex.Unlock() + + e.expected = expected + e.events = make([]string, 0, 100) +} + +func (e *events) get() []string { + e.mutex.Lock() + defer e.mutex.Unlock() + + results := make([]string, 0, len(e.events)) + results = append(results, e.events...) + + return results +} diff --git a/internal/daemon/controller/files.go b/internal/daemon/controller/files.go new file mode 100644 index 0000000..dca211a --- /dev/null +++ b/internal/daemon/controller/files.go @@ -0,0 +1,77 @@ +package controller + +import ( + "path" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" +) + +// TODO: With Go 1.16, this can be moved to embed files + +const logstashConfig = ` +pipeline.unsafe_shutdown: true +` + +// TODO: Why is Logstash still printing +// Sending Logstash logs to .../3rdparty/logstash-7.10.0/logs which is now configured via log4j2.properties +// to stdout on startup? +const log4j2Config = `status = error +name = LogstashPropertiesConfig + +appender.json_file.type = File +appender.json_file.name = json_file +appender.json_file.fileName = {{ .WorkDir }}/logstash.log +appender.json_file.layout.type = JSONLayout +appender.json_file.layout.compact = true +appender.json_file.layout.eventEol = true + +rootLogger.level = ${sys:ls.log.level} +rootLogger.appenderRef.console.ref = json_file +` + +// Dummy pipeline, which prevents logstash from stopping when all inputs have +// been finished/drained. +const stdinPipeline = `input { stdin {} } +output { stdout { } } +` + +const outputPipeline = `input { + pipeline { + address => __lfv_output + } +} +# filter { +# mutate { +# copy => { +# "[@metadata]" => "[__metadata]" +# } +# } +# ruby { +# code => 'metadata = event.get("__metadata") +# metadata.delete_if {|key, value| key.start_with?("__lfv") || key.start_with?("__tmp") } +# event.set("__metadata", metadata)' +# } +# } +output { + stdout { + codec => json_lines + } +} +` + +func basePipelines(workDir string) pipeline.Pipelines { + return pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "stdin", + Config: path.Join(workDir, "stdin.conf"), + Ordered: "true", + Workers: 1, + }, + pipeline.Pipeline{ + ID: "output", + Config: path.Join(workDir, "output.conf"), + Ordered: "true", + Workers: 1, + }, + } +} diff --git a/internal/daemon/controller/instance.go b/internal/daemon/controller/instance.go new file mode 100644 index 0000000..1f6dbe8 --- /dev/null +++ b/internal/daemon/controller/instance.go @@ -0,0 +1,9 @@ +package controller + +//go:generate moq -fmt goimports -pkg mock -out ../instance/mock/logstash_instance_mock.go . Instance + +type Instance interface { + Start(controller *Controller, workdir string) error + Shutdown() + ConfigReload() error +} diff --git a/internal/daemon/controller/pipelines.go b/internal/daemon/controller/pipelines.go new file mode 100644 index 0000000..3e96bb3 --- /dev/null +++ b/internal/daemon/controller/pipelines.go @@ -0,0 +1,44 @@ +package controller + +import "sync" + +type pipelines struct { + pipelines map[string]bool + mutex *sync.Mutex +} + +func newPipelines() *pipelines { + return &pipelines{ + pipelines: make(map[string]bool), + mutex: &sync.Mutex{}, + } +} + +func (p *pipelines) reset(pipelines ...string) { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.pipelines = make(map[string]bool, len(pipelines)) + + for _, pipeline := range pipelines { + p.pipelines[pipeline] = false + } +} + +func (p *pipelines) setReady(pipelines ...string) { + p.mutex.Lock() + defer p.mutex.Unlock() + + for _, pipeline := range pipelines { + p.pipelines[pipeline] = true + } +} + +func (p *pipelines) isReady() bool { + for _, ready := range p.pipelines { + if !ready { + return false + } + } + return true +} diff --git a/internal/daemon/controller/statemachine.go b/internal/daemon/controller/statemachine.go new file mode 100644 index 0000000..6320ab1 --- /dev/null +++ b/internal/daemon/controller/statemachine.go @@ -0,0 +1,134 @@ +package controller + +import ( + "sync" + + "github.com/pkg/errors" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type stateMachine struct { + currentState stateName + mutex *sync.Mutex + cond *sync.Cond + + shutdown chan struct{} + log logging.Logger +} + +func newStateMachine(shutdown chan struct{}, log logging.Logger) *stateMachine { + mu := &sync.Mutex{} + cond := sync.NewCond(mu) + go func() { + <-shutdown + log.Debug("broadcast shutdown for waitForState") + cond.Broadcast() + }() + return &stateMachine{ + currentState: stateCreated, + mutex: mu, + cond: cond, + shutdown: shutdown, + log: log, + } +} + +func (s *stateMachine) waitForState(target stateName) error { + s.log.Debugf("waitForState: %v", target) + // TODO: Add a timeout to exit if the expected state is not reached in due time. + // Create go routine to wake up every 1 second, + s.mutex.Lock() + defer s.mutex.Unlock() + + for s.currentState != target { + s.cond.Wait() + + select { + case <-s.shutdown: + // TODO: Can we do this without error return? + return errors.Errorf("shutdown while waiting for state: %s", target) + default: + } + } + return nil +} + +func (s *stateMachine) executeCommand(command command) { + s.mutex.Lock() + defer s.mutex.Unlock() + + currentState := s.currentState + tupple := stateCommand{currentState, command} + newState := stateUnknown + f := stateTransitionsTable[tupple] + if f != nil { + newState = f() + } + + s.log.Debugf("state change: %q -> %q by %q", currentState, newState, command) + s.currentState = newState + s.cond.Broadcast() +} + +type stateName string + +const ( + stateUnknown stateName = "unknown" + + stateCreated stateName = "created" + stateStarted stateName = "started" + stateReady stateName = "ready" + stateSettingUpTest stateName = "setting_up_test" + stateReadyForTest stateName = "ready_for_test" + stateExecutingTest stateName = "executing_test" + stateRunningTest stateName = "running_test" +) + +func (s stateName) String() string { + return string(s) +} + +type command string + +const ( + commandStart command = "start" + commandPipelineReady command = "pipeline-ready" + commandSetupTest command = "setup-test" + commandExecuteTest command = "execute-test" + commandTestComplete command = "test-complete" + commandTeardown command = "teardown" +) + +type stateCommand struct { + state stateName + cmd command +} + +type transitionFunc func() stateName + +var stateTransitionsTable = map[stateCommand]transitionFunc{ + // State Created + {stateCreated, commandStart}: func() stateName { return stateStarted }, + + // State Started + {stateStarted, commandPipelineReady}: func() stateName { return stateReady }, + + // State Ready + {stateReady, commandPipelineReady}: func() stateName { return stateReady }, + {stateReady, commandSetupTest}: func() stateName { return stateSettingUpTest }, + + // State LoadingSUT + {stateSettingUpTest, commandPipelineReady}: func() stateName { return stateReadyForTest }, + + // State Ready for Test + {stateReadyForTest, commandExecuteTest}: func() stateName { return stateExecutingTest }, + {stateReadyForTest, commandTeardown}: func() stateName { return stateStarted }, + + // State Starting Test + {stateExecutingTest, commandPipelineReady}: func() stateName { return stateRunningTest }, + + // State Running Test + {stateRunningTest, commandPipelineReady}: func() stateName { return stateRunningTest }, + {stateRunningTest, commandTestComplete}: func() stateName { return stateReadyForTest }, +} diff --git a/internal/daemon/file/file.go b/internal/daemon/file/file.go new file mode 100644 index 0000000..4751729 --- /dev/null +++ b/internal/daemon/file/file.go @@ -0,0 +1,20 @@ +package file + +import ( + "bytes" + "io/ioutil" + "os" +) + +func Exists(filename string) bool { + _, err := os.Stat(filename) + return !os.IsNotExist(err) +} + +func Contains(filename string, needle string) bool { + body, err := ioutil.ReadFile(filename) + if err != nil { + return false + } + return bytes.Contains(body, []byte(needle)) +} diff --git a/internal/daemon/idgen/idgen.go b/internal/daemon/idgen/idgen.go new file mode 100644 index 0000000..b3e2d55 --- /dev/null +++ b/internal/daemon/idgen/idgen.go @@ -0,0 +1,14 @@ +package idgen + +import ( + nanoid "github.com/matoous/go-nanoid" +) + +const ( + alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + size = 8 +) + +func New() string { + return nanoid.MustGenerate(alphabet, size) +} diff --git a/internal/daemon/instance/logstash/dummy_reader.go b/internal/daemon/instance/logstash/dummy_reader.go new file mode 100644 index 0000000..2be6dc6 --- /dev/null +++ b/internal/daemon/instance/logstash/dummy_reader.go @@ -0,0 +1,19 @@ +package logstash + +import "io" + +// stdinBlockReader implements the io.Reader interface and blocks reading +// until the shutdown channel unblocks (close of channel). +// After the shutdown channel is unblocked, the Read function +// returns io.EOF to signal the end of the input stream +// +// This stdinBlockReader is used to block stdin of the controlled +// Logstash instance. +type stdinBlockReader struct { + shutdown chan struct{} +} + +func (s *stdinBlockReader) Read(_ []byte) (int, error) { + <-s.shutdown + return 0, io.EOF +} diff --git a/internal/daemon/instance/logstash/instance.go b/internal/daemon/instance/logstash/instance.go new file mode 100644 index 0000000..a5ce42b --- /dev/null +++ b/internal/daemon/instance/logstash/instance.go @@ -0,0 +1,139 @@ +package logstash + +import ( + "os" + "os/exec" + "sync" + "syscall" + + "github.com/hpcloud/tail" + "github.com/pkg/errors" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/controller" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type instance struct { + controller *controller.Controller + + command string + child *exec.Cmd + + log logging.Logger + + logstashStarted chan struct{} + shutdown chan struct{} + instanceShutdown chan struct{} + logstashShutdownWG *sync.WaitGroup + shutdownWG *sync.WaitGroup +} + +func New(command string, log logging.Logger, shutdown chan struct{}, shutdownWG *sync.WaitGroup) controller.Instance { + return &instance{ + command: command, + log: log, + logstashStarted: make(chan struct{}), + shutdown: shutdown, + logstashShutdownWG: &sync.WaitGroup{}, + shutdownWG: shutdownWG, + } +} + +// start starts a Logstash child process with the previously supplied +// configuration. +func (i *instance) Start(controller *controller.Controller, workdir string) error { + i.controller = controller + + args := []string{ + "--path.settings", + workdir, + } + + i.child = exec.Command(i.command, args...) + stdout, err := i.child.StdoutPipe() + if err != nil { + return errors.Wrap(err, "failed to setup stdoutPipe") + } + stderr, err := i.child.StderrPipe() + if err != nil { + return errors.Wrap(err, "failed to setup stdoutPipe") + } + i.instanceShutdown = make(chan struct{}) + i.child.Stdin = &stdinBlockReader{ + shutdown: i.instanceShutdown, + } + + i.logstashShutdownWG.Add(2) + go i.stdoutProcessor(stdout) + go i.stderrProcessor(stderr) + + err = i.child.Start() + if err != nil { + return errors.Wrap(err, "failed to start logstash process") + } + close(i.logstashStarted) // signal stdout and stderr scanners to start + + logfile := workdir + "/logstash.log" + _ = os.Remove(logfile) + t, err := tail.TailFile(logfile, tail.Config{Follow: true, Logger: tailLogger{i.log}}) + if err != nil { + return errors.Wrap(err, "failed to read from logstash log file") + } + + i.logstashShutdownWG.Add(1) + go i.logstashLogProcessor(t) + + // stop process and free all allocated resources connected to this process. + go i.shutdownSignalHandler() + + return nil +} + +// TODO: What is needed for what? +func (i *instance) shutdownSignalHandler() { + // Wait for shutdown signal coming from the daemon. + <-i.shutdown + + i.Shutdown() +} + +// TODO: What is needed for what? +func (i *instance) Shutdown() { + close(i.instanceShutdown) + + i.stopLogstash() + + i.logstashShutdownWG.Wait() + + i.shutdownWG.Done() +} + +func (i *instance) stopLogstash() { + if i.child.Process == nil { + return + } + + err := i.child.Process.Signal(syscall.SIGTERM) + if err != nil { + i.log.Errorf("failed to send SIGTERM, Logstash might already be down:", err) + } + + // TODO: Add timeout, then send syscall.SIGKILL + err = i.child.Wait() + if err != nil { + i.log.Errorf("failed to wait for child process: %v", err) + } +} + +func (i *instance) ConfigReload() error { + if i.child.Process == nil { + return errors.New("can't signal to an unborn process") + } + + err := i.child.Process.Signal(syscall.SIGHUP) + if err != nil { + return errors.Wrap(err, "failed to send SIGHUP") + } + + return nil +} diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go new file mode 100644 index 0000000..25a607e --- /dev/null +++ b/internal/daemon/instance/logstash/processors.go @@ -0,0 +1,118 @@ +package logstash + +import ( + "bufio" + "io" + "strings" + + "github.com/hpcloud/tail" + "github.com/tidwall/gjson" +) + +func (i *instance) stdoutProcessor(stdout io.ReadCloser) { + defer i.logstashShutdownWG.Done() + + // The stdoutProcessor can only be started after the process is created. + select { + case <-i.logstashStarted: + case <-i.instanceShutdown: + return + } + + i.log.Debug("start stdout scanner") + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + i.log.Debugf("stdout: %s", scanner.Text()) + + // Only events starting with "{" are accepted. All other output is discarded. + if !strings.HasPrefix(scanner.Text(), "{") { + continue + } + + err := i.controller.ReceiveEvent(scanner.Text()) + if err != nil { + // Shutdown signal received in waitForState + break + } + } + if err := scanner.Err(); err != nil { + i.log.Error("reading standard output:", err) + } + + // Termination of stdout scanner is only expected, if shutdown is in progress. + select { + case <-i.instanceShutdown: + default: + i.log.Warning("stdout scanner closed unexpectetly") + } + + i.log.Debug("exit stdout scanner") +} + +func (i *instance) stderrProcessor(stderr io.ReadCloser) { + defer i.logstashShutdownWG.Done() + + // The stderrProcessor can only be started after the process is created. + select { + case <-i.logstashStarted: + case <-i.instanceShutdown: + return + } + + i.log.Debug("start stderr scanner") + + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + i.log.Debugf("stderr: %s", scanner.Text()) + } + if err := scanner.Err(); err != nil { + i.log.Error("reading standard err:", err) + } + + // Termination of stderr scanner is only expected, if shutdown is in progress. + select { + case <-i.instanceShutdown: + default: + i.log.Warning("stderr scanner closed unexpectetly") + } + + i.log.Debug("exit stderr scanner") +} + +func (i *instance) logstashLogProcessor(t *tail.Tail) { + defer i.logstashShutdownWG.Done() + + for { + select { + case line := <-t.Lines: + switch gjson.Get(line.Text, "logEvent.message").String() { + case "Pipeline started": + pipelineID := gjson.Get(line.Text, `logEvent.pipeline\.id`).String() + i.log.Debugf("taillog: -> pipeline started: %s", pipelineID) + + i.controller.PipelinesReady(pipelineID) + case "Pipelines running": + rp := gjson.Get(line.Text, `logEvent.running_pipelines.0.metaClass.metaClass.metaClass.running_pipelines`).String() + runningPipelines := extractPipelines(rp) + i.log.Debugf("taillog: -> pipeline running: %v", runningPipelines) + + i.controller.PipelinesReady(runningPipelines...) + } + case <-i.instanceShutdown: + i.log.Debug("shutdown log reader") + return + } + } +} + +func extractPipelines(in string) []string { + if len(in) < 3 { + return nil + } + pipelines := strings.Split(in[1:len(in)-1], ",") + for i := range pipelines { + pipelines[i] = strings.Trim(pipelines[i], " :") + } + return pipelines +} diff --git a/internal/daemon/instance/logstash/tail_logger.go b/internal/daemon/instance/logstash/tail_logger.go new file mode 100644 index 0000000..f3f5fd1 --- /dev/null +++ b/internal/daemon/instance/logstash/tail_logger.go @@ -0,0 +1,17 @@ +package logstash + +import "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + +type tailLogger struct { + log logging.Logger +} + +func (t tailLogger) Fatal(v ...interface{}) { t.log.Fatal(v...) } +func (t tailLogger) Fatalf(format string, v ...interface{}) { t.log.Fatalf(format, v...) } +func (t tailLogger) Fatalln(v ...interface{}) { t.log.Fatal(v...) } +func (t tailLogger) Panic(v ...interface{}) { t.log.Error(v...) } +func (t tailLogger) Panicf(format string, v ...interface{}) { t.log.Errorf(format, v...); panic("") } +func (t tailLogger) Panicln(v ...interface{}) { t.log.Error(v...); panic("") } +func (t tailLogger) Print(v ...interface{}) { t.log.Info(v...) } +func (t tailLogger) Printf(format string, v ...interface{}) { t.log.Infof(format, v...) } +func (t tailLogger) Println(v ...interface{}) { t.log.Info(v...) } diff --git a/internal/daemon/instance/mock/logstash_instance_mock.go b/internal/daemon/instance/mock/logstash_instance_mock.go new file mode 100644 index 0000000..58b6b89 --- /dev/null +++ b/internal/daemon/instance/mock/logstash_instance_mock.go @@ -0,0 +1,145 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package mock + +import ( + "sync" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/controller" +) + +// Ensure, that InstanceMock does implement controller.Instance. +// If this is not the case, regenerate this file with moq. +var _ controller.Instance = &InstanceMock{} + +// InstanceMock is a mock implementation of controller.Instance. +// +// func TestSomethingThatUsesInstance(t *testing.T) { +// +// // make and configure a mocked controller.Instance +// mockedInstance := &InstanceMock{ +// ConfigReloadFunc: func() error { +// panic("mock out the ConfigReload method") +// }, +// ShutdownFunc: func() { +// panic("mock out the Shutdown method") +// }, +// StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { +// panic("mock out the Start method") +// }, +// } +// +// // use mockedInstance in code that requires controller.Instance +// // and then make assertions. +// +// } +type InstanceMock struct { + // ConfigReloadFunc mocks the ConfigReload method. + ConfigReloadFunc func() error + + // ShutdownFunc mocks the Shutdown method. + ShutdownFunc func() + + // StartFunc mocks the Start method. + StartFunc func(controllerMoqParam *controller.Controller, workdir string) error + + // calls tracks calls to the methods. + calls struct { + // ConfigReload holds details about calls to the ConfigReload method. + ConfigReload []struct{} + // Shutdown holds details about calls to the Shutdown method. + Shutdown []struct{} + // Start holds details about calls to the Start method. + Start []struct { + // ControllerMoqParam is the controllerMoqParam argument value. + ControllerMoqParam *controller.Controller + // Workdir is the workdir argument value. + Workdir string + } + } + lockConfigReload sync.RWMutex + lockShutdown sync.RWMutex + lockStart sync.RWMutex +} + +// ConfigReload calls ConfigReloadFunc. +func (mock *InstanceMock) ConfigReload() error { + if mock.ConfigReloadFunc == nil { + panic("InstanceMock.ConfigReloadFunc: method is nil but Instance.ConfigReload was just called") + } + callInfo := struct{}{} + mock.lockConfigReload.Lock() + mock.calls.ConfigReload = append(mock.calls.ConfigReload, callInfo) + mock.lockConfigReload.Unlock() + return mock.ConfigReloadFunc() +} + +// ConfigReloadCalls gets all the calls that were made to ConfigReload. +// Check the length with: +// len(mockedInstance.ConfigReloadCalls()) +func (mock *InstanceMock) ConfigReloadCalls() []struct{} { + var calls []struct{} + mock.lockConfigReload.RLock() + calls = mock.calls.ConfigReload + mock.lockConfigReload.RUnlock() + return calls +} + +// Shutdown calls ShutdownFunc. +func (mock *InstanceMock) Shutdown() { + if mock.ShutdownFunc == nil { + panic("InstanceMock.ShutdownFunc: method is nil but Instance.Shutdown was just called") + } + callInfo := struct{}{} + mock.lockShutdown.Lock() + mock.calls.Shutdown = append(mock.calls.Shutdown, callInfo) + mock.lockShutdown.Unlock() + mock.ShutdownFunc() +} + +// ShutdownCalls gets all the calls that were made to Shutdown. +// Check the length with: +// len(mockedInstance.ShutdownCalls()) +func (mock *InstanceMock) ShutdownCalls() []struct{} { + var calls []struct{} + mock.lockShutdown.RLock() + calls = mock.calls.Shutdown + mock.lockShutdown.RUnlock() + return calls +} + +// Start calls StartFunc. +func (mock *InstanceMock) Start(controllerMoqParam *controller.Controller, workdir string) error { + if mock.StartFunc == nil { + panic("InstanceMock.StartFunc: method is nil but Instance.Start was just called") + } + callInfo := struct { + ControllerMoqParam *controller.Controller + Workdir string + }{ + ControllerMoqParam: controllerMoqParam, + Workdir: workdir, + } + mock.lockStart.Lock() + mock.calls.Start = append(mock.calls.Start, callInfo) + mock.lockStart.Unlock() + return mock.StartFunc(controllerMoqParam, workdir) +} + +// StartCalls gets all the calls that were made to Start. +// Check the length with: +// len(mockedInstance.StartCalls()) +func (mock *InstanceMock) StartCalls() []struct { + ControllerMoqParam *controller.Controller + Workdir string +} { + var calls []struct { + ControllerMoqParam *controller.Controller + Workdir string + } + mock.lockStart.RLock() + calls = mock.calls.Start + mock.lockStart.RUnlock() + return calls +} diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go new file mode 100644 index 0000000..4fb0a48 --- /dev/null +++ b/internal/daemon/logstashconfig/file.go @@ -0,0 +1,118 @@ +package logstashconfig + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path" + + config "github.com/breml/logstash-config" + "github.com/breml/logstash-config/ast" + "github.com/breml/logstash-config/ast/astutil" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" +) + +type File struct { + Name string + Body []byte + + config *ast.Config +} + +func (f File) Save(targetDir string) error { + err := os.MkdirAll(path.Join(targetDir, path.Dir(f.Name)), 0700) + if err != nil { + return err + } + + err = ioutil.WriteFile(path.Join(targetDir, f.Name), f.Body, 0600) + if err != nil { + return err + } + + return nil +} + +func (f *File) parse() error { + if f.config != nil { + return nil + } + + icfg, err := config.Parse(f.Name, f.Body) + if err != nil { + return err + } + + cfg, ok := icfg.(ast.Config) + if !ok { + return errors.New("not a valid logstash config") + } + + f.config = &cfg + + return nil +} + +func (f *File) ReplaceInputs() error { + err := f.parse() + if err != nil { + return err + } + + for i := range f.config.Input { + astutil.ApplyPlugins(f.config.Input[i].BranchOrPlugins, replaceInputs) + } + + f.Body = []byte(f.config.String()) + + return nil +} + +func replaceInputs(c *astutil.Cursor) { + if c.Plugin() == nil || c.Plugin().Name() == "pipeline" { + return + } + + // TODO: __lfv_input must reflect the actual input, that has been replaced, such that this input + // can be referenced in the test case configuration. + c.Replace(ast.NewPlugin("pipeline", ast.NewStringAttribute("address", "__lfv_input", ast.Bareword))) +} + +func (f *File) ReplaceOutputs() ([]string, error) { + err := f.parse() + if err != nil { + return nil, err + } + + outputs := outputPipelineReplacer{ + outputs: make([]string, 0), + } + for i := range f.config.Output { + astutil.ApplyPlugins(f.config.Output[i].BranchOrPlugins, outputs.walk) + } + + f.Body = []byte(f.config.String()) + + return outputs.outputs, nil +} + +type outputPipelineReplacer struct { + outputs []string +} + +func (o *outputPipelineReplacer) walk(c *astutil.Cursor) { + if c.Plugin() == nil || c.Plugin().Name() == "pipeline" { + return + } + + id, err := c.Plugin().ID() + if err != nil { + id = idgen.New() + } + outputName := fmt.Sprintf("lfv_output_%s", id) + o.outputs = append(o.outputs, id) + + c.Replace(ast.NewPlugin("pipeline", ast.NewArrayAttribute("send_to", ast.NewStringAttribute("", outputName, ast.DoubleQuoted)))) +} diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go new file mode 100644 index 0000000..b7fef45 --- /dev/null +++ b/internal/daemon/logstashconfig/file_test.go @@ -0,0 +1,169 @@ +package logstashconfig_test + +import ( + "path" + "strings" + "testing" + + "github.com/matryer/is" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" +) + +func TestSave(t *testing.T) { + cases := []struct { + name string + }{ + { + name: "successful", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + tempdir := t.TempDir() + + f := logstashconfig.File{ + Name: "test.conf", + Body: []byte("test"), + } + + err := f.Save(tempdir) + is.NoErr(err) + + is.True(file.Exists(path.Join(tempdir, f.Name))) // test.conf + is.True(file.Contains(path.Join(tempdir, f.Name), string(f.Body))) // test.conf contains "test" + }) + } +} + +func TestReplaceInputs(t *testing.T) { + cases := []struct { + name string + config string + + wantConfig string + }{ + { + name: "successful replacement", + config: "input { stdin{} }", + + wantConfig: `input { + pipeline { + address => __lfv_input + } +} +`, + }, + { + name: "successful untouched pipeline input", + config: "input { pipeline{} }", + + wantConfig: `input { + pipeline {} +} +`, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + f := logstashconfig.File{ + Body: []byte(test.config), + } + + err := f.ReplaceInputs() + is.NoErr(err) + + is.Equal(test.wantConfig, string(f.Body)) + }) + } +} + +func TestReplaceOutputs(t *testing.T) { + cases := []struct { + name string + config string + + wantOutputs []string + wantConfig string + }{ + { + name: "successful replace", + config: "output { stdout{ id => testid } }", + + wantOutputs: []string{"testid"}, + wantConfig: `output { + pipeline { + send_to => [ "lfv_output_testid" ] + } +} +`, + }, + { + name: "successful untouched pipeline output", + config: "output { pipeline{ id => testid } }", + + wantOutputs: []string{}, + wantConfig: `output { + pipeline { + id => testid + } +} +`, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + f := logstashconfig.File{ + Body: []byte(test.config), + } + + outputs, err := f.ReplaceOutputs() + is.NoErr(err) + + is.Equal(test.wantOutputs, outputs) + is.Equal(test.wantConfig, string(f.Body)) + }) + } +} + +func TestReplaceOutputsWithoutID(t *testing.T) { + cases := []struct { + name string + config string + + wantOutputs []string + wantConfig string + }{ + { + name: "successful replacement without id", + config: "output { stdout{} }", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + f := logstashconfig.File{ + Body: []byte(test.config), + } + + outputs, err := f.ReplaceOutputs() + is.NoErr(err) + + is.True(len(outputs) == 1) // len(outputs) == 1 + is.True(strings.Contains(string(f.Body), "pipeline")) // f.Body contains "pipeline" + is.True(strings.Contains(string(f.Body), "lfv_output_")) // f.Body contains "lfv_output_" + }) + } +} diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go new file mode 100644 index 0000000..a32daf2 --- /dev/null +++ b/internal/daemon/pipeline/pipeline.go @@ -0,0 +1,107 @@ +package pipeline + +import ( + "archive/zip" + "bytes" + "io/ioutil" + "os" + "path" + "strings" + + "github.com/bmatcuk/doublestar/v2" + "gopkg.in/yaml.v2" +) + +type Archive struct { + Pipelines Pipelines + File string + BasePath string +} + +type Pipelines []Pipeline + +type Pipeline struct { + ID string `yaml:"pipeline.id"` + Config string `yaml:"path.config"` + Ordered string `yaml:"pipeline.ordered"` + Workers int `yaml:"pipeline.workers"` +} + +func NewArchive(file, basePath string) (Archive, error) { + b, err := ioutil.ReadFile(file) + if err != nil { + return Archive{}, err + } + + p := Pipelines{} + err = yaml.Unmarshal([]byte(b), &p) + if err != nil { + return Archive{}, err + } + + a := Archive{ + Pipelines: p, + File: file, + BasePath: basePath, + } + + return a, nil +} + +func (a Archive) ZipBytes() ([]byte, error) { + buf := new(bytes.Buffer) + w := zip.NewWriter(buf) + + f, err := w.Create("pipelines.yml") + if err != nil { + return nil, err + } + body, err := ioutil.ReadFile(a.File) + if err != nil { + return nil, err + } + _, err = f.Write(body) + if err != nil { + return nil, err + } + + for _, pipeline := range a.Pipelines { + files, err := doublestar.Glob(path.Join(a.BasePath, pipeline.Config)) + if err != nil { + return nil, err + } + for _, file := range files { + var relFile string + if path.IsAbs(a.BasePath) { + relFile = strings.TrimPrefix(file, a.BasePath) + } else { + cwd, err := os.Getwd() + if err != nil { + return nil, err + } + relFile = strings.TrimPrefix(file, path.Join(cwd, a.BasePath)) + } + + f, err := w.Create(relFile) + if err != nil { + return nil, err + } + + body, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + _, err = f.Write(body) + if err != nil { + return nil, err + } + } + } + + err = w.Close() + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go new file mode 100644 index 0000000..6b221f5 --- /dev/null +++ b/internal/daemon/pipeline/pipeline_test.go @@ -0,0 +1,110 @@ +package pipeline_test + +import ( + "archive/zip" + "bytes" + "testing" + + "github.com/matryer/is" + "gopkg.in/yaml.v2" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" +) + +func TestPipeline(t *testing.T) { + tt := []struct { + name string + config string + + want pipeline.Pipelines + }{ + { + name: "one pipeline", + config: `- pipeline.id: main + path.config: "pipelines/main/main.conf" +`, + + want: pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "main", + Config: "pipelines/main/main.conf", + }, + }, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + is := is.New(t) + + p := pipeline.Pipelines{} + + err := yaml.Unmarshal([]byte(tc.config), &p) + is.NoErr(err) + + is.Equal(tc.want, p) + }) + } +} + +func TestZipArchive(t *testing.T) { + cases := []struct { + name string + pipeline string + basePath string + + wantNewArchiveErr bool + wantZipBytesErr bool + wantFiles int + }{ + { + name: "success basic pipeline", + pipeline: "testdata/pipelines_basic.yml", + + wantFiles: 2, + }, + { + name: "success basic pipeline", + pipeline: "testdata/pipelines_advanced.yml", + + wantFiles: 3, + }, + { + name: "error pipeline file not found", + pipeline: "testdata/pipelines_invalid.yml", + + wantNewArchiveErr: true, + }, + { + name: "error pipeline file not yaml", + pipeline: "testdata/pipelines_invalid_yaml.yml", + + wantNewArchiveErr: true, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + a, err := pipeline.NewArchive(test.pipeline, "testdata") + is.True(err != nil == test.wantNewArchiveErr) // NewArchive error + + if test.wantNewArchiveErr { + return + } + + b, err := a.ZipBytes() + is.True(err != nil == test.wantZipBytesErr) // ZipBytes error + + if test.wantZipBytesErr { + return + } + + r, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) + is.NoErr(err) + + is.Equal(test.wantFiles, len(r.File)) + }) + } +} diff --git a/internal/daemon/pipeline/testdata/folder/main.conf b/internal/daemon/pipeline/testdata/folder/main.conf new file mode 100644 index 0000000..177e889 --- /dev/null +++ b/internal/daemon/pipeline/testdata/folder/main.conf @@ -0,0 +1,7 @@ +input { + stdin {} +} + +output { + stdout {} +} diff --git a/internal/daemon/pipeline/testdata/folder/subfolder/other.conf b/internal/daemon/pipeline/testdata/folder/subfolder/other.conf new file mode 100644 index 0000000..e2acb19 --- /dev/null +++ b/internal/daemon/pipeline/testdata/folder/subfolder/other.conf @@ -0,0 +1,5 @@ +filter { + mutate { + add_tag => [ "other" ] + } +} \ No newline at end of file diff --git a/internal/daemon/pipeline/testdata/pipelines_advanced.yml b/internal/daemon/pipeline/testdata/pipelines_advanced.yml new file mode 100644 index 0000000..9e6007f --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_advanced.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "folder/**/*.conf" diff --git a/internal/daemon/pipeline/testdata/pipelines_basic.yml b/internal/daemon/pipeline/testdata/pipelines_basic.yml new file mode 100644 index 0000000..08fb71e --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_basic.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "folder/main.conf" diff --git a/internal/daemon/pipeline/testdata/pipelines_invalid_yaml.yml b/internal/daemon/pipeline/testdata/pipelines_invalid_yaml.yml new file mode 100644 index 0000000..81750b9 --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_invalid_yaml.yml @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go new file mode 100644 index 0000000..486b106 --- /dev/null +++ b/internal/daemon/session/controller.go @@ -0,0 +1,111 @@ +package session + +import ( + "sync" + + "github.com/pkg/errors" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +// A Controller manages the sessions. +type Controller struct { + mutex *sync.Mutex + wg *sync.WaitGroup + cond *sync.Cond + + // TODO: define specific type for sessionID + sessions map[string]*Session + finished bool + + tempdir string + logstashController LogstashController + log logging.Logger +} + +// NewController creates a new session Controller. +func NewController(tempdir string, logstashController LogstashController, log logging.Logger) *Controller { + mu := &sync.Mutex{} + + return &Controller{ + mutex: mu, + wg: &sync.WaitGroup{}, + cond: sync.NewCond(mu), + + sessions: make(map[string]*Session, 10), + + tempdir: tempdir, + logstashController: logstashController, + log: log, + } +} + +// Create creates a new Session. +func (s *Controller) Create(pipelines pipeline.Pipelines, configFiles []logstashconfig.File) (*Session, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + + for len(s.sessions) != 0 { + s.cond.Wait() + if s.finished { + return nil, errors.New("shutdown in progress") + } + } + + session := new(s.tempdir, s.logstashController, s.log) + s.sessions[session.ID()] = session + + s.wg.Add(1) + + err := session.setupTest(pipelines, configFiles) + if err != nil { + return nil, err + } + + return session, nil +} + +// Get returns an existing session by id. +func (s *Controller) Get(id string) (*Session, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + session, ok := s.sessions[id] + if !ok { + return nil, errors.Errorf("no valid session found for id %q", id) + } + return session, nil +} + +// Destroy deletes an existing session. +func (s *Controller) DestroyByID(id string) error { + s.mutex.Lock() + defer s.mutex.Unlock() + session, ok := s.sessions[id] + if !ok { + return errors.Errorf("no valid session found for id %q", id) + } + defer func() { + delete(s.sessions, id) + s.cond.Signal() + s.wg.Done() + }() + + return session.teardown() +} + +// WaitFinish waits for all currently running sessions to finish. +func (s *Controller) WaitFinish() chan struct{} { + s.mutex.Lock() + s.finished = true + s.mutex.Unlock() + + s.cond.Broadcast() + + s.wg.Wait() + + c := make(chan struct{}) + close(c) + return c +} diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go new file mode 100644 index 0000000..57b1303 --- /dev/null +++ b/internal/daemon/session/controller_test.go @@ -0,0 +1,165 @@ +package session_test + +import ( + "path" + "testing" + "time" + + "github.com/matryer/is" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func TestSession(t *testing.T) { + cases := []struct { + name string + }{ + { + name: "success", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + tempdir := t.TempDir() + + logstashController := &LogstashControllerMock{ + SetupTestFunc: func(pipelines pipeline.Pipelines) error { + is.True(len(pipelines) == 2) // Expect 2 pipelines (main, output) + return nil + }, + TeardownFunc: func() error { + return nil + }, + ExecuteTestFunc: func(pipelines pipeline.Pipelines, expectedEvents int) error { + is.True(len(pipelines) == 3) // Expect 2 pipelines (input, main, output) + return nil + }, + GetResultsFunc: func() ([]string, error) { + return []string{"some_random_result"}, nil + }, + } + + c := session.NewController(tempdir, logstashController, logging.NoopLogger) + + pipelines := pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "main", + Config: "main.conf", + Ordered: "true", + Workers: 1, + }, + } + + configFiles := []logstashconfig.File{ + { + Name: "main.conf", + Body: []byte(`input { stdin{} } filter { mutate{ add_tag => [ "test" ] } } output { stdout{} }`), + }, + } + + s, err := c.Create(pipelines, configFiles) + is.NoErr(err) + + is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "sut", "main.conf"))) // sut/main.conf + is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "sut", "main.conf"), "__lfv_input")) // sut/main.conf contains "__lfv_input" + is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "sut", "main.conf"), "lfv_output_")) // sut/main.conf contains "lfv_output_" + + _, err = c.Get("invalid") + is.True(err != nil) // Get invalid session error + + s, err = c.Get(s.ID()) + is.NoErr(err) + + inputLines := []string{"some_random_input"} + inFields := map[string]string{ + "some_random_key": "value", + } + err = s.ExecuteTest(inputLines, inFields) + is.NoErr(err) + + is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"))) // lfv_inputs/1/fields.json + is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"), "some_random_key")) // lfv_inputs/1/fields.json contains "some_random_key" + is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"), "some_random_input")) // lfv_inputs/1/fields.json contains "some_random_input" + is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"))) // lfv_inputs/1/input.conf + is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"), "lfv_inputs/1/fields.json")) // lfv_inputs/1/input.conf contains "lfv_inputs/1/fields.json" + + results, err := s.GetResults() + is.NoErr(err) + is.True(len(results) > 0) // GetResults does return results + + err = c.DestroyByID("invalid") + is.True(err != nil) // DestroyByID invalid session error + + err = c.DestroyByID(s.ID()) + is.NoErr(err) + + <-c.WaitFinish() + }) + } +} + +func TestCreate(t *testing.T) { + cases := []struct { + name string + }{ + { + name: "success", + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + tempdir := t.TempDir() + + logstashController := &LogstashControllerMock{ + SetupTestFunc: func(pipelines pipeline.Pipelines) error { + return nil + }, + TeardownFunc: func() error { + return nil + }, + } + + c := session.NewController(tempdir, logstashController, logging.NoopLogger) + + pipelines := pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "main", + Config: "main.conf", + Ordered: "true", + Workers: 1, + }, + } + + configFiles := []logstashconfig.File{ + { + Name: "main.conf", + Body: []byte(`input { stdin{} } filter { mutate{ add_tag => [ "test" ] } } output { stdout{} }`), + }, + } + + s, err := c.Create(pipelines, configFiles) + is.NoErr(err) + + go func() { + time.Sleep(10 * time.Millisecond) + err = c.DestroyByID(s.ID()) + is.NoErr(err) + }() + + s2, err := c.Create(pipelines, configFiles) + is.NoErr(err) + + is.True(s.ID() != s2.ID()) // IDs of two seperate sessions are not equal + }) + } +} diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go new file mode 100644 index 0000000..e19af51 --- /dev/null +++ b/internal/daemon/session/files.go @@ -0,0 +1,66 @@ +package session + +const outputPipeline = `input { + pipeline { + address => {{ .PipelineName }} + } +} + +filter { + mutate { + add_tag => [ "{{ .PipelineOrigName }}_passed" ] + } +} + +output { + pipeline { + send_to => __lfv_output + } +} +` + +const inputGenerator = ` +input { + generator { + lines => [ + {{ .InputLines }} + ] + count => 1 + codec => plain + threads => 1 + } +} + +filter { + mutate { + add_tag => [ "__lfv_in_passed" ] + # Remove the fields "sequence" and "host", which are automatically created by the generator input. + remove_field => [ "host", "sequence" ] + # We use the message as the LFV event ID, so move this to the right field. + replace => { + "[@metadata][__lfv_id]" => "%{[message]}" + } + } + + translate { + dictionary_path => "{{ .FieldsFilename }}" + field => "[@metadata][__lfv_id]" + destination => "[@metadata][__lfv_fields]" + exact => true + override => true + # TODO: Add default value (e.g. "__lfv_fields_not_found"), if not found in dictionary + } + ruby { + # TODO: If default value ("__lfv_fields_not_found"), then skip this ruby + # code and add an tag instead + code => 'fields = event.get("[@metadata][__lfv_fields]") + fields.each { |key, value| event.set(key, value) }' + } +} + +output { + pipeline { + send_to => [__lfv_input] + } +} +` diff --git a/internal/daemon/session/logstash_controller.go b/internal/daemon/session/logstash_controller.go new file mode 100644 index 0000000..032db9b --- /dev/null +++ b/internal/daemon/session/logstash_controller.go @@ -0,0 +1,12 @@ +package session + +import "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + +//go:generate moq -fmt goimports -pkg session_test -out ./logstash_controller_mock_test.go . LogstashController + +type LogstashController interface { + SetupTest(pipelines pipeline.Pipelines) error + ExecuteTest(pipelines pipeline.Pipelines, expectedEvents int) error + GetResults() ([]string, error) + Teardown() error +} diff --git a/internal/daemon/session/logstash_controller_mock_test.go b/internal/daemon/session/logstash_controller_mock_test.go new file mode 100644 index 0000000..0917476 --- /dev/null +++ b/internal/daemon/session/logstash_controller_mock_test.go @@ -0,0 +1,189 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package session_test + +import ( + "sync" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" +) + +// Ensure, that LogstashControllerMock does implement session.LogstashController. +// If this is not the case, regenerate this file with moq. +var _ session.LogstashController = &LogstashControllerMock{} + +// LogstashControllerMock is a mock implementation of session.LogstashController. +// +// func TestSomethingThatUsesLogstashController(t *testing.T) { +// +// // make and configure a mocked session.LogstashController +// mockedLogstashController := &LogstashControllerMock{ +// ExecuteTestFunc: func(pipelines pipeline.Pipelines, expectedEvents int) error { +// panic("mock out the ExecuteTest method") +// }, +// GetResultsFunc: func() ([]string, error) { +// panic("mock out the GetResults method") +// }, +// SetupTestFunc: func(pipelines pipeline.Pipelines) error { +// panic("mock out the SetupTest method") +// }, +// TeardownFunc: func() error { +// panic("mock out the Teardown method") +// }, +// } +// +// // use mockedLogstashController in code that requires session.LogstashController +// // and then make assertions. +// +// } +type LogstashControllerMock struct { + // ExecuteTestFunc mocks the ExecuteTest method. + ExecuteTestFunc func(pipelines pipeline.Pipelines, expectedEvents int) error + + // GetResultsFunc mocks the GetResults method. + GetResultsFunc func() ([]string, error) + + // SetupTestFunc mocks the SetupTest method. + SetupTestFunc func(pipelines pipeline.Pipelines) error + + // TeardownFunc mocks the Teardown method. + TeardownFunc func() error + + // calls tracks calls to the methods. + calls struct { + // ExecuteTest holds details about calls to the ExecuteTest method. + ExecuteTest []struct { + // Pipelines is the pipelines argument value. + Pipelines pipeline.Pipelines + // ExpectedEvents is the expectedEvents argument value. + ExpectedEvents int + } + // GetResults holds details about calls to the GetResults method. + GetResults []struct{} + // SetupTest holds details about calls to the SetupTest method. + SetupTest []struct { + // Pipelines is the pipelines argument value. + Pipelines pipeline.Pipelines + } + // Teardown holds details about calls to the Teardown method. + Teardown []struct{} + } + lockExecuteTest sync.RWMutex + lockGetResults sync.RWMutex + lockSetupTest sync.RWMutex + lockTeardown sync.RWMutex +} + +// ExecuteTest calls ExecuteTestFunc. +func (mock *LogstashControllerMock) ExecuteTest(pipelines pipeline.Pipelines, expectedEvents int) error { + if mock.ExecuteTestFunc == nil { + panic("LogstashControllerMock.ExecuteTestFunc: method is nil but LogstashController.ExecuteTest was just called") + } + callInfo := struct { + Pipelines pipeline.Pipelines + ExpectedEvents int + }{ + Pipelines: pipelines, + ExpectedEvents: expectedEvents, + } + mock.lockExecuteTest.Lock() + mock.calls.ExecuteTest = append(mock.calls.ExecuteTest, callInfo) + mock.lockExecuteTest.Unlock() + return mock.ExecuteTestFunc(pipelines, expectedEvents) +} + +// ExecuteTestCalls gets all the calls that were made to ExecuteTest. +// Check the length with: +// len(mockedLogstashController.ExecuteTestCalls()) +func (mock *LogstashControllerMock) ExecuteTestCalls() []struct { + Pipelines pipeline.Pipelines + ExpectedEvents int +} { + var calls []struct { + Pipelines pipeline.Pipelines + ExpectedEvents int + } + mock.lockExecuteTest.RLock() + calls = mock.calls.ExecuteTest + mock.lockExecuteTest.RUnlock() + return calls +} + +// GetResults calls GetResultsFunc. +func (mock *LogstashControllerMock) GetResults() ([]string, error) { + if mock.GetResultsFunc == nil { + panic("LogstashControllerMock.GetResultsFunc: method is nil but LogstashController.GetResults was just called") + } + callInfo := struct{}{} + mock.lockGetResults.Lock() + mock.calls.GetResults = append(mock.calls.GetResults, callInfo) + mock.lockGetResults.Unlock() + return mock.GetResultsFunc() +} + +// GetResultsCalls gets all the calls that were made to GetResults. +// Check the length with: +// len(mockedLogstashController.GetResultsCalls()) +func (mock *LogstashControllerMock) GetResultsCalls() []struct{} { + var calls []struct{} + mock.lockGetResults.RLock() + calls = mock.calls.GetResults + mock.lockGetResults.RUnlock() + return calls +} + +// SetupTest calls SetupTestFunc. +func (mock *LogstashControllerMock) SetupTest(pipelines pipeline.Pipelines) error { + if mock.SetupTestFunc == nil { + panic("LogstashControllerMock.SetupTestFunc: method is nil but LogstashController.SetupTest was just called") + } + callInfo := struct { + Pipelines pipeline.Pipelines + }{ + Pipelines: pipelines, + } + mock.lockSetupTest.Lock() + mock.calls.SetupTest = append(mock.calls.SetupTest, callInfo) + mock.lockSetupTest.Unlock() + return mock.SetupTestFunc(pipelines) +} + +// SetupTestCalls gets all the calls that were made to SetupTest. +// Check the length with: +// len(mockedLogstashController.SetupTestCalls()) +func (mock *LogstashControllerMock) SetupTestCalls() []struct { + Pipelines pipeline.Pipelines +} { + var calls []struct { + Pipelines pipeline.Pipelines + } + mock.lockSetupTest.RLock() + calls = mock.calls.SetupTest + mock.lockSetupTest.RUnlock() + return calls +} + +// Teardown calls TeardownFunc. +func (mock *LogstashControllerMock) Teardown() error { + if mock.TeardownFunc == nil { + panic("LogstashControllerMock.TeardownFunc: method is nil but LogstashController.Teardown was just called") + } + callInfo := struct{}{} + mock.lockTeardown.Lock() + mock.calls.Teardown = append(mock.calls.Teardown, callInfo) + mock.lockTeardown.Unlock() + return mock.TeardownFunc() +} + +// TeardownCalls gets all the calls that were made to Teardown. +// Check the length with: +// len(mockedLogstashController.TeardownCalls()) +func (mock *LogstashControllerMock) TeardownCalls() []struct{} { + var calls []struct{} + mock.lockTeardown.RLock() + calls = mock.calls.Teardown + mock.lockTeardown.RUnlock() + return calls +} diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go new file mode 100644 index 0000000..aad487e --- /dev/null +++ b/internal/daemon/session/session.go @@ -0,0 +1,246 @@ +package session + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + + "github.com/pkg/errors" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/template" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type Session struct { + id string + + logstashController LogstashController + + baseDir string + sessionDir string + + pipelines pipeline.Pipelines + testexec int + + log logging.Logger +} + +func new(baseDir string, logstashController LogstashController, log logging.Logger) *Session { + sessionID := idgen.New() + sessionDir := fmt.Sprintf("%s/session/%s", baseDir, sessionID) + return &Session{ + id: sessionID, + baseDir: baseDir, + sessionDir: sessionDir, + logstashController: logstashController, + log: log, + } +} + +// ID returns the id of the session. +func (s Session) ID() string { + return s.id +} + +// setupTest prepares the Logstash configuration for a new test run. +func (s *Session) setupTest(pipelines pipeline.Pipelines, configFiles []logstashconfig.File) error { + err := os.MkdirAll(s.sessionDir, 0700) + if err != nil { + return err + } + + sutConfigDir := path.Join(s.sessionDir, "sut") + + // adjust pipeline names and config directories to session + for i := range pipelines { + pipelineName := fmt.Sprintf("lfv_%s_%s", s.id, pipelines[i].ID) + + pipelines[i].ID = pipelineName + pipelines[i].Config = path.Join(sutConfigDir, pipelines[i].Config) + pipelines[i].Ordered = "true" + pipelines[i].Workers = 1 + } + + // Preprocess and Save Config Files + for _, configFile := range configFiles { + err := configFile.ReplaceInputs() + if err != nil { + return err + } + + outputs, err := configFile.ReplaceOutputs() + if err != nil { + return err + } + + err = configFile.Save(sutConfigDir) + if err != nil { + return err + } + + outputPipelines, err := s.createOutputPipelines(outputs) + if err != nil { + return err + } + + pipelines = append(pipelines, outputPipelines...) + } + + // Reload Logstash Config + s.pipelines = pipelines + // err = s.logstash.ReloadPipelines(pipelines) + err = s.logstashController.SetupTest(pipelines) + if err != nil { + s.log.Errorf("failed to reload Logstash config: %v", err) + } + + return nil +} + +func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, error) { + lfvOutputsDir := path.Join(s.sessionDir, "lfv_outputs") + err := os.MkdirAll(lfvOutputsDir, 0700) + if err != nil { + return nil, err + } + + pipelines := make([]pipeline.Pipeline, 0) + for _, output := range outputs { + pipelineName := fmt.Sprintf("lfv_output_%s", output) + + templateData := struct { + PipelineName string + PipelineOrigName string + }{ + PipelineName: pipelineName, + PipelineOrigName: output, + } + + err = template.ToFile(path.Join(lfvOutputsDir, output+".conf"), outputPipeline, templateData, 0644) + if err != nil { + return nil, err + } + + pipeline := pipeline.Pipeline{ + ID: pipelineName, + Config: path.Join(lfvOutputsDir, output+".conf"), + Ordered: "true", + Workers: 1, + } + pipelines = append(pipelines, pipeline) + } + + return pipelines, nil +} + +// ExecuteTest runs a test case set against the Logstash configuration, that has +// been loaded previously with SetupTest +func (s *Session) ExecuteTest(inputLines []string, inFields map[string]string) error { + s.testexec++ + pipelineName := fmt.Sprintf("lfv_input_%d", s.testexec) + inputDir := path.Join(s.sessionDir, "lfv_inputs", strconv.Itoa(s.testexec)) + + // Prepare input directory + err := os.MkdirAll(inputDir, 0700) + if err != nil { + return err + } + + fieldsFilename := path.Join(inputDir, "fields.json") + ids, err := prepareFields(fieldsFilename, inputLines, inFields) + if err != nil { + return err + } + + pipelineFilename := path.Join(inputDir, "input.conf") + err = createInput(pipelineFilename, fieldsFilename, ids) + if err != nil { + return err + } + + pipeline := pipeline.Pipeline{ + ID: pipelineName, + Config: pipelineFilename, + Ordered: "true", + Workers: 1, + } + pipelines := append(s.pipelines, pipeline) + err = s.logstashController.ExecuteTest(pipelines, len(inputLines)) + if err != nil { + return err + } + + return nil +} + +func prepareFields(fieldsFilename string, inputLines []string, inFields map[string]string) ([]string, error) { + // FIXME: This does not allow arbritary nested fields yet. + fields := make(map[string]map[string]string) + + ids := make([]string, 0, len(inputLines)) + for i, line := range inputLines { + id := fmt.Sprintf("%d", i) + ids = append(ids, fmt.Sprintf("%q", id)) + fields[id] = make(map[string]string) + fields[id]["message"] = line + + for field, value := range inFields { + fields[id][field] = value + } + } + + bfields, err := json.Marshal(fields) + if err != nil { + return nil, err + } + err = ioutil.WriteFile(fieldsFilename, bfields, 0600) + if err != nil { + return nil, err + } + + return ids, nil +} + +func createInput(pipelineFilename string, fieldsFilename string, ids []string) error { + templateData := struct { + InputLines string + FieldsFilename string + }{ + InputLines: strings.Join(ids, ", "), + FieldsFilename: fieldsFilename, + } + err := template.ToFile(pipelineFilename, inputGenerator, templateData, 0600) + if err != nil { + return err + } + + return nil +} + +// GetResults returns the returned events from Logstash. +func (s *Session) GetResults() ([]string, error) { + return s.logstashController.GetResults() +} + +// GetStats returns the statistics for a test suite. +func (s *Session) GetStats() { + panic("not implemented") +} + +func (s *Session) teardown() error { + // TODO: Perform a reset of the Logstash instance including Stdin Buffer, etc. + err1 := s.logstashController.Teardown() + err2 := os.RemoveAll(s.sessionDir) + if err1 != nil || err2 != nil { + return errors.Errorf("session teardown failed: %v, %v", err1, err2) + } + + return nil +} diff --git a/internal/daemon/template/template.go b/internal/daemon/template/template.go new file mode 100644 index 0000000..d6ab664 --- /dev/null +++ b/internal/daemon/template/template.go @@ -0,0 +1,25 @@ +package template + +import ( + "bytes" + "io/ioutil" + "os" + "text/template" +) + +func ToFile(filename string, templateContent string, data interface{}, perm os.FileMode) error { + tmpl, err := template.New("template").Parse(templateContent) + if err != nil { + return err + } + b := &bytes.Buffer{} + err = tmpl.Execute(b, data) + if err != nil { + return err + } + err = ioutil.WriteFile(filename, b.Bytes(), perm) + if err != nil { + return err + } + return nil +} diff --git a/internal/logging/logger.go b/internal/logging/logger.go index fefaa4f..bd8382a 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -8,6 +8,8 @@ import ( oplogging "github.com/op/go-logging" ) +//go:generate moq -fmt goimports -pkg logging -out logger_mock.go . Logger + type Logger interface { Debug(args ...interface{}) Debugf(format string, args ...interface{}) @@ -21,6 +23,19 @@ type Logger interface { Warningf(format string, args ...interface{}) } +var NoopLogger = &LoggerMock{ + DebugFunc: func(args ...interface{}) {}, + DebugfFunc: func(format string, args ...interface{}) {}, + ErrorFunc: func(args ...interface{}) {}, + ErrorfFunc: func(format string, args ...interface{}) {}, + FatalFunc: func(args ...interface{}) {}, + FatalfFunc: func(format string, args ...interface{}) {}, + InfoFunc: func(args ...interface{}) {}, + InfofFunc: func(format string, args ...interface{}) {}, + WarningFunc: func(args ...interface{}) {}, + WarningfFunc: func(format string, args ...interface{}) {}, +} + const ( logModule = "logstash-filter-verifier" ) diff --git a/internal/logging/logger_mock.go b/internal/logging/logger_mock.go new file mode 100644 index 0000000..aeafa19 --- /dev/null +++ b/internal/logging/logger_mock.go @@ -0,0 +1,490 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package logging + +import ( + "sync" +) + +// Ensure, that LoggerMock does implement Logger. +// If this is not the case, regenerate this file with moq. +var _ Logger = &LoggerMock{} + +// LoggerMock is a mock implementation of Logger. +// +// func TestSomethingThatUsesLogger(t *testing.T) { +// +// // make and configure a mocked Logger +// mockedLogger := &LoggerMock{ +// DebugFunc: func(args ...interface{}) { +// panic("mock out the Debug method") +// }, +// DebugfFunc: func(format string, args ...interface{}) { +// panic("mock out the Debugf method") +// }, +// ErrorFunc: func(args ...interface{}) { +// panic("mock out the Error method") +// }, +// ErrorfFunc: func(format string, args ...interface{}) { +// panic("mock out the Errorf method") +// }, +// FatalFunc: func(args ...interface{}) { +// panic("mock out the Fatal method") +// }, +// FatalfFunc: func(format string, args ...interface{}) { +// panic("mock out the Fatalf method") +// }, +// InfoFunc: func(args ...interface{}) { +// panic("mock out the Info method") +// }, +// InfofFunc: func(format string, args ...interface{}) { +// panic("mock out the Infof method") +// }, +// WarningFunc: func(args ...interface{}) { +// panic("mock out the Warning method") +// }, +// WarningfFunc: func(format string, args ...interface{}) { +// panic("mock out the Warningf method") +// }, +// } +// +// // use mockedLogger in code that requires Logger +// // and then make assertions. +// +// } +type LoggerMock struct { + // DebugFunc mocks the Debug method. + DebugFunc func(args ...interface{}) + + // DebugfFunc mocks the Debugf method. + DebugfFunc func(format string, args ...interface{}) + + // ErrorFunc mocks the Error method. + ErrorFunc func(args ...interface{}) + + // ErrorfFunc mocks the Errorf method. + ErrorfFunc func(format string, args ...interface{}) + + // FatalFunc mocks the Fatal method. + FatalFunc func(args ...interface{}) + + // FatalfFunc mocks the Fatalf method. + FatalfFunc func(format string, args ...interface{}) + + // InfoFunc mocks the Info method. + InfoFunc func(args ...interface{}) + + // InfofFunc mocks the Infof method. + InfofFunc func(format string, args ...interface{}) + + // WarningFunc mocks the Warning method. + WarningFunc func(args ...interface{}) + + // WarningfFunc mocks the Warningf method. + WarningfFunc func(format string, args ...interface{}) + + // calls tracks calls to the methods. + calls struct { + // Debug holds details about calls to the Debug method. + Debug []struct { + // Args is the args argument value. + Args []interface{} + } + // Debugf holds details about calls to the Debugf method. + Debugf []struct { + // Format is the format argument value. + Format string + // Args is the args argument value. + Args []interface{} + } + // Error holds details about calls to the Error method. + Error []struct { + // Args is the args argument value. + Args []interface{} + } + // Errorf holds details about calls to the Errorf method. + Errorf []struct { + // Format is the format argument value. + Format string + // Args is the args argument value. + Args []interface{} + } + // Fatal holds details about calls to the Fatal method. + Fatal []struct { + // Args is the args argument value. + Args []interface{} + } + // Fatalf holds details about calls to the Fatalf method. + Fatalf []struct { + // Format is the format argument value. + Format string + // Args is the args argument value. + Args []interface{} + } + // Info holds details about calls to the Info method. + Info []struct { + // Args is the args argument value. + Args []interface{} + } + // Infof holds details about calls to the Infof method. + Infof []struct { + // Format is the format argument value. + Format string + // Args is the args argument value. + Args []interface{} + } + // Warning holds details about calls to the Warning method. + Warning []struct { + // Args is the args argument value. + Args []interface{} + } + // Warningf holds details about calls to the Warningf method. + Warningf []struct { + // Format is the format argument value. + Format string + // Args is the args argument value. + Args []interface{} + } + } + lockDebug sync.RWMutex + lockDebugf sync.RWMutex + lockError sync.RWMutex + lockErrorf sync.RWMutex + lockFatal sync.RWMutex + lockFatalf sync.RWMutex + lockInfo sync.RWMutex + lockInfof sync.RWMutex + lockWarning sync.RWMutex + lockWarningf sync.RWMutex +} + +// Debug calls DebugFunc. +func (mock *LoggerMock) Debug(args ...interface{}) { + if mock.DebugFunc == nil { + panic("LoggerMock.DebugFunc: method is nil but Logger.Debug was just called") + } + callInfo := struct { + Args []interface{} + }{ + Args: args, + } + mock.lockDebug.Lock() + mock.calls.Debug = append(mock.calls.Debug, callInfo) + mock.lockDebug.Unlock() + mock.DebugFunc(args...) +} + +// DebugCalls gets all the calls that were made to Debug. +// Check the length with: +// len(mockedLogger.DebugCalls()) +func (mock *LoggerMock) DebugCalls() []struct { + Args []interface{} +} { + var calls []struct { + Args []interface{} + } + mock.lockDebug.RLock() + calls = mock.calls.Debug + mock.lockDebug.RUnlock() + return calls +} + +// Debugf calls DebugfFunc. +func (mock *LoggerMock) Debugf(format string, args ...interface{}) { + if mock.DebugfFunc == nil { + panic("LoggerMock.DebugfFunc: method is nil but Logger.Debugf was just called") + } + callInfo := struct { + Format string + Args []interface{} + }{ + Format: format, + Args: args, + } + mock.lockDebugf.Lock() + mock.calls.Debugf = append(mock.calls.Debugf, callInfo) + mock.lockDebugf.Unlock() + mock.DebugfFunc(format, args...) +} + +// DebugfCalls gets all the calls that were made to Debugf. +// Check the length with: +// len(mockedLogger.DebugfCalls()) +func (mock *LoggerMock) DebugfCalls() []struct { + Format string + Args []interface{} +} { + var calls []struct { + Format string + Args []interface{} + } + mock.lockDebugf.RLock() + calls = mock.calls.Debugf + mock.lockDebugf.RUnlock() + return calls +} + +// Error calls ErrorFunc. +func (mock *LoggerMock) Error(args ...interface{}) { + if mock.ErrorFunc == nil { + panic("LoggerMock.ErrorFunc: method is nil but Logger.Error was just called") + } + callInfo := struct { + Args []interface{} + }{ + Args: args, + } + mock.lockError.Lock() + mock.calls.Error = append(mock.calls.Error, callInfo) + mock.lockError.Unlock() + mock.ErrorFunc(args...) +} + +// ErrorCalls gets all the calls that were made to Error. +// Check the length with: +// len(mockedLogger.ErrorCalls()) +func (mock *LoggerMock) ErrorCalls() []struct { + Args []interface{} +} { + var calls []struct { + Args []interface{} + } + mock.lockError.RLock() + calls = mock.calls.Error + mock.lockError.RUnlock() + return calls +} + +// Errorf calls ErrorfFunc. +func (mock *LoggerMock) Errorf(format string, args ...interface{}) { + if mock.ErrorfFunc == nil { + panic("LoggerMock.ErrorfFunc: method is nil but Logger.Errorf was just called") + } + callInfo := struct { + Format string + Args []interface{} + }{ + Format: format, + Args: args, + } + mock.lockErrorf.Lock() + mock.calls.Errorf = append(mock.calls.Errorf, callInfo) + mock.lockErrorf.Unlock() + mock.ErrorfFunc(format, args...) +} + +// ErrorfCalls gets all the calls that were made to Errorf. +// Check the length with: +// len(mockedLogger.ErrorfCalls()) +func (mock *LoggerMock) ErrorfCalls() []struct { + Format string + Args []interface{} +} { + var calls []struct { + Format string + Args []interface{} + } + mock.lockErrorf.RLock() + calls = mock.calls.Errorf + mock.lockErrorf.RUnlock() + return calls +} + +// Fatal calls FatalFunc. +func (mock *LoggerMock) Fatal(args ...interface{}) { + if mock.FatalFunc == nil { + panic("LoggerMock.FatalFunc: method is nil but Logger.Fatal was just called") + } + callInfo := struct { + Args []interface{} + }{ + Args: args, + } + mock.lockFatal.Lock() + mock.calls.Fatal = append(mock.calls.Fatal, callInfo) + mock.lockFatal.Unlock() + mock.FatalFunc(args...) +} + +// FatalCalls gets all the calls that were made to Fatal. +// Check the length with: +// len(mockedLogger.FatalCalls()) +func (mock *LoggerMock) FatalCalls() []struct { + Args []interface{} +} { + var calls []struct { + Args []interface{} + } + mock.lockFatal.RLock() + calls = mock.calls.Fatal + mock.lockFatal.RUnlock() + return calls +} + +// Fatalf calls FatalfFunc. +func (mock *LoggerMock) Fatalf(format string, args ...interface{}) { + if mock.FatalfFunc == nil { + panic("LoggerMock.FatalfFunc: method is nil but Logger.Fatalf was just called") + } + callInfo := struct { + Format string + Args []interface{} + }{ + Format: format, + Args: args, + } + mock.lockFatalf.Lock() + mock.calls.Fatalf = append(mock.calls.Fatalf, callInfo) + mock.lockFatalf.Unlock() + mock.FatalfFunc(format, args...) +} + +// FatalfCalls gets all the calls that were made to Fatalf. +// Check the length with: +// len(mockedLogger.FatalfCalls()) +func (mock *LoggerMock) FatalfCalls() []struct { + Format string + Args []interface{} +} { + var calls []struct { + Format string + Args []interface{} + } + mock.lockFatalf.RLock() + calls = mock.calls.Fatalf + mock.lockFatalf.RUnlock() + return calls +} + +// Info calls InfoFunc. +func (mock *LoggerMock) Info(args ...interface{}) { + if mock.InfoFunc == nil { + panic("LoggerMock.InfoFunc: method is nil but Logger.Info was just called") + } + callInfo := struct { + Args []interface{} + }{ + Args: args, + } + mock.lockInfo.Lock() + mock.calls.Info = append(mock.calls.Info, callInfo) + mock.lockInfo.Unlock() + mock.InfoFunc(args...) +} + +// InfoCalls gets all the calls that were made to Info. +// Check the length with: +// len(mockedLogger.InfoCalls()) +func (mock *LoggerMock) InfoCalls() []struct { + Args []interface{} +} { + var calls []struct { + Args []interface{} + } + mock.lockInfo.RLock() + calls = mock.calls.Info + mock.lockInfo.RUnlock() + return calls +} + +// Infof calls InfofFunc. +func (mock *LoggerMock) Infof(format string, args ...interface{}) { + if mock.InfofFunc == nil { + panic("LoggerMock.InfofFunc: method is nil but Logger.Infof was just called") + } + callInfo := struct { + Format string + Args []interface{} + }{ + Format: format, + Args: args, + } + mock.lockInfof.Lock() + mock.calls.Infof = append(mock.calls.Infof, callInfo) + mock.lockInfof.Unlock() + mock.InfofFunc(format, args...) +} + +// InfofCalls gets all the calls that were made to Infof. +// Check the length with: +// len(mockedLogger.InfofCalls()) +func (mock *LoggerMock) InfofCalls() []struct { + Format string + Args []interface{} +} { + var calls []struct { + Format string + Args []interface{} + } + mock.lockInfof.RLock() + calls = mock.calls.Infof + mock.lockInfof.RUnlock() + return calls +} + +// Warning calls WarningFunc. +func (mock *LoggerMock) Warning(args ...interface{}) { + if mock.WarningFunc == nil { + panic("LoggerMock.WarningFunc: method is nil but Logger.Warning was just called") + } + callInfo := struct { + Args []interface{} + }{ + Args: args, + } + mock.lockWarning.Lock() + mock.calls.Warning = append(mock.calls.Warning, callInfo) + mock.lockWarning.Unlock() + mock.WarningFunc(args...) +} + +// WarningCalls gets all the calls that were made to Warning. +// Check the length with: +// len(mockedLogger.WarningCalls()) +func (mock *LoggerMock) WarningCalls() []struct { + Args []interface{} +} { + var calls []struct { + Args []interface{} + } + mock.lockWarning.RLock() + calls = mock.calls.Warning + mock.lockWarning.RUnlock() + return calls +} + +// Warningf calls WarningfFunc. +func (mock *LoggerMock) Warningf(format string, args ...interface{}) { + if mock.WarningfFunc == nil { + panic("LoggerMock.WarningfFunc: method is nil but Logger.Warningf was just called") + } + callInfo := struct { + Format string + Args []interface{} + }{ + Format: format, + Args: args, + } + mock.lockWarningf.Lock() + mock.calls.Warningf = append(mock.calls.Warningf, callInfo) + mock.lockWarningf.Unlock() + mock.WarningfFunc(format, args...) +} + +// WarningfCalls gets all the calls that were made to Warningf. +// Check the length with: +// len(mockedLogger.WarningfCalls()) +func (mock *LoggerMock) WarningfCalls() []struct { + Format string + Args []interface{} +} { + var calls []struct { + Format string + Args []interface{} + } + mock.lockWarningf.RLock() + calls = mock.calls.Warningf + mock.lockWarningf.RUnlock() + return calls +} diff --git a/internal/logstash/pipelineconfigdir_test.go b/internal/logstash/pipelineconfigdir_test.go index 3e071c5..f62e4bc 100644 --- a/internal/logstash/pipelineconfigdir_test.go +++ b/internal/logstash/pipelineconfigdir_test.go @@ -296,22 +296,22 @@ func TestRemoveInputOutput(t *testing.T) { { // Input, output, and filter "input { beats { port => 5044 } } filter { grok {} } output{ elasticsearch {} }", - "filter { grok { }}", + "filter { grok {}}", }, { // Input and filter "input { beats { port => 5044 } } filter { grok {} }", - "filter { grok { }}", + "filter { grok {}}", }, { // Output and filter "filter { grok {} } output{ elasticsearch {} }", - "filter { grok { }}", + "filter { grok {}}", }, { // Filter only "filter { grok {} }", - "filter { grok { }}", + "filter { grok {}}", }, { // Empty file diff --git a/logstash-filter-verifier.yml.example b/logstash-filter-verifier.yml.example new file mode 100644 index 0000000..615c73a --- /dev/null +++ b/logstash-filter-verifier.yml.example @@ -0,0 +1,22 @@ +--- + +loglevel: debug + +# Standalone mode +#diff-command: +#keep-env: +#logstash-args: +#logstash-output: +#logstash-paths: +#logstash-version: +#sockets: +#sockets-timeout: +#quiet: + +# Daemon mode +logstash: + path: ./3rdparty/logstash-7.10.0/bin/logstash +pipeline: ./testdata/basic_pipeline.yml +pipeline-base: ./testdata/basic_pipeline +testcase-dir: ./testdata/testcases/basic_pipeline +socket: /tmp/logstash-filter-verifier.sock diff --git a/testdata/basic_pipeline.yml b/testdata/basic_pipeline.yml new file mode 100644 index 0000000..a9f230e --- /dev/null +++ b/testdata/basic_pipeline.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "main/**/*.conf" diff --git a/testdata/basic_pipeline/main/dir/main2.conf b/testdata/basic_pipeline/main/dir/main2.conf new file mode 100644 index 0000000..8411fc2 --- /dev/null +++ b/testdata/basic_pipeline/main/dir/main2.conf @@ -0,0 +1,5 @@ +filter { + mutate { + add_tag => [ "main2_passed" ] + } +} \ No newline at end of file diff --git a/testdata/basic_pipeline/main/main.conf b/testdata/basic_pipeline/main/main.conf new file mode 100644 index 0000000..5a1c8fd --- /dev/null +++ b/testdata/basic_pipeline/main/main.conf @@ -0,0 +1,16 @@ +input { + stdin {} +} + +filter { + mutate { + add_tag => [ "sut_passed" ] + # foobar => "test" + } +} + +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/conditional_output.yml b/testdata/conditional_output.yml new file mode 100644 index 0000000..3b10926 --- /dev/null +++ b/testdata/conditional_output.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "main/*.conf" diff --git a/testdata/conditional_output/main/conditional_output.conf b/testdata/conditional_output/main/conditional_output.conf new file mode 100644 index 0000000..a1d02f9 --- /dev/null +++ b/testdata/conditional_output/main/conditional_output.conf @@ -0,0 +1,25 @@ +input { + stdin { + id => "stdin" + } +} + +filter { + mutate { + add_tag => [ "conditional_output_filter" ] + } +} + +output { + if [message] == "stdout" { + stdout { + id => "stdout" + } + } + if [message] == "file" { + file { + id => "file" + path => "./test.log" + } + } +} diff --git a/testdata/pipeline_to_pipeline.yml b/testdata/pipeline_to_pipeline.yml new file mode 100644 index 0000000..d94d870 --- /dev/null +++ b/testdata/pipeline_to_pipeline.yml @@ -0,0 +1,4 @@ +- pipeline.id: first_stage + path.config: "first_stage/*.conf" +- pipeline.id: second_stage + path.config: "second_stage/*.conf" diff --git a/testdata/pipeline_to_pipeline/first_stage/first_stage.conf b/testdata/pipeline_to_pipeline/first_stage/first_stage.conf new file mode 100644 index 0000000..872d688 --- /dev/null +++ b/testdata/pipeline_to_pipeline/first_stage/first_stage.conf @@ -0,0 +1,19 @@ +input { + stdin { + id => "stage1_input" + } +} + +filter { + mutate { + id => "mutate_stage_1" + add_tag => [ "mutate_stage_1_passed" ] + } +} + +output { + pipeline { + id => "stage1_to_stage2" + send_to => "stage2" + } +} diff --git a/testdata/pipeline_to_pipeline/second_stage/second_stage.conf b/testdata/pipeline_to_pipeline/second_stage/second_stage.conf new file mode 100644 index 0000000..d538b45 --- /dev/null +++ b/testdata/pipeline_to_pipeline/second_stage/second_stage.conf @@ -0,0 +1,19 @@ +input { + pipeline { + id => "stage2_input" + address => "stage2" + } +} + +filter { + mutate { + id => "mutate_stage_2" + add_tag => [ "mutate_stage_2_passed" ] + } +} + +output { + stdout { + id => "stage2_output" + } +} diff --git a/testdata/testcases/basic_pipeline/testcase1.json b/testdata/testcases/basic_pipeline/testcase1.json new file mode 100644 index 0000000..1080b42 --- /dev/null +++ b/testdata/testcases/basic_pipeline/testcase1.json @@ -0,0 +1,38 @@ +{ + "fields": { + "type": "syslog" + }, + "ignore": [ + "@timestamp" + ], + "testcases": [ + { + "input": [ + "test case message", + "test case message 2" + ], + "expected": [ + { + "message": "test case message", + "tags": [ + "__lfv_in_passed", + "main2_passed", + "sut_passed", + "stdout_passed" + ], + "type": "syslog" + }, + { + "message": "test case message 2", + "tags": [ + "__lfv_in_passed", + "main2_passed", + "sut_passed", + "stdout_passed" + ], + "type": "syslog" + } + ] + } + ] +} diff --git a/testdata/testcases/basic_pipeline/testcase2.json b/testdata/testcases/basic_pipeline/testcase2.json new file mode 100644 index 0000000..8f4841b --- /dev/null +++ b/testdata/testcases/basic_pipeline/testcase2.json @@ -0,0 +1,29 @@ +{ + "fields": { + "first": "1", + "second": "2" + }, + "ignore": [ + "@timestamp" + ], + "testcases": [ + { + "input": [ + "" + ], + "expected": [ + { + "first": "1", + "message": "", + "second": "2", + "tags": [ + "__lfv_in_passed", + "main2_passed", + "sut_passed", + "stdout_passed" + ] + } + ] + } + ] +} diff --git a/testdata/testcases/conditional_output/conditional_output.json b/testdata/testcases/conditional_output/conditional_output.json new file mode 100644 index 0000000..be60d7d --- /dev/null +++ b/testdata/testcases/conditional_output/conditional_output.json @@ -0,0 +1,33 @@ +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "testcases": [ + { + "input": [ + "stdout", + "file" + ], + "expected": [ + { + "message": "stdout", + "tags": [ + "__lfv_in_passed", + "conditional_output_filter", + "stdout_passed" + ] + }, + { + "message": "file", + "tags": [ + "__lfv_in_passed", + "conditional_output_filter", + "file_passed" + ] + } + ] + } + ] +} diff --git a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json new file mode 100644 index 0000000..f8fc623 --- /dev/null +++ b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json @@ -0,0 +1,25 @@ +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "testcases": [ + { + "input": [ + "test case message" + ], + "expected": [ + { + "message": "test case message", + "tags": [ + "__lfv_in_passed", + "mutate_stage_1_passed", + "mutate_stage_2_passed", + "stage2_output_passed" + ] + } + ] + } + ] +} diff --git a/testdata/testcases/testcase_lfv.json b/testdata/testcases/testcase_lfv.json new file mode 100644 index 0000000..1b36e4c --- /dev/null +++ b/testdata/testcases/testcase_lfv.json @@ -0,0 +1,25 @@ +{ + "fields": { + "type": "syslog" + }, + "ignore": [ + "@timestamp", + "host" + ], + "testcases": [ + { + "input": [ + "" + ], + "expected": [ + { + "message": "", + "tags": [ + "sut_passed" + ], + "type": "syslog" + } + ] + } + ] +} diff --git a/tools.go b/tools.go index 10a5fd3..b144964 100644 --- a/tools.go +++ b/tools.go @@ -10,4 +10,7 @@ import ( _ "github.com/axw/gocov/gocov" _ "github.com/go-playground/overalls" _ "github.com/matm/gocov-html" + _ "github.com/matryer/moq" + _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" + _ "google.golang.org/protobuf/cmd/protoc-gen-go" ) From a5a130dc5ef0f57621f98857ae81e8ab893e8f03 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 08:35:26 +0100 Subject: [PATCH 025/143] Address review feedback --- Makefile | 4 +-- README.md | 12 +++++++++ internal/app/daemon/daemon.go | 47 +++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index acfcef5..d9523a4 100644 --- a/Makefile +++ b/Makefile @@ -69,11 +69,9 @@ $(GOVVV): $(OVERALLS): go get github.com/go-playground/overalls -# TODO: For protoc to find this dependency, I suppose, they must reside in the PATH $(PROTOC_GEN_GO): go get google.golang.org/protobuf/cmd/protoc-gen-go -# TODO: For protoc to find this dependency, I suppose, they must reside in the PATH $(PROTOC_GEN_GO_GRPC): go get google.golang.org/grpc/cmd/protoc-gen-go-grpc @@ -86,7 +84,7 @@ $(PROGRAM)$(EXEC_SUFFIX): gogenerate .FORCE $(GOVVV) govvv build -o $@ .PHONY: gogenerate -generate: $(MOQ) # TODO: go generate also depends on protobuf-compiler, which needs to be installed as well. +generate: $(MOQ) go generate ./... .PHONY: check diff --git a/README.md b/README.md index 8cc51a4..8610a07 100644 --- a/README.md +++ b/README.md @@ -466,6 +466,18 @@ are a couple of known quirks that are easy to work around: tool to use. +## Development + +### Dependencies + +For a fully working development environment, the following tooling needs to be +present: + +* Go compiler +* `make` command +* Proto buffer compiler (`protobuf-compiler`) + + ## Known limitations and future work * Some log formats don't include all timestamp components. For diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 33de2b6..70e57ef 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -247,35 +247,38 @@ func (d *Daemon) extractZip(in []byte) (pipeline.Pipelines, []logstashconfig.Fil pipelines := pipeline.Pipelines{} configFiles := make([]logstashconfig.File, 0, len(r.File)) for _, f := range r.File { - rc, err := f.Open() - if err != nil { - return pipeline.Pipelines{}, nil, err - } - - switch f.Name { - case "pipelines.yml": - pipelinesBody, err := ioutil.ReadAll(rc) + err = func() (err error) { + rc, err := f.Open() if err != nil { - return pipeline.Pipelines{}, nil, err + return err } + defer func() { + errClose := rc.Close() + if errClose != nil { + err = errors.Wrapf(errClose, "failed to close file, underlying error: %v", err) + } + }() - err = yaml.Unmarshal([]byte(pipelinesBody), &pipelines) - if err != nil { - return pipeline.Pipelines{}, nil, err - } - default: body, err := ioutil.ReadAll(rc) if err != nil { - return pipeline.Pipelines{}, nil, err - } - configFile := logstashconfig.File{ - Name: f.Name, - Body: body, + return err } - configFiles = append(configFiles, configFile) - } - err = rc.Close() + switch f.Name { + case "pipelines.yml": + err = yaml.Unmarshal(body, &pipelines) + if err != nil { + return err + } + default: + configFile := logstashconfig.File{ + Name: f.Name, + Body: body, + } + configFiles = append(configFiles, configFile) + } + return nil + }() if err != nil { return pipeline.Pipelines{}, nil, err } From c05bfd9443ff03d970d078d089163913d32d54a4 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 08:42:57 +0100 Subject: [PATCH 026/143] Rename test package to run for consistency --- go.mod | 1 + go.sum | 2 ++ integration_test.go | 4 ++-- internal/app/daemon/{test/test.go => run/run.go} | 2 +- internal/app/daemon_run.go | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) rename internal/app/daemon/{test/test.go => run/run.go} (99%) diff --git a/go.mod b/go.mod index 622c002..48a4306 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/breml/logstash-config v0.4.0 + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 diff --git a/go.sum b/go.sum index 1eef112..edfdf45 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= diff --git a/integration_test.go b/integration_test.go index 843cd3b..25245eb 100644 --- a/integration_test.go +++ b/integration_test.go @@ -10,7 +10,7 @@ import ( "github.com/matryer/is" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/test" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/run" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" @@ -90,7 +90,7 @@ func TestIntegration(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - client, err := test.New( + client, err := run.New( path.Join(tempdir, "integration_test.socket"), log, "testdata/"+tc.name+".yml", diff --git a/internal/app/daemon/test/test.go b/internal/app/daemon/run/run.go similarity index 99% rename from internal/app/daemon/test/test.go rename to internal/app/daemon/run/run.go index e6f6792..e9080b7 100644 --- a/internal/app/daemon/test/test.go +++ b/internal/app/daemon/run/run.go @@ -1,4 +1,4 @@ -package test +package run import ( "context" diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 05ad269..10c6a3a 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -4,7 +4,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/test" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/run" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) @@ -29,7 +29,7 @@ func runDaemonRun(_ *cobra.Command, args []string) error { // TODO: Remove this // logging.SetLevel(oplogging.INFO) - t, err := test.New(viper.GetString("socket"), viper.Get("logger").(logging.Logger), viper.GetString("pipeline"), viper.GetString("pipeline-base"), viper.GetString("testcase-dir")) + t, err := run.New(viper.GetString("socket"), viper.Get("logger").(logging.Logger), viper.GetString("pipeline"), viper.GetString("pipeline-base"), viper.GetString("testcase-dir")) if err != nil { return err } From b4209bfaea662c8c6cb7f707631c814634ca8112 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 14:08:39 +0100 Subject: [PATCH 027/143] Signal shutdown with cancel context --- integration_test.go | 9 +- internal/app/daemon/daemon.go | 156 +++++++++++------- internal/app/daemon_start.go | 4 +- internal/daemon/controller/controller.go | 15 +- internal/daemon/controller/controller_test.go | 30 ++-- internal/daemon/controller/instance.go | 5 +- internal/daemon/controller/statemachine.go | 15 +- .../daemon/instance/logstash/dummy_reader.go | 9 +- internal/daemon/instance/logstash/instance.go | 41 +++-- .../daemon/instance/logstash/processors.go | 14 +- .../instance/mock/logstash_instance_mock.go | 59 +++---- internal/daemon/session/controller.go | 8 +- .../session/logstash_controller_mock_test.go | 24 ++- 13 files changed, 215 insertions(+), 174 deletions(-) diff --git a/integration_test.go b/integration_test.go index 25245eb..ec9d316 100644 --- a/integration_test.go +++ b/integration_test.go @@ -49,12 +49,17 @@ func TestIntegration(t *testing.T) { log := testLogger server := daemon.New(socket, logstashPath, log) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + defer cancel() + is := is.New(t) defer server.Cleanup() - err := server.Run() + err := server.Run(ctx) is.NoErr(err) }() @@ -107,5 +112,5 @@ func TestIntegration(t *testing.T) { _, err := server.Shutdown(context.Background(), &grpc.ShutdownRequest{}) is.NoErr(err) - time.Sleep(3 * time.Second) + <-ctx.Done() } diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 70e57ef..c7dbf59 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -26,6 +26,18 @@ import ( ) type Daemon struct { + // This context is passed to exec.Command. When killFunc is called, + // the child process is killed with signal kill. + killFunc context.CancelFunc + + // the ctxShutdownSignal allows shutdown request, that are received over + // gRPC to signal shutdown to the shutdownSignalHandler. + ctxShutdownSignal context.Context + + // shutdownSignalFunc is used by the shutdown gRPC handler to signal + // shutdown to the shutdownSignalHandler. + shutdownSignalFunc context.CancelFunc + socket string logstashPath string @@ -36,18 +48,6 @@ type Daemon struct { server *grpc.Server logstashController *controller.Controller - // This channel is closed as soon as the shutdown is in progress. - shutdownInProgress chan struct{} - - // Global shutdownLogstashInstance channel, all Go routines should listen to this channel to - // get notified and safely exit on shutdownLogstashInstance of the daemon. - // The shutdownSignalHandler() will close this channel on shutdownLogstashInstance. - shutdownLogstashInstance chan struct{} - - // shutdownSignal is sent by the Shutdown GRPC handler, when a shutdown command - // is received. The shutdownSignal channel is processed by the shutdownSignalHandler(). - shutdownSignal chan struct{} - // Global shutdown wait group. Daemon.Run() will wait for this wait group // before returning and exiting the main Go routine. shutdownLogstashInstancesWG *sync.WaitGroup @@ -59,19 +59,26 @@ type Daemon struct { // New creates a new logstash filter verifier daemon. func New(socket string, logstashPath string, log logging.Logger) Daemon { + ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ socket: socket, logstashPath: logstashPath, log: log, - shutdownInProgress: make(chan struct{}), - shutdownLogstashInstance: make(chan struct{}), - shutdownSignal: make(chan struct{}), shutdownLogstashInstancesWG: &sync.WaitGroup{}, + ctxShutdownSignal: ctxShutdownSignal, + shutdownSignalFunc: shutdownSignalFunc, } } // Run starts the logstash filter verifier daemon. -func (d *Daemon) Run() error { +func (d *Daemon) Run(ctx context.Context) error { + // Two stage exit, cancel allows for graceful shutdown + // kill exits sub processes with signal kill. + ctxKill, killFunc := context.WithCancel(ctx) + d.killFunc = killFunc + ctx, shutdown := context.WithCancel(ctxKill) + defer shutdown() + tempdir, err := ioutil.TempDir("", "lfv-") if err != nil { return err @@ -81,14 +88,14 @@ func (d *Daemon) Run() error { // Create and start Logstash Controller d.shutdownLogstashInstancesWG.Add(1) - instance := logstash.New(d.logstashPath, d.log, d.shutdownLogstashInstance, d.shutdownLogstashInstancesWG) - logstashController, err := controller.NewController(instance, tempdir, d.log, d.shutdownLogstashInstance) + instance := logstash.New(ctxKill, d.logstashPath, d.log, d.shutdownLogstashInstancesWG) + logstashController, err := controller.NewController(instance, tempdir, d.log) if err != nil { return err } d.logstashController = logstashController - err = d.logstashController.Launch() + err = d.logstashController.Launch(ctx) if err != nil { return err } @@ -96,10 +103,6 @@ func (d *Daemon) Run() error { // Create Session Handler d.sessionController = session.NewController(d.tempdir, d.logstashController, d.log) - // Setup signal handler and shutdown coordinator - shutdownHandlerCompleted := make(chan struct{}) - go d.shutdownSignalHandler(shutdownHandlerCompleted) - // Create and start GRPC Server lis, err := net.Listen("unix", d.socket) if err != nil { @@ -107,46 +110,75 @@ func (d *Daemon) Run() error { } d.server = grpc.NewServer() pb.RegisterControlServer(d.server, d) + go func() { + d.log.Infof("Daemon listening on %s", d.socket) + err = d.server.Serve(lis) + if err != nil { + d.log.Error("failed to start daemon: %v", err) + shutdown() + } + }() - d.log.Infof("Daemon listening on %s", d.socket) - err = d.server.Serve(lis) - - // This is called from the main Go routine, so we have to wait for all others - // to shutdown, before we can return and end the program/daemon. - <-shutdownHandlerCompleted + // Setup signal handler and shutdown coordinator + d.shutdownSignalHandler(shutdown) - return err + return nil } -func (d *Daemon) shutdownSignalHandler(shutdownHandlerCompleted chan struct{}) { - // Make sure, shutdownHandlerCompleted channel is closed and main Go routine - // exits cleanly. - defer close(shutdownHandlerCompleted) +const hardExitDelay = 20 * time.Millisecond + +func (d *Daemon) shutdownSignalHandler(shutdown func()) { + var hardExit bool + + defer func() { + d.killFunc() + if hardExit { + // Give a little time to propagate Done to kill context + time.Sleep(hardExitDelay) + err := os.Remove(d.socket) + if err != nil && !os.IsNotExist(err) { + d.log.Warningf("failed to remove socket file %s during hard exit: %v", d.socket, err) + } + } + }() - // Listen to shutdown signal (comming from shutdown GRPC requests) as well + // Listen to shutdown signal (coming from shutdown GRPC requests) as well // as OS signals interrupt and SIGTERM (not present on all systems). - c := make(chan os.Signal, 2) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) + sigInt := make(chan os.Signal, 10) + signal.Notify(sigInt, os.Interrupt) + sigTerm := make(chan os.Signal, 10) + signal.Notify(sigTerm, syscall.SIGTERM) select { - case <-d.shutdownSignal: - case <-c: + case <-d.ctxShutdownSignal.Done(): + case <-sigInt: + d.log.Info("Interrupt signal (Ctrl+c) received. Shutdown initiated.") + d.log.Info("Press Ctrl+c again to exit immediately") + case <-sigTerm: + d.log.Info("Term signal received. Shutdown initiated.") } - // Shutdown signal or OS signal received, start shutdown procedure - // Signal shutdown to all - close(d.shutdownInProgress) - close(d.shutdownSignal) - // TODO: Make shutdown timeout configurable - t := time.NewTimer(3 * time.Second) + t := time.NewTimer(10 * time.Second) // Wait for currently running sessions to finish. select { case <-d.sessionController.WaitFinish(): - t.Stop() case <-t.C: + d.log.Debug("Wait for sessions timed out") + case <-sigInt: + d.log.Debug("Double interrupt signal received, exit now") + hardExit = true + return } + // Stop timer and drain channel + if !t.Stop() { + select { + case <-t.C: + default: + } + } + shutdown() // Stop accepting new connections, wait for currently running handlers to finish properly. serverStopped := make(chan struct{}) @@ -158,13 +190,12 @@ func (d *Daemon) shutdownSignalHandler(shutdownHandlerCompleted chan struct{}) { // Stop Logstash instance logstashInstancesStopped := make(chan struct{}) go func() { - close(d.shutdownLogstashInstance) d.shutdownLogstashInstancesWG.Wait() close(logstashInstancesStopped) }() // TODO: Make shutdown timeout configurable - t.Reset(3 * time.Second) + t.Reset(10 * time.Second) // Wait for Logstash and GRPC Server to shutdown serverStopComplete := false @@ -184,9 +215,19 @@ func (d *Daemon) shutdownSignalHandler(shutdownHandlerCompleted chan struct{}) { d.log.Debug("logstash instance successfully stopped.") logstashInstanceStopComplete = true logstashInstancesStopped = nil + case <-sigInt: + d.log.Debug("Double interrupt signal received, exit now") + hardExit = true + return + } + } + // Stop timer and drain channel + if !t.Stop() { + select { + case <-t.C: + default: } } - t.Stop() } // Cleanup removes the temporary files created by the daemon. @@ -200,27 +241,22 @@ func (d *Daemon) Cleanup() { // Shutdown signals the daemon to shutdown. func (d *Daemon) Shutdown(ctx context.Context, in *pb.ShutdownRequest) (*pb.ShutdownResponse, error) { select { - case d.shutdownSignal <- struct{}{}: + case <-d.ctxShutdownSignal.Done(): + return nil, errors.New("daemon is already shutting down") default: + d.shutdownSignalFunc() } return &pb.ShutdownResponse{}, nil } -func (d *Daemon) isShutdownInProgress() bool { - select { - case <-d.shutdownInProgress: - return true - default: - } - return false -} - // SetupTest creates a new session, receives the pipeline configuration // (zip archive), and prepares the files for the new session. func (d *Daemon) SetupTest(ctx context.Context, in *pb.SetupTestRequest) (*pb.SetupTestResponse, error) { - if d.isShutdownInProgress() { + select { + case <-d.ctxShutdownSignal.Done(): return nil, errors.New("daemon is shutting down, no new sessions accepted") + default: } pipelines, configFiles, err := d.extractZip(in.Pipeline) diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index 765a37c..fa623c9 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -1,6 +1,8 @@ package app import ( + "context" + "github.com/spf13/cobra" "github.com/spf13/viper" @@ -32,5 +34,5 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { s := daemon.New(socket, logstashPath, log) defer s.Cleanup() - return s.Run() + return s.Run(context.Background()) } diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 2fd6394..21f5ca0 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -1,6 +1,7 @@ package controller import ( + "context" "io/ioutil" "os" "path" @@ -16,13 +17,13 @@ import ( const LogstashInstanceDirectoryPrefix = "logstash-instance" type Controller struct { + ctx context.Context + id string workDir string log logging.Logger - shutdown chan struct{} - instance Instance stateMachine *stateMachine @@ -30,7 +31,7 @@ type Controller struct { pipelines *pipelines } -func NewController(instance Instance, baseDir string, log logging.Logger, shutdown chan struct{}) (*Controller, error) { +func NewController(instance Instance, baseDir string, log logging.Logger) (*Controller, error) { id := idgen.New() workDir := path.Join(baseDir, LogstashInstanceDirectoryPrefix, id) @@ -64,10 +65,8 @@ func NewController(instance Instance, baseDir string, log logging.Logger, shutdo id: id, workDir: workDir, log: log, - shutdown: shutdown, instance: instance, - stateMachine: newStateMachine(shutdown, log), receivedEvents: newEvents(), pipelines: newPipelines(), } @@ -84,13 +83,13 @@ func (c *Controller) ID() string { return c.id } -func (c *Controller) Launch() error { +func (c *Controller) Launch(ctx context.Context) error { c.pipelines.reset("stdin", "output") + c.stateMachine = newStateMachine(ctx, c.log) c.stateMachine.executeCommand(commandStart) - err := c.instance.Start(c, c.workDir) + err := c.instance.Start(ctx, c, c.workDir) if err != nil { - c.instance.Shutdown() return err } diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index 316d6a4..59ec811 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -1,6 +1,7 @@ package controller_test import ( + "context" "errors" "path" "testing" @@ -29,7 +30,7 @@ func TestNewController(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(nil, tempdir, logging.NoopLogger, nil) + c, err := controller.NewController(nil, tempdir, logging.NoopLogger) is.NoErr(err) is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml @@ -64,18 +65,17 @@ func TestLaunch(t *testing.T) { is := is.New(t) instance := &mock.InstanceMock{ - StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { + StartFunc: func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { return test.instanceStartErr }, - ShutdownFunc: func() {}, } tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, nil) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger) is.NoErr(err) - err = c.Launch() + err = c.Launch(context.Background()) is.True(err != nil == test.wantErr) // Launch error }) } @@ -95,7 +95,7 @@ func TestCompleteCycle(t *testing.T) { is := is.New(t) instance := &mock.InstanceMock{ - StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { + StartFunc: func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { return nil }, ConfigReloadFunc: func() error { @@ -105,12 +105,10 @@ func TestCompleteCycle(t *testing.T) { tempdir := t.TempDir() - shutdown := make(chan struct{}) - - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, shutdown) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger) is.NoErr(err) - err = c.Launch() + err = c.Launch(context.Background()) is.NoErr(err) // Simulate pipelines ready from instance @@ -182,7 +180,7 @@ func TestSetupTest_Shutdown(t *testing.T) { is := is.New(t) instance := &mock.InstanceMock{ - StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { + StartFunc: func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { return nil }, ConfigReloadFunc: func() error { @@ -191,16 +189,16 @@ func TestSetupTest_Shutdown(t *testing.T) { } tempdir := t.TempDir() - shutdown := make(chan struct{}) - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, shutdown) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger) is.NoErr(err) - err = c.Launch() + ctx, cancel := context.WithCancel(context.Background()) + err = c.Launch(ctx) is.NoErr(err) - // Simulate shutdown signal - close(shutdown) + // signal shutdown + cancel() pipelines := pipeline.Pipelines{ pipeline.Pipeline{ diff --git a/internal/daemon/controller/instance.go b/internal/daemon/controller/instance.go index 1f6dbe8..b16a2f8 100644 --- a/internal/daemon/controller/instance.go +++ b/internal/daemon/controller/instance.go @@ -1,9 +1,10 @@ package controller +import "context" + //go:generate moq -fmt goimports -pkg mock -out ../instance/mock/logstash_instance_mock.go . Instance type Instance interface { - Start(controller *Controller, workdir string) error - Shutdown() + Start(ctx context.Context, controller *Controller, workdir string) error ConfigReload() error } diff --git a/internal/daemon/controller/statemachine.go b/internal/daemon/controller/statemachine.go index 6320ab1..99b3d67 100644 --- a/internal/daemon/controller/statemachine.go +++ b/internal/daemon/controller/statemachine.go @@ -1,6 +1,7 @@ package controller import ( + "context" "sync" "github.com/pkg/errors" @@ -9,27 +10,29 @@ import ( ) type stateMachine struct { + ctx context.Context + currentState stateName mutex *sync.Mutex cond *sync.Cond - shutdown chan struct{} - log logging.Logger + log logging.Logger } -func newStateMachine(shutdown chan struct{}, log logging.Logger) *stateMachine { +func newStateMachine(ctx context.Context, log logging.Logger) *stateMachine { mu := &sync.Mutex{} cond := sync.NewCond(mu) go func() { - <-shutdown + <-ctx.Done() log.Debug("broadcast shutdown for waitForState") cond.Broadcast() }() return &stateMachine{ + ctx: ctx, + currentState: stateCreated, mutex: mu, cond: cond, - shutdown: shutdown, log: log, } } @@ -45,7 +48,7 @@ func (s *stateMachine) waitForState(target stateName) error { s.cond.Wait() select { - case <-s.shutdown: + case <-s.ctx.Done(): // TODO: Can we do this without error return? return errors.Errorf("shutdown while waiting for state: %s", target) default: diff --git a/internal/daemon/instance/logstash/dummy_reader.go b/internal/daemon/instance/logstash/dummy_reader.go index 2be6dc6..88fba1f 100644 --- a/internal/daemon/instance/logstash/dummy_reader.go +++ b/internal/daemon/instance/logstash/dummy_reader.go @@ -1,6 +1,9 @@ package logstash -import "io" +import ( + "context" + "io" +) // stdinBlockReader implements the io.Reader interface and blocks reading // until the shutdown channel unblocks (close of channel). @@ -10,10 +13,10 @@ import "io" // This stdinBlockReader is used to block stdin of the controlled // Logstash instance. type stdinBlockReader struct { - shutdown chan struct{} + ctx context.Context } func (s *stdinBlockReader) Read(_ []byte) (int, error) { - <-s.shutdown + <-s.ctx.Done() return 0, io.EOF } diff --git a/internal/daemon/instance/logstash/instance.go b/internal/daemon/instance/logstash/instance.go index a5ce42b..342e412 100644 --- a/internal/daemon/instance/logstash/instance.go +++ b/internal/daemon/instance/logstash/instance.go @@ -1,6 +1,7 @@ package logstash import ( + "context" "os" "os/exec" "sync" @@ -14,6 +15,9 @@ import ( ) type instance struct { + ctxKill context.Context + ctxShutdown context.Context + controller *controller.Controller command string @@ -22,18 +26,16 @@ type instance struct { log logging.Logger logstashStarted chan struct{} - shutdown chan struct{} - instanceShutdown chan struct{} logstashShutdownWG *sync.WaitGroup shutdownWG *sync.WaitGroup } -func New(command string, log logging.Logger, shutdown chan struct{}, shutdownWG *sync.WaitGroup) controller.Instance { +func New(ctxKill context.Context, command string, log logging.Logger, shutdownWG *sync.WaitGroup) controller.Instance { return &instance{ + ctxKill: ctxKill, command: command, log: log, logstashStarted: make(chan struct{}), - shutdown: shutdown, logstashShutdownWG: &sync.WaitGroup{}, shutdownWG: shutdownWG, } @@ -41,7 +43,17 @@ func New(command string, log logging.Logger, shutdown chan struct{}, shutdownWG // start starts a Logstash child process with the previously supplied // configuration. -func (i *instance) Start(controller *controller.Controller, workdir string) error { +func (i *instance) Start(ctx context.Context, controller *controller.Controller, workdir string) (err error) { + ctx, cancel := context.WithCancel(ctx) + i.ctxShutdown = ctx + defer func() { + // if there has been an error during Start, cancel the context to signal + // shutdown to all potentially running Go routines of instance. + if err != nil { + cancel() + } + }() + i.controller = controller args := []string{ @@ -49,7 +61,7 @@ func (i *instance) Start(controller *controller.Controller, workdir string) erro workdir, } - i.child = exec.Command(i.command, args...) + i.child = exec.CommandContext(i.ctxKill, i.command, args...) // nolint: gosec stdout, err := i.child.StdoutPipe() if err != nil { return errors.Wrap(err, "failed to setup stdoutPipe") @@ -58,10 +70,12 @@ func (i *instance) Start(controller *controller.Controller, workdir string) erro if err != nil { return errors.Wrap(err, "failed to setup stdoutPipe") } - i.instanceShutdown = make(chan struct{}) i.child.Stdin = &stdinBlockReader{ - shutdown: i.instanceShutdown, + ctx: i.ctxShutdown, } + // Ensure a separate process group id for the Logstash child process, such + // that signals like interrupt are not propagated automatically. + i.child.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} i.logstashShutdownWG.Add(2) go i.stdoutProcessor(stdout) @@ -89,17 +103,9 @@ func (i *instance) Start(controller *controller.Controller, workdir string) erro return nil } -// TODO: What is needed for what? func (i *instance) shutdownSignalHandler() { // Wait for shutdown signal coming from the daemon. - <-i.shutdown - - i.Shutdown() -} - -// TODO: What is needed for what? -func (i *instance) Shutdown() { - close(i.instanceShutdown) + <-i.ctxShutdown.Done() i.stopLogstash() @@ -118,7 +124,6 @@ func (i *instance) stopLogstash() { i.log.Errorf("failed to send SIGTERM, Logstash might already be down:", err) } - // TODO: Add timeout, then send syscall.SIGKILL err = i.child.Wait() if err != nil { i.log.Errorf("failed to wait for child process: %v", err) diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go index 25a607e..fc4f4f9 100644 --- a/internal/daemon/instance/logstash/processors.go +++ b/internal/daemon/instance/logstash/processors.go @@ -15,7 +15,7 @@ func (i *instance) stdoutProcessor(stdout io.ReadCloser) { // The stdoutProcessor can only be started after the process is created. select { case <-i.logstashStarted: - case <-i.instanceShutdown: + case <-i.ctxShutdown.Done(): return } @@ -42,9 +42,9 @@ func (i *instance) stdoutProcessor(stdout io.ReadCloser) { // Termination of stdout scanner is only expected, if shutdown is in progress. select { - case <-i.instanceShutdown: + case <-i.ctxShutdown.Done(): default: - i.log.Warning("stdout scanner closed unexpectetly") + i.log.Warning("stdout scanner closed unexpectedly") } i.log.Debug("exit stdout scanner") @@ -56,7 +56,7 @@ func (i *instance) stderrProcessor(stderr io.ReadCloser) { // The stderrProcessor can only be started after the process is created. select { case <-i.logstashStarted: - case <-i.instanceShutdown: + case <-i.ctxShutdown.Done(): return } @@ -72,9 +72,9 @@ func (i *instance) stderrProcessor(stderr io.ReadCloser) { // Termination of stderr scanner is only expected, if shutdown is in progress. select { - case <-i.instanceShutdown: + case <-i.ctxShutdown.Done(): default: - i.log.Warning("stderr scanner closed unexpectetly") + i.log.Warning("stderr scanner closed unexpectedly") } i.log.Debug("exit stderr scanner") @@ -99,7 +99,7 @@ func (i *instance) logstashLogProcessor(t *tail.Tail) { i.controller.PipelinesReady(runningPipelines...) } - case <-i.instanceShutdown: + case <-i.ctxShutdown.Done(): i.log.Debug("shutdown log reader") return } diff --git a/internal/daemon/instance/mock/logstash_instance_mock.go b/internal/daemon/instance/mock/logstash_instance_mock.go index 58b6b89..63c4ca4 100644 --- a/internal/daemon/instance/mock/logstash_instance_mock.go +++ b/internal/daemon/instance/mock/logstash_instance_mock.go @@ -4,6 +4,7 @@ package mock import ( + "context" "sync" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/controller" @@ -22,10 +23,7 @@ var _ controller.Instance = &InstanceMock{} // ConfigReloadFunc: func() error { // panic("mock out the ConfigReload method") // }, -// ShutdownFunc: func() { -// panic("mock out the Shutdown method") -// }, -// StartFunc: func(controllerMoqParam *controller.Controller, workdir string) error { +// StartFunc: func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { // panic("mock out the Start method") // }, // } @@ -38,20 +36,18 @@ type InstanceMock struct { // ConfigReloadFunc mocks the ConfigReload method. ConfigReloadFunc func() error - // ShutdownFunc mocks the Shutdown method. - ShutdownFunc func() - // StartFunc mocks the Start method. - StartFunc func(controllerMoqParam *controller.Controller, workdir string) error + StartFunc func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error // calls tracks calls to the methods. calls struct { // ConfigReload holds details about calls to the ConfigReload method. - ConfigReload []struct{} - // Shutdown holds details about calls to the Shutdown method. - Shutdown []struct{} + ConfigReload []struct { + } // Start holds details about calls to the Start method. Start []struct { + // Ctx is the ctx argument value. + Ctx context.Context // ControllerMoqParam is the controllerMoqParam argument value. ControllerMoqParam *controller.Controller // Workdir is the workdir argument value. @@ -59,7 +55,6 @@ type InstanceMock struct { } } lockConfigReload sync.RWMutex - lockShutdown sync.RWMutex lockStart sync.RWMutex } @@ -68,7 +63,8 @@ func (mock *InstanceMock) ConfigReload() error { if mock.ConfigReloadFunc == nil { panic("InstanceMock.ConfigReloadFunc: method is nil but Instance.ConfigReload was just called") } - callInfo := struct{}{} + callInfo := struct { + }{} mock.lockConfigReload.Lock() mock.calls.ConfigReload = append(mock.calls.ConfigReload, callInfo) mock.lockConfigReload.Unlock() @@ -78,63 +74,46 @@ func (mock *InstanceMock) ConfigReload() error { // ConfigReloadCalls gets all the calls that were made to ConfigReload. // Check the length with: // len(mockedInstance.ConfigReloadCalls()) -func (mock *InstanceMock) ConfigReloadCalls() []struct{} { - var calls []struct{} +func (mock *InstanceMock) ConfigReloadCalls() []struct { +} { + var calls []struct { + } mock.lockConfigReload.RLock() calls = mock.calls.ConfigReload mock.lockConfigReload.RUnlock() return calls } -// Shutdown calls ShutdownFunc. -func (mock *InstanceMock) Shutdown() { - if mock.ShutdownFunc == nil { - panic("InstanceMock.ShutdownFunc: method is nil but Instance.Shutdown was just called") - } - callInfo := struct{}{} - mock.lockShutdown.Lock() - mock.calls.Shutdown = append(mock.calls.Shutdown, callInfo) - mock.lockShutdown.Unlock() - mock.ShutdownFunc() -} - -// ShutdownCalls gets all the calls that were made to Shutdown. -// Check the length with: -// len(mockedInstance.ShutdownCalls()) -func (mock *InstanceMock) ShutdownCalls() []struct{} { - var calls []struct{} - mock.lockShutdown.RLock() - calls = mock.calls.Shutdown - mock.lockShutdown.RUnlock() - return calls -} - // Start calls StartFunc. -func (mock *InstanceMock) Start(controllerMoqParam *controller.Controller, workdir string) error { +func (mock *InstanceMock) Start(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { if mock.StartFunc == nil { panic("InstanceMock.StartFunc: method is nil but Instance.Start was just called") } callInfo := struct { + Ctx context.Context ControllerMoqParam *controller.Controller Workdir string }{ + Ctx: ctx, ControllerMoqParam: controllerMoqParam, Workdir: workdir, } mock.lockStart.Lock() mock.calls.Start = append(mock.calls.Start, callInfo) mock.lockStart.Unlock() - return mock.StartFunc(controllerMoqParam, workdir) + return mock.StartFunc(ctx, controllerMoqParam, workdir) } // StartCalls gets all the calls that were made to Start. // Check the length with: // len(mockedInstance.StartCalls()) func (mock *InstanceMock) StartCalls() []struct { + Ctx context.Context ControllerMoqParam *controller.Controller Workdir string } { var calls []struct { + Ctx context.Context ControllerMoqParam *controller.Controller Workdir string } diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go index 486b106..7f2e989 100644 --- a/internal/daemon/session/controller.go +++ b/internal/daemon/session/controller.go @@ -103,9 +103,11 @@ func (s *Controller) WaitFinish() chan struct{} { s.cond.Broadcast() - s.wg.Wait() - c := make(chan struct{}) - close(c) + go func() { + s.wg.Wait() + close(c) + }() + return c } diff --git a/internal/daemon/session/logstash_controller_mock_test.go b/internal/daemon/session/logstash_controller_mock_test.go index 0917476..295749d 100644 --- a/internal/daemon/session/logstash_controller_mock_test.go +++ b/internal/daemon/session/logstash_controller_mock_test.go @@ -61,14 +61,16 @@ type LogstashControllerMock struct { ExpectedEvents int } // GetResults holds details about calls to the GetResults method. - GetResults []struct{} + GetResults []struct { + } // SetupTest holds details about calls to the SetupTest method. SetupTest []struct { // Pipelines is the pipelines argument value. Pipelines pipeline.Pipelines } // Teardown holds details about calls to the Teardown method. - Teardown []struct{} + Teardown []struct { + } } lockExecuteTest sync.RWMutex lockGetResults sync.RWMutex @@ -116,7 +118,8 @@ func (mock *LogstashControllerMock) GetResults() ([]string, error) { if mock.GetResultsFunc == nil { panic("LogstashControllerMock.GetResultsFunc: method is nil but LogstashController.GetResults was just called") } - callInfo := struct{}{} + callInfo := struct { + }{} mock.lockGetResults.Lock() mock.calls.GetResults = append(mock.calls.GetResults, callInfo) mock.lockGetResults.Unlock() @@ -126,8 +129,10 @@ func (mock *LogstashControllerMock) GetResults() ([]string, error) { // GetResultsCalls gets all the calls that were made to GetResults. // Check the length with: // len(mockedLogstashController.GetResultsCalls()) -func (mock *LogstashControllerMock) GetResultsCalls() []struct{} { - var calls []struct{} +func (mock *LogstashControllerMock) GetResultsCalls() []struct { +} { + var calls []struct { + } mock.lockGetResults.RLock() calls = mock.calls.GetResults mock.lockGetResults.RUnlock() @@ -170,7 +175,8 @@ func (mock *LogstashControllerMock) Teardown() error { if mock.TeardownFunc == nil { panic("LogstashControllerMock.TeardownFunc: method is nil but LogstashController.Teardown was just called") } - callInfo := struct{}{} + callInfo := struct { + }{} mock.lockTeardown.Lock() mock.calls.Teardown = append(mock.calls.Teardown, callInfo) mock.lockTeardown.Unlock() @@ -180,8 +186,10 @@ func (mock *LogstashControllerMock) Teardown() error { // TeardownCalls gets all the calls that were made to Teardown. // Check the length with: // len(mockedLogstashController.TeardownCalls()) -func (mock *LogstashControllerMock) TeardownCalls() []struct{} { - var calls []struct{} +func (mock *LogstashControllerMock) TeardownCalls() []struct { +} { + var calls []struct { + } mock.lockTeardown.RLock() calls = mock.calls.Teardown mock.lockTeardown.RUnlock() From 3183011ed85cb04690dc12152d442b35c0dd82be Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 18:15:52 +0100 Subject: [PATCH 028/143] Validate pipeline and configuration prior to test execution --- internal/app/daemon/run/run.go | 29 ++++--- internal/daemon/logstashconfig/file.go | 38 +++++++++- internal/daemon/logstashconfig/file_test.go | 49 ++++++++++++ internal/daemon/pipeline/pipeline.go | 47 +++++++++++- internal/daemon/pipeline/pipeline_test.go | 76 +++++++++++++++++-- .../daemon/pipeline/testdata/folder/main.conf | 8 +- .../pipeline/testdata/invalid/main.conf | 7 ++ .../testdata/pipelines_basic_base_path.yml | 4 + .../testdata/pipelines_invalid_config.yml | 4 + testdata/basic_pipeline/main/dir/main2.conf | 1 + testdata/basic_pipeline/main/main.conf | 6 +- .../main/conditional_output.conf | 1 + 12 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 internal/daemon/pipeline/testdata/invalid/main.conf create mode 100644 internal/daemon/pipeline/testdata/pipelines_basic_base_path.yml create mode 100644 internal/daemon/pipeline/testdata/pipelines_invalid_config.yml diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index e9080b7..0ce9f74 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -47,8 +47,24 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath } func (s Test) Run() error { - s.log.Debug("Test on socket ", s.socket) + // FIXME: Read pipeline, find config (fix paths based on pipelineBase if necessary) + a, err := pipeline.New(s.pipeline, s.pipelineBase) + if err != nil { + return err + } + // TODO: ensure, that IDs are also unique for the whole set of pipelines + err = a.Validate() + if err != nil { + return err + } + + b, err := a.Zip() + if err != nil { + return err + } + + s.log.Debugf("socket to daemon %q", s.socket) conn, err := grpc.Dial( s.socket, grpc.WithInsecure(), @@ -64,17 +80,6 @@ func (s Test) Run() error { defer conn.Close() c := pb.NewControlClient(conn) - // FIXME: Read pipeline, find config (fix paths based on pipelineBase if necessary) - a, err := pipeline.NewArchive(s.pipeline, s.pipelineBase) - if err != nil { - return err - } - - b, err := a.ZipBytes() - if err != nil { - return err - } - result, err := c.SetupTest(context.Background(), &pb.SetupTestRequest{ Pipeline: b, }) diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 4fb0a48..69eaccb 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -1,12 +1,13 @@ package logstashconfig import ( - "errors" "fmt" "io/ioutil" "os" "path" + "github.com/pkg/errors" + config "github.com/breml/logstash-config" "github.com/breml/logstash-config/ast" "github.com/breml/logstash-config/ast/astutil" @@ -116,3 +117,38 @@ func (o *outputPipelineReplacer) walk(c *astutil.Cursor) { c.Replace(ast.NewPlugin("pipeline", ast.NewArrayAttribute("send_to", ast.NewStringAttribute("", outputName, ast.DoubleQuoted)))) } + +func (f *File) Validate() error { + err := f.parse() + if err != nil { + return err + } + + v := validator{} + + for i := range f.config.Input { + astutil.ApplyPlugins(f.config.Input[i].BranchOrPlugins, v.walk) + } + for i := range f.config.Filter { + astutil.ApplyPlugins(f.config.Filter[i].BranchOrPlugins, v.walk) + } + for i := range f.config.Output { + astutil.ApplyPlugins(f.config.Output[i].BranchOrPlugins, v.walk) + } + + if len(v.noIDs) > 0 { + return errors.Errorf("no IDs found for %v", v.noIDs) + } + return nil +} + +type validator struct { + noIDs []string +} + +func (v *validator) walk(c *astutil.Cursor) { + _, err := c.Plugin().ID() + if err != nil { + v.noIDs = append(v.noIDs, c.Plugin().Name()) + } +} diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index b7fef45..c20bdcb 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/matryer/is" + "github.com/pkg/errors" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" @@ -167,3 +168,51 @@ func TestReplaceOutputsWithoutID(t *testing.T) { }) } } + +func TestValidate(t *testing.T) { + cases := []struct { + name string + config string + + wantErr error + }{ + { + name: "successful validate", + config: `input { stdin { id => stdin } } +filter { mutate { id => mutate } } +output { stdout { id => testid } }`, + }, + { + name: "error without ids", + config: `input { stdin { } } +filter { mutate { } } +output { stdout { } }`, + + wantErr: errors.Errorf("no IDs found for [stdin mutate stdout]"), + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + f := logstashconfig.File{ + Body: []byte(test.config), + } + + err := f.Validate() + compareErr(t, test.wantErr, err) + }) + } +} + +func compareErr(t *testing.T, wantErr, err error) { + switch { + case wantErr == nil && err == nil: + case wantErr == nil && err != nil: + t.Errorf("expect error to be nil, got: %q", err.Error()) + case wantErr != nil && err == nil: + t.Errorf("expect error to be %q, got no error", wantErr.Error()) + case wantErr.Error() == err.Error(): + default: + t.Errorf("expect error to be %q, got: %q", wantErr.Error(), err.Error()) + } +} diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index a32daf2..31496e6 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -10,6 +10,8 @@ import ( "github.com/bmatcuk/doublestar/v2" "gopkg.in/yaml.v2" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" ) type Archive struct { @@ -27,7 +29,7 @@ type Pipeline struct { Workers int `yaml:"pipeline.workers"` } -func NewArchive(file, basePath string) (Archive, error) { +func New(file, basePath string) (Archive, error) { b, err := ioutil.ReadFile(file) if err != nil { return Archive{}, err @@ -48,7 +50,48 @@ func NewArchive(file, basePath string) (Archive, error) { return a, nil } -func (a Archive) ZipBytes() ([]byte, error) { +func (a Archive) Validate() error { + for _, pipeline := range a.Pipelines { + files, err := doublestar.Glob(path.Join(a.BasePath, pipeline.Config)) + if err != nil { + return err + } + for _, file := range files { + var relFile string + if path.IsAbs(a.BasePath) { + relFile = strings.TrimPrefix(file, a.BasePath) + } else { + cwd, err := os.Getwd() + if err != nil { + return err + } + relFile = strings.TrimPrefix(file, path.Join(cwd, a.BasePath)) + } + + body, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + configFile := logstashconfig.File{ + Name: relFile, + Body: body, + } + + err = configFile.Validate() + if err != nil { + return err + } + } + } + return nil +} + +// func (a Archive) walk() { + +// } + +func (a Archive) Zip() ([]byte, error) { buf := new(bytes.Buffer) w := zip.NewWriter(buf) diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index 6b221f5..a2f71ba 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -3,6 +3,7 @@ package pipeline_test import ( "archive/zip" "bytes" + "os" "testing" "github.com/matryer/is" @@ -11,7 +12,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" ) -func TestPipeline(t *testing.T) { +func TestNew(t *testing.T) { tt := []struct { name string config string @@ -47,7 +48,51 @@ func TestPipeline(t *testing.T) { } } -func TestZipArchive(t *testing.T) { +func TestValidate(t *testing.T) { + wd, _ := os.Getwd() + + cases := []struct { + name string + pipeline string + basePath string + + wantValidateErr bool + }{ + { + name: "success basic pipeline", + pipeline: "testdata/pipelines_basic.yml", + basePath: "testdata/", + }, + { + name: "success basic pipeline with absolute base path", + pipeline: "testdata/pipelines_basic_base_path.yml", + basePath: wd, + }, + { + name: "error invalid config", + pipeline: "testdata/pipelines_invalid_config.yml", + basePath: "testdata/", + + wantValidateErr: true, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + a, err := pipeline.New(test.pipeline, test.basePath) + is.NoErr(err) + + err = a.Validate() + is.True(err != nil == test.wantValidateErr) // Validate error + }) + } +} + +func TestZip(t *testing.T) { + wd, _ := os.Getwd() + cases := []struct { name string pipeline string @@ -60,24 +105,41 @@ func TestZipArchive(t *testing.T) { { name: "success basic pipeline", pipeline: "testdata/pipelines_basic.yml", + basePath: "testdata/", wantFiles: 2, }, { - name: "success basic pipeline", + name: "success basic pipeline without base path", + pipeline: "testdata/pipelines_basic_base_path.yml", + + wantFiles: 2, + }, + { + name: "success basic pipeline with absolute base path", + pipeline: "testdata/pipelines_basic_base_path.yml", + basePath: wd, + + wantFiles: 2, + }, + { + name: "success advanced pipeline", pipeline: "testdata/pipelines_advanced.yml", + basePath: "testdata/", wantFiles: 3, }, { name: "error pipeline file not found", pipeline: "testdata/pipelines_invalid.yml", + basePath: "testdata/", wantNewArchiveErr: true, }, { name: "error pipeline file not yaml", pipeline: "testdata/pipelines_invalid_yaml.yml", + basePath: "testdata/", wantNewArchiveErr: true, }, @@ -87,15 +149,15 @@ func TestZipArchive(t *testing.T) { t.Run(test.name, func(t *testing.T) { is := is.New(t) - a, err := pipeline.NewArchive(test.pipeline, "testdata") - is.True(err != nil == test.wantNewArchiveErr) // NewArchive error + a, err := pipeline.New(test.pipeline, test.basePath) + is.True(err != nil == test.wantNewArchiveErr) // New error if test.wantNewArchiveErr { return } - b, err := a.ZipBytes() - is.True(err != nil == test.wantZipBytesErr) // ZipBytes error + b, err := a.Zip() + is.True(err != nil == test.wantZipBytesErr) // Zip error if test.wantZipBytesErr { return diff --git a/internal/daemon/pipeline/testdata/folder/main.conf b/internal/daemon/pipeline/testdata/folder/main.conf index 177e889..e5c74bc 100644 --- a/internal/daemon/pipeline/testdata/folder/main.conf +++ b/internal/daemon/pipeline/testdata/folder/main.conf @@ -1,7 +1,11 @@ input { - stdin {} + stdin { + id => stdin + } } output { - stdout {} + stdout { + id => stdout + } } diff --git a/internal/daemon/pipeline/testdata/invalid/main.conf b/internal/daemon/pipeline/testdata/invalid/main.conf new file mode 100644 index 0000000..177e889 --- /dev/null +++ b/internal/daemon/pipeline/testdata/invalid/main.conf @@ -0,0 +1,7 @@ +input { + stdin {} +} + +output { + stdout {} +} diff --git a/internal/daemon/pipeline/testdata/pipelines_basic_base_path.yml b/internal/daemon/pipeline/testdata/pipelines_basic_base_path.yml new file mode 100644 index 0000000..410d745 --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_basic_base_path.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "testdata/folder/main.conf" diff --git a/internal/daemon/pipeline/testdata/pipelines_invalid_config.yml b/internal/daemon/pipeline/testdata/pipelines_invalid_config.yml new file mode 100644 index 0000000..5588b3f --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_invalid_config.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "invalid/main.conf" diff --git a/testdata/basic_pipeline/main/dir/main2.conf b/testdata/basic_pipeline/main/dir/main2.conf index 8411fc2..4ca4d61 100644 --- a/testdata/basic_pipeline/main/dir/main2.conf +++ b/testdata/basic_pipeline/main/dir/main2.conf @@ -1,5 +1,6 @@ filter { mutate { + id => mutate2 add_tag => [ "main2_passed" ] } } \ No newline at end of file diff --git a/testdata/basic_pipeline/main/main.conf b/testdata/basic_pipeline/main/main.conf index 5a1c8fd..fbb4273 100644 --- a/testdata/basic_pipeline/main/main.conf +++ b/testdata/basic_pipeline/main/main.conf @@ -1,11 +1,13 @@ input { - stdin {} + stdin { + id => stdin + } } filter { mutate { + id => mutate add_tag => [ "sut_passed" ] - # foobar => "test" } } diff --git a/testdata/conditional_output/main/conditional_output.conf b/testdata/conditional_output/main/conditional_output.conf index a1d02f9..02aac69 100644 --- a/testdata/conditional_output/main/conditional_output.conf +++ b/testdata/conditional_output/main/conditional_output.conf @@ -6,6 +6,7 @@ input { filter { mutate { + id => mutate add_tag => [ "conditional_output_filter" ] } } From 2f7d1b535b4749d4447a5927b5c3c2e1cc14be3d Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 20:18:07 +0100 Subject: [PATCH 029/143] Ensure ordering of results from Logstash --- go.mod | 1 + go.sum | 2 ++ internal/daemon/controller/files.go | 28 ++++++++++++++++------------ internal/daemon/session/files.go | 4 ++-- internal/daemon/session/session.go | 18 +++++++++++++++++- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 48a4306..22c71e8 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.5.1 github.com/tidwall/gjson v1.6.8 + github.com/tidwall/sjson v1.1.5 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect google.golang.org/grpc v1.34.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 diff --git a/go.sum b/go.sum index edfdf45..4d2a350 100644 --- a/go.sum +++ b/go.sum @@ -392,6 +392,8 @@ github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE= +github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c= github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= diff --git a/internal/daemon/controller/files.go b/internal/daemon/controller/files.go index dca211a..e32c324 100644 --- a/internal/daemon/controller/files.go +++ b/internal/daemon/controller/files.go @@ -40,18 +40,22 @@ const outputPipeline = `input { address => __lfv_output } } -# filter { -# mutate { -# copy => { -# "[@metadata]" => "[__metadata]" -# } -# } -# ruby { -# code => 'metadata = event.get("__metadata") -# metadata.delete_if {|key, value| key.start_with?("__lfv") || key.start_with?("__tmp") } -# event.set("__metadata", metadata)' -# } -# } +filter { + mutate { + copy => { + "[@metadata]" => "[__metadata]" + } + } + ruby { + code => 'metadata = event.get("__metadata") + metadata.delete_if {|key, value| key.start_with?("__lfv") || key.start_with?("__tmp") } + if metadata.length > 0 + event.set("__metadata", metadata) + else + event.remove("__metadata") + end' + } +} output { stdout { codec => json_lines diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index e19af51..df808de 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -38,13 +38,13 @@ filter { remove_field => [ "host", "sequence" ] # We use the message as the LFV event ID, so move this to the right field. replace => { - "[@metadata][__lfv_id]" => "%{[message]}" + "[__lfv_id]" => "%{[message]}" } } translate { dictionary_path => "{{ .FieldsFilename }}" - field => "[@metadata][__lfv_id]" + field => "[__lfv_id]" destination => "[@metadata][__lfv_fields]" exact => true override => true diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index aad487e..5c2eb69 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -6,10 +6,13 @@ import ( "io/ioutil" "os" "path" + "sort" "strconv" "strings" "github.com/pkg/errors" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" @@ -226,7 +229,20 @@ func createInput(pipelineFilename string, fieldsFilename string, ids []string) e // GetResults returns the returned events from Logstash. func (s *Session) GetResults() ([]string, error) { - return s.logstashController.GetResults() + results, err := s.logstashController.GetResults() + if err != nil { + return results, err + } + sort.Slice(results, func(i, j int) bool { + return gjson.Get(results[i], `__lfv_id`).Int() < gjson.Get(results[j], `__lfv_id`).Int() + }) + for i := range results { + results[i], err = sjson.Delete(results[i], "__lfv_id") + if err != nil { + return results, err + } + } + return results, err } // GetStats returns the statistics for a test suite. From 1f95cf45057955391de5ef85177ad8852d550572 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 20:22:29 +0100 Subject: [PATCH 030/143] Fix linting issues --- internal/daemon/controller/controller.go | 2 -- internal/daemon/pipeline/pipeline.go | 2 +- internal/daemon/session/controller_test.go | 2 +- internal/daemon/session/session.go | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 21f5ca0..d0afcb4 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -17,8 +17,6 @@ import ( const LogstashInstanceDirectoryPrefix = "logstash-instance" type Controller struct { - ctx context.Context - id string workDir string diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 31496e6..6620308 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -36,7 +36,7 @@ func New(file, basePath string) (Archive, error) { } p := Pipelines{} - err = yaml.Unmarshal([]byte(b), &p) + err = yaml.Unmarshal(b, &p) if err != nil { return Archive{}, err } diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index 57b1303..0b9e25d 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -159,7 +159,7 @@ func TestCreate(t *testing.T) { s2, err := c.Create(pipelines, configFiles) is.NoErr(err) - is.True(s.ID() != s2.ID()) // IDs of two seperate sessions are not equal + is.True(s.ID() != s2.ID()) // IDs of two separate sessions are not equal }) } } diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 5c2eb69..4864d62 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -144,7 +144,7 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, } // ExecuteTest runs a test case set against the Logstash configuration, that has -// been loaded previously with SetupTest +// been loaded previously with SetupTest. func (s *Session) ExecuteTest(inputLines []string, inFields map[string]string) error { s.testexec++ pipelineName := fmt.Sprintf("lfv_input_%d", s.testexec) @@ -184,7 +184,7 @@ func (s *Session) ExecuteTest(inputLines []string, inFields map[string]string) e } func prepareFields(fieldsFilename string, inputLines []string, inFields map[string]string) ([]string, error) { - // FIXME: This does not allow arbritary nested fields yet. + // FIXME: This does not allow arbritrary nested fields yet. fields := make(map[string]map[string]string) ids := make([]string, 0, len(inputLines)) From c0261bc2bc049bd1380ee4c2f0124001adf7122d Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 20:42:24 +0100 Subject: [PATCH 031/143] Add support for nested fields --- internal/app/daemon/daemon.go | 9 +- internal/app/daemon/run/run.go | 10 +- internal/daemon/api/grpc/api.pb.go | 126 ++++++++++----------- internal/daemon/api/grpc/api.proto | 2 +- internal/daemon/session/controller_test.go | 2 +- internal/daemon/session/session.go | 8 +- 6 files changed, 77 insertions(+), 80 deletions(-) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index c7dbf59..87e2eaf 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -4,6 +4,7 @@ import ( "archive/zip" "bytes" "context" + "encoding/json" "io/ioutil" "net" "os" @@ -331,7 +332,13 @@ func (d *Daemon) ExecuteTest(ctx context.Context, in *pb.ExecuteTestRequest) (*p return nil, errors.Wrap(err, "invalid session ID") } - err = session.ExecuteTest(in.InputLines, in.Fields) + fields := map[string]interface{}{} + err = json.Unmarshal(in.Fields, &fields) + if err != nil { + return nil, errors.Wrap(err, "invalid json for fields") + } + + err = session.ExecuteTest(in.InputLines, fields) if err != nil { return nil, err } diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 0ce9f74..14a6022 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,7 +3,6 @@ package run import ( "context" "encoding/json" - "fmt" "net" "os" "path" @@ -103,15 +102,14 @@ func (s Test) Run() error { } for _, t := range tests { - fields := make(map[string]string, len(t.InputFields)) - for k, v := range t.InputFields { - // FIXME: dirty hack to convert interface{} to string, does not work with nested Logstash fields e.g. [fields][nested_field] - fields[k] = fmt.Sprint(v) + b, err := json.Marshal(t.InputFields) + if err != nil { + return err } result, err := c.ExecuteTest(context.Background(), &pb.ExecuteTestRequest{ SessionID: sessionID, InputLines: t.InputLines, - Fields: fields, + Fields: b, }) if err != nil { return err diff --git a/internal/daemon/api/grpc/api.pb.go b/internal/daemon/api/grpc/api.pb.go index ee1c753..15b032a 100644 --- a/internal/daemon/api/grpc/api.pb.go +++ b/internal/daemon/api/grpc/api.pb.go @@ -200,9 +200,9 @@ type ExecuteTestRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` - InputLines []string `protobuf:"bytes,2,rep,name=inputLines,proto3" json:"inputLines,omitempty"` - Fields map[string]string `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` + InputLines []string `protobuf:"bytes,2,rep,name=inputLines,proto3" json:"inputLines,omitempty"` + Fields []byte `protobuf:"bytes,3,opt,name=fields,proto3" json:"fields,omitempty"` } func (x *ExecuteTestRequest) Reset() { @@ -251,7 +251,7 @@ func (x *ExecuteTestRequest) GetInputLines() []string { return nil } -func (x *ExecuteTestRequest) GetFields() map[string]string { +func (x *ExecuteTestRequest) GetFields() []byte { if x != nil { return x.Fields } @@ -419,54 +419,48 @@ var file_api_proto_rawDesc = []byte{ 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x31, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0xcb, 0x01, 0x0a, 0x12, - 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, - 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, - 0x12, 0x3c, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, - 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, - 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, - 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2f, 0x0a, 0x13, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x54, 0x65, - 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x6a, 0x0a, 0x12, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, - 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x73, 0x32, 0x95, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, - 0x3b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, - 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09, - 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, - 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, - 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, - 0x73, 0x74, 0x12, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, - 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, 0x52, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, 0x75, 0x73, - 0x62, 0x61, 0x65, 0x63, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, 0x2d, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x76, - 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, - 0x6e, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, - 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x2f, 0x0a, 0x13, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, + 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x54, 0x65, 0x61, 0x72, + 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x32, 0x95, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x3b, 0x0a, + 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, + 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, + 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x47, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, + 0x12, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, 0x52, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, 0x75, 0x73, 0x62, 0x61, + 0x65, 0x63, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, 0x2d, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x2d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, + 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -481,7 +475,7 @@ func file_api_proto_rawDescGZIP() []byte { return file_api_proto_rawDescData } -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_api_proto_goTypes = []interface{}{ (*ShutdownRequest)(nil), // 0: grpc.ShutdownRequest (*ShutdownResponse)(nil), // 1: grpc.ShutdownResponse @@ -491,23 +485,21 @@ var file_api_proto_goTypes = []interface{}{ (*ExecuteTestResponse)(nil), // 5: grpc.ExecuteTestResponse (*TeardownTestRequest)(nil), // 6: grpc.TeardownTestRequest (*TeardownTestResponse)(nil), // 7: grpc.TeardownTestResponse - nil, // 8: grpc.ExecuteTestRequest.FieldsEntry } var file_api_proto_depIdxs = []int32{ - 8, // 0: grpc.ExecuteTestRequest.fields:type_name -> grpc.ExecuteTestRequest.FieldsEntry - 0, // 1: grpc.Control.Shutdown:input_type -> grpc.ShutdownRequest - 2, // 2: grpc.Control.SetupTest:input_type -> grpc.SetupTestRequest - 4, // 3: grpc.Control.ExecuteTest:input_type -> grpc.ExecuteTestRequest - 6, // 4: grpc.Control.TeardownTest:input_type -> grpc.TeardownTestRequest - 1, // 5: grpc.Control.Shutdown:output_type -> grpc.ShutdownResponse - 3, // 6: grpc.Control.SetupTest:output_type -> grpc.SetupTestResponse - 5, // 7: grpc.Control.ExecuteTest:output_type -> grpc.ExecuteTestResponse - 7, // 8: grpc.Control.TeardownTest:output_type -> grpc.TeardownTestResponse - 5, // [5:9] is the sub-list for method output_type - 1, // [1:5] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 0, // 0: grpc.Control.Shutdown:input_type -> grpc.ShutdownRequest + 2, // 1: grpc.Control.SetupTest:input_type -> grpc.SetupTestRequest + 4, // 2: grpc.Control.ExecuteTest:input_type -> grpc.ExecuteTestRequest + 6, // 3: grpc.Control.TeardownTest:input_type -> grpc.TeardownTestRequest + 1, // 4: grpc.Control.Shutdown:output_type -> grpc.ShutdownResponse + 3, // 5: grpc.Control.SetupTest:output_type -> grpc.SetupTestResponse + 5, // 6: grpc.Control.ExecuteTest:output_type -> grpc.ExecuteTestResponse + 7, // 7: grpc.Control.TeardownTest:output_type -> grpc.TeardownTestResponse + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_api_proto_init() } @@ -619,7 +611,7 @@ func file_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_proto_rawDesc, NumEnums: 0, - NumMessages: 9, + NumMessages: 8, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/daemon/api/grpc/api.proto b/internal/daemon/api/grpc/api.proto index 91c81ca..8b6ee2d 100644 --- a/internal/daemon/api/grpc/api.proto +++ b/internal/daemon/api/grpc/api.proto @@ -28,7 +28,7 @@ message SetupTestResponse { message ExecuteTestRequest { string sessionID = 1; repeated string inputLines = 2; - map fields = 3; + bytes fields = 3; } message ExecuteTestResponse { diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index 0b9e25d..8763906 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -78,7 +78,7 @@ func TestSession(t *testing.T) { is.NoErr(err) inputLines := []string{"some_random_input"} - inFields := map[string]string{ + inFields := map[string]interface{}{ "some_random_key": "value", } err = s.ExecuteTest(inputLines, inFields) diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 4864d62..7808284 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -145,7 +145,7 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, // ExecuteTest runs a test case set against the Logstash configuration, that has // been loaded previously with SetupTest. -func (s *Session) ExecuteTest(inputLines []string, inFields map[string]string) error { +func (s *Session) ExecuteTest(inputLines []string, inFields map[string]interface{}) error { s.testexec++ pipelineName := fmt.Sprintf("lfv_input_%d", s.testexec) inputDir := path.Join(s.sessionDir, "lfv_inputs", strconv.Itoa(s.testexec)) @@ -183,15 +183,15 @@ func (s *Session) ExecuteTest(inputLines []string, inFields map[string]string) e return nil } -func prepareFields(fieldsFilename string, inputLines []string, inFields map[string]string) ([]string, error) { +func prepareFields(fieldsFilename string, inputLines []string, inFields map[string]interface{}) ([]string, error) { // FIXME: This does not allow arbritrary nested fields yet. - fields := make(map[string]map[string]string) + fields := make(map[string]map[string]interface{}) ids := make([]string, 0, len(inputLines)) for i, line := range inputLines { id := fmt.Sprintf("%d", i) ids = append(ids, fmt.Sprintf("%q", id)) - fields[id] = make(map[string]string) + fields[id] = make(map[string]interface{}) fields[id]["message"] = line for field, value := range inFields { From d16ebf1ed5ea986a140bf6fbc997e2f34e254e94 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 20:44:41 +0100 Subject: [PATCH 032/143] Add timeout to integration tests --- integration_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration_test.go b/integration_test.go index ec9d316..9e036e0 100644 --- a/integration_test.go +++ b/integration_test.go @@ -23,7 +23,6 @@ func TestIntegration(t *testing.T) { is := is.New(t) - // FIXME: use test logger testLogger := &logging.LoggerMock{ DebugFunc: func(args ...interface{}) { t.Log(args...) }, DebugfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, @@ -45,11 +44,10 @@ func TestIntegration(t *testing.T) { t.Fatalf("Logstash needs to be present in %q for the integration tests to work", logstashPath) } - // FIXME: use test logger log := testLogger server := daemon.New(socket, logstashPath, log) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() go func() { From 4b8341708c9a7ebb361d18782658915d078ede56 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 20:56:33 +0100 Subject: [PATCH 033/143] Make shutdown timeouts configurable --- integration_test.go | 2 +- internal/app/daemon/daemon.go | 13 ++++++++----- internal/app/daemon/run/run.go | 1 - internal/app/daemon_start.go | 11 ++++++++++- logstash-filter-verifier.yml.example | 2 ++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/integration_test.go b/integration_test.go index 9e036e0..5cf0c9a 100644 --- a/integration_test.go +++ b/integration_test.go @@ -45,7 +45,7 @@ func TestIntegration(t *testing.T) { } log := testLogger - server := daemon.New(socket, logstashPath, log) + server := daemon.New(socket, logstashPath, log, 10*time.Second, 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 87e2eaf..a01dd24 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -44,6 +44,9 @@ type Daemon struct { tempdir string + inflightShutdownTimeout time.Duration + shutdownTimeout time.Duration + sessionController *session.Controller server *grpc.Server @@ -59,11 +62,13 @@ type Daemon struct { } // New creates a new logstash filter verifier daemon. -func New(socket string, logstashPath string, log logging.Logger) Daemon { +func New(socket string, logstashPath string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration) Daemon { ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ socket: socket, logstashPath: logstashPath, + inflightShutdownTimeout: inflightShutdownTimeout, + shutdownTimeout: shutdownTimeout, log: log, shutdownLogstashInstancesWG: &sync.WaitGroup{}, ctxShutdownSignal: ctxShutdownSignal, @@ -159,8 +164,7 @@ func (d *Daemon) shutdownSignalHandler(shutdown func()) { d.log.Info("Term signal received. Shutdown initiated.") } - // TODO: Make shutdown timeout configurable - t := time.NewTimer(10 * time.Second) + t := time.NewTimer(d.inflightShutdownTimeout) // Wait for currently running sessions to finish. select { @@ -195,8 +199,7 @@ func (d *Daemon) shutdownSignalHandler(shutdown func()) { close(logstashInstancesStopped) }() - // TODO: Make shutdown timeout configurable - t.Reset(10 * time.Second) + t.Reset(d.shutdownTimeout) // Wait for Logstash and GRPC Server to shutdown serverStopComplete := false diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 14a6022..1fbec4c 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -46,7 +46,6 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath } func (s Test) Run() error { - // FIXME: Read pipeline, find config (fix paths based on pipelineBase if necessary) a, err := pipeline.New(s.pipeline, s.pipelineBase) if err != nil { return err diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index fa623c9..53dee30 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -2,6 +2,7 @@ package app import ( "context" + "time" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -20,18 +21,26 @@ func makeDaemonStartCmd() *cobra.Command { cmd.Flags().StringP("logstash-path", "", "/usr/share/logstash/bin/logstash", "location where the logstash executable is found") _ = viper.BindPFlag("logstash.path", cmd.Flags().Lookup("logstash-path")) + cmd.Flags().DurationP("inflight-shutdown-timeout", "", 10*time.Second, "maximum duration to wait for in-flight test executions to finish during shutdown") + _ = viper.BindPFlag("inflight-shutdown-timeout", cmd.Flags().Lookup("inflight-shutdown-timeout")) + + cmd.Flags().DurationP("shutdown-timeout", "", 3*time.Second, "maximum duration to wait for Logstash and gRPC server to gracefully shutdown") + _ = viper.BindPFlag("shutdown-timeout", cmd.Flags().Lookup("shutdown-timeout")) + return cmd } func runDaemonStart(_ *cobra.Command, _ []string) error { socket := viper.GetString("socket") logstashPath := viper.GetString("logstash.path") + inflightShutdownTimeout := viper.GetDuration("inflight-shutdown-timeout") + shutdownTimeout := viper.GetDuration("shutdown-timeout") log := viper.Get("logger").(logging.Logger) log.Debugf("config: socket: %s", socket) log.Debugf("config: logstash-path: %s", logstashPath) - s := daemon.New(socket, logstashPath, log) + s := daemon.New(socket, logstashPath, log, inflightShutdownTimeout, shutdownTimeout) defer s.Cleanup() return s.Run(context.Background()) diff --git a/logstash-filter-verifier.yml.example b/logstash-filter-verifier.yml.example index 615c73a..b112aa1 100644 --- a/logstash-filter-verifier.yml.example +++ b/logstash-filter-verifier.yml.example @@ -20,3 +20,5 @@ pipeline: ./testdata/basic_pipeline.yml pipeline-base: ./testdata/basic_pipeline testcase-dir: ./testdata/testcases/basic_pipeline socket: /tmp/logstash-filter-verifier.sock +inflight-shutdown-timeout: 10s +shutdown-timeout: 3s From ec6e34faab484cb13c1a3b65bd19e0b8e7b1b0e0 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 21:14:24 +0100 Subject: [PATCH 034/143] Fix import paths to v2 --- internal/app/daemon/run/run.go | 6 +++--- internal/app/daemon_run.go | 3 --- internal/testcase/testcase.go | 3 ++- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 1fbec4c..d3415ca 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -11,12 +11,12 @@ import ( "github.com/imkira/go-observer" "google.golang.org/grpc" - "github.com/magnusbaeck/logstash-filter-verifier/logstash" - lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/observer" - "github.com/magnusbaeck/logstash-filter-verifier/testcase" pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" + lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testcase" ) type Test struct { diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 10c6a3a..b15df8f 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -26,9 +26,6 @@ func makeDaemonRunCmd() *cobra.Command { } func runDaemonRun(_ *cobra.Command, args []string) error { - // TODO: Remove this - // logging.SetLevel(oplogging.INFO) - t, err := run.New(viper.GetString("socket"), viper.Get("logger").(logging.Logger), viper.GetString("pipeline"), viper.GetString("pipeline-base"), viper.GetString("testcase-dir")) if err != nil { return err diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 83547c9..751d97b 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -18,10 +18,11 @@ import ( unjson "github.com/hashicorp/packer/common/json" "github.com/imkira/go-observer" + "github.com/mikefarah/yaml/v2" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" - "github.com/mikefarah/yaml/v2" ) // TestCaseSet contains the configuration of a Logstash filter test case. From 470978c92e6afd6de90d84133f3f52807c957617 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Feb 2021 21:23:47 +0100 Subject: [PATCH 035/143] Add some log output for ready and shutdown --- internal/app/daemon/daemon.go | 1 + internal/daemon/controller/controller.go | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index a01dd24..39ec63a 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -157,6 +157,7 @@ func (d *Daemon) shutdownSignalHandler(shutdown func()) { select { case <-d.ctxShutdownSignal.Done(): + d.log.Info("Shutdown initiated.") case <-sigInt: d.log.Info("Interrupt signal (Ctrl+c) received. Shutdown initiated.") d.log.Info("Press Ctrl+c again to exit immediately") diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index d0afcb4..b6abda5 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path" + "sync" "gopkg.in/yaml.v2" @@ -22,7 +23,8 @@ type Controller struct { workDir string log logging.Logger - instance Instance + instance Instance + instanceReady *sync.Once stateMachine *stateMachine receivedEvents *events @@ -60,10 +62,11 @@ func NewController(instance Instance, baseDir string, log logging.Logger) (*Cont } controller := Controller{ - id: id, - workDir: workDir, - log: log, - instance: instance, + id: id, + workDir: workDir, + log: log, + instance: instance, + instanceReady: &sync.Once{}, receivedEvents: newEvents(), pipelines: newPipelines(), @@ -190,6 +193,10 @@ func (c *Controller) ReceiveEvent(event string) error { func (c *Controller) PipelinesReady(pipelines ...string) { c.pipelines.setReady(pipelines...) if c.pipelines.isReady() { + c.instanceReady.Do(func() { + c.log.Info("Ready to process tests") + }) + c.stateMachine.executeCommand(commandPipelineReady) } } From 7dc13fa735896bd2bf3727520beb9cd6db2083ce Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 23 Feb 2021 10:24:32 +0100 Subject: [PATCH 036/143] Go mod tidy --- go.mod | 4 ++-- go.sum | 29 ----------------------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 22c71e8..b2078bf 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,11 @@ require ( github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/breml/logstash-config v0.4.0 - github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 github.com/hpcloud/tail v1.0.0 github.com/imkira/go-observer v1.0.3 - github.com/magnusbaeck/logstash-filter-verifier v0.0.0-20201128205846-61a579889997 github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b github.com/matoous/go-nanoid v1.5.0 github.com/matryer/is v1.4.0 @@ -28,9 +26,11 @@ require ( github.com/stretchr/testify v1.5.1 github.com/tidwall/gjson v1.6.8 github.com/tidwall/sjson v1.1.5 + github.com/yookoala/realpath v1.0.0 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect google.golang.org/grpc v1.34.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 google.golang.org/protobuf v1.25.0 + gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 4d2a350..9064e9a 100644 --- a/go.sum +++ b/go.sum @@ -30,11 +30,8 @@ github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:O github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/ahmetb/govvv v0.3.0 h1:YGLGwEyiUwHFy5eh/RUhdupbuaCGBYn5T5GWXp+WJB0= github.com/ahmetb/govvv v0.3.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= -github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= @@ -61,8 +58,6 @@ github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/breml/logstash-config v0.1.0 h1:fym6y8WJpJxEicmwUBhdzH2VSHzaFoUf2jA2tOTV7Tw= -github.com/breml/logstash-config v0.1.0/go.mod h1:ZBjsBLmordIP3WF/sMfUlRkwBhrHgw6njXx2m7P4Cmo= github.com/breml/logstash-config v0.4.0 h1:KHzk5ePORMYD/tMQzhr3oPe7LbJjF9TRaquMV7keGvQ= github.com/breml/logstash-config v0.4.0/go.mod h1:Opa416n2AzUie2KU8MsVze1CoLpbqdqt6sVqZjgoa9k= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= @@ -70,10 +65,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -93,7 +86,6 @@ github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437/go.mod h1: github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8/go.mod h1:/YnlngP1PARC0SKAZx6kaAEMOp8bNTQGqS+Ka3MctNI= github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dnaeon/go-vcr v1.0.0/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/docker v0.0.0-20180422163414-57142e89befe h1:VW8TnWi0CZgg7oCv0wH6evNwkzcJg/emnw4HrVIWws4= github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= @@ -123,7 +115,6 @@ github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuu github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -133,14 +124,12 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= @@ -154,7 +143,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -251,12 +239,9 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LE github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magnusbaeck/logstash-filter-verifier v0.0.0-20201128205846-61a579889997 h1:lAZRZB9Elk7diV4eYzIp4aEVEedCQuHibiDVlDlNcL8= -github.com/magnusbaeck/logstash-filter-verifier v0.0.0-20201128205846-61a579889997/go.mod h1:0FkNWNMV/zvGcRgvXIArlw5LIriH4WHClLkFX+Sd40c= github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= -github.com/matm/gocov-html v0.0.0-20191111163307-9ee104d84c82/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b h1:5Wc/N1FIBnExmX0/SEdKe0A0COvdJc3rCGHQ7s1oBPQ= github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b/go.mod h1:zha4ZSIA/qviBBKx3j6tJG/Lx6aIdjOXPWuKAcJchQM= github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek= @@ -316,7 +301,6 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -350,7 +334,6 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -374,11 +357,9 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -455,7 +436,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -483,7 +463,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= @@ -493,7 +472,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -509,12 +487,10 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2pTvDyhZNduBUMD2q3iqs4= @@ -540,17 +516,14 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= @@ -562,7 +535,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= @@ -587,7 +559,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= From e8b89fb667127730c60d33a2dd914841c219636b Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 23 Feb 2021 21:59:03 +0100 Subject: [PATCH 037/143] Add logstash pool --- internal/app/daemon/daemon.go | 56 +++++---- internal/daemon/pool/pool.go | 90 +++++++++++++ internal/daemon/session/controller.go | 32 +++-- internal/daemon/session/controller_test.go | 57 +++++---- .../daemon/session/logstash_controller.go | 11 +- internal/daemon/session/pool.go | 10 ++ internal/daemon/session/pool_mock_test.go | 118 ++++++++++++++++++ internal/daemon/session/session.go | 5 +- 8 files changed, 312 insertions(+), 67 deletions(-) create mode 100644 internal/daemon/pool/pool.go create mode 100644 internal/daemon/session/pool.go create mode 100644 internal/daemon/session/pool_mock_test.go diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 39ec63a..23d7a3b 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -22,6 +22,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/instance/logstash" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) @@ -49,12 +50,7 @@ type Daemon struct { sessionController *session.Controller - server *grpc.Server - logstashController *controller.Controller - - // Global shutdown wait group. Daemon.Run() will wait for this wait group - // before returning and exiting the main Go routine. - shutdownLogstashInstancesWG *sync.WaitGroup + server *grpc.Server log logging.Logger @@ -65,14 +61,13 @@ type Daemon struct { func New(socket string, logstashPath string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration) Daemon { ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ - socket: socket, - logstashPath: logstashPath, - inflightShutdownTimeout: inflightShutdownTimeout, - shutdownTimeout: shutdownTimeout, - log: log, - shutdownLogstashInstancesWG: &sync.WaitGroup{}, - ctxShutdownSignal: ctxShutdownSignal, - shutdownSignalFunc: shutdownSignalFunc, + socket: socket, + logstashPath: logstashPath, + inflightShutdownTimeout: inflightShutdownTimeout, + shutdownTimeout: shutdownTimeout, + log: log, + ctxShutdownSignal: ctxShutdownSignal, + shutdownSignalFunc: shutdownSignalFunc, } } @@ -92,22 +87,31 @@ func (d *Daemon) Run(ctx context.Context) error { d.tempdir = tempdir d.log.Debugf("Temporary directory for daemon created in %q", d.tempdir) - // Create and start Logstash Controller - d.shutdownLogstashInstancesWG.Add(1) - instance := logstash.New(ctxKill, d.logstashPath, d.log, d.shutdownLogstashInstancesWG) - logstashController, err := controller.NewController(instance, tempdir, d.log) - if err != nil { - return err + // Factory to create and start Logstash Controller + shutdownLogstashInstancesWG := &sync.WaitGroup{} + logstashControllerFactory := func() (session.LogstashController, error) { + shutdownLogstashInstancesWG.Add(1) + instance := logstash.New(ctxKill, d.logstashPath, d.log, shutdownLogstashInstancesWG) + logstashController, err := controller.NewController(instance, tempdir, d.log) + if err != nil { + return nil, err + } + + err = logstashController.Launch(ctx) + if err != nil { + return nil, err + } + + return logstashController, nil } - d.logstashController = logstashController - err = d.logstashController.Launch(ctx) + pool, err := pool.New(logstashControllerFactory, 1) if err != nil { return err } // Create Session Handler - d.sessionController = session.NewController(d.tempdir, d.logstashController, d.log) + d.sessionController = session.NewController(d.tempdir, pool, d.log) // Create and start GRPC Server lis, err := net.Listen("unix", d.socket) @@ -126,14 +130,14 @@ func (d *Daemon) Run(ctx context.Context) error { }() // Setup signal handler and shutdown coordinator - d.shutdownSignalHandler(shutdown) + d.shutdownSignalHandler(shutdown, shutdownLogstashInstancesWG) return nil } const hardExitDelay = 20 * time.Millisecond -func (d *Daemon) shutdownSignalHandler(shutdown func()) { +func (d *Daemon) shutdownSignalHandler(shutdown func(), shutdownLogstashInstancesWG *sync.WaitGroup) { var hardExit bool defer func() { @@ -196,7 +200,7 @@ func (d *Daemon) shutdownSignalHandler(shutdown func()) { // Stop Logstash instance logstashInstancesStopped := make(chan struct{}) go func() { - d.shutdownLogstashInstancesWG.Wait() + shutdownLogstashInstancesWG.Wait() close(logstashInstancesStopped) }() diff --git a/internal/daemon/pool/pool.go b/internal/daemon/pool/pool.go new file mode 100644 index 0000000..88413da --- /dev/null +++ b/internal/daemon/pool/pool.go @@ -0,0 +1,90 @@ +package pool + +import ( + "sync" + + "github.com/pkg/errors" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" +) + +type LogstashController interface { + SetupTest(pipelines pipeline.Pipelines) error + ExecuteTest(pipelines pipeline.Pipelines, expectedEvents int) error + GetResults() ([]string, error) + Teardown() error +} + +type LogstashControllerFactory func() (LogstashController, error) + +type Pool struct { + logstashControllerFactory LogstashControllerFactory + maxControllers int + + mutex *sync.Mutex + availableControllers []LogstashController + assignedControllers []LogstashController +} + +func New(logstashControllerFactory LogstashControllerFactory, maxControllers int) (*Pool, error) { + if maxControllers < 1 { + maxControllers = 1 + } + + instance, err := logstashControllerFactory() + if err != nil { + return nil, err + } + + return &Pool{ + logstashControllerFactory: logstashControllerFactory, + maxControllers: maxControllers, + + mutex: &sync.Mutex{}, + availableControllers: []LogstashController{instance}, + assignedControllers: []LogstashController{}, + }, nil +} + +func (p *Pool) Get() (LogstashController, error) { + p.mutex.Lock() + defer p.mutex.Unlock() + + if len(p.availableControllers) > 0 { + instance := p.availableControllers[0] + p.availableControllers = p.availableControllers[1:] + p.assignedControllers = append(p.assignedControllers, instance) + return instance, nil + } + + if len(p.assignedControllers) < p.maxControllers { + instance, err := p.logstashControllerFactory() + if err != nil { + return nil, err + } + p.assignedControllers = append(p.assignedControllers, instance) + return instance, nil + } + + return nil, errors.Errorf("No instance available from pool") +} + +func (p *Pool) Return(instance LogstashController, clean bool) { + p.mutex.Lock() + defer p.mutex.Unlock() + + for i := range p.assignedControllers { + if p.assignedControllers[i] == instance { + // Delete without preserving order + p.assignedControllers[i] = p.assignedControllers[len(p.assignedControllers)-1] + p.assignedControllers = p.assignedControllers[:len(p.assignedControllers)-1] + + if clean { + p.availableControllers = append(p.availableControllers, instance) + } + + return + } + } + panic("instance not found in assigned controllers") +} diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go index 7f2e989..18d1483 100644 --- a/internal/daemon/session/controller.go +++ b/internal/daemon/session/controller.go @@ -20,13 +20,13 @@ type Controller struct { sessions map[string]*Session finished bool - tempdir string - logstashController LogstashController - log logging.Logger + tempdir string + logstashPool Pool + log logging.Logger } // NewController creates a new session Controller. -func NewController(tempdir string, logstashController LogstashController, log logging.Logger) *Controller { +func NewController(tempdir string, logstashPool Pool, log logging.Logger) *Controller { mu := &sync.Mutex{} return &Controller{ @@ -36,9 +36,9 @@ func NewController(tempdir string, logstashController LogstashController, log lo sessions: make(map[string]*Session, 10), - tempdir: tempdir, - logstashController: logstashController, - log: log, + tempdir: tempdir, + logstashPool: logstashPool, + log: log, } } @@ -54,12 +54,17 @@ func (s *Controller) Create(pipelines pipeline.Pipelines, configFiles []logstash } } - session := new(s.tempdir, s.logstashController, s.log) + logstashController, err := s.logstashPool.Get() + if err != nil { + return nil, err + } + + session := new(s.tempdir, logstashController, s.log) s.sessions[session.ID()] = session s.wg.Add(1) - err := session.setupTest(pipelines, configFiles) + err = session.setupTest(pipelines, configFiles) if err != nil { return nil, err } @@ -92,7 +97,14 @@ func (s *Controller) DestroyByID(id string) error { s.wg.Done() }() - return session.teardown() + err := session.teardown() + if err != nil { + s.logstashPool.Return(session.logstashController, false) + return err + } + s.logstashPool.Return(session.logstashController, true) + + return nil } // WaitFinish waits for all currently running sessions to finish. diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index 8763906..efb3174 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -10,6 +10,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) @@ -29,24 +30,30 @@ func TestSession(t *testing.T) { tempdir := t.TempDir() - logstashController := &LogstashControllerMock{ - SetupTestFunc: func(pipelines pipeline.Pipelines) error { - is.True(len(pipelines) == 2) // Expect 2 pipelines (main, output) - return nil - }, - TeardownFunc: func() error { - return nil - }, - ExecuteTestFunc: func(pipelines pipeline.Pipelines, expectedEvents int) error { - is.True(len(pipelines) == 3) // Expect 2 pipelines (input, main, output) - return nil - }, - GetResultsFunc: func() ([]string, error) { - return []string{"some_random_result"}, nil + pool := &PoolMock{ + GetFunc: func() (pool.LogstashController, error) { + logstashController := &LogstashControllerMock{ + SetupTestFunc: func(pipelines pipeline.Pipelines) error { + is.True(len(pipelines) == 2) // Expect 2 pipelines (main, output) + return nil + }, + TeardownFunc: func() error { + return nil + }, + ExecuteTestFunc: func(pipelines pipeline.Pipelines, expectedEvents int) error { + is.True(len(pipelines) == 3) // Expect 2 pipelines (input, main, output) + return nil + }, + GetResultsFunc: func() ([]string, error) { + return []string{"some_random_result"}, nil + }, + } + return logstashController, nil }, + ReturnFunc: func(instance pool.LogstashController, clean bool) {}, } - c := session.NewController(tempdir, logstashController, logging.NoopLogger) + c := session.NewController(tempdir, pool, logging.NoopLogger) pipelines := pipeline.Pipelines{ pipeline.Pipeline{ @@ -120,16 +127,22 @@ func TestCreate(t *testing.T) { tempdir := t.TempDir() - logstashController := &LogstashControllerMock{ - SetupTestFunc: func(pipelines pipeline.Pipelines) error { - return nil - }, - TeardownFunc: func() error { - return nil + pool := &PoolMock{ + GetFunc: func() (pool.LogstashController, error) { + logstashController := &LogstashControllerMock{ + SetupTestFunc: func(pipelines pipeline.Pipelines) error { + return nil + }, + TeardownFunc: func() error { + return nil + }, + } + return logstashController, nil }, + ReturnFunc: func(instance pool.LogstashController, clean bool) {}, } - c := session.NewController(tempdir, logstashController, logging.NoopLogger) + c := session.NewController(tempdir, pool, logging.NoopLogger) pipelines := pipeline.Pipelines{ pipeline.Pipeline{ diff --git a/internal/daemon/session/logstash_controller.go b/internal/daemon/session/logstash_controller.go index 032db9b..481fbdc 100644 --- a/internal/daemon/session/logstash_controller.go +++ b/internal/daemon/session/logstash_controller.go @@ -1,12 +1,9 @@ package session -import "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" +import ( + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" +) //go:generate moq -fmt goimports -pkg session_test -out ./logstash_controller_mock_test.go . LogstashController -type LogstashController interface { - SetupTest(pipelines pipeline.Pipelines) error - ExecuteTest(pipelines pipeline.Pipelines, expectedEvents int) error - GetResults() ([]string, error) - Teardown() error -} +type LogstashController = pool.LogstashController diff --git a/internal/daemon/session/pool.go b/internal/daemon/session/pool.go new file mode 100644 index 0000000..cfb8f12 --- /dev/null +++ b/internal/daemon/session/pool.go @@ -0,0 +1,10 @@ +package session + +import "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" + +//go:generate moq -fmt goimports -pkg session_test -out ./pool_mock_test.go . Pool + +type Pool interface { + Get() (pool.LogstashController, error) + Return(instance pool.LogstashController, clean bool) +} diff --git a/internal/daemon/session/pool_mock_test.go b/internal/daemon/session/pool_mock_test.go new file mode 100644 index 0000000..07881ea --- /dev/null +++ b/internal/daemon/session/pool_mock_test.go @@ -0,0 +1,118 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package session_test + +import ( + "sync" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" +) + +// Ensure, that PoolMock does implement session.Pool. +// If this is not the case, regenerate this file with moq. +var _ session.Pool = &PoolMock{} + +// PoolMock is a mock implementation of session.Pool. +// +// func TestSomethingThatUsesPool(t *testing.T) { +// +// // make and configure a mocked session.Pool +// mockedPool := &PoolMock{ +// GetFunc: func() (pool.LogstashController, error) { +// panic("mock out the Get method") +// }, +// ReturnFunc: func(instance pool.LogstashController, clean bool) { +// panic("mock out the Return method") +// }, +// } +// +// // use mockedPool in code that requires session.Pool +// // and then make assertions. +// +// } +type PoolMock struct { + // GetFunc mocks the Get method. + GetFunc func() (pool.LogstashController, error) + + // ReturnFunc mocks the Return method. + ReturnFunc func(instance pool.LogstashController, clean bool) + + // calls tracks calls to the methods. + calls struct { + // Get holds details about calls to the Get method. + Get []struct { + } + // Return holds details about calls to the Return method. + Return []struct { + // Instance is the instance argument value. + Instance pool.LogstashController + // Clean is the clean argument value. + Clean bool + } + } + lockGet sync.RWMutex + lockReturn sync.RWMutex +} + +// Get calls GetFunc. +func (mock *PoolMock) Get() (pool.LogstashController, error) { + if mock.GetFunc == nil { + panic("PoolMock.GetFunc: method is nil but Pool.Get was just called") + } + callInfo := struct { + }{} + mock.lockGet.Lock() + mock.calls.Get = append(mock.calls.Get, callInfo) + mock.lockGet.Unlock() + return mock.GetFunc() +} + +// GetCalls gets all the calls that were made to Get. +// Check the length with: +// len(mockedPool.GetCalls()) +func (mock *PoolMock) GetCalls() []struct { +} { + var calls []struct { + } + mock.lockGet.RLock() + calls = mock.calls.Get + mock.lockGet.RUnlock() + return calls +} + +// Return calls ReturnFunc. +func (mock *PoolMock) Return(instance pool.LogstashController, clean bool) { + if mock.ReturnFunc == nil { + panic("PoolMock.ReturnFunc: method is nil but Pool.Return was just called") + } + callInfo := struct { + Instance pool.LogstashController + Clean bool + }{ + Instance: instance, + Clean: clean, + } + mock.lockReturn.Lock() + mock.calls.Return = append(mock.calls.Return, callInfo) + mock.lockReturn.Unlock() + mock.ReturnFunc(instance, clean) +} + +// ReturnCalls gets all the calls that were made to Return. +// Check the length with: +// len(mockedPool.ReturnCalls()) +func (mock *PoolMock) ReturnCalls() []struct { + Instance pool.LogstashController + Clean bool +} { + var calls []struct { + Instance pool.LogstashController + Clean bool + } + mock.lockReturn.RLock() + calls = mock.calls.Return + mock.lockReturn.RUnlock() + return calls +} diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 7808284..2ec8da3 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -17,6 +17,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/template" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) @@ -24,7 +25,7 @@ import ( type Session struct { id string - logstashController LogstashController + logstashController pool.LogstashController baseDir string sessionDir string @@ -35,7 +36,7 @@ type Session struct { log logging.Logger } -func new(baseDir string, logstashController LogstashController, log logging.Logger) *Session { +func new(baseDir string, logstashController pool.LogstashController, log logging.Logger) *Session { sessionID := idgen.New() sessionDir := fmt.Sprintf("%s/session/%s", baseDir, sessionID) return &Session{ From 4756cd4aa5185f0fa4482417642a93ea90c3de09 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 23 Feb 2021 22:33:21 +0100 Subject: [PATCH 038/143] Pool with 2 logstash instances --- internal/app/daemon/daemon.go | 2 +- internal/daemon/instance/logstash/instance.go | 9 +++++++++ internal/daemon/session/controller.go | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 23d7a3b..e66e158 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -105,7 +105,7 @@ func (d *Daemon) Run(ctx context.Context) error { return logstashController, nil } - pool, err := pool.New(logstashControllerFactory, 1) + pool, err := pool.New(logstashControllerFactory, 2) if err != nil { return err } diff --git a/internal/daemon/instance/logstash/instance.go b/internal/daemon/instance/logstash/instance.go index 342e412..d9a500c 100644 --- a/internal/daemon/instance/logstash/instance.go +++ b/internal/daemon/instance/logstash/instance.go @@ -59,6 +59,15 @@ func (i *instance) Start(ctx context.Context, controller *controller.Controller, args := []string{ "--path.settings", workdir, + "--path.logs", + workdir, + "--path.data", + workdir, + // TODO: figure out the correct paths + // "--path.plugins", + // workdir, + // "--path.config", + // workdir, } i.child = exec.CommandContext(i.ctxKill, i.command, args...) // nolint: gosec diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go index 18d1483..ba8fd13 100644 --- a/internal/daemon/session/controller.go +++ b/internal/daemon/session/controller.go @@ -47,7 +47,8 @@ func (s *Controller) Create(pipelines pipeline.Pipelines, configFiles []logstash s.mutex.Lock() defer s.mutex.Unlock() - for len(s.sessions) != 0 { + // TODO: Link this value to pool size + for len(s.sessions) > 1 { s.cond.Wait() if s.finished { return nil, errors.New("shutdown in progress") From 2b78b2bc90108049f71aefc1d2eb9e5716bf1c21 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 24 Feb 2021 22:10:54 +0100 Subject: [PATCH 039/143] Detect and handle Logstash crashes --- internal/app/daemon/daemon.go | 10 ++- internal/daemon/controller/controller.go | 17 +++++ internal/daemon/controller/statemachine.go | 58 +++++++++++++-- .../daemon/instance/logstash/processors.go | 2 + internal/daemon/pool/pool.go | 73 ++++++++++++++++++- .../session/logstash_controller_mock_test.go | 72 ++++++++++++++++++ testdata/basic_pipeline/main/main.conf | 3 + 7 files changed, 223 insertions(+), 12 deletions(-) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index e66e158..b4baa2b 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -105,7 +105,7 @@ func (d *Daemon) Run(ctx context.Context) error { return logstashController, nil } - pool, err := pool.New(logstashControllerFactory, 2) + pool, err := pool.New(ctx, logstashControllerFactory, 2, d.log) if err != nil { return err } @@ -334,12 +334,18 @@ func (d *Daemon) extractZip(in []byte) (pipeline.Pipelines, []logstashconfig.Fil // ExecuteTest runs a test case set against the Logstash configuration, that has // been loaded previously with SetupTest. -func (d *Daemon) ExecuteTest(ctx context.Context, in *pb.ExecuteTestRequest) (*pb.ExecuteTestResponse, error) { +func (d *Daemon) ExecuteTest(ctx context.Context, in *pb.ExecuteTestRequest) (out *pb.ExecuteTestResponse, err error) { session, err := d.sessionController.Get(in.SessionID) if err != nil { return nil, errors.Wrap(err, "invalid session ID") } + defer func() { + if err != nil { + d.sessionController.DestroyByID(in.SessionID) + } + }() + fields := map[string]interface{}{} err = json.Unmarshal(in.Fields, &fields) if err != nil { diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index b6abda5..167cf14 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -25,6 +25,7 @@ type Controller struct { instance Instance instanceReady *sync.Once + shutdown context.CancelFunc stateMachine *stateMachine receivedEvents *events @@ -85,6 +86,9 @@ func (c *Controller) ID() string { } func (c *Controller) Launch(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + c.shutdown = cancel + c.pipelines.reset("stdin", "output") c.stateMachine = newStateMachine(ctx, c.log) c.stateMachine.executeCommand(commandStart) @@ -200,3 +204,16 @@ func (c *Controller) PipelinesReady(pipelines ...string) { c.stateMachine.executeCommand(commandPipelineReady) } } + +func (c *Controller) SignalCrash() { + c.stateMachine.executeCommand(commandCrash) + c.Kill() +} + +func (c *Controller) Kill() { + c.shutdown() +} + +func (c *Controller) IsHealthy() bool { + return c.stateMachine.getState() != stateUnknown +} diff --git a/internal/daemon/controller/statemachine.go b/internal/daemon/controller/statemachine.go index 99b3d67..c839719 100644 --- a/internal/daemon/controller/statemachine.go +++ b/internal/daemon/controller/statemachine.go @@ -3,6 +3,7 @@ package controller import ( "context" "sync" + "time" "github.com/pkg/errors" @@ -23,9 +24,19 @@ func newStateMachine(ctx context.Context, log logging.Logger) *stateMachine { mu := &sync.Mutex{} cond := sync.NewCond(mu) go func() { - <-ctx.Done() - log.Debug("broadcast shutdown for waitForState") - cond.Broadcast() + defer cond.Broadcast() + t := time.NewTicker(time.Second) + defer t.Stop() + for { + select { + case <-ctx.Done(): + log.Debug("broadcast shutdown for waitForState") + return + case <-t.C: + // Wakeup all waitForState to allow them to timeout + cond.Broadcast() + } + } }() return &stateMachine{ ctx: ctx, @@ -37,22 +48,44 @@ func newStateMachine(ctx context.Context, log logging.Logger) *stateMachine { } } +// TODO: Allow this timeout to be set in configuration. +// TODO: Maybe allow different timeouts for different states. +const waitForStateTimeout = 30 * time.Second + func (s *stateMachine) waitForState(target stateName) error { s.log.Debugf("waitForState: %v", target) - // TODO: Add a timeout to exit if the expected state is not reached in due time. - // Create go routine to wake up every 1 second, + s.mutex.Lock() defer s.mutex.Unlock() - for s.currentState != target { - s.cond.Wait() + t := time.NewTimer(waitForStateTimeout) + defer func() { + // Stop timer and drain channel + if !t.Stop() { + select { + case <-t.C: + default: + } + } + }() + for s.currentState != target { select { case <-s.ctx.Done(): // TODO: Can we do this without error return? return errors.Errorf("shutdown while waiting for state: %s", target) + case <-t.C: + s.log.Debugf("state change: %q because of waitForState timeout", stateUnknown) + s.currentState = stateUnknown + s.cond.Broadcast() + return errors.Errorf("timed out while waiting for state: %s", target) default: } + if s.currentState == stateUnknown { + return errors.Errorf("state unknown, failed to wait for state: %s", target) + } + + s.cond.Wait() } return nil } @@ -74,6 +107,13 @@ func (s *stateMachine) executeCommand(command command) { s.cond.Broadcast() } +func (s *stateMachine) getState() stateName { + s.mutex.Lock() + defer s.mutex.Unlock() + + return s.currentState +} + type stateName string const ( @@ -101,6 +141,7 @@ const ( commandExecuteTest command = "execute-test" commandTestComplete command = "test-complete" commandTeardown command = "teardown" + commandCrash command = "crash" ) type stateCommand struct { @@ -134,4 +175,7 @@ var stateTransitionsTable = map[stateCommand]transitionFunc{ // State Running Test {stateRunningTest, commandPipelineReady}: func() stateName { return stateRunningTest }, {stateRunningTest, commandTestComplete}: func() stateName { return stateReadyForTest }, + + // State Unknown is the fallback state for all undefined state transitions. + // {*, *}: func() stateName { return stateUnknown }, } diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go index fc4f4f9..5ae651e 100644 --- a/internal/daemon/instance/logstash/processors.go +++ b/internal/daemon/instance/logstash/processors.go @@ -45,6 +45,7 @@ func (i *instance) stdoutProcessor(stdout io.ReadCloser) { case <-i.ctxShutdown.Done(): default: i.log.Warning("stdout scanner closed unexpectedly") + i.controller.SignalCrash() } i.log.Debug("exit stdout scanner") @@ -75,6 +76,7 @@ func (i *instance) stderrProcessor(stderr io.ReadCloser) { case <-i.ctxShutdown.Done(): default: i.log.Warning("stderr scanner closed unexpectedly") + i.controller.SignalCrash() } i.log.Debug("exit stderr scanner") diff --git a/internal/daemon/pool/pool.go b/internal/daemon/pool/pool.go index 88413da..f305f99 100644 --- a/internal/daemon/pool/pool.go +++ b/internal/daemon/pool/pool.go @@ -1,11 +1,14 @@ package pool import ( + "context" "sync" + "time" "github.com/pkg/errors" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) type LogstashController interface { @@ -13,6 +16,8 @@ type LogstashController interface { ExecuteTest(pipelines pipeline.Pipelines, expectedEvents int) error GetResults() ([]string, error) Teardown() error + IsHealthy() bool + Kill() } type LogstashControllerFactory func() (LogstashController, error) @@ -24,9 +29,11 @@ type Pool struct { mutex *sync.Mutex availableControllers []LogstashController assignedControllers []LogstashController + + log logging.Logger } -func New(logstashControllerFactory LogstashControllerFactory, maxControllers int) (*Pool, error) { +func New(ctx context.Context, logstashControllerFactory LogstashControllerFactory, maxControllers int, log logging.Logger) (*Pool, error) { if maxControllers < 1 { maxControllers = 1 } @@ -36,20 +43,80 @@ func New(logstashControllerFactory LogstashControllerFactory, maxControllers int return nil, err } - return &Pool{ + p := &Pool{ logstashControllerFactory: logstashControllerFactory, maxControllers: maxControllers, mutex: &sync.Mutex{}, availableControllers: []LogstashController{instance}, assignedControllers: []LogstashController{}, - }, nil + + log: log, + } + + go p.housekeeping(ctx) + + return p, nil +} + +// housekeeping removes unhealthy instances, ensure at least one running instance. +func (p *Pool) housekeeping(ctx context.Context) { + t := time.NewTicker(time.Second) + defer t.Stop() + + for { + select { + case <-t.C: + case <-ctx.Done(): + return + } + func() { + p.mutex.Lock() + defer p.mutex.Unlock() + + // Remove unhealthy instances + for i := range p.availableControllers { + if !p.availableControllers[i].IsHealthy() { + // Delete without preserving order + p.availableControllers[i].Kill() + p.availableControllers[i] = p.availableControllers[len(p.availableControllers)-1] + p.availableControllers = p.availableControllers[:len(p.availableControllers)-1] + } + } + for i := range p.assignedControllers { + if !p.assignedControllers[i].IsHealthy() { + // Delete without preserving order + p.assignedControllers[i].Kill() + p.assignedControllers[i] = p.assignedControllers[len(p.assignedControllers)-1] + p.assignedControllers = p.assignedControllers[:len(p.assignedControllers)-1] + } + } + + if len(p.availableControllers)+len(p.assignedControllers) == 0 { + instance, err := p.logstashControllerFactory() + if err != nil { + p.log.Warning("logstash pool housekeeping failed to start new instance: %v", err) + } + p.availableControllers = append(p.availableControllers, instance) + } + }() + } } func (p *Pool) Get() (LogstashController, error) { p.mutex.Lock() defer p.mutex.Unlock() + // Remove unhealthy instances + for i := range p.availableControllers { + if !p.availableControllers[i].IsHealthy() { + // Delete without preserving order + p.availableControllers[i].Kill() + p.availableControllers[i] = p.availableControllers[len(p.availableControllers)-1] + p.availableControllers = p.availableControllers[:len(p.availableControllers)-1] + } + } + if len(p.availableControllers) > 0 { instance := p.availableControllers[0] p.availableControllers = p.availableControllers[1:] diff --git a/internal/daemon/session/logstash_controller_mock_test.go b/internal/daemon/session/logstash_controller_mock_test.go index 295749d..2466fbd 100644 --- a/internal/daemon/session/logstash_controller_mock_test.go +++ b/internal/daemon/session/logstash_controller_mock_test.go @@ -26,6 +26,12 @@ var _ session.LogstashController = &LogstashControllerMock{} // GetResultsFunc: func() ([]string, error) { // panic("mock out the GetResults method") // }, +// IsHealthyFunc: func() bool { +// panic("mock out the IsHealthy method") +// }, +// KillFunc: func() { +// panic("mock out the Kill method") +// }, // SetupTestFunc: func(pipelines pipeline.Pipelines) error { // panic("mock out the SetupTest method") // }, @@ -45,6 +51,12 @@ type LogstashControllerMock struct { // GetResultsFunc mocks the GetResults method. GetResultsFunc func() ([]string, error) + // IsHealthyFunc mocks the IsHealthy method. + IsHealthyFunc func() bool + + // KillFunc mocks the Kill method. + KillFunc func() + // SetupTestFunc mocks the SetupTest method. SetupTestFunc func(pipelines pipeline.Pipelines) error @@ -63,6 +75,12 @@ type LogstashControllerMock struct { // GetResults holds details about calls to the GetResults method. GetResults []struct { } + // IsHealthy holds details about calls to the IsHealthy method. + IsHealthy []struct { + } + // Kill holds details about calls to the Kill method. + Kill []struct { + } // SetupTest holds details about calls to the SetupTest method. SetupTest []struct { // Pipelines is the pipelines argument value. @@ -74,6 +92,8 @@ type LogstashControllerMock struct { } lockExecuteTest sync.RWMutex lockGetResults sync.RWMutex + lockIsHealthy sync.RWMutex + lockKill sync.RWMutex lockSetupTest sync.RWMutex lockTeardown sync.RWMutex } @@ -139,6 +159,58 @@ func (mock *LogstashControllerMock) GetResultsCalls() []struct { return calls } +// IsHealthy calls IsHealthyFunc. +func (mock *LogstashControllerMock) IsHealthy() bool { + if mock.IsHealthyFunc == nil { + panic("LogstashControllerMock.IsHealthyFunc: method is nil but LogstashController.IsHealthy was just called") + } + callInfo := struct { + }{} + mock.lockIsHealthy.Lock() + mock.calls.IsHealthy = append(mock.calls.IsHealthy, callInfo) + mock.lockIsHealthy.Unlock() + return mock.IsHealthyFunc() +} + +// IsHealthyCalls gets all the calls that were made to IsHealthy. +// Check the length with: +// len(mockedLogstashController.IsHealthyCalls()) +func (mock *LogstashControllerMock) IsHealthyCalls() []struct { +} { + var calls []struct { + } + mock.lockIsHealthy.RLock() + calls = mock.calls.IsHealthy + mock.lockIsHealthy.RUnlock() + return calls +} + +// Kill calls KillFunc. +func (mock *LogstashControllerMock) Kill() { + if mock.KillFunc == nil { + panic("LogstashControllerMock.KillFunc: method is nil but LogstashController.Kill was just called") + } + callInfo := struct { + }{} + mock.lockKill.Lock() + mock.calls.Kill = append(mock.calls.Kill, callInfo) + mock.lockKill.Unlock() + mock.KillFunc() +} + +// KillCalls gets all the calls that were made to Kill. +// Check the length with: +// len(mockedLogstashController.KillCalls()) +func (mock *LogstashControllerMock) KillCalls() []struct { +} { + var calls []struct { + } + mock.lockKill.RLock() + calls = mock.calls.Kill + mock.lockKill.RUnlock() + return calls +} + // SetupTest calls SetupTestFunc. func (mock *LogstashControllerMock) SetupTest(pipelines pipeline.Pipelines) error { if mock.SetupTestFunc == nil { diff --git a/testdata/basic_pipeline/main/main.conf b/testdata/basic_pipeline/main/main.conf index fbb4273..088a16c 100644 --- a/testdata/basic_pipeline/main/main.conf +++ b/testdata/basic_pipeline/main/main.conf @@ -9,6 +9,9 @@ filter { id => mutate add_tag => [ "sut_passed" ] } + # foobar { + # id => foobar + # } } output { From 68915668c7e4c2552337740bd5467afb5a176ce7 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 22 Mar 2021 21:38:16 +0100 Subject: [PATCH 040/143] Address review feedback --- Makefile | 2 +- internal/daemon/logstashconfig/file.go | 7 +------ internal/daemon/pipeline/pipeline.go | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index d9523a4..14e8ecc 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ $(PROGRAM)$(EXEC_SUFFIX): gogenerate .FORCE $(GOVVV) govvv build -o $@ .PHONY: gogenerate -generate: $(MOQ) +gogenerate: $(MOQ) go generate ./... .PHONY: check diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 69eaccb..3fb7120 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -28,12 +28,7 @@ func (f File) Save(targetDir string) error { return err } - err = ioutil.WriteFile(path.Join(targetDir, f.Name), f.Body, 0600) - if err != nil { - return err - } - - return nil + return ioutil.WriteFile(path.Join(targetDir, f.Name), f.Body, 0600) } func (f *File) parse() error { diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 6620308..163f5a6 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -87,10 +87,6 @@ func (a Archive) Validate() error { return nil } -// func (a Archive) walk() { - -// } - func (a Archive) Zip() ([]byte, error) { buf := new(bytes.Buffer) w := zip.NewWriter(buf) From 51bf2fc8917d1db1f051f9672459a6957f4fda28 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Mar 2021 12:02:29 +0100 Subject: [PATCH 041/143] Add filename to log output if config validation fails --- internal/daemon/logstashconfig/file.go | 2 +- internal/daemon/logstashconfig/file_test.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 3fb7120..f71171c 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -132,7 +132,7 @@ func (f *File) Validate() error { } if len(v.noIDs) > 0 { - return errors.Errorf("no IDs found for %v", v.noIDs) + return errors.Errorf("%q no IDs found for %v", f.Name, v.noIDs) } return nil } diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index c20bdcb..46d63ca 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -188,13 +188,14 @@ output { stdout { id => testid } }`, filter { mutate { } } output { stdout { } }`, - wantErr: errors.Errorf("no IDs found for [stdin mutate stdout]"), + wantErr: errors.Errorf(`"filename.conf" no IDs found for [stdin mutate stdout]`), }, } for _, test := range cases { t.Run(test.name, func(t *testing.T) { f := logstashconfig.File{ + Name: "filename.conf", Body: []byte(test.config), } From 2b37166c1b12106e80062208e4cc7a7c55d89d66 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Mar 2021 14:20:34 +0100 Subject: [PATCH 042/143] Add support for keep-envs --- integration_test.go | 2 +- internal/app/daemon/daemon.go | 9 +++++++-- internal/app/daemon_start.go | 8 +++++++- internal/daemon/instance/logstash/instance.go | 5 ++++- internal/logstash/detectversion.go | 6 ++---- internal/logstash/env.go | 4 ++-- internal/logstash/env_test.go | 2 +- internal/logstash/parallel_process.go | 2 +- internal/logstash/process.go | 2 +- 9 files changed, 26 insertions(+), 14 deletions(-) diff --git a/integration_test.go b/integration_test.go index 5cf0c9a..bca1340 100644 --- a/integration_test.go +++ b/integration_test.go @@ -45,7 +45,7 @@ func TestIntegration(t *testing.T) { } log := testLogger - server := daemon.New(socket, logstashPath, log, 10*time.Second, 3*time.Second) + server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index b4baa2b..5b7bb5e 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -25,6 +25,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/session" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + standalonelogstash "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" ) type Daemon struct { @@ -42,6 +43,7 @@ type Daemon struct { socket string logstashPath string + keptEnvVars []string tempdir string @@ -58,11 +60,12 @@ type Daemon struct { } // New creates a new logstash filter verifier daemon. -func New(socket string, logstashPath string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration) Daemon { +func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration) Daemon { ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ socket: socket, logstashPath: logstashPath, + keptEnvVars: keptEnvVars, inflightShutdownTimeout: inflightShutdownTimeout, shutdownTimeout: shutdownTimeout, log: log, @@ -87,11 +90,13 @@ func (d *Daemon) Run(ctx context.Context) error { d.tempdir = tempdir d.log.Debugf("Temporary directory for daemon created in %q", d.tempdir) + env := standalonelogstash.GetLimitedEnvironment(os.Environ(), d.keptEnvVars) + // Factory to create and start Logstash Controller shutdownLogstashInstancesWG := &sync.WaitGroup{} logstashControllerFactory := func() (session.LogstashController, error) { shutdownLogstashInstancesWG.Add(1) - instance := logstash.New(ctxKill, d.logstashPath, d.log, shutdownLogstashInstancesWG) + instance := logstash.New(ctxKill, d.logstashPath, env, d.log, shutdownLogstashInstancesWG) logstashController, err := controller.NewController(instance, tempdir, d.log) if err != nil { return nil, err diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index 53dee30..fa0e931 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -27,12 +27,18 @@ func makeDaemonStartCmd() *cobra.Command { cmd.Flags().DurationP("shutdown-timeout", "", 3*time.Second, "maximum duration to wait for Logstash and gRPC server to gracefully shutdown") _ = viper.BindPFlag("shutdown-timeout", cmd.Flags().Lookup("shutdown-timeout")) + // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. + // TODO: Not yet sure, if this should be global or only in standalone. + cmd.Flags().StringSlice("keep-env", []string{"PATH"}, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") + _ = viper.BindPFlag("keep-envs", cmd.Flags().Lookup("keep-env")) + return cmd } func runDaemonStart(_ *cobra.Command, _ []string) error { socket := viper.GetString("socket") logstashPath := viper.GetString("logstash.path") + keptEnvs := viper.GetStringSlice("keep-envs") inflightShutdownTimeout := viper.GetDuration("inflight-shutdown-timeout") shutdownTimeout := viper.GetDuration("shutdown-timeout") log := viper.Get("logger").(logging.Logger) @@ -40,7 +46,7 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { log.Debugf("config: socket: %s", socket) log.Debugf("config: logstash-path: %s", logstashPath) - s := daemon.New(socket, logstashPath, log, inflightShutdownTimeout, shutdownTimeout) + s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout) defer s.Cleanup() return s.Run(context.Background()) diff --git a/internal/daemon/instance/logstash/instance.go b/internal/daemon/instance/logstash/instance.go index d9a500c..1186996 100644 --- a/internal/daemon/instance/logstash/instance.go +++ b/internal/daemon/instance/logstash/instance.go @@ -21,6 +21,7 @@ type instance struct { controller *controller.Controller command string + env []string child *exec.Cmd log logging.Logger @@ -30,10 +31,11 @@ type instance struct { shutdownWG *sync.WaitGroup } -func New(ctxKill context.Context, command string, log logging.Logger, shutdownWG *sync.WaitGroup) controller.Instance { +func New(ctxKill context.Context, command string, env []string, log logging.Logger, shutdownWG *sync.WaitGroup) controller.Instance { return &instance{ ctxKill: ctxKill, command: command, + env: env, log: log, logstashStarted: make(chan struct{}), logstashShutdownWG: &sync.WaitGroup{}, @@ -71,6 +73,7 @@ func (i *instance) Start(ctx context.Context, controller *controller.Controller, } i.child = exec.CommandContext(i.ctxKill, i.command, args...) // nolint: gosec + i.child.Env = i.env stdout, err := i.child.StdoutPipe() if err != nil { return errors.Wrap(err, "failed to setup stdoutPipe") diff --git a/internal/logstash/detectversion.go b/internal/logstash/detectversion.go index 97251e4..ad00ab8 100644 --- a/internal/logstash/detectversion.go +++ b/internal/logstash/detectversion.go @@ -12,16 +12,14 @@ import ( semver "github.com/Masterminds/semver/v3" ) -var ( - logstashVersionRegexp = regexp.MustCompile(`^(?i)Logstash v?(\d+\.\S+)`) -) +var logstashVersionRegexp = regexp.MustCompile(`^(?i)Logstash v?(\d+\.\S+)`) // DetectVersion runs "logstash --version" and tries to parse the // result in order to determine which version of Logstash is being // run. func DetectVersion(logstashPath string, keptEnvVars []string) (*semver.Version, error) { c := exec.Command(logstashPath, "--version") - c.Env = getLimitedEnvironment(os.Environ(), keptEnvVars) + c.Env = GetLimitedEnvironment(os.Environ(), keptEnvVars) output, err := c.CombinedOutput() if err != nil { return nil, fmt.Errorf("error running \"%s --version\": %s, process output: %q", logstashPath, err, output) diff --git a/internal/logstash/env.go b/internal/logstash/env.go index dd185d2..8d8d282 100644 --- a/internal/logstash/env.go +++ b/internal/logstash/env.go @@ -6,7 +6,7 @@ import ( "strings" ) -// getLimitedEnvironment returns a list of "key=value" strings +// GetLimitedEnvironment returns a list of "key=value" strings // representing a process's environment based on an original set of // variables (e.g. returned by os.Environ()) that's intersected with a // list of the names of variables that should be kept. @@ -16,7 +16,7 @@ import ( // stable and independent of the current timezone so there's no risk // of a @timestamp mismatch just because we've gone into daylight // savings time. -func getLimitedEnvironment(originalVars, keptVars []string) []string { +func GetLimitedEnvironment(originalVars, keptVars []string) []string { keepVar := func(varname string) bool { for _, s := range keptVars { if varname == s { diff --git a/internal/logstash/env_test.go b/internal/logstash/env_test.go index eade2fe..3d8ce10 100644 --- a/internal/logstash/env_test.go +++ b/internal/logstash/env_test.go @@ -81,7 +81,7 @@ func TestGetLimitedEnvironment(t *testing.T) { }, } for i, c := range cases { - actual := getLimitedEnvironment(c.original, c.kept) + actual := GetLimitedEnvironment(c.original, c.kept) if !reflect.DeepEqual(c.expected, actual) { t.Errorf("Test %d:\nExpected:\n%#v\nGot:\n%#v", i, c.expected, actual) } diff --git a/internal/logstash/parallel_process.go b/internal/logstash/parallel_process.go index 9cde630..7c3d2cc 100644 --- a/internal/logstash/parallel_process.go +++ b/internal/logstash/parallel_process.go @@ -187,7 +187,7 @@ func NewParallelProcess(inv *Invocation, testStream []*TestStream, keptEnvVars [ return nil, err } - env := getLimitedEnvironment(os.Environ(), keptEnvVars) + env := GetLimitedEnvironment(os.Environ(), keptEnvVars) inputs := fmt.Sprintf("input { %s } ", strings.Join(logstashInput, " ")) outputs := fmt.Sprintf("output { %s }", strings.Join(logstashOutput, " ")) args, err := inv.Args(inputs, outputs) diff --git a/internal/logstash/process.go b/internal/logstash/process.go index 7107515..0a46ea9 100644 --- a/internal/logstash/process.go +++ b/internal/logstash/process.go @@ -54,7 +54,7 @@ func NewProcess(inv *Invocation, inputCodec string, fields FieldSet, keptEnvVars inputs := fmt.Sprintf("input { stdin { codec => %s add_field => %s } }", inputCodec, fieldHash) outputs := fmt.Sprintf("output { file { path => %q codec => \"json_lines\" } }", outputFile.Name()) - env := getLimitedEnvironment(os.Environ(), keptEnvVars) + env := GetLimitedEnvironment(os.Environ(), keptEnvVars) args, err := inv.Args(inputs, outputs) if err != nil { _ = outputFile.Close() From 4cde826320bfe2e6a649174480b06da99e7539a6 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Mar 2021 14:49:06 +0100 Subject: [PATCH 043/143] Cleanup internal fields and tags, add debug flag * cleanup __lfv internal fields and tags * add debug flag * move post processing to client --- integration_test.go | 17 ++++-- internal/app/daemon/run/run.go | 55 ++++++++++++++++++- internal/app/daemon_run.go | 27 ++++++--- internal/daemon/session/controller.go | 2 +- internal/daemon/session/files.go | 2 +- internal/daemon/session/session.go | 21 ++----- .../testcases/basic_pipeline/testcase1.json | 8 +-- .../testcases/basic_pipeline/testcase2.json | 4 +- .../basic_pipeline_debug/testcase1.json | 40 ++++++++++++++ .../basic_pipeline_debug/testcase2.json | 30 ++++++++++ .../conditional_output.json | 8 +-- .../pipeline_to_pipeline.json | 4 +- 12 files changed, 166 insertions(+), 52 deletions(-) create mode 100644 testdata/testcases/basic_pipeline_debug/testcase1.json create mode 100644 testdata/testcases/basic_pipeline_debug/testcase2.json diff --git a/integration_test.go b/integration_test.go index bca1340..6d07a83 100644 --- a/integration_test.go +++ b/integration_test.go @@ -75,10 +75,8 @@ func TestIntegration(t *testing.T) { // Run tests cases := []struct { - name string - pipeline string - basePath string - testcases string + name string + debug bool }{ { name: "basic_pipeline", @@ -89,16 +87,25 @@ func TestIntegration(t *testing.T) { { name: "pipeline_to_pipeline", }, + { + name: "basic_pipeline", + debug: true, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + testcases := tc.name + if tc.debug { + testcases += "_debug" + } client, err := run.New( path.Join(tempdir, "integration_test.socket"), log, "testdata/"+tc.name+".yml", "testdata/"+tc.name, - "testdata/testcases/"+tc.name, + "testdata/testcases/"+testcases, + tc.debug, ) is.NoErr(err) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index d3415ca..837fa84 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -6,9 +6,13 @@ import ( "net" "os" "path" + "sort" + "strings" "time" "github.com/imkira/go-observer" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" "google.golang.org/grpc" pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" @@ -24,11 +28,12 @@ type Test struct { pipeline string pipelineBase string testcasePath string + debug bool log logging.Logger } -func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath string) (Test, error) { +func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath string, debug bool) (Test, error) { if !path.IsAbs(pipelineBase) { cwd, err := os.Getwd() if err != nil { @@ -41,6 +46,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath pipeline: pipeline, pipelineBase: pipelineBase, testcasePath: testcasePath, + debug: debug, log: log, }, nil } @@ -114,8 +120,13 @@ func (s Test) Run() error { return err } + results, err := s.postProcessResults(result.Results) + if err != nil { + return err + } + var events []logstash.Event - for _, line := range result.Results { + for _, line := range results { var event logstash.Event err = json.Unmarshal([]byte(line), &event) if err != nil { @@ -148,3 +159,43 @@ func (s Test) Run() error { return nil } + +func (s Test) postProcessResults(results []string) ([]string, error) { + var err error + + sort.Slice(results, func(i, j int) bool { + return gjson.Get(results[i], `__lfv_id`).Int() < gjson.Get(results[j], `__lfv_id`).Int() + }) + + // No cleanup if debug is set + if s.debug { + return results, nil + } + + for i := range results { + results[i], err = sjson.Delete(results[i], "__lfv_id") + if err != nil { + return nil, err + } + + tags := []string{} + for _, tag := range gjson.Get(results[i], "tags").Array() { + if strings.HasPrefix(tag.String(), "__lfv_") { + continue + } + tags = append(tags, tag.String()) + } + + // Remove tag entry, if there are no tags + if len(tags) == 0 { + results[i], err = sjson.Delete(results[i], "tags") + } else { + results[i], err = sjson.Set(results[i], "tags", tags) + } + if err != nil { + return nil, err + } + } + + return results, nil +} diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index b15df8f..c210765 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -9,24 +9,33 @@ import ( ) func makeDaemonRunCmd() *cobra.Command { - rootCmd := &cobra.Command{ + cmd := &cobra.Command{ Use: "run", Short: "Run test suite with logstash-filter-verifier daemon", RunE: runDaemonRun, } - rootCmd.Flags().StringP("pipeline", "p", "", "location of the pipelines.yml file to be processed") - _ = viper.BindPFlag("pipeline", rootCmd.Flags().Lookup("pipeline")) - rootCmd.Flags().StringP("pipeline-base", "", "", "base directory for relative paths in the pipelines.yml") - _ = viper.BindPFlag("pipeline-base", rootCmd.Flags().Lookup("pipeline-base")) - rootCmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") - _ = viper.BindPFlag("testcase-dir", rootCmd.Flags().Lookup("testcase-dir")) + cmd.Flags().StringP("pipeline", "p", "", "location of the pipelines.yml file to be processed") + _ = viper.BindPFlag("pipeline", cmd.Flags().Lookup("pipeline")) + cmd.Flags().StringP("pipeline-base", "", "", "base directory for relative paths in the pipelines.yml") + _ = viper.BindPFlag("pipeline-base", cmd.Flags().Lookup("pipeline-base")) + cmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") + _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) + cmd.Flags().Bool("debug", false, "enable debug mode; e.g. prevents stripping '__lfv' data from Logstash events") + _ = viper.BindPFlag("debug", cmd.Flags().Lookup("debug")) - return rootCmd + return cmd } func runDaemonRun(_ *cobra.Command, args []string) error { - t, err := run.New(viper.GetString("socket"), viper.Get("logger").(logging.Logger), viper.GetString("pipeline"), viper.GetString("pipeline-base"), viper.GetString("testcase-dir")) + socket := viper.GetString("socket") + log := viper.Get("logger").(logging.Logger) + pipeline := viper.GetString("pipeline") + pipelineBase := viper.GetString("pipeline-base") + testcaseDir := viper.GetString("testcase-dir") + debug := viper.GetBool("debug") + + t, err := run.New(socket, log, pipeline, pipelineBase, testcaseDir, debug) if err != nil { return err } diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go index ba8fd13..642bfdd 100644 --- a/internal/daemon/session/controller.go +++ b/internal/daemon/session/controller.go @@ -60,7 +60,7 @@ func (s *Controller) Create(pipelines pipeline.Pipelines, configFiles []logstash return nil, err } - session := new(s.tempdir, logstashController, s.log) + session := newSession(s.tempdir, logstashController, s.log) s.sessions[session.ID()] = session s.wg.Add(1) diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index df808de..67ae5bf 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -8,7 +8,7 @@ const outputPipeline = `input { filter { mutate { - add_tag => [ "{{ .PipelineOrigName }}_passed" ] + add_tag => [ "__lfv_out_{{ .PipelineOrigName }}_passed" ] } } diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 2ec8da3..fac13e8 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -6,13 +6,10 @@ import ( "io/ioutil" "os" "path" - "sort" "strconv" "strings" "github.com/pkg/errors" - "github.com/tidwall/gjson" - "github.com/tidwall/sjson" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" @@ -33,10 +30,11 @@ type Session struct { pipelines pipeline.Pipelines testexec int - log logging.Logger + debug bool + log logging.Logger } -func new(baseDir string, logstashController pool.LogstashController, log logging.Logger) *Session { +func newSession(baseDir string, logstashController pool.LogstashController, log logging.Logger) *Session { sessionID := idgen.New() sessionDir := fmt.Sprintf("%s/session/%s", baseDir, sessionID) return &Session{ @@ -232,18 +230,9 @@ func createInput(pipelineFilename string, fieldsFilename string, ids []string) e func (s *Session) GetResults() ([]string, error) { results, err := s.logstashController.GetResults() if err != nil { - return results, err - } - sort.Slice(results, func(i, j int) bool { - return gjson.Get(results[i], `__lfv_id`).Int() < gjson.Get(results[j], `__lfv_id`).Int() - }) - for i := range results { - results[i], err = sjson.Delete(results[i], "__lfv_id") - if err != nil { - return results, err - } + return nil, err } - return results, err + return results, nil } // GetStats returns the statistics for a test suite. diff --git a/testdata/testcases/basic_pipeline/testcase1.json b/testdata/testcases/basic_pipeline/testcase1.json index 1080b42..2d12dd5 100644 --- a/testdata/testcases/basic_pipeline/testcase1.json +++ b/testdata/testcases/basic_pipeline/testcase1.json @@ -15,20 +15,16 @@ { "message": "test case message", "tags": [ - "__lfv_in_passed", "main2_passed", - "sut_passed", - "stdout_passed" + "sut_passed" ], "type": "syslog" }, { "message": "test case message 2", "tags": [ - "__lfv_in_passed", "main2_passed", - "sut_passed", - "stdout_passed" + "sut_passed" ], "type": "syslog" } diff --git a/testdata/testcases/basic_pipeline/testcase2.json b/testdata/testcases/basic_pipeline/testcase2.json index 8f4841b..9f537aa 100644 --- a/testdata/testcases/basic_pipeline/testcase2.json +++ b/testdata/testcases/basic_pipeline/testcase2.json @@ -17,10 +17,8 @@ "message": "", "second": "2", "tags": [ - "__lfv_in_passed", "main2_passed", - "sut_passed", - "stdout_passed" + "sut_passed" ] } ] diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json new file mode 100644 index 0000000..3031490 --- /dev/null +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -0,0 +1,40 @@ +{ + "fields": { + "type": "syslog" + }, + "ignore": [ + "@timestamp" + ], + "testcases": [ + { + "input": [ + "test case message", + "test case message 2" + ], + "expected": [ + { + "__lfv_id": "0", + "message": "test case message", + "tags": [ + "__lfv_in_passed", + "main2_passed", + "sut_passed", + "__lfv_out_stdout_passed" + ], + "type": "syslog" + }, + { + "__lfv_id": "1", + "message": "test case message 2", + "tags": [ + "__lfv_in_passed", + "main2_passed", + "sut_passed", + "__lfv_out_stdout_passed" + ], + "type": "syslog" + } + ] + } + ] +} diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json new file mode 100644 index 0000000..1ca6540 --- /dev/null +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -0,0 +1,30 @@ +{ + "fields": { + "first": "1", + "second": "2" + }, + "ignore": [ + "@timestamp" + ], + "testcases": [ + { + "input": [ + "" + ], + "expected": [ + { + "__lfv_id": "0", + "first": "1", + "message": "", + "second": "2", + "tags": [ + "__lfv_in_passed", + "main2_passed", + "sut_passed", + "__lfv_out_stdout_passed" + ] + } + ] + } + ] +} diff --git a/testdata/testcases/conditional_output/conditional_output.json b/testdata/testcases/conditional_output/conditional_output.json index be60d7d..9500689 100644 --- a/testdata/testcases/conditional_output/conditional_output.json +++ b/testdata/testcases/conditional_output/conditional_output.json @@ -14,17 +14,13 @@ { "message": "stdout", "tags": [ - "__lfv_in_passed", - "conditional_output_filter", - "stdout_passed" + "conditional_output_filter" ] }, { "message": "file", "tags": [ - "__lfv_in_passed", - "conditional_output_filter", - "file_passed" + "conditional_output_filter" ] } ] diff --git a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json index f8fc623..615d41e 100644 --- a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json +++ b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json @@ -13,10 +13,8 @@ { "message": "test case message", "tags": [ - "__lfv_in_passed", "mutate_stage_1_passed", - "mutate_stage_2_passed", - "stage2_output_passed" + "mutate_stage_2_passed" ] } ] From 2a4f2f334d90e0758fddaab37715531114583027 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Mar 2021 17:25:53 +0100 Subject: [PATCH 044/143] Add metadata-key flag to set key for @metadata object --- integration_test.go | 9 ++--- internal/app/daemon/daemon.go | 2 ++ internal/app/daemon/run/run.go | 35 +++++++++++++++---- internal/app/daemon_run.go | 5 ++- internal/daemon/controller/files.go | 9 ----- testdata/basic_pipeline_debug.yml | 2 ++ .../basic_pipeline_debug/main/dir/main2.conf | 6 ++++ testdata/basic_pipeline_debug/main/main.conf | 21 +++++++++++ .../basic_pipeline_debug/testcase1.json | 6 ++++ .../basic_pipeline_debug/testcase2.json | 3 ++ 10 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 testdata/basic_pipeline_debug.yml create mode 100644 testdata/basic_pipeline_debug/main/dir/main2.conf create mode 100644 testdata/basic_pipeline_debug/main/main.conf diff --git a/integration_test.go b/integration_test.go index 6d07a83..9752266 100644 --- a/integration_test.go +++ b/integration_test.go @@ -88,23 +88,20 @@ func TestIntegration(t *testing.T) { name: "pipeline_to_pipeline", }, { - name: "basic_pipeline", + name: "basic_pipeline_debug", debug: true, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - testcases := tc.name - if tc.debug { - testcases += "_debug" - } client, err := run.New( path.Join(tempdir, "integration_test.socket"), log, "testdata/"+tc.name+".yml", "testdata/"+tc.name, - "testdata/testcases/"+testcases, + "testdata/testcases/"+tc.name, + "@metadata", tc.debug, ) is.NoErr(err) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 5b7bb5e..87ad1d6 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -45,6 +45,8 @@ type Daemon struct { logstashPath string keptEnvVars []string + metadataKey string + tempdir string inflightShutdownTimeout time.Duration diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 837fa84..b8f4338 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -28,12 +28,13 @@ type Test struct { pipeline string pipelineBase string testcasePath string + metadataKey string debug bool log logging.Logger } -func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath string, debug bool) (Test, error) { +func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath string, metadataKey string, debug bool) (Test, error) { if !path.IsAbs(pipelineBase) { cwd, err := os.Getwd() if err != nil { @@ -46,6 +47,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath pipeline: pipeline, pipelineBase: pipelineBase, testcasePath: testcasePath, + metadataKey: metadataKey, debug: debug, log: log, }, nil @@ -167,12 +169,33 @@ func (s Test) postProcessResults(results []string) ([]string, error) { return gjson.Get(results[i], `__lfv_id`).Int() < gjson.Get(results[j], `__lfv_id`).Int() }) - // No cleanup if debug is set - if s.debug { - return results, nil - } - for i := range results { + metadata := gjson.Get(results[i], "__metadata") + if metadata.Exists() && metadata.IsObject() { + md := make(map[string]json.RawMessage, len(metadata.Map())) + for key, value := range metadata.Map() { + if strings.HasPrefix(key, "__lfv") || strings.HasPrefix(key, "__tmp") { + continue + } + md[key] = json.RawMessage(value.Raw) + } + if len(md) > 0 { + results[i], err = sjson.Set(results[i], s.metadataKey, md) + if err != nil { + return nil, err + } + } + } + results[i], err = sjson.Delete(results[i], "__metadata") + if err != nil { + return nil, err + } + + // No cleanup if debug is set + if s.debug { + continue + } + results[i], err = sjson.Delete(results[i], "__lfv_id") if err != nil { return nil, err diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index c210765..6423f4f 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -23,6 +23,8 @@ func makeDaemonRunCmd() *cobra.Command { _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) cmd.Flags().Bool("debug", false, "enable debug mode; e.g. prevents stripping '__lfv' data from Logstash events") _ = viper.BindPFlag("debug", cmd.Flags().Lookup("debug")) + cmd.Flags().String("metadata-key", "@metadata", "Key under which the content of the `@metadata` field is exposed in the returned events.") + _ = viper.BindPFlag("metadata-key", cmd.Flags().Lookup("metadata-key")) return cmd } @@ -34,8 +36,9 @@ func runDaemonRun(_ *cobra.Command, args []string) error { pipelineBase := viper.GetString("pipeline-base") testcaseDir := viper.GetString("testcase-dir") debug := viper.GetBool("debug") + metadataKey := viper.GetString("metadata-key") - t, err := run.New(socket, log, pipeline, pipelineBase, testcaseDir, debug) + t, err := run.New(socket, log, pipeline, pipelineBase, testcaseDir, metadataKey, debug) if err != nil { return err } diff --git a/internal/daemon/controller/files.go b/internal/daemon/controller/files.go index e32c324..a1e2a48 100644 --- a/internal/daemon/controller/files.go +++ b/internal/daemon/controller/files.go @@ -46,15 +46,6 @@ filter { "[@metadata]" => "[__metadata]" } } - ruby { - code => 'metadata = event.get("__metadata") - metadata.delete_if {|key, value| key.start_with?("__lfv") || key.start_with?("__tmp") } - if metadata.length > 0 - event.set("__metadata", metadata) - else - event.remove("__metadata") - end' - } } output { stdout { diff --git a/testdata/basic_pipeline_debug.yml b/testdata/basic_pipeline_debug.yml new file mode 100644 index 0000000..a9f230e --- /dev/null +++ b/testdata/basic_pipeline_debug.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "main/**/*.conf" diff --git a/testdata/basic_pipeline_debug/main/dir/main2.conf b/testdata/basic_pipeline_debug/main/dir/main2.conf new file mode 100644 index 0000000..4ca4d61 --- /dev/null +++ b/testdata/basic_pipeline_debug/main/dir/main2.conf @@ -0,0 +1,6 @@ +filter { + mutate { + id => mutate2 + add_tag => [ "main2_passed" ] + } +} \ No newline at end of file diff --git a/testdata/basic_pipeline_debug/main/main.conf b/testdata/basic_pipeline_debug/main/main.conf new file mode 100644 index 0000000..2ae94f2 --- /dev/null +++ b/testdata/basic_pipeline_debug/main/main.conf @@ -0,0 +1,21 @@ +input { + stdin { + id => stdin + } +} + +filter { + mutate { + id => mutate + add_tag => [ "sut_passed" ] + add_field => { + "[@metadata][hidden_field]" => "hidden value" + } + } +} + +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index 3031490..5f497dd 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -13,6 +13,9 @@ ], "expected": [ { + "@metadata": { + "hidden_field": "hidden value" + }, "__lfv_id": "0", "message": "test case message", "tags": [ @@ -24,6 +27,9 @@ "type": "syslog" }, { + "@metadata": { + "hidden_field": "hidden value" + }, "__lfv_id": "1", "message": "test case message 2", "tags": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index 1ca6540..8a71885 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -13,6 +13,9 @@ ], "expected": [ { + "@metadata": { + "hidden_field": "hidden value" + }, "__lfv_id": "0", "first": "1", "message": "", From d62f77b01f2759391744afacf0b0b7c745c90ad6 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Mar 2021 17:39:16 +0100 Subject: [PATCH 045/143] Add exportMetadata option to test case definition --- internal/app/daemon/run/run.go | 30 ++++++++++--------- internal/testcase/testcase.go | 7 +++++ .../basic_pipeline_debug/testcase1.json | 6 ---- .../basic_pipeline_debug/testcase2.json | 1 + 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index b8f4338..8bd50c6 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -122,7 +122,7 @@ func (s Test) Run() error { return err } - results, err := s.postProcessResults(result.Results) + results, err := s.postProcessResults(result.Results, t.ExportMetadata) if err != nil { return err } @@ -162,7 +162,7 @@ func (s Test) Run() error { return nil } -func (s Test) postProcessResults(results []string) ([]string, error) { +func (s Test) postProcessResults(results []string, exportMetadata bool) ([]string, error) { var err error sort.Slice(results, func(i, j int) bool { @@ -170,19 +170,21 @@ func (s Test) postProcessResults(results []string) ([]string, error) { }) for i := range results { - metadata := gjson.Get(results[i], "__metadata") - if metadata.Exists() && metadata.IsObject() { - md := make(map[string]json.RawMessage, len(metadata.Map())) - for key, value := range metadata.Map() { - if strings.HasPrefix(key, "__lfv") || strings.HasPrefix(key, "__tmp") { - continue + if exportMetadata { + metadata := gjson.Get(results[i], "__metadata") + if metadata.Exists() && metadata.IsObject() { + md := make(map[string]json.RawMessage, len(metadata.Map())) + for key, value := range metadata.Map() { + if strings.HasPrefix(key, "__lfv") || strings.HasPrefix(key, "__tmp") { + continue + } + md[key] = json.RawMessage(value.Raw) } - md[key] = json.RawMessage(value.Raw) - } - if len(md) > 0 { - results[i], err = sjson.Set(results[i], s.metadataKey, md) - if err != nil { - return nil, err + if len(md) > 0 { + results[i], err = sjson.Set(results[i], s.metadataKey, md) + if err != nil { + return nil, err + } } } } diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 751d97b..c69b109 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -66,6 +66,13 @@ type TestCaseSet struct { // process. ExpectedEvents []logstash.Event `json:"expected" yaml:"expected"` + // ExportMetadata controls if the metadata of the event processed + // by Logstash is returned The metadata is contained in the field + // `[@metadata]` in the Logstash event. + // If the metadata is exported, the respective fields are compared + // with the expected result of the testcase as well. (default: false) + ExportMetadata bool `json:"exportMetadata" yaml:"exportMetadata"` + // TestCases is a slice of test cases, which include at minimum // a pair of an input and an expected event // Optionally other information regarding the test case diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index 5f497dd..3031490 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -13,9 +13,6 @@ ], "expected": [ { - "@metadata": { - "hidden_field": "hidden value" - }, "__lfv_id": "0", "message": "test case message", "tags": [ @@ -27,9 +24,6 @@ "type": "syslog" }, { - "@metadata": { - "hidden_field": "hidden value" - }, "__lfv_id": "1", "message": "test case message 2", "tags": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index 8a71885..303040f 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -6,6 +6,7 @@ "ignore": [ "@timestamp" ], + "exportMetadata": true, "testcases": [ { "input": [ From f70f4169dabb48881e9abed61ea216b365bfeb4f Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 12 Mar 2021 18:02:21 +0100 Subject: [PATCH 046/143] Add __lfv prefix to exported metadata --- internal/app/daemon/run/run.go | 4 ++-- internal/daemon/controller/files.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 8bd50c6..c259e5d 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -171,7 +171,7 @@ func (s Test) postProcessResults(results []string, exportMetadata bool) ([]strin for i := range results { if exportMetadata { - metadata := gjson.Get(results[i], "__metadata") + metadata := gjson.Get(results[i], "__lfv_metadata") if metadata.Exists() && metadata.IsObject() { md := make(map[string]json.RawMessage, len(metadata.Map())) for key, value := range metadata.Map() { @@ -188,7 +188,7 @@ func (s Test) postProcessResults(results []string, exportMetadata bool) ([]strin } } } - results[i], err = sjson.Delete(results[i], "__metadata") + results[i], err = sjson.Delete(results[i], "__lfv_metadata") if err != nil { return nil, err } diff --git a/internal/daemon/controller/files.go b/internal/daemon/controller/files.go index a1e2a48..33757aa 100644 --- a/internal/daemon/controller/files.go +++ b/internal/daemon/controller/files.go @@ -43,7 +43,7 @@ const outputPipeline = `input { filter { mutate { copy => { - "[@metadata]" => "[__metadata]" + "[@metadata]" => "[__lfv_metadata]" } } } From 158bec6fd3f32c022d7199d3dd6def1513848e46 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 15 Mar 2021 21:55:22 +0100 Subject: [PATCH 047/143] Support multiple inputs, codecs and add_field/tags on inputs --- integration_test.go | 3 + internal/app/daemon/daemon.go | 6 +- internal/app/daemon/run/run.go | 9 +- internal/daemon/api/grpc/api.pb.go | 104 ++++++++++-------- internal/daemon/api/grpc/api.proto | 5 +- internal/daemon/logstashconfig/file.go | 43 +++++++- internal/daemon/logstashconfig/file_test.go | 15 ++- internal/daemon/session/controller_test.go | 13 ++- internal/daemon/session/files.go | 24 ++-- internal/daemon/session/session.go | 69 +++++++----- internal/testcase/testcase.go | 34 +++++- internal/testcase/testcase_test.go | 5 +- testdata/codec_test.yml | 2 + testdata/codec_test/codec_test.conf | 27 +++++ .../testcases/basic_pipeline/testcase1.json | 1 + .../testcases/basic_pipeline/testcase2.json | 1 + .../basic_pipeline_debug/testcase1.json | 1 + .../basic_pipeline_debug/testcase2.json | 1 + testdata/testcases/codec_test/line.json | 25 +++++ testdata/testcases/codec_test/plain.json | 26 +++++ .../conditional_output.json | 1 + .../pipeline_to_pipeline.json | 1 + 22 files changed, 305 insertions(+), 111 deletions(-) create mode 100644 testdata/codec_test.yml create mode 100644 testdata/codec_test/codec_test.conf create mode 100644 testdata/testcases/codec_test/line.json create mode 100644 testdata/testcases/codec_test/plain.json diff --git a/integration_test.go b/integration_test.go index 9752266..5e8b6d9 100644 --- a/integration_test.go +++ b/integration_test.go @@ -91,6 +91,9 @@ func TestIntegration(t *testing.T) { name: "basic_pipeline_debug", debug: true, }, + { + name: "codec_test", + }, } for _, tc := range cases { diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 87ad1d6..7242414 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -353,13 +353,13 @@ func (d *Daemon) ExecuteTest(ctx context.Context, in *pb.ExecuteTestRequest) (ou } }() - fields := map[string]interface{}{} - err = json.Unmarshal(in.Fields, &fields) + events := []map[string]interface{}{} + err = json.Unmarshal(in.Events, &events) if err != nil { return nil, errors.Wrap(err, "invalid json for fields") } - err = session.ExecuteTest(in.InputLines, fields) + err = session.ExecuteTest(in.InputPlugin, in.InputLines, events) if err != nil { return nil, err } diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index c259e5d..fed2e37 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -109,14 +109,15 @@ func (s Test) Run() error { } for _, t := range tests { - b, err := json.Marshal(t.InputFields) + b, err := json.Marshal(t.Events) if err != nil { return err } result, err := c.ExecuteTest(context.Background(), &pb.ExecuteTestRequest{ - SessionID: sessionID, - InputLines: t.InputLines, - Fields: b, + SessionID: sessionID, + InputPlugin: t.InputPlugin, + InputLines: t.InputLines, + Events: b, }) if err != nil { return err diff --git a/internal/daemon/api/grpc/api.pb.go b/internal/daemon/api/grpc/api.pb.go index 15b032a..55553cf 100644 --- a/internal/daemon/api/grpc/api.pb.go +++ b/internal/daemon/api/grpc/api.pb.go @@ -200,9 +200,10 @@ type ExecuteTestRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` - InputLines []string `protobuf:"bytes,2,rep,name=inputLines,proto3" json:"inputLines,omitempty"` - Fields []byte `protobuf:"bytes,3,opt,name=fields,proto3" json:"fields,omitempty"` + SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` + InputPlugin string `protobuf:"bytes,2,opt,name=input_plugin,json=inputPlugin,proto3" json:"input_plugin,omitempty"` + InputLines []string `protobuf:"bytes,3,rep,name=inputLines,proto3" json:"inputLines,omitempty"` + Events []byte `protobuf:"bytes,4,opt,name=events,proto3" json:"events,omitempty"` } func (x *ExecuteTestRequest) Reset() { @@ -244,6 +245,13 @@ func (x *ExecuteTestRequest) GetSessionID() string { return "" } +func (x *ExecuteTestRequest) GetInputPlugin() string { + if x != nil { + return x.InputPlugin + } + return "" +} + func (x *ExecuteTestRequest) GetInputLines() []string { if x != nil { return x.InputLines @@ -251,9 +259,9 @@ func (x *ExecuteTestRequest) GetInputLines() []string { return nil } -func (x *ExecuteTestRequest) GetFields() []byte { +func (x *ExecuteTestRequest) GetEvents() []byte { if x != nil { - return x.Fields + return x.Events } return nil } @@ -419,48 +427,50 @@ var file_api_proto_rawDesc = []byte{ 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x31, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x6a, 0x0a, 0x12, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, - 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x12, - 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x22, 0x2f, 0x0a, 0x13, 0x45, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x54, 0x65, 0x61, 0x72, - 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, - 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x73, 0x32, 0x95, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x3b, 0x0a, - 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, - 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, - 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x12, 0x47, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, - 0x12, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, 0x52, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, 0x75, 0x73, 0x62, 0x61, - 0x65, 0x63, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, 0x2d, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x2d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, - 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x8d, 0x01, 0x0a, 0x12, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, + 0x6e, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x2f, 0x0a, 0x13, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, + 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, + 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x32, 0x95, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, + 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, + 0x0a, 0x09, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, + 0x0a, 0x0b, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, + 0x54, 0x65, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, + 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, + 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, + 0x52, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, + 0x75, 0x73, 0x62, 0x61, 0x65, 0x63, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, + 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, + 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/daemon/api/grpc/api.proto b/internal/daemon/api/grpc/api.proto index 8b6ee2d..7b07de4 100644 --- a/internal/daemon/api/grpc/api.proto +++ b/internal/daemon/api/grpc/api.proto @@ -27,8 +27,9 @@ message SetupTestResponse { message ExecuteTestRequest { string sessionID = 1; - repeated string inputLines = 2; - bytes fields = 3; + string input_plugin = 2; + repeated string inputLines = 3; + bytes events = 4; } message ExecuteTestResponse { diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index f71171c..e4b2abd 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -51,29 +51,60 @@ func (f *File) parse() error { return nil } -func (f *File) ReplaceInputs() error { +func (f *File) ReplaceInputs(idPrefix string) (map[string]string, error) { err := f.parse() if err != nil { - return err + return nil, err + } + + w := replaceInputsWalker{ + idPrefix: idPrefix, + inputCodecs: map[string]string{}, } for i := range f.config.Input { - astutil.ApplyPlugins(f.config.Input[i].BranchOrPlugins, replaceInputs) + astutil.ApplyPlugins(f.config.Input[i].BranchOrPlugins, w.replaceInputs) } f.Body = []byte(f.config.String()) - return nil + return w.inputCodecs, nil +} + +type replaceInputsWalker struct { + idPrefix string + inputCodecs map[string]string } -func replaceInputs(c *astutil.Cursor) { +func (r replaceInputsWalker) replaceInputs(c *astutil.Cursor) { if c.Plugin() == nil || c.Plugin().Name() == "pipeline" { return } + id, err := c.Plugin().ID() + if err != nil { + panic(err) + } + + var attrs []ast.Attribute + attrs = append(attrs, ast.NewStringAttribute("address", fmt.Sprintf("%s_%s_%s", "__lfv_input", r.idPrefix, id), ast.Bareword)) + + for _, attr := range c.Plugin().Attributes { + if attr == nil { + continue + } + switch attr.Name() { + case "add_field", "tags": + attrs = append(attrs, attr) + case "codec": + r.inputCodecs[id] = attr.String() + default: + } + } + // TODO: __lfv_input must reflect the actual input, that has been replaced, such that this input // can be referenced in the test case configuration. - c.Replace(ast.NewPlugin("pipeline", ast.NewStringAttribute("address", "__lfv_input", ast.Bareword))) + c.Replace(ast.NewPlugin("pipeline", attrs...)) } func (f *File) ReplaceOutputs() ([]string, error) { diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index 46d63ca..38f2e7b 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -50,21 +50,23 @@ func TestReplaceInputs(t *testing.T) { }{ { name: "successful replacement", - config: "input { stdin{} }", + config: "input { stdin{ id => testid } }", wantConfig: `input { pipeline { - address => __lfv_input + address => __lfv_input_prefix_testid } } `, }, { name: "successful untouched pipeline input", - config: "input { pipeline{} }", + config: "input { pipeline{ id => testid } }", wantConfig: `input { - pipeline {} + pipeline { + id => testid + } } `, }, @@ -78,7 +80,8 @@ func TestReplaceInputs(t *testing.T) { Body: []byte(test.config), } - err := f.ReplaceInputs() + // FIXME: Add test for the inputCodecs map as well + _, err := f.ReplaceInputs("prefix") is.NoErr(err) is.Equal(test.wantConfig, string(f.Body)) @@ -147,7 +150,7 @@ func TestReplaceOutputsWithoutID(t *testing.T) { }{ { name: "successful replacement without id", - config: "output { stdout{} }", + config: "output { stdout{ } }", }, } diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index efb3174..e816bea 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -67,7 +67,7 @@ func TestSession(t *testing.T) { configFiles := []logstashconfig.File{ { Name: "main.conf", - Body: []byte(`input { stdin{} } filter { mutate{ add_tag => [ "test" ] } } output { stdout{} }`), + Body: []byte(`input { stdin{ id => testid } } filter { mutate{ add_tag => [ "test" ] } } output { stdout{} }`), }, } @@ -85,15 +85,16 @@ func TestSession(t *testing.T) { is.NoErr(err) inputLines := []string{"some_random_input"} - inFields := map[string]interface{}{ - "some_random_key": "value", + inFields := []map[string]interface{}{ + { + "some_random_key": "value", + }, } - err = s.ExecuteTest(inputLines, inFields) + err = s.ExecuteTest("input", inputLines, inFields) is.NoErr(err) is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"))) // lfv_inputs/1/fields.json is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"), "some_random_key")) // lfv_inputs/1/fields.json contains "some_random_key" - is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"), "some_random_input")) // lfv_inputs/1/fields.json contains "some_random_input" is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"))) // lfv_inputs/1/input.conf is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"), "lfv_inputs/1/fields.json")) // lfv_inputs/1/input.conf contains "lfv_inputs/1/fields.json" @@ -156,7 +157,7 @@ func TestCreate(t *testing.T) { configFiles := []logstashconfig.File{ { Name: "main.conf", - Body: []byte(`input { stdin{} } filter { mutate{ add_tag => [ "test" ] } } output { stdout{} }`), + Body: []byte(`input { stdin{ id => testid } } filter { mutate{ add_tag => [ "test" ] } } output { stdout{} }`), }, } diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index 67ae5bf..5a39ac3 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -25,21 +25,26 @@ input { lines => [ {{ .InputLines }} ] + {{ .InputCodec }} count => 1 - codec => plain threads => 1 } } filter { + ruby { + id => '__lfv_ruby_count' + init => '@count = 0' + code => 'event.set("__lfv_id", @count.to_s) + @count += 1' + tag_on_exception => '__lfv_ruby_count_exception' + } + mutate { add_tag => [ "__lfv_in_passed" ] - # Remove the fields "sequence" and "host", which are automatically created by the generator input. - remove_field => [ "host", "sequence" ] - # We use the message as the LFV event ID, so move this to the right field. - replace => { - "[__lfv_id]" => "%{[message]}" - } + # Remove fields "host", "sequence" and optionally "message", which are + # automatically created by the generator input. + remove_field => [ {{ .RemoveGeneratorFields }} ] } translate { @@ -50,17 +55,20 @@ filter { override => true # TODO: Add default value (e.g. "__lfv_fields_not_found"), if not found in dictionary } + ruby { + id => '__lfv_ruby_fields' # TODO: If default value ("__lfv_fields_not_found"), then skip this ruby # code and add an tag instead code => 'fields = event.get("[@metadata][__lfv_fields]") fields.each { |key, value| event.set(key, value) }' + tag_on_exception => '__lfv_ruby_fields_exception' } } output { pipeline { - send_to => [__lfv_input] + send_to => [ "{{ .InputPluginName }}" ] } } ` diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index fac13e8..763eacb 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -27,11 +27,11 @@ type Session struct { baseDir string sessionDir string - pipelines pipeline.Pipelines - testexec int + pipelines pipeline.Pipelines + inputPluginCodecs map[string]string + testexec int - debug bool - log logging.Logger + log logging.Logger } func newSession(baseDir string, logstashController pool.LogstashController, log logging.Logger) *Session { @@ -42,6 +42,7 @@ func newSession(baseDir string, logstashController pool.LogstashController, log baseDir: baseDir, sessionDir: sessionDir, logstashController: logstashController, + inputPluginCodecs: map[string]string{}, log: log, } } @@ -72,10 +73,11 @@ func (s *Session) setupTest(pipelines pipeline.Pipelines, configFiles []logstash // Preprocess and Save Config Files for _, configFile := range configFiles { - err := configFile.ReplaceInputs() + inputCodecs, err := configFile.ReplaceInputs(s.id) if err != nil { return err } + s.inputPluginCodecs = inputCodecs outputs, err := configFile.ReplaceOutputs() if err != nil { @@ -144,10 +146,15 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, // ExecuteTest runs a test case set against the Logstash configuration, that has // been loaded previously with SetupTest. -func (s *Session) ExecuteTest(inputLines []string, inFields map[string]interface{}) error { +func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents []map[string]interface{}) error { s.testexec++ pipelineName := fmt.Sprintf("lfv_input_%d", s.testexec) inputDir := path.Join(s.sessionDir, "lfv_inputs", strconv.Itoa(s.testexec)) + inputPluginName := fmt.Sprintf("%s_%s_%s", "__lfv_input", s.id, inputPlugin) + inputCodec, ok := s.inputPluginCodecs[inputPlugin] + if !ok { + inputCodec = "codec => plain" + } // Prepare input directory err := os.MkdirAll(inputDir, 0700) @@ -156,13 +163,13 @@ func (s *Session) ExecuteTest(inputLines []string, inFields map[string]interface } fieldsFilename := path.Join(inputDir, "fields.json") - ids, err := prepareFields(fieldsFilename, inputLines, inFields) + err = prepareFields(fieldsFilename, inEvents) if err != nil { return err } pipelineFilename := path.Join(inputDir, "input.conf") - err = createInput(pipelineFilename, fieldsFilename, ids) + err = createInput(pipelineFilename, fieldsFilename, inputPluginName, inputLines, inputCodec, false) if err != nil { return err } @@ -182,41 +189,49 @@ func (s *Session) ExecuteTest(inputLines []string, inFields map[string]interface return nil } -func prepareFields(fieldsFilename string, inputLines []string, inFields map[string]interface{}) ([]string, error) { - // FIXME: This does not allow arbritrary nested fields yet. +func prepareFields(fieldsFilename string, inEvents []map[string]interface{}) error { fields := make(map[string]map[string]interface{}) - ids := make([]string, 0, len(inputLines)) - for i, line := range inputLines { + for i, event := range inEvents { id := fmt.Sprintf("%d", i) - ids = append(ids, fmt.Sprintf("%q", id)) - fields[id] = make(map[string]interface{}) - fields[id]["message"] = line - - for field, value := range inFields { - fields[id][field] = value - } + fields[id] = event } bfields, err := json.Marshal(fields) if err != nil { - return nil, err + return err } err = ioutil.WriteFile(fieldsFilename, bfields, 0600) if err != nil { - return nil, err + return err } - return ids, nil + return nil } -func createInput(pipelineFilename string, fieldsFilename string, ids []string) error { +func createInput(pipelineFilename string, fieldsFilename string, inputPluginName string, inputLines []string, inputCodec string, removeMessageField bool) error { + removeGeneratorFields := `"host", "sequence"` + if removeMessageField { + removeGeneratorFields += `, "message"` + } + + // FIXME: inputLines are not properly escaped for Logstash + for i := range inputLines { + inputLines[i] = "'" + inputLines[i] + "'" + } + templateData := struct { - InputLines string - FieldsFilename string + InputPluginName string + InputLines string + InputCodec string + FieldsFilename string + RemoveGeneratorFields string }{ - InputLines: strings.Join(ids, ", "), - FieldsFilename: fieldsFilename, + InputPluginName: inputPluginName, + InputLines: strings.Join(inputLines, ", "), + InputCodec: inputCodec, + FieldsFilename: fieldsFilename, + RemoveGeneratorFields: removeGeneratorFields, } err := template.ToFile(pipelineFilename, inputGenerator, templateData, 0600) if err != nil { diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index c69b109..1136ffc 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -32,6 +32,13 @@ type TestCaseSet struct { // test case was read. File string `json:"-" yaml:"-"` + // The unique ID of the input plugin in the tested configuration, where the + // test input is coming from. This is necessary, if a setup with multiple + // inputs is tested, which either have different codecs or are part of + // different pipelines. + // https://www.elastic.co/guide/en/logstash/7.10/plugins-inputs-file.html#plugins-inputs-file-id + InputPlugin string `json:"input_plugin" yaml:"input_plugin"` + // Codec names the Logstash codec that should be used when // events are read. This is normally "line" or "json_lines". Codec string `json:"codec" yaml:"codec"` @@ -79,7 +86,12 @@ type TestCaseSet struct { // may be supplied. TestCases []TestCase `json:"testcases" yaml:"testcases"` - descriptions []string `json:"descriptions" yaml:"descriptions"` + // Events contains the fields for each event. This fields is filled + // in the New function. The sources are: InputFields, TestCase.Event and + // TestCase.InputLines + Events []logstash.FieldSet `json:"-" yaml:"-"` + + descriptions []string } // TestCase is a pair of an input line that should be fed @@ -90,6 +102,10 @@ type TestCase struct { // to the Logstash process. InputLines []string `json:"input" yaml:"input"` + // Local fields, only added to the events of this test case. + // These fields overwrite global fields. + Event logstash.FieldSet `json:"event" yaml:"event"` + // ExpectedEvents contains a slice of expected events to be // compared to the actual events produced by the Logstash // process. @@ -173,9 +189,25 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { tcs.IgnoredFields = append(tcs.IgnoredFields, defaultIgnoredFields...) sort.Strings(tcs.IgnoredFields) tcs.descriptions = make([]string, len(tcs.ExpectedEvents)) + for range tcs.InputLines { + tcs.Events = append(tcs.Events, tcs.InputFields) + } for _, tc := range tcs.TestCases { + // Add event, if there are no input lines. + if len(tc.InputLines) == 0 { + tc.InputLines = []string{""} + } tcs.InputLines = append(tcs.InputLines, tc.InputLines...) tcs.ExpectedEvents = append(tcs.ExpectedEvents, tc.ExpectedEvents...) + // Process each input line + for range tc.InputLines { + // Global Fields first. + tcs.Events = append(tcs.Events, tcs.InputFields) + // Merge with test case fields, eventually overwriting global fields. + for k, v := range tc.Event { + tcs.Events[len(tcs.Events)-1][k] = v + } + } for range tc.ExpectedEvents { tcs.descriptions = append(tcs.descriptions, tc.Description) } diff --git a/internal/testcase/testcase_test.go b/internal/testcase/testcase_test.go index b17762e..b5468a4 100644 --- a/internal/testcase/testcase_test.go +++ b/internal/testcase/testcase_test.go @@ -11,8 +11,9 @@ import ( "testing" "github.com/imkira/go-observer" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" "github.com/stretchr/testify/assert" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" ) func TestNew(t *testing.T) { @@ -75,6 +76,7 @@ func TestNew(t *testing.T) { InputLines: []string{"{\"[test][path]\": \"test\"}"}, IgnoredFields: []string{"@version"}, InputFields: logstash.FieldSet{}, + Events: []logstash.FieldSet{{}}, }, }, // handle input with bracket notation when codec is json_lines @@ -85,6 +87,7 @@ func TestNew(t *testing.T) { InputLines: []string{"{\"test\":{\"path\":\"test\"}}"}, IgnoredFields: []string{"@version"}, InputFields: logstash.FieldSet{}, + Events: []logstash.FieldSet{{}}, }, }, } diff --git a/testdata/codec_test.yml b/testdata/codec_test.yml new file mode 100644 index 0000000..df67ef8 --- /dev/null +++ b/testdata/codec_test.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "codec_test.conf" diff --git a/testdata/codec_test/codec_test.conf b/testdata/codec_test/codec_test.conf new file mode 100644 index 0000000..a73270d --- /dev/null +++ b/testdata/codec_test/codec_test.conf @@ -0,0 +1,27 @@ +input { + stdin { + id => plain + codec => plain + add_field => { + "input_codec" => "plain" + } + tags => [ "input_codec_plain" ] + } +} + +input { + stdin { + id => line + codec => line + add_field => { + "input_codec" => "line" + } + tags => [ "input_codec_line" ] + } +} + +output { + stdout { + id => stdout + } +} diff --git a/testdata/testcases/basic_pipeline/testcase1.json b/testdata/testcases/basic_pipeline/testcase1.json index 2d12dd5..bf2a204 100644 --- a/testdata/testcases/basic_pipeline/testcase1.json +++ b/testdata/testcases/basic_pipeline/testcase1.json @@ -5,6 +5,7 @@ "ignore": [ "@timestamp" ], + "input_plugin": "stdin", "testcases": [ { "input": [ diff --git a/testdata/testcases/basic_pipeline/testcase2.json b/testdata/testcases/basic_pipeline/testcase2.json index 9f537aa..0e0d4b0 100644 --- a/testdata/testcases/basic_pipeline/testcase2.json +++ b/testdata/testcases/basic_pipeline/testcase2.json @@ -6,6 +6,7 @@ "ignore": [ "@timestamp" ], + "input_plugin": "stdin", "testcases": [ { "input": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index 3031490..46574b1 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -5,6 +5,7 @@ "ignore": [ "@timestamp" ], + "input_plugin": "stdin", "testcases": [ { "input": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index 303040f..cc72c3c 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -6,6 +6,7 @@ "ignore": [ "@timestamp" ], + "input_plugin": "stdin", "exportMetadata": true, "testcases": [ { diff --git a/testdata/testcases/codec_test/line.json b/testdata/testcases/codec_test/line.json new file mode 100644 index 0000000..6d2141e --- /dev/null +++ b/testdata/testcases/codec_test/line.json @@ -0,0 +1,25 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "line", + "testcases": [ + { + "input": [ + "test case message\ntest case message 2" + ], + "expected": [ + { + "input_codec": "line", + "message": "test case message", + "tags": [ "input_codec_line" ] + }, + { + "input_codec": "line", + "message": "test case message 2", + "tags": [ "input_codec_line" ] + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/plain.json b/testdata/testcases/codec_test/plain.json new file mode 100644 index 0000000..2411e79 --- /dev/null +++ b/testdata/testcases/codec_test/plain.json @@ -0,0 +1,26 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "plain", + "testcases": [ + { + "input": [ + "test case message", + "test case message 2" + ], + "expected": [ + { + "input_codec": "plain", + "message": "test case message", + "tags": [ "input_codec_plain" ] + }, + { + "input_codec": "plain", + "message": "test case message 2", + "tags": [ "input_codec_plain" ] + } + ] + } + ] +} diff --git a/testdata/testcases/conditional_output/conditional_output.json b/testdata/testcases/conditional_output/conditional_output.json index 9500689..e1b6e22 100644 --- a/testdata/testcases/conditional_output/conditional_output.json +++ b/testdata/testcases/conditional_output/conditional_output.json @@ -4,6 +4,7 @@ "ignore": [ "@timestamp" ], + "input_plugin": "stdin", "testcases": [ { "input": [ diff --git a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json index 615d41e..2fde702 100644 --- a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json +++ b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json @@ -4,6 +4,7 @@ "ignore": [ "@timestamp" ], + "input_plugin": "stage1_input", "testcases": [ { "input": [ From 77de4a67e82ae4ba339879b9a9d5245639908b05 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 30 Mar 2021 21:33:55 +0200 Subject: [PATCH 048/143] Count non pipeline input and output plugins, error if there are 0 --- go.mod | 4 +-- go.sum | 9 ++++--- internal/daemon/logstashconfig/file.go | 25 +++++++++++++----- internal/daemon/logstashconfig/file_test.go | 28 ++++++++++++++++++--- internal/daemon/pipeline/pipeline.go | 11 +++++++- 5 files changed, 62 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b2078bf..c95c495 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/magnusbaeck/logstash-filter-verifier/v2 -go 1.13 +go 1.16 require ( github.com/Masterminds/semver/v3 v3.0.1 github.com/ahmetb/govvv v0.3.0 github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 - github.com/breml/logstash-config v0.4.0 + github.com/breml/logstash-config v0.4.1 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 diff --git a/go.sum b/go.sum index 9064e9a..2f80f23 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/breml/logstash-config v0.4.0 h1:KHzk5ePORMYD/tMQzhr3oPe7LbJjF9TRaquMV7keGvQ= -github.com/breml/logstash-config v0.4.0/go.mod h1:Opa416n2AzUie2KU8MsVze1CoLpbqdqt6sVqZjgoa9k= +github.com/breml/logstash-config v0.4.1 h1:0zJ7UUkxnFV/RxT3+2YSEmZ9mReupz6JmpYbyo0MvxI= +github.com/breml/logstash-config v0.4.1/go.mod h1:NaBkWLM71LaEUF/VoCAHMcQf0nAnOcPaaiRKKoRgPN0= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -329,6 +329,8 @@ github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-cli v0.0.0-20180921094345-7b12c9699d70/go.mod h1:XjlXWPd6VONhsRSEuzGkV8mzRpH7ou1cdLV7IKJk96s= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -541,8 +543,9 @@ google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index e4b2abd..fc9d94e 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -144,32 +144,38 @@ func (o *outputPipelineReplacer) walk(c *astutil.Cursor) { c.Replace(ast.NewPlugin("pipeline", ast.NewArrayAttribute("send_to", ast.NewStringAttribute("", outputName, ast.DoubleQuoted)))) } -func (f *File) Validate() error { - err := f.parse() +func (f *File) Validate() (inputs int, outputs int, err error) { + err = f.parse() if err != nil { - return err + return 0, 0, err } v := validator{} for i := range f.config.Input { + v.pluginType = ast.Input astutil.ApplyPlugins(f.config.Input[i].BranchOrPlugins, v.walk) } for i := range f.config.Filter { + v.pluginType = ast.Filter astutil.ApplyPlugins(f.config.Filter[i].BranchOrPlugins, v.walk) } for i := range f.config.Output { + v.pluginType = ast.Output astutil.ApplyPlugins(f.config.Output[i].BranchOrPlugins, v.walk) } if len(v.noIDs) > 0 { - return errors.Errorf("%q no IDs found for %v", f.Name, v.noIDs) + return 0, 0, errors.Errorf("%q no IDs found for %v", f.Name, v.noIDs) } - return nil + return v.inputs, v.outputs, nil } type validator struct { - noIDs []string + noIDs []string + pluginType ast.PluginType + inputs int + outputs int } func (v *validator) walk(c *astutil.Cursor) { @@ -177,4 +183,11 @@ func (v *validator) walk(c *astutil.Cursor) { if err != nil { v.noIDs = append(v.noIDs, c.Plugin().Name()) } + + if v.pluginType == ast.Input && c.Plugin().Name() != "pipeline" { + v.inputs++ + } + if v.pluginType == ast.Output && c.Plugin().Name() != "pipeline" { + v.outputs++ + } } diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index 38f2e7b..ba1e6ec 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -104,7 +104,9 @@ func TestReplaceOutputs(t *testing.T) { wantOutputs: []string{"testid"}, wantConfig: `output { pipeline { - send_to => [ "lfv_output_testid" ] + send_to => [ + "lfv_output_testid" + ] } } `, @@ -177,13 +179,18 @@ func TestValidate(t *testing.T) { name string config string - wantErr error + wantErr error + wantInputs int + wantOutputs int }{ { name: "successful validate", config: `input { stdin { id => stdin } } filter { mutate { id => mutate } } output { stdout { id => testid } }`, + + wantInputs: 1, + wantOutputs: 1, }, { name: "error without ids", @@ -193,6 +200,15 @@ output { stdout { } }`, wantErr: errors.Errorf(`"filename.conf" no IDs found for [stdin mutate stdout]`), }, + { + name: "successful validate - with pipeline input and output", + config: `input { stdin { id => stdin } file { id => file } pipeline { id => pipeline } } +filter { mutate { id => mutate } } +output { stdout { id => testid } file { id => file } pipeline { id => pipeline } }`, + + wantInputs: 2, + wantOutputs: 2, + }, } for _, test := range cases { @@ -202,8 +218,14 @@ output { stdout { } }`, Body: []byte(test.config), } - err := f.Validate() + inputs, outputs, err := f.Validate() compareErr(t, test.wantErr, err) + if test.wantInputs != inputs { + t.Errorf("expected %d inputs, got %d", test.wantInputs, inputs) + } + if test.wantOutputs != outputs { + t.Errorf("expected %d outputs, got %d", test.wantOutputs, outputs) + } }) } } diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 163f5a6..259d703 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/bmatcuk/doublestar/v2" + "github.com/pkg/errors" "gopkg.in/yaml.v2" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" @@ -51,6 +52,7 @@ func New(file, basePath string) (Archive, error) { } func (a Archive) Validate() error { + var inputs, outputs int for _, pipeline := range a.Pipelines { files, err := doublestar.Glob(path.Join(a.BasePath, pipeline.Config)) if err != nil { @@ -78,12 +80,19 @@ func (a Archive) Validate() error { Body: body, } - err = configFile.Validate() + in, out, err := configFile.Validate() if err != nil { return err } + inputs += in + outputs += out } } + + if inputs == 0 || outputs == 0 { + return errors.Errorf("expect the Logstash config to have at least 1 input and 1 output, got %d inputs and %d outputs", inputs, outputs) + } + return nil } From 0b5554f7f56a53894993b73a3eb30b45957de4dc Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 2 Apr 2021 19:48:08 +0200 Subject: [PATCH 049/143] Add more integration tests with different codecs --- integration_test.go | 11 +++ testdata/codec_optional_test.yml | 2 + .../codec_optional_test.conf | 22 ++++++ testdata/codec_test/codec_test.conf | 76 +++++++++++++++++-- .../testcases/codec_optional_test/csv.json | 38 ++++++++++ testdata/testcases/codec_test/cef.json | 29 +++++++ testdata/testcases/codec_test/edn.json | 21 +++++ testdata/testcases/codec_test/edn_lines.json | 40 ++++++++++ testdata/testcases/codec_test/graphite.json | 25 ++++++ testdata/testcases/codec_test/json.json | 33 ++++++++ testdata/testcases/codec_test/json_lines.json | 40 ++++++++++ testdata/testcases/codec_test/multiline.json | 25 ++++++ 12 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 testdata/codec_optional_test.yml create mode 100644 testdata/codec_optional_test/codec_optional_test.conf create mode 100644 testdata/testcases/codec_optional_test/csv.json create mode 100644 testdata/testcases/codec_test/cef.json create mode 100644 testdata/testcases/codec_test/edn.json create mode 100644 testdata/testcases/codec_test/edn_lines.json create mode 100644 testdata/testcases/codec_test/graphite.json create mode 100644 testdata/testcases/codec_test/json.json create mode 100644 testdata/testcases/codec_test/json_lines.json create mode 100644 testdata/testcases/codec_test/multiline.json diff --git a/integration_test.go b/integration_test.go index 5e8b6d9..42038f4 100644 --- a/integration_test.go +++ b/integration_test.go @@ -77,6 +77,10 @@ func TestIntegration(t *testing.T) { cases := []struct { name string debug bool + + // optional integration tests require additional logstash plugins, + // which are not provided by a default installation. + optional bool }{ { name: "basic_pipeline", @@ -94,10 +98,17 @@ func TestIntegration(t *testing.T) { { name: "codec_test", }, + { + name: "codec_optional_test", + optional: true, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + if tc.optional && os.Getenv("INTEGRATION_TEST_OPTIONAL") != "1" { + t.Skipf("optional integration test %q skipped, enable with env var `INTEGRATION_TEST_OPTIONAL=1`", tc.name) + } client, err := run.New( path.Join(tempdir, "integration_test.socket"), log, diff --git a/testdata/codec_optional_test.yml b/testdata/codec_optional_test.yml new file mode 100644 index 0000000..70e9f86 --- /dev/null +++ b/testdata/codec_optional_test.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "codec_optional_test.conf" diff --git a/testdata/codec_optional_test/codec_optional_test.conf b/testdata/codec_optional_test/codec_optional_test.conf new file mode 100644 index 0000000..e123cd8 --- /dev/null +++ b/testdata/codec_optional_test/codec_optional_test.conf @@ -0,0 +1,22 @@ +input { + stdin { + id => csv + codec => csv { + columns => [ "str_value", "int_value", "bool_value" ] + convert => { + "int_value" => "integer" + "bool_value" => "boolean" + } + } + add_field => { + "input_codec" => "csv" + } + tags => [ "input_codec_csv" ] + } +} + +output { + stdout { + id => stdout + } +} diff --git a/testdata/codec_test/codec_test.conf b/testdata/codec_test/codec_test.conf index a73270d..e088b50 100644 --- a/testdata/codec_test/codec_test.conf +++ b/testdata/codec_test/codec_test.conf @@ -1,15 +1,58 @@ input { stdin { - id => plain - codec => plain + id => cef + codec => cef add_field => { - "input_codec" => "plain" + "input_codec" => "cef" } - tags => [ "input_codec_plain" ] + tags => [ "input_codec_cef" ] + } + + stdin { + id => edn + codec => edn + add_field => { + "input_codec" => "edn" + } + tags => [ "input_codec_edn" ] + } + + stdin { + id => edn_lines + codec => edn_lines + add_field => { + "input_codec" => "edn_lines" + } + tags => [ "input_codec_edn_lines" ] + } + + stdin { + id => graphite + codec => graphite + add_field => { + "input_codec" => "graphite" + } + tags => [ "input_codec_graphite" ] + } + + stdin { + id => json + codec => json + add_field => { + "input_codec" => "json" + } + tags => [ "input_codec_json" ] + } + + stdin { + id => json_lines + codec => json_lines + add_field => { + "input_codec" => "json_lines" + } + tags => [ "input_codec_json_lines" ] } -} -input { stdin { id => line codec => line @@ -18,6 +61,27 @@ input { } tags => [ "input_codec_line" ] } + + stdin { + id => multiline + codec => multiline { + pattern => "^\s" + what => "previous" + } + add_field => { + "input_codec" => "multiline" + } + tags => [ "input_codec_multiline" ] + } + + stdin { + id => plain + codec => plain + add_field => { + "input_codec" => "plain" + } + tags => [ "input_codec_plain" ] + } } output { diff --git a/testdata/testcases/codec_optional_test/csv.json b/testdata/testcases/codec_optional_test/csv.json new file mode 100644 index 0000000..7561cb8 --- /dev/null +++ b/testdata/testcases/codec_optional_test/csv.json @@ -0,0 +1,38 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "csv", + "testcases": [ + { + "input": [ + "foo,0,false", + "bar,1,true", + "baz,2,false" + ], + "expected": [ + { + "bool_value": false, + "input_codec": "csv", + "int_value": 0, + "str_value": "foo", + "tags": [ "input_codec_csv" ] + }, + { + "bool_value": true, + "input_codec": "csv", + "int_value": 1, + "str_value": "bar", + "tags": [ "input_codec_csv" ] + }, + { + "bool_value": false, + "input_codec": "csv", + "int_value": 2, + "str_value": "baz", + "tags": [ "input_codec_csv" ] + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/cef.json b/testdata/testcases/codec_test/cef.json new file mode 100644 index 0000000..9fec8b9 --- /dev/null +++ b/testdata/testcases/codec_test/cef.json @@ -0,0 +1,29 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "cef", + "testcases": [ + { + "input": [ + "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" + ], + "expected": [ + { + "cefVersion": "0", + "destinationAddress": "12.121.122.82", + "deviceEventClassId": "100", + "deviceProduct": "threatmanager", + "deviceVendor": "security", + "deviceVersion": "1.0", + "input_codec": "cef", + "name": "trojan successfully stopped", + "severity": "10", + "sourceAddress": "10.0.0.192", + "sourcePort": "1232", + "tags": [ "input_codec_cef" ] + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/edn.json b/testdata/testcases/codec_test/edn.json new file mode 100644 index 0000000..5d4d3ee --- /dev/null +++ b/testdata/testcases/codec_test/edn.json @@ -0,0 +1,21 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "edn", + "testcases": [ + { + "input": [ + "{:test \"foo\", :value 0}" + ], + "expected": [ + { + "input_codec": "edn", + "test": "foo", + "tags": [ "input_codec_edn" ], + "value": 0 + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/edn_lines.json b/testdata/testcases/codec_test/edn_lines.json new file mode 100644 index 0000000..da28832 --- /dev/null +++ b/testdata/testcases/codec_test/edn_lines.json @@ -0,0 +1,40 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "edn_lines", + "testcases": [ + { + "input": [ + "{:test \"foo\", :value 0}\n{:test \"bar\", :value 1}\n{:test \"baz\", :value 2}\n", + "{:test \"foo2\", :value 3}\n" + ], + "expected": [ + { + "input_codec": "edn_lines", + "test": "foo", + "tags": [ "input_codec_edn_lines" ], + "value": 0 + }, + { + "input_codec": "edn_lines", + "test": "bar", + "tags": [ "input_codec_edn_lines" ], + "value": 1 + }, + { + "input_codec": "edn_lines", + "test": "baz", + "tags": [ "input_codec_edn_lines" ], + "value": 2 + }, + { + "input_codec": "edn_lines", + "test": "foo2", + "tags": [ "input_codec_edn_lines" ], + "value": 3 + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/graphite.json b/testdata/testcases/codec_test/graphite.json new file mode 100644 index 0000000..2ce7b37 --- /dev/null +++ b/testdata/testcases/codec_test/graphite.json @@ -0,0 +1,25 @@ +{ + "input_plugin": "graphite", + "testcases": [ + { + "input": [ + "foo.bar.metric1 100 1617385070\n", + "foo.bar.metric2 200 1617385070\n" + ], + "expected": [ + { + "@timestamp": "2021-04-02T17:37:50.000Z", + "foo.bar.metric1": 100, + "input_codec": "graphite", + "tags": [ "input_codec_graphite" ] + }, + { + "@timestamp": "2021-04-02T17:37:50.000Z", + "foo.bar.metric2": 200, + "input_codec": "graphite", + "tags": [ "input_codec_graphite" ] + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/json.json b/testdata/testcases/codec_test/json.json new file mode 100644 index 0000000..838aff9 --- /dev/null +++ b/testdata/testcases/codec_test/json.json @@ -0,0 +1,33 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "json", + "testcases": [ + { + "input": [ + "[ {\"test\": \"foo\", \"value\": 0},{\"test\": \"bar\", \"value\": 1},{\"test\": \"baz\", \"value\": 2} ]" + ], + "expected": [ + { + "input_codec": "json", + "test": "foo", + "tags": [ "input_codec_json" ], + "value": 0 + }, + { + "input_codec": "json", + "test": "bar", + "tags": [ "input_codec_json" ], + "value": 1 + }, + { + "input_codec": "json", + "test": "baz", + "tags": [ "input_codec_json" ], + "value": 2 + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/json_lines.json b/testdata/testcases/codec_test/json_lines.json new file mode 100644 index 0000000..d6c1cda --- /dev/null +++ b/testdata/testcases/codec_test/json_lines.json @@ -0,0 +1,40 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "json_lines", + "testcases": [ + { + "input": [ + "{\"test\": \"foo\", \"value\": 0}\n{\"test\": \"bar\", \"value\": 1}\n{\"test\": \"baz\", \"value\": 2}\n", + "{\"test\": \"foo2\", \"value\": 3}" + ], + "expected": [ + { + "input_codec": "json_lines", + "test": "foo", + "tags": [ "input_codec_json_lines" ], + "value": 0 + }, + { + "input_codec": "json_lines", + "test": "bar", + "tags": [ "input_codec_json_lines" ], + "value": 1 + }, + { + "input_codec": "json_lines", + "test": "baz", + "tags": [ "input_codec_json_lines" ], + "value": 2 + }, + { + "input_codec": "json_lines", + "test": "foo2", + "tags": [ "input_codec_json_lines" ], + "value": 3 + } + ] + } + ] +} diff --git a/testdata/testcases/codec_test/multiline.json b/testdata/testcases/codec_test/multiline.json new file mode 100644 index 0000000..b77b6f3 --- /dev/null +++ b/testdata/testcases/codec_test/multiline.json @@ -0,0 +1,25 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "multiline", + "testcases": [ + { + "input": [ + "foo\n bar\n baz\nother foo\n bar\n baz" + ], + "expected": [ + { + "input_codec": "multiline", + "message": "foo\n bar\n baz", + "tags": [ "multiline", "input_codec_multiline" ] + }, + { + "input_codec": "multiline", + "message": "other foo\n bar\n baz", + "tags": [ "multiline", "input_codec_multiline" ] + } + ] + } + ] +} From f0c82ba952bc2a054a81cb89041b2d74936a378f Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 2 Apr 2021 20:04:00 +0200 Subject: [PATCH 050/143] Better control for debug log output in integration tests --- integration_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/integration_test.go b/integration_test.go index 42038f4..e9bace5 100644 --- a/integration_test.go +++ b/integration_test.go @@ -24,8 +24,8 @@ func TestIntegration(t *testing.T) { is := is.New(t) testLogger := &logging.LoggerMock{ - DebugFunc: func(args ...interface{}) { t.Log(args...) }, - DebugfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, + DebugFunc: func(args ...interface{}) {}, + DebugfFunc: func(format string, args ...interface{}) {}, ErrorFunc: func(args ...interface{}) { t.Log(args...) }, ErrorfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, FatalFunc: func(args ...interface{}) { t.Log(args...) }, @@ -35,6 +35,13 @@ func TestIntegration(t *testing.T) { WarningFunc: func(args ...interface{}) { t.Log(args...) }, WarningfFunc: func(format string, args ...interface{}) { t.Logf(format, args...) }, } + logging.SetLevel("INFO") + + if os.Getenv("INTEGRATION_TEST_DEBUG") == "1" { + testLogger.DebugFunc = func(args ...interface{}) { t.Log(args...) } + testLogger.DebugfFunc = func(format string, args ...interface{}) { t.Logf(format, args...) } + logging.SetLevel("DEBUG") + } tempdir := t.TempDir() // Start Daemon From 942364524b4348069ef396e10f883aaa5ff10e56 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 2 Apr 2021 21:18:35 +0200 Subject: [PATCH 051/143] Fix wrong number of expected events for integration tests --- internal/daemon/session/files.go | 7 +++---- testdata/testcases/codec_test/edn_lines.json | 4 +++- testdata/testcases/codec_test/json.json | 4 +++- testdata/testcases/codec_test/json_lines.json | 4 +++- testdata/testcases/codec_test/line.json | 3 ++- testdata/testcases/codec_test/multiline.json | 3 ++- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index 5a39ac3..77c990e 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -53,15 +53,14 @@ filter { destination => "[@metadata][__lfv_fields]" exact => true override => true - # TODO: Add default value (e.g. "__lfv_fields_not_found"), if not found in dictionary + fallback => "__lfv_fields_not_found" } ruby { id => '__lfv_ruby_fields' - # TODO: If default value ("__lfv_fields_not_found"), then skip this ruby - # code and add an tag instead code => 'fields = event.get("[@metadata][__lfv_fields]") - fields.each { |key, value| event.set(key, value) }' + fields.each { |key, value| event.set(key, value) } unless fields == "__lfv_fields_not_found" + event.tag("lfv_fields_not_found") if fields == "__lfv_fields_not_found"' tag_on_exception => '__lfv_ruby_fields_exception' } } diff --git a/testdata/testcases/codec_test/edn_lines.json b/testdata/testcases/codec_test/edn_lines.json index da28832..9b505a8 100644 --- a/testdata/testcases/codec_test/edn_lines.json +++ b/testdata/testcases/codec_test/edn_lines.json @@ -6,7 +6,9 @@ "testcases": [ { "input": [ - "{:test \"foo\", :value 0}\n{:test \"bar\", :value 1}\n{:test \"baz\", :value 2}\n", + "{:test \"foo\", :value 0}\n", + "{:test \"bar\", :value 1}\n", + "{:test \"baz\", :value 2}\n", "{:test \"foo2\", :value 3}\n" ], "expected": [ diff --git a/testdata/testcases/codec_test/json.json b/testdata/testcases/codec_test/json.json index 838aff9..ea87f15 100644 --- a/testdata/testcases/codec_test/json.json +++ b/testdata/testcases/codec_test/json.json @@ -6,7 +6,9 @@ "testcases": [ { "input": [ - "[ {\"test\": \"foo\", \"value\": 0},{\"test\": \"bar\", \"value\": 1},{\"test\": \"baz\", \"value\": 2} ]" + "[ {\"test\": \"foo\", \"value\": 0},{\"test\": \"bar\", \"value\": 1},{\"test\": \"baz\", \"value\": 2} ]", + "", + "" ], "expected": [ { diff --git a/testdata/testcases/codec_test/json_lines.json b/testdata/testcases/codec_test/json_lines.json index d6c1cda..99e0a42 100644 --- a/testdata/testcases/codec_test/json_lines.json +++ b/testdata/testcases/codec_test/json_lines.json @@ -6,7 +6,9 @@ "testcases": [ { "input": [ - "{\"test\": \"foo\", \"value\": 0}\n{\"test\": \"bar\", \"value\": 1}\n{\"test\": \"baz\", \"value\": 2}\n", + "{\"test\": \"foo\", \"value\": 0}\n", + "{\"test\": \"bar\", \"value\": 1}\n", + "{\"test\": \"baz\", \"value\": 2}\n", "{\"test\": \"foo2\", \"value\": 3}" ], "expected": [ diff --git a/testdata/testcases/codec_test/line.json b/testdata/testcases/codec_test/line.json index 6d2141e..16a6a36 100644 --- a/testdata/testcases/codec_test/line.json +++ b/testdata/testcases/codec_test/line.json @@ -6,7 +6,8 @@ "testcases": [ { "input": [ - "test case message\ntest case message 2" + "test case message\n", + "test case message 2\n" ], "expected": [ { diff --git a/testdata/testcases/codec_test/multiline.json b/testdata/testcases/codec_test/multiline.json index b77b6f3..0b49bc1 100644 --- a/testdata/testcases/codec_test/multiline.json +++ b/testdata/testcases/codec_test/multiline.json @@ -6,7 +6,8 @@ "testcases": [ { "input": [ - "foo\n bar\n baz\nother foo\n bar\n baz" + "foo\n bar\n baz\n", + "other foo\n bar\n baz\n" ], "expected": [ { From b2285afbe74a413948d096b3c8ddb1cd0fb4e886 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 2 Apr 2021 21:19:27 +0200 Subject: [PATCH 052/143] Remove unused field --- internal/app/daemon/daemon.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 7242414..42e104a 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -45,8 +45,6 @@ type Daemon struct { logstashPath string keptEnvVars []string - metadataKey string - tempdir string inflightShutdownTimeout time.Duration From 9aa1db80a61a08bf8181684f3364ba1ce44c102f Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 2 Apr 2021 21:35:54 +0200 Subject: [PATCH 053/143] Fail integration tests, if logstash tests fail --- integration_test.go | 2 ++ internal/app/daemon/run/run.go | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/integration_test.go b/integration_test.go index e9bace5..8642b1f 100644 --- a/integration_test.go +++ b/integration_test.go @@ -113,6 +113,8 @@ func TestIntegration(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + is := is.New(t) + if tc.optional && os.Getenv("INTEGRATION_TEST_OPTIONAL") != "1" { t.Skipf("optional integration test %q skipped, enable with env var `INTEGRATION_TEST_OPTIONAL=1`", tc.name) } diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index fed2e37..d15729c 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,6 +3,7 @@ package run import ( "context" "encoding/json" + "errors" "net" "os" "path" @@ -108,6 +109,7 @@ func (s Test) Run() error { } } + testsPassed := true for _, t := range tests { b, err := json.Marshal(t.Events) if err != nil { @@ -138,10 +140,13 @@ func (s Test) Run() error { events = append(events, event) } - _, err = t.Compare(events, []string{"diff", "-u"}, liveObserver) + ok, err := t.Compare(events, []string{"diff", "-u"}, liveObserver) if err != nil { return err } + if !ok { + testsPassed = false + } } _, err = c.TeardownTest(context.Background(), &pb.TeardownTestRequest{ @@ -160,6 +165,10 @@ func (s Test) Run() error { } } + if !testsPassed { + return errors.New("failed test cases") + } + return nil } From 4dc7a55c11a9f755b3597f4d00cf065b2fe9d5e0 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 6 Apr 2021 21:40:01 +0200 Subject: [PATCH 054/143] Address review feedback --- internal/daemon/logstashconfig/file.go | 2 -- internal/testcase/testcase.go | 2 +- testdata/testcases/basic_pipeline_debug/testcase2.json | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index fc9d94e..45fdf0d 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -102,8 +102,6 @@ func (r replaceInputsWalker) replaceInputs(c *astutil.Cursor) { } } - // TODO: __lfv_input must reflect the actual input, that has been replaced, such that this input - // can be referenced in the test case configuration. c.Replace(ast.NewPlugin("pipeline", attrs...)) } diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 1136ffc..7d97baf 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -78,7 +78,7 @@ type TestCaseSet struct { // `[@metadata]` in the Logstash event. // If the metadata is exported, the respective fields are compared // with the expected result of the testcase as well. (default: false) - ExportMetadata bool `json:"exportMetadata" yaml:"exportMetadata"` + ExportMetadata bool `json:"export_metadata" yaml:"export_metadata"` // TestCases is a slice of test cases, which include at minimum // a pair of an input and an expected event diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index cc72c3c..062c1d2 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -7,7 +7,7 @@ "@timestamp" ], "input_plugin": "stdin", - "exportMetadata": true, + "export_metadata": true, "testcases": [ { "input": [ From 34644a1be11f2acf71b283c71bb68f6dc4449373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Wed, 7 Apr 2021 22:10:40 +0200 Subject: [PATCH 055/143] Makefile: Add missing dependencies for generate goal --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 14e8ecc..38ef90b 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ $(PROGRAM)$(EXEC_SUFFIX): gogenerate .FORCE $(GOVVV) govvv build -o $@ .PHONY: gogenerate -gogenerate: $(MOQ) +gogenerate: $(MOQ) $(PROTOC_GEN_GO) $(PROTOC_GEN_GO_GRPC) go generate ./... .PHONY: check From b0daeba9e97886762617410b5672e3324e846c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Wed, 7 Apr 2021 22:45:10 +0200 Subject: [PATCH 056/143] Makefile: Install deps with "go install" instead of "go get" Using "go get" to install a build dependency also upgrades the module in question, rendering the tools.go file that we use to lock down tool dependencies rather useless. --- Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 38ef90b..77da14c 100644 --- a/Makefile +++ b/Makefile @@ -53,10 +53,10 @@ all: $(PROGRAM)$(EXEC_SUFFIX) .FORCE: $(GOCOV): - go get github.com/axw/gocov/gocov + go install github.com/axw/gocov/gocov $(GOCOV_HTML): - go get github.com/matm/gocov-html + go install github.com/matm/gocov-html $(GOLANGCI_LINT): curl --silent --show-error --location \ @@ -64,19 +64,19 @@ $(GOLANGCI_LINT): | sh -s -- -b $(dir $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) $(GOVVV): - go get github.com/ahmetb/govvv + go install github.com/ahmetb/govvv $(OVERALLS): - go get github.com/go-playground/overalls + go install github.com/go-playground/overalls $(PROTOC_GEN_GO): - go get google.golang.org/protobuf/cmd/protoc-gen-go + go install google.golang.org/protobuf/cmd/protoc-gen-go $(PROTOC_GEN_GO_GRPC): - go get google.golang.org/grpc/cmd/protoc-gen-go-grpc + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc $(MOQ): - go get github.com/matryer/moq + go install github.com/matryer/moq # The Go compiler is fast and pretty good about figuring out what to # build so we don't try to to outsmart it. From 3fa517c4e25fd73862231465801f39c2f672d484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Wed, 7 Apr 2021 21:36:40 +0200 Subject: [PATCH 057/143] Replace Travis CI with GitHub Actions We replace the previous Travis CI configuration with a single GitHub Actions workflow that's triggered on pushes and PRs on all branches. The new workflow is equivalent to the old configuration except that it also installs the newly introduced protobuf-compiler dependency. --- .github/workflows/test.yml | 32 ++++++++++++++++++++++++++++++++ .travis.yml | 6 ------ README.md | 2 +- 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b2ddd06 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,32 @@ +name: test + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + run-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: "1.15.5" + + - name: Install APT-based build dependencies + run: > + sudo apt-get update && + sudo apt-get install make protobuf-compiler + + - name: Run static analysis + run: make check checktidy + + - name: Run tests + run: make test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 31c1546..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -language: go -go: - - "1.15.5" -script: - - make check checktidy test diff --git a/README.md b/README.md index 8610a07..7bc5689 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Logstash Filter Verifier -[![Travis](https://travis-ci.org/magnusbaeck/logstash-filter-verifier.svg?branch=master)](https://travis-ci.org/magnusbaeck/logstash-filter-verifier) +![build](https://github.com/magnusbaeck/logstash-filter-verifier/actions/workflows/test.yml/badge.svg?event=push) [![GoReportCard](http://goreportcard.com/badge/magnusbaeck/logstash-filter-verifier)](http://goreportcard.com/report/magnusbaeck/logstash-filter-verifier) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/magnusbaeck/logstash-filter-verifier/master/LICENSE) From 3544594c9de38f806e59831deaed62d33cab7e9c Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sat, 3 Apr 2021 13:38:07 +0200 Subject: [PATCH 058/143] Add download tool for Logstash --- go.mod | 9 +++ go.sum | 50 +++++++++++++ internal/app/app.go | 1 + internal/app/setup.go | 73 +++++++++++++++++++ internal/app/setup/setup.go | 137 ++++++++++++++++++++++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 internal/app/setup.go create mode 100644 internal/app/setup/setup.go diff --git a/go.mod b/go.mod index c95c495..538d44b 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/breml/logstash-config v0.4.1 + github.com/cheggaaa/pb v1.0.29 // indirect github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 @@ -18,6 +19,7 @@ require ( github.com/matryer/is v1.4.0 github.com/matryer/moq v0.2.1 github.com/mattn/go-shellwords v1.0.6 + github.com/mholt/archiver/v3 v3.5.0 // indirect github.com/mikefarah/yaml/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pkg/errors v0.9.1 @@ -27,10 +29,17 @@ require ( github.com/tidwall/gjson v1.6.8 github.com/tidwall/sjson v1.1.5 github.com/yookoala/realpath v1.0.0 // indirect + golang.org/x/mod v0.4.2 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect google.golang.org/grpc v1.34.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 google.golang.org/protobuf v1.25.0 + gopkg.in/VividCortex/ewma.v1 v1.1.1 // indirect + gopkg.in/cheggaaa/pb.v2 v2.0.7 // indirect + gopkg.in/fatih/color.v1 v1.7.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/mattn/go-colorable.v0 v0.1.0 // indirect + gopkg.in/mattn/go-isatty.v0 v0.0.4 // indirect + gopkg.in/mattn/go-runewidth.v0 v0.0.4 // indirect gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 2f80f23..94dff9b 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/antchfx/htmlquery v1.0.0/go.mod h1:MS9yksVSQXls00iXkiMqXr0J+umL/AmxXKuP28SUJM8= github.com/antchfx/xmlquery v1.0.0/go.mod h1:/+CnyD/DzHRnv2eRxrVbieRU/FIF6N0C+7oTtyUtCKk= @@ -65,6 +67,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= +github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -87,6 +91,9 @@ github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8/go.mod h1:/Yn github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/dnaeon/go-vcr v1.0.0/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/docker v0.0.0-20180422163414-57142e89befe/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY= @@ -97,6 +104,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -134,6 +142,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -224,9 +234,15 @@ github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= +github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -251,13 +267,21 @@ github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwM github.com/matryer/moq v0.2.1 h1:4roNlIsfEXb7127O3v558H+9jmV70G2FAJPYIZX84lQ= github.com/matryer/moq v0.2.1/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= +github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.1/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mikefarah/yaml/v2 v2.4.0 h1:eYqfooY0BnvKTJxr7+ABJs13n3dg9n347GScDaU2Lww= @@ -285,6 +309,8 @@ github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JG github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -299,7 +325,10 @@ github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a/go.mod h1 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E= +github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -381,8 +410,13 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c= github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0/go.mod h1:sBh287mCRwCz6zyXHMmw7sSZGPohVpnx+o+OY4M+i3A= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854= @@ -423,6 +457,8 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -459,6 +495,7 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -466,6 +503,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -541,13 +579,19 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/VividCortex/ewma.v1 v1.1.1 h1:tWHEKkKq802K/JT9RiqGCBU5fW3raAPnJGTE9ostZvg= +gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v2 v2.0.7 h1:beaAg8eacCdMQS9Y7obFEtkY7gQl0uZ6Zayb3ry41VY= +gopkg.in/cheggaaa/pb.v2 v2.0.7/go.mod h1:0CiZ1p8pvtxBlQpLXkHuUTpdJ1shm3OqCF1QugkjHL4= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fatih/color.v1 v1.7.0 h1:bYGjb+HezBM6j/QmgBfgm1adxHpzzrss6bj4r9ROppk= +gopkg.in/fatih/color.v1 v1.7.0/go.mod h1:P7yosIhqIl/sX8J8UypY5M+dDpD2KmyfP5IRs5v/fo0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= @@ -557,6 +601,12 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw= +gopkg.in/mattn/go-colorable.v0 v0.1.0 h1:WYuADWvfvYC07fm8ygYB3LMcsc5CunpxfMGKawHkAos= +gopkg.in/mattn/go-colorable.v0 v0.1.0/go.mod h1:BVJlBXzARQxdi3nZo6f6bnl5yR20/tOL6p+V0KejgSY= +gopkg.in/mattn/go-isatty.v0 v0.0.4 h1:NtS1rQGQr4IaFWBGz4Cz4BhB///gyys4gDVtKA7hIsc= +gopkg.in/mattn/go-isatty.v0 v0.0.4/go.mod h1:wt691ab7g0X4ilKZNmMII3egK0bTxl37fEn/Fwbd8gc= +gopkg.in/mattn/go-runewidth.v0 v0.0.4 h1:r0P71TnzQDlNIcizCqvPSSANoFa3WVGtcNJf3TWurcY= +gopkg.in/mattn/go-runewidth.v0 v0.0.4/go.mod h1:BmXejnxvhwdaATwiJbB1vZ2dtXkQKZGu9yLFCZb4msQ= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/internal/app/app.go b/internal/app/app.go index 81d3656..123f6cf 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -79,6 +79,7 @@ func makeRootCmd(version string) *cobra.Command { rootCmd.AddCommand(makeStandaloneCmd()) rootCmd.AddCommand(makeDaemonCmd()) + rootCmd.AddCommand(makeSetupCmd()) return rootCmd } diff --git a/internal/app/setup.go b/internal/app/setup.go new file mode 100644 index 0000000..68b3934 --- /dev/null +++ b/internal/app/setup.go @@ -0,0 +1,73 @@ +package app + +import ( + "errors" + "fmt" + "runtime" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/mod/semver" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/setup" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +func makeSetupCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "setup [] ", + Short: "Setup the given version of Logstash for usage with logstash-filter-verifier", + RunE: runSetup, + Args: validateSetupArgs, + } + + cmd.Flags().StringP("target-dir", "t", "./3rdparty", "target directory, where Logstash is downloaded and unarchived to") + _ = viper.BindPFlag("target-dir", cmd.Flags().Lookup("target-dir")) + + cmd.Flags().Bool("oss", false, "setup oss release of Logstash") + _ = viper.BindPFlag("oss", cmd.Flags().Lookup("oss")) + + cmd.Flags().String("os-arch", osArch(), "os and arch string for the Logstash version to be downloaded, e.g. linux-x86_64") + _ = viper.BindPFlag("os-arch", cmd.Flags().Lookup("os-arch")) + + cmd.Flags().String("archive-type", "tar.gz", "archive type to be downloaded, e.g. tar.gz or zip") + _ = viper.BindPFlag("archive-type", cmd.Flags().Lookup("archive-type")) + + return cmd +} + +func runSetup(_ *cobra.Command, args []string) error { + s := setup.New( + args[0], + viper.GetString("target-dir"), + viper.GetBool("oss"), + viper.GetString("os-arch"), + viper.GetString("archive-type"), + viper.Get("logger").(logging.Logger), + ) + + return s.Run() +} + +func validateSetupArgs(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("required argument 'logstash-version' not provided, try --help") + } + + if !semver.IsValid("v" + args[0]) { + return errors.New("invalid version string provided, correct format x.y.z, e.g. 7.12.0") + } + return nil +} + +func osArch() string { + var arch string + switch runtime.GOARCH { + case "amd64": + arch = "x86_64" + case "arm64": + arch = "aarch64" + } + + return fmt.Sprintf("%s-%s", runtime.GOOS, arch) +} diff --git a/internal/app/setup/setup.go b/internal/app/setup/setup.go new file mode 100644 index 0000000..1af725c --- /dev/null +++ b/internal/app/setup/setup.go @@ -0,0 +1,137 @@ +package setup + +import ( + "fmt" + "io" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "github.com/mholt/archiver/v3" + "github.com/pkg/errors" + "golang.org/x/mod/semver" + "gopkg.in/cheggaaa/pb.v2" + + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" +) + +type Setup struct { + version string + targetDir string + oss bool + osArch string + archiveType string + log logging.Logger +} + +// New creates a new setup command to install versions of Logstash. +func New(version string, targetDir string, oss bool, osArch string, archiveType string, log logging.Logger) Setup { + return Setup{ + version: version, + targetDir: targetDir, + oss: oss, + osArch: osArch, + archiveType: archiveType, + log: log, + } +} + +const firstOSSpecificRelease = "v7.10.0" + +// Setup installs versions of Logstash. +func (s *Setup) Run() error { + downloadDir := filepath.Join(s.targetDir, "/downloads") + + err := os.MkdirAll(downloadDir, 0775) + if err != nil { + return errors.Wrap(err, "failed to create base directory for Logstash setup") + } + + oss := "" + if s.oss { + oss = "oss-" + } + filename := fmt.Sprintf("logstash-%s%s-%s.%s", oss, s.version, s.osArch, s.archiveType) + targetDirVersion := path.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) + if semver.Compare("v"+s.version, firstOSSpecificRelease) < 0 { + filename = fmt.Sprintf("logstash-%s%s.%s", oss, s.version, s.archiveType) + targetDirVersion = path.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) + } + + targetFile := filepath.Join(downloadDir, filename) + if !fileExists(targetFile) { + s.log.Infof("Download of Logstash version %s (%s)", s.version, s.osArch) + + err = download(filename, targetFile) + if err != nil { + return errors.Wrap(err, "failed to download archive for Logstash version") + } + } + + if !fileExists(targetDirVersion) { + s.log.Infof("Unarchive Logstash to %s", targetDirVersion) + + var u archiver.Unarchiver + + switch { + case strings.HasSuffix(filename, "zip"): + u = &archiver.Zip{ + MkdirAll: true, + OverwriteExisting: true, + StripComponents: 1, + } + case strings.HasSuffix(filename, "tar.gz"): + u = &archiver.TarGz{ + Tar: &archiver.Tar{ + MkdirAll: true, + OverwriteExisting: true, + StripComponents: 1, + }, + } + default: + return errors.New("file suffix not supported for unarchiving") + } + + err = u.Unarchive(targetFile, targetDirVersion) + if err != nil { + return errors.Wrap(err, "failed to unarchive downloaded Logstash archive") + } + } + + s.log.Infof("Done, files available in %s", targetDirVersion) + + return nil +} + +func fileExists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +func download(filename string, targetFile string) error { + res, err := http.Get(fmt.Sprintf("https://artifacts.elastic.co/downloads/logstash/%s", filename)) + if err != nil { + return errors.Wrap(err, "failed to download Logstash release") + } + defer res.Body.Close() + + file, err := os.OpenFile(targetFile, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return errors.Wrap(err, "failed to open target file for downloaded Logstash archive") + } + defer file.Close() + + bar := pb.Full.Start64(res.ContentLength) + barReader := bar.NewProxyReader(res.Body) + defer bar.Finish() + + _, err = io.Copy(file, barReader) + if err != nil { + return errors.Wrap(err, "failed to write target file for downloaded Logstash archive") + } + bar.Finish() + + return nil +} From a2c3fa9abe4d547f2280e181b93944568ce9cc99 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sat, 3 Apr 2021 13:51:12 +0200 Subject: [PATCH 059/143] Use filepath.Join for files for better OS support --- integration_test.go | 8 +++---- internal/app/app.go | 3 +-- internal/app/daemon/run/run.go | 3 ++- internal/app/setup/setup.go | 5 ++-- internal/daemon/controller/controller.go | 8 +++---- internal/daemon/controller/controller_test.go | 24 +++++++++---------- internal/daemon/controller/files.go | 6 ++--- internal/daemon/logstashconfig/file.go | 5 ++-- internal/daemon/logstashconfig/file_test.go | 6 ++--- internal/daemon/pipeline/pipeline.go | 9 +++---- internal/daemon/session/controller_test.go | 16 ++++++------- internal/daemon/session/session.go | 18 +++++++------- 12 files changed, 56 insertions(+), 55 deletions(-) diff --git a/integration_test.go b/integration_test.go index 8642b1f..4c0ba50 100644 --- a/integration_test.go +++ b/integration_test.go @@ -3,7 +3,7 @@ package main_test import ( "context" "os" - "path" + "path/filepath" "testing" "time" @@ -45,8 +45,8 @@ func TestIntegration(t *testing.T) { tempdir := t.TempDir() // Start Daemon - socket := path.Join(tempdir, "integration_test.socket") - logstashPath := path.Join("3rdparty/logstash-7.10.0/bin/logstash") + socket := filepath.Join(tempdir, "integration_test.socket") + logstashPath := filepath.Join("3rdparty/logstash-7.10.0/bin/logstash") if !file.Exists(logstashPath) { t.Fatalf("Logstash needs to be present in %q for the integration tests to work", logstashPath) } @@ -119,7 +119,7 @@ func TestIntegration(t *testing.T) { t.Skipf("optional integration test %q skipped, enable with env var `INTEGRATION_TEST_OPTIONAL=1`", tc.name) } client, err := run.New( - path.Join(tempdir, "integration_test.socket"), + filepath.Join(tempdir, "integration_test.socket"), log, "testdata/"+tc.name+".yml", "testdata/"+tc.name, diff --git a/internal/app/app.go b/internal/app/app.go index 123f6cf..00907da 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "os" - "path" "path/filepath" "strings" @@ -28,7 +27,7 @@ func Execute(version string, stdout, stderr io.Writer) int { viper.AddConfigPath(".") configDir, err := os.UserConfigDir() if err == nil { - viper.AddConfigPath(path.Join(configDir, "logstash-filter-verifier")) + viper.AddConfigPath(filepath.Join(configDir, "logstash-filter-verifier")) } viper.AddConfigPath("/etc/logstash-filter-verifier/") diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index d15729c..28c7b2a 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -7,6 +7,7 @@ import ( "net" "os" "path" + "path/filepath" "sort" "strings" "time" @@ -41,7 +42,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath if err != nil { return Test{}, err } - pipelineBase = path.Join(cwd, pipelineBase) + pipelineBase = filepath.Join(cwd, pipelineBase) } return Test{ socket: socket, diff --git a/internal/app/setup/setup.go b/internal/app/setup/setup.go index 1af725c..51b4463 100644 --- a/internal/app/setup/setup.go +++ b/internal/app/setup/setup.go @@ -5,7 +5,6 @@ import ( "io" "net/http" "os" - "path" "path/filepath" "strings" @@ -54,10 +53,10 @@ func (s *Setup) Run() error { oss = "oss-" } filename := fmt.Sprintf("logstash-%s%s-%s.%s", oss, s.version, s.osArch, s.archiveType) - targetDirVersion := path.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) + targetDirVersion := filepath.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) if semver.Compare("v"+s.version, firstOSSpecificRelease) < 0 { filename = fmt.Sprintf("logstash-%s%s.%s", oss, s.version, s.archiveType) - targetDirVersion = path.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) + targetDirVersion = filepath.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) } targetFile := filepath.Join(downloadDir, filename) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 167cf14..ed4c66d 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -4,7 +4,7 @@ import ( "context" "io/ioutil" "os" - "path" + "path/filepath" "sync" "gopkg.in/yaml.v2" @@ -35,7 +35,7 @@ type Controller struct { func NewController(instance Instance, baseDir string, log logging.Logger) (*Controller, error) { id := idgen.New() - workDir := path.Join(baseDir, LogstashInstanceDirectoryPrefix, id) + workDir := filepath.Join(baseDir, LogstashInstanceDirectoryPrefix, id) templateData := struct { WorkDir string @@ -56,7 +56,7 @@ func NewController(instance Instance, baseDir string, log logging.Logger) (*Cont } for filename, tmpl := range templates { - err = template.ToFile(path.Join(workDir, filename), tmpl, templateData, 0600) + err = template.ToFile(filepath.Join(workDir, filename), tmpl, templateData, 0600) if err != nil { return nil, err } @@ -171,7 +171,7 @@ func (c *Controller) writePipelines(pipelines ...pipeline.Pipeline) error { return err } - err = ioutil.WriteFile(path.Join(c.workDir, "pipelines.yml"), pipelinesBody, 0600) + err = ioutil.WriteFile(filepath.Join(c.workDir, "pipelines.yml"), pipelinesBody, 0600) if err != nil { return err } diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index 59ec811..813cea9 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -3,7 +3,7 @@ package controller_test import ( "context" "errors" - "path" + "path/filepath" "testing" "github.com/matryer/is" @@ -33,11 +33,11 @@ func TestNewController(t *testing.T) { c, err := controller.NewController(nil, tempdir, logging.NoopLogger) is.NoErr(err) - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "log4j2.properties"))) // log4j2.properties - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "stdin.conf"))) // stdin.conf - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "output.conf"))) // output.conf - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "log4j2.properties"))) // log4j2.properties + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "stdin.conf"))) // stdin.conf + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "output.conf"))) // output.conf + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml }) } } @@ -151,17 +151,17 @@ func TestCompleteCycle(t *testing.T) { is.Equal(2, len(res)) // Test content of pipeline.yml - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml - is.True(file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: main")) // pipelines.yml contains "id: main" - is.True(file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: input")) // pipelines.yml contains "id: input" + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml + is.True(file.Contains(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: main")) // pipelines.yml contains "id: main" + is.True(file.Contains(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: input")) // pipelines.yml contains "id: input" err = c.Teardown() is.NoErr(err) // Test if pipelines are reomved from pipeline.yml - is.True(file.Exists(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml - is.True(!file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: main")) // pipelines.yml contains "id: main" - is.True(!file.Contains(path.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: input")) // pipelines.yml contains "id: input" + is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"))) // pipelines.yml + is.True(!file.Contains(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: main")) // pipelines.yml contains "id: main" + is.True(!file.Contains(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "pipelines.yml"), "id: input")) // pipelines.yml contains "id: input" }) } } diff --git a/internal/daemon/controller/files.go b/internal/daemon/controller/files.go index 33757aa..020f08b 100644 --- a/internal/daemon/controller/files.go +++ b/internal/daemon/controller/files.go @@ -1,7 +1,7 @@ package controller import ( - "path" + "path/filepath" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" ) @@ -58,13 +58,13 @@ func basePipelines(workDir string) pipeline.Pipelines { return pipeline.Pipelines{ pipeline.Pipeline{ ID: "stdin", - Config: path.Join(workDir, "stdin.conf"), + Config: filepath.Join(workDir, "stdin.conf"), Ordered: "true", Workers: 1, }, pipeline.Pipeline{ ID: "output", - Config: path.Join(workDir, "output.conf"), + Config: filepath.Join(workDir, "output.conf"), Ordered: "true", Workers: 1, }, diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 45fdf0d..a20a4df 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "github.com/pkg/errors" @@ -23,12 +24,12 @@ type File struct { } func (f File) Save(targetDir string) error { - err := os.MkdirAll(path.Join(targetDir, path.Dir(f.Name)), 0700) + err := os.MkdirAll(filepath.Join(targetDir, path.Dir(f.Name)), 0700) if err != nil { return err } - return ioutil.WriteFile(path.Join(targetDir, f.Name), f.Body, 0600) + return ioutil.WriteFile(filepath.Join(targetDir, f.Name), f.Body, 0600) } func (f *File) parse() error { diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index ba1e6ec..7f5796c 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -1,7 +1,7 @@ package logstashconfig_test import ( - "path" + "path/filepath" "strings" "testing" @@ -35,8 +35,8 @@ func TestSave(t *testing.T) { err := f.Save(tempdir) is.NoErr(err) - is.True(file.Exists(path.Join(tempdir, f.Name))) // test.conf - is.True(file.Contains(path.Join(tempdir, f.Name), string(f.Body))) // test.conf contains "test" + is.True(file.Exists(filepath.Join(tempdir, f.Name))) // test.conf + is.True(file.Contains(filepath.Join(tempdir, f.Name), string(f.Body))) // test.conf contains "test" }) } } diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 259d703..b4af6f1 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "strings" "github.com/bmatcuk/doublestar/v2" @@ -54,7 +55,7 @@ func New(file, basePath string) (Archive, error) { func (a Archive) Validate() error { var inputs, outputs int for _, pipeline := range a.Pipelines { - files, err := doublestar.Glob(path.Join(a.BasePath, pipeline.Config)) + files, err := doublestar.Glob(filepath.Join(a.BasePath, pipeline.Config)) if err != nil { return err } @@ -67,7 +68,7 @@ func (a Archive) Validate() error { if err != nil { return err } - relFile = strings.TrimPrefix(file, path.Join(cwd, a.BasePath)) + relFile = strings.TrimPrefix(file, filepath.Join(cwd, a.BasePath)) } body, err := ioutil.ReadFile(file) @@ -114,7 +115,7 @@ func (a Archive) Zip() ([]byte, error) { } for _, pipeline := range a.Pipelines { - files, err := doublestar.Glob(path.Join(a.BasePath, pipeline.Config)) + files, err := doublestar.Glob(filepath.Join(a.BasePath, pipeline.Config)) if err != nil { return nil, err } @@ -127,7 +128,7 @@ func (a Archive) Zip() ([]byte, error) { if err != nil { return nil, err } - relFile = strings.TrimPrefix(file, path.Join(cwd, a.BasePath)) + relFile = strings.TrimPrefix(file, filepath.Join(cwd, a.BasePath)) } f, err := w.Create(relFile) diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index e816bea..622a9b3 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -1,7 +1,7 @@ package session_test import ( - "path" + "path/filepath" "testing" "time" @@ -74,9 +74,9 @@ func TestSession(t *testing.T) { s, err := c.Create(pipelines, configFiles) is.NoErr(err) - is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "sut", "main.conf"))) // sut/main.conf - is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "sut", "main.conf"), "__lfv_input")) // sut/main.conf contains "__lfv_input" - is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "sut", "main.conf"), "lfv_output_")) // sut/main.conf contains "lfv_output_" + is.True(file.Exists(filepath.Join(tempdir, "session", s.ID(), "sut", "main.conf"))) // sut/main.conf + is.True(file.Contains(filepath.Join(tempdir, "session", s.ID(), "sut", "main.conf"), "__lfv_input")) // sut/main.conf contains "__lfv_input" + is.True(file.Contains(filepath.Join(tempdir, "session", s.ID(), "sut", "main.conf"), "lfv_output_")) // sut/main.conf contains "lfv_output_" _, err = c.Get("invalid") is.True(err != nil) // Get invalid session error @@ -93,10 +93,10 @@ func TestSession(t *testing.T) { err = s.ExecuteTest("input", inputLines, inFields) is.NoErr(err) - is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"))) // lfv_inputs/1/fields.json - is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"), "some_random_key")) // lfv_inputs/1/fields.json contains "some_random_key" - is.True(file.Exists(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"))) // lfv_inputs/1/input.conf - is.True(file.Contains(path.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"), "lfv_inputs/1/fields.json")) // lfv_inputs/1/input.conf contains "lfv_inputs/1/fields.json" + is.True(file.Exists(filepath.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"))) // lfv_inputs/1/fields.json + is.True(file.Contains(filepath.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"), "some_random_key")) // lfv_inputs/1/fields.json contains "some_random_key" + is.True(file.Exists(filepath.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"))) // lfv_inputs/1/input.conf + is.True(file.Contains(filepath.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "input.conf"), "lfv_inputs/1/fields.json")) // lfv_inputs/1/input.conf contains "lfv_inputs/1/fields.json" results, err := s.GetResults() is.NoErr(err) diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 763eacb..83d48a0 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -5,7 +5,7 @@ import ( "fmt" "io/ioutil" "os" - "path" + "path/filepath" "strconv" "strings" @@ -59,14 +59,14 @@ func (s *Session) setupTest(pipelines pipeline.Pipelines, configFiles []logstash return err } - sutConfigDir := path.Join(s.sessionDir, "sut") + sutConfigDir := filepath.Join(s.sessionDir, "sut") // adjust pipeline names and config directories to session for i := range pipelines { pipelineName := fmt.Sprintf("lfv_%s_%s", s.id, pipelines[i].ID) pipelines[i].ID = pipelineName - pipelines[i].Config = path.Join(sutConfigDir, pipelines[i].Config) + pipelines[i].Config = filepath.Join(sutConfigDir, pipelines[i].Config) pipelines[i].Ordered = "true" pipelines[i].Workers = 1 } @@ -109,7 +109,7 @@ func (s *Session) setupTest(pipelines pipeline.Pipelines, configFiles []logstash } func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, error) { - lfvOutputsDir := path.Join(s.sessionDir, "lfv_outputs") + lfvOutputsDir := filepath.Join(s.sessionDir, "lfv_outputs") err := os.MkdirAll(lfvOutputsDir, 0700) if err != nil { return nil, err @@ -127,14 +127,14 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, PipelineOrigName: output, } - err = template.ToFile(path.Join(lfvOutputsDir, output+".conf"), outputPipeline, templateData, 0644) + err = template.ToFile(filepath.Join(lfvOutputsDir, output+".conf"), outputPipeline, templateData, 0644) if err != nil { return nil, err } pipeline := pipeline.Pipeline{ ID: pipelineName, - Config: path.Join(lfvOutputsDir, output+".conf"), + Config: filepath.Join(lfvOutputsDir, output+".conf"), Ordered: "true", Workers: 1, } @@ -149,7 +149,7 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents []map[string]interface{}) error { s.testexec++ pipelineName := fmt.Sprintf("lfv_input_%d", s.testexec) - inputDir := path.Join(s.sessionDir, "lfv_inputs", strconv.Itoa(s.testexec)) + inputDir := filepath.Join(s.sessionDir, "lfv_inputs", strconv.Itoa(s.testexec)) inputPluginName := fmt.Sprintf("%s_%s_%s", "__lfv_input", s.id, inputPlugin) inputCodec, ok := s.inputPluginCodecs[inputPlugin] if !ok { @@ -162,13 +162,13 @@ func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents return err } - fieldsFilename := path.Join(inputDir, "fields.json") + fieldsFilename := filepath.Join(inputDir, "fields.json") err = prepareFields(fieldsFilename, inEvents) if err != nil { return err } - pipelineFilename := path.Join(inputDir, "input.conf") + pipelineFilename := filepath.Join(inputDir, "input.conf") err = createInput(pipelineFilename, fieldsFilename, inputPluginName, inputLines, inputCodec, false) if err != nil { return err From fab70fef4937b6e538a903c589d521f252bf92a4 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sat, 3 Apr 2021 13:53:30 +0200 Subject: [PATCH 060/143] Use Logstash path from config file in integration tests --- integration_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/integration_test.go b/integration_test.go index 4c0ba50..6f5548e 100644 --- a/integration_test.go +++ b/integration_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/matryer/is" + "github.com/spf13/viper" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/daemon/run" @@ -23,6 +24,18 @@ func TestIntegration(t *testing.T) { is := is.New(t) + viper.SetConfigName("logstash-filter-verifier") + viper.AddConfigPath(".") + + viper.SetDefault("logstash.path", "/usr/share/logstash/bin/logstash") + + // Read config + if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + t.Fatalf("Error processing config file: %v", err) + } + } + testLogger := &logging.LoggerMock{ DebugFunc: func(args ...interface{}) {}, DebugfFunc: func(format string, args ...interface{}) {}, @@ -46,7 +59,7 @@ func TestIntegration(t *testing.T) { tempdir := t.TempDir() // Start Daemon socket := filepath.Join(tempdir, "integration_test.socket") - logstashPath := filepath.Join("3rdparty/logstash-7.10.0/bin/logstash") + logstashPath := viper.GetString("logstash.path") if !file.Exists(logstashPath) { t.Fatalf("Logstash needs to be present in %q for the integration tests to work", logstashPath) } From 7e040a82e2936468b5bb919d16d78f838cd97c86 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sun, 4 Apr 2021 21:25:39 +0200 Subject: [PATCH 061/143] Switch to already used Masterminds/semver --- go.mod | 7 ++++--- go.sum | 5 ----- internal/app/setup.go | 12 +++++++----- internal/app/setup/setup.go | 11 ++++++----- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 538d44b..d64c358 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/breml/logstash-config v0.4.1 - github.com/cheggaaa/pb v1.0.29 // indirect github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 @@ -18,8 +17,10 @@ require ( github.com/matoous/go-nanoid v1.5.0 github.com/matryer/is v1.4.0 github.com/matryer/moq v0.2.1 + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.11 // indirect github.com/mattn/go-shellwords v1.0.6 - github.com/mholt/archiver/v3 v3.5.0 // indirect + github.com/mholt/archiver/v3 v3.5.0 github.com/mikefarah/yaml/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pkg/errors v0.9.1 @@ -35,7 +36,7 @@ require ( google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 google.golang.org/protobuf v1.25.0 gopkg.in/VividCortex/ewma.v1 v1.1.1 // indirect - gopkg.in/cheggaaa/pb.v2 v2.0.7 // indirect + gopkg.in/cheggaaa/pb.v2 v2.0.7 gopkg.in/fatih/color.v1 v1.7.0 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/mattn/go-colorable.v0 v0.1.0 // indirect diff --git a/go.sum b/go.sum index 94dff9b..dff4ebd 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= -github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -104,7 +102,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -274,7 +271,6 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= @@ -455,7 +451,6 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= diff --git a/internal/app/setup.go b/internal/app/setup.go index 68b3934..58dfeeb 100644 --- a/internal/app/setup.go +++ b/internal/app/setup.go @@ -1,13 +1,13 @@ package app import ( - "errors" "fmt" "runtime" + semver "github.com/Masterminds/semver/v3" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "golang.org/x/mod/semver" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/app/setup" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" @@ -38,7 +38,8 @@ func makeSetupCmd() *cobra.Command { func runSetup(_ *cobra.Command, args []string) error { s := setup.New( - args[0], + // version argument already validated in validateSetupArgs + semver.MustParse(args[0]), viper.GetString("target-dir"), viper.GetBool("oss"), viper.GetString("os-arch"), @@ -54,8 +55,9 @@ func validateSetupArgs(cmd *cobra.Command, args []string) error { return errors.New("required argument 'logstash-version' not provided, try --help") } - if !semver.IsValid("v" + args[0]) { - return errors.New("invalid version string provided, correct format x.y.z, e.g. 7.12.0") + _, err := semver.NewVersion(args[0]) + if err != nil { + return errors.Wrapf(err, "failed to parse version string %q, correct format x.y.z, e.g. 7.12.0", args[0]) } return nil } diff --git a/internal/app/setup/setup.go b/internal/app/setup/setup.go index 51b4463..87be2a4 100644 --- a/internal/app/setup/setup.go +++ b/internal/app/setup/setup.go @@ -8,16 +8,16 @@ import ( "path/filepath" "strings" + semver "github.com/Masterminds/semver/v3" "github.com/mholt/archiver/v3" "github.com/pkg/errors" - "golang.org/x/mod/semver" "gopkg.in/cheggaaa/pb.v2" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) type Setup struct { - version string + version *semver.Version targetDir string oss bool osArch string @@ -26,7 +26,7 @@ type Setup struct { } // New creates a new setup command to install versions of Logstash. -func New(version string, targetDir string, oss bool, osArch string, archiveType string, log logging.Logger) Setup { +func New(version *semver.Version, targetDir string, oss bool, osArch string, archiveType string, log logging.Logger) Setup { return Setup{ version: version, targetDir: targetDir, @@ -37,7 +37,7 @@ func New(version string, targetDir string, oss bool, osArch string, archiveType } } -const firstOSSpecificRelease = "v7.10.0" +var firstOSSpecificRelease = semver.MustParse("7.10.0") // Setup installs versions of Logstash. func (s *Setup) Run() error { @@ -54,7 +54,8 @@ func (s *Setup) Run() error { } filename := fmt.Sprintf("logstash-%s%s-%s.%s", oss, s.version, s.osArch, s.archiveType) targetDirVersion := filepath.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) - if semver.Compare("v"+s.version, firstOSSpecificRelease) < 0 { + + if s.version.Compare(firstOSSpecificRelease) < 0 { filename = fmt.Sprintf("logstash-%s%s.%s", oss, s.version, s.archiveType) targetDirVersion = filepath.Join(s.targetDir, fmt.Sprintf("logstash-%s%s-%s", oss, s.version, s.osArch)) } From f79b86a41de964c8f91294268bb7de2566c28957 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sun, 4 Apr 2021 22:14:43 +0200 Subject: [PATCH 062/143] Add workaround for ordered events in pipelines Workaround from https://github.com/magnusbaeck/logstash-filter-verifier/commit/6166b6249256689806e64a1e078c1701307c9438 Restores ordered events in pipelines for Logstash versions before 7.7.0. --- internal/daemon/instance/logstash/instance.go | 4 ++++ internal/daemon/pipeline/pipeline.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/daemon/instance/logstash/instance.go b/internal/daemon/instance/logstash/instance.go index 1186996..0f9eeb7 100644 --- a/internal/daemon/instance/logstash/instance.go +++ b/internal/daemon/instance/logstash/instance.go @@ -65,6 +65,10 @@ func (i *instance) Start(ctx context.Context, controller *controller.Controller, workdir, "--path.data", workdir, + // Workaround from https://github.com/magnusbaeck/logstash-filter-verifier/commit/6166b6249256689806e64a1e078c1701307c9438 + // to ensure ordering of events in Logstash before 7.7.0 + "--pipeline.batch.size", + "1", // TODO: figure out the correct paths // "--path.plugins", // workdir, diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index b4af6f1..19b5c2a 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -27,7 +27,7 @@ type Pipelines []Pipeline type Pipeline struct { ID string `yaml:"pipeline.id"` Config string `yaml:"path.config"` - Ordered string `yaml:"pipeline.ordered"` + Ordered string `yaml:"-"` Workers int `yaml:"pipeline.workers"` } From 86df1afee46f31a3900e941fa9705056570ddc76 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 7 Apr 2021 21:48:50 +0200 Subject: [PATCH 063/143] Add pprof debug endpoint, guarded with build tag --- internal/app/daemon/daemon.go | 2 +- pprof.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 pprof.go diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 42e104a..abe28b1 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -129,7 +129,7 @@ func (d *Daemon) Run(ctx context.Context) error { d.log.Infof("Daemon listening on %s", d.socket) err = d.server.Serve(lis) if err != nil { - d.log.Error("failed to start daemon: %v", err) + d.log.Errorf("failed to start daemon: %v", err) shutdown() } }() diff --git a/pprof.go b/pprof.go new file mode 100644 index 0000000..c4c3de7 --- /dev/null +++ b/pprof.go @@ -0,0 +1,19 @@ +// +build pprof + +package main + +import ( + "fmt" + "net/http" + _ "net/http/pprof" + "os" +) + +func init() { + go func() { + err := http.ListenAndServe("localhost:6060", nil) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to start http server for pprof: %v", err) + } + }() +} From 55fc16f0dadbd49367b3c22b090bc579390c0ffd Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 7 Apr 2021 22:00:15 +0200 Subject: [PATCH 064/143] Fix index out of range panic --- internal/daemon/pool/pool.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/daemon/pool/pool.go b/internal/daemon/pool/pool.go index f305f99..976f7a9 100644 --- a/internal/daemon/pool/pool.go +++ b/internal/daemon/pool/pool.go @@ -83,12 +83,13 @@ func (p *Pool) housekeeping(ctx context.Context) { p.availableControllers = p.availableControllers[:len(p.availableControllers)-1] } } - for i := range p.assignedControllers { + for i := 0; i < len(p.assignedControllers); i++ { if !p.assignedControllers[i].IsHealthy() { // Delete without preserving order p.assignedControllers[i].Kill() p.assignedControllers[i] = p.assignedControllers[len(p.assignedControllers)-1] p.assignedControllers = p.assignedControllers[:len(p.assignedControllers)-1] + i-- } } @@ -108,12 +109,13 @@ func (p *Pool) Get() (LogstashController, error) { defer p.mutex.Unlock() // Remove unhealthy instances - for i := range p.availableControllers { + for i := 0; i < len(p.availableControllers); i++ { if !p.availableControllers[i].IsHealthy() { // Delete without preserving order p.availableControllers[i].Kill() p.availableControllers[i] = p.availableControllers[len(p.availableControllers)-1] p.availableControllers = p.availableControllers[:len(p.availableControllers)-1] + i-- } } @@ -140,7 +142,7 @@ func (p *Pool) Return(instance LogstashController, clean bool) { p.mutex.Lock() defer p.mutex.Unlock() - for i := range p.assignedControllers { + for i := 0; i < len(p.assignedControllers); i++ { if p.assignedControllers[i] == instance { // Delete without preserving order p.assignedControllers[i] = p.assignedControllers[len(p.assignedControllers)-1] From d9a559d1d37dff41f17351dc5dbd19b8dc7004e5 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 7 Apr 2021 23:14:55 +0200 Subject: [PATCH 065/143] Make daemon mode work with Logstash < 7.0.0 --- integration_test.go | 13 ++++++++++++- internal/daemon/controller/pipelines.go | 3 ++- internal/daemon/instance/logstash/processors.go | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/integration_test.go b/integration_test.go index 6f5548e..dce910c 100644 --- a/integration_test.go +++ b/integration_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + semver "github.com/Masterminds/semver/v3" "github.com/matryer/is" "github.com/spf13/viper" @@ -15,6 +16,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/file" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + standalonelogstash "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" ) func TestIntegration(t *testing.T) { @@ -67,6 +69,9 @@ func TestIntegration(t *testing.T) { log := testLogger server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second) + version, err := standalonelogstash.DetectVersion(logstashPath, os.Environ()) + is.NoErr(err) + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() @@ -98,6 +103,8 @@ func TestIntegration(t *testing.T) { name string debug bool + minimumVersion *semver.Version + // optional integration tests require additional logstash plugins, // which are not provided by a default installation. optional bool @@ -128,6 +135,10 @@ func TestIntegration(t *testing.T) { t.Run(tc.name, func(t *testing.T) { is := is.New(t) + if tc.minimumVersion != nil && tc.minimumVersion.GreaterThan(version) { + t.Skipf("Logstash minimum version not satisfied, require %s, have %s", tc.minimumVersion.String(), version.String()) + } + if tc.optional && os.Getenv("INTEGRATION_TEST_OPTIONAL") != "1" { t.Skipf("optional integration test %q skipped, enable with env var `INTEGRATION_TEST_OPTIONAL=1`", tc.name) } @@ -147,7 +158,7 @@ func TestIntegration(t *testing.T) { }) } - _, err := server.Shutdown(context.Background(), &grpc.ShutdownRequest{}) + _, err = server.Shutdown(context.Background(), &grpc.ShutdownRequest{}) is.NoErr(err) <-ctx.Done() diff --git a/internal/daemon/controller/pipelines.go b/internal/daemon/controller/pipelines.go index 3e96bb3..934049e 100644 --- a/internal/daemon/controller/pipelines.go +++ b/internal/daemon/controller/pipelines.go @@ -18,7 +18,8 @@ func (p *pipelines) reset(pipelines ...string) { p.mutex.Lock() defer p.mutex.Unlock() - p.pipelines = make(map[string]bool, len(pipelines)) + p.pipelines = make(map[string]bool, len(pipelines)+1) + p.pipelines["__lfv_pipelines_running"] = false for _, pipeline := range pipelines { p.pipelines[pipeline] = false diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go index 5ae651e..cf53b4f 100644 --- a/internal/daemon/instance/logstash/processors.go +++ b/internal/daemon/instance/logstash/processors.go @@ -93,6 +93,11 @@ func (i *instance) logstashLogProcessor(t *tail.Tail) { pipelineID := gjson.Get(line.Text, `logEvent.pipeline\.id`).String() i.log.Debugf("taillog: -> pipeline started: %s", pipelineID) + i.controller.PipelinesReady(pipelineID) + case "Pipeline started successfully": // Logstash < 7.0.0 + pipelineID := gjson.Get(line.Text, `logEvent.pipeline_id`).String() + i.log.Debugf("taillog: -> pipeline started: %s", pipelineID) + i.controller.PipelinesReady(pipelineID) case "Pipelines running": rp := gjson.Get(line.Text, `logEvent.running_pipelines.0.metaClass.metaClass.metaClass.running_pipelines`).String() @@ -100,6 +105,7 @@ func (i *instance) logstashLogProcessor(t *tail.Tail) { i.log.Debugf("taillog: -> pipeline running: %v", runningPipelines) i.controller.PipelinesReady(runningPipelines...) + i.controller.PipelinesReady("__lfv_pipelines_running") } case <-i.ctxShutdown.Done(): i.log.Debug("shutdown log reader") From f393f5acd6526049ffca2d9986ca2af3628afa4d Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 10:34:05 +0200 Subject: [PATCH 066/143] Update protobuf --- internal/daemon/api/grpc/api.pb.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/daemon/api/grpc/api.pb.go b/internal/daemon/api/grpc/api.pb.go index 55553cf..d4e0484 100644 --- a/internal/daemon/api/grpc/api.pb.go +++ b/internal/daemon/api/grpc/api.pb.go @@ -1,13 +1,12 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.25.0 +// protoc-gen-go v1.26.0 // protoc v3.6.1 // source: api.proto package grpc import ( - proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -21,10 +20,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// This is a compile-time assertion that a sufficiently up-to-date version -// of the legacy proto package is being used. -const _ = proto.ProtoPackageIsVersion4 - type ShutdownRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache From 4eb4303f7d578151a7bdf1a3a7da9552c6fe7b3e Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 12:38:01 +0200 Subject: [PATCH 067/143] Prevent keep-envs setting in daemon from hide setting in standalone mode --- internal/app/daemon_start.go | 7 +++++-- internal/app/standalone.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index fa0e931..3843b41 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -29,8 +29,8 @@ func makeDaemonStartCmd() *cobra.Command { // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. // TODO: Not yet sure, if this should be global or only in standalone. - cmd.Flags().StringSlice("keep-env", []string{"PATH"}, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") - _ = viper.BindPFlag("keep-envs", cmd.Flags().Lookup("keep-env")) + cmd.Flags().StringSlice("keep-env", nil, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") + _ = viper.BindPFlag("daemon-keep-envs", cmd.Flags().Lookup("keep-env")) return cmd } @@ -39,6 +39,9 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { socket := viper.GetString("socket") logstashPath := viper.GetString("logstash.path") keptEnvs := viper.GetStringSlice("keep-envs") + if len(viper.GetStringSlice("daemon-keep-envs")) > 0 { + keptEnvs = viper.GetStringSlice("daemon-keep-envs") + } inflightShutdownTimeout := viper.GetDuration("inflight-shutdown-timeout") shutdownTimeout := viper.GetDuration("shutdown-timeout") log := viper.Get("logger").(logging.Logger) diff --git a/internal/app/standalone.go b/internal/app/standalone.go index e46f0c2..9601e65 100644 --- a/internal/app/standalone.go +++ b/internal/app/standalone.go @@ -27,7 +27,7 @@ func makeStandaloneCmd() *cobra.Command { // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. // TODO: Not yet sure, if this should be global or only in standalone. - cmd.Flags().StringSlice("keep-env", []string{"PATH"}, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") + cmd.Flags().StringSlice("keep-env", nil, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") _ = viper.BindPFlag("keep-envs", cmd.Flags().Lookup("keep-env")) // TODO: Not yet sure, if this should be global or only in standalone. From d65e2e4d2a04a18da36a869a63c1ce5a29a17d9c Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 12:53:03 +0200 Subject: [PATCH 068/143] Remove unnecessary short flag options --- internal/app/daemon_run.go | 2 +- internal/app/daemon_start.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 6423f4f..6753ab5 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -17,7 +17,7 @@ func makeDaemonRunCmd() *cobra.Command { cmd.Flags().StringP("pipeline", "p", "", "location of the pipelines.yml file to be processed") _ = viper.BindPFlag("pipeline", cmd.Flags().Lookup("pipeline")) - cmd.Flags().StringP("pipeline-base", "", "", "base directory for relative paths in the pipelines.yml") + cmd.Flags().String("pipeline-base", "", "base directory for relative paths in the pipelines.yml") _ = viper.BindPFlag("pipeline-base", cmd.Flags().Lookup("pipeline-base")) cmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index 3843b41..e347c5c 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -18,13 +18,13 @@ func makeDaemonStartCmd() *cobra.Command { RunE: runDaemonStart, } - cmd.Flags().StringP("logstash-path", "", "/usr/share/logstash/bin/logstash", "location where the logstash executable is found") + cmd.Flags().String("logstash-path", "/usr/share/logstash/bin/logstash", "location where the logstash executable is found") _ = viper.BindPFlag("logstash.path", cmd.Flags().Lookup("logstash-path")) - cmd.Flags().DurationP("inflight-shutdown-timeout", "", 10*time.Second, "maximum duration to wait for in-flight test executions to finish during shutdown") + cmd.Flags().Duration("inflight-shutdown-timeout", 10*time.Second, "maximum duration to wait for in-flight test executions to finish during shutdown") _ = viper.BindPFlag("inflight-shutdown-timeout", cmd.Flags().Lookup("inflight-shutdown-timeout")) - cmd.Flags().DurationP("shutdown-timeout", "", 3*time.Second, "maximum duration to wait for Logstash and gRPC server to gracefully shutdown") + cmd.Flags().Duration("shutdown-timeout", 3*time.Second, "maximum duration to wait for Logstash and gRPC server to gracefully shutdown") _ = viper.BindPFlag("shutdown-timeout", cmd.Flags().Lookup("shutdown-timeout")) // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. From 1a037122bade70f33ea8830fb4a2aacd5029a2d4 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 13:07:29 +0200 Subject: [PATCH 069/143] Add default values to viper --- internal/app/app.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/app/app.go b/internal/app/app.go index 00907da..915326e 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -35,7 +36,9 @@ func Execute(version string, stdout, stderr io.Writer) int { viper.SetDefault("loglevel", "WARNING") viper.SetDefault("socket", "/tmp/logstash-filter-verifier.sock") viper.SetDefault("pipeline", "/etc/logstash/pipelines.yml") - viper.SetDefault("logstash.path", "") + viper.SetDefault("logstash.path", "/usr/share/logstash/bin/logstash") + viper.SetDefault("inflight-shutdown-timeout", 10*time.Second) + viper.SetDefault("shutdown-timeout", 3*time.Second) // Read config if err := viper.ReadInConfig(); err != nil { From 64e5021e7eec82d1e376cdf9f79b4cd6ac2f7241 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 15:41:28 +0200 Subject: [PATCH 070/143] Fix panic in pool Do not panic if removed by housekeeping --- internal/daemon/pool/pool.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/daemon/pool/pool.go b/internal/daemon/pool/pool.go index 976f7a9..08e6b8d 100644 --- a/internal/daemon/pool/pool.go +++ b/internal/daemon/pool/pool.go @@ -75,12 +75,13 @@ func (p *Pool) housekeeping(ctx context.Context) { defer p.mutex.Unlock() // Remove unhealthy instances - for i := range p.availableControllers { + for i := 0; i < len(p.availableControllers); i++ { if !p.availableControllers[i].IsHealthy() { // Delete without preserving order p.availableControllers[i].Kill() p.availableControllers[i] = p.availableControllers[len(p.availableControllers)-1] p.availableControllers = p.availableControllers[:len(p.availableControllers)-1] + i-- } } for i := 0; i < len(p.assignedControllers); i++ { @@ -135,7 +136,7 @@ func (p *Pool) Get() (LogstashController, error) { return instance, nil } - return nil, errors.Errorf("No instance available from pool") + return nil, errors.Errorf("no instance available from pool") } func (p *Pool) Return(instance LogstashController, clean bool) { @@ -155,5 +156,5 @@ func (p *Pool) Return(instance LogstashController, clean bool) { return } } - panic("instance not found in assigned controllers") + p.log.Warning("Instance not found in assigned controllers. Instance might have been cleaned up by housekeeping due to unhealthy state.") } From 711323af10e755a78e6eafd0823187b5415a8474 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 16:05:57 +0200 Subject: [PATCH 071/143] Fix unit tests --- internal/daemon/controller/controller_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index 813cea9..ef048f4 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -112,7 +112,7 @@ func TestCompleteCycle(t *testing.T) { is.NoErr(err) // Simulate pipelines ready from instance - c.PipelinesReady("stdin", "output") + c.PipelinesReady("stdin", "output", "__lfv_pipelines_running") pipelines := pipeline.Pipelines{ pipeline.Pipeline{ @@ -127,7 +127,7 @@ func TestCompleteCycle(t *testing.T) { is.NoErr(err) // Simulate pipelines ready from instance - c.PipelinesReady("stdin", "output", "main") + c.PipelinesReady("stdin", "output", "main", "__lfv_pipelines_running") pipelines = append(pipelines, pipeline.Pipeline{ ID: "input", @@ -140,7 +140,7 @@ func TestCompleteCycle(t *testing.T) { is.NoErr(err) // Simulate pipelines ready from instance - c.PipelinesReady("stdin", "output", "main", "input") + c.PipelinesReady("stdin", "output", "main", "input", "__lfv_pipelines_running") err = c.ReceiveEvent("result 1") is.NoErr(err) err = c.ReceiveEvent("result 2") From c93ea3a86557194110a7e59c7f0ff5a4ef5e34a6 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 16 Apr 2021 16:06:38 +0200 Subject: [PATCH 072/143] Make wait-for-state-timeout configurable --- integration_test.go | 2 +- internal/app/app.go | 1 + internal/app/daemon/daemon.go | 6 ++++-- internal/app/daemon_start.go | 6 +++++- internal/daemon/controller/controller.go | 14 +++++++++----- internal/daemon/controller/controller_test.go | 11 +++++++---- internal/daemon/controller/statemachine.go | 16 +++++++++------- 7 files changed, 36 insertions(+), 20 deletions(-) diff --git a/integration_test.go b/integration_test.go index dce910c..f34030a 100644 --- a/integration_test.go +++ b/integration_test.go @@ -67,7 +67,7 @@ func TestIntegration(t *testing.T) { } log := testLogger - server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second) + server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second, 30*time.Second) version, err := standalonelogstash.DetectVersion(logstashPath, os.Environ()) is.NoErr(err) diff --git a/internal/app/app.go b/internal/app/app.go index 915326e..3d93a42 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -39,6 +39,7 @@ func Execute(version string, stdout, stderr io.Writer) int { viper.SetDefault("logstash.path", "/usr/share/logstash/bin/logstash") viper.SetDefault("inflight-shutdown-timeout", 10*time.Second) viper.SetDefault("shutdown-timeout", 3*time.Second) + viper.SetDefault("wait-for-state-timeout", 30*time.Second) // Read config if err := viper.ReadInConfig(); err != nil { diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index abe28b1..1958c66 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -49,6 +49,7 @@ type Daemon struct { inflightShutdownTimeout time.Duration shutdownTimeout time.Duration + waitForStateTimeout time.Duration sessionController *session.Controller @@ -60,7 +61,7 @@ type Daemon struct { } // New creates a new logstash filter verifier daemon. -func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration) Daemon { +func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration, waitForStateTimeout time.Duration) Daemon { ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ socket: socket, @@ -71,6 +72,7 @@ func New(socket string, logstashPath string, keptEnvVars []string, log logging.L log: log, ctxShutdownSignal: ctxShutdownSignal, shutdownSignalFunc: shutdownSignalFunc, + waitForStateTimeout: waitForStateTimeout, } } @@ -97,7 +99,7 @@ func (d *Daemon) Run(ctx context.Context) error { logstashControllerFactory := func() (session.LogstashController, error) { shutdownLogstashInstancesWG.Add(1) instance := logstash.New(ctxKill, d.logstashPath, env, d.log, shutdownLogstashInstancesWG) - logstashController, err := controller.NewController(instance, tempdir, d.log) + logstashController, err := controller.NewController(instance, tempdir, d.log, d.waitForStateTimeout) if err != nil { return nil, err } diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index e347c5c..004935e 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -27,6 +27,9 @@ func makeDaemonStartCmd() *cobra.Command { cmd.Flags().Duration("shutdown-timeout", 3*time.Second, "maximum duration to wait for Logstash and gRPC server to gracefully shutdown") _ = viper.BindPFlag("shutdown-timeout", cmd.Flags().Lookup("shutdown-timeout")) + cmd.Flags().Duration("wait-for-state-timeout", 30*time.Second, "maximum duration to wait for Logstash to reach an expected state (e.g. pipeline ready)") + _ = viper.BindPFlag("wait-for-state-timeout", cmd.Flags().Lookup("wait-for-state-timeout")) + // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. // TODO: Not yet sure, if this should be global or only in standalone. cmd.Flags().StringSlice("keep-env", nil, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") @@ -44,12 +47,13 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { } inflightShutdownTimeout := viper.GetDuration("inflight-shutdown-timeout") shutdownTimeout := viper.GetDuration("shutdown-timeout") + waitForStateTimeout := viper.GetDuration("wait-for-state-timeout") log := viper.Get("logger").(logging.Logger) log.Debugf("config: socket: %s", socket) log.Debugf("config: logstash-path: %s", logstashPath) - s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout) + s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout, waitForStateTimeout) defer s.Cleanup() return s.Run(context.Background()) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index ed4c66d..441afac 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "sync" + "time" "gopkg.in/yaml.v2" @@ -27,12 +28,13 @@ type Controller struct { instanceReady *sync.Once shutdown context.CancelFunc - stateMachine *stateMachine - receivedEvents *events - pipelines *pipelines + stateMachine *stateMachine + waitForStateTimeout time.Duration + receivedEvents *events + pipelines *pipelines } -func NewController(instance Instance, baseDir string, log logging.Logger) (*Controller, error) { +func NewController(instance Instance, baseDir string, log logging.Logger, waitForStateTimeout time.Duration) (*Controller, error) { id := idgen.New() workDir := filepath.Join(baseDir, LogstashInstanceDirectoryPrefix, id) @@ -69,6 +71,8 @@ func NewController(instance Instance, baseDir string, log logging.Logger) (*Cont instance: instance, instanceReady: &sync.Once{}, + waitForStateTimeout: waitForStateTimeout, + receivedEvents: newEvents(), pipelines: newPipelines(), } @@ -90,7 +94,7 @@ func (c *Controller) Launch(ctx context.Context) error { c.shutdown = cancel c.pipelines.reset("stdin", "output") - c.stateMachine = newStateMachine(ctx, c.log) + c.stateMachine = newStateMachine(ctx, c.log, c.waitForStateTimeout) c.stateMachine.executeCommand(commandStart) err := c.instance.Start(ctx, c, c.workDir) diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index ef048f4..a6bb58f 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -5,6 +5,7 @@ import ( "errors" "path/filepath" "testing" + "time" "github.com/matryer/is" @@ -15,6 +16,8 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) +const defaultWaitForStateTimeout = 30 * time.Second + func TestNewController(t *testing.T) { cases := []struct { name string @@ -30,7 +33,7 @@ func TestNewController(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(nil, tempdir, logging.NoopLogger) + c, err := controller.NewController(nil, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) is.NoErr(err) is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml @@ -72,7 +75,7 @@ func TestLaunch(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) is.NoErr(err) err = c.Launch(context.Background()) @@ -105,7 +108,7 @@ func TestCompleteCycle(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) is.NoErr(err) err = c.Launch(context.Background()) @@ -190,7 +193,7 @@ func TestSetupTest_Shutdown(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) is.NoErr(err) ctx, cancel := context.WithCancel(context.Background()) diff --git a/internal/daemon/controller/statemachine.go b/internal/daemon/controller/statemachine.go index c839719..dd3ff4b 100644 --- a/internal/daemon/controller/statemachine.go +++ b/internal/daemon/controller/statemachine.go @@ -17,10 +17,13 @@ type stateMachine struct { mutex *sync.Mutex cond *sync.Cond + // TODO: Maybe allow different timeouts for different states. + waitForStateTimeout time.Duration + log logging.Logger } -func newStateMachine(ctx context.Context, log logging.Logger) *stateMachine { +func newStateMachine(ctx context.Context, log logging.Logger, waitForStateTimeout time.Duration) *stateMachine { mu := &sync.Mutex{} cond := sync.NewCond(mu) go func() { @@ -44,21 +47,20 @@ func newStateMachine(ctx context.Context, log logging.Logger) *stateMachine { currentState: stateCreated, mutex: mu, cond: cond, - log: log, + + waitForStateTimeout: waitForStateTimeout, + + log: log, } } -// TODO: Allow this timeout to be set in configuration. -// TODO: Maybe allow different timeouts for different states. -const waitForStateTimeout = 30 * time.Second - func (s *stateMachine) waitForState(target stateName) error { s.log.Debugf("waitForState: %v", target) s.mutex.Lock() defer s.mutex.Unlock() - t := time.NewTimer(waitForStateTimeout) + t := time.NewTimer(s.waitForStateTimeout) defer func() { // Stop timer and drain channel if !t.Stop() { From 040cddd3d24a74b05aec42d5505cb406dadc4613 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sat, 17 Apr 2021 13:44:46 +0200 Subject: [PATCH 073/143] Escape inputs as Logstash strings --- go.mod | 3 +- go.sum | 4 +- integration_test.go | 3 ++ internal/app/daemon/run/run.go | 18 +++++++- internal/daemon/session/session.go | 11 ++++- testdata/special_chars.yml | 2 + testdata/special_chars/special_chars.conf | 10 +++++ .../testcases/special_chars/testcase1.json | 42 +++++++++++++++++++ 8 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 testdata/special_chars.yml create mode 100644 testdata/special_chars/special_chars.conf create mode 100644 testdata/testcases/special_chars/testcase1.json diff --git a/go.mod b/go.mod index d64c358..73eee39 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,8 @@ require ( github.com/ahmetb/govvv v0.3.0 github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 - github.com/breml/logstash-config v0.4.1 + github.com/breml/logstash-config v0.4.4 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 - github.com/golang/protobuf v1.4.2 github.com/hashicorp/packer v1.4.4 github.com/hpcloud/tail v1.0.0 github.com/imkira/go-observer v1.0.3 diff --git a/go.sum b/go.sum index dff4ebd..a8d0c19 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/breml/logstash-config v0.4.1 h1:0zJ7UUkxnFV/RxT3+2YSEmZ9mReupz6JmpYbyo0MvxI= -github.com/breml/logstash-config v0.4.1/go.mod h1:NaBkWLM71LaEUF/VoCAHMcQf0nAnOcPaaiRKKoRgPN0= +github.com/breml/logstash-config v0.4.4 h1:JpRTXVnbZ19nBTf7hIfYzgSSyc2+mpdmdlj9Jt4UfU8= +github.com/breml/logstash-config v0.4.4/go.mod h1:NaBkWLM71LaEUF/VoCAHMcQf0nAnOcPaaiRKKoRgPN0= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/integration_test.go b/integration_test.go index f34030a..955c089 100644 --- a/integration_test.go +++ b/integration_test.go @@ -129,6 +129,9 @@ func TestIntegration(t *testing.T) { name: "codec_optional_test", optional: true, }, + { + name: "special_chars", + }, } for _, tc := range cases { diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 28c7b2a..2329e5e 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,7 +3,6 @@ package run import ( "context" "encoding/json" - "errors" "net" "os" "path" @@ -13,10 +12,14 @@ import ( "time" "github.com/imkira/go-observer" + "github.com/pkg/errors" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "google.golang.org/grpc" + "github.com/breml/logstash-config/ast" + "github.com/breml/logstash-config/ast/astutil" + pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" @@ -116,6 +119,8 @@ func (s Test) Run() error { if err != nil { return err } + s.validateInputLines(t.InputLines) + result, err := c.ExecuteTest(context.Background(), &pb.ExecuteTestRequest{ SessionID: sessionID, InputPlugin: t.InputPlugin, @@ -173,6 +178,17 @@ func (s Test) Run() error { return nil } +func (s Test) validateInputLines(lines []string) { + for _, line := range lines { + _, doubleQuoteErr := astutil.Quote(line, ast.DoubleQuoted) + _, singleQuoteErr := astutil.Quote(line, ast.SingleQuoted) + + if doubleQuoteErr != nil && singleQuoteErr != nil { + s.log.Warningf("Test input line %q contains unescaped double and single quotes, single quotes will be escaped automatically", line) + } + } +} + func (s Test) postProcessResults(results []string, exportMetadata bool) ([]string, error) { var err error diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 83d48a0..7e7cf40 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -11,6 +11,9 @@ import ( "github.com/pkg/errors" + "github.com/breml/logstash-config/ast" + "github.com/breml/logstash-config/ast/astutil" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" @@ -215,9 +218,13 @@ func createInput(pipelineFilename string, fieldsFilename string, inputPluginName removeGeneratorFields += `, "message"` } - // FIXME: inputLines are not properly escaped for Logstash for i := range inputLines { - inputLines[i] = "'" + inputLines[i] + "'" + var err error + inputLine, err := astutil.Quote(inputLines[i], ast.DoubleQuoted) + if err != nil { + inputLine = astutil.QuoteWithEscape(inputLines[i], ast.SingleQuoted) + } + inputLines[i] = inputLine } templateData := struct { diff --git a/testdata/special_chars.yml b/testdata/special_chars.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/special_chars.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/special_chars/special_chars.conf b/testdata/special_chars/special_chars.conf new file mode 100644 index 0000000..41a957d --- /dev/null +++ b/testdata/special_chars/special_chars.conf @@ -0,0 +1,10 @@ +input { + stdin { + id => "stdin" + } +} +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/testcases/special_chars/testcase1.json b/testdata/testcases/special_chars/testcase1.json new file mode 100644 index 0000000..a392398 --- /dev/null +++ b/testdata/testcases/special_chars/testcase1.json @@ -0,0 +1,42 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "message with \r \n \t \\ (special characters)", + "message with only \" (double quote)", + "message with only ' (single quote)", + "message with \" and ' (double and single quote)", + "message with \\\" and ' (escaped double and single quote)", + "message with \" and \\' (double and escaped single quote)", + "message with \\\" and \\' (escaped double and escaped single quote)" + ], + "expected": [ + { + "message": "message with \r \n \t \\ (special characters)" + }, + { + "message": "message with only \" (double quote)" + }, + { + "message": "message with only ' (single quote)" + }, + { + "message": "message with \" and \\' (double and single quote)" + }, + { + "message": "message with \\\" and ' (escaped double and single quote)" + }, + { + "message": "message with \" and \\' (double and escaped single quote)" + }, + { + "message": "message with \\\" and \\' (escaped double and escaped single quote)" + } + ] + } + ] +} From 5da2106e932fc9a875f77f871cf7b39059cbe9b7 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 23 Apr 2021 07:54:13 +0200 Subject: [PATCH 074/143] Fix import groups for github.com/breml imports --- internal/app/daemon/run/run.go | 5 ++--- internal/daemon/logstashconfig/file.go | 3 +-- internal/daemon/session/session.go | 3 +-- internal/logstash/pipelineconfigdir_test.go | 1 + 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 2329e5e..553a331 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -11,15 +11,14 @@ import ( "strings" "time" + "github.com/breml/logstash-config/ast" + "github.com/breml/logstash-config/ast/astutil" "github.com/imkira/go-observer" "github.com/pkg/errors" "github.com/tidwall/gjson" "github.com/tidwall/sjson" "google.golang.org/grpc" - "github.com/breml/logstash-config/ast" - "github.com/breml/logstash-config/ast/astutil" - pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index a20a4df..6286e06 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -7,11 +7,10 @@ import ( "path" "path/filepath" - "github.com/pkg/errors" - config "github.com/breml/logstash-config" "github.com/breml/logstash-config/ast" "github.com/breml/logstash-config/ast/astutil" + "github.com/pkg/errors" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" ) diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 7e7cf40..767a707 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -9,10 +9,9 @@ import ( "strconv" "strings" - "github.com/pkg/errors" - "github.com/breml/logstash-config/ast" "github.com/breml/logstash-config/ast/astutil" + "github.com/pkg/errors" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" diff --git a/internal/logstash/pipelineconfigdir_test.go b/internal/logstash/pipelineconfigdir_test.go index f62e4bc..ea72222 100644 --- a/internal/logstash/pipelineconfigdir_test.go +++ b/internal/logstash/pipelineconfigdir_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/breml/logstash-config/ast" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testhelpers" ) From 0b7e716754cbb72a54095633f98932ef88a4d103 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 23 Apr 2021 11:59:01 +0200 Subject: [PATCH 075/143] Fix reload issue for translate filter when LFV is long running --- internal/daemon/session/files.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index 77c990e..4d4c020 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -54,6 +54,7 @@ filter { exact => true override => true fallback => "__lfv_fields_not_found" + refresh_interval => 0 } ruby { From f99f5909fafa6a75e33fd3d73c8c78758654cdc9 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 30 Apr 2021 11:36:01 +0200 Subject: [PATCH 076/143] Add no-cleanup flag for later inspection of the Logstash pipelines and log files --- integration_test.go | 7 ++++++- internal/app/daemon/daemon.go | 11 +++++++++-- internal/app/daemon_start.go | 6 +++++- internal/daemon/session/controller.go | 6 ++++-- internal/daemon/session/controller_test.go | 4 ++-- internal/daemon/session/session.go | 10 ++++++++-- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/integration_test.go b/integration_test.go index 955c089..d93db3d 100644 --- a/integration_test.go +++ b/integration_test.go @@ -24,6 +24,11 @@ func TestIntegration(t *testing.T) { t.Skip("integration test skipped, enable with env var `INTEGRATION_TEST=1`") } + noCleanup := false + if os.Getenv("INTEGRATION_TEST_NOCLEANUP") == "1" { + noCleanup = true + } + is := is.New(t) viper.SetConfigName("logstash-filter-verifier") @@ -67,7 +72,7 @@ func TestIntegration(t *testing.T) { } log := testLogger - server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second, 30*time.Second) + server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second, 30*time.Second, noCleanup) version, err := standalonelogstash.DetectVersion(logstashPath, os.Environ()) is.NoErr(err) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 1958c66..21334c2 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -51,6 +51,8 @@ type Daemon struct { shutdownTimeout time.Duration waitForStateTimeout time.Duration + noCleanup bool + sessionController *session.Controller server *grpc.Server @@ -61,7 +63,7 @@ type Daemon struct { } // New creates a new logstash filter verifier daemon. -func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration, waitForStateTimeout time.Duration) Daemon { +func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration, waitForStateTimeout time.Duration, noCleanup bool) Daemon { ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ socket: socket, @@ -73,6 +75,7 @@ func New(socket string, logstashPath string, keptEnvVars []string, log logging.L ctxShutdownSignal: ctxShutdownSignal, shutdownSignalFunc: shutdownSignalFunc, waitForStateTimeout: waitForStateTimeout, + noCleanup: noCleanup, } } @@ -118,7 +121,7 @@ func (d *Daemon) Run(ctx context.Context) error { } // Create Session Handler - d.sessionController = session.NewController(d.tempdir, pool, d.log) + d.sessionController = session.NewController(d.tempdir, pool, d.noCleanup, d.log) // Create and start GRPC Server lis, err := net.Listen("unix", d.socket) @@ -248,6 +251,10 @@ func (d *Daemon) shutdownSignalHandler(shutdown func(), shutdownLogstashInstance // Cleanup removes the temporary files created by the daemon. func (d *Daemon) Cleanup() { + if d.noCleanup { + return + } + err := os.RemoveAll(d.tempdir) if err != nil { d.log.Errorf("Failed to cleanup temporary directory for daemon %q: %v", d.tempdir, err) diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index 004935e..2df5c90 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -30,6 +30,9 @@ func makeDaemonStartCmd() *cobra.Command { cmd.Flags().Duration("wait-for-state-timeout", 30*time.Second, "maximum duration to wait for Logstash to reach an expected state (e.g. pipeline ready)") _ = viper.BindPFlag("wait-for-state-timeout", cmd.Flags().Lookup("wait-for-state-timeout")) + cmd.Flags().Bool("no-cleanup", false, "with no-cleanup set to true, the temporary files (logstash pipelines, logs, etc.) are not cleaned up and left on the system for later inspection.") + _ = viper.BindPFlag("no-cleanup", cmd.Flags().Lookup("no-cleanup")) + // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. // TODO: Not yet sure, if this should be global or only in standalone. cmd.Flags().StringSlice("keep-env", nil, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") @@ -48,12 +51,13 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { inflightShutdownTimeout := viper.GetDuration("inflight-shutdown-timeout") shutdownTimeout := viper.GetDuration("shutdown-timeout") waitForStateTimeout := viper.GetDuration("wait-for-state-timeout") + noCleanup := viper.GetBool("no-cleanup") log := viper.Get("logger").(logging.Logger) log.Debugf("config: socket: %s", socket) log.Debugf("config: logstash-path: %s", logstashPath) - s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout, waitForStateTimeout) + s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout, waitForStateTimeout, noCleanup) defer s.Cleanup() return s.Run(context.Background()) diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go index 642bfdd..0b2bc6b 100644 --- a/internal/daemon/session/controller.go +++ b/internal/daemon/session/controller.go @@ -22,11 +22,12 @@ type Controller struct { tempdir string logstashPool Pool + noCleanup bool log logging.Logger } // NewController creates a new session Controller. -func NewController(tempdir string, logstashPool Pool, log logging.Logger) *Controller { +func NewController(tempdir string, logstashPool Pool, noCleanup bool, log logging.Logger) *Controller { mu := &sync.Mutex{} return &Controller{ @@ -38,6 +39,7 @@ func NewController(tempdir string, logstashPool Pool, log logging.Logger) *Contr tempdir: tempdir, logstashPool: logstashPool, + noCleanup: noCleanup, log: log, } } @@ -60,7 +62,7 @@ func (s *Controller) Create(pipelines pipeline.Pipelines, configFiles []logstash return nil, err } - session := newSession(s.tempdir, logstashController, s.log) + session := newSession(s.tempdir, logstashController, s.noCleanup, s.log) s.sessions[session.ID()] = session s.wg.Add(1) diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index 622a9b3..7a3077b 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -53,7 +53,7 @@ func TestSession(t *testing.T) { ReturnFunc: func(instance pool.LogstashController, clean bool) {}, } - c := session.NewController(tempdir, pool, logging.NoopLogger) + c := session.NewController(tempdir, pool, false, logging.NoopLogger) pipelines := pipeline.Pipelines{ pipeline.Pipeline{ @@ -143,7 +143,7 @@ func TestCreate(t *testing.T) { ReturnFunc: func(instance pool.LogstashController, clean bool) {}, } - c := session.NewController(tempdir, pool, logging.NoopLogger) + c := session.NewController(tempdir, pool, false, logging.NoopLogger) pipelines := pipeline.Pipelines{ pipeline.Pipeline{ diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 767a707..e456507 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -33,10 +33,12 @@ type Session struct { inputPluginCodecs map[string]string testexec int + noCleanup bool + log logging.Logger } -func newSession(baseDir string, logstashController pool.LogstashController, log logging.Logger) *Session { +func newSession(baseDir string, logstashController pool.LogstashController, noCleanup bool, log logging.Logger) *Session { sessionID := idgen.New() sessionDir := fmt.Sprintf("%s/session/%s", baseDir, sessionID) return &Session{ @@ -44,6 +46,7 @@ func newSession(baseDir string, logstashController pool.LogstashController, log baseDir: baseDir, sessionDir: sessionDir, logstashController: logstashController, + noCleanup: noCleanup, inputPluginCodecs: map[string]string{}, log: log, } @@ -264,7 +267,10 @@ func (s *Session) GetStats() { func (s *Session) teardown() error { // TODO: Perform a reset of the Logstash instance including Stdin Buffer, etc. err1 := s.logstashController.Teardown() - err2 := os.RemoveAll(s.sessionDir) + var err2 error + if !s.noCleanup { + err2 = os.RemoveAll(s.sessionDir) + } if err1 != nil || err2 != nil { return errors.Errorf("session teardown failed: %v, %v", err1, err2) } From e587d46f166fcd94753109490adc86852e29eac7 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 30 Apr 2021 11:39:02 +0200 Subject: [PATCH 077/143] Omit pipeline.ordered if Logstash < 7.7.0 --- internal/app/daemon/daemon.go | 14 ++++++-- internal/daemon/controller/controller.go | 19 +++++++---- internal/daemon/controller/controller_test.go | 8 ++--- internal/daemon/controller/files.go | 2 -- internal/daemon/pipeline/pipeline.go | 2 +- internal/daemon/pool/pool.go | 3 ++ internal/daemon/session/controller.go | 22 +++++++------ internal/daemon/session/controller_test.go | 4 +-- internal/daemon/session/session.go | 32 ++++++++++++------- 9 files changed, 67 insertions(+), 39 deletions(-) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 21334c2..4a34d7f 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -13,6 +13,7 @@ import ( "syscall" "time" + "github.com/Masterminds/semver/v3" "github.com/pkg/errors" "google.golang.org/grpc" "gopkg.in/yaml.v2" @@ -97,12 +98,21 @@ func (d *Daemon) Run(ctx context.Context) error { env := standalonelogstash.GetLimitedEnvironment(os.Environ(), d.keptEnvVars) + logstashVersion, err := standalonelogstash.DetectVersion(d.logstashPath, env) + if err != nil { + return err + } + var isOrderedPipelineSupported bool + if logstashVersion.Compare(semver.MustParse("v7.7.0")) >= 0 { + isOrderedPipelineSupported = true + } + // Factory to create and start Logstash Controller shutdownLogstashInstancesWG := &sync.WaitGroup{} logstashControllerFactory := func() (session.LogstashController, error) { shutdownLogstashInstancesWG.Add(1) instance := logstash.New(ctxKill, d.logstashPath, env, d.log, shutdownLogstashInstancesWG) - logstashController, err := controller.NewController(instance, tempdir, d.log, d.waitForStateTimeout) + logstashController, err := controller.NewController(instance, tempdir, d.log, d.waitForStateTimeout, isOrderedPipelineSupported) if err != nil { return nil, err } @@ -121,7 +131,7 @@ func (d *Daemon) Run(ctx context.Context) error { } // Create Session Handler - d.sessionController = session.NewController(d.tempdir, pool, d.noCleanup, d.log) + d.sessionController = session.NewController(d.tempdir, pool, d.noCleanup, isOrderedPipelineSupported, d.log) // Create and start GRPC Server lis, err := net.Listen("unix", d.socket) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 441afac..d314337 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -28,13 +28,14 @@ type Controller struct { instanceReady *sync.Once shutdown context.CancelFunc - stateMachine *stateMachine - waitForStateTimeout time.Duration - receivedEvents *events - pipelines *pipelines + stateMachine *stateMachine + waitForStateTimeout time.Duration + isOrderedPipelineSupported bool + receivedEvents *events + pipelines *pipelines } -func NewController(instance Instance, baseDir string, log logging.Logger, waitForStateTimeout time.Duration) (*Controller, error) { +func NewController(instance Instance, baseDir string, log logging.Logger, waitForStateTimeout time.Duration, isOrderedPipelineSupported bool) (*Controller, error) { id := idgen.New() workDir := filepath.Join(baseDir, LogstashInstanceDirectoryPrefix, id) @@ -71,7 +72,8 @@ func NewController(instance Instance, baseDir string, log logging.Logger, waitFo instance: instance, instanceReady: &sync.Once{}, - waitForStateTimeout: waitForStateTimeout, + waitForStateTimeout: waitForStateTimeout, + isOrderedPipelineSupported: isOrderedPipelineSupported, receivedEvents: newEvents(), pipelines: newPipelines(), @@ -167,6 +169,11 @@ func (c *Controller) reload(pipelines pipeline.Pipelines, expectedEvents int) er func (c *Controller) writePipelines(pipelines ...pipeline.Pipeline) error { basePipelines := basePipelines(c.workDir) + if c.isOrderedPipelineSupported { + for i := range basePipelines { + basePipelines[i].Ordered = "true" + } + } pipelines = append(basePipelines, pipelines...) diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index a6bb58f..5bce68b 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -33,7 +33,7 @@ func TestNewController(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(nil, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) + c, err := controller.NewController(nil, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) is.NoErr(err) is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml @@ -75,7 +75,7 @@ func TestLaunch(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) is.NoErr(err) err = c.Launch(context.Background()) @@ -108,7 +108,7 @@ func TestCompleteCycle(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) is.NoErr(err) err = c.Launch(context.Background()) @@ -193,7 +193,7 @@ func TestSetupTest_Shutdown(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) is.NoErr(err) ctx, cancel := context.WithCancel(context.Background()) diff --git a/internal/daemon/controller/files.go b/internal/daemon/controller/files.go index 020f08b..44fc8b7 100644 --- a/internal/daemon/controller/files.go +++ b/internal/daemon/controller/files.go @@ -59,13 +59,11 @@ func basePipelines(workDir string) pipeline.Pipelines { pipeline.Pipeline{ ID: "stdin", Config: filepath.Join(workDir, "stdin.conf"), - Ordered: "true", Workers: 1, }, pipeline.Pipeline{ ID: "output", Config: filepath.Join(workDir, "output.conf"), - Ordered: "true", Workers: 1, }, } diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 19b5c2a..eac3b23 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -27,7 +27,7 @@ type Pipelines []Pipeline type Pipeline struct { ID string `yaml:"pipeline.id"` Config string `yaml:"path.config"` - Ordered string `yaml:"-"` + Ordered string `yaml:"pipeline.ordered,omitempty"` Workers int `yaml:"pipeline.workers"` } diff --git a/internal/daemon/pool/pool.go b/internal/daemon/pool/pool.go index 08e6b8d..63d5e0d 100644 --- a/internal/daemon/pool/pool.go +++ b/internal/daemon/pool/pool.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/Masterminds/semver/v3" "github.com/pkg/errors" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" @@ -22,6 +23,8 @@ type LogstashController interface { type LogstashControllerFactory func() (LogstashController, error) +type LogstashDetectVersion func() (semver.Version, error) + type Pool struct { logstashControllerFactory LogstashControllerFactory maxControllers int diff --git a/internal/daemon/session/controller.go b/internal/daemon/session/controller.go index 0b2bc6b..3db9519 100644 --- a/internal/daemon/session/controller.go +++ b/internal/daemon/session/controller.go @@ -20,14 +20,15 @@ type Controller struct { sessions map[string]*Session finished bool - tempdir string - logstashPool Pool - noCleanup bool - log logging.Logger + tempdir string + logstashPool Pool + noCleanup bool + isOrderedPipelineSupported bool + log logging.Logger } // NewController creates a new session Controller. -func NewController(tempdir string, logstashPool Pool, noCleanup bool, log logging.Logger) *Controller { +func NewController(tempdir string, logstashPool Pool, noCleanup bool, isOrderedPipelineSupported bool, log logging.Logger) *Controller { mu := &sync.Mutex{} return &Controller{ @@ -37,10 +38,11 @@ func NewController(tempdir string, logstashPool Pool, noCleanup bool, log loggin sessions: make(map[string]*Session, 10), - tempdir: tempdir, - logstashPool: logstashPool, - noCleanup: noCleanup, - log: log, + tempdir: tempdir, + logstashPool: logstashPool, + noCleanup: noCleanup, + isOrderedPipelineSupported: isOrderedPipelineSupported, + log: log, } } @@ -62,7 +64,7 @@ func (s *Controller) Create(pipelines pipeline.Pipelines, configFiles []logstash return nil, err } - session := newSession(s.tempdir, logstashController, s.noCleanup, s.log) + session := newSession(s.tempdir, logstashController, s.noCleanup, s.isOrderedPipelineSupported, s.log) s.sessions[session.ID()] = session s.wg.Add(1) diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index 7a3077b..017e166 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -53,7 +53,7 @@ func TestSession(t *testing.T) { ReturnFunc: func(instance pool.LogstashController, clean bool) {}, } - c := session.NewController(tempdir, pool, false, logging.NoopLogger) + c := session.NewController(tempdir, pool, false, true, logging.NoopLogger) pipelines := pipeline.Pipelines{ pipeline.Pipeline{ @@ -143,7 +143,7 @@ func TestCreate(t *testing.T) { ReturnFunc: func(instance pool.LogstashController, clean bool) {}, } - c := session.NewController(tempdir, pool, false, logging.NoopLogger) + c := session.NewController(tempdir, pool, false, true, logging.NoopLogger) pipelines := pipeline.Pipelines{ pipeline.Pipeline{ diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index e456507..3585ecc 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -24,7 +24,8 @@ import ( type Session struct { id string - logstashController pool.LogstashController + logstashController pool.LogstashController + isOrderedPipelineSupported bool baseDir string sessionDir string @@ -38,17 +39,18 @@ type Session struct { log logging.Logger } -func newSession(baseDir string, logstashController pool.LogstashController, noCleanup bool, log logging.Logger) *Session { +func newSession(baseDir string, logstashController pool.LogstashController, noCleanup bool, isOrderedPipelineSupported bool, log logging.Logger) *Session { sessionID := idgen.New() sessionDir := fmt.Sprintf("%s/session/%s", baseDir, sessionID) return &Session{ - id: sessionID, - baseDir: baseDir, - sessionDir: sessionDir, - logstashController: logstashController, - noCleanup: noCleanup, - inputPluginCodecs: map[string]string{}, - log: log, + id: sessionID, + baseDir: baseDir, + sessionDir: sessionDir, + logstashController: logstashController, + isOrderedPipelineSupported: isOrderedPipelineSupported, + noCleanup: noCleanup, + inputPluginCodecs: map[string]string{}, + log: log, } } @@ -72,7 +74,9 @@ func (s *Session) setupTest(pipelines pipeline.Pipelines, configFiles []logstash pipelines[i].ID = pipelineName pipelines[i].Config = filepath.Join(sutConfigDir, pipelines[i].Config) - pipelines[i].Ordered = "true" + if s.isOrderedPipelineSupported { + pipelines[i].Ordered = "true" + } pipelines[i].Workers = 1 } @@ -140,9 +144,11 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, pipeline := pipeline.Pipeline{ ID: pipelineName, Config: filepath.Join(lfvOutputsDir, output+".conf"), - Ordered: "true", Workers: 1, } + if s.isOrderedPipelineSupported { + pipeline.Ordered = "true" + } pipelines = append(pipelines, pipeline) } @@ -182,9 +188,11 @@ func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents pipeline := pipeline.Pipeline{ ID: pipelineName, Config: pipelineFilename, - Ordered: "true", Workers: 1, } + if s.isOrderedPipelineSupported { + pipeline.Ordered = "true" + } pipelines := append(s.pipelines, pipeline) err = s.logstashController.ExecuteTest(pipelines, len(inputLines)) if err != nil { From 87bdbc15e9e392bf85541c1dfc542fb417cd5464 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 30 Apr 2021 13:55:02 +0200 Subject: [PATCH 078/143] Add support for nested keys in pipelines.yml --- internal/daemon/pipeline/pipeline.go | 21 +++++++++++++++++++ internal/daemon/pipeline/pipeline_test.go | 5 +++++ .../testdata/pipelines_basic_nested_keys.yml | 6 ++++++ 3 files changed, 32 insertions(+) create mode 100644 internal/daemon/pipeline/testdata/pipelines_basic_nested_keys.yml diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index eac3b23..d7a7a4f 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -29,6 +29,9 @@ type Pipeline struct { Config string `yaml:"path.config"` Ordered string `yaml:"pipeline.ordered,omitempty"` Workers int `yaml:"pipeline.workers"` + + Path map[string]interface{} `yaml:"path,omitempty"` + Pipeline map[string]interface{} `yaml:"pipeline,omitempty"` } func New(file, basePath string) (Archive, error) { @@ -43,6 +46,8 @@ func New(file, basePath string) (Archive, error) { return Archive{}, err } + processNestedKeys(p) + a := Archive{ Pipelines: p, File: file, @@ -52,6 +57,22 @@ func New(file, basePath string) (Archive, error) { return a, nil } +func processNestedKeys(pipelines Pipelines) { + for i := range pipelines { + if ival, ok := pipelines[i].Path["config"]; ok { + if val, ok := ival.(string); ok { + pipelines[i].Config = val + } + } + + if ival, ok := pipelines[i].Pipeline["id"]; ok { + if val, ok := ival.(string); ok { + pipelines[i].ID = val + } + } + } +} + func (a Archive) Validate() error { var inputs, outputs int for _, pipeline := range a.Pipelines { diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index a2f71ba..fcaff9d 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -75,6 +75,11 @@ func TestValidate(t *testing.T) { wantValidateErr: true, }, + { + name: "success basic pipeline with nested keys", + pipeline: "testdata/pipelines_basic_nested_keys.yml", + basePath: "testdata/", + }, } for _, test := range cases { diff --git a/internal/daemon/pipeline/testdata/pipelines_basic_nested_keys.yml b/internal/daemon/pipeline/testdata/pipelines_basic_nested_keys.yml new file mode 100644 index 0000000..9ac3ba8 --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_basic_nested_keys.yml @@ -0,0 +1,6 @@ +--- + +- pipeline: + id: main + path: + config: "folder/main.conf" From 7c9af3c862dfd38a0261e18e2ba86df35ffdbd5c Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 07:49:21 +0200 Subject: [PATCH 079/143] Cleanup test case files --- .../{testcase1.json => testcases.json} | 0 testdata/testcases/testcase_lfv.json | 25 ------------------- 2 files changed, 25 deletions(-) rename testdata/testcases/special_chars/{testcase1.json => testcases.json} (100%) delete mode 100644 testdata/testcases/testcase_lfv.json diff --git a/testdata/testcases/special_chars/testcase1.json b/testdata/testcases/special_chars/testcases.json similarity index 100% rename from testdata/testcases/special_chars/testcase1.json rename to testdata/testcases/special_chars/testcases.json diff --git a/testdata/testcases/testcase_lfv.json b/testdata/testcases/testcase_lfv.json deleted file mode 100644 index 1b36e4c..0000000 --- a/testdata/testcases/testcase_lfv.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "fields": { - "type": "syslog" - }, - "ignore": [ - "@timestamp", - "host" - ], - "testcases": [ - { - "input": [ - "" - ], - "expected": [ - { - "message": "", - "tags": [ - "sut_passed" - ], - "type": "syslog" - } - ] - } - ] -} From ab14eec41bfc4f176a8fbe93419cc79f15562269 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 07:51:03 +0200 Subject: [PATCH 080/143] Remove message, if no input is given --- internal/daemon/session/files.go | 5 +++-- internal/daemon/session/session.go | 30 +++++++++++++----------------- internal/testcase/testcase.go | 4 +++- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index 4d4c020..d087193 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -44,7 +44,7 @@ filter { add_tag => [ "__lfv_in_passed" ] # Remove fields "host", "sequence" and optionally "message", which are # automatically created by the generator input. - remove_field => [ {{ .RemoveGeneratorFields }} ] + remove_field => [ "host", "sequence" ] } translate { @@ -61,7 +61,8 @@ filter { id => '__lfv_ruby_fields' code => 'fields = event.get("[@metadata][__lfv_fields]") fields.each { |key, value| event.set(key, value) } unless fields == "__lfv_fields_not_found" - event.tag("lfv_fields_not_found") if fields == "__lfv_fields_not_found"' + event.tag("lfv_fields_not_found") if fields == "__lfv_fields_not_found" + event.remove("[message]") if event.get("[message]") == "{{ .DummyEventInputIndicator }}"' tag_on_exception => '__lfv_ruby_fields_exception' } } diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 3585ecc..8f03028 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -19,6 +19,7 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pool" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/template" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/testcase" ) type Session struct { @@ -180,7 +181,7 @@ func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents } pipelineFilename := filepath.Join(inputDir, "input.conf") - err = createInput(pipelineFilename, fieldsFilename, inputPluginName, inputLines, inputCodec, false) + err = createInput(pipelineFilename, fieldsFilename, inputPluginName, inputLines, inputCodec) if err != nil { return err } @@ -222,12 +223,7 @@ func prepareFields(fieldsFilename string, inEvents []map[string]interface{}) err return nil } -func createInput(pipelineFilename string, fieldsFilename string, inputPluginName string, inputLines []string, inputCodec string, removeMessageField bool) error { - removeGeneratorFields := `"host", "sequence"` - if removeMessageField { - removeGeneratorFields += `, "message"` - } - +func createInput(pipelineFilename string, fieldsFilename string, inputPluginName string, inputLines []string, inputCodec string) error { for i := range inputLines { var err error inputLine, err := astutil.Quote(inputLines[i], ast.DoubleQuoted) @@ -238,17 +234,17 @@ func createInput(pipelineFilename string, fieldsFilename string, inputPluginName } templateData := struct { - InputPluginName string - InputLines string - InputCodec string - FieldsFilename string - RemoveGeneratorFields string + InputPluginName string + InputLines string + InputCodec string + FieldsFilename string + DummyEventInputIndicator string }{ - InputPluginName: inputPluginName, - InputLines: strings.Join(inputLines, ", "), - InputCodec: inputCodec, - FieldsFilename: fieldsFilename, - RemoveGeneratorFields: removeGeneratorFields, + InputPluginName: inputPluginName, + InputLines: strings.Join(inputLines, ", "), + InputCodec: inputCodec, + FieldsFilename: fieldsFilename, + DummyEventInputIndicator: testcase.DummyEventInputIndicator, } err := template.ToFile(pipelineFilename, inputGenerator, templateData, 0600) if err != nil { diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 7d97baf..c48979d 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -25,6 +25,8 @@ import ( lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" ) +const DummyEventInputIndicator = "__lfv_dummy_event" + // TestCaseSet contains the configuration of a Logstash filter test case. // Most of the fields are supplied by the user via a JSON file or YAML file. type TestCaseSet struct { @@ -195,7 +197,7 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { for _, tc := range tcs.TestCases { // Add event, if there are no input lines. if len(tc.InputLines) == 0 { - tc.InputLines = []string{""} + tc.InputLines = []string{DummyEventInputIndicator} } tcs.InputLines = append(tcs.InputLines, tc.InputLines...) tcs.ExpectedEvents = append(tcs.ExpectedEvents, tc.ExpectedEvents...) From df262ae38403c1a36ad6696f8f13ccc004108f33 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 08:44:40 +0200 Subject: [PATCH 081/143] Count unique received events --- internal/daemon/controller/controller.go | 4 ++++ internal/daemon/controller/controller_test.go | 4 ++-- internal/daemon/controller/events.go | 23 +++++++++++++------ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index d314337..813d1c8 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -199,6 +199,10 @@ func (c *Controller) ReceiveEvent(event string) error { return err } + // The last event might be sent through multiple outputs, therefore we give + // a little headroom for more events with the same ID to arrive. + time.Sleep(100 * time.Millisecond) + c.stateMachine.executeCommand(commandTestComplete) } diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index 5bce68b..a88073f 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -144,9 +144,9 @@ func TestCompleteCycle(t *testing.T) { // Simulate pipelines ready from instance c.PipelinesReady("stdin", "output", "main", "input", "__lfv_pipelines_running") - err = c.ReceiveEvent("result 1") + err = c.ReceiveEvent(`{ "__lfv_id": "1", "message": "result 1" }`) is.NoErr(err) - err = c.ReceiveEvent("result 2") + err = c.ReceiveEvent(`{ "__lfv_id": "2", "message": "result 2" }`) is.NoErr(err) res, err := c.GetResults() diff --git a/internal/daemon/controller/events.go b/internal/daemon/controller/events.go index 470f85c..387886c 100644 --- a/internal/daemon/controller/events.go +++ b/internal/daemon/controller/events.go @@ -1,17 +1,23 @@ package controller -import "sync" +import ( + "sync" + + "github.com/tidwall/gjson" +) type events struct { - events []string - expected int - mutex *sync.Mutex + events []string + receivedUniqueIDs map[string]struct{} + expected int + mutex *sync.Mutex } func newEvents() *events { return &events{ - events: make([]string, 0, 100), - mutex: &sync.Mutex{}, + events: make([]string, 0, 100), + receivedUniqueIDs: make(map[string]struct{}, 100), + mutex: &sync.Mutex{}, } } @@ -20,13 +26,15 @@ func (e *events) append(event string) { defer e.mutex.Unlock() e.events = append(e.events, event) + id := gjson.Get(event, `__lfv_id`).String() + e.receivedUniqueIDs[id] = struct{}{} } func (e *events) isComplete() bool { e.mutex.Lock() defer e.mutex.Unlock() - return e.expected == len(e.events) + return e.expected == len(e.receivedUniqueIDs) } func (e *events) reset(expected int) { @@ -35,6 +43,7 @@ func (e *events) reset(expected int) { e.expected = expected e.events = make([]string, 0, 100) + e.receivedUniqueIDs = make(map[string]struct{}, 100) } func (e *events) get() []string { From 7ca9b95826e8fcb29ccdf2cd86f06b1328526ad1 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 10:20:05 +0200 Subject: [PATCH 082/143] Add ExpectedOutputs processing --- internal/app/daemon/run/run.go | 78 +++++++++++++++++++++--- internal/daemon/controller/controller.go | 2 +- internal/daemon/session/files.go | 1 + internal/testcase/testcase.go | 11 ++++ 4 files changed, 82 insertions(+), 10 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 553a331..2c17109 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,6 +3,7 @@ package run import ( "context" "encoding/json" + "fmt" "net" "os" "path" @@ -130,11 +131,13 @@ func (s Test) Run() error { return err } - results, err := s.postProcessResults(result.Results, t.ExportMetadata) + results, seenOutputs, err := s.postProcessResults(result.Results, t) if err != nil { return err } + verifySeenOutputs(t, seenOutputs, liveObserver) + var events []logstash.Event for _, line := range results { var event logstash.Event @@ -188,15 +191,35 @@ func (s Test) validateInputLines(lines []string) { } } -func (s Test) postProcessResults(results []string, exportMetadata bool) ([]string, error) { +func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]string, map[int][]string, error) { var err error sort.Slice(results, func(i, j int) bool { return gjson.Get(results[i], `__lfv_id`).Int() < gjson.Get(results[j], `__lfv_id`).Int() }) - for i := range results { - if exportMetadata { + lastID := "n/a" + testcase := -1 + seenOutputs := make(map[int][]string, len(results)) + for i := 0; i < len(results); i++ { + // Collect expected outputs + id := gjson.Get(results[i], "__lfv_id").String() + if id != lastID { + testcase++ + } + + seenOutputs[testcase] = append(seenOutputs[testcase], gjson.Get(results[i], "__lfv_metadata.__lfv_out_passed").String()) + + if id == lastID { + // Remove duplicate event, processed by different output + results = append(results[:i], results[i+1:]...) + i-- + continue + } + lastID = id + + // Export metadata + if t.ExportMetadata { metadata := gjson.Get(results[i], "__lfv_metadata") if metadata.Exists() && metadata.IsObject() { md := make(map[string]json.RawMessage, len(metadata.Map())) @@ -209,14 +232,14 @@ func (s Test) postProcessResults(results []string, exportMetadata bool) ([]strin if len(md) > 0 { results[i], err = sjson.Set(results[i], s.metadataKey, md) if err != nil { - return nil, err + return nil, nil, err } } } } results[i], err = sjson.Delete(results[i], "__lfv_metadata") if err != nil { - return nil, err + return nil, nil, err } // No cleanup if debug is set @@ -226,7 +249,7 @@ func (s Test) postProcessResults(results []string, exportMetadata bool) ([]strin results[i], err = sjson.Delete(results[i], "__lfv_id") if err != nil { - return nil, err + return nil, nil, err } tags := []string{} @@ -244,9 +267,46 @@ func (s Test) postProcessResults(results []string, exportMetadata bool) ([]strin results[i], err = sjson.Set(results[i], "tags", tags) } if err != nil { - return nil, err + return nil, nil, err } } - return results, nil + return results, seenOutputs, nil +} + +func verifySeenOutputs(tcs testcase.TestCaseSet, seenOutputs map[int][]string, liveObserver observer.Property) { + for i := 0; i < len(tcs.TestCases); i++ { + if len(tcs.TestCases[i].ExpectedOutputs) == 0 { + continue + } + + sort.Strings(tcs.TestCases[i].ExpectedOutputs) + sort.Strings(seenOutputs[i]) + + comparisonResult := lfvobserver.ComparisonResult{ + Status: true, + Name: fmt.Sprintf("Comparing Logstash outputs of message %d of %d", i+1, len(tcs.TestCases)), + Path: filepath.Base(tcs.File), + EventIndex: i, + } + + if !equal(tcs.TestCases[i].ExpectedOutputs, seenOutputs[i]) { + comparisonResult.Status = false + comparisonResult.Explain = fmt.Sprintf("Expected Logstash outputs: %v\nActually seen Logstash outputs: %v", tcs.TestCases[i].ExpectedOutputs, seenOutputs[i]) + } + + liveObserver.Update(comparisonResult) + } +} + +func equal(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true } diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 813d1c8..8f64f7f 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -201,7 +201,7 @@ func (c *Controller) ReceiveEvent(event string) error { // The last event might be sent through multiple outputs, therefore we give // a little headroom for more events with the same ID to arrive. - time.Sleep(100 * time.Millisecond) + time.Sleep(200 * time.Millisecond) c.stateMachine.executeCommand(commandTestComplete) } diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index 4d4c020..44c40e7 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -9,6 +9,7 @@ const outputPipeline = `input { filter { mutate { add_tag => [ "__lfv_out_{{ .PipelineOrigName }}_passed" ] + add_field => { "[@metadata][__lfv_out_passed]" => "{{ .PipelineOrigName }}" } } } diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 7d97baf..c6be05b 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -111,6 +111,17 @@ type TestCase struct { // process. ExpectedEvents []logstash.Event `json:"expected" yaml:"expected"` + // The unique ID of the output plugins in the tested configuration, where + // the event leaves Logstash. (optional) + // If no value is present or the list is empty, this criteria is not verified. + // If a value is present, the event is expected to be processed by + // the exact list of expected outputs. + // By listing multiple output plugins it is possible to test Logstash + // configurations with multiple (conditional) outputs: + // e.g. save the events to elasticsearch and, if the threshold is above x, + // additionally send an email. + ExpectedOutputs []string `json:"expected_outputs" yaml:"expected_outputs"` + // Description contains an optional description of the test case // which will be printed while the tests are executed. Description string `json:"description" yaml:"description"` From 472e93e3e38b1c582f257646d4448038816ed83c Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 10:20:30 +0200 Subject: [PATCH 083/143] Add integration tests --- integration_test.go | 3 ++ testdata/multiple_parallel_outputs.yml | 2 + .../multiple_parallel_outputs.conf | 17 ++++++ .../multiple_parallel_outputs.json | 52 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 testdata/multiple_parallel_outputs.yml create mode 100644 testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf create mode 100644 testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json diff --git a/integration_test.go b/integration_test.go index d93db3d..dda2218 100644 --- a/integration_test.go +++ b/integration_test.go @@ -120,6 +120,9 @@ func TestIntegration(t *testing.T) { { name: "conditional_output", }, + { + name: "multiple_parallel_outputs", + }, { name: "pipeline_to_pipeline", }, diff --git a/testdata/multiple_parallel_outputs.yml b/testdata/multiple_parallel_outputs.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/multiple_parallel_outputs.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf b/testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf new file mode 100644 index 0000000..dbdb36b --- /dev/null +++ b/testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf @@ -0,0 +1,17 @@ +input { + stdin { + id => "stdin" + } +} + +output { + stdout { + id => "stdout" + } + if [message] =~ /multiple/ { + file { + id => "file" + path => "./test.log" + } + } +} diff --git a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json new file mode 100644 index 0000000..4ead503 --- /dev/null +++ b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json @@ -0,0 +1,52 @@ +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "expected_outputs not defined (one output)" + ], + "expected": [ + { + "message": "expected_outputs not defined (one output)" + } + ] + }, + { + "input": [ + "expected_outputs not defined (multiple outputs)" + ], + "expected": [ + { + "message": "expected_outputs not defined (multiple outputs)" + } + ] + }, + { + "input": [ + "expected_outputs defined (one output)" + ], + "expected": [ + { + "message": "expected_outputs defined (one output)" + } + ], + "expected_outputs": [ "stdout" ] + }, + { + "input": [ + "expected_outputs defined (multiple output)" + ], + "expected": [ + { + "message": "expected_outputs defined (multiple output)" + } + ], + "expected_outputs": [ "stdout", "file" ] + } + ] +} From f6e5b047e689c2084f14b70bf66f057eb3e92bb8 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 11:09:49 +0200 Subject: [PATCH 084/143] Fix handling for late arriving events with multiple outputs --- internal/daemon/controller/controller.go | 23 ++++++++----------- internal/daemon/controller/controller_test.go | 6 ++--- internal/daemon/controller/events.go | 11 +++++++-- .../daemon/instance/logstash/processors.go | 6 +---- 4 files changed, 22 insertions(+), 24 deletions(-) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 8f64f7f..126c112 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -135,6 +135,10 @@ func (c *Controller) GetResults() ([]string, error) { return nil, err } + // The last event might be sent through multiple outputs, therefore we give + // a little headroom for more events with the same ID to arrive. + time.Sleep(50 * time.Millisecond) + return c.receivedEvents.get(), nil } @@ -190,23 +194,16 @@ func (c *Controller) writePipelines(pipelines ...pipeline.Pipeline) error { return nil } -func (c *Controller) ReceiveEvent(event string) error { +func (c *Controller) ReceiveEvent(event string) { c.receivedEvents.append(event) - if c.receivedEvents.isComplete() { - err := c.stateMachine.waitForState(stateRunningTest) - if err != nil { - return err - } - - // The last event might be sent through multiple outputs, therefore we give - // a little headroom for more events with the same ID to arrive. - time.Sleep(200 * time.Millisecond) + if c.receivedEvents.isCompleteFirstTime() { + go func() { + _ = c.stateMachine.waitForState(stateRunningTest) - c.stateMachine.executeCommand(commandTestComplete) + c.stateMachine.executeCommand(commandTestComplete) + }() } - - return nil } func (c *Controller) PipelinesReady(pipelines ...string) { diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index a88073f..7576853 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -144,10 +144,8 @@ func TestCompleteCycle(t *testing.T) { // Simulate pipelines ready from instance c.PipelinesReady("stdin", "output", "main", "input", "__lfv_pipelines_running") - err = c.ReceiveEvent(`{ "__lfv_id": "1", "message": "result 1" }`) - is.NoErr(err) - err = c.ReceiveEvent(`{ "__lfv_id": "2", "message": "result 2" }`) - is.NoErr(err) + c.ReceiveEvent(`{ "__lfv_id": "1", "message": "result 1" }`) + c.ReceiveEvent(`{ "__lfv_id": "2", "message": "result 2" }`) res, err := c.GetResults() is.NoErr(err) diff --git a/internal/daemon/controller/events.go b/internal/daemon/controller/events.go index 387886c..3204934 100644 --- a/internal/daemon/controller/events.go +++ b/internal/daemon/controller/events.go @@ -9,6 +9,7 @@ import ( type events struct { events []string receivedUniqueIDs map[string]struct{} + completeFirstTime bool expected int mutex *sync.Mutex } @@ -30,11 +31,16 @@ func (e *events) append(event string) { e.receivedUniqueIDs[id] = struct{}{} } -func (e *events) isComplete() bool { +func (e *events) isCompleteFirstTime() bool { e.mutex.Lock() defer e.mutex.Unlock() - return e.expected == len(e.receivedUniqueIDs) + if e.expected == len(e.receivedUniqueIDs) && !e.completeFirstTime { + e.completeFirstTime = true + return true + } + + return false } func (e *events) reset(expected int) { @@ -44,6 +50,7 @@ func (e *events) reset(expected int) { e.expected = expected e.events = make([]string, 0, 100) e.receivedUniqueIDs = make(map[string]struct{}, 100) + e.completeFirstTime = false } func (e *events) get() []string { diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go index cf53b4f..6bef4e2 100644 --- a/internal/daemon/instance/logstash/processors.go +++ b/internal/daemon/instance/logstash/processors.go @@ -30,11 +30,7 @@ func (i *instance) stdoutProcessor(stdout io.ReadCloser) { continue } - err := i.controller.ReceiveEvent(scanner.Text()) - if err != nil { - // Shutdown signal received in waitForState - break - } + i.controller.ReceiveEvent(scanner.Text()) } if err := scanner.Err(); err != nil { i.log.Error("reading standard output:", err) From f427c455b4b55130da0fc52c10c948344fab3743 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 07:52:38 +0200 Subject: [PATCH 085/143] Add integration tests --- integration_test.go | 3 ++ .../testcases/testcases_event/testcases.json | 29 +++++++++++++++++++ testdata/testcases_event.yml | 2 ++ testdata/testcases_event/testcases_event.conf | 10 +++++++ 4 files changed, 44 insertions(+) create mode 100644 testdata/testcases/testcases_event/testcases.json create mode 100644 testdata/testcases_event.yml create mode 100644 testdata/testcases_event/testcases_event.conf diff --git a/integration_test.go b/integration_test.go index d93db3d..d329646 100644 --- a/integration_test.go +++ b/integration_test.go @@ -137,6 +137,9 @@ func TestIntegration(t *testing.T) { { name: "special_chars", }, + { + name: "testcases_event", + }, } for _, tc := range cases { diff --git a/testdata/testcases/testcases_event/testcases.json b/testdata/testcases/testcases_event/testcases.json new file mode 100644 index 0000000..316e80f --- /dev/null +++ b/testdata/testcases/testcases_event/testcases.json @@ -0,0 +1,29 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "fields": { + "global_field": "global", + "overwritten_field": "global" + }, + "testcases": [ + { + "event": { + "string": "string value", + "number": 123, + "bool": true, + "overwritten_field": "event" + }, + "expected": [ + { + "string": "string value", + "number": 123, + "bool": true, + "global_field": "global", + "overwritten_field": "event" + } + ] + } + ] +} diff --git a/testdata/testcases_event.yml b/testdata/testcases_event.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/testcases_event.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/testcases_event/testcases_event.conf b/testdata/testcases_event/testcases_event.conf new file mode 100644 index 0000000..41a957d --- /dev/null +++ b/testdata/testcases_event/testcases_event.conf @@ -0,0 +1,10 @@ +input { + stdin { + id => "stdin" + } +} +output { + stdout { + id => "stdout" + } +} From 77d7d7e1fe27ca13076b2075451f150613fa2443 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 11:28:21 +0200 Subject: [PATCH 086/143] Add test multiple outputs with pipeline2pipeline communication --- testdata/multiple_parallel_outputs.yml | 7 ++- .../multiple_parallel_outputs.conf | 17 ------- .../multiple_parallel_outputs/stage1.conf | 23 ++++++++++ .../multiple_parallel_outputs/stage2.conf | 13 ++++++ .../multiple_parallel_outputs.json | 46 ++++++++++++++++++- 5 files changed, 85 insertions(+), 21 deletions(-) delete mode 100644 testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf create mode 100644 testdata/multiple_parallel_outputs/stage1.conf create mode 100644 testdata/multiple_parallel_outputs/stage2.conf diff --git a/testdata/multiple_parallel_outputs.yml b/testdata/multiple_parallel_outputs.yml index 75d1a35..70f3c30 100644 --- a/testdata/multiple_parallel_outputs.yml +++ b/testdata/multiple_parallel_outputs.yml @@ -1,2 +1,5 @@ -- pipeline.id: main - path.config: "*.conf" +- pipeline.id: stage1 + path.config: "stage1.conf" + +- pipeline.id: stage2 + path.config: "stage2.conf" diff --git a/testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf b/testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf deleted file mode 100644 index dbdb36b..0000000 --- a/testdata/multiple_parallel_outputs/multiple_parallel_outputs.conf +++ /dev/null @@ -1,17 +0,0 @@ -input { - stdin { - id => "stdin" - } -} - -output { - stdout { - id => "stdout" - } - if [message] =~ /multiple/ { - file { - id => "file" - path => "./test.log" - } - } -} diff --git a/testdata/multiple_parallel_outputs/stage1.conf b/testdata/multiple_parallel_outputs/stage1.conf new file mode 100644 index 0000000..9ff5d38 --- /dev/null +++ b/testdata/multiple_parallel_outputs/stage1.conf @@ -0,0 +1,23 @@ +input { + stdin { + id => "stdin" + } +} + +output { + stdout { + id => "stage1_stdout" + } + if [message] =~ /multiple/ { + file { + id => "stage1_file" + path => "./test.log" + } + } + if [message] =~ /stage2/ { + pipeline { + id => "stage1_to_stage2" + send_to => "stage2" + } + } +} diff --git a/testdata/multiple_parallel_outputs/stage2.conf b/testdata/multiple_parallel_outputs/stage2.conf new file mode 100644 index 0000000..efb0e6c --- /dev/null +++ b/testdata/multiple_parallel_outputs/stage2.conf @@ -0,0 +1,13 @@ +input { + pipeline { + id => "stage2_input" + address => "stage2" + } +} + +output { + file { + id => "stage2_file" + path => "./stage2_test.log" + } +} diff --git a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json index 4ead503..c14c1a9 100644 --- a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json +++ b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json @@ -35,7 +35,7 @@ "message": "expected_outputs defined (one output)" } ], - "expected_outputs": [ "stdout" ] + "expected_outputs": [ "stage1_stdout" ] }, { "input": [ @@ -46,7 +46,49 @@ "message": "expected_outputs defined (multiple output)" } ], - "expected_outputs": [ "stdout", "file" ] + "expected_outputs": [ "stage1_stdout", "stage1_file" ] + }, + { + "input": [ + "expected_outputs not defined (stage2)" + ], + "expected": [ + { + "message": "expected_outputs not defined (stage2)" + } + ] + }, + { + "input": [ + "expected_outputs not defined (multiple outputs and stage2)" + ], + "expected": [ + { + "message": "expected_outputs not defined (multiple outputs and stage2)" + } + ] + }, + { + "input": [ + "expected_outputs defined (stage2)" + ], + "expected": [ + { + "message": "expected_outputs defined (stage2)" + } + ], + "expected_outputs": [ "stage1_stdout", "stage2_file" ] + }, + { + "input": [ + "expected_outputs defined (multiple output and stage2)" + ], + "expected": [ + { + "message": "expected_outputs defined (multiple output and stage2)" + } + ], + "expected_outputs": [ "stage1_stdout", "stage1_file", "stage2_file" ] } ] } From c8c4314c73c8cd2380dcf0d61a9f7b1998dcd3c7 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 12:08:15 +0200 Subject: [PATCH 087/143] Fix support for drop and split filter --- go.mod | 4 +- go.sum | 14 ++- integration_test.go | 3 + internal/app/daemon/daemon.go | 2 +- internal/app/daemon/run/run.go | 9 +- internal/daemon/api/grpc/api.pb.go | 98 ++++++++++++-------- internal/daemon/api/grpc/api.proto | 1 + internal/daemon/api/grpc/api_grpc.pb.go | 8 +- internal/daemon/session/controller_test.go | 2 +- internal/daemon/session/session.go | 4 +- testdata/drop_and_split.yml | 2 + testdata/drop_and_split/drop_and_split.conf | 26 ++++++ testdata/testcases/drop_and_split/drop.json | 25 +++++ testdata/testcases/drop_and_split/split.json | 31 +++++++ 14 files changed, 169 insertions(+), 60 deletions(-) create mode 100644 testdata/drop_and_split.yml create mode 100644 testdata/drop_and_split/drop_and_split.conf create mode 100644 testdata/testcases/drop_and_split/drop.json create mode 100644 testdata/testcases/drop_and_split/split.json diff --git a/go.mod b/go.mod index 73eee39..584ba27 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/bmatcuk/doublestar/v2 v2.0.4 github.com/breml/logstash-config v0.4.4 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 + github.com/golang/protobuf v1.5.2 github.com/hashicorp/packer v1.4.4 github.com/hpcloud/tail v1.0.0 github.com/imkira/go-observer v1.0.3 @@ -31,9 +32,10 @@ require ( github.com/yookoala/realpath v1.0.0 // indirect golang.org/x/mod v0.4.2 // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/grpc v1.34.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 - google.golang.org/protobuf v1.25.0 + google.golang.org/protobuf v1.26.0 gopkg.in/VividCortex/ewma.v1 v1.1.1 // indirect gopkg.in/cheggaaa/pb.v2 v2.0.7 gopkg.in/fatih/color.v1 v1.7.0 // indirect diff --git a/go.sum b/go.sum index a8d0c19..5c5c3ce 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,10 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -147,8 +149,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -532,8 +535,9 @@ golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2p golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -572,8 +576,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/VividCortex/ewma.v1 v1.1.1 h1:tWHEKkKq802K/JT9RiqGCBU5fW3raAPnJGTE9ostZvg= gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/integration_test.go b/integration_test.go index d93db3d..7dcf5f6 100644 --- a/integration_test.go +++ b/integration_test.go @@ -117,6 +117,9 @@ func TestIntegration(t *testing.T) { { name: "basic_pipeline", }, + { + name: "drop_and_split", + }, { name: "conditional_output", }, diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 4a34d7f..b7102de 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -376,7 +376,7 @@ func (d *Daemon) ExecuteTest(ctx context.Context, in *pb.ExecuteTestRequest) (ou return nil, errors.Wrap(err, "invalid json for fields") } - err = session.ExecuteTest(in.InputPlugin, in.InputLines, events) + err = session.ExecuteTest(in.InputPlugin, in.InputLines, events, int(in.ExpectedEvents)) if err != nil { return nil, err } diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 553a331..823022e 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -121,10 +121,11 @@ func (s Test) Run() error { s.validateInputLines(t.InputLines) result, err := c.ExecuteTest(context.Background(), &pb.ExecuteTestRequest{ - SessionID: sessionID, - InputPlugin: t.InputPlugin, - InputLines: t.InputLines, - Events: b, + SessionID: sessionID, + InputPlugin: t.InputPlugin, + InputLines: t.InputLines, + Events: b, + ExpectedEvents: int32(len(t.ExpectedEvents)), }) if err != nil { return err diff --git a/internal/daemon/api/grpc/api.pb.go b/internal/daemon/api/grpc/api.pb.go index d4e0484..19a490b 100644 --- a/internal/daemon/api/grpc/api.pb.go +++ b/internal/daemon/api/grpc/api.pb.go @@ -1,12 +1,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.26.0 +// protoc-gen-go v1.25.0 // protoc v3.6.1 // source: api.proto package grpc import ( + proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -20,6 +21,10 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + type ShutdownRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -195,10 +200,11 @@ type ExecuteTestRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` - InputPlugin string `protobuf:"bytes,2,opt,name=input_plugin,json=inputPlugin,proto3" json:"input_plugin,omitempty"` - InputLines []string `protobuf:"bytes,3,rep,name=inputLines,proto3" json:"inputLines,omitempty"` - Events []byte `protobuf:"bytes,4,opt,name=events,proto3" json:"events,omitempty"` + SessionID string `protobuf:"bytes,1,opt,name=sessionID,proto3" json:"sessionID,omitempty"` + InputPlugin string `protobuf:"bytes,2,opt,name=input_plugin,json=inputPlugin,proto3" json:"input_plugin,omitempty"` + InputLines []string `protobuf:"bytes,3,rep,name=inputLines,proto3" json:"inputLines,omitempty"` + Events []byte `protobuf:"bytes,4,opt,name=events,proto3" json:"events,omitempty"` + ExpectedEvents int32 `protobuf:"varint,5,opt,name=expectedEvents,proto3" json:"expectedEvents,omitempty"` } func (x *ExecuteTestRequest) Reset() { @@ -261,6 +267,13 @@ func (x *ExecuteTestRequest) GetEvents() []byte { return nil } +func (x *ExecuteTestRequest) GetExpectedEvents() int32 { + if x != nil { + return x.ExpectedEvents + } + return 0 +} + type ExecuteTestResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -422,7 +435,7 @@ var file_api_proto_rawDesc = []byte{ 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x31, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x8d, 0x01, 0x0a, 0x12, + 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x22, 0xb5, 0x01, 0x0a, 0x12, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, @@ -431,41 +444,44 @@ var file_api_proto_rawDesc = []byte{ 0x67, 0x69, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x2f, 0x0a, 0x13, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, - 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, - 0x44, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, - 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x74, 0x73, 0x32, 0x95, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, - 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, - 0x0a, 0x09, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, - 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, - 0x0a, 0x0b, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0c, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, - 0x54, 0x65, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, - 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, - 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, - 0x52, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, - 0x75, 0x73, 0x62, 0x61, 0x65, 0x63, 0x6b, 0x2f, 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, - 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2d, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, - 0x6d, 0x6f, 0x6e, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, - 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x01, 0x28, 0x0c, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x65, + 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x73, 0x22, 0x2f, 0x0a, 0x13, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, + 0x2c, 0x0a, 0x14, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x32, 0x95, 0x02, + 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x3b, 0x0a, 0x08, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x15, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, + 0x65, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x65, 0x54, 0x65, 0x73, 0x74, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x19, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0c, + 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, + 0x65, 0x61, 0x72, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x54, 0x5a, 0x52, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x67, 0x6e, 0x75, 0x73, 0x62, 0x61, 0x65, 0x63, 0x6b, 0x2f, + 0x6c, 0x6f, 0x67, 0x73, 0x74, 0x61, 0x73, 0x68, 0x2d, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x2d, + 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x64, 0x61, 0x65, 0x6d, + 0x6f, 0x6e, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( diff --git a/internal/daemon/api/grpc/api.proto b/internal/daemon/api/grpc/api.proto index 7b07de4..cc201d4 100644 --- a/internal/daemon/api/grpc/api.proto +++ b/internal/daemon/api/grpc/api.proto @@ -30,6 +30,7 @@ message ExecuteTestRequest { string input_plugin = 2; repeated string inputLines = 3; bytes events = 4; + int32 expectedEvents = 5; } message ExecuteTestResponse { diff --git a/internal/daemon/api/grpc/api_grpc.pb.go b/internal/daemon/api/grpc/api_grpc.pb.go index b1dd4ac..faf5f85 100644 --- a/internal/daemon/api/grpc/api_grpc.pb.go +++ b/internal/daemon/api/grpc/api_grpc.pb.go @@ -11,7 +11,6 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // ControlClient is the client API for Control service. @@ -105,7 +104,7 @@ type UnsafeControlServer interface { } func RegisterControlServer(s grpc.ServiceRegistrar, srv ControlServer) { - s.RegisterService(&Control_ServiceDesc, srv) + s.RegisterService(&_Control_serviceDesc, srv) } func _Control_Shutdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { @@ -180,10 +179,7 @@ func _Control_TeardownTest_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } -// Control_ServiceDesc is the grpc.ServiceDesc for Control service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Control_ServiceDesc = grpc.ServiceDesc{ +var _Control_serviceDesc = grpc.ServiceDesc{ ServiceName: "grpc.Control", HandlerType: (*ControlServer)(nil), Methods: []grpc.MethodDesc{ diff --git a/internal/daemon/session/controller_test.go b/internal/daemon/session/controller_test.go index 017e166..49dba30 100644 --- a/internal/daemon/session/controller_test.go +++ b/internal/daemon/session/controller_test.go @@ -90,7 +90,7 @@ func TestSession(t *testing.T) { "some_random_key": "value", }, } - err = s.ExecuteTest("input", inputLines, inFields) + err = s.ExecuteTest("input", inputLines, inFields, 1) is.NoErr(err) is.True(file.Exists(filepath.Join(tempdir, "session", s.ID(), "lfv_inputs", "1", "fields.json"))) // lfv_inputs/1/fields.json diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 3585ecc..dd353cb 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -157,7 +157,7 @@ func (s *Session) createOutputPipelines(outputs []string) ([]pipeline.Pipeline, // ExecuteTest runs a test case set against the Logstash configuration, that has // been loaded previously with SetupTest. -func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents []map[string]interface{}) error { +func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents []map[string]interface{}, expectedEvents int) error { s.testexec++ pipelineName := fmt.Sprintf("lfv_input_%d", s.testexec) inputDir := filepath.Join(s.sessionDir, "lfv_inputs", strconv.Itoa(s.testexec)) @@ -194,7 +194,7 @@ func (s *Session) ExecuteTest(inputPlugin string, inputLines []string, inEvents pipeline.Ordered = "true" } pipelines := append(s.pipelines, pipeline) - err = s.logstashController.ExecuteTest(pipelines, len(inputLines)) + err = s.logstashController.ExecuteTest(pipelines, expectedEvents) if err != nil { return err } diff --git a/testdata/drop_and_split.yml b/testdata/drop_and_split.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/drop_and_split.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/drop_and_split/drop_and_split.conf b/testdata/drop_and_split/drop_and_split.conf new file mode 100644 index 0000000..4f46fb7 --- /dev/null +++ b/testdata/drop_and_split/drop_and_split.conf @@ -0,0 +1,26 @@ +input { + stdin { + id => "stdin" + } +} + +filter { + if [message] =~ /drop/ { + drop { + id => "drop" + } + } + + if [message] =~ /split/ { + split { + id => "split" + # field => "results" + } + } +} + +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/testcases/drop_and_split/drop.json b/testdata/testcases/drop_and_split/drop.json new file mode 100644 index 0000000..90ba68d --- /dev/null +++ b/testdata/testcases/drop_and_split/drop.json @@ -0,0 +1,25 @@ +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "normal input", + "drop this input", + "normal input again" + ], + "expected": [ + { + "message": "normal input" + }, + { + "message": "normal input again" + } + ] + } + ] +} diff --git a/testdata/testcases/drop_and_split/split.json b/testdata/testcases/drop_and_split/split.json new file mode 100644 index 0000000..781d7e3 --- /dev/null +++ b/testdata/testcases/drop_and_split/split.json @@ -0,0 +1,31 @@ +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "normal input", + "split this input\ninto multiple events", + "normal input again" + ], + "expected": [ + { + "message": "normal input" + }, + { + "message": "split this input" + }, + { + "message": "into multiple events" + }, + { + "message": "normal input again" + } + ] + } + ] +} From 6c2fa63b1f8085d9a6cea90ed91690bdbc6e7de8 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 7 May 2021 15:33:25 +0200 Subject: [PATCH 088/143] Add filter mock support --- go.mod | 2 +- go.sum | 4 +- integration_test.go | 8 ++ internal/app/daemon/run/run.go | 17 ++- internal/app/daemon_run.go | 5 +- internal/daemon/filtermock/filtermock.go | 100 ++++++++++++++++++ internal/daemon/logstashconfig/file.go | 16 +++ internal/daemon/pipeline/pipeline.go | 28 ++++- internal/daemon/pipeline/pipeline_test.go | 2 +- testdata/filtermock.yml | 2 + testdata/filtermock/filtermock.conf | 34 ++++++ testdata/mocks/filtermock.yml | 10 ++ testdata/testcases/filtermock/filtermock.json | 19 ++++ 13 files changed, 239 insertions(+), 8 deletions(-) create mode 100644 internal/daemon/filtermock/filtermock.go create mode 100644 testdata/filtermock.yml create mode 100644 testdata/filtermock/filtermock.conf create mode 100644 testdata/mocks/filtermock.yml create mode 100644 testdata/testcases/filtermock/filtermock.json diff --git a/go.mod b/go.mod index 73eee39..e2f5dd9 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/ahmetb/govvv v0.3.0 github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 - github.com/breml/logstash-config v0.4.4 + github.com/breml/logstash-config v0.4.5 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/hashicorp/packer v1.4.4 github.com/hpcloud/tail v1.0.0 diff --git a/go.sum b/go.sum index a8d0c19..ee90306 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/breml/logstash-config v0.4.4 h1:JpRTXVnbZ19nBTf7hIfYzgSSyc2+mpdmdlj9Jt4UfU8= -github.com/breml/logstash-config v0.4.4/go.mod h1:NaBkWLM71LaEUF/VoCAHMcQf0nAnOcPaaiRKKoRgPN0= +github.com/breml/logstash-config v0.4.5 h1:12WtKpvkNQTOnyssgKxPLCYP9ZOA826d6hoDUeUcAqk= +github.com/breml/logstash-config v0.4.5/go.mod h1:NaBkWLM71LaEUF/VoCAHMcQf0nAnOcPaaiRKKoRgPN0= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/integration_test.go b/integration_test.go index d93db3d..c020c62 100644 --- a/integration_test.go +++ b/integration_test.go @@ -113,6 +113,8 @@ func TestIntegration(t *testing.T) { // optional integration tests require additional logstash plugins, // which are not provided by a default installation. optional bool + + filterMock string }{ { name: "basic_pipeline", @@ -134,6 +136,11 @@ func TestIntegration(t *testing.T) { name: "codec_optional_test", optional: true, }, + { + name: "filtermock", + + filterMock: "testdata/mocks/filtermock.yml", + }, { name: "special_chars", }, @@ -156,6 +163,7 @@ func TestIntegration(t *testing.T) { "testdata/"+tc.name+".yml", "testdata/"+tc.name, "testdata/testcases/"+tc.name, + tc.filterMock, "@metadata", tc.debug, ) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 553a331..4d7d464 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -20,6 +20,7 @@ import ( "google.golang.org/grpc" pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" @@ -32,13 +33,14 @@ type Test struct { pipeline string pipelineBase string testcasePath string + filterMock string metadataKey string debug bool log logging.Logger } -func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath string, metadataKey string, debug bool) (Test, error) { +func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath, filterMock, metadataKey string, debug bool) (Test, error) { if !path.IsAbs(pipelineBase) { cwd, err := os.Getwd() if err != nil { @@ -51,6 +53,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath pipeline: pipeline, pipelineBase: pipelineBase, testcasePath: testcasePath, + filterMock: filterMock, metadataKey: metadataKey, debug: debug, log: log, @@ -69,7 +72,17 @@ func (s Test) Run() error { return err } - b, err := a.Zip() + m, err := filtermock.FromFile(s.filterMock) + if err != nil { + return err + } + + preprocessor := pipeline.NoopPreprocessor + if len(m) > 0 { + preprocessor = pipeline.ApplyMocksPreprocessor(m) + } + + b, err := a.ZipWithPreprocessor(preprocessor) if err != nil { return err } diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 6753ab5..c2eafb6 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -21,6 +21,8 @@ func makeDaemonRunCmd() *cobra.Command { _ = viper.BindPFlag("pipeline-base", cmd.Flags().Lookup("pipeline-base")) cmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) + cmd.Flags().String("filter-mock", "", "path to a yaml file containing the definition for the filter mocks.") + _ = viper.BindPFlag("filter-mock", cmd.Flags().Lookup("filter-mock")) cmd.Flags().Bool("debug", false, "enable debug mode; e.g. prevents stripping '__lfv' data from Logstash events") _ = viper.BindPFlag("debug", cmd.Flags().Lookup("debug")) cmd.Flags().String("metadata-key", "@metadata", "Key under which the content of the `@metadata` field is exposed in the returned events.") @@ -35,10 +37,11 @@ func runDaemonRun(_ *cobra.Command, args []string) error { pipeline := viper.GetString("pipeline") pipelineBase := viper.GetString("pipeline-base") testcaseDir := viper.GetString("testcase-dir") + filterMock := viper.GetString("filter-mock") debug := viper.GetBool("debug") metadataKey := viper.GetString("metadata-key") - t, err := run.New(socket, log, pipeline, pipelineBase, testcaseDir, metadataKey, debug) + t, err := run.New(socket, log, pipeline, pipelineBase, testcaseDir, filterMock, metadataKey, debug) if err != nil { return err } diff --git a/internal/daemon/filtermock/filtermock.go b/internal/daemon/filtermock/filtermock.go new file mode 100644 index 0000000..bed7631 --- /dev/null +++ b/internal/daemon/filtermock/filtermock.go @@ -0,0 +1,100 @@ +// Filter mocks allow to replace an existing filter, identified by its ID, in +// the config with a mock implementation. +// This comes in handy, if a filter does perform a call out to an external +// system e.g. lookup in Elasticsearch. +// Because the existing filter is replaced with whatever is present in +// `filter`, it is also possible to remove a filter by simple keep the +// `filter` empty (or not present). +package filtermock + +import ( + "fmt" + "io/ioutil" + + config "github.com/breml/logstash-config" + "github.com/breml/logstash-config/ast" + "github.com/breml/logstash-config/ast/astutil" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +type Mock struct { + // ID of the Logstash filter to be mocked. + ID string `json:"id" yaml:"id"` + + // Logstash configuration snippet of the replacement filter. E.g. + // + // # Constant lookup, does return the same result for each event + // mutate { + // replace => { + // "[field]" => "value" + // } + // } + Filter string `json:"filter,omitempty" yaml:"filter,omitempty"` +} + +func FromFile(filename string) (Mocks, error) { + if filename == "" { + return Mocks{}, nil + } + + mocks := []Mock{} + + body, err := ioutil.ReadFile(filename) + if err != nil { + return Mocks{}, err + } + + err = yaml.Unmarshal(body, &mocks) + if err != nil { + return Mocks{}, err + } + + filtermocks := make(Mocks, len(mocks)) + + for _, m := range mocks { + if m.Filter == "" { + filtermocks[m.ID] = nil + continue + } + + wrappedFilter := []byte(fmt.Sprintf("filter {\n%s\n}", m.Filter)) + cfg, err := config.Parse("", wrappedFilter) + if err != nil { + return Mocks{}, err + } + + var filter ast.Plugin + var recoverErr interface{} + + func() { + defer func() { + recoverErr = recover() + }() + filter = cfg.(ast.Config).Filter[0].BranchOrPlugins[0].(ast.Plugin) + }() + + if recoverErr != nil { + return Mocks{}, errors.Errorf("failed to parse mock filter: %s", m.Filter) + } + + filtermocks[m.ID] = &filter + } + + return filtermocks, nil +} + +type Mocks map[string]*ast.Plugin + +func (m Mocks) Walk(c *astutil.Cursor) { + id, _ := c.Plugin().ID() + + if replacement, ok := m[id]; ok { + if replacement == nil { + c.Delete() + return + } + replacement.Attributes = append(replacement.Attributes, ast.NewStringAttribute("id", id, ast.Bareword)) + c.Replace(replacement) + } +} diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 6286e06..4787c25 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -12,6 +12,7 @@ import ( "github.com/breml/logstash-config/ast/astutil" "github.com/pkg/errors" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" ) @@ -189,3 +190,18 @@ func (v *validator) walk(c *astutil.Cursor) { v.outputs++ } } + +func (f *File) ApplyMocks(m filtermock.Mocks) error { + err := f.parse() + if err != nil { + return err + } + + for i := 0; i < len(f.config.Filter); i++ { + f.config.Filter[i].BranchOrPlugins = astutil.ApplyPlugins(f.config.Filter[i].BranchOrPlugins, m.Walk) + } + + f.Body = []byte(f.config.String()) + + return nil +} diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index d7a7a4f..1c93307 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v2" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" ) @@ -118,7 +119,7 @@ func (a Archive) Validate() error { return nil } -func (a Archive) Zip() ([]byte, error) { +func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([]byte, error) { buf := new(bytes.Buffer) w := zip.NewWriter(buf) @@ -161,6 +162,12 @@ func (a Archive) Zip() ([]byte, error) { if err != nil { return nil, err } + + body, err = preprocess(body) + if err != nil { + return nil, err + } + _, err = f.Write(body) if err != nil { return nil, err @@ -175,3 +182,22 @@ func (a Archive) Zip() ([]byte, error) { return buf.Bytes(), nil } + +func NoopPreprocessor(body []byte) ([]byte, error) { + return body, nil +} + +func ApplyMocksPreprocessor(m filtermock.Mocks) func(body []byte) ([]byte, error) { + return func(body []byte) ([]byte, error) { + configFile := logstashconfig.File{ + Body: body, + } + + err := configFile.ApplyMocks(m) + if err != nil { + return body, err + } + + return configFile.Body, nil + } +} diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index fcaff9d..0ab85e9 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -161,7 +161,7 @@ func TestZip(t *testing.T) { return } - b, err := a.Zip() + b, err := a.ZipWithPreprocessor(pipeline.NoopPreprocessor) is.True(err != nil == test.wantZipBytesErr) // Zip error if test.wantZipBytesErr { diff --git a/testdata/filtermock.yml b/testdata/filtermock.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/filtermock.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/filtermock/filtermock.conf b/testdata/filtermock/filtermock.conf new file mode 100644 index 0000000..e099273 --- /dev/null +++ b/testdata/filtermock/filtermock.conf @@ -0,0 +1,34 @@ +input { + stdin { + id => "stdin" + } +} + +filter { + mutate { + id => "removeme" + add_field => { + "removeme" => "should not be present in output" + } + } + + mutate { + id => "mockme" + replace => { + "[message]" => "not mocket" + } + } + + mutate { + id => "keepme" + add_field => { + "keepme" => "visible in output" + } + } +} + +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/mocks/filtermock.yml b/testdata/mocks/filtermock.yml new file mode 100644 index 0000000..27fc9bd --- /dev/null +++ b/testdata/mocks/filtermock.yml @@ -0,0 +1,10 @@ +--- + +- id: removeme +- id: mockme + filter: | + mutate { + replace => { + "[message]" => "mocked" + } + } diff --git a/testdata/testcases/filtermock/filtermock.json b/testdata/testcases/filtermock/filtermock.json new file mode 100644 index 0000000..e2b3b67 --- /dev/null +++ b/testdata/testcases/filtermock/filtermock.json @@ -0,0 +1,19 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "input" + ], + "expected": [ + { + "message": "mocked", + "keepme": "visible in output" + } + ] + } + ] +} From da76c5b09dda2dfa107e889ae80668f9ff741177 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 28 May 2021 13:55:52 +0200 Subject: [PATCH 089/143] Fix multiple outputs from different pipelines --- internal/app/daemon/run/run.go | 87 +++++-------------- internal/daemon/controller/events.go | 13 +-- internal/daemon/session/files.go | 2 +- internal/testcase/testcase.go | 19 ++-- .../multiple_parallel_outputs/stage1.conf | 7 ++ .../multiple_parallel_outputs/stage2.conf | 7 ++ .../basic_pipeline_debug/testcase1.json | 2 + .../basic_pipeline_debug/testcase2.json | 1 + .../multiple_parallel_outputs.json | 76 ++++++---------- ...tiple_parallel_outputs_export_outputs.json | 80 +++++++++++++++++ 10 files changed, 154 insertions(+), 140 deletions(-) create mode 100644 testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index bf6c0ee..fa97880 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,7 +3,6 @@ package run import ( "context" "encoding/json" - "fmt" "net" "os" "path" @@ -145,13 +144,11 @@ func (s Test) Run() error { return err } - results, seenOutputs, err := s.postProcessResults(result.Results, t) + results, err := s.postProcessResults(result.Results, t) if err != nil { return err } - verifySeenOutputs(t, seenOutputs, liveObserver) - var events []logstash.Event for _, line := range results { var event logstash.Event @@ -205,33 +202,19 @@ func (s Test) validateInputLines(lines []string) { } } -func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]string, map[int][]string, error) { +func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]string, error) { var err error sort.Slice(results, func(i, j int) bool { - return gjson.Get(results[i], `__lfv_id`).Int() < gjson.Get(results[j], `__lfv_id`).Int() + idI := gjson.Get(results[i], `__lfv_id`).Int() + idJ := gjson.Get(results[j], `__lfv_id`).Int() + if idI == idJ { + return gjson.Get(results[i], `__lfv_out_passed`).String() < gjson.Get(results[j], `__lfv_out_passed`).String() + } + return idI < idJ }) - lastID := "n/a" - testcase := -1 - seenOutputs := make(map[int][]string, len(results)) for i := 0; i < len(results); i++ { - // Collect expected outputs - id := gjson.Get(results[i], "__lfv_id").String() - if id != lastID { - testcase++ - } - - seenOutputs[testcase] = append(seenOutputs[testcase], gjson.Get(results[i], "__lfv_metadata.__lfv_out_passed").String()) - - if id == lastID { - // Remove duplicate event, processed by different output - results = append(results[:i], results[i+1:]...) - i-- - continue - } - lastID = id - // Export metadata if t.ExportMetadata { metadata := gjson.Get(results[i], "__lfv_metadata") @@ -246,14 +229,14 @@ func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]st if len(md) > 0 { results[i], err = sjson.Set(results[i], s.metadataKey, md) if err != nil { - return nil, nil, err + return nil, err } } } } results[i], err = sjson.Delete(results[i], "__lfv_metadata") if err != nil { - return nil, nil, err + return nil, err } // No cleanup if debug is set @@ -263,7 +246,14 @@ func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]st results[i], err = sjson.Delete(results[i], "__lfv_id") if err != nil { - return nil, nil, err + return nil, err + } + + if !t.ExportOutputs { + results[i], err = sjson.Delete(results[i], "__lfv_out_passed") + if err != nil { + return nil, err + } } tags := []string{} @@ -281,46 +271,9 @@ func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]st results[i], err = sjson.Set(results[i], "tags", tags) } if err != nil { - return nil, nil, err - } - } - - return results, seenOutputs, nil -} - -func verifySeenOutputs(tcs testcase.TestCaseSet, seenOutputs map[int][]string, liveObserver observer.Property) { - for i := 0; i < len(tcs.TestCases); i++ { - if len(tcs.TestCases[i].ExpectedOutputs) == 0 { - continue - } - - sort.Strings(tcs.TestCases[i].ExpectedOutputs) - sort.Strings(seenOutputs[i]) - - comparisonResult := lfvobserver.ComparisonResult{ - Status: true, - Name: fmt.Sprintf("Comparing Logstash outputs of message %d of %d", i+1, len(tcs.TestCases)), - Path: filepath.Base(tcs.File), - EventIndex: i, + return nil, err } - - if !equal(tcs.TestCases[i].ExpectedOutputs, seenOutputs[i]) { - comparisonResult.Status = false - comparisonResult.Explain = fmt.Sprintf("Expected Logstash outputs: %v\nActually seen Logstash outputs: %v", tcs.TestCases[i].ExpectedOutputs, seenOutputs[i]) - } - - liveObserver.Update(comparisonResult) } -} -func equal(a, b []string) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if v != b[i] { - return false - } - } - return true + return results, nil } diff --git a/internal/daemon/controller/events.go b/internal/daemon/controller/events.go index 3204934..d0fa370 100644 --- a/internal/daemon/controller/events.go +++ b/internal/daemon/controller/events.go @@ -2,13 +2,10 @@ package controller import ( "sync" - - "github.com/tidwall/gjson" ) type events struct { events []string - receivedUniqueIDs map[string]struct{} completeFirstTime bool expected int mutex *sync.Mutex @@ -16,9 +13,8 @@ type events struct { func newEvents() *events { return &events{ - events: make([]string, 0, 100), - receivedUniqueIDs: make(map[string]struct{}, 100), - mutex: &sync.Mutex{}, + events: make([]string, 0, 100), + mutex: &sync.Mutex{}, } } @@ -27,15 +23,13 @@ func (e *events) append(event string) { defer e.mutex.Unlock() e.events = append(e.events, event) - id := gjson.Get(event, `__lfv_id`).String() - e.receivedUniqueIDs[id] = struct{}{} } func (e *events) isCompleteFirstTime() bool { e.mutex.Lock() defer e.mutex.Unlock() - if e.expected == len(e.receivedUniqueIDs) && !e.completeFirstTime { + if e.expected == len(e.events) && !e.completeFirstTime { e.completeFirstTime = true return true } @@ -49,7 +43,6 @@ func (e *events) reset(expected int) { e.expected = expected e.events = make([]string, 0, 100) - e.receivedUniqueIDs = make(map[string]struct{}, 100) e.completeFirstTime = false } diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index 79ffc21..afc08b0 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -9,7 +9,7 @@ const outputPipeline = `input { filter { mutate { add_tag => [ "__lfv_out_{{ .PipelineOrigName }}_passed" ] - add_field => { "[@metadata][__lfv_out_passed]" => "{{ .PipelineOrigName }}" } + add_field => { "[__lfv_out_passed]" => "{{ .PipelineOrigName }}" } } } diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 8acd16b..3eee4d5 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -76,12 +76,18 @@ type TestCaseSet struct { ExpectedEvents []logstash.Event `json:"expected" yaml:"expected"` // ExportMetadata controls if the metadata of the event processed - // by Logstash is returned The metadata is contained in the field + // by Logstash is returned. The metadata is contained in the field // `[@metadata]` in the Logstash event. // If the metadata is exported, the respective fields are compared // with the expected result of the testcase as well. (default: false) ExportMetadata bool `json:"export_metadata" yaml:"export_metadata"` + // ExportOutputs controls if the ID of the output, a particular event has + // emitted by, is kept in the event or not. + // If this is enabled, the expected event needs to contain a field named + // __lfv_out_passed which contains the ID of the Logstash output. + ExportOutputs bool `json:"export_outputs" yaml:"export_outputs"` + // TestCases is a slice of test cases, which include at minimum // a pair of an input and an expected event // Optionally other information regarding the test case @@ -113,17 +119,6 @@ type TestCase struct { // process. ExpectedEvents []logstash.Event `json:"expected" yaml:"expected"` - // The unique ID of the output plugins in the tested configuration, where - // the event leaves Logstash. (optional) - // If no value is present or the list is empty, this criteria is not verified. - // If a value is present, the event is expected to be processed by - // the exact list of expected outputs. - // By listing multiple output plugins it is possible to test Logstash - // configurations with multiple (conditional) outputs: - // e.g. save the events to elasticsearch and, if the threshold is above x, - // additionally send an email. - ExpectedOutputs []string `json:"expected_outputs" yaml:"expected_outputs"` - // Description contains an optional description of the test case // which will be printed while the tests are executed. Description string `json:"description" yaml:"description"` diff --git a/testdata/multiple_parallel_outputs/stage1.conf b/testdata/multiple_parallel_outputs/stage1.conf index 9ff5d38..6894c24 100644 --- a/testdata/multiple_parallel_outputs/stage1.conf +++ b/testdata/multiple_parallel_outputs/stage1.conf @@ -4,6 +4,13 @@ input { } } +filter { + mutate { + id => "stage1_mutate" + add_tag => [ "stage1" ] + } +} + output { stdout { id => "stage1_stdout" diff --git a/testdata/multiple_parallel_outputs/stage2.conf b/testdata/multiple_parallel_outputs/stage2.conf index efb0e6c..bec6b28 100644 --- a/testdata/multiple_parallel_outputs/stage2.conf +++ b/testdata/multiple_parallel_outputs/stage2.conf @@ -5,6 +5,13 @@ input { } } +filter { + mutate { + id => "stage2_mutate" + add_tag => [ "stage2" ] + } +} + output { file { id => "stage2_file" diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index 46574b1..bf85412 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -15,6 +15,7 @@ "expected": [ { "__lfv_id": "0", + "__lfv_out_passed": "stdout", "message": "test case message", "tags": [ "__lfv_in_passed", @@ -26,6 +27,7 @@ }, { "__lfv_id": "1", + "__lfv_out_passed": "stdout", "message": "test case message 2", "tags": [ "__lfv_in_passed", diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index 062c1d2..65dab9d 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -19,6 +19,7 @@ "hidden_field": "hidden value" }, "__lfv_id": "0", + "__lfv_out_passed": "stdout", "first": "1", "message": "", "second": "2", diff --git a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json index c14c1a9..07e25fc 100644 --- a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json +++ b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json @@ -8,87 +8,63 @@ "testcases": [ { "input": [ - "expected_outputs not defined (one output)" + "one output" ], "expected": [ { - "message": "expected_outputs not defined (one output)" + "message": "one output", + "tags": [ "stage1" ] } ] }, { "input": [ - "expected_outputs not defined (multiple outputs)" + "multiple outputs" ], "expected": [ { - "message": "expected_outputs not defined (multiple outputs)" - } - ] - }, - { - "input": [ - "expected_outputs defined (one output)" - ], - "expected": [ + "message": "multiple outputs", + "tags": [ "stage1" ] + }, { - "message": "expected_outputs defined (one output)" + "message": "multiple outputs", + "tags": [ "stage1" ] } - ], - "expected_outputs": [ "stage1_stdout" ] + ] }, { "input": [ - "expected_outputs defined (multiple output)" + "stage2" ], "expected": [ { - "message": "expected_outputs defined (multiple output)" - } - ], - "expected_outputs": [ "stage1_stdout", "stage1_file" ] - }, - { - "input": [ - "expected_outputs not defined (stage2)" - ], - "expected": [ + "message": "stage2", + "tags": [ "stage1" ] + }, { - "message": "expected_outputs not defined (stage2)" + "message": "stage2", + "tags": [ "stage1", "stage2" ] } ] }, { "input": [ - "expected_outputs not defined (multiple outputs and stage2)" + "multiple outputs and stage2" ], "expected": [ { - "message": "expected_outputs not defined (multiple outputs and stage2)" - } - ] - }, - { - "input": [ - "expected_outputs defined (stage2)" - ], - "expected": [ + "message": "multiple outputs and stage2", + "tags": [ "stage1" ] + }, { - "message": "expected_outputs defined (stage2)" - } - ], - "expected_outputs": [ "stage1_stdout", "stage2_file" ] - }, - { - "input": [ - "expected_outputs defined (multiple output and stage2)" - ], - "expected": [ + "message": "multiple outputs and stage2", + "tags": [ "stage1" ] + }, { - "message": "expected_outputs defined (multiple output and stage2)" + "message": "multiple outputs and stage2", + "tags": [ "stage1", "stage2" ] } - ], - "expected_outputs": [ "stage1_stdout", "stage1_file", "stage2_file" ] + ] } ] } diff --git a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json new file mode 100644 index 0000000..181c420 --- /dev/null +++ b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json @@ -0,0 +1,80 @@ + +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "export_outputs": true, + "testcases": [ + { + "input": [ + "one output" + ], + "expected": [ + { + "__lfv_out_passed": "stage1_stdout", + "message": "one output", + "tags": [ "stage1" ] + } + ] + }, + { + "input": [ + "multiple outputs" + ], + "expected": [ + { + "__lfv_out_passed": "stage1_file", + "message": "multiple outputs", + "tags": [ "stage1" ] + }, + { + "__lfv_out_passed": "stage1_stdout", + "message": "multiple outputs", + "tags": [ "stage1" ] + } + ] + }, + { + "input": [ + "stage2" + ], + "expected": [ + { + "__lfv_out_passed": "stage1_stdout", + "message": "stage2", + "tags": [ "stage1" ] + }, + { + "__lfv_out_passed": "stage2_file", + "message": "stage2", + "tags": [ "stage1", "stage2" ] + } + ] + }, + { + "input": [ + "multiple outputs and stage2" + ], + "expected": [ + { + "__lfv_out_passed": "stage1_file", + "message": "multiple outputs and stage2", + "tags": [ "stage1" ] + }, + { + "__lfv_out_passed": "stage1_stdout", + "message": "multiple outputs and stage2", + "tags": [ "stage1" ] + }, + { + "__lfv_out_passed": "stage2_file", + "message": "multiple outputs and stage2", + "tags": [ "stage1", "stage2" ] + } + ] + } + ] +} From cc27e226b51d6f722d94f987e6787270e6b31f6b Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 11 Jun 2021 15:28:35 +0200 Subject: [PATCH 090/143] Fix case when 0 events are expected (drop all) --- internal/daemon/controller/controller.go | 7 +++++++ testdata/testcases/drop_and_split/drop_all.json | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 testdata/testcases/drop_and_split/drop_all.json diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 126c112..787b60c 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -130,6 +130,9 @@ func (c *Controller) ExecuteTest(pipelines pipeline.Pipelines, expectedEvents in } func (c *Controller) GetResults() ([]string, error) { + // Check if complete right away for the special case, where no event is expected. + c.checkComplete() + err := c.stateMachine.waitForState(stateReadyForTest) if err != nil { return nil, err @@ -197,6 +200,10 @@ func (c *Controller) writePipelines(pipelines ...pipeline.Pipeline) error { func (c *Controller) ReceiveEvent(event string) { c.receivedEvents.append(event) + c.checkComplete() +} + +func (c *Controller) checkComplete() { if c.receivedEvents.isCompleteFirstTime() { go func() { _ = c.stateMachine.waitForState(stateRunningTest) diff --git a/testdata/testcases/drop_and_split/drop_all.json b/testdata/testcases/drop_and_split/drop_all.json new file mode 100644 index 0000000..9dd7637 --- /dev/null +++ b/testdata/testcases/drop_and_split/drop_all.json @@ -0,0 +1,17 @@ +{ + "fields": { + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "drop this input", + "drop this input" + ], + "expected": [] + } + ] +} From 3beb4adb861cd99fd83347eaaf71f67375a01c05 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 25 Jun 2021 10:29:24 +0200 Subject: [PATCH 091/143] Add support plain logstash config without pipeline config create implicit logstash pipeline --- integration_test.go | 25 +++++- internal/app/daemon/run/run.go | 84 +++++++++++++++---- internal/app/daemon_run.go | 10 ++- .../basic_logstash_config_dir/filter.conf | 6 ++ .../input_output.conf | 11 +++ testdata/basic_logstash_config_file.conf | 18 ++++ .../basic_logstash_config_dir/testcase1.json | 33 ++++++++ .../testcase1.json | 33 ++++++++ 8 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 testdata/basic_logstash_config_dir/filter.conf create mode 100644 testdata/basic_logstash_config_dir/input_output.conf create mode 100644 testdata/basic_logstash_config_file.conf create mode 100644 testdata/testcases/basic_logstash_config_dir/testcase1.json create mode 100644 testdata/testcases/basic_logstash_config_file.conf/testcase1.json diff --git a/integration_test.go b/integration_test.go index 5d30f04..b84a6c4 100644 --- a/integration_test.go +++ b/integration_test.go @@ -114,11 +114,21 @@ func TestIntegration(t *testing.T) { // which are not provided by a default installation. optional bool + withoutPipeline bool + filterMock string }{ { name: "basic_pipeline", }, + { + name: "basic_logstash_config_dir", + withoutPipeline: true, + }, + { + name: "basic_logstash_config_file.conf", + withoutPipeline: true, + }, { name: "drop_and_split", }, @@ -166,11 +176,22 @@ func TestIntegration(t *testing.T) { if tc.optional && os.Getenv("INTEGRATION_TEST_OPTIONAL") != "1" { t.Skipf("optional integration test %q skipped, enable with env var `INTEGRATION_TEST_OPTIONAL=1`", tc.name) } + + pipeline := "testdata/" + tc.name + ".yml" + pipelineBaseDir := "testdata/" + tc.name + logstashConfig := "" + if tc.withoutPipeline { + pipeline = "" + pipelineBaseDir = "" + logstashConfig = "testdata/" + tc.name + } + client, err := run.New( filepath.Join(tempdir, "integration_test.socket"), log, - "testdata/"+tc.name+".yml", - "testdata/"+tc.name, + pipeline, + pipelineBaseDir, + logstashConfig, "testdata/testcases/"+tc.name, tc.filterMock, "@metadata", diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index fa97880..7dee18b 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,6 +3,7 @@ package run import ( "context" "encoding/json" + "io/ioutil" "net" "os" "path" @@ -18,6 +19,7 @@ import ( "github.com/tidwall/gjson" "github.com/tidwall/sjson" "google.golang.org/grpc" + "gopkg.in/yaml.v2" pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" @@ -29,18 +31,19 @@ import ( ) type Test struct { - socket string - pipeline string - pipelineBase string - testcasePath string - filterMock string - metadataKey string - debug bool + socket string + pipeline string + pipelineBase string + logstashConfig string + testcasePath string + filterMock string + metadataKey string + debug bool log logging.Logger } -func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath, filterMock, metadataKey string, debug bool) (Test, error) { +func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConfig, testcasePath, filterMock, metadataKey string, debug bool) (Test, error) { if !path.IsAbs(pipelineBase) { cwd, err := os.Getwd() if err != nil { @@ -49,18 +52,30 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, testcasePath pipelineBase = filepath.Join(cwd, pipelineBase) } return Test{ - socket: socket, - pipeline: pipeline, - pipelineBase: pipelineBase, - testcasePath: testcasePath, - filterMock: filterMock, - metadataKey: metadataKey, - debug: debug, - log: log, + socket: socket, + pipeline: pipeline, + pipelineBase: pipelineBase, + logstashConfig: logstashConfig, + testcasePath: testcasePath, + filterMock: filterMock, + metadataKey: metadataKey, + debug: debug, + log: log, }, nil } func (s Test) Run() error { + if s.logstashConfig != "" { + pipelineFile, err := s.createImplicitPipeline() + if err != nil { + return err + } + defer os.RemoveAll(filepath.Dir(pipelineFile)) + + s.pipeline = pipelineFile + s.pipelineBase = "" + } + a, err := pipeline.New(s.pipeline, s.pipelineBase) if err != nil { return err @@ -191,6 +206,43 @@ func (s Test) Run() error { return nil } +func (s Test) createImplicitPipeline() (string, error) { + fi, err := os.Stat(s.logstashConfig) + if err != nil { + return "", errors.Wrap(err, "failed to read logstash config") + } + + pipelineBaseDir, err := ioutil.TempDir("", "lfv-pipeline-*") + if err != nil { + return "", errors.Wrap(err, "failed to create temporary directory for implicit pipeline") + } + + if fi.IsDir() { + s.logstashConfig = filepath.Join(s.logstashConfig, "*") + } + + pipelines := pipeline.Pipelines{ + pipeline.Pipeline{ + ID: "lfv_implicit", + Config: s.logstashConfig, + Workers: 1, + }, + } + + body, err := yaml.Marshal(pipelines) + if err != nil { + return "", err + } + + pipelineFile := filepath.Join(pipelineBaseDir, "pipelines.yml") + err = ioutil.WriteFile(pipelineFile, body, 0600) + if err != nil { + return "", errors.Wrap(err, "failed to write implicit pipelines.yml file") + } + + return pipelineFile, nil +} + func (s Test) validateInputLines(lines []string) { for _, line := range lines { _, doubleQuoteErr := astutil.Quote(line, ast.DoubleQuoted) diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index c2eafb6..3b7e7c8 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -1,6 +1,7 @@ package app import ( + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -19,6 +20,8 @@ func makeDaemonRunCmd() *cobra.Command { _ = viper.BindPFlag("pipeline", cmd.Flags().Lookup("pipeline")) cmd.Flags().String("pipeline-base", "", "base directory for relative paths in the pipelines.yml") _ = viper.BindPFlag("pipeline-base", cmd.Flags().Lookup("pipeline-base")) + cmd.Flags().String("logstash-config", "", "path of the Logstash config for use, if no pipelines.yml exists (mutual exclusive with --pipeline flag).") + _ = viper.BindPFlag("logstash-config", cmd.Flags().Lookup("logstash-config")) cmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) cmd.Flags().String("filter-mock", "", "path to a yaml file containing the definition for the filter mocks.") @@ -36,12 +39,17 @@ func runDaemonRun(_ *cobra.Command, args []string) error { log := viper.Get("logger").(logging.Logger) pipeline := viper.GetString("pipeline") pipelineBase := viper.GetString("pipeline-base") + logstashConfig := viper.GetString("logstash-config") testcaseDir := viper.GetString("testcase-dir") filterMock := viper.GetString("filter-mock") debug := viper.GetBool("debug") metadataKey := viper.GetString("metadata-key") - t, err := run.New(socket, log, pipeline, pipelineBase, testcaseDir, filterMock, metadataKey, debug) + if pipeline != "" && logstashConfig != "" { + return errors.New("--pipeline and --logstash-config flags are mutual exclusive") + } + + t, err := run.New(socket, log, pipeline, pipelineBase, logstashConfig, testcaseDir, filterMock, metadataKey, debug) if err != nil { return err } diff --git a/testdata/basic_logstash_config_dir/filter.conf b/testdata/basic_logstash_config_dir/filter.conf new file mode 100644 index 0000000..bec8dfc --- /dev/null +++ b/testdata/basic_logstash_config_dir/filter.conf @@ -0,0 +1,6 @@ +filter { + mutate { + id => mutate + add_tag => [ "sut_passed" ] + } +} diff --git a/testdata/basic_logstash_config_dir/input_output.conf b/testdata/basic_logstash_config_dir/input_output.conf new file mode 100644 index 0000000..0c44f7d --- /dev/null +++ b/testdata/basic_logstash_config_dir/input_output.conf @@ -0,0 +1,11 @@ +input { + stdin { + id => stdin + } +} + +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/basic_logstash_config_file.conf b/testdata/basic_logstash_config_file.conf new file mode 100644 index 0000000..fbb4273 --- /dev/null +++ b/testdata/basic_logstash_config_file.conf @@ -0,0 +1,18 @@ +input { + stdin { + id => stdin + } +} + +filter { + mutate { + id => mutate + add_tag => [ "sut_passed" ] + } +} + +output { + stdout { + id => "stdout" + } +} diff --git a/testdata/testcases/basic_logstash_config_dir/testcase1.json b/testdata/testcases/basic_logstash_config_dir/testcase1.json new file mode 100644 index 0000000..24eda32 --- /dev/null +++ b/testdata/testcases/basic_logstash_config_dir/testcase1.json @@ -0,0 +1,33 @@ +{ + "fields": { + "type": "syslog" + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "test case message", + "test case message 2" + ], + "expected": [ + { + "message": "test case message", + "tags": [ + "sut_passed" + ], + "type": "syslog" + }, + { + "message": "test case message 2", + "tags": [ + "sut_passed" + ], + "type": "syslog" + } + ] + } + ] +} diff --git a/testdata/testcases/basic_logstash_config_file.conf/testcase1.json b/testdata/testcases/basic_logstash_config_file.conf/testcase1.json new file mode 100644 index 0000000..24eda32 --- /dev/null +++ b/testdata/testcases/basic_logstash_config_file.conf/testcase1.json @@ -0,0 +1,33 @@ +{ + "fields": { + "type": "syslog" + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "testcases": [ + { + "input": [ + "test case message", + "test case message 2" + ], + "expected": [ + { + "message": "test case message", + "tags": [ + "sut_passed" + ], + "type": "syslog" + }, + { + "message": "test case message 2", + "tags": [ + "sut_passed" + ], + "type": "syslog" + } + ] + } + ] +} From a0227f425583ba8ef4541464e6ef47bd871c7171 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 25 Jun 2021 11:37:37 +0200 Subject: [PATCH 092/143] Add --add-missing-id flag Modify the Logstash config under test such that every plugin has an ID. --- integration_test.go | 5 ++- internal/app/daemon/run/run.go | 6 ++-- internal/app/daemon_run.go | 5 ++- internal/daemon/logstashconfig/file.go | 33 +++++++++++++------ internal/daemon/logstashconfig/file_test.go | 17 ++++++++-- internal/daemon/pipeline/pipeline.go | 4 +-- internal/daemon/pipeline/pipeline_test.go | 5 ++- testdata/basic_pipeline/main/main.conf | 6 ++-- .../basic_pipeline_debug/main/dir/main2.conf | 2 +- 9 files changed, 59 insertions(+), 24 deletions(-) diff --git a/integration_test.go b/integration_test.go index b84a6c4..1984a33 100644 --- a/integration_test.go +++ b/integration_test.go @@ -115,11 +115,13 @@ func TestIntegration(t *testing.T) { optional bool withoutPipeline bool + addMissingID bool filterMock string }{ { - name: "basic_pipeline", + name: "basic_pipeline", + addMissingID: true, }, { name: "basic_logstash_config_dir", @@ -196,6 +198,7 @@ func TestIntegration(t *testing.T) { tc.filterMock, "@metadata", tc.debug, + tc.addMissingID, ) is.NoErr(err) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 7dee18b..6feba89 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -39,11 +39,12 @@ type Test struct { filterMock string metadataKey string debug bool + addMissingID bool log logging.Logger } -func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConfig, testcasePath, filterMock, metadataKey string, debug bool) (Test, error) { +func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConfig, testcasePath, filterMock, metadataKey string, debug, addMissingID bool) (Test, error) { if !path.IsAbs(pipelineBase) { cwd, err := os.Getwd() if err != nil { @@ -60,6 +61,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConf filterMock: filterMock, metadataKey: metadataKey, debug: debug, + addMissingID: addMissingID, log: log, }, nil } @@ -82,7 +84,7 @@ func (s Test) Run() error { } // TODO: ensure, that IDs are also unique for the whole set of pipelines - err = a.Validate() + err = a.Validate(s.addMissingID) if err != nil { return err } diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 3b7e7c8..0fe9c99 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -30,6 +30,8 @@ func makeDaemonRunCmd() *cobra.Command { _ = viper.BindPFlag("debug", cmd.Flags().Lookup("debug")) cmd.Flags().String("metadata-key", "@metadata", "Key under which the content of the `@metadata` field is exposed in the returned events.") _ = viper.BindPFlag("metadata-key", cmd.Flags().Lookup("metadata-key")) + cmd.Flags().Bool("add-missing-id", false, "add implicit id for the plugins in the Logstash config if they are missing") + _ = viper.BindPFlag("add-missing-id", cmd.Flags().Lookup("add-missing-id")) return cmd } @@ -44,12 +46,13 @@ func runDaemonRun(_ *cobra.Command, args []string) error { filterMock := viper.GetString("filter-mock") debug := viper.GetBool("debug") metadataKey := viper.GetString("metadata-key") + addMissingID := viper.GetBool("add-missing-id") if pipeline != "" && logstashConfig != "" { return errors.New("--pipeline and --logstash-config flags are mutual exclusive") } - t, err := run.New(socket, log, pipeline, pipelineBase, logstashConfig, testcaseDir, filterMock, metadataKey, debug) + t, err := run.New(socket, log, pipeline, pipelineBase, logstashConfig, testcaseDir, filterMock, metadataKey, debug, addMissingID) if err != nil { return err } diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 4787c25..a4e39c3 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -143,13 +143,15 @@ func (o *outputPipelineReplacer) walk(c *astutil.Cursor) { c.Replace(ast.NewPlugin("pipeline", ast.NewArrayAttribute("send_to", ast.NewStringAttribute("", outputName, ast.DoubleQuoted)))) } -func (f *File) Validate() (inputs int, outputs int, err error) { +func (f *File) Validate(addMissingID bool) (inputs int, outputs int, err error) { err = f.parse() if err != nil { return 0, 0, err } - v := validator{} + v := validator{ + addMissingID: addMissingID, + } for i := range f.config.Input { v.pluginType = ast.Input @@ -171,17 +173,16 @@ func (f *File) Validate() (inputs int, outputs int, err error) { } type validator struct { - noIDs []string - pluginType ast.PluginType - inputs int - outputs int + noIDs []string + pluginType ast.PluginType + inputs int + outputs int + count int + addMissingID bool } func (v *validator) walk(c *astutil.Cursor) { - _, err := c.Plugin().ID() - if err != nil { - v.noIDs = append(v.noIDs, c.Plugin().Name()) - } + v.count++ if v.pluginType == ast.Input && c.Plugin().Name() != "pipeline" { v.inputs++ @@ -189,6 +190,18 @@ func (v *validator) walk(c *astutil.Cursor) { if v.pluginType == ast.Output && c.Plugin().Name() != "pipeline" { v.outputs++ } + + _, err := c.Plugin().ID() + if err != nil { + if v.addMissingID { + plugin := c.Plugin() + plugin.Attributes = append(plugin.Attributes, ast.NewStringAttribute("id", fmt.Sprintf("%s-%d", c.Plugin().Name(), v.count), ast.DoubleQuoted)) + + c.Replace(plugin) + } else { + v.noIDs = append(v.noIDs, c.Plugin().Name()) + } + } } func (f *File) ApplyMocks(m filtermock.Mocks) error { diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index 7f5796c..f0b82f4 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -1,6 +1,7 @@ package logstashconfig_test import ( + "fmt" "path/filepath" "strings" "testing" @@ -212,13 +213,13 @@ output { stdout { id => testid } file { id => file } pipeline { id => pipeline } } for _, test := range cases { - t.Run(test.name, func(t *testing.T) { + t.Run(fmt.Sprintf("validate-%s", test.name), func(t *testing.T) { f := logstashconfig.File{ Name: "filename.conf", Body: []byte(test.config), } - inputs, outputs, err := f.Validate() + inputs, outputs, err := f.Validate(false) compareErr(t, test.wantErr, err) if test.wantInputs != inputs { t.Errorf("expected %d inputs, got %d", test.wantInputs, inputs) @@ -227,6 +228,18 @@ output { stdout { id => testid } file { id => file } pipeline { id => pipeline } t.Errorf("expected %d outputs, got %d", test.wantOutputs, outputs) } }) + + t.Run(fmt.Sprintf("validate-with-fix-%s", test.name), func(t *testing.T) { + f := logstashconfig.File{ + Name: "filename.conf", + Body: []byte(test.config), + } + + _, _, err := f.Validate(true) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) } } diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 1c93307..87eea9f 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -74,7 +74,7 @@ func processNestedKeys(pipelines Pipelines) { } } -func (a Archive) Validate() error { +func (a Archive) Validate(addMissingID bool) error { var inputs, outputs int for _, pipeline := range a.Pipelines { files, err := doublestar.Glob(filepath.Join(a.BasePath, pipeline.Config)) @@ -103,7 +103,7 @@ func (a Archive) Validate() error { Body: body, } - in, out, err := configFile.Validate() + in, out, err := configFile.Validate(addMissingID) if err != nil { return err } diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index 0ab85e9..58672ba 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -89,8 +89,11 @@ func TestValidate(t *testing.T) { a, err := pipeline.New(test.pipeline, test.basePath) is.NoErr(err) - err = a.Validate() + err = a.Validate(false) is.True(err != nil == test.wantValidateErr) // Validate error + + err = a.Validate(true) + is.NoErr(err) }) } } diff --git a/testdata/basic_pipeline/main/main.conf b/testdata/basic_pipeline/main/main.conf index 088a16c..2e8a03f 100644 --- a/testdata/basic_pipeline/main/main.conf +++ b/testdata/basic_pipeline/main/main.conf @@ -6,12 +6,10 @@ input { filter { mutate { - id => mutate + # id commented out intentionally + # id => mutate add_tag => [ "sut_passed" ] } - # foobar { - # id => foobar - # } } output { diff --git a/testdata/basic_pipeline_debug/main/dir/main2.conf b/testdata/basic_pipeline_debug/main/dir/main2.conf index 4ca4d61..152a43a 100644 --- a/testdata/basic_pipeline_debug/main/dir/main2.conf +++ b/testdata/basic_pipeline_debug/main/dir/main2.conf @@ -3,4 +3,4 @@ filter { id => mutate2 add_tag => [ "main2_passed" ] } -} \ No newline at end of file +} From 38554d567811c206ad785682bd175c207ba649dc Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 9 Jul 2021 15:09:55 +0200 Subject: [PATCH 093/143] Typo --- internal/testcase/testcase.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 3eee4d5..86df773 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -89,9 +89,8 @@ type TestCaseSet struct { ExportOutputs bool `json:"export_outputs" yaml:"export_outputs"` // TestCases is a slice of test cases, which include at minimum - // a pair of an input and an expected event - // Optionally other information regarding the test case - // may be supplied. + // a pair of an input and an expected event. + // Optionally other information regarding the test case may be supplied. TestCases []TestCase `json:"testcases" yaml:"testcases"` // Events contains the fields for each event. This fields is filled From 63916fc2629b213bfcfcd4a86a383ee11e16c3e8 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 9 Jul 2021 15:10:20 +0200 Subject: [PATCH 094/143] Add instructions about Daemon mode / Version 2.0 to README.md --- README.md | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 205 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7bc5689..d05e2ca 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,27 @@ * [Introduction](#introduction) * [Installing](#installing) +* [Standalone and Daemon mode (since Version 2.0)](#standalone-and-daemon-mode-since-version-20) * [Examples](#examples) * [Syslog messages](#syslog-messages) + * [Beats messages](#beats-messages) * [JSON messages](#json-messages) + * [Version 2.0 (Daemon mode only)](#version-20-daemon-mode-only) * [Test case file reference](#test-case-file-reference) + * [Standalone mode / Logstash Filter Verifier before version 2.0](#standalone-mode--logstash-filter-verifier-before-version-20) + * [Daemon mode](#daemon-mode) + * [Filter mock](#filter-mock) * [Migrating to the current test case file format](#migrating-to-the-current-test-case-file-format) * [Notes](#notes) * [The \-\-sockets flag](#the---sockets-flag) * [The \-\-logstash\-arg flag](#the---logstash-arg-flag) * [Logstash compatibility](#logstash-compatibility) + * [Standalone mode](#standalone-mode) + * [Daemon mode](#daemon-mode) * [Windows compatibility](#windows-compatibility) + * [Plugin ID (Daemon mode)](#plugin-id-daemon-mode) +* [Development](#development) + * [Dependencies](#dependencies) * [Known limitations and future work](#known-limitations-and-future-work) * [License](#license) @@ -91,6 +102,31 @@ command: $ make install PREFIX=$HOME +## Standalone and Daemon mode (since Version 2.0) + +Since version 2.0, there are two different modes, Logstash Filter Verifier +can be operated in. + +1. **Standalone**: In this mode, for each test run a fresh instance of Logstash + is started in the background by Logstash Filter Verifier. If a user wants to + frequently execute test cases, this might be slow and tedious. + This has been to only mode available in versions prior to 2.0. +2. **Daemon**: In this mode, Logstash Filter Verifier is executed twice in + parallel (preferably in two different shells). One instance is the daemon. + The daemon starts and controls the Logstash instances (there might be + multiple). This daemon process is normally left running for the time the user + is working on the Logstash configuration and testing it with Logstash Filter + Verifier. + For each execution of the test cases, another instance Logstash Filter + Verifier is started (client). The client collects the current state of the + Logstash configuration as well as the test cases and passes them to the + daemon. The daemon reloads the configuration in one of the running Logstash + instances, executes the test cases and returns the result back to the client. + The client shows the results to the user and exits, while the daemon + continues to run and waits for the next client to submit a test execution + job. + + ## Examples The examples that follow build upon each other and do not only show @@ -199,7 +235,7 @@ This command will run this test case file through Logstash Filter Verifier (replace all "path/to" with the actual paths to the files, obviously): - $ path/to/logstash-filter-verifier path/to/syslog.json path/to/filters + $ path/to/logstash-filter-verifier standalone path/to/syslog.json path/to/filters If the test is successful, Logstash Filter Verifier will terminate with a zero exit code and (almost) no output. If the test fails it'll @@ -215,10 +251,11 @@ expected event either. Additional fields can be ignored with the ### Beats messages -In [Beats](https://www.elastic.co/guide/en/beats/libbeat/current/beats-reference.html) +In [Beats](https://www.elastic.co/guide/en/beats/libbeat/current/beats-reference.html) you can also specify fields to control the behavior of the Logstash pipeline. An example in Beats config might look like this: -``` + +```yaml - input_type: log paths: ["/var/log/work/*.log"] fields: @@ -228,24 +265,30 @@ An example in Beats config might look like this: fields: type: trace ``` -The Logstash configuration would then look like this to check the + +The Logstash configuration would then look like this to check the given field: -``` + +```none if ([fields][type] == "openlog") { Do something for type openlog ``` + But, in order to test the behavior with LFV you have to give it like so: -``` + +```none { "fields": { "[fields][type]": "openlog" }, ``` -The reason is, that Beats is inserting by default declared fields under a -root element `fields`, while the LFV is just considering it as a configuration + +The reason is, that Beats is inserting by default declared fields under a +root element `fields`, while the LFV is just considering it as a configuration option. Alternatively you can tell Beats to insert the configured fields on root: -``` + +```yaml fields_under_root: true ``` @@ -265,6 +308,7 @@ should mimic on the Logstash Filter Verifier side too. Use `codec` for that: Sample with JSON format: + ```json { "fields": { @@ -292,6 +336,7 @@ Sample with JSON format: ``` Sample with YAML format: + ```yaml fields: type: "app" @@ -336,8 +381,76 @@ There are a few points to be made here: run, we ignore that field with the `ignore` property. +### Version 2.0 (Daemon mode only) + +With version 2.0 of Logstash Filter Verifier (Daemon mode) some new features +have been added: + +* **Export of @metadata**: + There is out of the box support to let Logstash Filter Verifier export + the values in the (otherwise hidden) `@metadata` field of the event. + This allows to write test cases, which take the values in the `@metadata` + field into account. (see `export_metadata` in test case file reference) +* **Pipeline configuration**: + Logstash Filter Verifier in Daemon mode accepts complete Logstash pipelines + as configuration. This includes the localization of the Logstash configuration + files through the paths provided in the `pipelines.yml` file and replacing all + input and output filters with the respective parts to execute the tests. +* **Multiple pipelines** + The pipeline configuration may consist of multiple pipelines, that might be + linked ([pipeline to pipeline communication](https://www.elastic.co/guide/en/logstash/current/pipeline-to-pipeline.html)) + or independent pipelines. +* **Filter mock** + Filter mock allows to replace (or remove) filter plugins in the Logstash + configuration under test, that do not work during or that would potentially + not produce the expected results test execution. Examples for such filter + plugins are mainly plugins, that perform some sort of call out to a third + party system, for example to look up data ([elasticsearch](https://www.elastic.co/guide/en/logstash/current/plugins-filters-elasticsearch.html), + [http](https://www.elastic.co/guide/en/logstash/current/plugins-filters-http.html), + [jdbc](https://www.elastic.co/guide/en/logstash/current/plugins-filters-jdbc_static.html), + [memcached](https://www.elastic.co/guide/en/logstash/current/plugins-filters-memcached.html)). + In order to to be able to produce reproducible results in the test cases, + these plugins can be replaced with mocks. In particular the [mutate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html) + and the [translate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-translate.html) + filters have proven to be helpful as replacements. + +In order to execute a test case in daemon mode, first the daemon needs to be +started (e.g. in its own terminal or shell): + + $ path/to/logstash-filter-verifier daemon start + +Next, a single test case run can be launched with (in second terminal/shell): + + $ path/to/logstash-filter-verifier daemon run --pipeline path/to/pipelines.yml --pipeline-base base/path/of/logstash-configuration --testcase-dir path/to/testcases + +The flag `--pipeline-base` is required, if the `pipelines.yml` file does use +relative paths for the actual logstash pipeline configuration. + +If the Logstash configuration under test does not contain `id` attributes for +all plugins, the `--add-missing-id` flag instructs Logstash Filter Verifier to +add the missing `id` attributes on the fly. + +As an example, we can execute the `basic_pipeline` test case from this +repository. + +Let us assume, the following setup: + +* The logstash filter verifier binary is available at `/usr/local/bin/logstash-fitler-verifier`. +* This repository is available at `/tmp/logstash-filter-verifier` (e.g. with `git clone https://github.com/magnusbaeck/logstash-filter-verifier `). + +The command to run the `basic_pipeline` example would look like this (the daemon +needs to be started beforehand): + + $ /usr/local/bin/logstash-fitler-verifier daemon run --pipeline /tmp/logstash-filter-verifier/testdata/basic_pipeline.yml --pipeline-base /tmp/logstash-filter-verifier/testdata/basic_pipeline --testcase-dir /tmp/logstash-filter-verifier/testdata/testcases/basic_pipeline --add-missing-id + +More examples (e.g. multiple pipelines and filter mock) can be found in the +`testcases/` folder of this repository. + + ## Test case file reference +### Standalone mode / Logstash Filter Verifier before version 2.0 + Test case files are JSON files containing a single object. That object may have the following properties: @@ -380,6 +493,59 @@ may have the following properties: progress messages. +### Daemon mode + +Test case files for the Daemon mode have the same fields as for Standalone mode +with the following changes/additions + +Additional fields: + +* `input_plugin`: The unique [ID](https://www.elastic.co/guide/en/logstash/7.10/plugins-inputs-file.html#plugins-inputs-file-id) + of the input plugin in the tested configuration, where the test input is + coming from. This is necessary, if a setup with multiple inputs is tested, + which either have different codecs or are part of different pipelines. +* `export_metadata`: Controls if the metadata of the event processed by Logstash + is returned. The metadata is contained in the field `[@metadata]` in the + Logstash event. If the metadata is exported, the respective fields are + compared with the expected result of the testcase as well. (default: false) +* `export_outputs`: Controls if the ID of the output, a particular event has + emitted by, is kept in the event or not. If this is enabled, the expected + event needs to contain a field named `_lfv_out_passed` which contains the ID + of the Logstash output. +* `testcases`: + * `event`: Local fields, only added to the events of this test case. These + fields overwrite global fields. + +Ignored / obsolete fields: + +* `codec` + + +#### Filter mock + +The filter mock config file (yaml) consists of an array of filter mock elements. +Each filter mock element consists for the plugin id that should be replaced as +well as the Logstash configuration string that should be used as the +replacement. This string might be empty. In this case, the mocked filter is just +removed from the Logstash configuration. + +Example: + +```yaml +- id: removeme +- id: mockme + filter: | + mutate { + replace => { + "[message]" => "mocked" + } + } +``` + +Given the above filter mock configuration, the plugin with the ID `removeme` is +removed from the Logstash configuration. The plugin with the ID `mockme` is +replaced with the given Logstash configuration. + ## Migrating to the current test case file format Originally the `input` and `expected` configuration keys were at the @@ -405,7 +571,7 @@ case file by hand afterwards. ## Notes -### The `--sockets` flag +### The `--sockets` flag (Standalone mode) The command line flag `--sockets` allows to use unix domain sockets instead of stdin to send the input to Logstash. The advantage of this approach is, that @@ -439,6 +605,8 @@ to be provided to Logstash Filter Verifier: ### Logstash compatibility +#### Standalone mode + Different versions of Logstash behave slightly differently and changes in Logstash may require changes in Logstash Filter Verifier. Upon startup, the program will attempt to auto-detect the version of @@ -450,7 +618,14 @@ code in the JVM so it took several seconds. To avoid this you can use the `--logstash-version` flag to tell Logstash Filter Verifier which version of Logstash it should expect. Example: - logstash-filter-verifier ... --logstash-version 2.4.0 + logstash-filter-verifier standalone ... --logstash-version 2.4.0 + + +#### Daemon mode + +In order to use Logstash Filter Verifier in Daemon mode, at least Logstash +version 6.7.x is required. Older versions of Logstash are not supported and do +not work with Daemon mode. ### Windows compatibility @@ -466,6 +641,22 @@ are a couple of known quirks that are easy to work around: tool to use. +### Plugin ID (Daemon mode) + +The Daemon mode of Logstash Filter Verifier expects each plugin in the Logstash +configuration to have a unique [ID](https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html#plugins-filters-mutate-id). +In order to test an existing Logstash configuration, which lacks these ID, there +are two options: + +1. Permanently add the missing ID to the configuration. This can either be done + by hand or with the help of [`mustache`](https://github.com/breml/logstash-config). + + mustache lint --auto-fix-id + +2. Let Logstash Filter Verifier add the ID temporarily just for the execution + of the test cases by adding the flag `--add-missing-id`. + + ## Development ### Dependencies @@ -487,6 +678,6 @@ present: ## License -This software is copyright 2015–2020 by Magnus Bäck <> -and licensed under the Apache 2.0 license. See the LICENSE file for the full -license text. +This software is copyright 2015–2021 by Magnus Bäck <> and +other contributors and licensed under the Apache 2.0 license. See the LICENSE +file for the full license text. From 77bb2492911f7bd1e00d14e3bff66b773050deea Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 30 Jul 2021 15:47:59 +0200 Subject: [PATCH 095/143] Rename testcases.event to testcases.fields in test case config --- README.md | 2 +- internal/testcase/testcase.go | 4 ++-- testdata/testcases/testcases_event/testcases.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d05e2ca..790e909 100644 --- a/README.md +++ b/README.md @@ -513,7 +513,7 @@ Additional fields: event needs to contain a field named `_lfv_out_passed` which contains the ID of the Logstash output. * `testcases`: - * `event`: Local fields, only added to the events of this test case. These + * `fields`: Local fields, only added to the events of this test case. These fields overwrite global fields. Ignored / obsolete fields: diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 86df773..9b386a1 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -111,7 +111,7 @@ type TestCase struct { // Local fields, only added to the events of this test case. // These fields overwrite global fields. - Event logstash.FieldSet `json:"event" yaml:"event"` + InputFields logstash.FieldSet `json:"fields" yaml:"fields"` // ExpectedEvents contains a slice of expected events to be // compared to the actual events produced by the Logstash @@ -211,7 +211,7 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { // Global Fields first. tcs.Events = append(tcs.Events, tcs.InputFields) // Merge with test case fields, eventually overwriting global fields. - for k, v := range tc.Event { + for k, v := range tc.InputFields { tcs.Events[len(tcs.Events)-1][k] = v } } diff --git a/testdata/testcases/testcases_event/testcases.json b/testdata/testcases/testcases_event/testcases.json index 316e80f..4911984 100644 --- a/testdata/testcases/testcases_event/testcases.json +++ b/testdata/testcases/testcases_event/testcases.json @@ -9,7 +9,7 @@ }, "testcases": [ { - "event": { + "fields": { "string": "string value", "number": 123, "bool": true, From e479a50161d87f7323cd7906f0fb1ce3684c9071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 5 Aug 2021 20:21:26 +0200 Subject: [PATCH 096/143] Fix broken Go Report Card URL The Go Report Card URL was broken. Switching over to HTTPS and inserting a "github.com" path component fixed things. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 790e909..45ef8f4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Logstash Filter Verifier ![build](https://github.com/magnusbaeck/logstash-filter-verifier/actions/workflows/test.yml/badge.svg?event=push) -[![GoReportCard](http://goreportcard.com/badge/magnusbaeck/logstash-filter-verifier)](http://goreportcard.com/report/magnusbaeck/logstash-filter-verifier) +[![GoReportCard](https://goreportcard.com/badge/github.com/magnusbaeck/logstash-filter-verifier)](https://goreportcard.com/report/github.com/magnusbaeck/logstash-filter-verifier) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/magnusbaeck/logstash-filter-verifier/master/LICENSE) * [Introduction](#introduction) From b6f8a808508854313623f644544b4f0db02552f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Fri, 6 Aug 2021 23:31:45 +0200 Subject: [PATCH 097/143] Makefile: Use Go 1.16 for release builds Since commit 77de4a67 go.mod states that Go 1.16 is required. This might not be true since the code builds fine with Go 1.15 but there's no reason to use anything but 1.16 to produce the release builds anyway. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 77da14c..816696a 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ OS_NAME := $(shell uname -s) endif # The Docker image to use when building release images. -GOLANG_DOCKER_IMAGE := golang:1.15.5 +GOLANG_DOCKER_IMAGE := golang:1.16.7 INSTALL := install From 8473358c4558c50454681d0b3c788e7cdf5ab3bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Fri, 6 Aug 2021 23:32:07 +0200 Subject: [PATCH 098/143] Makefile: Disable Windows targets Windows builds currently fail with internal/daemon/instance/logstash/instance.go:94:45: unknown field 'Setpgid' in struct literal of type syscall.SysProcAttr and to be able to run "make release-tarballs" we disable the Windows targets in the makefile. It remains to be decided whether we should fix this error (probably by disabling daemon mode) or drop Windows support. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 816696a..23be31f 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ PROGRAM := logstash-filter-verifier # List of all GOOS_GOARCH combinations that we should build release # binaries for. See https://golang.org/doc/install/source#environment # for all available combinations. -TARGETS := darwin_amd64 linux_386 linux_amd64 windows_386 windows_amd64 +TARGETS := darwin_amd64 linux_386 linux_amd64 VERSION := $(shell git describe --tags --always) From ed63c99a958180cc2b409906ce5f55c739822853 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 16 Aug 2021 08:27:26 +0200 Subject: [PATCH 099/143] Update logstash-config to v0.5.1 Fixes bug with multiline strings in logstash config https://github.com/magnusbaeck/logstash-filter-verifier/issues/96#issuecomment-898247999 --- go.mod | 6 +++--- go.sum | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 889554e..00a345d 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/ahmetb/govvv v0.3.0 github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 - github.com/breml/logstash-config v0.4.5 + github.com/breml/logstash-config v0.5.1 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.5.2 github.com/hashicorp/packer v1.4.4 @@ -24,7 +24,7 @@ require ( github.com/mikefarah/yaml/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.1.1 + github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.5.1 github.com/tidwall/gjson v1.6.8 @@ -43,5 +43,5 @@ require ( gopkg.in/mattn/go-colorable.v0 v0.1.0 // indirect gopkg.in/mattn/go-isatty.v0 v0.0.4 // indirect gopkg.in/mattn/go-runewidth.v0 v0.0.4 // indirect - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 17bb10b..59c865b 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/breml/logstash-config v0.4.5 h1:12WtKpvkNQTOnyssgKxPLCYP9ZOA826d6hoDUeUcAqk= -github.com/breml/logstash-config v0.4.5/go.mod h1:NaBkWLM71LaEUF/VoCAHMcQf0nAnOcPaaiRKKoRgPN0= +github.com/breml/logstash-config v0.5.1 h1:g9n1KnARE/462o2YTl/LrvJ465OX5JyWFYGcxjVcfCI= +github.com/breml/logstash-config v0.5.1/go.mod h1:3vVy6pRfnuI1f4f3ZlH8g3/lCQ0B7N6yOoaUfYALtBY= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -185,6 +185,7 @@ github.com/hashicorp/go-getter v1.3.1-0.20190906090232-a0f878cb75da/go.mod h1:7q github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79/go.mod h1:09jT3Y/OIsjTjQ2+3bkVNPDKqWcGIYYvjB2BEKVUdvc= github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= @@ -246,8 +247,9 @@ github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -376,8 +378,8 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -615,8 +617,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 219b8525f7cb322fb6dfbd7f7657ddd2c06e620f Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 18 Aug 2021 17:13:45 +0200 Subject: [PATCH 100/143] Fix handling of input codecs for cases, where multiple files contain input plugins --- internal/daemon/session/session.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index d763166..8f575a4 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -87,7 +87,9 @@ func (s *Session) setupTest(pipelines pipeline.Pipelines, configFiles []logstash if err != nil { return err } - s.inputPluginCodecs = inputCodecs + for id, codec := range inputCodecs { + s.inputPluginCodecs[id] = codec + } outputs, err := configFile.ReplaceOutputs() if err != nil { From db20b891e365e8c254ff91555a97f8c91a2c3c61 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 18 Aug 2021 17:15:52 +0200 Subject: [PATCH 101/143] Remove default value for --pipeline flag --- internal/app/app.go | 1 - internal/app/daemon_run.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 3d93a42..549ba68 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -35,7 +35,6 @@ func Execute(version string, stdout, stderr io.Writer) int { // Setup default values viper.SetDefault("loglevel", "WARNING") viper.SetDefault("socket", "/tmp/logstash-filter-verifier.sock") - viper.SetDefault("pipeline", "/etc/logstash/pipelines.yml") viper.SetDefault("logstash.path", "/usr/share/logstash/bin/logstash") viper.SetDefault("inflight-shutdown-timeout", 10*time.Second) viper.SetDefault("shutdown-timeout", 3*time.Second) diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 0fe9c99..5d71a11 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -16,7 +16,7 @@ func makeDaemonRunCmd() *cobra.Command { RunE: runDaemonRun, } - cmd.Flags().StringP("pipeline", "p", "", "location of the pipelines.yml file to be processed") + cmd.Flags().StringP("pipeline", "p", "", "location of the pipelines.yml file to be processed (e.g. /etc/logstash/pipelines.yml)") _ = viper.BindPFlag("pipeline", cmd.Flags().Lookup("pipeline")) cmd.Flags().String("pipeline-base", "", "base directory for relative paths in the pipelines.yml") _ = viper.BindPFlag("pipeline-base", cmd.Flags().Lookup("pipeline-base")) From a095183e3f4321e46873aefd763025f8a54b3f21 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 1 Sep 2021 09:12:26 +0200 Subject: [PATCH 102/143] Fix how pipeline base path is determined (#123) * default value for --pipeline-base stays "" (empty string) * if the config paths found in the pipelines.yml are absolute paths, the pipeline base path setting is ignored. * else (if the config paths found in the pipelines.yml are relative paths) * if the pipeline base path is not set (this is, if it is the empty string ""), the absolute path of the pipelines.yml file is used as the pipeline base path (the assumption is, that relative paths in the pipelines.yml are relative to the location of the file it self). * if the pipeline base path is relative, it is converted to an absolute path by prefixing it with the current working directory. * if the pipeline base path is absolute, it is used as is. the resulting absolute pipeline base path is prefixed to the relative config paths found in the pipelines.yml See also: https://github.com/magnusbaeck/logstash-filter-verifier/issues/96#issuecomment-904878269 --- internal/app/daemon/run/run.go | 10 ++++++++-- internal/daemon/pipeline/pipeline.go | 12 ++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 6feba89..5729702 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "net" "os" - "path" "path/filepath" "sort" "strings" @@ -45,7 +44,14 @@ type Test struct { } func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConfig, testcasePath, filterMock, metadataKey string, debug, addMissingID bool) (Test, error) { - if !path.IsAbs(pipelineBase) { + if pipelineBase == "" { + absPipeline, err := filepath.Abs(pipeline) + if err != nil { + return Test{}, err + } + pipelineBase = filepath.Dir(absPipeline) + } + if !filepath.IsAbs(pipelineBase) { cwd, err := os.Getwd() if err != nil { return Test{}, err diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 87eea9f..169cee3 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -77,7 +77,11 @@ func processNestedKeys(pipelines Pipelines) { func (a Archive) Validate(addMissingID bool) error { var inputs, outputs int for _, pipeline := range a.Pipelines { - files, err := doublestar.Glob(filepath.Join(a.BasePath, pipeline.Config)) + configFilepath := filepath.Join(a.BasePath, pipeline.Config) + if filepath.IsAbs(pipeline.Config) { + configFilepath = pipeline.Config + } + files, err := doublestar.Glob(configFilepath) if err != nil { return err } @@ -137,7 +141,11 @@ func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([ } for _, pipeline := range a.Pipelines { - files, err := doublestar.Glob(filepath.Join(a.BasePath, pipeline.Config)) + configFilepath := filepath.Join(a.BasePath, pipeline.Config) + if filepath.IsAbs(pipeline.Config) { + configFilepath = pipeline.Config + } + files, err := doublestar.Glob(configFilepath) if err != nil { return nil, err } From f22e89f907f26c8b943177c79a73732eb38a0007 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 8 Sep 2021 18:13:10 +0200 Subject: [PATCH 103/143] Fix pipelines.yml issues (#125) * Fix pipeline.id with dash * Fix path.config pointing to dir in pipelines.yml Solves issue mentioned in: https://github.com/magnusbaeck/logstash-filter-verifier/issues/93#issuecomment-913725182 --- .../daemon/instance/logstash/processors.go | 3 +- .../instance/logstash/processors_test.go | 38 +++++++++++++++++++ internal/daemon/pipeline/pipeline.go | 20 ++++++++++ internal/daemon/pipeline/pipeline_test.go | 7 ++++ .../testdata/pipelines_basic_dir_name.yml | 4 ++ testdata/basic_pipeline_debug.yml | 4 +- testdata/conditional_output.yml | 2 +- 7 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 internal/daemon/instance/logstash/processors_test.go create mode 100644 internal/daemon/pipeline/testdata/pipelines_basic_dir_name.yml diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go index 6bef4e2..bed8d40 100644 --- a/internal/daemon/instance/logstash/processors.go +++ b/internal/daemon/instance/logstash/processors.go @@ -116,7 +116,8 @@ func extractPipelines(in string) []string { } pipelines := strings.Split(in[1:len(in)-1], ",") for i := range pipelines { - pipelines[i] = strings.Trim(pipelines[i], " :") + pipelines[i] = strings.TrimLeft(pipelines[i], " :") + pipelines[i] = strings.Trim(pipelines[i], `"`) } return pipelines } diff --git a/internal/daemon/instance/logstash/processors_test.go b/internal/daemon/instance/logstash/processors_test.go new file mode 100644 index 0000000..1bd2f7b --- /dev/null +++ b/internal/daemon/instance/logstash/processors_test.go @@ -0,0 +1,38 @@ +package logstash + +import ( + "testing" + + "github.com/matryer/is" +) + +func TestExtractPipelines(t *testing.T) { + cases := []struct { + name string + input string + + want []string + }{ + { + name: "single word pipeline id", + input: `[:output, :stdin, :lfv_output_stdout, :lfv_ukPSsPZk_main, :lfv_input_1]`, + + want: []string{"output", "stdin", "lfv_output_stdout", "lfv_ukPSsPZk_main", "lfv_input_1"}, + }, + { + name: "pipeline id with dash", + input: `[:output, :stdin, :lfv_output_stdout, :"lfv_ukPSsPZk_main-test", :lfv_input_1]`, + + want: []string{"output", "stdin", "lfv_output_stdout", "lfv_ukPSsPZk_main-test", "lfv_input_1"}, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + is := is.New(t) + + got := extractPipelines(test.input) + is.Equal(test.want, got) + }) + } +} diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 169cee3..444e0d2 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -77,6 +77,9 @@ func processNestedKeys(pipelines Pipelines) { func (a Archive) Validate(addMissingID bool) error { var inputs, outputs int for _, pipeline := range a.Pipelines { + if strings.HasSuffix(pipeline.Config, "/") { + pipeline.Config += "*" + } configFilepath := filepath.Join(a.BasePath, pipeline.Config) if filepath.IsAbs(pipeline.Config) { configFilepath = pipeline.Config @@ -86,6 +89,13 @@ func (a Archive) Validate(addMissingID bool) error { return err } for _, file := range files { + fi, err := os.Stat(file) + if err != nil { + return err + } + if fi.IsDir() { + continue + } var relFile string if path.IsAbs(a.BasePath) { relFile = strings.TrimPrefix(file, a.BasePath) @@ -141,6 +151,9 @@ func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([ } for _, pipeline := range a.Pipelines { + if strings.HasSuffix(pipeline.Config, "/") { + pipeline.Config += "*" + } configFilepath := filepath.Join(a.BasePath, pipeline.Config) if filepath.IsAbs(pipeline.Config) { configFilepath = pipeline.Config @@ -150,6 +163,13 @@ func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([ return nil, err } for _, file := range files { + fi, err := os.Stat(file) + if err != nil { + return nil, err + } + if fi.IsDir() { + continue + } var relFile string if path.IsAbs(a.BasePath) { relFile = strings.TrimPrefix(file, a.BasePath) diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index 58672ba..d7a8600 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -130,6 +130,13 @@ func TestZip(t *testing.T) { wantFiles: 2, }, + { + name: "success basic pipeline dir name", + pipeline: "testdata/pipelines_basic_dir_name.yml", + basePath: "testdata/", + + wantFiles: 2, + }, { name: "success advanced pipeline", pipeline: "testdata/pipelines_advanced.yml", diff --git a/internal/daemon/pipeline/testdata/pipelines_basic_dir_name.yml b/internal/daemon/pipeline/testdata/pipelines_basic_dir_name.yml new file mode 100644 index 0000000..e4378d2 --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_basic_dir_name.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "folder/" diff --git a/testdata/basic_pipeline_debug.yml b/testdata/basic_pipeline_debug.yml index a9f230e..f8a90c7 100644 --- a/testdata/basic_pipeline_debug.yml +++ b/testdata/basic_pipeline_debug.yml @@ -1,2 +1,2 @@ -- pipeline.id: main - path.config: "main/**/*.conf" +- pipeline.id: main-debug + path.config: "main/**/*" diff --git a/testdata/conditional_output.yml b/testdata/conditional_output.yml index 3b10926..932d836 100644 --- a/testdata/conditional_output.yml +++ b/testdata/conditional_output.yml @@ -1,2 +1,2 @@ - pipeline.id: main - path.config: "main/*.conf" + path.config: "main/" From 482dceaa725f6c51eefa7a3283eea7deae589306 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 8 Sep 2021 22:32:52 +0200 Subject: [PATCH 104/143] Add input plugin with dash in id (integration tests) Verify https://github.com/magnusbaeck/logstash-filter-verifier/issues/93#issuecomment-914189695 --- internal/daemon/api/grpc/api.pb.go | 2 +- internal/daemon/filtermock/filtermock.go | 2 +- internal/daemon/logstashconfig/file.go | 2 +- internal/daemon/logstashconfig/file_test.go | 2 +- testdata/basic_pipeline/main/dir/main2.conf | 2 +- testdata/basic_pipeline_debug/main/main.conf | 2 +- testdata/testcases/basic_pipeline_debug/testcase1.json | 2 +- testdata/testcases/basic_pipeline_debug/testcase2.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/daemon/api/grpc/api.pb.go b/internal/daemon/api/grpc/api.pb.go index 19a490b..a770d73 100644 --- a/internal/daemon/api/grpc/api.pb.go +++ b/internal/daemon/api/grpc/api.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 -// protoc v3.6.1 +// protoc v3.15.7 // source: api.proto package grpc diff --git a/internal/daemon/filtermock/filtermock.go b/internal/daemon/filtermock/filtermock.go index bed7631..0c6aa9a 100644 --- a/internal/daemon/filtermock/filtermock.go +++ b/internal/daemon/filtermock/filtermock.go @@ -94,7 +94,7 @@ func (m Mocks) Walk(c *astutil.Cursor) { c.Delete() return } - replacement.Attributes = append(replacement.Attributes, ast.NewStringAttribute("id", id, ast.Bareword)) + replacement.Attributes = append(replacement.Attributes, ast.NewStringAttribute("id", id, ast.DoubleQuoted)) c.Replace(replacement) } } diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index a4e39c3..f6662a0 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -88,7 +88,7 @@ func (r replaceInputsWalker) replaceInputs(c *astutil.Cursor) { } var attrs []ast.Attribute - attrs = append(attrs, ast.NewStringAttribute("address", fmt.Sprintf("%s_%s_%s", "__lfv_input", r.idPrefix, id), ast.Bareword)) + attrs = append(attrs, ast.NewStringAttribute("address", fmt.Sprintf("%s_%s_%s", "__lfv_input", r.idPrefix, id), ast.DoubleQuoted)) for _, attr := range c.Plugin().Attributes { if attr == nil { diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index f0b82f4..fb4617d 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -55,7 +55,7 @@ func TestReplaceInputs(t *testing.T) { wantConfig: `input { pipeline { - address => __lfv_input_prefix_testid + address => "__lfv_input_prefix_testid" } } `, diff --git a/testdata/basic_pipeline/main/dir/main2.conf b/testdata/basic_pipeline/main/dir/main2.conf index 4ca4d61..152a43a 100644 --- a/testdata/basic_pipeline/main/dir/main2.conf +++ b/testdata/basic_pipeline/main/dir/main2.conf @@ -3,4 +3,4 @@ filter { id => mutate2 add_tag => [ "main2_passed" ] } -} \ No newline at end of file +} diff --git a/testdata/basic_pipeline_debug/main/main.conf b/testdata/basic_pipeline_debug/main/main.conf index 2ae94f2..d9b0735 100644 --- a/testdata/basic_pipeline_debug/main/main.conf +++ b/testdata/basic_pipeline_debug/main/main.conf @@ -1,6 +1,6 @@ input { stdin { - id => stdin + id => "stdin-with-dash" } } diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index bf85412..2c0c59e 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -5,7 +5,7 @@ "ignore": [ "@timestamp" ], - "input_plugin": "stdin", + "input_plugin": "stdin-with-dash", "testcases": [ { "input": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index 65dab9d..aefb168 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -6,7 +6,7 @@ "ignore": [ "@timestamp" ], - "input_plugin": "stdin", + "input_plugin": "stdin-with-dash", "export_metadata": true, "testcases": [ { From ffbc4e82e0d8ace2d7e0eb8ccbee51fa2f7d023f Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 8 Sep 2021 22:33:08 +0200 Subject: [PATCH 105/143] Fix injecting of nested fields Fixes: #126 --- internal/testcase/testcase.go | 17 +++++++++++----- .../testcases/testcases_event/testcases.json | 20 +++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 9b386a1..e2aab59 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -134,6 +134,9 @@ var ( func (tcs *TestCaseSet) convertBracketFields() error { // Convert fields in input fields tcs.InputFields = parseAllBracketProperties(tcs.InputFields) + for i := range tcs.TestCases { + tcs.TestCases[i].InputFields = parseAllBracketProperties(tcs.TestCases[i].InputFields) + } // Convert fields in expected events for i, expected := range tcs.ExpectedEvents { @@ -193,12 +196,21 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { if err = tcs.InputFields.IsValid(); err != nil { return nil, err } + + // Convert bracket fields + if err := tcs.convertBracketFields(); err != nil { + return nil, err + } + tcs.IgnoredFields = append(tcs.IgnoredFields, defaultIgnoredFields...) sort.Strings(tcs.IgnoredFields) + tcs.descriptions = make([]string, len(tcs.ExpectedEvents)) + for range tcs.InputLines { tcs.Events = append(tcs.Events, tcs.InputFields) } + for _, tc := range tcs.TestCases { // Add event, if there are no input lines. if len(tc.InputLines) == 0 { @@ -220,11 +232,6 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { } } - // Convert bracket fields - if err := tcs.convertBracketFields(); err != nil { - return nil, err - } - log.Debugf("Current TestCaseSet after converting fields: %+v", tcs) return &tcs, nil } diff --git a/testdata/testcases/testcases_event/testcases.json b/testdata/testcases/testcases_event/testcases.json index 4911984..7be3eb9 100644 --- a/testdata/testcases/testcases_event/testcases.json +++ b/testdata/testcases/testcases_event/testcases.json @@ -13,7 +13,14 @@ "string": "string value", "number": 123, "bool": true, - "overwritten_field": "event" + "overwritten_field": "event", + "parent": { + "key": "parent_key_value", + "child": { + "key": "parent_child_key_value" + } + }, + "[logstash_parent][key]": "logstash_parent_key_value" }, "expected": [ { @@ -21,7 +28,16 @@ "number": 123, "bool": true, "global_field": "global", - "overwritten_field": "event" + "overwritten_field": "event", + "parent": { + "key": "parent_key_value", + "child": { + "key": "parent_child_key_value" + } + }, + "logstash_parent": { + "key": "logstash_parent_key_value" + } } ] } From cf3b21c29897326d762027ce1ede97032f11be6f Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 13 Sep 2021 20:58:00 +0200 Subject: [PATCH 106/143] Support int for Logstash literals Fixes: #133 --- internal/logstash/fieldset.go | 2 ++ internal/logstash/fieldset_test.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/internal/logstash/fieldset.go b/internal/logstash/fieldset.go index c7b2797..f16273f 100644 --- a/internal/logstash/fieldset.go +++ b/internal/logstash/fieldset.go @@ -62,6 +62,8 @@ func serializeAsLogstashLiteral(k string, v interface{}) ([]string, []string, er return []string{k}, []string{fmt.Sprintf("%v", v)}, nil } return []string{k}, []string{fmt.Sprintf("%f", v)}, nil + case int: + return []string{k}, []string{fmt.Sprintf("%d", v)}, nil case string: return []string{k}, []string{fmt.Sprintf("%q", v)}, nil case []interface{}: diff --git a/internal/logstash/fieldset_test.go b/internal/logstash/fieldset_test.go index 50a5bcc..f82b057 100644 --- a/internal/logstash/fieldset_test.go +++ b/internal/logstash/fieldset_test.go @@ -83,6 +83,14 @@ func TestLogstashHash(t *testing.T) { `{ "a" => 1234567890.123000 }`, nil, }, + // Integers should work as well. + { + FieldSet{ + "a": 1234, + }, + `{ "a" => 1234 }`, + nil, + }, // Single string value is okay { FieldSet{ From cd06e977b8e2e60e040a3a23b876e1d5e05bc99b Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 13 Sep 2021 21:37:38 +0200 Subject: [PATCH 107/143] Teardown test session on error Fixes: #132 --- internal/app/daemon/run/run.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 5729702..fa2a4bd 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -3,6 +3,7 @@ package run import ( "context" "encoding/json" + "fmt" "io/ioutil" "net" "os" @@ -72,7 +73,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConf }, nil } -func (s Test) Run() error { +func (s Test) Run() (err error) { if s.logstashConfig != "" { pipelineFile, err := s.createImplicitPipeline() if err != nil { @@ -110,6 +111,11 @@ func (s Test) Run() error { return err } + tests, err := testcase.DiscoverTests(s.testcasePath) + if err != nil { + return err + } + s.log.Debugf("socket to daemon %q", s.socket) conn, err := grpc.Dial( s.socket, @@ -134,10 +140,15 @@ func (s Test) Run() error { } sessionID := result.SessionID - tests, err := testcase.DiscoverTests(s.testcasePath) - if err != nil { - return err - } + defer func() { + _, teardownErr := c.TeardownTest(context.Background(), &pb.TeardownTestRequest{ + SessionID: sessionID, + Stats: false, + }) + if teardownErr != nil { + err = fmt.Errorf("failed to teardown connection: %v, root cause: %v", teardownErr, err) + } + }() observers := make([]lfvobserver.Interface, 0) liveObserver := observer.NewProperty(lfvobserver.TestExecutionStart{}) @@ -191,14 +202,6 @@ func (s Test) Run() error { } } - _, err = c.TeardownTest(context.Background(), &pb.TeardownTestRequest{ - SessionID: sessionID, - Stats: false, - }) - if err != nil { - return err - } - liveObserver.Update(lfvobserver.TestExecutionEnd{}) for _, obs := range observers { From 6b9e2fd7ea5ea7226927c189aa08eb52b219f8a5 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Sun, 29 Aug 2021 21:52:32 +0200 Subject: [PATCH 108/143] Support input & output plugins for mocking In order to support isolated testing of Logstash configurations, which use pipeline input and output plugins, the mocking is extended to all types of plugins. See also discussion: https://github.com/magnusbaeck/logstash-filter-verifier/issues/96#issuecomment-906726509 --- README.md | 22 ++++++----- integration_test.go | 11 ++++-- internal/app/daemon/run/run.go | 22 +++++------ internal/app/daemon_run.go | 8 ++-- internal/daemon/logstashconfig/file.go | 10 ++++- internal/daemon/pipeline/pipeline.go | 11 ++++-- internal/daemon/pipeline/pipeline_test.go | 4 +- .../pluginmock.go} | 38 ++++++++++--------- testdata/inputoutputmock.yml | 2 + testdata/inputoutputmock/main.conf | 18 +++++++++ testdata/mocks/filtermock.yml | 2 +- testdata/mocks/inputoutputmock.yml | 8 ++++ .../testcases/inputoutputmock/testcase.json | 25 ++++++++++++ 13 files changed, 128 insertions(+), 53 deletions(-) rename internal/daemon/{filtermock/filtermock.go => pluginmock/pluginmock.go} (59%) create mode 100644 testdata/inputoutputmock.yml create mode 100644 testdata/inputoutputmock/main.conf create mode 100644 testdata/mocks/inputoutputmock.yml create mode 100644 testdata/testcases/inputoutputmock/testcase.json diff --git a/README.md b/README.md index 45ef8f4..bdfeaea 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ * [Test case file reference](#test-case-file-reference) * [Standalone mode / Logstash Filter Verifier before version 2.0](#standalone-mode--logstash-filter-verifier-before-version-20) * [Daemon mode](#daemon-mode) - * [Filter mock](#filter-mock) + * [Plugin mock](#plugin-mock) * [Migrating to the current test case file format](#migrating-to-the-current-test-case-file-format) * [Notes](#notes) * [The \-\-sockets flag](#the---sockets-flag) @@ -400,8 +400,8 @@ have been added: The pipeline configuration may consist of multiple pipelines, that might be linked ([pipeline to pipeline communication](https://www.elastic.co/guide/en/logstash/current/pipeline-to-pipeline.html)) or independent pipelines. -* **Filter mock** - Filter mock allows to replace (or remove) filter plugins in the Logstash +* **Plugin mock** + Plugin mock allows to replace (or remove) plugins in the Logstash configuration under test, that do not work during or that would potentially not produce the expected results test execution. Examples for such filter plugins are mainly plugins, that perform some sort of call out to a third @@ -413,6 +413,8 @@ have been added: these plugins can be replaced with mocks. In particular the [mutate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html) and the [translate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-translate.html) filters have proven to be helpful as replacements. + An other use case for mocks is to replace pipeline input and output plugins + in order to test pipelines in isolation. In order to execute a test case in daemon mode, first the daemon needs to be started (e.g. in its own terminal or shell): @@ -443,7 +445,7 @@ needs to be started beforehand): $ /usr/local/bin/logstash-fitler-verifier daemon run --pipeline /tmp/logstash-filter-verifier/testdata/basic_pipeline.yml --pipeline-base /tmp/logstash-filter-verifier/testdata/basic_pipeline --testcase-dir /tmp/logstash-filter-verifier/testdata/testcases/basic_pipeline --add-missing-id -More examples (e.g. multiple pipelines and filter mock) can be found in the +More examples (e.g. multiple pipelines and plugin mock) can be found in the `testcases/` folder of this repository. @@ -521,12 +523,12 @@ Ignored / obsolete fields: * `codec` -#### Filter mock +#### Plugin mock -The filter mock config file (yaml) consists of an array of filter mock elements. -Each filter mock element consists for the plugin id that should be replaced as +The plugin mock config file (yaml) consists of an array of plugin mock elements. +Each plugin mock element consists for the plugin id that should be replaced as well as the Logstash configuration string that should be used as the -replacement. This string might be empty. In this case, the mocked filter is just +replacement. This string might be empty. In this case, the mocked plugin is just removed from the Logstash configuration. Example: @@ -534,7 +536,7 @@ Example: ```yaml - id: removeme - id: mockme - filter: | + mock: | mutate { replace => { "[message]" => "mocked" @@ -542,7 +544,7 @@ Example: } ``` -Given the above filter mock configuration, the plugin with the ID `removeme` is +Given the above plugin mock configuration, the plugin with the ID `removeme` is removed from the Logstash configuration. The plugin with the ID `mockme` is replaced with the given Logstash configuration. diff --git a/integration_test.go b/integration_test.go index 1984a33..ddeb26c 100644 --- a/integration_test.go +++ b/integration_test.go @@ -117,7 +117,7 @@ func TestIntegration(t *testing.T) { withoutPipeline bool addMissingID bool - filterMock string + pluginMock string }{ { name: "basic_pipeline", @@ -157,7 +157,12 @@ func TestIntegration(t *testing.T) { { name: "filtermock", - filterMock: "testdata/mocks/filtermock.yml", + pluginMock: "testdata/mocks/filtermock.yml", + }, + { + name: "inputoutputmock", + + pluginMock: "testdata/mocks/inputoutputmock.yml", }, { name: "special_chars", @@ -195,7 +200,7 @@ func TestIntegration(t *testing.T) { pipelineBaseDir, logstashConfig, "testdata/testcases/"+tc.name, - tc.filterMock, + tc.pluginMock, "@metadata", tc.debug, tc.addMissingID, diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index fa2a4bd..a125cb6 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -22,8 +22,8 @@ import ( "gopkg.in/yaml.v2" pb "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/api/grpc" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pipeline" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pluginmock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logstash" lfvobserver "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/observer" @@ -36,7 +36,7 @@ type Test struct { pipelineBase string logstashConfig string testcasePath string - filterMock string + pluginMock string metadataKey string debug bool addMissingID bool @@ -44,7 +44,7 @@ type Test struct { log logging.Logger } -func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConfig, testcasePath, filterMock, metadataKey string, debug, addMissingID bool) (Test, error) { +func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConfig, testcasePath, pluginMock, metadataKey string, debug, addMissingID bool) (Test, error) { if pipelineBase == "" { absPipeline, err := filepath.Abs(pipeline) if err != nil { @@ -65,7 +65,7 @@ func New(socket string, log logging.Logger, pipeline, pipelineBase, logstashConf pipelineBase: pipelineBase, logstashConfig: logstashConfig, testcasePath: testcasePath, - filterMock: filterMock, + pluginMock: pluginMock, metadataKey: metadataKey, debug: debug, addMissingID: addMissingID, @@ -90,13 +90,7 @@ func (s Test) Run() (err error) { return err } - // TODO: ensure, that IDs are also unique for the whole set of pipelines - err = a.Validate(s.addMissingID) - if err != nil { - return err - } - - m, err := filtermock.FromFile(s.filterMock) + m, err := pluginmock.FromFile(s.pluginMock) if err != nil { return err } @@ -106,6 +100,12 @@ func (s Test) Run() (err error) { preprocessor = pipeline.ApplyMocksPreprocessor(m) } + // TODO: ensure, that IDs are also unique for the whole set of pipelines + err = a.Validate(preprocessor, s.addMissingID) + if err != nil { + return err + } + b, err := a.ZipWithPreprocessor(preprocessor) if err != nil { return err diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index 5d71a11..f783d3c 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -24,8 +24,8 @@ func makeDaemonRunCmd() *cobra.Command { _ = viper.BindPFlag("logstash-config", cmd.Flags().Lookup("logstash-config")) cmd.Flags().StringP("testcase-dir", "t", "", "directory containing the test case files") _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) - cmd.Flags().String("filter-mock", "", "path to a yaml file containing the definition for the filter mocks.") - _ = viper.BindPFlag("filter-mock", cmd.Flags().Lookup("filter-mock")) + cmd.Flags().String("plugin-mock", "", "path to a yaml file containing the definition for the plugin mocks.") + _ = viper.BindPFlag("plugin-mock", cmd.Flags().Lookup("plugin-mock")) cmd.Flags().Bool("debug", false, "enable debug mode; e.g. prevents stripping '__lfv' data from Logstash events") _ = viper.BindPFlag("debug", cmd.Flags().Lookup("debug")) cmd.Flags().String("metadata-key", "@metadata", "Key under which the content of the `@metadata` field is exposed in the returned events.") @@ -43,7 +43,7 @@ func runDaemonRun(_ *cobra.Command, args []string) error { pipelineBase := viper.GetString("pipeline-base") logstashConfig := viper.GetString("logstash-config") testcaseDir := viper.GetString("testcase-dir") - filterMock := viper.GetString("filter-mock") + pluginMock := viper.GetString("plugin-mock") debug := viper.GetBool("debug") metadataKey := viper.GetString("metadata-key") addMissingID := viper.GetBool("add-missing-id") @@ -52,7 +52,7 @@ func runDaemonRun(_ *cobra.Command, args []string) error { return errors.New("--pipeline and --logstash-config flags are mutual exclusive") } - t, err := run.New(socket, log, pipeline, pipelineBase, logstashConfig, testcaseDir, filterMock, metadataKey, debug, addMissingID) + t, err := run.New(socket, log, pipeline, pipelineBase, logstashConfig, testcaseDir, pluginMock, metadataKey, debug, addMissingID) if err != nil { return err } diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index f6662a0..4c9d1fe 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -12,8 +12,8 @@ import ( "github.com/breml/logstash-config/ast/astutil" "github.com/pkg/errors" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/idgen" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pluginmock" ) type File struct { @@ -204,15 +204,21 @@ func (v *validator) walk(c *astutil.Cursor) { } } -func (f *File) ApplyMocks(m filtermock.Mocks) error { +func (f *File) ApplyMocks(m pluginmock.Mocks) error { err := f.parse() if err != nil { return err } + for i := 0; i < len(f.config.Input); i++ { + f.config.Input[i].BranchOrPlugins = astutil.ApplyPlugins(f.config.Input[i].BranchOrPlugins, m.Walk) + } for i := 0; i < len(f.config.Filter); i++ { f.config.Filter[i].BranchOrPlugins = astutil.ApplyPlugins(f.config.Filter[i].BranchOrPlugins, m.Walk) } + for i := 0; i < len(f.config.Output); i++ { + f.config.Output[i].BranchOrPlugins = astutil.ApplyPlugins(f.config.Output[i].BranchOrPlugins, m.Walk) + } f.Body = []byte(f.config.String()) diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 444e0d2..08a77d6 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -13,8 +13,8 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v2" - "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/filtermock" "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/logstashconfig" + "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/daemon/pluginmock" ) type Archive struct { @@ -74,7 +74,7 @@ func processNestedKeys(pipelines Pipelines) { } } -func (a Archive) Validate(addMissingID bool) error { +func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID bool) error { var inputs, outputs int for _, pipeline := range a.Pipelines { if strings.HasSuffix(pipeline.Config, "/") { @@ -112,6 +112,11 @@ func (a Archive) Validate(addMissingID bool) error { return err } + body, err = preprocess(body) + if err != nil { + return err + } + configFile := logstashconfig.File{ Name: relFile, Body: body, @@ -215,7 +220,7 @@ func NoopPreprocessor(body []byte) ([]byte, error) { return body, nil } -func ApplyMocksPreprocessor(m filtermock.Mocks) func(body []byte) ([]byte, error) { +func ApplyMocksPreprocessor(m pluginmock.Mocks) func(body []byte) ([]byte, error) { return func(body []byte) ([]byte, error) { configFile := logstashconfig.File{ Body: body, diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index d7a8600..4d1419f 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -89,10 +89,10 @@ func TestValidate(t *testing.T) { a, err := pipeline.New(test.pipeline, test.basePath) is.NoErr(err) - err = a.Validate(false) + err = a.Validate(pipeline.NoopPreprocessor, false) is.True(err != nil == test.wantValidateErr) // Validate error - err = a.Validate(true) + err = a.Validate(pipeline.NoopPreprocessor, true) is.NoErr(err) }) } diff --git a/internal/daemon/filtermock/filtermock.go b/internal/daemon/pluginmock/pluginmock.go similarity index 59% rename from internal/daemon/filtermock/filtermock.go rename to internal/daemon/pluginmock/pluginmock.go index 0c6aa9a..a53bcfc 100644 --- a/internal/daemon/filtermock/filtermock.go +++ b/internal/daemon/pluginmock/pluginmock.go @@ -1,11 +1,13 @@ -// Filter mocks allow to replace an existing filter, identified by its ID, in -// the config with a mock implementation. +// Plugin mocks allow to replace an existing plugin (input, output, filter), +// identified by its ID, in the config with a mock implementation. // This comes in handy, if a filter does perform a call out to an external // system e.g. lookup in Elasticsearch. +// An other use case is to replace pipeline input or outputs to test such +// pipelines in isolation. // Because the existing filter is replaced with whatever is present in -// `filter`, it is also possible to remove a filter by simple keep the -// `filter` empty (or not present). -package filtermock +// `mock`, it is also possible to remove a plugin by simple keep the +// `mock` empty (or not present). +package pluginmock import ( "fmt" @@ -22,7 +24,7 @@ type Mock struct { // ID of the Logstash filter to be mocked. ID string `json:"id" yaml:"id"` - // Logstash configuration snippet of the replacement filter. E.g. + // Logstash configuration snippet of the replacement plugin. E.g. // // # Constant lookup, does return the same result for each event // mutate { @@ -30,7 +32,7 @@ type Mock struct { // "[field]" => "value" // } // } - Filter string `json:"filter,omitempty" yaml:"filter,omitempty"` + Mock string `json:"mock,omitempty" yaml:"mock,omitempty"` } func FromFile(filename string) (Mocks, error) { @@ -50,38 +52,40 @@ func FromFile(filename string) (Mocks, error) { return Mocks{}, err } - filtermocks := make(Mocks, len(mocks)) + pluginmocks := make(Mocks, len(mocks)) for _, m := range mocks { - if m.Filter == "" { - filtermocks[m.ID] = nil + if m.Mock == "" { + pluginmocks[m.ID] = nil continue } - wrappedFilter := []byte(fmt.Sprintf("filter {\n%s\n}", m.Filter)) - cfg, err := config.Parse("", wrappedFilter) + // The mock plugin definition is wrapped with filter to form a valid + // Logstash config, which can then be parsed. + wrappedPlugin := []byte(fmt.Sprintf("filter {\n%s\n}", m.Mock)) + cfg, err := config.Parse("", wrappedPlugin) if err != nil { return Mocks{}, err } - var filter ast.Plugin + var plugin ast.Plugin var recoverErr interface{} func() { defer func() { recoverErr = recover() }() - filter = cfg.(ast.Config).Filter[0].BranchOrPlugins[0].(ast.Plugin) + plugin = cfg.(ast.Config).Filter[0].BranchOrPlugins[0].(ast.Plugin) }() if recoverErr != nil { - return Mocks{}, errors.Errorf("failed to parse mock filter: %s", m.Filter) + return Mocks{}, errors.Errorf("failed to parse mock: %s", m.Mock) } - filtermocks[m.ID] = &filter + pluginmocks[m.ID] = &plugin } - return filtermocks, nil + return pluginmocks, nil } type Mocks map[string]*ast.Plugin diff --git a/testdata/inputoutputmock.yml b/testdata/inputoutputmock.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/inputoutputmock.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/inputoutputmock/main.conf b/testdata/inputoutputmock/main.conf new file mode 100644 index 0000000..3a40edd --- /dev/null +++ b/testdata/inputoutputmock/main.conf @@ -0,0 +1,18 @@ +input { + pipeline { + id => input + } +} + +filter { + mutate { + id => filter + add_tag => [ "sut_passed" ] + } +} + +output { + pipeline { + id => output + } +} diff --git a/testdata/mocks/filtermock.yml b/testdata/mocks/filtermock.yml index 27fc9bd..f04d620 100644 --- a/testdata/mocks/filtermock.yml +++ b/testdata/mocks/filtermock.yml @@ -2,7 +2,7 @@ - id: removeme - id: mockme - filter: | + mock: | mutate { replace => { "[message]" => "mocked" diff --git a/testdata/mocks/inputoutputmock.yml b/testdata/mocks/inputoutputmock.yml new file mode 100644 index 0000000..951db99 --- /dev/null +++ b/testdata/mocks/inputoutputmock.yml @@ -0,0 +1,8 @@ +--- + +- id: input + mock: | + stdin {} +- id: output + mock: | + stdout {} diff --git a/testdata/testcases/inputoutputmock/testcase.json b/testdata/testcases/inputoutputmock/testcase.json new file mode 100644 index 0000000..ca08ae0 --- /dev/null +++ b/testdata/testcases/inputoutputmock/testcase.json @@ -0,0 +1,25 @@ +{ + "fields": { + "type": "syslog" + }, + "ignore": [ + "@timestamp" + ], + "input_plugin": "input", + "testcases": [ + { + "input": [ + "test case message" + ], + "expected": [ + { + "message": "test case message", + "tags": [ + "sut_passed" + ], + "type": "syslog" + } + ] + } + ] +} From 9c46ddb1b4e5971e941c9adeecab81e436dc1312 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 8 Sep 2021 23:31:00 +0200 Subject: [PATCH 109/143] Check for precense of input plugin and duplicate plugin ids Fixes: #127 --- internal/app/daemon/run/run.go | 7 ++- internal/daemon/logstashconfig/file.go | 44 ++++++++++++------ internal/daemon/logstashconfig/file_test.go | 50 +++++++++++++-------- internal/daemon/pipeline/pipeline.go | 32 +++++++------ internal/daemon/pipeline/pipeline_test.go | 14 +++++- 5 files changed, 98 insertions(+), 49 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index a125cb6..e4ffaa6 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -101,7 +101,7 @@ func (s Test) Run() (err error) { } // TODO: ensure, that IDs are also unique for the whole set of pipelines - err = a.Validate(preprocessor, s.addMissingID) + inputs, err := a.Validate(preprocessor, s.addMissingID) if err != nil { return err } @@ -115,6 +115,11 @@ func (s Test) Run() (err error) { if err != nil { return err } + for _, test := range tests { + if _, ok := inputs[test.InputPlugin]; !ok { + return errors.Errorf("input plugin %q defined in test case but not present in Logstash config", test.InputPlugin) + } + } s.log.Debugf("socket to daemon %q", s.socket) conn, err := grpc.Dial( diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 4c9d1fe..ad54858 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -143,13 +143,16 @@ func (o *outputPipelineReplacer) walk(c *astutil.Cursor) { c.Replace(ast.NewPlugin("pipeline", ast.NewArrayAttribute("send_to", ast.NewStringAttribute("", outputName, ast.DoubleQuoted)))) } -func (f *File) Validate(addMissingID bool) (inputs int, outputs int, err error) { +func (f *File) Validate(addMissingID bool) (inputs map[string]int, outputs map[string]int, err error) { err = f.parse() if err != nil { - return 0, 0, err + return nil, nil, err } v := validator{ + pluginIDs: map[string]int{}, + inputs: map[string]int{}, + outputs: map[string]int{}, addMissingID: addMissingID, } @@ -167,16 +170,24 @@ func (f *File) Validate(addMissingID bool) (inputs int, outputs int, err error) } if len(v.noIDs) > 0 { - return 0, 0, errors.Errorf("%q no IDs found for %v", f.Name, v.noIDs) + return nil, nil, errors.Errorf("%q no IDs found for %v", f.Name, v.noIDs) } + + for id, count := range v.pluginIDs { + if count != 1 { + return nil, nil, errors.Errorf("plugin id must be unique, but %q appeared %d times", id, count) + } + } + return v.inputs, v.outputs, nil } type validator struct { noIDs []string pluginType ast.PluginType - inputs int - outputs int + pluginIDs map[string]int + inputs map[string]int + outputs map[string]int count int addMissingID bool } @@ -184,24 +195,29 @@ type validator struct { func (v *validator) walk(c *astutil.Cursor) { v.count++ - if v.pluginType == ast.Input && c.Plugin().Name() != "pipeline" { - v.inputs++ - } - if v.pluginType == ast.Output && c.Plugin().Name() != "pipeline" { - v.outputs++ - } + name := c.Plugin().Name() - _, err := c.Plugin().ID() + id, err := c.Plugin().ID() if err != nil { if v.addMissingID { plugin := c.Plugin() - plugin.Attributes = append(plugin.Attributes, ast.NewStringAttribute("id", fmt.Sprintf("%s-%d", c.Plugin().Name(), v.count), ast.DoubleQuoted)) + id = fmt.Sprintf("%s-%d", name, v.count) + plugin.Attributes = append(plugin.Attributes, ast.NewStringAttribute("id", id, ast.DoubleQuoted)) c.Replace(plugin) } else { - v.noIDs = append(v.noIDs, c.Plugin().Name()) + v.noIDs = append(v.noIDs, name) + return } } + + v.pluginIDs[id]++ + if v.pluginType == ast.Input && name != "pipeline" { + v.inputs[id]++ + } + if v.pluginType == ast.Output && name != "pipeline" { + v.outputs[id]++ + } } func (f *File) ApplyMocks(m pluginmock.Mocks) error { diff --git a/internal/daemon/logstashconfig/file_test.go b/internal/daemon/logstashconfig/file_test.go index fb4617d..9c41f04 100644 --- a/internal/daemon/logstashconfig/file_test.go +++ b/internal/daemon/logstashconfig/file_test.go @@ -177,8 +177,9 @@ func TestReplaceOutputsWithoutID(t *testing.T) { func TestValidate(t *testing.T) { cases := []struct { - name string - config string + name string + config string + nonFixableError bool wantErr error wantInputs int @@ -203,13 +204,22 @@ output { stdout { } }`, }, { name: "successful validate - with pipeline input and output", - config: `input { stdin { id => stdin } file { id => file } pipeline { id => pipeline } } + config: `input { stdin { id => stdin } file { id => file_in } pipeline { id => pipeline_in } } filter { mutate { id => mutate } } -output { stdout { id => testid } file { id => file } pipeline { id => pipeline } }`, +output { stdout { id => testid } file { id => file_out } pipeline { id => pipeline_out } }`, wantInputs: 2, wantOutputs: 2, }, + { + name: "error duplicate plugin id", + config: `input { stdin { id => name } } +filter { mutate { id => name } } +output { stdout { id => name } }`, + nonFixableError: true, + + wantErr: errors.Errorf(`plugin id must be unique, but "name" appeared 3 times`), + }, } for _, test := range cases { @@ -221,25 +231,27 @@ output { stdout { id => testid } file { id => file } pipeline { id => pipeline } inputs, outputs, err := f.Validate(false) compareErr(t, test.wantErr, err) - if test.wantInputs != inputs { - t.Errorf("expected %d inputs, got %d", test.wantInputs, inputs) + if test.wantInputs != len(inputs) { + t.Errorf("expected %d inputs, got %d", test.wantInputs, len(inputs)) } - if test.wantOutputs != outputs { - t.Errorf("expected %d outputs, got %d", test.wantOutputs, outputs) + if test.wantOutputs != len(outputs) { + t.Errorf("expected %d outputs, got %d", test.wantOutputs, len(outputs)) } }) - t.Run(fmt.Sprintf("validate-with-fix-%s", test.name), func(t *testing.T) { - f := logstashconfig.File{ - Name: "filename.conf", - Body: []byte(test.config), - } - - _, _, err := f.Validate(true) - if err != nil { - t.Errorf("expected no error, got: %v", err) - } - }) + if !test.nonFixableError { + t.Run(fmt.Sprintf("validate-with-fix-%s", test.name), func(t *testing.T) { + f := logstashconfig.File{ + Name: "filename.conf", + Body: []byte(test.config), + } + + _, _, err := f.Validate(true) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + } } } diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 08a77d6..26e7d7b 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -74,8 +74,9 @@ func processNestedKeys(pipelines Pipelines) { } } -func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID bool) error { - var inputs, outputs int +func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID bool) (map[string]int, error) { + inputs := map[string]int{} + outputs := map[string]int{} for _, pipeline := range a.Pipelines { if strings.HasSuffix(pipeline.Config, "/") { pipeline.Config += "*" @@ -86,12 +87,12 @@ func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID } files, err := doublestar.Glob(configFilepath) if err != nil { - return err + return nil, err } for _, file := range files { fi, err := os.Stat(file) if err != nil { - return err + return nil, err } if fi.IsDir() { continue @@ -102,19 +103,19 @@ func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID } else { cwd, err := os.Getwd() if err != nil { - return err + return nil, err } relFile = strings.TrimPrefix(file, filepath.Join(cwd, a.BasePath)) } body, err := ioutil.ReadFile(file) if err != nil { - return err + return nil, err } body, err = preprocess(body) if err != nil { - return err + return nil, err } configFile := logstashconfig.File{ @@ -124,18 +125,23 @@ func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID in, out, err := configFile.Validate(addMissingID) if err != nil { - return err + return nil, err + } + + for id, count := range in { + inputs[id] += count + } + for id, count := range out { + outputs[id] += count } - inputs += in - outputs += out } } - if inputs == 0 || outputs == 0 { - return errors.Errorf("expect the Logstash config to have at least 1 input and 1 output, got %d inputs and %d outputs", inputs, outputs) + if len(inputs) == 0 || len(outputs) == 0 { + return nil, errors.Errorf("expect the Logstash config to have at least 1 input and 1 output, got %d inputs and %d outputs", len(inputs), len(outputs)) } - return nil + return inputs, nil } func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([]byte, error) { diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index 4d1419f..8ac7329 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -56,29 +56,37 @@ func TestValidate(t *testing.T) { pipeline string basePath string + wantInputs int wantValidateErr bool }{ { name: "success basic pipeline", pipeline: "testdata/pipelines_basic.yml", basePath: "testdata/", + + wantInputs: 1, }, { name: "success basic pipeline with absolute base path", pipeline: "testdata/pipelines_basic_base_path.yml", basePath: wd, + + wantInputs: 1, }, { name: "error invalid config", pipeline: "testdata/pipelines_invalid_config.yml", basePath: "testdata/", + wantInputs: 1, wantValidateErr: true, }, { name: "success basic pipeline with nested keys", pipeline: "testdata/pipelines_basic_nested_keys.yml", basePath: "testdata/", + + wantInputs: 1, }, } @@ -89,11 +97,13 @@ func TestValidate(t *testing.T) { a, err := pipeline.New(test.pipeline, test.basePath) is.NoErr(err) - err = a.Validate(pipeline.NoopPreprocessor, false) + _, err = a.Validate(pipeline.NoopPreprocessor, false) is.True(err != nil == test.wantValidateErr) // Validate error - err = a.Validate(pipeline.NoopPreprocessor, true) + inputs, err := a.Validate(pipeline.NoopPreprocessor, true) + is.NoErr(err) + is.Equal(test.wantInputs, len(inputs)) }) } } From aa86f82e49c3ea6868b23d41bc6079c70744c385 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Wed, 15 Sep 2021 21:49:55 +0200 Subject: [PATCH 110/143] Add integration test for multi file config & codec Closes: #138 --- testdata/codec_test.yml | 2 +- testdata/codec_test/codec_test.conf | 6 ------ testdata/codec_test/output.conf | 5 +++++ 3 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 testdata/codec_test/output.conf diff --git a/testdata/codec_test.yml b/testdata/codec_test.yml index df67ef8..75d1a35 100644 --- a/testdata/codec_test.yml +++ b/testdata/codec_test.yml @@ -1,2 +1,2 @@ - pipeline.id: main - path.config: "codec_test.conf" + path.config: "*.conf" diff --git a/testdata/codec_test/codec_test.conf b/testdata/codec_test/codec_test.conf index e088b50..b685af3 100644 --- a/testdata/codec_test/codec_test.conf +++ b/testdata/codec_test/codec_test.conf @@ -83,9 +83,3 @@ input { tags => [ "input_codec_plain" ] } } - -output { - stdout { - id => stdout - } -} diff --git a/testdata/codec_test/output.conf b/testdata/codec_test/output.conf new file mode 100644 index 0000000..5481eaa --- /dev/null +++ b/testdata/codec_test/output.conf @@ -0,0 +1,5 @@ +output { + stdout { + id => stdout + } +} From fa6603450cef8aed26f450e45bc97829e2303e3e Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 24 Sep 2021 09:19:49 +0200 Subject: [PATCH 111/143] Return partial results Fixes: #145 --- internal/daemon/controller/controller.go | 2 +- internal/daemon/session/session.go | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 787b60c..3124b51 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -135,7 +135,7 @@ func (c *Controller) GetResults() ([]string, error) { err := c.stateMachine.waitForState(stateReadyForTest) if err != nil { - return nil, err + return c.receivedEvents.get(), err } // The last event might be sent through multiple outputs, therefore we give diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index 8f575a4..c46cbbd 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -258,11 +258,7 @@ func createInput(pipelineFilename string, fieldsFilename string, inputPluginName // GetResults returns the returned events from Logstash. func (s *Session) GetResults() ([]string, error) { - results, err := s.logstashController.GetResults() - if err != nil { - return nil, err - } - return results, nil + return s.logstashController.GetResults() } // GetStats returns the statistics for a test suite. From 3e559ab7907f3a206c173376bfb0c67dc048785b Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 20 Sep 2021 13:29:35 +0200 Subject: [PATCH 112/143] Align Logstash bracket field syntax with Logstash Align bracket field regex with: https://www.elastic.co/guide/en/logstash/current/field-references-deepdive.html#formal-grammar-field-name https://github.com/elastic/logstash/blob/c698aa224b44c7356fbfeb58ebb0c4a66d72dc71/logstash-core/lib/logstash/config/grammar.treetop#L236 Fixes: #142 --- internal/testcase/helper.go | 4 +-- internal/testcase/testcase_test.go | 6 ++++ .../testcases_metadata_logstash.json | 22 +++++++++++++++ .../testcases_metadata_logstash_global.json | 26 +++++++++++++++++ .../testcases_metadata_nested.json | 24 ++++++++++++++++ .../testcases_metadata_nested_global.json | 28 +++++++++++++++++++ testdata/testcases_event/testcases_event.conf | 6 ++++ 7 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 testdata/testcases/testcases_event/testcases_metadata_logstash.json create mode 100644 testdata/testcases/testcases_event/testcases_metadata_logstash_global.json create mode 100644 testdata/testcases/testcases_event/testcases_metadata_nested.json create mode 100644 testdata/testcases/testcases_event/testcases_metadata_nested_global.json diff --git a/internal/testcase/helper.go b/internal/testcase/helper.go index 833a26e..406d1dc 100644 --- a/internal/testcase/helper.go +++ b/internal/testcase/helper.go @@ -26,8 +26,8 @@ func parseAllBracketProperties(data map[string]interface{}) map[string]interface // extractBracketFields convert bracket notation to slice of key. func extractBracketFields(key string) []string { - rValidator := regexp.MustCompile(`^(\[\w+\])+$`) - rExtractField := regexp.MustCompile(`\[(\w+)\]`) + rValidator := regexp.MustCompile(`^(\[[^\[\],]+\])+$`) + rExtractField := regexp.MustCompile(`\[([^\[\],]+)\]`) listKeys := make([]string, 0, 1) if rValidator.MatchString(key) { diff --git a/internal/testcase/testcase_test.go b/internal/testcase/testcase_test.go index b5468a4..5763eeb 100644 --- a/internal/testcase/testcase_test.go +++ b/internal/testcase/testcase_test.go @@ -462,6 +462,8 @@ func TestConvertBracketFields(t *testing.T) { "type": "test", "[log][file][path]": "/tmp/file.log", "[log][origin][file]": "test.java", + "[log][special_field.name-with some+spice]": "value", + "[@metadata][field]": "value", }, Codec: "json_lines", InputLines: []string{ @@ -487,6 +489,10 @@ func TestConvertBracketFields(t *testing.T) { "origin": map[string]interface{}{ "file": "test.java", }, + "special_field.name-with some+spice": "value", + }, + "@metadata": map[string]interface{}{ + "field": "value", }, }, Codec: "json_lines", diff --git a/testdata/testcases/testcases_event/testcases_metadata_logstash.json b/testdata/testcases/testcases_event/testcases_metadata_logstash.json new file mode 100644 index 0000000..fe2a139 --- /dev/null +++ b/testdata/testcases/testcases_event/testcases_metadata_logstash.json @@ -0,0 +1,22 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "export_metadata": true, + "testcases": [ + { + "fields": { + "[@metadata][testcase]": "value" + }, + "expected": [ + { + "@metadata": { + "filter": "value", + "testcase": "value" + } + } + ] + } + ] +} diff --git a/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json b/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json new file mode 100644 index 0000000..ac2b416 --- /dev/null +++ b/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json @@ -0,0 +1,26 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "export_metadata": true, + "fields": { + "[@metadata][global]": "value" + }, + "testcases": [ + { + "fields": { + "key": "value" + }, + "expected": [ + { + "@metadata": { + "filter": "value", + "global": "value" + }, + "key": "value" + } + ] + } + ] +} diff --git a/testdata/testcases/testcases_event/testcases_metadata_nested.json b/testdata/testcases/testcases_event/testcases_metadata_nested.json new file mode 100644 index 0000000..94fa771 --- /dev/null +++ b/testdata/testcases/testcases_event/testcases_metadata_nested.json @@ -0,0 +1,24 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "export_metadata": true, + "testcases": [ + { + "fields": { + "@metadata": { + "testcase": "value" + } + }, + "expected": [ + { + "@metadata": { + "filter": "value", + "testcase": "value" + } + } + ] + } + ] +} diff --git a/testdata/testcases/testcases_event/testcases_metadata_nested_global.json b/testdata/testcases/testcases_event/testcases_metadata_nested_global.json new file mode 100644 index 0000000..285995e --- /dev/null +++ b/testdata/testcases/testcases_event/testcases_metadata_nested_global.json @@ -0,0 +1,28 @@ +{ + "ignore": [ + "@timestamp" + ], + "input_plugin": "stdin", + "export_metadata": true, + "fields": { + "@metadata": { + "global": "value" + } + }, + "testcases": [ + { + "fields": { + "key": "value" + }, + "expected": [ + { + "@metadata": { + "filter": "value", + "global": "value" + }, + "key": "value" + } + ] + } + ] +} diff --git a/testdata/testcases_event/testcases_event.conf b/testdata/testcases_event/testcases_event.conf index 41a957d..befb680 100644 --- a/testdata/testcases_event/testcases_event.conf +++ b/testdata/testcases_event/testcases_event.conf @@ -3,6 +3,12 @@ input { id => "stdin" } } +filter { + mutate { + id => "add_metadata" + add_field => { "[@metadata][filter]" => "value" } + } +} output { stdout { id => "stdout" From 1a7e432c78e958bbd6e65244365c5a74e165ac39 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 13 Sep 2021 16:16:52 +0200 Subject: [PATCH 113/143] Fix automatic IDs Automatically adding the IDs did not work before, because during the creation of the ZIP file, that is passed to the daemon, the Logstash config files have been read from disk again. With this, the original config has been passed along, which does not contain the required IDs. To prevent this, the validation step for the Logstash config is now part of the creation of the ZIP file. Fixes: #131 --- integration_test.go | 2 +- internal/app/daemon/run/run.go | 7 +- internal/daemon/logstashconfig/file.go | 6 +- internal/daemon/pipeline/pipeline.go | 119 ++++++------------ internal/daemon/pipeline/pipeline_test.go | 90 +++++-------- .../testdata/folder/subfolder/other.conf | 3 +- .../invalid/{main.conf => no_id.conf} | 0 .../pipeline/testdata/invalid/no_input.conf | 5 + .../pipeline/testdata/invalid/no_output.conf | 5 + .../testdata/pipelines_invalid_config.yml | 4 - .../pipelines_invalid_config_no_id.yml | 4 + .../pipelines_invalid_config_no_input.yml | 4 + .../pipelines_invalid_config_no_output.yml | 4 + internal/daemon/pluginmock/pluginmock.go | 5 +- testdata/basic_pipeline/main/main.conf | 5 + 15 files changed, 105 insertions(+), 158 deletions(-) rename internal/daemon/pipeline/testdata/invalid/{main.conf => no_id.conf} (100%) create mode 100644 internal/daemon/pipeline/testdata/invalid/no_input.conf create mode 100644 internal/daemon/pipeline/testdata/invalid/no_output.conf delete mode 100644 internal/daemon/pipeline/testdata/pipelines_invalid_config.yml create mode 100644 internal/daemon/pipeline/testdata/pipelines_invalid_config_no_id.yml create mode 100644 internal/daemon/pipeline/testdata/pipelines_invalid_config_no_input.yml create mode 100644 internal/daemon/pipeline/testdata/pipelines_invalid_config_no_output.yml diff --git a/integration_test.go b/integration_test.go index ddeb26c..04d84b8 100644 --- a/integration_test.go +++ b/integration_test.go @@ -77,7 +77,7 @@ func TestIntegration(t *testing.T) { version, err := standalonelogstash.DetectVersion(logstashPath, os.Environ()) is.NoErr(err) - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() go func() { diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index e4ffaa6..813a88d 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -101,12 +101,7 @@ func (s Test) Run() (err error) { } // TODO: ensure, that IDs are also unique for the whole set of pipelines - inputs, err := a.Validate(preprocessor, s.addMissingID) - if err != nil { - return err - } - - b, err := a.ZipWithPreprocessor(preprocessor) + b, inputs, err := a.ZipWithPreprocessor(s.addMissingID, preprocessor) if err != nil { return err } diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index ad54858..68e2633 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -84,7 +84,7 @@ func (r replaceInputsWalker) replaceInputs(c *astutil.Cursor) { id, err := c.Plugin().ID() if err != nil { - panic(err) + id = idgen.New() } var attrs []ast.Attribute @@ -179,6 +179,10 @@ func (f *File) Validate(addMissingID bool) (inputs map[string]int, outputs map[s } } + if addMissingID { + f.Body = []byte(f.config.String()) + } + return v.inputs, v.outputs, nil } diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index 26e7d7b..b1d722d 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -74,93 +74,25 @@ func processNestedKeys(pipelines Pipelines) { } } -func (a Archive) Validate(preprocess func([]byte) ([]byte, error), addMissingID bool) (map[string]int, error) { - inputs := map[string]int{} - outputs := map[string]int{} - for _, pipeline := range a.Pipelines { - if strings.HasSuffix(pipeline.Config, "/") { - pipeline.Config += "*" - } - configFilepath := filepath.Join(a.BasePath, pipeline.Config) - if filepath.IsAbs(pipeline.Config) { - configFilepath = pipeline.Config - } - files, err := doublestar.Glob(configFilepath) - if err != nil { - return nil, err - } - for _, file := range files { - fi, err := os.Stat(file) - if err != nil { - return nil, err - } - if fi.IsDir() { - continue - } - var relFile string - if path.IsAbs(a.BasePath) { - relFile = strings.TrimPrefix(file, a.BasePath) - } else { - cwd, err := os.Getwd() - if err != nil { - return nil, err - } - relFile = strings.TrimPrefix(file, filepath.Join(cwd, a.BasePath)) - } - - body, err := ioutil.ReadFile(file) - if err != nil { - return nil, err - } - - body, err = preprocess(body) - if err != nil { - return nil, err - } - - configFile := logstashconfig.File{ - Name: relFile, - Body: body, - } - - in, out, err := configFile.Validate(addMissingID) - if err != nil { - return nil, err - } - - for id, count := range in { - inputs[id] += count - } - for id, count := range out { - outputs[id] += count - } - } - } - - if len(inputs) == 0 || len(outputs) == 0 { - return nil, errors.Errorf("expect the Logstash config to have at least 1 input and 1 output, got %d inputs and %d outputs", len(inputs), len(outputs)) - } - - return inputs, nil -} - -func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([]byte, error) { +func (a Archive) ZipWithPreprocessor(addMissingID bool, preprocess func([]byte) ([]byte, error)) (data []byte, inputs map[string]int, err error) { buf := new(bytes.Buffer) w := zip.NewWriter(buf) f, err := w.Create("pipelines.yml") if err != nil { - return nil, err + return nil, nil, err } body, err := ioutil.ReadFile(a.File) if err != nil { - return nil, err + return nil, nil, err } _, err = f.Write(body) if err != nil { - return nil, err + return nil, nil, err } + inputs = map[string]int{} + outputs := map[string]int{} for _, pipeline := range a.Pipelines { if strings.HasSuffix(pipeline.Config, "/") { pipeline.Config += "*" @@ -171,12 +103,12 @@ func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([ } files, err := doublestar.Glob(configFilepath) if err != nil { - return nil, err + return nil, nil, err } for _, file := range files { fi, err := os.Stat(file) if err != nil { - return nil, err + return nil, nil, err } if fi.IsDir() { continue @@ -187,39 +119,60 @@ func (a Archive) ZipWithPreprocessor(preprocess func([]byte) ([]byte, error)) ([ } else { cwd, err := os.Getwd() if err != nil { - return nil, err + return nil, nil, err } relFile = strings.TrimPrefix(file, filepath.Join(cwd, a.BasePath)) } f, err := w.Create(relFile) if err != nil { - return nil, err + return nil, nil, err } body, err := ioutil.ReadFile(file) if err != nil { - return nil, err + return nil, nil, err } body, err = preprocess(body) if err != nil { - return nil, err + return nil, nil, err + } + + configFile := logstashconfig.File{ + Name: relFile, + Body: body, + } + + in, out, err := configFile.Validate(addMissingID) + if err != nil { + return nil, nil, err + } + + for id, count := range in { + inputs[id] += count + } + for id, count := range out { + outputs[id] += count } _, err = f.Write(body) if err != nil { - return nil, err + return nil, nil, err } } } err = w.Close() if err != nil { - return nil, err + return nil, nil, err + } + + if len(inputs) == 0 || len(outputs) == 0 { + return nil, nil, errors.Errorf("expect the Logstash config to have at least 1 input and 1 output, got %d inputs and %d outputs", len(inputs), len(outputs)) } - return buf.Bytes(), nil + return buf.Bytes(), inputs, nil } func NoopPreprocessor(body []byte) ([]byte, error) { diff --git a/internal/daemon/pipeline/pipeline_test.go b/internal/daemon/pipeline/pipeline_test.go index 8ac7329..36c82a7 100644 --- a/internal/daemon/pipeline/pipeline_test.go +++ b/internal/daemon/pipeline/pipeline_test.go @@ -48,66 +48,6 @@ func TestNew(t *testing.T) { } } -func TestValidate(t *testing.T) { - wd, _ := os.Getwd() - - cases := []struct { - name string - pipeline string - basePath string - - wantInputs int - wantValidateErr bool - }{ - { - name: "success basic pipeline", - pipeline: "testdata/pipelines_basic.yml", - basePath: "testdata/", - - wantInputs: 1, - }, - { - name: "success basic pipeline with absolute base path", - pipeline: "testdata/pipelines_basic_base_path.yml", - basePath: wd, - - wantInputs: 1, - }, - { - name: "error invalid config", - pipeline: "testdata/pipelines_invalid_config.yml", - basePath: "testdata/", - - wantInputs: 1, - wantValidateErr: true, - }, - { - name: "success basic pipeline with nested keys", - pipeline: "testdata/pipelines_basic_nested_keys.yml", - basePath: "testdata/", - - wantInputs: 1, - }, - } - - for _, test := range cases { - t.Run(test.name, func(t *testing.T) { - is := is.New(t) - - a, err := pipeline.New(test.pipeline, test.basePath) - is.NoErr(err) - - _, err = a.Validate(pipeline.NoopPreprocessor, false) - is.True(err != nil == test.wantValidateErr) // Validate error - - inputs, err := a.Validate(pipeline.NoopPreprocessor, true) - - is.NoErr(err) - is.Equal(test.wantInputs, len(inputs)) - }) - } -} - func TestZip(t *testing.T) { wd, _ := os.Getwd() @@ -147,6 +87,13 @@ func TestZip(t *testing.T) { wantFiles: 2, }, + { + name: "success basic pipeline with nested keys", + pipeline: "testdata/pipelines_basic_nested_keys.yml", + basePath: "testdata/", + + wantFiles: 2, + }, { name: "success advanced pipeline", pipeline: "testdata/pipelines_advanced.yml", @@ -168,6 +115,27 @@ func TestZip(t *testing.T) { wantNewArchiveErr: true, }, + { + name: "error invalid config", + pipeline: "testdata/pipelines_invalid_config_no_id.yml", + basePath: "testdata/", + + wantZipBytesErr: true, + }, + { + name: "error invalid config - no input", + pipeline: "testdata/pipelines_invalid_config_no_input.yml", + basePath: "testdata/", + + wantZipBytesErr: true, + }, + { + name: "error invalid config - no output", + pipeline: "testdata/pipelines_invalid_config_no_output.yml", + basePath: "testdata/", + + wantZipBytesErr: true, + }, } for _, test := range cases { @@ -181,7 +149,7 @@ func TestZip(t *testing.T) { return } - b, err := a.ZipWithPreprocessor(pipeline.NoopPreprocessor) + b, _, err := a.ZipWithPreprocessor(false, pipeline.NoopPreprocessor) is.True(err != nil == test.wantZipBytesErr) // Zip error if test.wantZipBytesErr { diff --git a/internal/daemon/pipeline/testdata/folder/subfolder/other.conf b/internal/daemon/pipeline/testdata/folder/subfolder/other.conf index e2acb19..beb8f82 100644 --- a/internal/daemon/pipeline/testdata/folder/subfolder/other.conf +++ b/internal/daemon/pipeline/testdata/folder/subfolder/other.conf @@ -1,5 +1,6 @@ filter { mutate { + id => mutate add_tag => [ "other" ] } -} \ No newline at end of file +} diff --git a/internal/daemon/pipeline/testdata/invalid/main.conf b/internal/daemon/pipeline/testdata/invalid/no_id.conf similarity index 100% rename from internal/daemon/pipeline/testdata/invalid/main.conf rename to internal/daemon/pipeline/testdata/invalid/no_id.conf diff --git a/internal/daemon/pipeline/testdata/invalid/no_input.conf b/internal/daemon/pipeline/testdata/invalid/no_input.conf new file mode 100644 index 0000000..5481eaa --- /dev/null +++ b/internal/daemon/pipeline/testdata/invalid/no_input.conf @@ -0,0 +1,5 @@ +output { + stdout { + id => stdout + } +} diff --git a/internal/daemon/pipeline/testdata/invalid/no_output.conf b/internal/daemon/pipeline/testdata/invalid/no_output.conf new file mode 100644 index 0000000..5fe4e9e --- /dev/null +++ b/internal/daemon/pipeline/testdata/invalid/no_output.conf @@ -0,0 +1,5 @@ +input { + stdin { + id => stdin + } +} diff --git a/internal/daemon/pipeline/testdata/pipelines_invalid_config.yml b/internal/daemon/pipeline/testdata/pipelines_invalid_config.yml deleted file mode 100644 index 5588b3f..0000000 --- a/internal/daemon/pipeline/testdata/pipelines_invalid_config.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- - -- pipeline.id: main - path.config: "invalid/main.conf" diff --git a/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_id.yml b/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_id.yml new file mode 100644 index 0000000..2ee0d6b --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_id.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "invalid/no_id.conf" diff --git a/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_input.yml b/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_input.yml new file mode 100644 index 0000000..00c9051 --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_input.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "invalid/no_input.conf" diff --git a/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_output.yml b/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_output.yml new file mode 100644 index 0000000..855f739 --- /dev/null +++ b/internal/daemon/pipeline/testdata/pipelines_invalid_config_no_output.yml @@ -0,0 +1,4 @@ +--- + +- pipeline.id: main + path.config: "invalid/no_output.conf" diff --git a/internal/daemon/pluginmock/pluginmock.go b/internal/daemon/pluginmock/pluginmock.go index a53bcfc..756e088 100644 --- a/internal/daemon/pluginmock/pluginmock.go +++ b/internal/daemon/pluginmock/pluginmock.go @@ -91,7 +91,10 @@ func FromFile(filename string) (Mocks, error) { type Mocks map[string]*ast.Plugin func (m Mocks) Walk(c *astutil.Cursor) { - id, _ := c.Plugin().ID() + id, err := c.Plugin().ID() + if err != nil { + return + } if replacement, ok := m[id]; ok { if replacement == nil { diff --git a/testdata/basic_pipeline/main/main.conf b/testdata/basic_pipeline/main/main.conf index 2e8a03f..33d1298 100644 --- a/testdata/basic_pipeline/main/main.conf +++ b/testdata/basic_pipeline/main/main.conf @@ -2,6 +2,11 @@ input { stdin { id => stdin } + + file { + # id commented out intentionally + # id => file + } } filter { From 001c8142eb7c11e967f362660b8f0dd52d1cfd38 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 17 Sep 2021 14:06:48 +0200 Subject: [PATCH 114/143] Update github.com/breml/logstash-config to v0.5.3 Fixes: #141 --- go.mod | 13 +- go.sum | 408 ++++++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 335 insertions(+), 86 deletions(-) diff --git a/go.mod b/go.mod index 00a345d..0025b7e 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/ahmetb/govvv v0.3.0 github.com/axw/gocov v1.0.0 github.com/bmatcuk/doublestar/v2 v2.0.4 - github.com/breml/logstash-config v0.5.1 + github.com/breml/logstash-config v0.5.3 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 github.com/golang/protobuf v1.5.2 github.com/hashicorp/packer v1.4.4 @@ -24,16 +24,13 @@ require ( github.com/mikefarah/yaml/v2 v2.4.0 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.1.3 - github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.5.1 + github.com/spf13/cobra v1.2.1 + github.com/spf13/viper v1.8.1 + github.com/stretchr/testify v1.7.0 github.com/tidwall/gjson v1.6.8 github.com/tidwall/sjson v1.1.5 github.com/yookoala/realpath v1.0.0 // indirect - golang.org/x/mod v0.4.2 // indirect - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/grpc v1.34.0 + google.golang.org/grpc v1.38.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 google.golang.org/protobuf v1.26.0 gopkg.in/VividCortex/ewma.v1 v1.1.1 // indirect diff --git a/go.sum b/go.sum index 59c865b..d283d87 100644 --- a/go.sum +++ b/go.sum @@ -5,33 +5,55 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE= github.com/Azure/azure-sdk-for-go v30.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChrisTrenkamp/goxpath v0.0.0-20170625215350-4fe035839290/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk= github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591/go.mod h1:EHGzQGbwozJBj/4qj3WGrTJ0FqjgOTOxLQ0VNWvPn08= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Telmate/proxmox-api-go v0.0.0-20190815172943-ef9222844e60/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/ahmetb/govvv v0.3.0 h1:YGLGwEyiUwHFy5eh/RUhdupbuaCGBYn5T5GWXp+WJB0= github.com/ahmetb/govvv v0.3.0/go.mod h1:4WRFpdWtc/YtKgPFwa1dr5+9hiRY5uKAL08bOlxOR6s= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= @@ -42,6 +64,7 @@ github.com/antchfx/xmlquery v1.0.0/go.mod h1:/+CnyD/DzHRnv2eRxrVbieRU/FIF6N0C+7o github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43/go.mod h1:S6puKjZ9ZeqUPBv2hEBnMZGcM2J6mOsDRQcmxkMAND0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -52,38 +75,33 @@ github.com/aws/aws-sdk-go v1.16.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.24.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/axw/gocov v1.0.0 h1:YsqYR66hUmilVr23tu8USgnJIJvnwh3n7j5zRn7x4LU= github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI= github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= -github.com/breml/logstash-config v0.5.1 h1:g9n1KnARE/462o2YTl/LrvJ465OX5JyWFYGcxjVcfCI= -github.com/breml/logstash-config v0.5.1/go.mod h1:3vVy6pRfnuI1f4f3ZlH8g3/lCQ0B7N6yOoaUfYALtBY= +github.com/breml/logstash-config v0.5.3 h1:LIFrahm9hMXDc18peZh71lwNwPRxzF3ucdfnE8dIPes= +github.com/breml/logstash-config v0.5.3/go.mod h1:zN6d/ekZxR5vDufuk3344/nSRdiAyaGVA2UTNksT86M= github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437/go.mod h1:PRcPVAAma6zcLpFd4GZrjR/MRpood3TamjKI2m/z/Uw= github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8/go.mod h1:/YnlngP1PARC0SKAZx6kaAEMOp8bNTQGqS+Ka3MctNI= github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= @@ -97,39 +115,53 @@ github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCf github.com/dylanmei/winrmtest v0.0.0-20170819153634-c2fbb09e6c08/go.mod h1:VBVDFSBXCIW8JaHQpI8lldSKfYaLMzP9oyq6IJ4fhzY= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 h1:mKMuZuxwRig082824nGPyH0xVjaKDPjf41kI9W2aTk0= github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018/go.mod h1:UqxAgEOt89sCiXlrc/ycnx00LVvUO/eS8tMUkWX4R7w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -137,7 +169,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -149,13 +183,30 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -168,16 +219,14 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= @@ -185,7 +234,7 @@ github.com/hashicorp/go-getter v1.3.1-0.20190906090232-a0f878cb75da/go.mod h1:7q github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79/go.mod h1:09jT3Y/OIsjTjQ2+3bkVNPDKqWcGIYYvjB2BEKVUdvc= github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= @@ -215,6 +264,8 @@ github.com/hetznercloud/hcloud-go v1.15.1/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hyperonecom/h1-client-go v0.0.0-20190122232013-cf38e8387775/go.mod h1:R9rU87RxxmcD3DkspW9JqGBXiJyg5MA+WNCtJrBtnXs= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-observer v1.0.3 h1:l45TYAEeAB4L2xF6PR2gRLn2NE5tYhudh33MLmC7B80= github.com/imkira/go-observer v1.0.3/go.mod h1:zLzElv2cGTHufQG17IEILJMPDg32TD85fFgKyFv00wU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -222,17 +273,18 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joyent/triton-go v0.0.0-20180116165742-545edbe0d564/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -246,7 +298,7 @@ github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -255,8 +307,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE= github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E= @@ -280,7 +332,6 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -290,7 +341,6 @@ github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RR github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= @@ -298,21 +348,22 @@ github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557/go.mod h1:QuAqW7/z+iv6aWFJdrA8kCbsF0OOJVKCICqTcYBexuY= github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U= github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -324,8 +375,9 @@ github.com/oracle/oci-go-sdk v1.8.0/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBc github.com/outscale/osc-go v0.0.1/go.mod h1:hJLmXzqU/t07qQYh90I0TqZzu9s85Zs6FMrxk3ukiFM= github.com/packer-community/winrmcp v0.0.0-20180921204643-0fd363d6159a/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E= @@ -335,22 +387,15 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v0.0.0-20160118190721-e84cc8c755ca/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible/go.mod h1:T3/WrziK7fYH3C8ilAFAHe99R452/IzIG3YYkqaOFeQ= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/renstrom/fuzzysearch v0.0.0-20160331204855-2d205ac6ec17/go.mod h1:SAEjPB4voP88qmWJXI7mA5m15uNlEnuHLx4Eu2mPGpQ= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -364,7 +409,6 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -372,29 +416,28 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= @@ -407,7 +450,6 @@ github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE= github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c= github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= @@ -418,24 +460,38 @@ github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0/go.mod h1:sBh287mCRwCz6zyXHMmw7sSZGPohVpnx+o+OY4M+i3A= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854= github.com/yookoala/realpath v1.0.0 h1:7OA9pj4FZd+oZDsyvXWQvjn5oBdcHRTV44PpdMSuImQ= github.com/yookoala/realpath v1.0.0/go.mod h1:gJJMA9wuX7AcqLy1+ffPatSCySA1FQ2S8Ya9AIoYBpE= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -443,6 +499,11 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -452,11 +513,21 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -475,24 +546,60 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -503,16 +610,54 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg= +golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -532,9 +677,42 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2pTvDyhZNduBUMD2q3iqs4= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -545,10 +723,30 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -558,16 +756,60 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 h1:M8spwkmx0pHrPq+uMdl22w5CvJ/Y+oAJTIs9oGoCpOE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -578,13 +820,13 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/VividCortex/ewma.v1 v1.1.1 h1:tWHEKkKq802K/JT9RiqGCBU5fW3raAPnJGTE9ostZvg= gopkg.in/VividCortex/ewma.v1 v1.1.1/go.mod h1:TekXuFipeiHWiAlO1+wSS23vTcyFau5u3rxXUSXj710= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= @@ -601,8 +843,9 @@ gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXa gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/h2non/gock.v1 v1.0.12/go.mod h1:KHI4Z1sxDW6P4N3DfTWSEza07YpkQP7KJBfglRMEjKY= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.0 h1:2t0h8NA59dpVQpa5Yh8cIcR6nHAeBIEk0zlLVqfw4N4= +gopkg.in/ini.v1 v1.63.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw= gopkg.in/mattn/go-colorable.v0 v0.1.0 h1:WYuADWvfvYC07fm8ygYB3LMcsc5CunpxfMGKawHkAos= gopkg.in/mattn/go-colorable.v0 v0.1.0/go.mod h1:BVJlBXzARQxdi3nZo6f6bnl5yR20/tOL6p+V0KejgSY= @@ -616,12 +859,21 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 8e658db2fcc6e11e12a95c5af30e93c629354060 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 17 Sep 2021 14:13:40 +0200 Subject: [PATCH 115/143] Update Go to 1.16.8 --- .github/workflows/test.yml | 2 +- Makefile | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2ddd06..c221b3a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: "1.15.5" + go-version: "1.16.8" - name: Install APT-based build dependencies run: > diff --git a/Makefile b/Makefile index 23be31f..c7dd09b 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ OS_NAME := $(shell uname -s) endif # The Docker image to use when building release images. -GOLANG_DOCKER_IMAGE := golang:1.16.7 +GOLANG_DOCKER_IMAGE := golang:1.16.8 INSTALL := install diff --git a/README.md b/README.md index bdfeaea..4ed51c0 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Many Linux distributions make some version of the Go compiler easily installable, but otherwise you can [download and install the latest version](https://golang.org/dl/). The source code is written to use [Go modules](https://github.com/golang/go/wiki/Modules) for dependency -management and it seems you need at least Go 1.13. +management. You need at least least Go 1.16.x. To just build an executable file you don't need anything but the Go compiler; just clone the Logstash Filter Verifier repository and run From 36879a5a5d3ac7ab82f20c1daca090bad582230a Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 18 Oct 2021 21:18:43 +0200 Subject: [PATCH 116/143] Show received events if received count != expect count Fixes: #148 --- internal/testcase/testcase.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index e2aab59..0d0edd5 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -273,10 +273,14 @@ func (tcs *TestCaseSet) Compare(events []logstash.Event, diffCommand []string, l // Don't even attempt to do a deep comparison of the event // lists unless their lengths are equal. if len(tcs.ExpectedEvents) != len(events) { + eventsJSON, err := json.MarshalIndent(events, "", " ") + if err != nil { + return false, err + } comparisonResult := lfvobserver.ComparisonResult{ Status: false, Name: "Compare actual event with expected event", - Explain: fmt.Sprintf("Expected %d event(s), got %d instead.", len(tcs.ExpectedEvents), len(events)), + Explain: fmt.Sprintf("Expected %d event(s), got %d instead.\nReceived events: %s", len(tcs.ExpectedEvents), len(events), string(eventsJSON)), Path: filepath.Base(tcs.File), EventIndex: 0, } From fdab863f91cdf9f0d0fda1c095306d0a998ceed6 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 25 Oct 2021 20:53:17 +0200 Subject: [PATCH 117/143] Move __lfv_id and __lfv_out_passed to @metadata Remove unnecessary __lfv_ tags --- internal/app/daemon/run/run.go | 32 ++++++++++--------- internal/daemon/controller/controller_test.go | 4 +-- internal/daemon/session/files.go | 8 ++--- .../basic_pipeline_debug/testcase1.json | 8 ++--- .../basic_pipeline_debug/testcase2.json | 4 +-- 5 files changed, 25 insertions(+), 31 deletions(-) diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 813a88d..1a6e199 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -269,15 +269,29 @@ func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]st var err error sort.Slice(results, func(i, j int) bool { - idI := gjson.Get(results[i], `__lfv_id`).Int() - idJ := gjson.Get(results[j], `__lfv_id`).Int() + idI := gjson.Get(results[i], `__lfv_metadata.__lfv_id`).Int() + idJ := gjson.Get(results[j], `__lfv_metadata.__lfv_id`).Int() if idI == idJ { - return gjson.Get(results[i], `__lfv_out_passed`).String() < gjson.Get(results[j], `__lfv_out_passed`).String() + return gjson.Get(results[i], `__lfv_metadata.__lfv_out_passed`).String() < gjson.Get(results[j], `__lfv_metadata.__lfv_out_passed`).String() } return idI < idJ }) for i := 0; i < len(results); i++ { + if s.debug { + results[i], err = sjson.Set(results[i], `__lfv_id`, gjson.Get(results[i], `__lfv_metadata.__lfv_id`).String()) + if err != nil { + return nil, err + } + } + + if t.ExportOutputs || s.debug { + results[i], err = sjson.Set(results[i], `__lfv_out_passed`, gjson.Get(results[i], `__lfv_metadata.__lfv_out_passed`).String()) + if err != nil { + return nil, err + } + } + // Export metadata if t.ExportMetadata { metadata := gjson.Get(results[i], "__lfv_metadata") @@ -307,18 +321,6 @@ func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]st continue } - results[i], err = sjson.Delete(results[i], "__lfv_id") - if err != nil { - return nil, err - } - - if !t.ExportOutputs { - results[i], err = sjson.Delete(results[i], "__lfv_out_passed") - if err != nil { - return nil, err - } - } - tags := []string{} for _, tag := range gjson.Get(results[i], "tags").Array() { if strings.HasPrefix(tag.String(), "__lfv_") { diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index 7576853..39d6d6b 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -144,8 +144,8 @@ func TestCompleteCycle(t *testing.T) { // Simulate pipelines ready from instance c.PipelinesReady("stdin", "output", "main", "input", "__lfv_pipelines_running") - c.ReceiveEvent(`{ "__lfv_id": "1", "message": "result 1" }`) - c.ReceiveEvent(`{ "__lfv_id": "2", "message": "result 2" }`) + c.ReceiveEvent(`{ "message": "result 1" }`) + c.ReceiveEvent(`{ "message": "result 2" }`) res, err := c.GetResults() is.NoErr(err) diff --git a/internal/daemon/session/files.go b/internal/daemon/session/files.go index afc08b0..9c1523a 100644 --- a/internal/daemon/session/files.go +++ b/internal/daemon/session/files.go @@ -8,8 +8,7 @@ const outputPipeline = `input { filter { mutate { - add_tag => [ "__lfv_out_{{ .PipelineOrigName }}_passed" ] - add_field => { "[__lfv_out_passed]" => "{{ .PipelineOrigName }}" } + add_field => { "[@metadata][__lfv_out_passed]" => "{{ .PipelineOrigName }}" } } } @@ -36,13 +35,12 @@ filter { ruby { id => '__lfv_ruby_count' init => '@count = 0' - code => 'event.set("__lfv_id", @count.to_s) + code => 'event.set("[@metadata][__lfv_id]", @count.to_s) @count += 1' tag_on_exception => '__lfv_ruby_count_exception' } mutate { - add_tag => [ "__lfv_in_passed" ] # Remove fields "host", "sequence" and optionally "message", which are # automatically created by the generator input. remove_field => [ "host", "sequence" ] @@ -50,7 +48,7 @@ filter { translate { dictionary_path => "{{ .FieldsFilename }}" - field => "[__lfv_id]" + field => "[@metadata][__lfv_id]" destination => "[@metadata][__lfv_fields]" exact => true override => true diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index 2c0c59e..5e5c6e8 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -18,10 +18,8 @@ "__lfv_out_passed": "stdout", "message": "test case message", "tags": [ - "__lfv_in_passed", "main2_passed", - "sut_passed", - "__lfv_out_stdout_passed" + "sut_passed" ], "type": "syslog" }, @@ -30,10 +28,8 @@ "__lfv_out_passed": "stdout", "message": "test case message 2", "tags": [ - "__lfv_in_passed", "main2_passed", - "sut_passed", - "__lfv_out_stdout_passed" + "sut_passed" ], "type": "syslog" } diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index aefb168..2a079e0 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -24,10 +24,8 @@ "message": "", "second": "2", "tags": [ - "__lfv_in_passed", "main2_passed", - "sut_passed", - "__lfv_out_stdout_passed" + "sut_passed" ] } ] From a3f8d89c42ad85d4d7f8800cc07a63f25c773282 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 25 Oct 2021 20:55:53 +0200 Subject: [PATCH 118/143] Add test case for issue #153 Fixes: #153 --- integration_test.go | 3 ++ testdata/issue_153.yml | 2 ++ testdata/issue_153/main.conf | 32 +++++++++++++++++++ testdata/testcases/issue_153/testcase.yml | 39 +++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 testdata/issue_153.yml create mode 100644 testdata/issue_153/main.conf create mode 100644 testdata/testcases/issue_153/testcase.yml diff --git a/integration_test.go b/integration_test.go index 04d84b8..1b0ee27 100644 --- a/integration_test.go +++ b/integration_test.go @@ -170,6 +170,9 @@ func TestIntegration(t *testing.T) { { name: "testcases_event", }, + { + name: "issue_153", + }, } for _, tc := range cases { diff --git a/testdata/issue_153.yml b/testdata/issue_153.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/issue_153.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/issue_153/main.conf b/testdata/issue_153/main.conf new file mode 100644 index 0000000..0d2cf4b --- /dev/null +++ b/testdata/issue_153/main.conf @@ -0,0 +1,32 @@ +input { + stdin { + codec => "json" + id => "input" + } +} + +filter { + clone { + id => "clone" + clones => [ "cloned-event" ] + } + + if ([type] == "cloned-event") { + prune { + id => "prune" + whitelist_names => [ "^type$", "^test$", "^original$" ] + } + mutate { + id => "mutate" + replace => { + "original" => "nope" + } + } + } +} + +output { + stdout { + id => "output" + } +} diff --git a/testdata/testcases/issue_153/testcase.yml b/testdata/testcases/issue_153/testcase.yml new file mode 100644 index 0000000..c74d65b --- /dev/null +++ b/testdata/testcases/issue_153/testcase.yml @@ -0,0 +1,39 @@ +--- +input_plugin: "input" +ignore: + - "@timestamp" + - type +testcases: + - input: + - > + { + "test": "Test 1", + "original": "yep" + } + expected: + - test: "Test 1" + original: "nope" + - test: "Test 1" + original: "yep" + - input: + - > + { + "test": "Test 2", + "original": "yep" + } + expected: + - test: "Test 2" + original: "nope" + - test: "Test 2" + original: "yep" + - input: + - > + { + "test": "Test 3", + "original": "yep" + } + expected: + - test: "Test 3" + original: "nope" + - test: "Test 3" + original: "yep" From e35a83f9773fb34bf9abe710bcdeef9cce8fa0ef Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 25 Oct 2021 21:23:01 +0200 Subject: [PATCH 119/143] Add testcase for issue 150 Fixes: #150 --- integration_test.go | 15 +++++++++---- testdata/issue_150.yml | 2 ++ testdata/issue_150/main.conf | 26 +++++++++++++++++++++++ testdata/testcases/issue_150/testcase.yml | 16 ++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 testdata/issue_150.yml create mode 100644 testdata/issue_150/main.conf create mode 100644 testdata/testcases/issue_150/testcase.yml diff --git a/integration_test.go b/integration_test.go index 1b0ee27..5b0f204 100644 --- a/integration_test.go +++ b/integration_test.go @@ -105,8 +105,9 @@ func TestIntegration(t *testing.T) { // Run tests cases := []struct { - name string - debug bool + name string + debug bool + repeat int minimumVersion *semver.Version @@ -173,6 +174,10 @@ func TestIntegration(t *testing.T) { { name: "issue_153", }, + { + name: "issue_150", + repeat: 5, + }, } for _, tc := range cases { @@ -210,8 +215,10 @@ func TestIntegration(t *testing.T) { ) is.NoErr(err) - err = client.Run() - is.NoErr(err) + for i := 0; i <= tc.repeat; i++ { + err = client.Run() + is.NoErr(err) + } }) } diff --git a/testdata/issue_150.yml b/testdata/issue_150.yml new file mode 100644 index 0000000..d988261 --- /dev/null +++ b/testdata/issue_150.yml @@ -0,0 +1,2 @@ +- pipeline.id: pipeline + path.config: "main.conf" diff --git a/testdata/issue_150/main.conf b/testdata/issue_150/main.conf new file mode 100644 index 0000000..ffb0684 --- /dev/null +++ b/testdata/issue_150/main.conf @@ -0,0 +1,26 @@ +input { + stdin { + id => "input" + } +} + +filter { + clone { + id => "clone" + clones => [ "cloned_event" ] + } +} + +output { + if ([type] == "cloned_event") { + stdout { + id => "clone_output" + } + } + + if ([type] == "original_event") { + stdout { + id => "original_output" + } + } +} diff --git a/testdata/testcases/issue_150/testcase.yml b/testdata/testcases/issue_150/testcase.yml new file mode 100644 index 0000000..8eb8b0d --- /dev/null +++ b/testdata/testcases/issue_150/testcase.yml @@ -0,0 +1,16 @@ +input_plugin: "input" +fields: + type: original_event +export_outputs: true +ignore: + - "@timestamp" + - "message" +testcases: + - input: + - > + Test message + expected: + - type: cloned_event + __lfv_out_passed: "clone_output" + - type: original_event + __lfv_out_passed: "original_output" From 9fe620581a68f3cc5d8020929daf2298da09d203 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 26 Oct 2021 21:41:33 +0200 Subject: [PATCH 120/143] Fix bug with fields (deep copy) Fixes: #155 --- go.mod | 1 + go.sum | 2 ++ integration_test.go | 3 ++ internal/logstash/fieldset.go | 6 ++++ internal/testcase/testcase.go | 4 +-- testdata/issue_155.yml | 2 ++ testdata/issue_155/main.conf | 12 ++++++++ testdata/testcases/issue_155/testcase.yml | 35 +++++++++++++++++++++++ 8 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 testdata/issue_155.yml create mode 100644 testdata/issue_155/main.conf create mode 100644 testdata/testcases/issue_155/testcase.yml diff --git a/go.mod b/go.mod index 0025b7e..71f84db 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/mattn/go-shellwords v1.0.6 github.com/mholt/archiver/v3 v3.5.0 github.com/mikefarah/yaml/v2 v2.4.0 + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.2.1 diff --git a/go.sum b/go.sum index d283d87..da91d73 100644 --- a/go.sum +++ b/go.sum @@ -358,6 +358,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/moul/anonuuid v0.0.0-20160222162117-609b752a95ef/go.mod h1:LgKrp0Iss/BVwquptq4eIe6HPr0s3t1WHT5x0qOh14U= github.com/moul/gotty-client v0.0.0-20180327180212-b26a57ebc215/go.mod h1:CxM/JGtpRrEPve5H04IhxJrGhxgwxMc6jSP2T4YD60w= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= diff --git a/integration_test.go b/integration_test.go index 5b0f204..6e7bbca 100644 --- a/integration_test.go +++ b/integration_test.go @@ -178,6 +178,9 @@ func TestIntegration(t *testing.T) { name: "issue_150", repeat: 5, }, + { + name: "issue_155", + }, } for _, tc := range cases { diff --git a/internal/logstash/fieldset.go b/internal/logstash/fieldset.go index f16273f..8acfc5b 100644 --- a/internal/logstash/fieldset.go +++ b/internal/logstash/fieldset.go @@ -7,6 +7,8 @@ import ( "fmt" "sort" "strings" + + "github.com/mohae/deepcopy" ) // FieldSet contains a set of fields for a Logstash event and can be @@ -48,6 +50,10 @@ func (fs FieldSet) LogstashHash() (string, error) { return "{ " + strings.Join(result, " ") + " }", nil } +func (fs FieldSet) Clone() FieldSet { + return deepcopy.Copy(fs).(FieldSet) +} + // serializeAsLogstashLiteral serializes a single entity into a // Logstash value literal. func serializeAsLogstashLiteral(k string, v interface{}) ([]string, []string, error) { diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 0d0edd5..6e0153e 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -208,7 +208,7 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { tcs.descriptions = make([]string, len(tcs.ExpectedEvents)) for range tcs.InputLines { - tcs.Events = append(tcs.Events, tcs.InputFields) + tcs.Events = append(tcs.Events, tcs.InputFields.Clone()) } for _, tc := range tcs.TestCases { @@ -221,7 +221,7 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { // Process each input line for range tc.InputLines { // Global Fields first. - tcs.Events = append(tcs.Events, tcs.InputFields) + tcs.Events = append(tcs.Events, tcs.InputFields.Clone()) // Merge with test case fields, eventually overwriting global fields. for k, v := range tc.InputFields { tcs.Events[len(tcs.Events)-1][k] = v diff --git a/testdata/issue_155.yml b/testdata/issue_155.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/issue_155.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/issue_155/main.conf b/testdata/issue_155/main.conf new file mode 100644 index 0000000..b2ec45e --- /dev/null +++ b/testdata/issue_155/main.conf @@ -0,0 +1,12 @@ +input { + stdin { + id => json_lines + codec => json + } +} + +output { + tcp { + id => "stdout" + } +} diff --git a/testdata/testcases/issue_155/testcase.yml b/testdata/testcases/issue_155/testcase.yml new file mode 100644 index 0000000..71ebe23 --- /dev/null +++ b/testdata/testcases/issue_155/testcase.yml @@ -0,0 +1,35 @@ +--- +ignore: + - "@timestamp" +input_plugin: "json_lines" +testcases: + - input: + - > + { + "message": "message1" + } + fields: + hostname: host1 + expected: + - message: "message1" + hostname: "host1" + - input: + - > + { + "message": "message2" + } + fields: + hostname: host2 + expected: + - message: "message2" + hostname: "host2" + - input: + - > + { + "message": "message3" + } + fields: + hostname: host3 + expected: + - message: "message3" + hostname: "host3" From f9650313204291682528813c3252dcfc250e0cb8 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 26 Oct 2021 21:07:10 +0200 Subject: [PATCH 121/143] Document LFV specific fields in @metadata --- README.md | 23 +++++++++++++++++++++++ internal/app/daemon/run/run.go | 2 +- internal/app/daemon_run.go | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ed51c0..12c779c 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ * [Daemon mode](#daemon-mode) * [Windows compatibility](#windows-compatibility) * [Plugin ID (Daemon mode)](#plugin-id-daemon-mode) + * [@metadata field](#metadata-field) * [Development](#development) * [Dependencies](#dependencies) * [Known limitations and future work](#known-limitations-and-future-work) @@ -659,6 +660,28 @@ are two options: of the test cases by adding the flag `--add-missing-id`. +### @metadata field + +Events in Logstash have a special field called [`@metadata`](https://www.elastic.co/guide/en/logstash/master/event-dependent-configuration.html#metadata). +The contents of `@metadata` are not part of any of your events at output time. +LFV does make use of `@metadata` to store information, which is required to +control the processing of the events. To ensure the correct operation of LFV, +the Logstash configuration under test is not allowed to alter any field within +the `@metadata`, which start with `__lfv_`. For example the following fields +are used by LFV (not conclusive): + +* `[@metadata][__lfv_id]` +* `[@metadata][__lfv_out_passed]` + +These fields are also removed from `@metadata` even when the test case definition +does include `export_metadata`. + +Additionally, all fields within `@metadata` starting with `__tmp_` are removed. + +For debug purposes, the `--debug` flag may be passed, which prevents the +removal of the above mentioned fields. + + ## Development ### Dependencies diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 1a6e199..9de052f 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -298,7 +298,7 @@ func (s Test) postProcessResults(results []string, t testcase.TestCaseSet) ([]st if metadata.Exists() && metadata.IsObject() { md := make(map[string]json.RawMessage, len(metadata.Map())) for key, value := range metadata.Map() { - if strings.HasPrefix(key, "__lfv") || strings.HasPrefix(key, "__tmp") { + if strings.HasPrefix(key, "__lfv_") || strings.HasPrefix(key, "__tmp_") { continue } md[key] = json.RawMessage(value.Raw) diff --git a/internal/app/daemon_run.go b/internal/app/daemon_run.go index f783d3c..84a0fd7 100644 --- a/internal/app/daemon_run.go +++ b/internal/app/daemon_run.go @@ -26,7 +26,7 @@ func makeDaemonRunCmd() *cobra.Command { _ = viper.BindPFlag("testcase-dir", cmd.Flags().Lookup("testcase-dir")) cmd.Flags().String("plugin-mock", "", "path to a yaml file containing the definition for the plugin mocks.") _ = viper.BindPFlag("plugin-mock", cmd.Flags().Lookup("plugin-mock")) - cmd.Flags().Bool("debug", false, "enable debug mode; e.g. prevents stripping '__lfv' data from Logstash events") + cmd.Flags().Bool("debug", false, "enable debug mode; e.g. prevents stripping '__lfv_' prefixed fields/tags from Logstash events") _ = viper.BindPFlag("debug", cmd.Flags().Lookup("debug")) cmd.Flags().String("metadata-key", "@metadata", "Key under which the content of the `@metadata` field is exposed in the returned events.") _ = viper.BindPFlag("metadata-key", cmd.Flags().Lookup("metadata-key")) From 4ed3de7d61673c59a2b36c7cbce2c12be693ff7b Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 26 Oct 2021 21:03:30 +0200 Subject: [PATCH 122/143] Add documentation about clone filter --- README.md | 16 ++++++++++++++++ integration_test.go | 4 ++++ testdata/issue_150a.yml | 2 ++ testdata/issue_150a/main.conf | 18 ++++++++++++++++++ testdata/testcases/issue_150a/testcase.yml | 15 +++++++++++++++ 5 files changed, 55 insertions(+) create mode 100644 testdata/issue_150a.yml create mode 100644 testdata/issue_150a/main.conf create mode 100644 testdata/testcases/issue_150a/testcase.yml diff --git a/README.md b/README.md index 12c779c..fb1ece4 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ * [Daemon mode](#daemon-mode) * [Windows compatibility](#windows-compatibility) * [Plugin ID (Daemon mode)](#plugin-id-daemon-mode) + * [Logstash Plugins](#logstash-plugins) * [@metadata field](#metadata-field) * [Development](#development) * [Dependencies](#dependencies) @@ -660,6 +661,21 @@ are two options: of the test cases by adding the flag `--add-missing-id`. +### Logstash Plugins + +#### Clone (Filter) + +LFV configures Logstash such that the ordering of the events while being +processed by the pipeline is ensured. In this regard, the [`clone` filter](https://www.elastic.co/guide/en/logstash/current/plugins-filters-clone.html) +is special, because it injects additional events into the processing pipeline. + +The cloned events can be expected in the following order: + +1. original event +2. clones of the event in the order of appearance in the `clones` attribute + of the `clone` filter. + + ### @metadata field Events in Logstash have a special field called [`@metadata`](https://www.elastic.co/guide/en/logstash/master/event-dependent-configuration.html#metadata). diff --git a/integration_test.go b/integration_test.go index 6e7bbca..e5f06e4 100644 --- a/integration_test.go +++ b/integration_test.go @@ -178,6 +178,10 @@ func TestIntegration(t *testing.T) { name: "issue_150", repeat: 5, }, + { + name: "issue_150a", + repeat: 5, + }, { name: "issue_155", }, diff --git a/testdata/issue_150a.yml b/testdata/issue_150a.yml new file mode 100644 index 0000000..d988261 --- /dev/null +++ b/testdata/issue_150a.yml @@ -0,0 +1,2 @@ +- pipeline.id: pipeline + path.config: "main.conf" diff --git a/testdata/issue_150a/main.conf b/testdata/issue_150a/main.conf new file mode 100644 index 0000000..7180aa9 --- /dev/null +++ b/testdata/issue_150a/main.conf @@ -0,0 +1,18 @@ +input { + stdin { + id => "input" + } +} + +filter { + clone { + id => "clone" + clones => [ "cloned1", "cloned2", "cloned3" ] + } +} + +output { + stdout { + id => "output" + } +} diff --git a/testdata/testcases/issue_150a/testcase.yml b/testdata/testcases/issue_150a/testcase.yml new file mode 100644 index 0000000..8e3787a --- /dev/null +++ b/testdata/testcases/issue_150a/testcase.yml @@ -0,0 +1,15 @@ +input_plugin: "input" +fields: + type: original_event +ignore: + - "@timestamp" + - "message" +testcases: + - input: + - > + Test message + expected: + - type: original_event + - type: cloned1 + - type: cloned2 + - type: cloned3 From af7126170be4de3feb73f562b0ce62d12ca6c19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 20 Nov 2021 22:25:00 +0100 Subject: [PATCH 123/143] Upgrade github.com/tidwall/gjson The previous version had a DoS vulnerability (CVE-2021-42836) that probably could affect LFV if it was fed untrusted input. --- go.mod | 2 +- go.sum | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 71f84db..d108824 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 - github.com/tidwall/gjson v1.6.8 + github.com/tidwall/gjson v1.11.0 github.com/tidwall/sjson v1.1.5 github.com/yookoala/realpath v1.0.0 // indirect google.golang.org/grpc v1.38.0 diff --git a/go.sum b/go.sum index da91d73..116c632 100644 --- a/go.sum +++ b/go.sum @@ -444,12 +444,15 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= github.com/tencentcloud/tencentcloud-sdk-go v3.0.71+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= -github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w= github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= -github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/gjson v1.11.0 h1:C16pk7tQNiH6VlCrtIXL1w8GaOsi1X3W8KDkE1BuYd4= +github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE= github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE= github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsNBlnBpRF2fj3c= From ed9d0f72f5d97e2c8859266f3c7de46c7fad7dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 20 Nov 2021 21:39:42 +0100 Subject: [PATCH 124/143] Upgrade golangci-lint and enable more linters The new linters are reasonable and didn't require any changes to the existing code. --- .golangci.yml | 8 ++++++++ Makefile | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 5dc118a..e08eb25 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -2,21 +2,29 @@ linters: disable-all: true enable: + - asciicheck + - bidichk - deadcode + - durationcheck + - errname + - exportloopref - gocyclo - godot - gofmt - gosec - gosimple - ineffassign + - makezero - misspell - prealloc - staticcheck - structcheck + - tenv - typecheck - unconvert - unused - varcheck + - wastedassign - whitespace linters-settings: diff --git a/Makefile b/Makefile index c7dd09b..b79b507 100644 --- a/Makefile +++ b/Makefile @@ -44,7 +44,7 @@ PROTOC_GEN_GO := $(GOBIN)/protoc-gen-go$(EXEC_SUFFIX) PROTOC_GEN_GO_GRPC := $(GOBIN)/protoc-gen-go-grpc$(EXEC_SUFFIX) MOQ := $(GOBIN)/moq$(EXEC_SUFFIX) -GOLANGCI_LINT_VERSION := v1.32.2 +GOLANGCI_LINT_VERSION := v1.43.0 .PHONY: all all: $(PROGRAM)$(EXEC_SUFFIX) From 86ac6cb4989a9a124589d2ba4ef3d4eb8e172198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Sat, 20 Nov 2021 21:19:01 +0100 Subject: [PATCH 125/143] Change default loglevel from WARNING to INFO With WARNING as the default loglevel the daemon wouldn't emit any useful status messages to let you know it has actually started up and is ready to be used. INFO will give users a few more messages overall but there's no dramatic increase and it's not likely to be regarded as spammy. --- internal/app/app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 549ba68..b9dfaab 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -33,7 +33,7 @@ func Execute(version string, stdout, stderr io.Writer) int { viper.AddConfigPath("/etc/logstash-filter-verifier/") // Setup default values - viper.SetDefault("loglevel", "WARNING") + viper.SetDefault("loglevel", "INFO") viper.SetDefault("socket", "/tmp/logstash-filter-verifier.sock") viper.SetDefault("logstash.path", "/usr/share/logstash/bin/logstash") viper.SetDefault("inflight-shutdown-timeout", 10*time.Second) @@ -76,7 +76,7 @@ func makeRootCmd(version string) *cobra.Command { rootCmd.InitDefaultVersionFlag() - rootCmd.PersistentFlags().String("loglevel", "WARNING", "Set the desired level of logging (one of: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG).") + rootCmd.PersistentFlags().String("loglevel", "INFO", "Set the desired level of logging (one of: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG).") _ = viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel")) rootCmd.AddCommand(makeStandaloneCmd()) From 69bc9a4ad3cd9e465b8c121bcd0995d872aae28d Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 10 Dec 2021 16:16:52 +0100 Subject: [PATCH 126/143] Preserve type field from original input Fixes: #166 --- integration_test.go | 3 +++ internal/daemon/logstashconfig/file.go | 2 +- testdata/issue_166.yml | 2 ++ testdata/issue_166/main.conf | 16 ++++++++++++++++ testdata/testcases/issue_166/testcase.yml | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 testdata/issue_166.yml create mode 100644 testdata/issue_166/main.conf create mode 100644 testdata/testcases/issue_166/testcase.yml diff --git a/integration_test.go b/integration_test.go index e5f06e4..b541727 100644 --- a/integration_test.go +++ b/integration_test.go @@ -185,6 +185,9 @@ func TestIntegration(t *testing.T) { { name: "issue_155", }, + { + name: "issue_166", + }, } for _, tc := range cases { diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 68e2633..d628791 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -95,7 +95,7 @@ func (r replaceInputsWalker) replaceInputs(c *astutil.Cursor) { continue } switch attr.Name() { - case "add_field", "tags": + case "add_field", "tags", "type": attrs = append(attrs, attr) case "codec": r.inputCodecs[id] = attr.String() diff --git a/testdata/issue_166.yml b/testdata/issue_166.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/issue_166.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/issue_166/main.conf b/testdata/issue_166/main.conf new file mode 100644 index 0000000..6bce559 --- /dev/null +++ b/testdata/issue_166/main.conf @@ -0,0 +1,16 @@ +input { + stdin { + id => input + type => "test_event" + add_field => { + "field" => "value" + } + tags => [ "tag" ] + } +} + +output { + tcp { + id => "stdout" + } +} diff --git a/testdata/testcases/issue_166/testcase.yml b/testdata/testcases/issue_166/testcase.yml new file mode 100644 index 0000000..4e42c3f --- /dev/null +++ b/testdata/testcases/issue_166/testcase.yml @@ -0,0 +1,14 @@ +--- +ignore: + - "@timestamp" +input_plugin: "input" +testcases: + - input: + - Test + expected: + - field: "value" + message: Test + type: test_event + tags: [ + "tag" + ] From 69c0af2e0299e56084267b45b47d0683fda518b0 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 10 Dec 2021 15:24:39 +0100 Subject: [PATCH 127/143] Make wait-for-late-arrivals-timeout configurable Fixes: #168 --- integration_test.go | 2 +- internal/app/daemon/daemon.go | 32 ++++++++++--------- internal/app/daemon_start.go | 6 +++- internal/daemon/controller/controller.go | 11 ++++--- internal/daemon/controller/controller_test.go | 13 +++++--- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/integration_test.go b/integration_test.go index b541727..9c276b2 100644 --- a/integration_test.go +++ b/integration_test.go @@ -72,7 +72,7 @@ func TestIntegration(t *testing.T) { } log := testLogger - server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second, 30*time.Second, noCleanup) + server := daemon.New(socket, logstashPath, nil, log, 10*time.Second, 3*time.Second, 30*time.Second, noCleanup, 50*time.Millisecond) version, err := standalonelogstash.DetectVersion(logstashPath, os.Environ()) is.NoErr(err) diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index b7102de..193b58a 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -48,9 +48,10 @@ type Daemon struct { tempdir string - inflightShutdownTimeout time.Duration - shutdownTimeout time.Duration - waitForStateTimeout time.Duration + inflightShutdownTimeout time.Duration + shutdownTimeout time.Duration + waitForStateTimeout time.Duration + waitForLateArrivalsTimeout time.Duration noCleanup bool @@ -64,19 +65,20 @@ type Daemon struct { } // New creates a new logstash filter verifier daemon. -func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration, waitForStateTimeout time.Duration, noCleanup bool) Daemon { +func New(socket string, logstashPath string, keptEnvVars []string, log logging.Logger, inflightShutdownTimeout time.Duration, shutdownTimeout time.Duration, waitForStateTimeout time.Duration, noCleanup bool, waitForLateArrivalsTimeout time.Duration) Daemon { ctxShutdownSignal, shutdownSignalFunc := context.WithCancel(context.Background()) return Daemon{ - socket: socket, - logstashPath: logstashPath, - keptEnvVars: keptEnvVars, - inflightShutdownTimeout: inflightShutdownTimeout, - shutdownTimeout: shutdownTimeout, - log: log, - ctxShutdownSignal: ctxShutdownSignal, - shutdownSignalFunc: shutdownSignalFunc, - waitForStateTimeout: waitForStateTimeout, - noCleanup: noCleanup, + socket: socket, + logstashPath: logstashPath, + keptEnvVars: keptEnvVars, + inflightShutdownTimeout: inflightShutdownTimeout, + shutdownTimeout: shutdownTimeout, + log: log, + ctxShutdownSignal: ctxShutdownSignal, + shutdownSignalFunc: shutdownSignalFunc, + waitForStateTimeout: waitForStateTimeout, + noCleanup: noCleanup, + waitForLateArrivalsTimeout: waitForLateArrivalsTimeout, } } @@ -112,7 +114,7 @@ func (d *Daemon) Run(ctx context.Context) error { logstashControllerFactory := func() (session.LogstashController, error) { shutdownLogstashInstancesWG.Add(1) instance := logstash.New(ctxKill, d.logstashPath, env, d.log, shutdownLogstashInstancesWG) - logstashController, err := controller.NewController(instance, tempdir, d.log, d.waitForStateTimeout, isOrderedPipelineSupported) + logstashController, err := controller.NewController(instance, tempdir, d.log, d.waitForStateTimeout, isOrderedPipelineSupported, d.waitForLateArrivalsTimeout) if err != nil { return nil, err } diff --git a/internal/app/daemon_start.go b/internal/app/daemon_start.go index 2df5c90..27af28d 100644 --- a/internal/app/daemon_start.go +++ b/internal/app/daemon_start.go @@ -33,6 +33,9 @@ func makeDaemonStartCmd() *cobra.Command { cmd.Flags().Bool("no-cleanup", false, "with no-cleanup set to true, the temporary files (logstash pipelines, logs, etc.) are not cleaned up and left on the system for later inspection.") _ = viper.BindPFlag("no-cleanup", cmd.Flags().Lookup("no-cleanup")) + cmd.Flags().Duration("wait-for-late-arrivals-timeout", 50*time.Millisecond, "duration to wait for late arriving events from Logstash (e.g. to test Logstash filters with a timeout like aggregation filter)") + _ = viper.BindPFlag("wait-for-late-arrivals-timeout", cmd.Flags().Lookup("wait-for-late-arrivals-timeout")) + // TODO: Move default values to some sort of global lookup like defaultKeptEnvVars. // TODO: Not yet sure, if this should be global or only in standalone. cmd.Flags().StringSlice("keep-env", nil, "Add this environment variable to the list of variables that will be preserved from the calling process's environment.") @@ -52,12 +55,13 @@ func runDaemonStart(_ *cobra.Command, _ []string) error { shutdownTimeout := viper.GetDuration("shutdown-timeout") waitForStateTimeout := viper.GetDuration("wait-for-state-timeout") noCleanup := viper.GetBool("no-cleanup") + waitForLateArrivalsTimeout := viper.GetDuration("wait-for-late-arrivals-timeout") log := viper.Get("logger").(logging.Logger) log.Debugf("config: socket: %s", socket) log.Debugf("config: logstash-path: %s", logstashPath) - s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout, waitForStateTimeout, noCleanup) + s := daemon.New(socket, logstashPath, keptEnvs, log, inflightShutdownTimeout, shutdownTimeout, waitForStateTimeout, noCleanup, waitForLateArrivalsTimeout) defer s.Cleanup() return s.Run(context.Background()) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 3124b51..aa82009 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -31,11 +31,13 @@ type Controller struct { stateMachine *stateMachine waitForStateTimeout time.Duration isOrderedPipelineSupported bool - receivedEvents *events - pipelines *pipelines + waitForLateArrivalsTimeout time.Duration + + receivedEvents *events + pipelines *pipelines } -func NewController(instance Instance, baseDir string, log logging.Logger, waitForStateTimeout time.Duration, isOrderedPipelineSupported bool) (*Controller, error) { +func NewController(instance Instance, baseDir string, log logging.Logger, waitForStateTimeout time.Duration, isOrderedPipelineSupported bool, waitForLateArrivalsTimeout time.Duration) (*Controller, error) { id := idgen.New() workDir := filepath.Join(baseDir, LogstashInstanceDirectoryPrefix, id) @@ -74,6 +76,7 @@ func NewController(instance Instance, baseDir string, log logging.Logger, waitFo waitForStateTimeout: waitForStateTimeout, isOrderedPipelineSupported: isOrderedPipelineSupported, + waitForLateArrivalsTimeout: waitForLateArrivalsTimeout, receivedEvents: newEvents(), pipelines: newPipelines(), @@ -140,7 +143,7 @@ func (c *Controller) GetResults() ([]string, error) { // The last event might be sent through multiple outputs, therefore we give // a little headroom for more events with the same ID to arrive. - time.Sleep(50 * time.Millisecond) + time.Sleep(c.waitForLateArrivalsTimeout) return c.receivedEvents.get(), nil } diff --git a/internal/daemon/controller/controller_test.go b/internal/daemon/controller/controller_test.go index 39d6d6b..e642eb2 100644 --- a/internal/daemon/controller/controller_test.go +++ b/internal/daemon/controller/controller_test.go @@ -16,7 +16,10 @@ import ( "github.com/magnusbaeck/logstash-filter-verifier/v2/internal/logging" ) -const defaultWaitForStateTimeout = 30 * time.Second +const ( + defaultWaitForStateTimeout = 30 * time.Second + defaultWaitForLateArrivalsTimeout = 50 * time.Millisecond +) func TestNewController(t *testing.T) { cases := []struct { @@ -33,7 +36,7 @@ func TestNewController(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(nil, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) + c, err := controller.NewController(nil, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true, defaultWaitForLateArrivalsTimeout) is.NoErr(err) is.True(file.Exists(filepath.Join(tempdir, controller.LogstashInstanceDirectoryPrefix, c.ID(), "logstash.yml"))) // logstash.yml @@ -75,7 +78,7 @@ func TestLaunch(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true, defaultWaitForLateArrivalsTimeout) is.NoErr(err) err = c.Launch(context.Background()) @@ -108,7 +111,7 @@ func TestCompleteCycle(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true, defaultWaitForLateArrivalsTimeout) is.NoErr(err) err = c.Launch(context.Background()) @@ -191,7 +194,7 @@ func TestSetupTest_Shutdown(t *testing.T) { tempdir := t.TempDir() - c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true) + c, err := controller.NewController(instance, tempdir, logging.NoopLogger, defaultWaitForStateTimeout, true, defaultWaitForLateArrivalsTimeout) is.NoErr(err) ctx, cancel := context.WithCancel(context.Background()) From 5ee643619da9b95305e96c9c569c38821ca90dec Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 13 Dec 2021 22:34:13 +0100 Subject: [PATCH 128/143] Fix adding of missing ID Fixes: #150 --- internal/daemon/logstashconfig/file.go | 2 +- internal/daemon/pipeline/pipeline.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index d628791..4b424eb 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -205,7 +205,7 @@ func (v *validator) walk(c *astutil.Cursor) { if err != nil { if v.addMissingID { plugin := c.Plugin() - id = fmt.Sprintf("%s-%d", name, v.count) + id = fmt.Sprintf("id_missing_%0000d", v.count) plugin.Attributes = append(plugin.Attributes, ast.NewStringAttribute("id", id, ast.DoubleQuoted)) c.Replace(plugin) diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index b1d722d..dbcb074 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -156,7 +156,7 @@ func (a Archive) ZipWithPreprocessor(addMissingID bool, preprocess func([]byte) outputs[id] += count } - _, err = f.Write(body) + _, err = f.Write(configFile.Body) if err != nil { return nil, nil, err } From 30181673e0b22d7db1e0b933b0ed92af0f518813 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 14 Dec 2021 08:43:34 +0100 Subject: [PATCH 129/143] Fix fmt format for leading zeros --- internal/daemon/logstashconfig/file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 4b424eb..b7b12e7 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -205,7 +205,7 @@ func (v *validator) walk(c *astutil.Cursor) { if err != nil { if v.addMissingID { plugin := c.Plugin() - id = fmt.Sprintf("id_missing_%0000d", v.count) + id = fmt.Sprintf("id_missing_%04d", v.count) plugin.Attributes = append(plugin.Attributes, ast.NewStringAttribute("id", id, ast.DoubleQuoted)) c.Replace(plugin) From 399cfcd700b19bbeaa947bcf65e416350beda1f5 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 20 Dec 2021 13:04:35 +0100 Subject: [PATCH 130/143] Make auto generated plugin ID unique among multiple files Fixes: #175 --- integration_test.go | 4 ++++ internal/daemon/logstashconfig/file.go | 19 ++++++++++++++++++- testdata/issue_175.yml | 4 ++++ testdata/issue_175/pipeline1/1.conf | 5 +++++ testdata/issue_175/pipeline1/2.conf | 5 +++++ testdata/issue_175/pipeline1/3.conf | 5 +++++ testdata/issue_175/pipeline2/main.conf | 9 +++++++++ testdata/testcases/issue_175/testcase.yml | 10 ++++++++++ 8 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 testdata/issue_175.yml create mode 100644 testdata/issue_175/pipeline1/1.conf create mode 100644 testdata/issue_175/pipeline1/2.conf create mode 100644 testdata/issue_175/pipeline1/3.conf create mode 100644 testdata/issue_175/pipeline2/main.conf create mode 100644 testdata/testcases/issue_175/testcase.yml diff --git a/integration_test.go b/integration_test.go index 9c276b2..38a3d9f 100644 --- a/integration_test.go +++ b/integration_test.go @@ -188,6 +188,10 @@ func TestIntegration(t *testing.T) { { name: "issue_166", }, + { + name: "issue_175", + addMissingID: true, + }, } for _, tc := range cases { diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index b7b12e7..9bb73c9 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -6,6 +6,7 @@ import ( "os" "path" "path/filepath" + "strings" config "github.com/breml/logstash-config" "github.com/breml/logstash-config/ast" @@ -137,6 +138,7 @@ func (o *outputPipelineReplacer) walk(c *astutil.Cursor) { if err != nil { id = idgen.New() } + id = pluginIDSave(id) outputName := fmt.Sprintf("lfv_output_%s", id) o.outputs = append(o.outputs, id) @@ -153,6 +155,7 @@ func (f *File) Validate(addMissingID bool) (inputs map[string]int, outputs map[s pluginIDs: map[string]int{}, inputs: map[string]int{}, outputs: map[string]int{}, + name: pluginIDSave(f.Name), addMissingID: addMissingID, } @@ -192,6 +195,7 @@ type validator struct { pluginIDs map[string]int inputs map[string]int outputs map[string]int + name string count int addMissingID bool } @@ -205,7 +209,7 @@ func (v *validator) walk(c *astutil.Cursor) { if err != nil { if v.addMissingID { plugin := c.Plugin() - id = fmt.Sprintf("id_missing_%04d", v.count) + id = fmt.Sprintf("id_missing_%s_%04d", v.name, v.count) plugin.Attributes = append(plugin.Attributes, ast.NewStringAttribute("id", id, ast.DoubleQuoted)) c.Replace(plugin) @@ -244,3 +248,16 @@ func (f *File) ApplyMocks(m pluginmock.Mocks) error { return nil } + +func pluginIDSave(in string) string { + return strings.Map(func(r rune) rune { + switch { + case r >= 'A' && r <= 'Z' || + r >= 'a' && r <= 'z' || + r >= '0' && r <= '9': + return r + default: + return '_' + } + }, in) +} diff --git a/testdata/issue_175.yml b/testdata/issue_175.yml new file mode 100644 index 0000000..d59f839 --- /dev/null +++ b/testdata/issue_175.yml @@ -0,0 +1,4 @@ +- pipeline.id: pipeline1 + path.config: "pipeline1/*.conf" +- pipeline.id: pipeline2 + path.config: "pipeline2/*.conf" diff --git a/testdata/issue_175/pipeline1/1.conf b/testdata/issue_175/pipeline1/1.conf new file mode 100644 index 0000000..75cc549 --- /dev/null +++ b/testdata/issue_175/pipeline1/1.conf @@ -0,0 +1,5 @@ +input { + stdin { + id => "input" + } +} diff --git a/testdata/issue_175/pipeline1/2.conf b/testdata/issue_175/pipeline1/2.conf new file mode 100644 index 0000000..7d0dd3f --- /dev/null +++ b/testdata/issue_175/pipeline1/2.conf @@ -0,0 +1,5 @@ +filter { + mutate { + add_field => { "blah" => "foo" } + } +} diff --git a/testdata/issue_175/pipeline1/3.conf b/testdata/issue_175/pipeline1/3.conf new file mode 100644 index 0000000..58ec053 --- /dev/null +++ b/testdata/issue_175/pipeline1/3.conf @@ -0,0 +1,5 @@ +output { + pipeline { + send_to => [pipeline2] + } +} diff --git a/testdata/issue_175/pipeline2/main.conf b/testdata/issue_175/pipeline2/main.conf new file mode 100644 index 0000000..1f6e843 --- /dev/null +++ b/testdata/issue_175/pipeline2/main.conf @@ -0,0 +1,9 @@ +input { + pipeline { + address => pipeline2 + } +} + +output { + stdout {} +} diff --git a/testdata/testcases/issue_175/testcase.yml b/testdata/testcases/issue_175/testcase.yml new file mode 100644 index 0000000..5e368fd --- /dev/null +++ b/testdata/testcases/issue_175/testcase.yml @@ -0,0 +1,10 @@ +input_plugin: "input" +ignore: + - "@timestamp" +testcases: + - input: + - >- + Test + expected: + - message: Test + blah: foo From 0293386ff02c4f40e7d3d4ca911d5c54f2493c37 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Mon, 20 Dec 2021 13:52:13 +0100 Subject: [PATCH 131/143] Update special cases in documentation Fixes: #120 Fixes: #151 Fixes: #169 --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fb1ece4..c1a70b5 100644 --- a/README.md +++ b/README.md @@ -415,8 +415,10 @@ have been added: these plugins can be replaced with mocks. In particular the [mutate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html) and the [translate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-translate.html) filters have proven to be helpful as replacements. - An other use case for mocks is to replace pipeline input and output plugins - in order to test pipelines in isolation. + Other use cases for mocks are: + * replace filters, that operate on environment variables, that need to be changed + between different test cases (see [#120](https://github.com/magnusbaeck/logstash-filter-verifier/issues/120)). + * replace pipeline input and output plugins in order to test pipelines in isolation. In order to execute a test case in daemon mode, first the daemon needs to be started (e.g. in its own terminal or shell): @@ -715,6 +717,9 @@ present: * Some log formats don't include all timestamp components. For example, most syslog formats don't include the year. This should be dealt with somehow. +* Logstash configurations, which remove the `@timestamp` field can not be tested + using the daemon mode (see [#151](https://github.com/magnusbaeck/logstash-filter-verifier/issues/151)). +* The maximum size of events, that can be processed is ~64KB (see [#169](https://github.com/magnusbaeck/logstash-filter-verifier/issues/169)). ## License From 2dbd2d560b8235c14aae4fc69dbbd43da892e9f3 Mon Sep 17 00:00:00 2001 From: Vladimir Kravets Date: Fri, 24 Mar 2023 14:05:31 +0200 Subject: [PATCH 132/143] Add additional logstash configuration path Fix the macos brew logstash'es configuration folder --- internal/logstash/invocation.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/logstash/invocation.go b/internal/logstash/invocation.go index 0df53a0..dcfdef4 100644 --- a/internal/logstash/invocation.go +++ b/internal/logstash/invocation.go @@ -160,13 +160,14 @@ func (inv *Invocation) Release() { // from the Logstash distribution so that e.g. JVM and logging is set // up properly even though we provide our own logstash.yml. // -// The files to copy are either dug up from ../config relative to the +// The files to copy are either dug up from ../config or ../etc/logstash relative to the // Logstash executable or from /etc/logstash, where they're stored in // the RPM/Debian case. If successful, the directory where the files // were found is returned. func copyConfigFiles(logstashPath string, configDir string) (string, error) { sourceDirs := []string{ filepath.Clean(filepath.Join(filepath.Dir(logstashPath), "../config")), + filepath.Clean(filepath.Join(filepath.Dir(logstashPath), "../etc/logstash")), "/etc/logstash", } sourceFiles := []string{ From e77dc8aa12df6f17b9a3ca2e2efbd7bf0140d5a9 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 24 May 2024 08:22:06 +0200 Subject: [PATCH 133/143] Add instructions on how to run integration tests --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index c1a70b5..eb62869 100644 --- a/README.md +++ b/README.md @@ -712,6 +712,37 @@ present: * Proto buffer compiler (`protobuf-compiler`) +### Run Integration Tests + +In order to run the integration tests, the following preperation is needed: + +1. Run `go run . setup 8.12.1` to download Logstash version 8.12.1 +2. Prepare a `logstash-filter-verifier.yml` config file, which points to the + downloaded Logstash version, e.g. + + ```yaml + --- + + loglevel: debug + logstash: + path: ./3rdparty/logstash-8.12.1-linux-x86_64/bin/logstash + keep-envs: + - TESTMODE + metadata-key: __metadata + ``` + +3. Execute the integration tests by providing the respective env var: + + `INTEGRATION_TEST=1 go test -v .` + + or + + ```shell + export INTEGRATION_TEST=1 + go test -v . + ``` + + ## Known limitations and future work * Some log formats don't include all timestamp components. For From 78778dd3a11414314fdff0837b6ab9d9e585c255 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 24 May 2024 08:24:01 +0200 Subject: [PATCH 134/143] Fix integration tests for Logstash 8.0.1 --- integration_test.go | 15 +++++++- testdata/codec_test/codec_test.conf | 9 ----- testdata/codec_version_7_test.yml | 2 ++ testdata/codec_version_7_test/codec_test.conf | 10 ++++++ testdata/codec_version_7_test/output.conf | 5 +++ testdata/codec_version_8_test.yml | 2 ++ testdata/codec_version_8_test/codec_test.conf | 10 ++++++ testdata/codec_version_8_test/output.conf | 5 +++ testdata/issue_150/main.conf | 1 + testdata/issue_150a/main.conf | 1 + testdata/issue_153/main.conf | 1 + .../basic_logstash_config_dir/testcase1.json | 3 +- .../testcase1.json | 3 +- .../testcases/basic_pipeline/testcase1.json | 3 +- .../testcases/basic_pipeline/testcase2.json | 3 +- .../basic_pipeline_debug/testcase1.json | 3 +- .../basic_pipeline_debug/testcase2.json | 3 +- .../testcases/codec_optional_test/csv.json | 3 +- testdata/testcases/codec_test/edn.json | 3 +- testdata/testcases/codec_test/edn_lines.json | 3 +- testdata/testcases/codec_test/graphite.json | 6 ++-- testdata/testcases/codec_test/json.json | 3 +- testdata/testcases/codec_test/json_lines.json | 3 +- testdata/testcases/codec_test/line.json | 3 +- testdata/testcases/codec_test/multiline.json | 3 +- testdata/testcases/codec_test/plain.json | 3 +- .../cef.json | 3 +- .../testcases/codec_version_8_test/cef.json | 36 +++++++++++++++++++ .../conditional_output.json | 3 +- testdata/testcases/drop_and_split/drop.json | 3 +- .../testcases/drop_and_split/drop_all.json | 3 +- testdata/testcases/drop_and_split/split.json | 3 +- testdata/testcases/filtermock/filtermock.json | 3 +- .../testcases/inputoutputmock/testcase.json | 3 +- testdata/testcases/issue_150/testcase.yml | 1 + testdata/testcases/issue_150a/testcase.yml | 1 + testdata/testcases/issue_153/testcase.yml | 1 + testdata/testcases/issue_155/testcase.yml | 1 + testdata/testcases/issue_166/testcase.yml | 1 + testdata/testcases/issue_175/testcase.yml | 1 + .../multiple_parallel_outputs.json | 3 +- ...tiple_parallel_outputs_export_outputs.json | 3 +- .../pipeline_to_pipeline.json | 3 +- .../testcases/special_chars/testcases.json | 3 +- .../testcases/testcases_event/testcases.json | 3 +- .../testcases_metadata_logstash.json | 3 +- .../testcases_metadata_logstash_global.json | 3 +- .../testcases_metadata_nested.json | 3 +- .../testcases_metadata_nested_global.json | 3 +- 49 files changed, 157 insertions(+), 42 deletions(-) create mode 100644 testdata/codec_version_7_test.yml create mode 100644 testdata/codec_version_7_test/codec_test.conf create mode 100644 testdata/codec_version_7_test/output.conf create mode 100644 testdata/codec_version_8_test.yml create mode 100644 testdata/codec_version_8_test/codec_test.conf create mode 100644 testdata/codec_version_8_test/output.conf rename testdata/testcases/{codec_test => codec_version_7_test}/cef.json (96%) create mode 100644 testdata/testcases/codec_version_8_test/cef.json diff --git a/integration_test.go b/integration_test.go index 38a3d9f..0420a4b 100644 --- a/integration_test.go +++ b/integration_test.go @@ -110,6 +110,7 @@ func TestIntegration(t *testing.T) { repeat int minimumVersion *semver.Version + maximumVersion *semver.Version // exclusive // optional integration tests require additional logstash plugins, // which are not provided by a default installation. @@ -151,6 +152,14 @@ func TestIntegration(t *testing.T) { { name: "codec_test", }, + { + name: "codec_version_7_test", + maximumVersion: semver.MustParse("8.0.0"), + }, + { + name: "codec_version_8_test", + minimumVersion: semver.MustParse("8.0.0"), + }, { name: "codec_optional_test", optional: true, @@ -199,7 +208,11 @@ func TestIntegration(t *testing.T) { is := is.New(t) if tc.minimumVersion != nil && tc.minimumVersion.GreaterThan(version) { - t.Skipf("Logstash minimum version not satisfied, require %s, have %s", tc.minimumVersion.String(), version.String()) + t.Skipf("Logstash minimum version not satisfied, require at least %s, have %s", tc.minimumVersion.String(), version.String()) + } + + if tc.maximumVersion != nil && tc.maximumVersion.LessThan(version) { + t.Skipf("Logstash maximum version not satisfied, require less than %s, have %s", tc.maximumVersion.String(), version.String()) } if tc.optional && os.Getenv("INTEGRATION_TEST_OPTIONAL") != "1" { diff --git a/testdata/codec_test/codec_test.conf b/testdata/codec_test/codec_test.conf index b685af3..cd24181 100644 --- a/testdata/codec_test/codec_test.conf +++ b/testdata/codec_test/codec_test.conf @@ -1,13 +1,4 @@ input { - stdin { - id => cef - codec => cef - add_field => { - "input_codec" => "cef" - } - tags => [ "input_codec_cef" ] - } - stdin { id => edn codec => edn diff --git a/testdata/codec_version_7_test.yml b/testdata/codec_version_7_test.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/codec_version_7_test.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/codec_version_7_test/codec_test.conf b/testdata/codec_version_7_test/codec_test.conf new file mode 100644 index 0000000..340b083 --- /dev/null +++ b/testdata/codec_version_7_test/codec_test.conf @@ -0,0 +1,10 @@ +input { + stdin { + id => cef + codec => cef + add_field => { + "input_codec" => "cef" + } + tags => [ "input_codec_cef" ] + } +} diff --git a/testdata/codec_version_7_test/output.conf b/testdata/codec_version_7_test/output.conf new file mode 100644 index 0000000..5481eaa --- /dev/null +++ b/testdata/codec_version_7_test/output.conf @@ -0,0 +1,5 @@ +output { + stdout { + id => stdout + } +} diff --git a/testdata/codec_version_8_test.yml b/testdata/codec_version_8_test.yml new file mode 100644 index 0000000..75d1a35 --- /dev/null +++ b/testdata/codec_version_8_test.yml @@ -0,0 +1,2 @@ +- pipeline.id: main + path.config: "*.conf" diff --git a/testdata/codec_version_8_test/codec_test.conf b/testdata/codec_version_8_test/codec_test.conf new file mode 100644 index 0000000..340b083 --- /dev/null +++ b/testdata/codec_version_8_test/codec_test.conf @@ -0,0 +1,10 @@ +input { + stdin { + id => cef + codec => cef + add_field => { + "input_codec" => "cef" + } + tags => [ "input_codec_cef" ] + } +} diff --git a/testdata/codec_version_8_test/output.conf b/testdata/codec_version_8_test/output.conf new file mode 100644 index 0000000..5481eaa --- /dev/null +++ b/testdata/codec_version_8_test/output.conf @@ -0,0 +1,5 @@ +output { + stdout { + id => stdout + } +} diff --git a/testdata/issue_150/main.conf b/testdata/issue_150/main.conf index ffb0684..60b52b5 100644 --- a/testdata/issue_150/main.conf +++ b/testdata/issue_150/main.conf @@ -8,6 +8,7 @@ filter { clone { id => "clone" clones => [ "cloned_event" ] + ecs_compatibility => "disabled" } } diff --git a/testdata/issue_150a/main.conf b/testdata/issue_150a/main.conf index 7180aa9..bb14ad5 100644 --- a/testdata/issue_150a/main.conf +++ b/testdata/issue_150a/main.conf @@ -8,6 +8,7 @@ filter { clone { id => "clone" clones => [ "cloned1", "cloned2", "cloned3" ] + ecs_compatibility => "disabled" } } diff --git a/testdata/issue_153/main.conf b/testdata/issue_153/main.conf index 0d2cf4b..ca84972 100644 --- a/testdata/issue_153/main.conf +++ b/testdata/issue_153/main.conf @@ -9,6 +9,7 @@ filter { clone { id => "clone" clones => [ "cloned-event" ] + ecs_compatibility => "disabled" } if ([type] == "cloned-event") { diff --git a/testdata/testcases/basic_logstash_config_dir/testcase1.json b/testdata/testcases/basic_logstash_config_dir/testcase1.json index 24eda32..5535b83 100644 --- a/testdata/testcases/basic_logstash_config_dir/testcase1.json +++ b/testdata/testcases/basic_logstash_config_dir/testcase1.json @@ -3,7 +3,8 @@ "type": "syslog" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/basic_logstash_config_file.conf/testcase1.json b/testdata/testcases/basic_logstash_config_file.conf/testcase1.json index 24eda32..5535b83 100644 --- a/testdata/testcases/basic_logstash_config_file.conf/testcase1.json +++ b/testdata/testcases/basic_logstash_config_file.conf/testcase1.json @@ -3,7 +3,8 @@ "type": "syslog" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/basic_pipeline/testcase1.json b/testdata/testcases/basic_pipeline/testcase1.json index bf2a204..1568623 100644 --- a/testdata/testcases/basic_pipeline/testcase1.json +++ b/testdata/testcases/basic_pipeline/testcase1.json @@ -3,7 +3,8 @@ "type": "syslog" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/basic_pipeline/testcase2.json b/testdata/testcases/basic_pipeline/testcase2.json index 0e0d4b0..b8d1b71 100644 --- a/testdata/testcases/basic_pipeline/testcase2.json +++ b/testdata/testcases/basic_pipeline/testcase2.json @@ -4,7 +4,8 @@ "second": "2" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase1.json b/testdata/testcases/basic_pipeline_debug/testcase1.json index 5e5c6e8..43cd96f 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase1.json +++ b/testdata/testcases/basic_pipeline_debug/testcase1.json @@ -3,7 +3,8 @@ "type": "syslog" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin-with-dash", "testcases": [ diff --git a/testdata/testcases/basic_pipeline_debug/testcase2.json b/testdata/testcases/basic_pipeline_debug/testcase2.json index 2a079e0..4aa80ed 100644 --- a/testdata/testcases/basic_pipeline_debug/testcase2.json +++ b/testdata/testcases/basic_pipeline_debug/testcase2.json @@ -4,7 +4,8 @@ "second": "2" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin-with-dash", "export_metadata": true, diff --git a/testdata/testcases/codec_optional_test/csv.json b/testdata/testcases/codec_optional_test/csv.json index 7561cb8..5e529b3 100644 --- a/testdata/testcases/codec_optional_test/csv.json +++ b/testdata/testcases/codec_optional_test/csv.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "csv", "testcases": [ diff --git a/testdata/testcases/codec_test/edn.json b/testdata/testcases/codec_test/edn.json index 5d4d3ee..3729cf9 100644 --- a/testdata/testcases/codec_test/edn.json +++ b/testdata/testcases/codec_test/edn.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "edn", "testcases": [ diff --git a/testdata/testcases/codec_test/edn_lines.json b/testdata/testcases/codec_test/edn_lines.json index 9b505a8..edeaa4f 100644 --- a/testdata/testcases/codec_test/edn_lines.json +++ b/testdata/testcases/codec_test/edn_lines.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "edn_lines", "testcases": [ diff --git a/testdata/testcases/codec_test/graphite.json b/testdata/testcases/codec_test/graphite.json index 2ce7b37..1933ab0 100644 --- a/testdata/testcases/codec_test/graphite.json +++ b/testdata/testcases/codec_test/graphite.json @@ -1,4 +1,8 @@ { + "ignore": [ + "@timestamp", + "event" + ], "input_plugin": "graphite", "testcases": [ { @@ -8,13 +12,11 @@ ], "expected": [ { - "@timestamp": "2021-04-02T17:37:50.000Z", "foo.bar.metric1": 100, "input_codec": "graphite", "tags": [ "input_codec_graphite" ] }, { - "@timestamp": "2021-04-02T17:37:50.000Z", "foo.bar.metric2": 200, "input_codec": "graphite", "tags": [ "input_codec_graphite" ] diff --git a/testdata/testcases/codec_test/json.json b/testdata/testcases/codec_test/json.json index ea87f15..083fccc 100644 --- a/testdata/testcases/codec_test/json.json +++ b/testdata/testcases/codec_test/json.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "json", "testcases": [ diff --git a/testdata/testcases/codec_test/json_lines.json b/testdata/testcases/codec_test/json_lines.json index 99e0a42..5fa93e6 100644 --- a/testdata/testcases/codec_test/json_lines.json +++ b/testdata/testcases/codec_test/json_lines.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "json_lines", "testcases": [ diff --git a/testdata/testcases/codec_test/line.json b/testdata/testcases/codec_test/line.json index 16a6a36..ad9d738 100644 --- a/testdata/testcases/codec_test/line.json +++ b/testdata/testcases/codec_test/line.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "line", "testcases": [ diff --git a/testdata/testcases/codec_test/multiline.json b/testdata/testcases/codec_test/multiline.json index 0b49bc1..774c719 100644 --- a/testdata/testcases/codec_test/multiline.json +++ b/testdata/testcases/codec_test/multiline.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "multiline", "testcases": [ diff --git a/testdata/testcases/codec_test/plain.json b/testdata/testcases/codec_test/plain.json index 2411e79..2dfb8c9 100644 --- a/testdata/testcases/codec_test/plain.json +++ b/testdata/testcases/codec_test/plain.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "plain", "testcases": [ diff --git a/testdata/testcases/codec_test/cef.json b/testdata/testcases/codec_version_7_test/cef.json similarity index 96% rename from testdata/testcases/codec_test/cef.json rename to testdata/testcases/codec_version_7_test/cef.json index 9fec8b9..51f4b52 100644 --- a/testdata/testcases/codec_test/cef.json +++ b/testdata/testcases/codec_version_7_test/cef.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "cef", "testcases": [ diff --git a/testdata/testcases/codec_version_8_test/cef.json b/testdata/testcases/codec_version_8_test/cef.json new file mode 100644 index 0000000..1a44296 --- /dev/null +++ b/testdata/testcases/codec_version_8_test/cef.json @@ -0,0 +1,36 @@ +{ + "ignore": [ + "@timestamp", + "event" + ], + "input_plugin": "cef", + "testcases": [ + { + "input": [ + "CEF:0|security|threatmanager|1.0|100|trojan successfully stopped|10|src=10.0.0.192 dst=12.121.122.82 spt=1232" + ], + "expected": [ + { + "cef": { + "name": "trojan successfully stopped", + "version": "0" + }, + "destination": { + "ip": "12.121.122.82" + }, + "input_codec": "cef", + "observer": { + "product": "threatmanager", + "vendor": "security", + "version": "1.0" + }, + "source": { + "ip": "10.0.0.192", + "port": "1232" + }, + "tags": [ "input_codec_cef" ] + } + ] + } + ] +} diff --git a/testdata/testcases/conditional_output/conditional_output.json b/testdata/testcases/conditional_output/conditional_output.json index e1b6e22..8aeb374 100644 --- a/testdata/testcases/conditional_output/conditional_output.json +++ b/testdata/testcases/conditional_output/conditional_output.json @@ -2,7 +2,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/drop_and_split/drop.json b/testdata/testcases/drop_and_split/drop.json index 90ba68d..d5876d3 100644 --- a/testdata/testcases/drop_and_split/drop.json +++ b/testdata/testcases/drop_and_split/drop.json @@ -2,7 +2,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/drop_and_split/drop_all.json b/testdata/testcases/drop_and_split/drop_all.json index 9dd7637..2a6cf52 100644 --- a/testdata/testcases/drop_and_split/drop_all.json +++ b/testdata/testcases/drop_and_split/drop_all.json @@ -2,7 +2,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/drop_and_split/split.json b/testdata/testcases/drop_and_split/split.json index 781d7e3..7c24bbc 100644 --- a/testdata/testcases/drop_and_split/split.json +++ b/testdata/testcases/drop_and_split/split.json @@ -2,7 +2,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/filtermock/filtermock.json b/testdata/testcases/filtermock/filtermock.json index e2b3b67..dcb81b6 100644 --- a/testdata/testcases/filtermock/filtermock.json +++ b/testdata/testcases/filtermock/filtermock.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/inputoutputmock/testcase.json b/testdata/testcases/inputoutputmock/testcase.json index ca08ae0..7c3b36c 100644 --- a/testdata/testcases/inputoutputmock/testcase.json +++ b/testdata/testcases/inputoutputmock/testcase.json @@ -3,7 +3,8 @@ "type": "syslog" }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "input", "testcases": [ diff --git a/testdata/testcases/issue_150/testcase.yml b/testdata/testcases/issue_150/testcase.yml index 8eb8b0d..6f9bd28 100644 --- a/testdata/testcases/issue_150/testcase.yml +++ b/testdata/testcases/issue_150/testcase.yml @@ -5,6 +5,7 @@ export_outputs: true ignore: - "@timestamp" - "message" + - "event" testcases: - input: - > diff --git a/testdata/testcases/issue_150a/testcase.yml b/testdata/testcases/issue_150a/testcase.yml index 8e3787a..ebb01e2 100644 --- a/testdata/testcases/issue_150a/testcase.yml +++ b/testdata/testcases/issue_150a/testcase.yml @@ -4,6 +4,7 @@ fields: ignore: - "@timestamp" - "message" + - "event" testcases: - input: - > diff --git a/testdata/testcases/issue_153/testcase.yml b/testdata/testcases/issue_153/testcase.yml index c74d65b..d7cf7e4 100644 --- a/testdata/testcases/issue_153/testcase.yml +++ b/testdata/testcases/issue_153/testcase.yml @@ -3,6 +3,7 @@ input_plugin: "input" ignore: - "@timestamp" - type + - event testcases: - input: - > diff --git a/testdata/testcases/issue_155/testcase.yml b/testdata/testcases/issue_155/testcase.yml index 71ebe23..3df5f6c 100644 --- a/testdata/testcases/issue_155/testcase.yml +++ b/testdata/testcases/issue_155/testcase.yml @@ -1,6 +1,7 @@ --- ignore: - "@timestamp" + - "event" input_plugin: "json_lines" testcases: - input: diff --git a/testdata/testcases/issue_166/testcase.yml b/testdata/testcases/issue_166/testcase.yml index 4e42c3f..197ed05 100644 --- a/testdata/testcases/issue_166/testcase.yml +++ b/testdata/testcases/issue_166/testcase.yml @@ -1,6 +1,7 @@ --- ignore: - "@timestamp" + - "event" input_plugin: "input" testcases: - input: diff --git a/testdata/testcases/issue_175/testcase.yml b/testdata/testcases/issue_175/testcase.yml index 5e368fd..c7049aa 100644 --- a/testdata/testcases/issue_175/testcase.yml +++ b/testdata/testcases/issue_175/testcase.yml @@ -1,6 +1,7 @@ input_plugin: "input" ignore: - "@timestamp" + - "event" testcases: - input: - >- diff --git a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json index 07e25fc..921f447 100644 --- a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json +++ b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs.json @@ -2,7 +2,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json index 181c420..558c6ef 100644 --- a/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json +++ b/testdata/testcases/multiple_parallel_outputs/multiple_parallel_outputs_export_outputs.json @@ -3,7 +3,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "export_outputs": true, diff --git a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json index 2fde702..1652e26 100644 --- a/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json +++ b/testdata/testcases/pipeline_to_pipeline/pipeline_to_pipeline.json @@ -2,7 +2,8 @@ "fields": { }, "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stage1_input", "testcases": [ diff --git a/testdata/testcases/special_chars/testcases.json b/testdata/testcases/special_chars/testcases.json index a392398..80b1b85 100644 --- a/testdata/testcases/special_chars/testcases.json +++ b/testdata/testcases/special_chars/testcases.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "testcases": [ diff --git a/testdata/testcases/testcases_event/testcases.json b/testdata/testcases/testcases_event/testcases.json index 7be3eb9..bd9bd3e 100644 --- a/testdata/testcases/testcases_event/testcases.json +++ b/testdata/testcases/testcases_event/testcases.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "fields": { diff --git a/testdata/testcases/testcases_event/testcases_metadata_logstash.json b/testdata/testcases/testcases_event/testcases_metadata_logstash.json index fe2a139..59c8ad6 100644 --- a/testdata/testcases/testcases_event/testcases_metadata_logstash.json +++ b/testdata/testcases/testcases_event/testcases_metadata_logstash.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "export_metadata": true, diff --git a/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json b/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json index ac2b416..e55df4f 100644 --- a/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json +++ b/testdata/testcases/testcases_event/testcases_metadata_logstash_global.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "export_metadata": true, diff --git a/testdata/testcases/testcases_event/testcases_metadata_nested.json b/testdata/testcases/testcases_event/testcases_metadata_nested.json index 94fa771..60f0b64 100644 --- a/testdata/testcases/testcases_event/testcases_metadata_nested.json +++ b/testdata/testcases/testcases_event/testcases_metadata_nested.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "export_metadata": true, diff --git a/testdata/testcases/testcases_event/testcases_metadata_nested_global.json b/testdata/testcases/testcases_event/testcases_metadata_nested_global.json index 285995e..4ca1bd7 100644 --- a/testdata/testcases/testcases_event/testcases_metadata_nested_global.json +++ b/testdata/testcases/testcases_event/testcases_metadata_nested_global.json @@ -1,6 +1,7 @@ { "ignore": [ - "@timestamp" + "@timestamp", + "event" ], "input_plugin": "stdin", "export_metadata": true, From 256424f9da13c16b6325be2292a4b5537061679d Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Fri, 24 May 2024 11:36:52 +0200 Subject: [PATCH 135/143] Fix pipeline running detection for Logstash > 8.11.x --- internal/daemon/instance/logstash/processors.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/daemon/instance/logstash/processors.go b/internal/daemon/instance/logstash/processors.go index bed8d40..70cbd00 100644 --- a/internal/daemon/instance/logstash/processors.go +++ b/internal/daemon/instance/logstash/processors.go @@ -98,6 +98,13 @@ func (i *instance) logstashLogProcessor(t *tail.Tail) { case "Pipelines running": rp := gjson.Get(line.Text, `logEvent.running_pipelines.0.metaClass.metaClass.metaClass.running_pipelines`).String() runningPipelines := extractPipelines(rp) + if runningPipelines == nil { + // Since Logstash 8.11.x, the running pipelines are directly in the logEvent. + rps := gjson.Get(line.Text, `logEvent.running_pipelines`).Array() + for _, rp := range rps { + runningPipelines = append(runningPipelines, rp.String()) + } + } i.log.Debugf("taillog: -> pipeline running: %v", runningPipelines) i.controller.PipelinesReady(runningPipelines...) From 7e0e8fb2630d4c20023b6f4edd0c5ce4d9213db6 Mon Sep 17 00:00:00 2001 From: Lucas Bremgartner Date: Tue, 28 May 2024 21:57:12 +0200 Subject: [PATCH 136/143] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Magnus Bäck --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb62869..deb6244 100644 --- a/README.md +++ b/README.md @@ -714,7 +714,7 @@ present: ### Run Integration Tests -In order to run the integration tests, the following preperation is needed: +In order to run the integration tests, the following preparation is needed: 1. Run `go run . setup 8.12.1` to download Logstash version 8.12.1 2. Prepare a `logstash-filter-verifier.yml` config file, which points to the From cf9cf2c39ae42ab2f293cbcc23040e46b98d6516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 6 Jun 2024 20:22:43 +0200 Subject: [PATCH 137/143] README.md: Add missing ToC entry for integration tests This entry was missing from commit e77dc8a where the section was added. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index deb6244..bdbc92b 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ * [@metadata field](#metadata-field) * [Development](#development) * [Dependencies](#dependencies) + * [Run Integration Tests](#run-integration-tests) * [Known limitations and future work](#known-limitations-and-future-work) * [License](#license) From 88819298457b6e6aefdef79838deac93698c6c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 6 Jun 2024 22:05:01 +0200 Subject: [PATCH 138/143] Upgrade Go compiler to 1.22.4 Apart from flipping all version references, a few other upgrades had to take place: - The old github.com/matryer/moq wasn't compatible with newer Go versions. This resulted in some whitespace changes in the generated files. - An execution of "go fix" changed the build tag in a few files from the old "// +build foo" to the newer "//go:build foo". - golangci-lint had to be upgraded to the newest version. In the new version, three linters we'd enabled had been removed so we had to remove them from the configuration, and one rule that triggered violations in a few dozen files has been suppressed and will be addressed separately. --- .github/workflows/test.yml | 2 +- .golangci.yml | 11 +- Makefile | 4 +- go.mod | 53 +++++++-- go.sum | 23 ++-- .../instance/mock/logstash_instance_mock.go | 32 +++--- .../session/logstash_controller_mock_test.go | 68 ++++++------ internal/daemon/session/pool_mock_test.go | 32 +++--- internal/logging/logger_mock.go | 104 ++++++++++-------- pprof.go | 2 +- tools.go | 2 +- 11 files changed, 201 insertions(+), 132 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c221b3a..1d979d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: "1.16.8" + go-version: "1.22.4" - name: Install APT-based build dependencies run: > diff --git a/.golangci.yml b/.golangci.yml index e08eb25..f2bb64f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,7 +4,6 @@ linters: enable: - asciicheck - bidichk - - deadcode - durationcheck - errname - exportloopref @@ -18,15 +17,21 @@ linters: - misspell - prealloc - staticcheck - - structcheck - tenv - typecheck - unconvert - unused - - varcheck - wastedassign - whitespace linters-settings: misspell: locale: US + +issues: + exclude-rules: + # We'll replace references to io/ioutil later. Until then we don't + # want to hear about it. + - linters: + - staticcheck + text: 'SA1019: "io/ioutil" has been deprecated' diff --git a/Makefile b/Makefile index b79b507..096b967 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ OS_NAME := $(shell uname -s) endif # The Docker image to use when building release images. -GOLANG_DOCKER_IMAGE := golang:1.16.8 +GOLANG_DOCKER_IMAGE := golang:1.22.4 INSTALL := install @@ -44,7 +44,7 @@ PROTOC_GEN_GO := $(GOBIN)/protoc-gen-go$(EXEC_SUFFIX) PROTOC_GEN_GO_GRPC := $(GOBIN)/protoc-gen-go-grpc$(EXEC_SUFFIX) MOQ := $(GOBIN)/moq$(EXEC_SUFFIX) -GOLANGCI_LINT_VERSION := v1.43.0 +GOLANGCI_LINT_VERSION := v1.59.0 .PHONY: all all: $(PROGRAM)$(EXEC_SUFFIX) diff --git a/go.mod b/go.mod index d108824..6d2a1dd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/magnusbaeck/logstash-filter-verifier/v2 -go 1.16 +go 1.22 + +toolchain go1.22.4 require ( github.com/Masterminds/semver/v3 v3.0.1 @@ -16,9 +18,7 @@ require ( github.com/matm/gocov-html v0.0.0-20200509184451-71874e2e203b github.com/matoous/go-nanoid v1.5.0 github.com/matryer/is v1.4.0 - github.com/matryer/moq v0.2.1 - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.11 // indirect + github.com/matryer/moq v0.3.4 github.com/mattn/go-shellwords v1.0.6 github.com/mholt/archiver/v3 v3.5.0 github.com/mikefarah/yaml/v2 v2.4.0 @@ -30,16 +30,55 @@ require ( github.com/stretchr/testify v1.7.0 github.com/tidwall/gjson v1.11.0 github.com/tidwall/sjson v1.1.5 - github.com/yookoala/realpath v1.0.0 // indirect google.golang.org/grpc v1.38.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 google.golang.org/protobuf v1.26.0 - gopkg.in/VividCortex/ewma.v1 v1.1.1 // indirect gopkg.in/cheggaaa/pb.v2 v2.0.7 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + github.com/andybalholm/brotli v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/klauspost/compress v1.10.10 // indirect + github.com/klauspost/pgzip v1.2.4 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.11 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/nwaples/rardecode v1.1.0 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pierrec/lz4/v4 v4.0.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/ulikunitz/xz v0.5.7 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/yookoala/realpath v1.0.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.17.0 // indirect + google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect + gopkg.in/VividCortex/ewma.v1 v1.1.1 // indirect gopkg.in/fatih/color.v1 v1.7.0 // indirect + gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect + gopkg.in/ini.v1 v1.63.0 // indirect gopkg.in/mattn/go-colorable.v0 v0.1.0 // indirect gopkg.in/mattn/go-isatty.v0 v0.0.4 // indirect gopkg.in/mattn/go-runewidth.v0 v0.0.4 // indirect - gopkg.in/yaml.v2 v2.4.0 + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index 116c632..155cb61 100644 --- a/go.sum +++ b/go.sum @@ -318,8 +318,8 @@ github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3eu github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/matryer/moq v0.2.1 h1:4roNlIsfEXb7127O3v558H+9jmV70G2FAJPYIZX84lQ= -github.com/matryer/moq v0.2.1/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE= +github.com/matryer/moq v0.3.4 h1:czCFIos9rI2tyOehN9ktc/6bQ76N9J4xQ2n3dk063ac= +github.com/matryer/moq v0.3.4/go.mod h1:wqm9QObyoMuUtH81zFfs3EK6mXEcByy+TjvSROOXJ2U= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -380,7 +380,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E= github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -533,8 +532,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -574,8 +574,9 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -599,6 +600,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -648,8 +651,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg= golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -658,8 +662,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -707,7 +712,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -716,8 +720,9 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/daemon/instance/mock/logstash_instance_mock.go b/internal/daemon/instance/mock/logstash_instance_mock.go index 63c4ca4..5e5e0f9 100644 --- a/internal/daemon/instance/mock/logstash_instance_mock.go +++ b/internal/daemon/instance/mock/logstash_instance_mock.go @@ -16,22 +16,22 @@ var _ controller.Instance = &InstanceMock{} // InstanceMock is a mock implementation of controller.Instance. // -// func TestSomethingThatUsesInstance(t *testing.T) { +// func TestSomethingThatUsesInstance(t *testing.T) { // -// // make and configure a mocked controller.Instance -// mockedInstance := &InstanceMock{ -// ConfigReloadFunc: func() error { -// panic("mock out the ConfigReload method") -// }, -// StartFunc: func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { -// panic("mock out the Start method") -// }, -// } +// // make and configure a mocked controller.Instance +// mockedInstance := &InstanceMock{ +// ConfigReloadFunc: func() error { +// panic("mock out the ConfigReload method") +// }, +// StartFunc: func(ctx context.Context, controllerMoqParam *controller.Controller, workdir string) error { +// panic("mock out the Start method") +// }, +// } // -// // use mockedInstance in code that requires controller.Instance -// // and then make assertions. +// // use mockedInstance in code that requires controller.Instance +// // and then make assertions. // -// } +// } type InstanceMock struct { // ConfigReloadFunc mocks the ConfigReload method. ConfigReloadFunc func() error @@ -73,7 +73,8 @@ func (mock *InstanceMock) ConfigReload() error { // ConfigReloadCalls gets all the calls that were made to ConfigReload. // Check the length with: -// len(mockedInstance.ConfigReloadCalls()) +// +// len(mockedInstance.ConfigReloadCalls()) func (mock *InstanceMock) ConfigReloadCalls() []struct { } { var calls []struct { @@ -106,7 +107,8 @@ func (mock *InstanceMock) Start(ctx context.Context, controllerMoqParam *control // StartCalls gets all the calls that were made to Start. // Check the length with: -// len(mockedInstance.StartCalls()) +// +// len(mockedInstance.StartCalls()) func (mock *InstanceMock) StartCalls() []struct { Ctx context.Context ControllerMoqParam *controller.Controller diff --git a/internal/daemon/session/logstash_controller_mock_test.go b/internal/daemon/session/logstash_controller_mock_test.go index 2466fbd..3a99a7f 100644 --- a/internal/daemon/session/logstash_controller_mock_test.go +++ b/internal/daemon/session/logstash_controller_mock_test.go @@ -16,34 +16,34 @@ var _ session.LogstashController = &LogstashControllerMock{} // LogstashControllerMock is a mock implementation of session.LogstashController. // -// func TestSomethingThatUsesLogstashController(t *testing.T) { +// func TestSomethingThatUsesLogstashController(t *testing.T) { // -// // make and configure a mocked session.LogstashController -// mockedLogstashController := &LogstashControllerMock{ -// ExecuteTestFunc: func(pipelines pipeline.Pipelines, expectedEvents int) error { -// panic("mock out the ExecuteTest method") -// }, -// GetResultsFunc: func() ([]string, error) { -// panic("mock out the GetResults method") -// }, -// IsHealthyFunc: func() bool { -// panic("mock out the IsHealthy method") -// }, -// KillFunc: func() { -// panic("mock out the Kill method") -// }, -// SetupTestFunc: func(pipelines pipeline.Pipelines) error { -// panic("mock out the SetupTest method") -// }, -// TeardownFunc: func() error { -// panic("mock out the Teardown method") -// }, -// } +// // make and configure a mocked session.LogstashController +// mockedLogstashController := &LogstashControllerMock{ +// ExecuteTestFunc: func(pipelines pipeline.Pipelines, expectedEvents int) error { +// panic("mock out the ExecuteTest method") +// }, +// GetResultsFunc: func() ([]string, error) { +// panic("mock out the GetResults method") +// }, +// IsHealthyFunc: func() bool { +// panic("mock out the IsHealthy method") +// }, +// KillFunc: func() { +// panic("mock out the Kill method") +// }, +// SetupTestFunc: func(pipelines pipeline.Pipelines) error { +// panic("mock out the SetupTest method") +// }, +// TeardownFunc: func() error { +// panic("mock out the Teardown method") +// }, +// } // -// // use mockedLogstashController in code that requires session.LogstashController -// // and then make assertions. +// // use mockedLogstashController in code that requires session.LogstashController +// // and then make assertions. // -// } +// } type LogstashControllerMock struct { // ExecuteTestFunc mocks the ExecuteTest method. ExecuteTestFunc func(pipelines pipeline.Pipelines, expectedEvents int) error @@ -118,7 +118,8 @@ func (mock *LogstashControllerMock) ExecuteTest(pipelines pipeline.Pipelines, ex // ExecuteTestCalls gets all the calls that were made to ExecuteTest. // Check the length with: -// len(mockedLogstashController.ExecuteTestCalls()) +// +// len(mockedLogstashController.ExecuteTestCalls()) func (mock *LogstashControllerMock) ExecuteTestCalls() []struct { Pipelines pipeline.Pipelines ExpectedEvents int @@ -148,7 +149,8 @@ func (mock *LogstashControllerMock) GetResults() ([]string, error) { // GetResultsCalls gets all the calls that were made to GetResults. // Check the length with: -// len(mockedLogstashController.GetResultsCalls()) +// +// len(mockedLogstashController.GetResultsCalls()) func (mock *LogstashControllerMock) GetResultsCalls() []struct { } { var calls []struct { @@ -174,7 +176,8 @@ func (mock *LogstashControllerMock) IsHealthy() bool { // IsHealthyCalls gets all the calls that were made to IsHealthy. // Check the length with: -// len(mockedLogstashController.IsHealthyCalls()) +// +// len(mockedLogstashController.IsHealthyCalls()) func (mock *LogstashControllerMock) IsHealthyCalls() []struct { } { var calls []struct { @@ -200,7 +203,8 @@ func (mock *LogstashControllerMock) Kill() { // KillCalls gets all the calls that were made to Kill. // Check the length with: -// len(mockedLogstashController.KillCalls()) +// +// len(mockedLogstashController.KillCalls()) func (mock *LogstashControllerMock) KillCalls() []struct { } { var calls []struct { @@ -229,7 +233,8 @@ func (mock *LogstashControllerMock) SetupTest(pipelines pipeline.Pipelines) erro // SetupTestCalls gets all the calls that were made to SetupTest. // Check the length with: -// len(mockedLogstashController.SetupTestCalls()) +// +// len(mockedLogstashController.SetupTestCalls()) func (mock *LogstashControllerMock) SetupTestCalls() []struct { Pipelines pipeline.Pipelines } { @@ -257,7 +262,8 @@ func (mock *LogstashControllerMock) Teardown() error { // TeardownCalls gets all the calls that were made to Teardown. // Check the length with: -// len(mockedLogstashController.TeardownCalls()) +// +// len(mockedLogstashController.TeardownCalls()) func (mock *LogstashControllerMock) TeardownCalls() []struct { } { var calls []struct { diff --git a/internal/daemon/session/pool_mock_test.go b/internal/daemon/session/pool_mock_test.go index 07881ea..2079fc3 100644 --- a/internal/daemon/session/pool_mock_test.go +++ b/internal/daemon/session/pool_mock_test.go @@ -16,22 +16,22 @@ var _ session.Pool = &PoolMock{} // PoolMock is a mock implementation of session.Pool. // -// func TestSomethingThatUsesPool(t *testing.T) { +// func TestSomethingThatUsesPool(t *testing.T) { // -// // make and configure a mocked session.Pool -// mockedPool := &PoolMock{ -// GetFunc: func() (pool.LogstashController, error) { -// panic("mock out the Get method") -// }, -// ReturnFunc: func(instance pool.LogstashController, clean bool) { -// panic("mock out the Return method") -// }, -// } +// // make and configure a mocked session.Pool +// mockedPool := &PoolMock{ +// GetFunc: func() (pool.LogstashController, error) { +// panic("mock out the Get method") +// }, +// ReturnFunc: func(instance pool.LogstashController, clean bool) { +// panic("mock out the Return method") +// }, +// } // -// // use mockedPool in code that requires session.Pool -// // and then make assertions. +// // use mockedPool in code that requires session.Pool +// // and then make assertions. // -// } +// } type PoolMock struct { // GetFunc mocks the Get method. GetFunc func() (pool.LogstashController, error) @@ -71,7 +71,8 @@ func (mock *PoolMock) Get() (pool.LogstashController, error) { // GetCalls gets all the calls that were made to Get. // Check the length with: -// len(mockedPool.GetCalls()) +// +// len(mockedPool.GetCalls()) func (mock *PoolMock) GetCalls() []struct { } { var calls []struct { @@ -102,7 +103,8 @@ func (mock *PoolMock) Return(instance pool.LogstashController, clean bool) { // ReturnCalls gets all the calls that were made to Return. // Check the length with: -// len(mockedPool.ReturnCalls()) +// +// len(mockedPool.ReturnCalls()) func (mock *PoolMock) ReturnCalls() []struct { Instance pool.LogstashController Clean bool diff --git a/internal/logging/logger_mock.go b/internal/logging/logger_mock.go index aeafa19..fe87574 100644 --- a/internal/logging/logger_mock.go +++ b/internal/logging/logger_mock.go @@ -13,46 +13,46 @@ var _ Logger = &LoggerMock{} // LoggerMock is a mock implementation of Logger. // -// func TestSomethingThatUsesLogger(t *testing.T) { +// func TestSomethingThatUsesLogger(t *testing.T) { // -// // make and configure a mocked Logger -// mockedLogger := &LoggerMock{ -// DebugFunc: func(args ...interface{}) { -// panic("mock out the Debug method") -// }, -// DebugfFunc: func(format string, args ...interface{}) { -// panic("mock out the Debugf method") -// }, -// ErrorFunc: func(args ...interface{}) { -// panic("mock out the Error method") -// }, -// ErrorfFunc: func(format string, args ...interface{}) { -// panic("mock out the Errorf method") -// }, -// FatalFunc: func(args ...interface{}) { -// panic("mock out the Fatal method") -// }, -// FatalfFunc: func(format string, args ...interface{}) { -// panic("mock out the Fatalf method") -// }, -// InfoFunc: func(args ...interface{}) { -// panic("mock out the Info method") -// }, -// InfofFunc: func(format string, args ...interface{}) { -// panic("mock out the Infof method") -// }, -// WarningFunc: func(args ...interface{}) { -// panic("mock out the Warning method") -// }, -// WarningfFunc: func(format string, args ...interface{}) { -// panic("mock out the Warningf method") -// }, -// } +// // make and configure a mocked Logger +// mockedLogger := &LoggerMock{ +// DebugFunc: func(args ...interface{}) { +// panic("mock out the Debug method") +// }, +// DebugfFunc: func(format string, args ...interface{}) { +// panic("mock out the Debugf method") +// }, +// ErrorFunc: func(args ...interface{}) { +// panic("mock out the Error method") +// }, +// ErrorfFunc: func(format string, args ...interface{}) { +// panic("mock out the Errorf method") +// }, +// FatalFunc: func(args ...interface{}) { +// panic("mock out the Fatal method") +// }, +// FatalfFunc: func(format string, args ...interface{}) { +// panic("mock out the Fatalf method") +// }, +// InfoFunc: func(args ...interface{}) { +// panic("mock out the Info method") +// }, +// InfofFunc: func(format string, args ...interface{}) { +// panic("mock out the Infof method") +// }, +// WarningFunc: func(args ...interface{}) { +// panic("mock out the Warning method") +// }, +// WarningfFunc: func(format string, args ...interface{}) { +// panic("mock out the Warningf method") +// }, +// } // -// // use mockedLogger in code that requires Logger -// // and then make assertions. +// // use mockedLogger in code that requires Logger +// // and then make assertions. // -// } +// } type LoggerMock struct { // DebugFunc mocks the Debug method. DebugFunc func(args ...interface{}) @@ -177,7 +177,8 @@ func (mock *LoggerMock) Debug(args ...interface{}) { // DebugCalls gets all the calls that were made to Debug. // Check the length with: -// len(mockedLogger.DebugCalls()) +// +// len(mockedLogger.DebugCalls()) func (mock *LoggerMock) DebugCalls() []struct { Args []interface{} } { @@ -210,7 +211,8 @@ func (mock *LoggerMock) Debugf(format string, args ...interface{}) { // DebugfCalls gets all the calls that were made to Debugf. // Check the length with: -// len(mockedLogger.DebugfCalls()) +// +// len(mockedLogger.DebugfCalls()) func (mock *LoggerMock) DebugfCalls() []struct { Format string Args []interface{} @@ -243,7 +245,8 @@ func (mock *LoggerMock) Error(args ...interface{}) { // ErrorCalls gets all the calls that were made to Error. // Check the length with: -// len(mockedLogger.ErrorCalls()) +// +// len(mockedLogger.ErrorCalls()) func (mock *LoggerMock) ErrorCalls() []struct { Args []interface{} } { @@ -276,7 +279,8 @@ func (mock *LoggerMock) Errorf(format string, args ...interface{}) { // ErrorfCalls gets all the calls that were made to Errorf. // Check the length with: -// len(mockedLogger.ErrorfCalls()) +// +// len(mockedLogger.ErrorfCalls()) func (mock *LoggerMock) ErrorfCalls() []struct { Format string Args []interface{} @@ -309,7 +313,8 @@ func (mock *LoggerMock) Fatal(args ...interface{}) { // FatalCalls gets all the calls that were made to Fatal. // Check the length with: -// len(mockedLogger.FatalCalls()) +// +// len(mockedLogger.FatalCalls()) func (mock *LoggerMock) FatalCalls() []struct { Args []interface{} } { @@ -342,7 +347,8 @@ func (mock *LoggerMock) Fatalf(format string, args ...interface{}) { // FatalfCalls gets all the calls that were made to Fatalf. // Check the length with: -// len(mockedLogger.FatalfCalls()) +// +// len(mockedLogger.FatalfCalls()) func (mock *LoggerMock) FatalfCalls() []struct { Format string Args []interface{} @@ -375,7 +381,8 @@ func (mock *LoggerMock) Info(args ...interface{}) { // InfoCalls gets all the calls that were made to Info. // Check the length with: -// len(mockedLogger.InfoCalls()) +// +// len(mockedLogger.InfoCalls()) func (mock *LoggerMock) InfoCalls() []struct { Args []interface{} } { @@ -408,7 +415,8 @@ func (mock *LoggerMock) Infof(format string, args ...interface{}) { // InfofCalls gets all the calls that were made to Infof. // Check the length with: -// len(mockedLogger.InfofCalls()) +// +// len(mockedLogger.InfofCalls()) func (mock *LoggerMock) InfofCalls() []struct { Format string Args []interface{} @@ -441,7 +449,8 @@ func (mock *LoggerMock) Warning(args ...interface{}) { // WarningCalls gets all the calls that were made to Warning. // Check the length with: -// len(mockedLogger.WarningCalls()) +// +// len(mockedLogger.WarningCalls()) func (mock *LoggerMock) WarningCalls() []struct { Args []interface{} } { @@ -474,7 +483,8 @@ func (mock *LoggerMock) Warningf(format string, args ...interface{}) { // WarningfCalls gets all the calls that were made to Warningf. // Check the length with: -// len(mockedLogger.WarningfCalls()) +// +// len(mockedLogger.WarningfCalls()) func (mock *LoggerMock) WarningfCalls() []struct { Format string Args []interface{} diff --git a/pprof.go b/pprof.go index c4c3de7..0d1e7f3 100644 --- a/pprof.go +++ b/pprof.go @@ -1,4 +1,4 @@ -// +build pprof +//go:build pprof package main diff --git a/tools.go b/tools.go index b144964..270abb7 100644 --- a/tools.go +++ b/tools.go @@ -1,4 +1,4 @@ -// +build tools +//go:build tools // This file exists only to get various parts of the toolchain // included in go.mod. From e5e97d7e9dc190d6fa858bb054462b4f2be3fb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 6 Jun 2024 23:18:37 +0200 Subject: [PATCH 139/143] Use testing.T.TempDir whenever possible --- internal/app/standalone/standalone_test.go | 9 ++---- internal/logstash/copyfile_test.go | 36 +++++++-------------- internal/logstash/invocation_test.go | 15 ++++----- internal/logstash/pipelineconfigdir_test.go | 33 ++++--------------- internal/testcase/discover_test.go | 19 +++-------- internal/testcase/testcase_test.go | 22 +++---------- 6 files changed, 34 insertions(+), 100 deletions(-) diff --git a/internal/app/standalone/standalone_test.go b/internal/app/standalone/standalone_test.go index 446a47c..fd5c89d 100644 --- a/internal/app/standalone/standalone_test.go +++ b/internal/app/standalone/standalone_test.go @@ -3,7 +3,6 @@ package standalone import ( - "io/ioutil" "os" "path/filepath" "regexp" @@ -104,14 +103,10 @@ func TestFindExecutable(t *testing.T) { }, } for i, c := range cases { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() for _, fwp := range c.files { - if err = fwp.Create(tempdir); err != nil { + if err := fwp.Create(tempdir); err != nil { t.Fatalf("Test %d: Unexpected error when creating test file: %s", i, err) } } diff --git a/internal/logstash/copyfile_test.go b/internal/logstash/copyfile_test.go index 153d0d2..7ea3828 100644 --- a/internal/logstash/copyfile_test.go +++ b/internal/logstash/copyfile_test.go @@ -86,18 +86,16 @@ func TestAllFilesExist(t *testing.T) { }, } for i, c := range cases { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() + // The test fails if it can't clean up the directory. + defer os.Chmod(tempdir, 0700) for _, fwm := range c.files { - if err = fwm.Create(tempdir); err != nil { + if err := fwm.Create(tempdir); err != nil { t.Fatalf("Test %d: Unexpected error when creating test file: %s", i, err) } } - if err = os.Chmod(tempdir, c.tempdirMode); err != nil { + if err := os.Chmod(tempdir, c.tempdirMode); err != nil { t.Fatalf("Test %d: Unexpected error when chmod'ing temp dir: %s", i, err) } @@ -131,13 +129,9 @@ func TestCopyAllFiles(t *testing.T) { }, } for i, c := range cases { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() destdir := filepath.Join(tempdir, "dest") - if err = os.Mkdir(destdir, 0755); err != nil { + if err := os.Mkdir(destdir, 0755); err != nil { t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) } @@ -145,11 +139,11 @@ func TestCopyAllFiles(t *testing.T) { for diridx, files := range c.files { thisdir := filepath.Join(tempdir, strconv.Itoa(diridx)) sourcedirs[diridx] = thisdir - if err = os.Mkdir(thisdir, 0755); err != nil { + if err := os.Mkdir(thisdir, 0755); err != nil { t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) } for _, fwm := range files { - if err = fwm.Create(thisdir); err != nil { + if err := fwm.Create(thisdir); err != nil { t.Fatalf("Test %d: Unexpected error when creating test file: %s", i, err) } } @@ -188,16 +182,8 @@ func TestCopyFile(t *testing.T) { defer os.Remove(source.Name()) source.Write([]byte(testData)) - // Create the destination directory. - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Unexpected error when creating temp dir: %s", err) - } - defer os.RemoveAll(tempdir) - destPath := filepath.Join(tempdir, "arbitrary-filename") - - err = copyFile(source.Name(), destPath) - if err != nil { + destPath := filepath.Join(t.TempDir(), "arbitrary-filename") + if err = copyFile(source.Name(), destPath); err != nil { t.Fatalf("Unexpected error: %s", err) } buf, err := ioutil.ReadFile(destPath) diff --git a/internal/logstash/invocation_test.go b/internal/logstash/invocation_test.go index b55c98f..85916ca 100644 --- a/internal/logstash/invocation_test.go +++ b/internal/logstash/invocation_test.go @@ -15,7 +15,7 @@ import ( ) func TestArgs(t *testing.T) { - tinv, err := createTestInvocation(*semver.MustParse("6.0.0")) + tinv, err := createTestInvocation(t, *semver.MustParse("6.0.0")) if err != nil { t.Fatalf("%s", err) } @@ -151,7 +151,7 @@ func TestNewInvocation(t *testing.T) { }, } for i, c := range cases { - tinv, err := createTestInvocation(*semver.MustParse(c.version)) + tinv, err := createTestInvocation(t, *semver.MustParse(c.version)) if err != nil { t.Errorf("Test %d: Error unexpectedly returned: %s", i, err) continue @@ -176,11 +176,8 @@ type testInvocation struct { configContents string } -func createTestInvocation(version semver.Version) (*testInvocation, error) { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - return nil, fmt.Errorf("Unexpected error when creating temp dir: %s", err) - } +func createTestInvocation(t *testing.T, version semver.Version) (*testInvocation, error) { + tempdir := t.TempDir() files := []testhelpers.FileWithMode{ {"bin", os.ModeDir | 0755, ""}, @@ -190,14 +187,14 @@ func createTestInvocation(version semver.Version) (*testInvocation, error) { {"config/log4j2.properties", 0644, ""}, } for _, fwm := range files { - if err = fwm.Create(tempdir); err != nil { + if err := fwm.Create(tempdir); err != nil { return nil, fmt.Errorf("Unexpected error when creating test file: %s", err) } } configFile := filepath.Join(tempdir, "configfile.conf") configContents := "" - if err = ioutil.WriteFile(configFile, []byte(configContents), 0600); err != nil { + if err := ioutil.WriteFile(configFile, []byte(configContents), 0600); err != nil { return nil, fmt.Errorf("Unexpected error when creating dummy configuration file: %s", err) } logstashPath := filepath.Join(tempdir, "bin/logstash") diff --git a/internal/logstash/pipelineconfigdir_test.go b/internal/logstash/pipelineconfigdir_test.go index ea72222..07b7bb3 100644 --- a/internal/logstash/pipelineconfigdir_test.go +++ b/internal/logstash/pipelineconfigdir_test.go @@ -101,14 +101,10 @@ func TestFlattenFilenames(t *testing.T) { }, } for i, c := range cases { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() for _, fwm := range c.existingFiles { - if err = fwm.Create(tempdir); err != nil { + if err := fwm.Create(tempdir); err != nil { t.Fatalf("Test %d: Unexpected error when creating test file: %s", i, err) } } @@ -154,27 +150,16 @@ func TestGetPipelineConfigDir(t *testing.T) { // Create the files listed in the test case in a new // temporary directory. The content of each file is // the base of its own name. - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(tempdir) - - resultDir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(resultDir) + tempdir := t.TempDir() + resultDir := t.TempDir() var configFiles []string for _, f := range c.files { - err = os.MkdirAll(filepath.Join(tempdir, filepath.Dir(f)), 0700) - if err != nil { + if err := os.MkdirAll(filepath.Join(tempdir, filepath.Dir(f)), 0700); err != nil { t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) } - err = ioutil.WriteFile(filepath.Join(tempdir, f), []byte(createLogstashConfigWithString(filepath.Base(f))), 0600) - if err != nil { + if err := ioutil.WriteFile(filepath.Join(tempdir, f), []byte(createLogstashConfigWithString(filepath.Base(f))), 0600); err != nil { t.Fatalf("Test %d: Unexpected error when writing to temp file: %s", i, err) } configFiles = append(configFiles, filepath.Join(tempdir, f)) @@ -251,11 +236,7 @@ func TestGetFilesInDir(t *testing.T) { }, } for i, c := range cases { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() for _, filename := range c.files { f, err := os.Create(filepath.Join(tempdir, filename)) diff --git a/internal/testcase/discover_test.go b/internal/testcase/discover_test.go index a669481..9dcc6a3 100644 --- a/internal/testcase/discover_test.go +++ b/internal/testcase/discover_test.go @@ -46,19 +46,14 @@ func TestDiscoverTests_Directory(t *testing.T) { }, } for cnum, c := range cases { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Errorf(err.Error()) - break - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() for _, f := range c.files { if strings.Contains(f, "/") { t.Errorf("This test doesn't support subdirectories: %s", f) break } - if err = ioutil.WriteFile(filepath.Join(tempdir, f), []byte(`{"type": "test"}`), 0600); err != nil { + if err := ioutil.WriteFile(filepath.Join(tempdir, f), []byte(`{"type": "test"}`), 0600); err != nil { t.Fatalf(err.Error()) } } @@ -111,18 +106,12 @@ func TestDiscoverTests_File(t *testing.T) { "filename.yaml", } for _, filename := range filenames { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatal(err.Error()) - } - defer os.RemoveAll(tempdir) - - inputpath := filepath.Join(tempdir, filename) + inputpath := filepath.Join(t.TempDir(), filename) // As it happens a valid JSON file is also a valid YAML file so // the file we create can have the same contents regardless of // the file format. - if err = ioutil.WriteFile(inputpath, []byte(`{"type": "test"}`), 0600); err != nil { + if err := ioutil.WriteFile(inputpath, []byte(`{"type": "test"}`), 0600); err != nil { t.Fatal(err.Error()) } diff --git a/internal/testcase/testcase_test.go b/internal/testcase/testcase_test.go index 5763eeb..bfb4853 100644 --- a/internal/testcase/testcase_test.go +++ b/internal/testcase/testcase_test.go @@ -114,11 +114,7 @@ func TestNewFromFile(t *testing.T) { "filename.yaml", } for _, filename := range filenames { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf(err.Error()) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() olddir, err := os.Getwd() if err != nil { t.Fatalf(err.Error()) @@ -151,11 +147,7 @@ func TestNewFromFile(t *testing.T) { func TestCompare(t *testing.T) { // Create an empty tempdir so that we can construct a path to // a diff binary that's guaranteed to not exist. - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf(err.Error()) - } - defer os.RemoveAll(tempdir) + tempdir := t.TempDir() liveObserver := observer.NewProperty(nil) @@ -415,16 +407,10 @@ func TestCompare(t *testing.T) { } func TestMarshalToFile(t *testing.T) { - tempdir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatalf(err.Error()) - } - defer os.RemoveAll(tempdir) - // Implicitly test that subdirectories are created as needed. - fullpath := filepath.Join(tempdir, "a", "b", "c.json") + fullpath := filepath.Join(t.TempDir(), "a", "b", "c.json") - if err = marshalToFile(logstash.Event{}, fullpath); err != nil { + if err := marshalToFile(logstash.Event{}, fullpath); err != nil { t.Fatalf(err.Error()) } From 71d3cb151e2cec4db573dad480af56341083f614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20B=C3=A4ck?= Date: Thu, 6 Jun 2024 22:04:20 +0200 Subject: [PATCH 140/143] Replace all references to the deprecated io/ioutil package --- .golangci.yml | 8 -------- internal/app/daemon/daemon.go | 6 +++--- internal/app/daemon/run/run.go | 6 ++---- internal/daemon/controller/controller.go | 4 +--- internal/daemon/file/file.go | 3 +-- internal/daemon/logstashconfig/file.go | 3 +-- internal/daemon/pipeline/pipeline.go | 7 +++---- internal/daemon/pluginmock/pluginmock.go | 4 ++-- internal/daemon/session/session.go | 5 ++--- internal/daemon/template/template.go | 3 +-- internal/logstash/copyfile_test.go | 7 +++---- internal/logstash/deleted_tempfile.go | 10 ++++------ internal/logstash/invocation.go | 7 +++---- internal/logstash/invocation_test.go | 5 ++--- internal/logstash/main_test.go | 4 ++-- internal/logstash/parallel_process.go | 7 +++---- internal/logstash/parallel_process_test.go | 3 +-- internal/logstash/pipelineconfigdir.go | 7 +++---- internal/logstash/pipelineconfigdir_test.go | 11 +++++------ internal/logstash/process.go | 5 ++--- internal/testcase/discover.go | 3 +-- internal/testcase/discover_test.go | 5 ++--- internal/testcase/testcase.go | 7 +++---- internal/testcase/testcase_test.go | 5 ++--- 24 files changed, 52 insertions(+), 83 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f2bb64f..2c4a6e7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,11 +27,3 @@ linters: linters-settings: misspell: locale: US - -issues: - exclude-rules: - # We'll replace references to io/ioutil later. Until then we don't - # want to hear about it. - - linters: - - staticcheck - text: 'SA1019: "io/ioutil" has been deprecated' diff --git a/internal/app/daemon/daemon.go b/internal/app/daemon/daemon.go index 193b58a..5d2cbec 100644 --- a/internal/app/daemon/daemon.go +++ b/internal/app/daemon/daemon.go @@ -5,7 +5,7 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" + "io" "net" "os" "os/signal" @@ -91,7 +91,7 @@ func (d *Daemon) Run(ctx context.Context) error { ctx, shutdown := context.WithCancel(ctxKill) defer shutdown() - tempdir, err := ioutil.TempDir("", "lfv-") + tempdir, err := os.MkdirTemp("", "lfv-") if err != nil { return err } @@ -330,7 +330,7 @@ func (d *Daemon) extractZip(in []byte) (pipeline.Pipelines, []logstashconfig.Fil } }() - body, err := ioutil.ReadAll(rc) + body, err := io.ReadAll(rc) if err != nil { return err } diff --git a/internal/app/daemon/run/run.go b/internal/app/daemon/run/run.go index 9de052f..f4f6577 100644 --- a/internal/app/daemon/run/run.go +++ b/internal/app/daemon/run/run.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "io/ioutil" "net" "os" "path/filepath" @@ -223,7 +222,7 @@ func (s Test) createImplicitPipeline() (string, error) { return "", errors.Wrap(err, "failed to read logstash config") } - pipelineBaseDir, err := ioutil.TempDir("", "lfv-pipeline-*") + pipelineBaseDir, err := os.MkdirTemp("", "lfv-pipeline-*") if err != nil { return "", errors.Wrap(err, "failed to create temporary directory for implicit pipeline") } @@ -246,8 +245,7 @@ func (s Test) createImplicitPipeline() (string, error) { } pipelineFile := filepath.Join(pipelineBaseDir, "pipelines.yml") - err = ioutil.WriteFile(pipelineFile, body, 0600) - if err != nil { + if err := os.WriteFile(pipelineFile, body, 0600); err != nil { return "", errors.Wrap(err, "failed to write implicit pipelines.yml file") } diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index aa82009..8788068 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -2,7 +2,6 @@ package controller import ( "context" - "io/ioutil" "os" "path/filepath" "sync" @@ -192,8 +191,7 @@ func (c *Controller) writePipelines(pipelines ...pipeline.Pipeline) error { return err } - err = ioutil.WriteFile(filepath.Join(c.workDir, "pipelines.yml"), pipelinesBody, 0600) - if err != nil { + if err := os.WriteFile(filepath.Join(c.workDir, "pipelines.yml"), pipelinesBody, 0600); err != nil { return err } diff --git a/internal/daemon/file/file.go b/internal/daemon/file/file.go index 4751729..69043c5 100644 --- a/internal/daemon/file/file.go +++ b/internal/daemon/file/file.go @@ -2,7 +2,6 @@ package file import ( "bytes" - "io/ioutil" "os" ) @@ -12,7 +11,7 @@ func Exists(filename string) bool { } func Contains(filename string, needle string) bool { - body, err := ioutil.ReadFile(filename) + body, err := os.ReadFile(filename) if err != nil { return false } diff --git a/internal/daemon/logstashconfig/file.go b/internal/daemon/logstashconfig/file.go index 9bb73c9..444e908 100644 --- a/internal/daemon/logstashconfig/file.go +++ b/internal/daemon/logstashconfig/file.go @@ -2,7 +2,6 @@ package logstashconfig import ( "fmt" - "io/ioutil" "os" "path" "path/filepath" @@ -30,7 +29,7 @@ func (f File) Save(targetDir string) error { return err } - return ioutil.WriteFile(filepath.Join(targetDir, f.Name), f.Body, 0600) + return os.WriteFile(filepath.Join(targetDir, f.Name), f.Body, 0600) } func (f *File) parse() error { diff --git a/internal/daemon/pipeline/pipeline.go b/internal/daemon/pipeline/pipeline.go index dbcb074..c610285 100644 --- a/internal/daemon/pipeline/pipeline.go +++ b/internal/daemon/pipeline/pipeline.go @@ -3,7 +3,6 @@ package pipeline import ( "archive/zip" "bytes" - "io/ioutil" "os" "path" "path/filepath" @@ -36,7 +35,7 @@ type Pipeline struct { } func New(file, basePath string) (Archive, error) { - b, err := ioutil.ReadFile(file) + b, err := os.ReadFile(file) if err != nil { return Archive{}, err } @@ -82,7 +81,7 @@ func (a Archive) ZipWithPreprocessor(addMissingID bool, preprocess func([]byte) if err != nil { return nil, nil, err } - body, err := ioutil.ReadFile(a.File) + body, err := os.ReadFile(a.File) if err != nil { return nil, nil, err } @@ -129,7 +128,7 @@ func (a Archive) ZipWithPreprocessor(addMissingID bool, preprocess func([]byte) return nil, nil, err } - body, err := ioutil.ReadFile(file) + body, err := os.ReadFile(file) if err != nil { return nil, nil, err } diff --git a/internal/daemon/pluginmock/pluginmock.go b/internal/daemon/pluginmock/pluginmock.go index 756e088..61f626d 100644 --- a/internal/daemon/pluginmock/pluginmock.go +++ b/internal/daemon/pluginmock/pluginmock.go @@ -11,7 +11,7 @@ package pluginmock import ( "fmt" - "io/ioutil" + "os" config "github.com/breml/logstash-config" "github.com/breml/logstash-config/ast" @@ -42,7 +42,7 @@ func FromFile(filename string) (Mocks, error) { mocks := []Mock{} - body, err := ioutil.ReadFile(filename) + body, err := os.ReadFile(filename) if err != nil { return Mocks{}, err } diff --git a/internal/daemon/session/session.go b/internal/daemon/session/session.go index c46cbbd..1ccf906 100644 --- a/internal/daemon/session/session.go +++ b/internal/daemon/session/session.go @@ -3,7 +3,6 @@ package session import ( "encoding/json" "fmt" - "io/ioutil" "os" "path/filepath" "strconv" @@ -217,8 +216,8 @@ func prepareFields(fieldsFilename string, inEvents []map[string]interface{}) err if err != nil { return err } - err = ioutil.WriteFile(fieldsFilename, bfields, 0600) - if err != nil { + + if err := os.WriteFile(fieldsFilename, bfields, 0600); err != nil { return err } diff --git a/internal/daemon/template/template.go b/internal/daemon/template/template.go index d6ab664..33b34e5 100644 --- a/internal/daemon/template/template.go +++ b/internal/daemon/template/template.go @@ -2,7 +2,6 @@ package template import ( "bytes" - "io/ioutil" "os" "text/template" ) @@ -17,7 +16,7 @@ func ToFile(filename string, templateContent string, data interface{}, perm os.F if err != nil { return err } - err = ioutil.WriteFile(filename, b.Bytes(), perm) + err = os.WriteFile(filename, b.Bytes(), perm) if err != nil { return err } diff --git a/internal/logstash/copyfile_test.go b/internal/logstash/copyfile_test.go index 7ea3828..73e1765 100644 --- a/internal/logstash/copyfile_test.go +++ b/internal/logstash/copyfile_test.go @@ -3,7 +3,6 @@ package logstash import ( - "io/ioutil" "os" "path/filepath" "strconv" @@ -160,7 +159,7 @@ func TestCopyAllFiles(t *testing.T) { // Are all files copied and do they contain the // expected contents (the filepath's basename)? for _, filename := range c.wanted { - buf, err := ioutil.ReadFile(filepath.Join(destdir, filename)) + buf, err := os.ReadFile(filepath.Join(destdir, filename)) if err != nil { t.Errorf("Test %d: Got error reading copied file: %s", i, err) } @@ -175,7 +174,7 @@ func TestCopyFile(t *testing.T) { testData := "random string\n" // Create the source file. - source, err := ioutil.TempFile("", "") + source, err := os.CreateTemp("", "") if err != nil { t.Fatalf("Unexpected error when creating file: %s", err) } @@ -186,7 +185,7 @@ func TestCopyFile(t *testing.T) { if err = copyFile(source.Name(), destPath); err != nil { t.Fatalf("Unexpected error: %s", err) } - buf, err := ioutil.ReadFile(destPath) + buf, err := os.ReadFile(destPath) if err != nil { t.Fatalf("Unexpected error reading destination file: %s", err) } diff --git a/internal/logstash/deleted_tempfile.go b/internal/logstash/deleted_tempfile.go index e69b8fc..94a634b 100644 --- a/internal/logstash/deleted_tempfile.go +++ b/internal/logstash/deleted_tempfile.go @@ -3,7 +3,6 @@ package logstash import ( - "io/ioutil" "os" ) @@ -13,12 +12,11 @@ type deletedTempFile struct { *os.File } -// NewDeletedTempFile creates a new temporary file that will be -// deleted upon closing. It uses the TempFile function from io/ioutil -// for the creation of the file and the dir and prefix parameters are -// passed straight through. +// NewDeletedTempFile creates a new temporary file that will be deleted +// upon closing. It uses os.CreateTemp for the creation of the file and +// the dir and prefix parameters are passed straight through. func newDeletedTempFile(dir, prefix string) (*deletedTempFile, error) { - f, err := ioutil.TempFile(dir, prefix) + f, err := os.CreateTemp(dir, prefix) if err != nil { return nil, err } diff --git a/internal/logstash/invocation.go b/internal/logstash/invocation.go index dcfdef4..cbe3f89 100644 --- a/internal/logstash/invocation.go +++ b/internal/logstash/invocation.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" @@ -34,7 +33,7 @@ func NewInvocation(logstashPath string, logstashArgs []string, logstashVersion * return nil, errors.New("must provide non-empty list of configuration file or directory names") } - tempDir, err := ioutil.TempDir("", "") + tempDir, err := os.MkdirTemp("", "") if err != nil { return nil, err } @@ -66,7 +65,7 @@ func NewInvocation(logstashPath string, logstashArgs []string, logstashVersion * // -f options so the invocation-specific input and output // configurations need to go in a file in the pipeline // directory. Generate a unique file for that purpose. - ioConfigFile, err := ioutil.TempFile(pipelineDir, "ioconfig.*.conf") + ioConfigFile, err := os.CreateTemp(pipelineDir, "ioconfig.*.conf") if err != nil { _ = logFile.Close() _ = os.RemoveAll(tempDir) @@ -110,7 +109,7 @@ func NewInvocation(logstashPath string, logstashArgs []string, logstashVersion * // put there. The various path settings that we need // to provide can just as well be passed as command // arguments. - err := ioutil.WriteFile(filepath.Join(configDir, "logstash.yml"), []byte{}, 0600) + err := os.WriteFile(filepath.Join(configDir, "logstash.yml"), []byte{}, 0600) if err != nil { _ = logFile.Close() _ = os.RemoveAll(tempDir) diff --git a/internal/logstash/invocation_test.go b/internal/logstash/invocation_test.go index 85916ca..619b838 100644 --- a/internal/logstash/invocation_test.go +++ b/internal/logstash/invocation_test.go @@ -5,7 +5,6 @@ package logstash import ( "errors" "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -48,7 +47,7 @@ func TestArgs(t *testing.T) { var filterOk bool var ioOk bool for _, file := range files { - buf, err := ioutil.ReadFile(filepath.Join(configOption, file)) + buf, err := os.ReadFile(filepath.Join(configOption, file)) if err != nil { t.Errorf("Error reading configuration file: %s", err) continue @@ -194,7 +193,7 @@ func createTestInvocation(t *testing.T, version semver.Version) (*testInvocation configFile := filepath.Join(tempdir, "configfile.conf") configContents := "" - if err := ioutil.WriteFile(configFile, []byte(configContents), 0600); err != nil { + if err := os.WriteFile(configFile, []byte(configContents), 0600); err != nil { return nil, fmt.Errorf("Unexpected error when creating dummy configuration file: %s", err) } logstashPath := filepath.Join(tempdir, "bin/logstash") diff --git a/internal/logstash/main_test.go b/internal/logstash/main_test.go index c69ef0e..65af948 100644 --- a/internal/logstash/main_test.go +++ b/internal/logstash/main_test.go @@ -4,7 +4,7 @@ package logstash import ( "fmt" - "io/ioutil" + "io" "net" "os" "testing" @@ -30,7 +30,7 @@ func logstashMock() { if err != nil { log.Fatalf("Failed to dial %s with error: %s", os.Getenv("TEST_SOCKET"), err) } - b, err := ioutil.ReadAll(conn) + b, err := io.ReadAll(conn) if err != nil { log.Fatalf("Eror while reading from socket: %s", err) } diff --git a/internal/logstash/parallel_process.go b/internal/logstash/parallel_process.go index 7c3d2cc..c80f74e 100644 --- a/internal/logstash/parallel_process.go +++ b/internal/logstash/parallel_process.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "os" "os/exec" @@ -37,7 +36,7 @@ type TestStream struct { // The timeout defines, how long to wait in Write for the receiver to // become available. func NewTestStream(inputCodec string, fields FieldSet, timeout time.Duration) (*TestStream, error) { - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { return nil, err } @@ -244,7 +243,7 @@ func (p *ParallelProcess) Wait() (*ParallelResult, error) { // Save the log output regardless of whether the child process // succeeded or not. - logbuf, logerr := ioutil.ReadAll(p.inv.logFile) + logbuf, logerr := io.ReadAll(p.inv.logFile) if logerr != nil { // Log this weird error condition but don't let it // fail the function. We don't care about the log @@ -252,7 +251,7 @@ func (p *ParallelProcess) Wait() (*ParallelResult, error) { // report that problem anyway. log.Errorf("Error reading the Logstash logfile: %s", logerr) } - outbuf, _ := ioutil.ReadAll(p.stdio) + outbuf, _ := io.ReadAll(p.stdio) result := ParallelResult{ Events: [][]Event{}, diff --git a/internal/logstash/parallel_process_test.go b/internal/logstash/parallel_process_test.go index 7f1d1b7..247390d 100644 --- a/internal/logstash/parallel_process_test.go +++ b/internal/logstash/parallel_process_test.go @@ -5,7 +5,6 @@ package logstash import ( "errors" "fmt" - "io/ioutil" "os" "reflect" "testing" @@ -24,7 +23,7 @@ func TestParallelProcess(t *testing.T) { } defer CleanupTestStreams([]*TestStream{ts}) - file, err := ioutil.TempFile("", "") + file, err := os.CreateTemp("", "") if err != nil { t.Fatalf("Failed to create temporary config file: %s", err) } diff --git a/internal/logstash/pipelineconfigdir.go b/internal/logstash/pipelineconfigdir.go index 4db5646..0be4db4 100644 --- a/internal/logstash/pipelineconfigdir.go +++ b/internal/logstash/pipelineconfigdir.go @@ -4,7 +4,6 @@ package logstash import ( "fmt" - "io/ioutil" "os" "path/filepath" "sort" @@ -82,12 +81,12 @@ func getPipelineConfigDir(dir string, configs []string) error { // is true the returned path will include the directory name. func getFilesInDir(dir string, includeDir bool) ([]string, error) { filenames := make([]string, 0) - files, err := ioutil.ReadDir(dir) + files, err := os.ReadDir(dir) if err != nil { return nil, err } for _, f := range files { - if !f.Mode().IsDir() { + if !f.IsDir() { if includeDir { filenames = append(filenames, filepath.Join(dir, f.Name())) } else { @@ -116,5 +115,5 @@ func removeInputOutput(path string) error { config.Input = nil config.Output = nil - return ioutil.WriteFile(path, []byte(config.String()), 0600) + return os.WriteFile(path, []byte(config.String()), 0600) } diff --git a/internal/logstash/pipelineconfigdir_test.go b/internal/logstash/pipelineconfigdir_test.go index 07b7bb3..2940aa5 100644 --- a/internal/logstash/pipelineconfigdir_test.go +++ b/internal/logstash/pipelineconfigdir_test.go @@ -4,7 +4,6 @@ package logstash import ( "errors" - "io/ioutil" "os" "path/filepath" "reflect" @@ -159,7 +158,7 @@ func TestGetPipelineConfigDir(t *testing.T) { t.Fatalf("Test %d: Unexpected error when creating temp dir: %s", i, err) } - if err := ioutil.WriteFile(filepath.Join(tempdir, f), []byte(createLogstashConfigWithString(filepath.Base(f))), 0600); err != nil { + if err := os.WriteFile(filepath.Join(tempdir, f), []byte(createLogstashConfigWithString(filepath.Base(f))), 0600); err != nil { t.Fatalf("Test %d: Unexpected error when writing to temp file: %s", i, err) } configFiles = append(configFiles, filepath.Join(tempdir, f)) @@ -197,7 +196,7 @@ func TestGetPipelineConfigDir(t *testing.T) { // Check that each file contains its own name. for _, f := range actualConfigFiles { - buf, err := ioutil.ReadFile(filepath.Join(resultDir, f)) + buf, err := os.ReadFile(filepath.Join(resultDir, f)) if err != nil { t.Fatalf("Test %d: Unexpected error when reading file: %s", i, err) } @@ -302,7 +301,7 @@ func TestRemoveInputOutput(t *testing.T) { }, } for i, c := range cases { - f, err := ioutil.TempFile("", "lsconf") + f, err := os.CreateTemp("", "lsconf") if err != nil { t.Fatalf("Test %d: Unexpected error when creating temp file: %s", i, err) } @@ -310,7 +309,7 @@ func TestRemoveInputOutput(t *testing.T) { path := f.Name() defer os.Remove(path) - err = ioutil.WriteFile(path, []byte(c.input), 0600) + err = os.WriteFile(path, []byte(c.input), 0600) if err != nil { t.Fatalf("Test %d: Unexpected error when writing to temp file: %s", i, err) } @@ -321,7 +320,7 @@ func TestRemoveInputOutput(t *testing.T) { continue } - data, err := ioutil.ReadFile(path) + data, err := os.ReadFile(path) if err != nil { t.Error(err) } diff --git a/internal/logstash/process.go b/internal/logstash/process.go index 0a46ea9..d5c40b8 100644 --- a/internal/logstash/process.go +++ b/internal/logstash/process.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "os/exec" ) @@ -115,7 +114,7 @@ func (p *Process) Wait() (*Result, error) { // Save the log output regardless of whether the child process // succeeded or not. - logbuf, logerr := ioutil.ReadAll(p.inv.logFile) + logbuf, logerr := io.ReadAll(p.inv.logFile) if logerr != nil { // Log this weird error condition but don't let it // fail the function. We don't care about the log @@ -123,7 +122,7 @@ func (p *Process) Wait() (*Result, error) { // report that problem anyway. log.Errorf("Error reading the Logstash logfile: %s", logerr) } - outbuf, _ := ioutil.ReadAll(p.stdio) + outbuf, _ := io.ReadAll(p.stdio) result := Result{ Events: []Event{}, diff --git a/internal/testcase/discover.go b/internal/testcase/discover.go index d27a4d1..92d825a 100644 --- a/internal/testcase/discover.go +++ b/internal/testcase/discover.go @@ -4,7 +4,6 @@ package testcase import ( "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -27,7 +26,7 @@ func DiscoverTests(path string) ([]TestCaseSet, error) { } func discoverTestDirectory(path string) ([]TestCaseSet, error) { - files, err := ioutil.ReadDir(path) + files, err := os.ReadDir(path) if err != nil { return nil, fmt.Errorf("Error discovering test case files: %s", err) } diff --git a/internal/testcase/discover_test.go b/internal/testcase/discover_test.go index 9dcc6a3..8d97f3a 100644 --- a/internal/testcase/discover_test.go +++ b/internal/testcase/discover_test.go @@ -3,7 +3,6 @@ package testcase import ( - "io/ioutil" "os" "path/filepath" "sort" @@ -53,7 +52,7 @@ func TestDiscoverTests_Directory(t *testing.T) { t.Errorf("This test doesn't support subdirectories: %s", f) break } - if err := ioutil.WriteFile(filepath.Join(tempdir, f), []byte(`{"type": "test"}`), 0600); err != nil { + if err := os.WriteFile(filepath.Join(tempdir, f), []byte(`{"type": "test"}`), 0600); err != nil { t.Fatalf(err.Error()) } } @@ -111,7 +110,7 @@ func TestDiscoverTests_File(t *testing.T) { // As it happens a valid JSON file is also a valid YAML file so // the file we create can have the same contents regardless of // the file format. - if err := ioutil.WriteFile(inputpath, []byte(`{"type": "test"}`), 0600); err != nil { + if err := os.WriteFile(inputpath, []byte(`{"type": "test"}`), 0600); err != nil { t.Fatal(err.Error()) } diff --git a/internal/testcase/testcase.go b/internal/testcase/testcase.go index 6e0153e..44dd8a3 100644 --- a/internal/testcase/testcase.go +++ b/internal/testcase/testcase.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -176,7 +175,7 @@ func New(reader io.Reader, configType string) (*TestCaseSet, error) { Codec: "line", InputFields: logstash.FieldSet{}, } - buf, err := ioutil.ReadAll(reader) + buf, err := io.ReadAll(reader) if err != nil { return nil, err } @@ -302,7 +301,7 @@ func (tcs *TestCaseSet) Compare(events []logstash.Event, diffCommand []string, l return true, nil } - tempdir, err := ioutil.TempDir("", "") + tempdir, err := os.MkdirTemp("", "") if err != nil { return false, err } @@ -367,7 +366,7 @@ func marshalToFile(event logstash.Event, filename string) error { if err = os.MkdirAll(filepath.Dir(filename), 0700); err != nil { return err } - return ioutil.WriteFile(filename, []byte(string(buf)+"\n"), 0600) + return os.WriteFile(filename, []byte(string(buf)+"\n"), 0600) } // runDiffCommand passes two files to the supplied command (executable diff --git a/internal/testcase/testcase_test.go b/internal/testcase/testcase_test.go index bfb4853..7e23ba7 100644 --- a/internal/testcase/testcase_test.go +++ b/internal/testcase/testcase_test.go @@ -5,7 +5,6 @@ package testcase import ( "bytes" "encoding/json" - "io/ioutil" "os" "path/filepath" "testing" @@ -129,7 +128,7 @@ func TestNewFromFile(t *testing.T) { // As it happens a valid JSON file is also a valid YAML file so // the file we create can have the same contents regardless of // the file format. - if err = ioutil.WriteFile(fullTestCasePath, []byte(`{"type": "test"}`), 0600); err != nil { + if err = os.WriteFile(fullTestCasePath, []byte(`{"type": "test"}`), 0600); err != nil { t.Fatal(err.Error()) } @@ -417,7 +416,7 @@ func TestMarshalToFile(t *testing.T) { // We won't verify the actual contents that was marshaled, // we'll just check that it can be unmarshalled again and that // the file ends with a newline. - buf, err := ioutil.ReadFile(fullpath) + buf, err := os.ReadFile(fullpath) if err != nil { t.Fatalf(err.Error()) } From d3982cafeb6cb9f58b249acfda16085be6224b55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:30:52 +0000 Subject: [PATCH 141/143] Bump gopkg.in/yaml.v3 from 3.0.0-20210107192922-496545a6307b to 3.0.0 Bumps gopkg.in/yaml.v3 from 3.0.0-20210107192922-496545a6307b to 3.0.0. --- updated-dependencies: - dependency-name: gopkg.in/yaml.v3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 6d2a1dd..2a6bc36 100644 --- a/go.mod +++ b/go.mod @@ -80,5 +80,5 @@ require ( gopkg.in/mattn/go-isatty.v0 v0.0.4 // indirect gopkg.in/mattn/go-runewidth.v0 v0.0.4 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect ) diff --git a/go.sum b/go.sum index 155cb61..9d122cd 100644 --- a/go.sum +++ b/go.sum @@ -875,8 +875,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 77a4aa09e621e8dd5dbca96deee6e50d4c6971b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:31:27 +0000 Subject: [PATCH 142/143] Bump github.com/ulikunitz/xz from 0.5.7 to 0.5.8 Bumps [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) from 0.5.7 to 0.5.8. - [Commits](https://github.com/ulikunitz/xz/compare/v0.5.7...v0.5.8) --- updated-dependencies: - dependency-name: github.com/ulikunitz/xz dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2a6bc36..2809821 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/subosito/gotenv v1.2.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect - github.com/ulikunitz/xz v0.5.7 // indirect + github.com/ulikunitz/xz v0.5.8 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yookoala/realpath v1.0.0 // indirect golang.org/x/mod v0.14.0 // indirect diff --git a/go.sum b/go.sum index 9d122cd..f4c526f 100644 --- a/go.sum +++ b/go.sum @@ -458,8 +458,9 @@ github.com/ucloud/ucloud-sdk-go v0.8.7/go.mod h1:lM6fpI8y6iwACtlbHUav823/uKPdXsN github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= -github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmware/govmomi v0.0.0-20170707011325-c2105a174311/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0/go.mod h1:sBh287mCRwCz6zyXHMmw7sSZGPohVpnx+o+OY4M+i3A= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= From d1b56d0551915888c0e4b3e14ae008c074f436b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:30:54 +0000 Subject: [PATCH 143/143] Bump golang.org/x/net from 0.20.0 to 0.23.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.23.0. - [Commits](https://github.com/golang/net/compare/v0.20.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2809821..cceaf7f 100644 --- a/go.mod +++ b/go.mod @@ -66,8 +66,8 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yookoala/realpath v1.0.0 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.17.0 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect diff --git a/go.sum b/go.sum index f4c526f..0c255be 100644 --- a/go.sum +++ b/go.sum @@ -576,8 +576,8 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -653,8 +653,8 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=