From 66425258783c41b3fac64c7d447aec7846dfc36c Mon Sep 17 00:00:00 2001 From: Vivek Lingaiah Date: Mon, 19 Aug 2024 22:51:09 +0000 Subject: [PATCH 1/2] Do not write status for Disable, Update. Create dummy status files during update if not available --- internal/cmds/cmds.go | 115 +++++++++++++++++++++++++++----- internal/cmds/cmds_test.go | 34 +++++----- internal/constants/constants.go | 4 ++ internal/status/status.go | 19 ++++-- internal/types/commands.go | 4 +- 5 files changed, 137 insertions(+), 39 deletions(-) mode change 100644 => 100755 internal/status/status.go mode change 100644 => 100755 internal/types/commands.go diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index e295784..bb54262 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -4,12 +4,14 @@ import ( "bufio" "bytes" "compress/gzip" + "container/list" "context" "encoding/base64" "fmt" "io" "os" "path/filepath" + "strconv" "strings" "time" @@ -334,21 +336,30 @@ func checkAndSaveSeqNum(ctx log.Logger, seq int, mrseqPath string) (shouldExit b // Copy state of the extension from old version to new version during update (.mrseq files, .status files) func CopyStateForUpdate(ctx log.Logger) error { // Copy .mrseq files (Most Recently executed Sequence number) that helps determine whether a sequence number of Run Command has been previously executed or not. - err := copyFiles(ctx, ".mrseq", "") - if err != nil { - return err + mrseqFilesNameList, mrseqFileCopyErr := copyFiles(ctx, ".mrseq", "") + if mrseqFileCopyErr != nil { + return mrseqFileCopyErr } // Copy .status files of already executed sequence numbers - err = copyFiles(ctx, ".status", "status") - if err != nil { - return err + _, statusFileCopyErr := copyFiles(ctx, ".status", constants.StatusFileDirectory) + if statusFileCopyErr != nil { + return statusFileCopyErr } + + // If status file corresponding to a .mrseq file does not exist, create a dummy status file to prevent poll status timeouts for already executed Run Commands after upgrade. + if mrseqFilesNameList != nil && mrseqFilesNameList.Len() > 0 { + createDummyStatusFilesErr := createDummyStatusFilesIfNeeded(ctx, mrseqFilesNameList) + if createDummyStatusFilesErr != nil { + return createDummyStatusFilesErr + } + } + return nil } -// Copy *.mrseq (Most Recently executed Sequence number) files from old extension version to new extension version during update. -func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory string) error { +// Copy files like *.mrseq (Most Recently executed Sequence number), .status files from old extension version to new extension version during update. +func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory string) (*list.List, error) { newExtensionVersion := os.Getenv(constants.ExtensionVersionEnvName) oldExtensionVersion := os.Getenv(constants.ExtensionVersionUpdatingFromEnvName) @@ -368,13 +379,13 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if err != nil { errr := os.Mkdir(newExtensionDirectory, 0700) if errr != nil { - return errors.Wrap(errr, fmt.Sprintf("Failed to create directory '%s'", newExtensionDirectory)) + return nil, errors.Wrap(errr, fmt.Sprintf("Failed to create directory '%s'", newExtensionDirectory)) } } } if oldExtensionDirectory == "" || newExtensionDirectory == "" { - return errors.New("oldExtesionDirectory or newExtensionDirectory is empty") + return nil, errors.New("oldExtesionDirectory or newExtensionDirectory is empty") } // Check if the directory exists @@ -382,17 +393,18 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if err != nil { errMessage := fmt.Sprintf("could not open sourceDirectory %s", oldExtensionDirectory) ctx.Log("message", errMessage) - return errors.Wrap(err, errMessage) + return nil, errors.Wrap(err, errMessage) } directoryEntries, err := sourceDirectoryFDRef.ReadDir(0) if err != nil { errMessage := fmt.Sprintf("could not read directory entries from sourceDirectory %s", oldExtensionDirectory) ctx.Log("message", errMessage) - return errors.Wrap(err, errMessage) + return nil, errors.Wrap(err, errMessage) } numberOfFilesMigrated := 0 + fileNamesMigrated := list.New() for _, dirEntry := range directoryEntries { fileName := dirEntry.Name() @@ -405,7 +417,7 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if sourceFileOpenError != nil { errMessage := "Failed to open '%s' file '%s' for reading. Contact ICM team AzureRT\\Extensions for this service error." ctx.Log("message", fmt.Sprintf(errMessage, fileExtensionSuffix, sourceFileFullPath)) - return errors.Wrapf(sourceFileOpenError, errMessage) + return fileNamesMigrated, errors.Wrapf(sourceFileOpenError, errMessage) } defer sourceFile.Close() @@ -413,7 +425,7 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if destFileCreateError != nil { errMessage := "Failed to create '%s' file '%s'. Contact ICM team AzureRT\\Extensions for this service error." ctx.Log("message", fmt.Sprintf(errMessage, fileExtensionSuffix, destinationFileFullPath)) - return errors.Wrapf(destFileCreateError, errMessage) + return fileNamesMigrated, errors.Wrapf(destFileCreateError, errMessage) } defer destFile.Close() @@ -422,16 +434,89 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory errMessage := fmt.Sprintf("Failed to copy '%s' file '%s' to path '%s'. Contact ICM team AzureRT\\Extensions for this service error.", fileExtensionSuffix, sourceFileFullPath, destinationFileFullPath) ctx.Log("message", errMessage) - return errors.Wrapf(copyError, errMessage) + return fileNamesMigrated, errors.Wrapf(copyError, errMessage) } else { ctx.Log("message", fmt.Sprintf("File '%s' was copied successfully to '%s'", sourceFileFullPath, destinationFileFullPath)) numberOfFilesMigrated++ + fileNamesMigrated.PushBack(fileName) } } } ctx.Log("message", fmt.Sprintf("Migrated %d '%s' files from extension version '%s' to '%s'", numberOfFilesMigrated, fileExtensionSuffix, oldExtensionVersion, newExtensionVersion)) + return fileNamesMigrated, nil +} + +// This need to be only executed by Update operation +func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.List) error { + if mrseqFilesNameList == nil || mrseqFilesNameList.Len() <= 0 { + return nil + } + + // Create dummy status file for .mrseq file if status file is not available. + newExtensionDirectory := os.Getenv(constants.ExtensionPathEnvName) + statusFileDirectoryPath := filepath.Join(newExtensionDirectory, constants.StatusFileDirectory) + + var mrSeqFileName string + var mrSeqFileFullPath string + var extensionName string + var mrSeqFileExtensionIndex int + var statusFileName string + var statusFilePath string + for mreSeqFileNameElement := mrseqFilesNameList.Front(); mreSeqFileNameElement != nil; mreSeqFileNameElement = mreSeqFileNameElement.Next() { + mrSeqFileName = (mreSeqFileNameElement.Value).(string) + + // Read the most recently executed sequence number from the .mrseq file + mrSeqFileFullPath = filepath.Join(newExtensionDirectory, mrSeqFileName) + content, err := os.ReadFile(mrSeqFileFullPath) + if err != nil { + ctx.Log("error", fmt.Sprintf("Reading mrseq (Most Recently executed Sequence number) from file '%s' failed with error '%s'", mrSeqFileFullPath, err.Error())) + return err + } + + var mrseqNumber int + if content != nil { + mrseqNumberString := string(content) + mrseqNum, errr := strconv.Atoi(mrseqNumberString) + if errr != nil { + ctx.Log("error", fmt.Sprintf("mrseqNumberString to mrseqNumber conversion (string to int) of '%s' failed with error '%s'", mrseqNumberString, errr.Error())) + return errr + } else { + mrseqNumber = mrseqNum + } + + } else { + errorMessage := fmt.Sprintf("Empty .mrseq file content. No sequence number was found inside file '%s' ", mrSeqFileFullPath) + ctx.Log("error", errorMessage) + return errors.New(errorMessage) + } + + // Find extension name from the .mrseq file + mrSeqFileExtensionIndex = strings.Index(mrSeqFileName, constants.MrSeqFileExtension) + if mrSeqFileExtensionIndex == -1 { + return errors.New(fmt.Sprintf("Invalid mrseq file '%s'", mrSeqFileName)) + } + extensionName = mrSeqFileName[0:mrSeqFileExtensionIndex] + + // Determine status file name and status file path + statusFileName = fmt.Sprintf("%s.%d.status", extensionName, mrseqNumber) + statusFilePath = filepath.Join(statusFileDirectoryPath, statusFileName) + + // If status file path does not exist, create a dummy status file to prevent poll status timeouts for already executed Run Commands after upgrade. + if !handlersettings.DoesFileExist(statusFilePath) { + statusReport := types.NewStatusReport(types.StatusSuccess, "Enable", "The script has been executed. However, the real execution state, output, error are unknown.") + rootStatusJson, err := status.MarshalStatusReportIntoJson(statusReport, true) + if err != nil { + return errors.Wrapf(err, fmt.Sprintf("failed to marshal status report into json for status file '%s'", statusFilePath)) + } + + err = status.SaveStatusReport(statusFileDirectoryPath, extensionName, mrseqNumber, rootStatusJson) + if err != nil { + return errors.Wrapf(err, fmt.Sprintf("Failed to create a dummy status file '%s' as it was not existing for .mrseq file '%s'", statusFilePath, mrSeqFileFullPath)) + } + } + } return nil } diff --git a/internal/cmds/cmds_test.go b/internal/cmds/cmds_test.go index 222ad72..5ca8d86 100755 --- a/internal/cmds/cmds_test.go +++ b/internal/cmds/cmds_test.go @@ -51,11 +51,11 @@ func Test_CopyMrseqFiles_MrseqFilesAreCopied(t *testing.T) { files, _ := ioutil.ReadDir(currentExtensionVersionDirectory) require.Equal(t, 0, len(files)) - os.Create(filepath.Join(previousExtensionVersionDirectory, "1.mrseq")) - os.Create(filepath.Join(previousExtensionVersionDirectory, "ABCD.mrseq")) - os.Create(filepath.Join(previousExtensionVersionDirectory, "2345.mrseq")) - os.Create(filepath.Join(previousExtensionVersionDirectory, "RC0804_0.mrseq")) - os.Create(filepath.Join(previousExtensionVersionDirectory, "asdfsad.mrseq")) + createMrseqFile(filepath.Join(previousExtensionVersionDirectory, "1.mrseq"), "0", t) + createMrseqFile(filepath.Join(previousExtensionVersionDirectory, "ABCD.mrseq"), "1", t) + createMrseqFile(filepath.Join(previousExtensionVersionDirectory, "2345.mrseq"), "0", t) + createMrseqFile(filepath.Join(previousExtensionVersionDirectory, "RC0804_0.mrseq"), "5", t) + createMrseqFile(filepath.Join(previousExtensionVersionDirectory, "asdfsad.mrseq"), "20", t) os.Create(filepath.Join(previousExtensionVersionDirectory, "abc.txt")) // this should not be copied to currentExtensionVersionDirectory statusSubdirectory := "status" @@ -63,13 +63,11 @@ func Test_CopyMrseqFiles_MrseqFilesAreCopied(t *testing.T) { // Create previousStatusDirectory err = os.Mkdir(previousStatusDirectory, 0777) require.Nil(t, err) - os.Create(filepath.Join(previousStatusDirectory, "1.status")) - os.Create(filepath.Join(previousStatusDirectory, "ABCD.status")) - os.Create(filepath.Join(previousStatusDirectory, "2345.status")) - os.Create(filepath.Join(previousStatusDirectory, "RC0804_0.status")) - os.Create(filepath.Join(previousStatusDirectory, "asdfsad.status")) - os.Create(filepath.Join(previousStatusDirectory, "xyusfd.status")) - os.Create(filepath.Join(previousStatusDirectory, "234434534.status")) + + // Only two status files are available. The rest of the 3 status files should be created during Update operation which would have dummy status. + // Dummy status files would be created to prevent poll status timeouts for already executed Run Commands after upgrade. + os.Create(filepath.Join(previousStatusDirectory, "1.0.status")) + os.Create(filepath.Join(previousStatusDirectory, "ABCD.1.status")) os.Create(filepath.Join(previousStatusDirectory, "abc.cs")) // this should not be copied to currentExtensionVersionDirectory err = CopyStateForUpdate(log.NewContext(log.NewNopLogger())) @@ -93,12 +91,18 @@ func Test_CopyMrseqFiles_MrseqFilesAreCopied(t *testing.T) { currentStatusDirectory := filepath.Join(currentExtensionVersionDirectory, statusSubdirectory) files, _ = ioutil.ReadDir(currentStatusDirectory) - require.Equal(t, 7, len(files)) + require.Equal(t, 5, len(files)) for _, file := range files { require.True(t, strings.HasSuffix(file.Name(), ".status")) } } +func createMrseqFile(mrseqFilePath string, mrseqNum string, t *testing.T) { + os.Create(mrseqFilePath) + err := os.WriteFile(mrseqFilePath, []byte(mrseqNum), 0644) + require.Nil(t, err) +} + func Test_commandsExist(t *testing.T) { // we expect these subcommands to be handled expect := []string{"install", "enable", "disable", "uninstall", "update"} @@ -119,8 +123,8 @@ func Test_commands_shouldReportStatus(t *testing.T) { // these subcommands SHOULD report status require.True(t, Cmds["enable"].ShouldReportStatus, "enable should report status") - require.True(t, Cmds["disable"].ShouldReportStatus, "disable should report status") - require.True(t, Cmds["update"].ShouldReportStatus, "update should report status") + require.False(t, Cmds["disable"].ShouldReportStatus, "disable should report status") + require.False(t, Cmds["update"].ShouldReportStatus, "update should report status") } func Test_checkAndSaveSeqNum_fails(t *testing.T) { diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 8c8ec80..20fc5bf 100755 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -21,6 +21,10 @@ const ( ConfigFileExtension = ".settings" + MrSeqFileExtension = ".mrseq" + + StatusFileDirectory = "status" + // General failed exit code when extension provisioning fails due to service errors. FailedExitCodeGeneral = -1 diff --git a/internal/status/status.go b/internal/status/status.go old mode 100644 new mode 100755 index b9f9c66..6480d20 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -35,7 +35,7 @@ func ReportStatusToLocalFile(ctx *log.Context, hEnv types.HandlerEnvironment, me } ctx.Log("message", "reporting status by writing status file locally") - err = saveStatusReport(hEnv.HandlerEnvironment.StatusFolder, metadata.ExtName, metadata.SeqNum, rootStatusJson) + err = SaveStatusReport(hEnv.HandlerEnvironment.StatusFolder, metadata.ExtName, metadata.SeqNum, rootStatusJson) if err != nil { ctx.Log("event", "failed to save handler status", "error", err) return errors.Wrap(err, "failed to save handler status") @@ -48,7 +48,7 @@ func ReportStatusToLocalFile(ctx *log.Context, hEnv types.HandlerEnvironment, me // SaveStatusReport persists the status message to the specified status folder using the // sequence number. The operation consists of writing to a temporary file in the // same folder and moving it to the final destination for atomicity. -func saveStatusReport(statusFolder string, extName string, seqNo int, rootStatusJson []byte) error { +func SaveStatusReport(statusFolder string, extName string, seqNo int, rootStatusJson []byte) error { fn := fmt.Sprintf("%d.status", seqNo) // Support multiconfig extensions where status file name should be: extName.seqNo.status if extName != "" { @@ -77,6 +77,15 @@ func getRootStatusJson(ctx *log.Context, statusType types.StatusType, c types.Cm ctx.Log("message", "creating json to report status") statusReport := types.NewStatusReport(statusType, c.Name, msg) + b, err := MarshalStatusReportIntoJson(statusReport, indent) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal status report into json") + } + + return b, nil +} + +func MarshalStatusReportIntoJson(statusReport types.StatusReport, indent bool) ([]byte, error) { var b []byte var err error if indent { @@ -85,11 +94,7 @@ func getRootStatusJson(ctx *log.Context, statusType types.StatusType, c types.Cm b, err = json.Marshal(statusReport) } - if err != nil { - return nil, errors.Wrap(err, "failed to marshal status report into json") - } - - return b, nil + return b, err } func reportStatusToEndpoint(ctx *log.Context, hEnv types.HandlerEnvironment, metadata types.RCMetadata, statusType types.StatusType, c types.Cmd, msg string, reporter statusreporter.IGuestInformationServiceClient) error { diff --git a/internal/types/commands.go b/internal/types/commands.go old mode 100644 new mode 100755 index 869e99a..7e4cbc2 --- a/internal/types/commands.go +++ b/internal/types/commands.go @@ -31,8 +31,8 @@ func (command Cmd) InitializeFunctions(input CmdFunctions) Cmd { var ( CmdInstallTemplate = Cmd{Name: "Install", ShouldReportStatus: false, FailExitCode: 52} CmdEnableTemplate = Cmd{Name: "Enable", ShouldReportStatus: true, FailExitCode: 3} - CmdDisableTemplate = Cmd{Name: "Disable", ShouldReportStatus: true, FailExitCode: 3} - CmdUpdateTemplate = Cmd{Name: "Update", ShouldReportStatus: true, FailExitCode: 3} + CmdDisableTemplate = Cmd{Name: "Disable", ShouldReportStatus: false, FailExitCode: 3} + CmdUpdateTemplate = Cmd{Name: "Update", ShouldReportStatus: false, FailExitCode: 3} CmdUninstallTemplate = Cmd{Name: "Uninstall", ShouldReportStatus: false, FailExitCode: 3} CmdRunServiceTemplate = Cmd{Name: "RunService", ShouldReportStatus: true, FailExitCode: 3} From 68aaef1404a9c16dd49b239e2262a35046abcc24 Mon Sep 17 00:00:00 2001 From: Vivek Lingaiah Date: Tue, 20 Aug 2024 14:22:35 +0000 Subject: [PATCH 2/2] Address feedback --- internal/cmds/cmds.go | 58 +++++++++++++++++++++++++--------------- internal/types/status.go | 4 +++ 2 files changed, 40 insertions(+), 22 deletions(-) mode change 100644 => 100755 internal/types/status.go diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index bb54262..df332bc 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -349,10 +349,9 @@ func CopyStateForUpdate(ctx log.Logger) error { // If status file corresponding to a .mrseq file does not exist, create a dummy status file to prevent poll status timeouts for already executed Run Commands after upgrade. if mrseqFilesNameList != nil && mrseqFilesNameList.Len() > 0 { - createDummyStatusFilesErr := createDummyStatusFilesIfNeeded(ctx, mrseqFilesNameList) - if createDummyStatusFilesErr != nil { - return createDummyStatusFilesErr - } + // This is best effort - Do not return error if any case of failures. + // Worst case that could happen is poll status timeouts for those few cases where creating dummy status file failed for some reason. + createDummyStatusFilesIfNeeded(ctx, mrseqFilesNameList) } return nil @@ -464,38 +463,48 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis var mrSeqFileExtensionIndex int var statusFileName string var statusFilePath string + var errorMessage string + var err error + var content []byte + var allErr error = errors.New("Refer to all error messages above.") + for mreSeqFileNameElement := mrseqFilesNameList.Front(); mreSeqFileNameElement != nil; mreSeqFileNameElement = mreSeqFileNameElement.Next() { mrSeqFileName = (mreSeqFileNameElement.Value).(string) // Read the most recently executed sequence number from the .mrseq file mrSeqFileFullPath = filepath.Join(newExtensionDirectory, mrSeqFileName) - content, err := os.ReadFile(mrSeqFileFullPath) + content, err = os.ReadFile(mrSeqFileFullPath) if err != nil { - ctx.Log("error", fmt.Sprintf("Reading mrseq (Most Recently executed Sequence number) from file '%s' failed with error '%s'", mrSeqFileFullPath, err.Error())) - return err + errorMessage = fmt.Sprintf("Reading mrseq (Most Recently executed Sequence number) from file '%s' failed with error '%s'", mrSeqFileFullPath, err.Error()) + ctx.Log("error", errorMessage) + allErr = errors.Wrap(allErr, errorMessage) + continue } var mrseqNumber int if content != nil { mrseqNumberString := string(content) - mrseqNum, errr := strconv.Atoi(mrseqNumberString) - if errr != nil { - ctx.Log("error", fmt.Sprintf("mrseqNumberString to mrseqNumber conversion (string to int) of '%s' failed with error '%s'", mrseqNumberString, errr.Error())) - return errr - } else { - mrseqNumber = mrseqNum + mrseqNumber, err = strconv.Atoi(mrseqNumberString) + if err != nil { + errorMessage = fmt.Sprintf("mrseqNumberString to mrseqNumber conversion (string to int) of '%s' failed with error '%s'", mrseqNumberString, err.Error()) + ctx.Log("error", errorMessage) + allErr = errors.Wrap(allErr, errorMessage) + continue } - } else { - errorMessage := fmt.Sprintf("Empty .mrseq file content. No sequence number was found inside file '%s' ", mrSeqFileFullPath) + errorMessage = fmt.Sprintf("Empty .mrseq file content. No sequence number was found inside file '%s' ", mrSeqFileFullPath) ctx.Log("error", errorMessage) - return errors.New(errorMessage) + allErr = errors.Wrap(allErr, errorMessage) + continue } // Find extension name from the .mrseq file mrSeqFileExtensionIndex = strings.Index(mrSeqFileName, constants.MrSeqFileExtension) if mrSeqFileExtensionIndex == -1 { - return errors.New(fmt.Sprintf("Invalid mrseq file '%s'", mrSeqFileName)) + errorMessage = fmt.Sprintf("Invalid mrseq file '%s'", mrSeqFileName) + ctx.Log("error", errorMessage) + allErr = errors.Wrap(allErr, errorMessage) + continue } extensionName = mrSeqFileName[0:mrSeqFileExtensionIndex] @@ -503,21 +512,26 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis statusFileName = fmt.Sprintf("%s.%d.status", extensionName, mrseqNumber) statusFilePath = filepath.Join(statusFileDirectoryPath, statusFileName) + var rootStatusJson []byte // If status file path does not exist, create a dummy status file to prevent poll status timeouts for already executed Run Commands after upgrade. if !handlersettings.DoesFileExist(statusFilePath) { - statusReport := types.NewStatusReport(types.StatusSuccess, "Enable", "The script has been executed. However, the real execution state, output, error are unknown.") - rootStatusJson, err := status.MarshalStatusReportIntoJson(statusReport, true) + statusReport := types.NewStatusReport(types.StatusWarning, "Enable", "The script has been executed. However, the execution state, output, error are unknown.") + rootStatusJson, err = status.MarshalStatusReportIntoJson(statusReport, true) if err != nil { - return errors.Wrapf(err, fmt.Sprintf("failed to marshal status report into json for status file '%s'", statusFilePath)) + errorMessage = fmt.Sprintf("failed to marshal status report into json for status file '%s' with error '%s'", statusFilePath, err.Error()) + allErr = errors.Wrap(allErr, errorMessage) + continue } err = status.SaveStatusReport(statusFileDirectoryPath, extensionName, mrseqNumber, rootStatusJson) if err != nil { - return errors.Wrapf(err, fmt.Sprintf("Failed to create a dummy status file '%s' as it was not existing for .mrseq file '%s'", statusFilePath, mrSeqFileFullPath)) + errorMessage = fmt.Sprintf("Failed to create a dummy status file '%s' as it was not existing for .mrseq file '%s' with error '%s'", statusFilePath, mrSeqFileFullPath, err.Error()) + allErr = errors.Wrap(allErr, errorMessage) + continue } } } - return nil + return allErr } // downloadScript downloads the script file specified in cfg into dir (creates if does diff --git a/internal/types/status.go b/internal/types/status.go old mode 100644 new mode 100755 index b6ef85b..dfcd002 --- a/internal/types/status.go +++ b/internal/types/status.go @@ -40,6 +40,10 @@ const ( // StatusSuccess indicates the operation succeeded StatusSuccess StatusType = "success" + + // StatusWarning indicates the operation was executed, but with one of the below conditions: + // 1) Status files have been lost. So, exact execution status (error or success), output and error are not known. + StatusWarning StatusType = "warning" ) // Status is used for serializing status in a manner the server understands