diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0663081..27bb2fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,20 +5,30 @@ jobs: name: Sca runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 + - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: v1.42 args: -c .golangci.yml + test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - name: Test run: | go test -race -coverprofile=profile.cov ./... + - name: Send coverage uses: shogo82148/actions-goveralls@v1 with: diff --git a/.golangci.yml b/.golangci.yml index 138da84..2e77b38 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,8 +4,8 @@ linters-settings: exhaustive: default-signifies-exhaustive: false funlen: - lines: 120 - statements: 120 + lines: 150 + statements: 150 gci: local-prefixes: github.com/cloudradar-monitoring/tacoscript goconst: @@ -25,7 +25,7 @@ linters-settings: - whyNoLint - wrapperFunc gocyclo: - min-complexity: 20 + min-complexity: 30 goimports: local-prefixes: github.com/golangci/golangci-lint golint: diff --git a/apptest/fs.go b/apptest/fs.go index cd5c02a..db4b46f 100644 --- a/apptest/fs.go +++ b/apptest/fs.go @@ -3,7 +3,6 @@ package apptest import ( "context" "fmt" - "io/ioutil" "net/url" "os" "time" @@ -62,7 +61,7 @@ func AssertFileMatchesExpectation(fe *FileExpectation) (isExpectationMatched boo return true, "", nil } - fileContentsBytes, err := ioutil.ReadFile(fe.FilePath) + fileContentsBytes, err := os.ReadFile(fe.FilePath) if err != nil { return false, "", err } diff --git a/apptest/fsOSWin.go b/apptest/fsOSWin.go index 799f440..6650e8c 100644 --- a/apptest/fsOSWin.go +++ b/apptest/fsOSWin.go @@ -3,6 +3,6 @@ package apptest -func AssertFileMatchesExpectationOS(filePath string, fe *FileExpectation) (bool, string, error) { +func AssertFileMatchesExpectationOS(filePath string, fe *FileExpectation) (isMatched bool, reason string, err error) { return true, "", nil } diff --git a/cmd/root.go b/cmd/root.go index 3b3ec5d..df2254c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,11 +1,13 @@ package cmd import ( + "fmt" "os" "github.com/cloudradar-monitoring/tacoscript/applog" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "gopkg.in/yaml.v2" ) var ( @@ -33,9 +35,17 @@ func initLog() { applog.Init(Verbose) } +type errorResult struct { + Error string `yaml:"Error"` +} + func Execute() error { if err := rootCmd.Execute(); err != nil { logrus.Debugf("Execute failed: %v", err) + + y, _ := yaml.Marshal(errorResult{Error: err.Error()}) + fmt.Println(string(y)) + os.Exit(1) } diff --git a/conv/conv.go b/conv/conv.go index 2060f61..2f0aad3 100644 --- a/conv/conv.go +++ b/conv/conv.go @@ -46,7 +46,7 @@ func ConvertToKeyValues(val interface{}, path string) (KeyValues, error) { key := item.Key.(string) val := item.Value res = append(res, KeyValue{ - Key: fmt.Sprint(key), + Key: key, Value: fmt.Sprint(val), }) } diff --git a/exec/runner.go b/exec/runner.go index 64c0b17..8e1c071 100644 --- a/exec/runner.go +++ b/exec/runner.go @@ -174,7 +174,7 @@ func (sr SystemRunner) createCmd(execContext *Context, tmpFile *os.File) (cmd *e rawCmds := prelude + strings.Join(execContext.Cmds, newLine) - if _, err = tmpFile.Write([]byte(rawCmds)); err != nil { + if _, err = tmpFile.WriteString(rawCmds); err != nil { return } diff --git a/script/builder.go b/script/builder.go index 319eb03..b52f9e5 100644 --- a/script/builder.go +++ b/script/builder.go @@ -4,7 +4,7 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "os" "text/template" "github.com/cloudradar-monitoring/tacoscript/utils" @@ -19,7 +19,7 @@ type FileDataProvider struct { } func (fdp FileDataProvider) Read() ([]byte, error) { - return ioutil.ReadFile(fdp.Path) + return os.ReadFile(fdp.Path) } type RawDataProvider interface { @@ -69,20 +69,22 @@ func (p Builder) BuildScripts() (tasks.Scripts, error) { Tasks: []tasks.Task{}, } index := 0 - steps := rawTask.Value.(yaml.MapSlice) - for _, step := range steps { - taskTypeID := step.Key.(string) - - index++ - task, e := p.TaskBuilder.Build(taskTypeID, fmt.Sprintf("%s.%s[%d]", scriptID, taskTypeID, index), step.Value) - if e != nil { - return tasks.Scripts{}, e + if steps, ok := rawTask.Value.(yaml.MapSlice); ok { + for _, step := range steps { + taskTypeID := step.Key.(string) + + index++ + task, e := p.TaskBuilder.Build(taskTypeID, fmt.Sprintf("%s.%s[%d]", scriptID, taskTypeID, index), step.Value) + if e != nil { + return tasks.Scripts{}, e + } + + errs.Add(task.Validate()) + script.Tasks = append(script.Tasks, task) } - - errs.Add(task.Validate()) - script.Tasks = append(script.Tasks, task) + } else { + errs.Add(fmt.Errorf("script failed to run. input YAML is malformed")) } - scripts = append(scripts, script) } err = ValidateScripts(scripts) diff --git a/script/builder_test.go b/script/builder_test.go index 57bf3ab..2a8d899 100644 --- a/script/builder_test.go +++ b/script/builder_test.go @@ -3,8 +3,8 @@ package script import ( "context" "errors" - "io/ioutil" "os" + "path/filepath" "testing" "github.com/cloudradar-monitoring/tacoscript/utils" @@ -65,7 +65,7 @@ func (tm *TaskBuilderTaskMock) GetPath() string { func (rdpm RawDataProviderMock) Read() ([]byte, error) { if rdpm.FileName != "" { - return ioutil.ReadFile("yaml" + string(os.PathSeparator) + rdpm.FileName) + return os.ReadFile(filepath.Join("yaml", rdpm.FileName)) } return []byte(rdpm.DataToReturn), rdpm.ErrToReturn diff --git a/script/scriptResult.go b/script/scriptResult.go index ccc639f..733c2ac 100644 --- a/script/scriptResult.go +++ b/script/scriptResult.go @@ -21,7 +21,7 @@ type taskResult struct { Started onlyTime `yaml:"Started"` Duration time.Duration `yaml:"Duration"` - Changes map[string]string `yaml:"Changes,omitempty"` // map for custom key-val data depending on type + Changes map[string]interface{} `yaml:"Changes,omitempty"` // map for custom key-val data depending on type } type scriptSummary struct { diff --git a/script/scriptRunner.go b/script/scriptRunner.go index af1d8f9..70f2db4 100644 --- a/script/scriptRunner.go +++ b/script/scriptRunner.go @@ -59,20 +59,25 @@ func (r Runner) Run(ctx context.Context, scripts tasks.Scripts, globalAbortOnErr tasksRun++ - changeMap := make(map[string]string) + name := "" + comment := "" + changeMap := make(map[string]interface{}) if cmdRunTask, ok := task.(*tasks.CmdRunTask); ok { + name = strings.Join(cmdRunTask.GetNames(), "; ") + if !res.IsSkipped { - changeMap["pid"] = fmt.Sprintf("%d", res.Pid) + comment = `Command "` + name + `" run` + changeMap["pid"] = res.Pid if runErr, ok := res.Err.(exec.RunError); ok { - changeMap["retcode"] = fmt.Sprintf("%d", runErr.ExitCode) + changeMap["retcode"] = runErr.ExitCode } changeMap["stderr"] = strings.TrimSpace(strings.ReplaceAll(res.StdErr, "\r\n", "\n")) changeMap["stdout"] = strings.TrimSpace(strings.ReplaceAll(res.StdOut, "\r\n", "\n")) if exec.IsPowerShell(cmdRunTask.Shell) { - changeMap["stdout"] = powershellUnquote(changeMap["stdout"]) + changeMap["stdout"] = powershellUnquote(changeMap["stdout"].(string)) } changes++ } @@ -82,6 +87,9 @@ func (r Runner) Run(ctx context.Context, scripts tasks.Scripts, globalAbortOnErr aborted = total - tasksRun } } + if pkgTask, ok := task.(*tasks.PkgTask); ok { + name = pkgTask.NamedTask.Name + } if len(res.Changes) > 0 { for k, v := range res.Changes { @@ -93,12 +101,24 @@ func (r Runner) Run(ctx context.Context, scripts tasks.Scripts, globalAbortOnErr if res.Err != nil { errString = res.Err.Error() } + + if managedTask, ok := task.(*tasks.FileManagedTask); ok { + name = managedTask.Name + if errString == "" { + if managedTask.Updated { + comment = "File " + name + " updated" + } else { + comment = "All files in creates exists" + } + } + } + result.Results = append(result.Results, taskResult{ ID: script.ID, Function: task.GetName(), - Name: res.Name, + Name: name, Result: res.Succeeded(), - Comment: res.Comment, + Comment: comment, Started: onlyTime(taskStart), Duration: res.Duration, Changes: changeMap, diff --git a/tasks/fileManagedTask.go b/tasks/fileManagedTask.go index b8caf25..491a181 100644 --- a/tasks/fileManagedTask.go +++ b/tasks/fileManagedTask.go @@ -5,6 +5,7 @@ import ( "context" "database/sql" "fmt" + "io/fs" "os" "time" @@ -140,6 +141,9 @@ type FileManagedTask struct { Creates []string OnlyIf []string Require []string + + // was managed file updated? + Updated bool } func (crt *FileManagedTask) GetName() string { @@ -199,7 +203,9 @@ type FileManagedTaskExecutor struct { func (fmte *FileManagedTaskExecutor) Execute(ctx context.Context, task Task) ExecutionResult { logrus.Debugf("will trigger '%s' task", task.GetPath()) - execRes := ExecutionResult{} + execRes := ExecutionResult{ + Changes: make(map[string]string), + } fileManagedTask, ok := task.(*FileManagedTask) if !ok { @@ -218,7 +224,7 @@ func (fmte *FileManagedTaskExecutor) Execute(ctx context.Context, task Task) Exe Path: fileManagedTask.Path, } logrus.Debugf("will check if the task '%s' should be executed", task.GetPath()) - skipReason, err := fmte.shouldBeExecuted(execCtx, fileManagedTask) + skipReason, err := fmte.shouldBeExecuted(execCtx, fileManagedTask, &execRes) if err != nil { execRes.Err = err return execRes @@ -257,6 +263,16 @@ func (fmte *FileManagedTaskExecutor) Execute(ctx context.Context, task Task) Exe execRes.Err = err return execRes } + fileManagedTask.Updated = true + + var info fs.FileInfo + info, err = fmte.FsManager.Stat(fileManagedTask.Name) + if err != nil { + execRes.Err = err + return execRes + } + + execRes.Changes["length"] = fmt.Sprintf("%d bytes written", info.Size()) } err = fmte.applyFileAttributesToTarget(fileManagedTask) if err != nil { @@ -301,7 +317,7 @@ func (fmte *FileManagedTaskExecutor) checkOnlyIfs(ctx *exec2.Context, fileManage if err != nil { runErr, isRunErr := err.(exec2.RunError) if isRunErr { - logrus.Debugf("will skip %s since onlyif condition has failed: %v", fileManagedTask, runErr) + logrus.Debugf("will skip %s since onlyif condition has failed: %v", fileManagedTask.String(), runErr) return false, nil } @@ -314,6 +330,7 @@ func (fmte *FileManagedTaskExecutor) checkOnlyIfs(ctx *exec2.Context, fileManage func (fmte *FileManagedTaskExecutor) shouldBeExecuted( ctx *exec2.Context, fileManagedTask *FileManagedTask, + execRes *ExecutionResult, ) (skipReason string, err error) { isExists, fileName, err := fmte.checkMissingFileCondition(fileManagedTask) if err != nil { @@ -352,7 +369,7 @@ func (fmte *FileManagedTaskExecutor) shouldBeExecuted( return onlyIfConditionFailedReason, nil } - skipReasonForContents, err := fmte.shouldSkipForContentExpectation(fileManagedTask) + skipReasonForContents, err := fmte.shouldSkipForContentExpectation(fileManagedTask, execRes) if err != nil { return "", err } @@ -360,7 +377,7 @@ func (fmte *FileManagedTaskExecutor) shouldBeExecuted( return skipReasonForContents, nil } - logrus.Debugf("all execution conditions are met, will continue %s", fileManagedTask) + logrus.Debugf("all execution conditions are met, will continue %s", fileManagedTask.String()) return "", nil } @@ -571,7 +588,10 @@ func (fmte *FileManagedTaskExecutor) copyContentToTarget(fileManagedTask *FileMa return err } -func (fmte *FileManagedTaskExecutor) shouldSkipForContentExpectation(fileManagedTask *FileManagedTask) (skipReason string, err error) { +func (fmte *FileManagedTaskExecutor) shouldSkipForContentExpectation( + fileManagedTask *FileManagedTask, + execRes *ExecutionResult, +) (skipReason string, err error) { if !fileManagedTask.Contents.Valid { logrus.Debug("contents section is missing, won't check the content") return "", nil @@ -607,7 +627,9 @@ func (fmte *FileManagedTaskExecutor) shouldSkipForContentExpectation(fileManaged logrus.WithFields( logrus.Fields{ "multiline": contentDiff, - }).Infof(`file '%s' differs from the expected content field, will copy diff to file`, fileManagedTask.Name) + }).Debugf(`file '%s' differs from the expected content field, will copy diff to file`, fileManagedTask.Name) + + execRes.Changes["diff"] = contentDiff return "", nil } diff --git a/tasks/fileManagedTask_test.go b/tasks/fileManagedTask_test.go index 0131a44..115bbce 100644 --- a/tasks/fileManagedTask_test.go +++ b/tasks/fileManagedTask_test.go @@ -5,8 +5,8 @@ import ( "database/sql" "errors" "fmt" - "io/ioutil" "net/url" + "os" "os/exec" "strings" "testing" @@ -75,27 +75,27 @@ func TestFileManagedTaskExecution(t *testing.T) { "sourceFileFTP.txt", } - err = ioutil.WriteFile("sourceFileAtLocal.txt", []byte("one two three"), 0600) + err = os.WriteFile("sourceFileAtLocal.txt", []byte("one two three"), 0600) assert.NoError(t, err) if err != nil { return } - err = ioutil.WriteFile("sourceFileHTTPS.txt", []byte("one two three"), 0600) + err = os.WriteFile("sourceFileHTTPS.txt", []byte("one two three"), 0600) assert.NoError(t, err) if err != nil { return } httpsSrvURL.Path = "/sourceFileHTTPS.txt" - err = ioutil.WriteFile("sourceFileHTTP.txt", []byte("one two three"), 0600) + err = os.WriteFile("sourceFileHTTP.txt", []byte("one two three"), 0600) assert.NoError(t, err) if err != nil { return } httpSrvURL.Path = "/sourceFileHTTP.txt" - err = ioutil.WriteFile("sourceFileFTP.txt", []byte("one two three"), 0600) + err = os.WriteFile("sourceFileFTP.txt", []byte("one two three"), 0600) assert.NoError(t, err) if err != nil { return @@ -541,7 +541,7 @@ three`, if tc.ContentEncodingToWrite != "" { e = utils.WriteEncodedFile(tc.ContentEncodingToWrite, tc.ContentToWrite, tc.Task.Name, 0600) } else { - e = ioutil.WriteFile(tc.Task.Name, []byte(tc.ContentToWrite), 0600) + e = os.WriteFile(tc.Task.Name, []byte(tc.ContentToWrite), 0600) } assert.NoError(t, e) } diff --git a/utils/encoding.go b/utils/encoding.go index 4032cf7..31f51d6 100644 --- a/utils/encoding.go +++ b/utils/encoding.go @@ -2,7 +2,6 @@ package utils import ( "fmt" - "io/ioutil" "os" "strings" @@ -118,11 +117,11 @@ func WriteEncodedFile(encodingName, contentsUtf8, fileName string, perm os.FileM return err } - return ioutil.WriteFile(fileName, encodedData, perm) + return os.WriteFile(fileName, encodedData, perm) } func ReadEncodedFile(encodingName, fileName string) (contentsUtf8 string, err error) { - contents, err := ioutil.ReadFile(fileName) + contents, err := os.ReadFile(fileName) if err != nil { return } diff --git a/utils/fs.go b/utils/fs.go index 40e5f1a..d9bf46d 100644 --- a/utils/fs.go +++ b/utils/fs.go @@ -5,7 +5,6 @@ import ( "crypto/tls" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -38,11 +37,11 @@ func (fmm *FsManager) CopyLocalFile(sourceFilePath, targetFilePath string, mode } func (fmm *FsManager) WriteFile(name, contents string, mode os.FileMode) error { - return ioutil.WriteFile(name, []byte(contents), mode) + return os.WriteFile(name, []byte(contents), mode) } func (fmm *FsManager) ReadFile(filePath string) (content string, err error) { - contentsByte, err := ioutil.ReadFile(filePath) + contentsByte, err := os.ReadFile(filePath) return string(contentsByte), err } @@ -90,12 +89,12 @@ func MoveFile(sourceFilePath, targetFilePath string) error { } func CopyLocalFile(sourceFilePath, targetFilePath string, mode os.FileMode) error { - input, err := ioutil.ReadFile(sourceFilePath) + input, err := os.ReadFile(sourceFilePath) if err != nil { return err } - err = ioutil.WriteFile(targetFilePath, input, mode) + err = os.WriteFile(targetFilePath, input, mode) if err != nil { return err } @@ -110,7 +109,7 @@ func DownloadHTTPFile(ctx context.Context, u fmt.Stringer, targetFilePath string } defer CloseResourceSecure(targetFilePath, out) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody) if err != nil { return nil } @@ -141,7 +140,7 @@ func DownloadHTTPSFile(ctx context.Context, skipTLS bool, u fmt.Stringer, target }, } - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody) if err != nil { return nil } @@ -150,6 +149,9 @@ func DownloadHTTPSFile(ctx context.Context, skipTLS bool, u fmt.Stringer, target if err != nil { return err } + if resp.StatusCode < 200 || resp.StatusCode > 299 { + return fmt.Errorf("http status %d %s", resp.StatusCode, http.StatusText(resp.StatusCode)) + } defer CloseResourceSecure("http body", resp.Body) _, err = io.Copy(out, resp.Body) diff --git a/utils/hash_test.go b/utils/hash_test.go index e09442a..80f524f 100644 --- a/utils/hash_test.go +++ b/utils/hash_test.go @@ -1,7 +1,6 @@ package utils import ( - "io/ioutil" "os" "testing" @@ -107,7 +106,7 @@ func TestHashes(t *testing.T) { } for _, testCase := range testCases { - err := ioutil.WriteFile("testFile.txt", []byte(testCase.data), 0600) + err := os.WriteFile("testFile.txt", []byte(testCase.data), 0600) assert.NoError(t, err) if err != nil { return