diff --git a/check/base.go b/check/base.go index ee1b09e..d11009c 100644 --- a/check/base.go +++ b/check/base.go @@ -78,7 +78,7 @@ func (c *FTWCheck) ForcedFail(id string) bool { // CloudMode returns true if we are running in cloud mode func (c *FTWCheck) CloudMode() bool { - return c.overrides.Mode == config.CloudMode + return config.FTWConfig.RunMode == config.CloudRunMode } // SetCloudMode alters the values for expected logs and status code diff --git a/check/base_test.go b/check/base_test.go index 0ff4abe..ad7239f 100644 --- a/check/base_test.go +++ b/check/base_test.go @@ -20,8 +20,7 @@ testoverride: ` var yamlCloudConfig = `--- -testoverride: - mode: "cloud" +mode: "cloud" ` func TestNewCheck(t *testing.T) { diff --git a/cmd/root.go b/cmd/root.go index 775e8dd..3614e16 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -60,6 +60,6 @@ func initConfig() { } } if cloud { - config.FTWConfig.TestOverride.Mode = config.CloudMode + config.FTWConfig.RunMode = config.CloudRunMode } } diff --git a/config/config.go b/config/config.go index 3186bd7..079c872 100644 --- a/config/config.go +++ b/config/config.go @@ -95,7 +95,7 @@ func loadDefaults() { if FTWConfig.LogMarkerHeaderName == "" { FTWConfig.LogMarkerHeaderName = DefaultLogMarkerHeaderName } - if FTWConfig.TestOverride.Mode == "" { - FTWConfig.TestOverride.Mode = DefaultMode + if FTWConfig.RunMode == "" { + FTWConfig.RunMode = DefaultRunMode } } diff --git a/config/config_test.go b/config/config_test.go index 9a31907..3faf0d4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -10,7 +10,6 @@ import ( var yamlConfig = `--- logfile: 'tests/logs/modsec2-apache/apache2/error.log' -cloudmode: True testoverride: input: dest_addr: 'httpbin.org' @@ -19,6 +18,10 @@ testoverride: '920400-1': 'This test result must be ignored' ` +var yamlCloudConfig = `--- +mode: 'cloud' +` + var yamlBadConfig = ` --- logfile: 'tests/logs/modsec2-apache/apache2/error.log' @@ -130,8 +133,8 @@ func TestNewConfigFromEnvHasDefaults(t *testing.T) { t.Error(err) } - if FTWConfig.TestOverride.Mode != DefaultMode { - t.Errorf("unexpected default value '%s' for testoverride.mode", FTWConfig.TestOverride.Mode) + if FTWConfig.RunMode != DefaultRunMode { + t.Errorf("unexpected default value '%s' for run mode", FTWConfig.RunMode) } if FTWConfig.LogMarkerHeaderName != DefaultLogMarkerHeaderName { t.Errorf("unexpected default value '%s' for logmarkerheadername", FTWConfig.LogMarkerHeaderName) @@ -146,8 +149,8 @@ func TestNewConfigFromFileHasDefaults(t *testing.T) { t.Error(err) } - if FTWConfig.TestOverride.Mode != DefaultMode { - t.Errorf("unexpected default value '%s' for testoverride.mode", FTWConfig.TestOverride.Mode) + if FTWConfig.RunMode != DefaultRunMode { + t.Errorf("unexpected default value '%s' for run mode", FTWConfig.RunMode) } if FTWConfig.LogMarkerHeaderName != DefaultLogMarkerHeaderName { t.Errorf("unexpected default value '%s' for logmarkerheadername", FTWConfig.LogMarkerHeaderName) @@ -159,10 +162,23 @@ func TestNewConfigFromStringHasDefaults(t *testing.T) { t.Error(err) } - if FTWConfig.TestOverride.Mode != DefaultMode { - t.Errorf("unexpected default value '%s' for testoverride.mode", FTWConfig.TestOverride.Mode) + if FTWConfig.RunMode != DefaultRunMode { + t.Errorf("unexpected default value '%s' for run mode", FTWConfig.RunMode) } if FTWConfig.LogMarkerHeaderName != DefaultLogMarkerHeaderName { t.Errorf("unexpected default value '%s' for logmarkerheadername", FTWConfig.LogMarkerHeaderName) } } + +func TestNewConfigFromFileRunMode(t *testing.T) { + filename, _ := utils.CreateTempFileWithContent(yamlCloudConfig, "test-*.yaml") + defer os.Remove(filename) + + if err := NewConfigFromFile(filename); err != nil { + t.Error(err) + } + + if FTWConfig.RunMode != CloudRunMode { + t.Errorf("unexpected value '%s' for run mode, expected '%s;", FTWConfig.RunMode, CloudRunMode) + } +} diff --git a/config/types.go b/config/types.go index d162447..ae42668 100644 --- a/config/types.go +++ b/config/types.go @@ -1,10 +1,14 @@ package config +// RunMode represents the mode of the test run +type RunMode string + const ( - // CloudMode is the string that will be used to override the mode of execution to cloud - CloudMode string = "cloud" - // DefaultMode is the default execution mode - DefaultMode string = "default" + // CloudRunMode is the string that will be used to override the run mode of execution to cloud + CloudRunMode RunMode = "cloud" + // DefaultRunMode is the default execution run mode + DefaultRunMode RunMode = "default" + // DefaultLogMarkerHeaderName is the default log marker header name DefaultLogMarkerHeaderName string = "X-CRS-Test" ) @@ -16,6 +20,7 @@ type FTWConfiguration struct { LogFile string `koanf:"logfile"` TestOverride FTWTestOverride `koanf:"testoverride"` LogMarkerHeaderName string `koanf:"logmarkerheadername"` + RunMode RunMode `koanf:"mode"` } // FTWTestOverride holds four lists: @@ -28,5 +33,4 @@ type FTWTestOverride struct { Ignore map[string]string `koanf:"ignore"` ForcePass map[string]string `koanf:"forcepass"` ForceFail map[string]string `koanf:"forcefail"` - Mode string `koanf:"mode"` } diff --git a/runner/run.go b/runner/run.go index 5b2957a..7318231 100644 --- a/runner/run.go +++ b/runner/run.go @@ -26,6 +26,8 @@ import ( func Run(include string, exclude string, showTime bool, output bool, ftwtests []test.FTWTest) TestRunContext { printUnlessQuietMode(output, ":rocket:Running go-ftw!\n") + logLines := waflog.NewFTWLogLines(waflog.WithLogFile(config.FTWConfig.LogFile)) + client := ftwhttp.NewClient() runContext := TestRunContext{ Include: include, @@ -33,6 +35,8 @@ func Run(include string, exclude string, showTime bool, output bool, ftwtests [] ShowTime: showTime, Output: output, Client: client, + LogLines: logLines, + RunMode: config.FTWConfig.RunMode, } for _, test := range ftwtests { @@ -41,6 +45,8 @@ func Run(include string, exclude string, showTime bool, output bool, ftwtests [] printSummary(output, runContext.Stats) + defer cleanLogs(logLines) + return runContext } @@ -54,7 +60,9 @@ func RunTest(runContext *TestRunContext, ftwTest test.FTWTest) { // if we received a particular testid, skip until we find it if needToSkipTest(runContext.Include, runContext.Exclude, testCase.TestTitle, ftwTest.Meta.Enabled) { addResultToStats(Skipped, testCase.TestTitle, &runContext.Stats) - printUnlessQuietMode(runContext.Output, "Skipping test %s\n", testCase.TestTitle) + if !ftwTest.Meta.Enabled { + printUnlessQuietMode(runContext.Output, "Skipping test %s\n", testCase.TestTitle) + } continue } // this is just for printing once the next test @@ -79,6 +87,7 @@ func RunTest(runContext *TestRunContext, ftwTest test.FTWTest) { // testCase is the test case the stage belongs to // stage is the stage you want to run func RunStage(runContext *TestRunContext, ftwCheck *check.FTWCheck, testCase test.Test, stage test.Stage) { + stageStartTime := time.Now() stageID := uuid.NewString() // Apply global overrides initially testRequest := stage.Input @@ -108,11 +117,13 @@ func RunStage(runContext *TestRunContext, ftwCheck *check.FTWCheck, testCase tes Protocol: testRequest.GetProtocol(), } - startMarker, err := markAndFlush(runContext.Client, dest, stageID) - if err != nil && !expectedOutput.ExpectError { - log.Fatal().Caller().Err(err).Msg("Failed to find start marker") + if notRunningInCloudMode(ftwCheck) { + startMarker, err := markAndFlush(runContext, dest, stageID) + if err != nil && !expectedOutput.ExpectError { + log.Fatal().Caller().Err(err).Msg("Failed to find start marker") + } + ftwCheck.SetStartMarker(startMarker) } - ftwCheck.SetStartMarker(startMarker) req = getRequestFromTest(testRequest) @@ -130,12 +141,14 @@ func RunStage(runContext *TestRunContext, ftwCheck *check.FTWCheck, testCase tes log.Fatal().Caller().Err(err).Msgf("can't connect to destination %+v - unexpected error found. Is your waf running?", dest) } - endMarker, err := markAndFlush(runContext.Client, dest, stageID) - if err != nil && !expectedOutput.ExpectError { - log.Fatal().Caller().Err(err).Msg("Failed to find end marker") + if notRunningInCloudMode(ftwCheck) { + endMarker, err := markAndFlush(runContext, dest, stageID) + if err != nil && !expectedOutput.ExpectError { + log.Fatal().Caller().Err(err).Msg("Failed to find end marker") + } + ftwCheck.SetEndMarker(endMarker) } - ftwCheck.SetEndMarker(endMarker) // Set expected test output in check ftwCheck.SetExpectTestOutput(&expectedOutput) @@ -143,29 +156,25 @@ func RunStage(runContext *TestRunContext, ftwCheck *check.FTWCheck, testCase tes // now get the test result based on output testResult := checkResult(ftwCheck, response, responseErr) - duration := runContext.Client.GetRoundTripTime().RoundTripDuration() + roundTripTime := runContext.Client.GetRoundTripTime().RoundTripDuration() + stageTime := time.Since(stageStartTime) addResultToStats(testResult, testCase.TestTitle, &runContext.Stats) runContext.Result = testResult // show the result unless quiet was passed in the command line - displayResult(runContext.Output, testResult, duration) + displayResult(runContext.Output, testResult, roundTripTime, stageTime) runContext.Stats.Run++ - runContext.Stats.RunTime += duration + runContext.Stats.RunTime += stageTime } -func markAndFlush(client *ftwhttp.Client, dest *ftwhttp.Destination, stageID string) ([]byte, error) { - var req *ftwhttp.Request - var logLines = &waflog.FTWLogLines{ - FileName: config.FTWConfig.LogFile, - } - +func markAndFlush(runContext *TestRunContext, dest *ftwhttp.Destination, stageID string) ([]byte, error) { rline := &ftwhttp.RequestLine{ Method: "GET", URI: "/", - Version: "HTTP/1.0", + Version: "HTTP/1.1", } headers := &ftwhttp.Header{ @@ -175,27 +184,27 @@ func markAndFlush(client *ftwhttp.Client, dest *ftwhttp.Destination, stageID str config.FTWConfig.LogMarkerHeaderName: stageID, } - req = ftwhttp.NewRequest(rline, *headers, nil, true) + req := ftwhttp.NewRequest(rline, *headers, nil, true) // 20 is a very conservative number. The web server should flush its // buffer a lot earlier but we have absolutely no control over that. for range [20]int{} { - err := client.NewConnection(*dest) + err := runContext.Client.NewConnection(*dest) if err != nil { return nil, fmt.Errorf("ftw/run: can't connect to destination %+v - unexpected error found. Is your waf running?", dest) } - _, err = client.Do(*req) + _, err = runContext.Client.Do(*req) if err != nil { return nil, fmt.Errorf("ftw/run: failed sending request to %+v - unexpected error found. Is your waf running?", dest) } - marker := logLines.CheckLogForMarker(stageID) + marker := runContext.LogLines.CheckLogForMarker(stageID) if marker != nil { return marker, nil } } - return nil, fmt.Errorf("can't find log marker. Am I reading the correct log? Log file: %s", logLines.FileName) + return nil, fmt.Errorf("can't find log marker. Am I reading the correct log? Log file: %s", runContext.LogLines.FileName) } func needToSkipTest(include string, exclude string, title string, enabled bool) bool { @@ -241,14 +250,14 @@ func checkTestSanity(testRequest test.Input) bool { (testRequest.EncodedRequest != "" && testRequest.RAWRequest != "") } -func displayResult(quiet bool, result TestResult, duration time.Duration) { +func displayResult(quiet bool, result TestResult, roundTripTime time.Duration, stageTime time.Duration) { switch result { case Success: - printUnlessQuietMode(quiet, ":check_mark:passed in %s\n", duration) + printUnlessQuietMode(quiet, ":check_mark:passed in %s (RTT %s)\n", stageTime, roundTripTime) case Failed: - printUnlessQuietMode(quiet, ":collision:failed in %s\n", duration) + printUnlessQuietMode(quiet, ":collision:failed in %s (RTT %s)\n", stageTime, roundTripTime) case Ignored: - printUnlessQuietMode(quiet, ":equal:test result ignored in %s\n", duration) + printUnlessQuietMode(quiet, ":equal:test result ignored in %s (RTT %s)\n", stageTime, roundTripTime) default: // don't print anything if skipped test } @@ -367,3 +376,13 @@ func applyInputOverride(testRequest *test.Input) error { } return retErr } + +func notRunningInCloudMode(c *check.FTWCheck) bool { + return !c.CloudMode() +} + +func cleanLogs(logLines *waflog.FTWLogLines) { + if err := logLines.Cleanup(); err != nil { + log.Fatal().Err(err).Msg("Failed to cleanup log file") + } +} diff --git a/runner/run_test.go b/runner/run_test.go index 28d3467..9bc9248 100644 --- a/runner/run_test.go +++ b/runner/run_test.go @@ -43,8 +43,7 @@ testoverride: var yamlCloudConfig = ` --- -testoverride: - mode: cloud +mode: cloud ` var logText = ` @@ -247,7 +246,7 @@ func newTestServer(t *testing.T, logLines string) (destination *ftwhttp.Destinat logFilePath = setUpLogFileForTestServer(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Hello, client")) writeTestServerLog(t, logLines, logFilePath, r) @@ -265,14 +264,10 @@ func newTestServer(t *testing.T, logLines string) (destination *ftwhttp.Destinat } // Error checking omitted for brevity -func newTestServerForCloudTest(t *testing.T, responseStatus int, logLines string) (server *httptest.Server, destination *ftwhttp.Destination, logFilePath string) { - logFilePath = setUpLogFileForTestServer(t) - +func newTestServerForCloudTest(t *testing.T, responseStatus int, logLines string) (server *httptest.Server, destination *ftwhttp.Destination) { server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(responseStatus) _, _ = w.Write([]byte("Hello, client")) - - writeTestServerLog(t, logLines, logFilePath, r) })) // close server after test @@ -283,12 +278,12 @@ func newTestServerForCloudTest(t *testing.T, responseStatus int, logLines string t.Error(err) t.FailNow() } - return server, dest, logFilePath + return server, dest } func setUpLogFileForTestServer(t *testing.T) (logFilePath string) { // log to the configured file - if config.FTWConfig != nil { + if config.FTWConfig != nil && config.FTWConfig.RunMode == config.DefaultRunMode { logFilePath = config.FTWConfig.LogFile } // if no file has been configured, create one and handle cleanup @@ -298,9 +293,7 @@ func setUpLogFileForTestServer(t *testing.T) (logFilePath string) { t.Error(err) } logFilePath = file.Name() - t.Cleanup(func() { - os.Remove(logFilePath) - }) + t.Cleanup(func() { os.Remove(logFilePath) }) } return logFilePath } @@ -417,7 +410,7 @@ func TestOverrideRun(t *testing.T) { // setup test webserver (not a waf) err := config.NewConfigFromString(yamlConfigOverride) if err != nil { - t.Errorf("Failed!") + t.Error(err) } dest, logFilePath := newTestServer(t, logText) @@ -556,10 +549,9 @@ func TestCloudRun(t *testing.T) { } else if stage.Output.NoLogContains != "" { responseStatus = 405 } - server, dest, logFilePath := newTestServerForCloudTest(t, responseStatus, logText) + server, dest := newTestServerForCloudTest(t, responseStatus, logText) replaceDestinationInConfiguration(*dest) - config.FTWConfig.LogFile = logFilePath replaceDestinationInTest(&ftwTest, *dest) if err != nil { @@ -571,6 +563,7 @@ func TestCloudRun(t *testing.T) { ShowTime: false, Output: true, Client: ftwhttp.NewClient(), + LogLines: nil, } RunStage(&runContext, ftwCheck, *testCase, *stage) @@ -585,8 +578,8 @@ func TestCloudRun(t *testing.T) { } func TestFailedTestsRun(t *testing.T) { - dest, logFilePath := newTestServer(t, logText) err := config.NewConfigFromString(yamlConfig) + dest, logFilePath := newTestServer(t, logText) if err != nil { t.Errorf("Failed!") } diff --git a/runner/types.go b/runner/types.go index 9650a56..9cc1ce6 100644 --- a/runner/types.go +++ b/runner/types.go @@ -3,7 +3,9 @@ package runner import ( "time" + "github.com/fzipi/go-ftw/config" "github.com/fzipi/go-ftw/ftwhttp" + "github.com/fzipi/go-ftw/waflog" ) // TestRunContext carries information about the current test run. @@ -18,4 +20,6 @@ type TestRunContext struct { Result TestResult Duration time.Duration Client *ftwhttp.Client + LogLines *waflog.FTWLogLines + RunMode config.RunMode } diff --git a/waflog/read.go b/waflog/read.go index 54f1dfe..59bae88 100644 --- a/waflog/read.go +++ b/waflog/read.go @@ -35,14 +35,12 @@ func (ll *FTWLogLines) Contains(match string) bool { func (ll *FTWLogLines) getMarkedLines() [][]byte { var found [][]byte - logfile, err := os.Open(ll.FileName) - if err != nil { - log.Fatal().Caller().Msgf("cannot open file %s", ll.FileName) + if err := ll.openLogFile(); err != nil { + log.Error().Caller().Msgf("cannot open log file: %s", err) } - defer logfile.Close() - fi, err := logfile.Stat() + fi, err := ll.logFile.Stat() if err != nil { log.Error().Caller().Msgf("cannot read file's size") return found @@ -52,7 +50,7 @@ func (ll *FTWLogLines) getMarkedLines() [][]byte { backscannerOptions := &backscanner.Options{ ChunkSize: 4096, } - scanner := backscanner.NewOptions(logfile, int(fi.Size()), backscannerOptions) + scanner := backscanner.NewOptions(ll.logFile, int(fi.Size()), backscannerOptions) endFound := false // end marker is the *first* marker when reading backwards, // start marker is the *last* marker @@ -80,18 +78,16 @@ func (ll *FTWLogLines) getMarkedLines() [][]byte { return found } -func (ll *FTWLogLines) CheckLogForMarker(stageId string) []byte { - logfile, err := os.Open(ll.FileName) - - if err != nil { - log.Error().Caller().Err(err).Msg("failed to open file") - return nil +// CheckLogForMarker reads the log file and searches for a marker line. +// logFile is the file to search +// stageID is the ID of the current stage, which is part of the marker line +func (ll *FTWLogLines) CheckLogForMarker(stageID string) []byte { + if config.FTWConfig.RunMode == config.DefaultRunMode && ll.logFile == nil { + log.Fatal().Caller().Msg("No log file supplied") } - defer logfile.Close() - - fi, err := logfile.Stat() + offset, err := ll.logFile.Seek(0, os.SEEK_END) if err != nil { - log.Error().Caller().Err(err).Msgf("cannot read file's size") + log.Error().Caller().Err(err).Msgf("failed to seek end of log file") return nil } @@ -99,8 +95,8 @@ func (ll *FTWLogLines) CheckLogForMarker(stageId string) []byte { backscannerOptions := &backscanner.Options{ ChunkSize: 4096, } - scanner := backscanner.NewOptions(logfile, int(fi.Size()), backscannerOptions) - stageIdBytes := []byte(stageId) + scanner := backscanner.NewOptions(ll.logFile, int(offset), backscannerOptions) + stageIDBytes := []byte(stageID) crsHeaderBytes := bytes.ToLower([]byte(config.FTWConfig.LogMarkerHeaderName)) line := []byte{} @@ -111,12 +107,11 @@ func (ll *FTWLogLines) CheckLogForMarker(stageId string) []byte { if err != nil { if err == io.EOF { return nil - } else { - log.Trace().Err(err) } + log.Trace().Err(err) } line = bytes.ToLower(line) - if bytes.Contains(line, crsHeaderBytes) && bytes.Contains(line, stageIdBytes) { + if bytes.Contains(line, crsHeaderBytes) && bytes.Contains(line, stageIDBytes) { return line } diff --git a/waflog/read_test.go b/waflog/read_test.go index 6925113..51e80ea 100644 --- a/waflog/read_test.go +++ b/waflog/read_test.go @@ -28,12 +28,10 @@ func TestReadCheckLogForMarkerNoMarkerAtEnd(t *testing.T) { if err != nil { t.Fatal(err) } + config.FTWConfig.LogFile = filename t.Cleanup(func() { os.Remove(filename) }) - ll := &FTWLogLines{ - FileName: filename, - StartMarker: bytes.ToLower([]byte(markerLine)), - } + ll := NewFTWLogLines(WithStartMarker(bytes.ToLower([]byte(markerLine)))) marker := ll.CheckLogForMarker(stageID) if marker != nil { @@ -57,11 +55,10 @@ func TestReadCheckLogForMarkerWithMarkerAtEnd(t *testing.T) { if err != nil { t.Fatal(err) } + config.FTWConfig.LogFile = filename t.Cleanup(func() { os.Remove(filename) }) - ll := &FTWLogLines{ - FileName: filename, - } + ll := NewFTWLogLines(WithStartMarker(bytes.ToLower([]byte(markerLine)))) marker := ll.CheckLogForMarker(stageID) if marker == nil { @@ -85,13 +82,12 @@ func TestReadGetMarkedLines(t *testing.T) { if err != nil { t.Fatal(err) } + config.FTWConfig.LogFile = filename t.Cleanup(func() { os.Remove(filename) }) - ll := &FTWLogLines{ - FileName: filename, - StartMarker: bytes.ToLower([]byte(startMarkerLine)), - EndMarker: bytes.ToLower([]byte(endMarkerLine)), - } + ll := NewFTWLogLines( + WithStartMarker(bytes.ToLower([]byte(startMarkerLine))), + WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))) foundLines := ll.getMarkedLines() // logs are scanned backwards @@ -123,13 +119,12 @@ func TestReadGetMarkedLinesWithTrailingEmptyLines(t *testing.T) { if err != nil { t.Fatal(err) } + config.FTWConfig.LogFile = filename t.Cleanup(func() { os.Remove(filename) }) - ll := &FTWLogLines{ - FileName: filename, - StartMarker: bytes.ToLower([]byte(startMarkerLine)), - EndMarker: bytes.ToLower([]byte(endMarkerLine)), - } + ll := NewFTWLogLines( + WithStartMarker(bytes.ToLower([]byte(startMarkerLine))), + WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))) foundLines := ll.getMarkedLines() // logs are scanned backwards @@ -164,13 +159,12 @@ func TestReadGetMarkedLinesWithPrecedingLines(t *testing.T) { if err != nil { t.Fatal(err) } + config.FTWConfig.LogFile = filename t.Cleanup(func() { os.Remove(filename) }) - ll := &FTWLogLines{ - FileName: filename, - StartMarker: bytes.ToLower([]byte(startMarkerLine)), - EndMarker: bytes.ToLower([]byte(endMarkerLine)), - } + ll := NewFTWLogLines( + WithStartMarker(bytes.ToLower([]byte(startMarkerLine))), + WithEndMarker(bytes.ToLower([]byte(endMarkerLine)))) foundLines := ll.getMarkedLines() // logs are scanned backwards diff --git a/waflog/types.go b/waflog/types.go index d7c4160..add8e54 100644 --- a/waflog/types.go +++ b/waflog/types.go @@ -1,9 +1,15 @@ -// Package waflog is responsible for getting logs from a WAF to compare with expected results +// Package waflog encapsulates getting logs from a WAF to compare with expected results package waflog +import "os" + // FTWLogLines represents the filename to search for logs in a certain timespan type FTWLogLines struct { + logFile *os.File FileName string StartMarker []byte EndMarker []byte } + +// FTWLogOption follows the option pattern for FTWLogLines +type FTWLogOption func(*FTWLogLines) diff --git a/waflog/waflog.go b/waflog/waflog.go new file mode 100644 index 0000000..baac817 --- /dev/null +++ b/waflog/waflog.go @@ -0,0 +1,72 @@ +package waflog + +import ( + "os" + + "github.com/fzipi/go-ftw/config" + "github.com/rs/zerolog/log" +) + +// NewFTWLogLines is the base struct for reading the log file +func NewFTWLogLines(opts ...FTWLogOption) *FTWLogLines { + ll := &FTWLogLines{ + logFile: nil, + FileName: config.FTWConfig.LogFile, + StartMarker: nil, + EndMarker: nil, + } + + // Loop through each option + for _, opt := range opts { + // Call the option giving the instantiated + // *FTWLogOption as the argument + opt(ll) + } + + if err := ll.openLogFile(); err != nil { + log.Error().Caller().Msgf("cannot open log file: %s", err) + } + + return ll +} + +// WithStartMarker sets the start marker for the log file +func WithStartMarker(marker []byte) FTWLogOption { + return func(ll *FTWLogLines) { + ll.StartMarker = marker + } +} + +// WithEndMarker sets the end marker for the log file +func WithEndMarker(marker []byte) FTWLogOption { + return func(ll *FTWLogLines) { + ll.EndMarker = marker + } +} + +// WithLogFile sets the log file to read +func WithLogFile(fileName string) FTWLogOption { + return func(ll *FTWLogLines) { + ll.FileName = fileName + } +} + +// Cleanup closes the log file +func (ll *FTWLogLines) Cleanup() error { + if ll.logFile != nil { + return ll.logFile.Close() + } + return nil +} + +func (ll *FTWLogLines) openLogFile() error { + // Using a log file is not required in cloud mode + if config.FTWConfig.RunMode == config.DefaultRunMode { + if ll.FileName != "" && ll.logFile == nil { + var err error + ll.logFile, err = os.Open(ll.FileName) + return err + } + } + return nil +} diff --git a/waflog/waflog_test.go b/waflog/waflog_test.go new file mode 100644 index 0000000..19eadd6 --- /dev/null +++ b/waflog/waflog_test.go @@ -0,0 +1,38 @@ +package waflog + +import ( + "testing" + + "github.com/fzipi/go-ftw/config" +) + +func TestNewFTWLogLines(t *testing.T) { + if err := config.NewConfigFromEnv(); err != nil { + t.Error(err) + } + + ll := NewFTWLogLines() + // Loop through each option + for _, opt := range []FTWLogOption{ + WithStartMarker([]byte("#")), + WithEndMarker([]byte("#")), + WithLogFile("test"), + } { + // Call the option giving the instantiated + // *House as the argument + opt(ll) + } + if ll.StartMarker == nil { + t.Errorf("Failed! StartMarker must be set") + } + if ll.EndMarker == nil { + t.Errorf("Failed! EndMarker must be set") + } + if ll.FileName != "test" { + t.Errorf("Failed! FileName must be set") + } + + if err := ll.Cleanup(); err != nil { + t.Error(err) + } +}