From e6aa4f58a5932a0048bbf45c9a4c93e3cedae0f2 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Fri, 18 Jul 2025 12:39:37 -0700 Subject: [PATCH 1/7] Adds extension events --- go.mod | 2 +- go.sum | 2 + internal/cmds/cmds.go | 141 ++++++++++++++---- internal/cmds/cmds_test.go | 12 +- internal/commandProcessor/commandProcessor.go | 5 + internal/immediatecmds/immediatecmds.go | 47 ++++-- internal/service/serviceinstall.go | 27 +++- internal/types/handlerenvironment.go | 14 +- 8 files changed, 198 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index e052aea..a5aa63a 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.5 require ( github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c - github.com/Azure/azure-extension-platform v0.0.0-20240610175536-404c704f82f8 + github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f github.com/Azure/azure-sdk-for-go v68.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 diff --git a/go.sum b/go.sum index 648c9c4..a08a5ae 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c h github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c/go.mod h1:sNC6lMTUkXwjrQ+nttr6GXhDfvSGT7t3UDq30BEYzu8= github.com/Azure/azure-extension-platform v0.0.0-20240610175536-404c704f82f8 h1:4AgLx0eXWAzh4nL7eBzwxoQaZEk5Hp2Ilq33YwYzEos= github.com/Azure/azure-extension-platform v0.0.0-20240610175536-404c704f82f8/go.mod h1:nEQQIC3RKmMnpdc+RakYHIdu556jdcHv67ML8PdsQeQ= +github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f h1:ddsUz/suc9txCMz/xWOslqNMvzhbWFMTflUrbcMNoSw= +github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index ca6aad4..888bb1a 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -7,6 +7,7 @@ import ( "container/list" "context" "encoding/base64" + "encoding/json" "fmt" "io" "os" @@ -15,6 +16,9 @@ import ( "strings" "time" + "github.com/Azure/azure-extension-platform/pkg/extensionevents" + "github.com/Azure/azure-extension-platform/pkg/handlerenv" + "github.com/Azure/azure-extension-platform/pkg/logging" "github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/appendblob" @@ -74,12 +78,13 @@ var ( ) func update(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCommandInstanceView, metadata types.RCMetadata, c types.Cmd) (string, string, error, int) { - exitCode, err := immediatecmds.Update(ctx, h, metadata.ExtName, metadata.SeqNum) + extensionEvents := createExtensionEventManager(ctx, h) + exitCode, err := immediatecmds.Update(ctx, h, metadata.ExtName, metadata.SeqNum, extensionEvents) if err != nil { return "", "", err, exitCode } - err = rehydrateMrSeqFilesForProblematicUpgrades(ctx, h) + err = rehydrateMrSeqFilesForProblematicUpgrades(ctx, h, extensionEvents) if err != nil { // If we fail on update, then there's a risk we could re-execute the customer's script. Don't take that chance. // By failing Update, the extension goal state will fail. WALA will try us again on the next goal state. @@ -89,7 +94,7 @@ func update(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma // Copy any .mrseq or .status files -Most Recently executed Sequence number files and status files for Run Commands from old version to new version. // This is necessary to prevent rerunning of already executed Run Commands after upgrade of extension version, and also return their statuses. - copyError := CopyStateForUpdate(ctx) + copyError := CopyStateForUpdate(ctx, extensionEvents) if copyError != nil { return "", "", errors.Wrap(copyError, "Migrating *.mrseq or .status files failed during update."), constants.ExitCode_CopyStateForUpdateFailed } @@ -99,14 +104,15 @@ func update(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma } func disable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCommandInstanceView, metadata types.RCMetadata, c types.Cmd) (string, string, error, int) { + extensionEvents := createExtensionEventManager(ctx, h) extensionHandlerName := commandProcessor.GetExtensionName(ctx) if extensionHandlerName == constants.ImmediateRunCommandHandlerName { - exitCode, err := immediatecmds.Disable(ctx, h, metadata.ExtName, metadata.SeqNum) + exitCode, err := immediatecmds.Disable(ctx, h, metadata.ExtName, metadata.SeqNum, extensionEvents) if err != nil { // Remove the mrseq file for the extension. For RunCommands that are called from the Guest Agent, it will delete these files for us // if the extension is actually being deleted, and keep them for an update. However, in IRC we're not called by Guest Agent, so we // need to delete them ourself. - resetSeqNum(ctx, metadata.MostRecentSequence) + resetSeqNum(ctx, metadata.MostRecentSequence, extensionEvents) return "", "", err, exitCode } } @@ -117,22 +123,26 @@ func disable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComm } func install(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCommandInstanceView, metadata types.RCMetadata, c types.Cmd) (string, string, error, int) { + extensionEvents := createExtensionEventManager(ctx, h) exitCode, err := immediatecmds.Install() if err != nil { return "", "", err, exitCode } if err := os.MkdirAll(constants.DataDir, 0755); err != nil { + extensionEvents.LogErrorEvent("install", "Failed to create data dir") return "", "", errors.Wrap(err, "failed to create data dir"), constants.ExitCode_CreateDataDirectoryFailed } ctx.Log("event", "created data dir", "path", constants.DataDir) ctx.Log("event", "installed") + extensionEvents.LogInformationalEvent("uninstall", "created data dir") return "", "", nil, constants.ExitCode_Okay } func uninstall(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCommandInstanceView, metadata types.RCMetadata, c types.Cmd) (string, string, error, int) { - exitCode, err := immediatecmds.Uninstall(ctx, h, metadata.ExtName, metadata.SeqNum) + extensionEvents := createExtensionEventManager(ctx, h) + exitCode, err := immediatecmds.Uninstall(ctx, h, metadata.ExtName, metadata.SeqNum, extensionEvents) if err != nil { return "", "", err, exitCode } @@ -141,9 +151,11 @@ func uninstall(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCo ctx = ctx.With("path", constants.DataDir) ctx.Log("event", "removing data dir", "path", constants.DataDir) if err := os.RemoveAll(constants.DataDir); err != nil { + extensionEvents.LogErrorEvent("uninstall", "failed to delete data directory") return "", "", errors.Wrap(err, "failed to delete data directory"), constants.ExitCode_RemoveDataDirectoryFailed } ctx.Log("event", "removed data dir") + extensionEvents.LogInformationalEvent("uninstall", "removed data dir") } ctx.Log("event", "uninstalled") return "", "", nil, constants.ExitCode_Okay @@ -168,13 +180,17 @@ func enablePre(ctx *log.Context, h types.HandlerEnvironment, metadata types.RCMe } func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCommandInstanceView, metadata types.RCMetadata, c types.Cmd) (string, string, error, int) { + extensionEvents := createExtensionEventManager(ctx, h) + // parse the extension handler settings (not available prior to 'enable') cfg, err1 := handlersettings.GetHandlerSettings(h.HandlerEnvironment.ConfigFolder, metadata.ExtName, metadata.SeqNum, ctx) if err1 != nil { + errMessage := fmt.Sprintf("Failed to get configuration: %v", err1) + extensionEvents.LogErrorEvent("enable", errMessage) return "", "", errors.Wrap(err1, "failed to get configuration"), constants.ExitCode_GetHandlerSettingsFailed } - exitCode, err := immediatecmds.Enable(ctx, h, metadata.ExtName, metadata.SeqNum, cfg) + exitCode, err := immediatecmds.Enable(ctx, h, metadata.ExtName, metadata.SeqNum, cfg, extensionEvents) // If there is an error or the customer requested to install the script as a service, return the error and exit code immediately. if err != nil || cfg.InstallAsService() { @@ -184,6 +200,8 @@ func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma dir := filepath.Join(metadata.DownloadPath, fmt.Sprintf("%d", metadata.SeqNum)) scriptFilePath, err := downloadScript(ctx, dir, &cfg) if err != nil { + errMessage := fmt.Sprintf("Failed to download script: %v", err) + extensionEvents.LogErrorEvent("enable", errMessage) return "", "", errors.Wrap(err, fmt.Sprintf("File downloads failed. Use either a public script URI that points to .sh file, Azure storage blob SAS URI or storage blob accessible by a managed identity and retry. If managed identity is used, make sure it has been given access to container of storage blob '%s' with 'Storage Blob Data Reader' role assignment. In case of user-assigned identity, make sure you add it under VM's identity. For more info, refer https://aka.ms/RunCommandManagedLinux", download.GetUriForLogging(cfg.ScriptURI()))), @@ -192,6 +210,8 @@ func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma err = downloadArtifacts(ctx, dir, &cfg) if err != nil { + errMessage := fmt.Sprintf("Failed to download artifacts: %v", err) + extensionEvents.LogErrorEvent("enable", errMessage) return "", "", errors.Wrap(err, "Artifact downloads failed. Use either a public artifact URI that points to .sh file, Azure storage blob SAS URI, or storage blob accessible by a managed identity and retry."), constants.ExitCode_DownloadArtifactFailed @@ -295,6 +315,28 @@ func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma return stdoutTail, stderrTail, runErr, exitCode } +func createExtensionEventManager(ctx *log.Context, hEnv types.HandlerEnvironment) *extensionevents.ExtensionEventManager { + el := logging.New(nil) + platformHandlerEnv := convertToPlatformHandlerEnv((hEnv)) + extensionEvents := extensionevents.New(el, platformHandlerEnv) + return extensionEvents +} + +func convertToPlatformHandlerEnv(myEnv types.HandlerEnvironment) *handlerenv.HandlerEnvironment { + data, err := json.Marshal(myEnv.HandlerEnvironment) + if err != nil { + return nil + } + + var result handlerenv.HandlerEnvironment + err = json.Unmarshal(data, &result) + if err != nil { + return nil + } + + return &result +} + // appendToBlob saves a file (from seeking position to the end of the file) to AppendBlob. Returns the new position (end of the file) func appendToBlob(sourceFilePath string, appendBlobRef *storage.Blob, appendBlobClient *appendblob.Client, outputFilePosition int64, ctx *log.Context) (int64, error) { var err error @@ -363,21 +405,23 @@ func checkAndSaveSeqNum(ctx log.Logger, seq int, mrseqPath string) (shouldExit b } // resetSeqNum deletes the seqNum file to reset the sequence number -func resetSeqNum(ctx log.Logger, mrseqPath string) { - ctx.Log("event", "resetting seqnum by deleting file", "path", mrseqPath) +func resetSeqNum(ctx log.Logger, mrseqPath string, extensionEvents *extensionevents.ExtensionEventManager) { + message := fmt.Sprintf("Resetting seqnum by deleting file '%s'", mrseqPath) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("resetseqnum", message) os.Remove(mrseqPath) } // Copy state of the extension from old version to new version during update (.mrseq files, .status files) -func CopyStateForUpdate(ctx log.Logger) error { +func CopyStateForUpdate(ctx log.Logger, extensionEvents *extensionevents.ExtensionEventManager) 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. - mrseqFilesNameList, mrseqFileCopyErr := copyFiles(ctx, constants.MrSeqFileExtension, "") + mrseqFilesNameList, mrseqFileCopyErr := copyFiles(ctx, constants.MrSeqFileExtension, "", extensionEvents) if mrseqFileCopyErr != nil { return mrseqFileCopyErr } // Copy .status files of already executed sequence numbers - _, statusFileCopyErr := copyFiles(ctx, ".status", constants.StatusFileDirectory) + _, statusFileCopyErr := copyFiles(ctx, ".status", constants.StatusFileDirectory, extensionEvents) if statusFileCopyErr != nil { return statusFileCopyErr } @@ -386,13 +430,13 @@ func CopyStateForUpdate(ctx log.Logger) error { if mrseqFilesNameList != nil && mrseqFilesNameList.Len() > 0 { // 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) + createDummyStatusFilesIfNeeded(ctx, mrseqFilesNameList, extensionEvents) } return nil } -func rehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, h types.HandlerEnvironment) error { +func rehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, h types.HandlerEnvironment, extensionEvents *extensionevents.ExtensionEventManager) error { // First, determine whether we're upgrading from a 'problematic' version, defined as one // where we mistakenly deleted the mrseq files in the Disable call newExtensionVersion := os.Getenv(constants.ExtensionVersionEnvName) @@ -412,21 +456,27 @@ func rehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, h types.Handler } if isProblematicVersion { - ctx.Log("message", fmt.Sprintf("Rehydrating mrseq files deleted by from version '%s' using status files", oldExtensionVersion)) - return doRehydrateMrSeqFilesForProblematicUpgrades(ctx, oldExtensionDirectory, newExtensionDirectory) + message := fmt.Sprintf("Rehydrating mrseq files deleted by from version '%s' using status files", oldExtensionVersion) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("rehydratemrseq", message) + return doRehydrateMrSeqFilesForProblematicUpgrades(ctx, oldExtensionDirectory, newExtensionDirectory, extensionEvents) } else { - ctx.Log("message", fmt.Sprintf("Previous extension version '%s' does not require mrseq hydration", oldExtensionVersion)) + message := fmt.Sprintf("Previous extension version '%s' does not require mrseq hydration", oldExtensionVersion) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("rehydratemrseq", message) } return nil } -func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionDirectory string, newExtensionDirectory string) error { +func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionDirectory string, newExtensionDirectory string, extensionEvents *extensionevents.ExtensionEventManager) error { oldExtensionStatusDirectory := filepath.Join(oldExtensionDirectory, constants.StatusFileDirectory) extensionStatusDirectoryFDRef, err := os.Open(oldExtensionStatusDirectory) if err != nil { - return errors.Wrap(err, fmt.Sprintf("Failed to open status directory '%s'", oldExtensionStatusDirectory)) + errMessage := fmt.Sprintf("Failed to open status directory '%s'", oldExtensionStatusDirectory) + extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) + return errors.Wrap(err, errMessage) } defer extensionStatusDirectoryFDRef.Close() @@ -437,6 +487,7 @@ func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionD if err != nil { errMessage := fmt.Sprintf("could not read directory entries from status directory %s", oldExtensionDirectory) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) return errors.Wrap(err, errMessage) } @@ -457,17 +508,23 @@ func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionD _, err = os.Stat(mrSeqFilePath) if err != nil { if errors.Is(err, os.ErrNotExist) { - ctx.Log("message", fmt.Sprintf("Rehydrating mrseq file for '%s' because it was mistakenly deleted during disable", extensionName)) + message := fmt.Sprintf("Rehydrating mrseq file for '%s' because it was mistakenly deleted during disable", extensionName) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("rehydratemrseq", message) err = os.WriteFile(mrSeqFilePath, []byte(seqNo), os.FileMode(0600)) if err != nil { errMessage := fmt.Sprintf("Could not write file '%s'", mrSeqFilePath) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) return errors.Wrap(err, errMessage) } - ctx.Log("message", fmt.Sprintf("Successfully rehydrated mrseq file for '%s' with seqNo '%s'. File location '%s'", extensionName, seqNo, mrSeqFilePath)) + message = fmt.Sprintf("Successfully rehydrated mrseq file for '%s' with seqNo '%s'. File location '%s'", extensionName, seqNo, mrSeqFilePath) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("rehydratemrseq", message) } else { errMessage := fmt.Sprintf("Could not access file '%s' even though it exists", mrSeqFilePath) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) return errors.Wrap(err, errMessage) } } else { @@ -476,6 +533,7 @@ func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionD if err != nil { errMessage := fmt.Sprintf("Could not read file '%s'", mrSeqFilePath) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) return errors.Wrap(err, errMessage) } @@ -485,9 +543,12 @@ func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionD if err != nil { errMessage := fmt.Sprintf("Could not write file '%s'", mrSeqFilePath) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) return errors.Wrap(err, errMessage) } - ctx.Log("message", fmt.Sprintf("Updated mrseq file for '%s' with seqNo '%s'. File location '%s'", extensionName, seqNo, mrSeqFilePath)) + message := fmt.Sprintf("Updated mrseq file for '%s' with seqNo '%s'. File location '%s'", extensionName, seqNo, mrSeqFilePath) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("rehydratemrseq", message) } } } @@ -498,12 +559,14 @@ func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionD } // 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) { +func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory string, extensionEvents *extensionevents.ExtensionEventManager) (*list.List, error) { newExtensionVersion := os.Getenv(constants.ExtensionVersionEnvName) oldExtensionVersion := os.Getenv(constants.ExtensionVersionUpdatingFromEnvName) - ctx.Log("message", fmt.Sprintf("Migrating '%s' files from extension version '%s' to '%s'", fileExtensionSuffix, oldExtensionVersion, newExtensionVersion)) + message := fmt.Sprintf("Migrating '%s' files from extension version '%s' to '%s'", fileExtensionSuffix, oldExtensionVersion, newExtensionVersion) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("copyfiles", message) newExtensionDirectory := os.Getenv(constants.ExtensionPathEnvName) oldExtensionDirectory := strings.ReplaceAll(newExtensionDirectory, newExtensionVersion, oldExtensionVersion) @@ -518,13 +581,17 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if err != nil { errr := os.Mkdir(newExtensionDirectory, 0700) if errr != nil { - return nil, errors.Wrap(errr, fmt.Sprintf("Failed to create directory '%s'", newExtensionDirectory)) + errMessage := fmt.Sprintf("Failed to create directory '%s'", newExtensionDirectory) + extensionEvents.LogErrorEvent("copyfiles", errMessage) + return nil, errors.Wrap(errr, errMessage) } } } if oldExtensionDirectory == "" || newExtensionDirectory == "" { - return nil, errors.New("oldExtesionDirectory or newExtensionDirectory is empty") + errMessage := "oldExtesionDirectory or newExtensionDirectory is empty" + extensionEvents.LogErrorEvent("copyfiles", errMessage) + return nil, errors.New(errMessage) } // Check if the directory exists @@ -532,6 +599,7 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if err != nil { errMessage := fmt.Sprintf("could not open sourceDirectory %s", oldExtensionDirectory) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("copyfiles", errMessage) return nil, errors.Wrap(err, errMessage) } @@ -539,6 +607,7 @@ func copyFiles(ctx log.Logger, fileExtensionSuffix string, extensionSubdirectory if err != nil { errMessage := fmt.Sprintf("could not read directory entries from sourceDirectory %s", oldExtensionDirectory) ctx.Log("message", errMessage) + extensionEvents.LogErrorEvent("copyfiles", errMessage) return nil, errors.Wrap(err, errMessage) } @@ -556,6 +625,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)) + extensionEvents.LogErrorEvent("copyfiles", errMessage) return fileNamesMigrated, errors.Wrapf(sourceFileOpenError, errMessage) } defer sourceFile.Close() @@ -564,6 +634,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)) + extensionEvents.LogErrorEvent("copyfiles", errMessage) return fileNamesMigrated, errors.Wrapf(destFileCreateError, errMessage) } defer destFile.Close() @@ -573,22 +644,27 @@ 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) + extensionEvents.LogErrorEvent("copyfiles", errMessage) return fileNamesMigrated, errors.Wrapf(copyError, errMessage) } else { - ctx.Log("message", fmt.Sprintf("File '%s' was copied successfully to '%s'", sourceFileFullPath, destinationFileFullPath)) + message := fmt.Sprintf("File '%s' was copied successfully to '%s'", sourceFileFullPath, destinationFileFullPath) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("copyfiles", message) numberOfFilesMigrated++ fileNamesMigrated.PushBack(fileName) } } } - ctx.Log("message", fmt.Sprintf("Migrated %d '%s' files from extension version '%s' to '%s'", numberOfFilesMigrated, fileExtensionSuffix, oldExtensionVersion, newExtensionVersion)) + message = fmt.Sprintf("Migrated %d '%s' files from extension version '%s' to '%s'", numberOfFilesMigrated, fileExtensionSuffix, oldExtensionVersion, newExtensionVersion) + ctx.Log("message", message) + extensionEvents.LogInformationalEvent("copyfiles", message) return fileNamesMigrated, nil } // This need to be only executed by Update operation -func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.List) error { +func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.List, extensionEvents *extensionevents.ExtensionEventManager) error { if mrseqFilesNameList == nil || mrseqFilesNameList.Len() <= 0 { return nil } @@ -618,6 +694,7 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis if err != nil { errorMessage = fmt.Sprintf("Reading mrseq (Most Recently executed Sequence number) from file '%s' failed with error '%s'", mrSeqFileFullPath, err.Error()) ctx.Log("error", errorMessage) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } @@ -629,12 +706,14 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis 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) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } } else { errorMessage = fmt.Sprintf("Empty .mrseq file content. No sequence number was found inside file '%s' ", mrSeqFileFullPath) ctx.Log("error", errorMessage) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } @@ -644,6 +723,7 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis if mrSeqFileExtensionIndex == -1 { errorMessage = fmt.Sprintf("Invalid mrseq file '%s'", mrSeqFileName) ctx.Log("error", errorMessage) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } @@ -670,6 +750,7 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis instanceViewMessage, err = instanceview.SerializeInstanceView(&instanceView) if err != nil { errorMessage = fmt.Sprintf("Failed to serialize unknown instanceView, error is '%s'", err.Error()) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } @@ -678,6 +759,7 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis rootStatusJson, err = status.MarshalStatusReportIntoJson(statusReport, true) if err != nil { errorMessage = fmt.Sprintf("failed to marshal status report into json for status file '%s' with error '%s'", statusFilePath, err.Error()) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } @@ -685,6 +767,7 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis err = status.SaveStatusReport(statusFileDirectoryPath, extensionName, mrseqNumber, rootStatusJson) if err != nil { 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()) + extensionEvents.LogErrorEvent("createdummystatusfiles", errorMessage) allErr = errors.Wrap(allErr, errorMessage) continue } diff --git a/internal/cmds/cmds_test.go b/internal/cmds/cmds_test.go index 164414f..329ccd0 100755 --- a/internal/cmds/cmds_test.go +++ b/internal/cmds/cmds_test.go @@ -7,11 +7,15 @@ import ( "net/http" "net/http/httptest" "os" + "path" "path/filepath" "strconv" "strings" "testing" + "github.com/Azure/azure-extension-platform/pkg/extensionevents" + "github.com/Azure/azure-extension-platform/pkg/handlerenv" + "github.com/Azure/azure-extension-platform/pkg/logging" "github.com/Azure/run-command-handler-linux/internal/constants" "github.com/Azure/run-command-handler-linux/internal/files" "github.com/Azure/run-command-handler-linux/internal/handlersettings" @@ -74,7 +78,13 @@ func Test_CopyMrseqFiles_MrseqFilesAreCopied(t *testing.T) { 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())) + handlerEnvironment := handlerenv.HandlerEnvironment{ + EventsFolder: path.Join(currentExtensionVersionDirectory, "events"), + } + + extensionLogger := logging.New(nil) + extensionEventManager := extensionevents.New(extensionLogger, &handlerEnvironment) + err = CopyStateForUpdate(log.NewContext(log.NewNopLogger()), extensionEventManager) require.Nil(t, err) files, _ = ioutil.ReadDir(currentExtensionVersionDirectory) diff --git a/internal/commandProcessor/commandProcessor.go b/internal/commandProcessor/commandProcessor.go index 91bf3c1..19cab6e 100644 --- a/internal/commandProcessor/commandProcessor.go +++ b/internal/commandProcessor/commandProcessor.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/Azure/azure-extension-platform/pkg/handlerenv" "github.com/Azure/azure-extension-platform/pkg/logging" "github.com/Azure/run-command-handler-linux/internal/constants" "github.com/Azure/run-command-handler-linux/internal/handlersettings" @@ -19,6 +20,10 @@ import ( "github.com/pkg/errors" ) +var ( + handlerEnvironmentGetter func(name, version string) (he *handlerenv.HandlerEnvironment, _ error) = handlerenv.GetHandlerEnvironment +) + func ProcessImmediateHandlerCommand(cmd types.Cmd, hs handlersettings.HandlerSettingsFile, extensionName string, seqNum int) error { ctx := initializeLogger(cmd) ctx = ctx.With("extensionName", extensionName) diff --git a/internal/immediatecmds/immediatecmds.go b/internal/immediatecmds/immediatecmds.go index 3ea6898..378eb7f 100644 --- a/internal/immediatecmds/immediatecmds.go +++ b/internal/immediatecmds/immediatecmds.go @@ -1,6 +1,9 @@ package immediatecmds import ( + "fmt" + + "github.com/Azure/azure-extension-platform/pkg/extensionevents" "github.com/Azure/run-command-handler-linux/internal/constants" "github.com/Azure/run-command-handler-linux/internal/handlersettings" "github.com/Azure/run-command-handler-linux/internal/service" @@ -11,16 +14,20 @@ import ( // Updates the service definition if any immediate run command service exists. // The action is skipped if the service has already been upgraded. -func Update(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int) (int, error) { +func Update(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int, extensionEvents *extensionevents.ExtensionEventManager) (int, error) { ctx.Log("message", "updating immediate run command") isInstalled, err := service.IsInstalled(ctx) if err != nil { + errMessage := fmt.Sprintf("Failed to check if any runcommand service is installed: %v", err) + extensionEvents.LogErrorEvent("immediateupdate", errMessage) return constants.ExitCode_CreateDataDirectoryFailed, errors.Wrap(err, "failed to check if any runcommand service is installed") } if isInstalled { - err = service.Register(ctx) + err = service.Register(ctx, extensionEvents) if err != nil { + errMessage := fmt.Sprintf("Failed to upgrade run command service: %v", err) + extensionEvents.LogErrorEvent("immediateupdate", errMessage) return constants.ExitCode_UpgradeInstalledServiceFailed, errors.Wrap(err, "failed to upgrade run command service") } } @@ -28,25 +35,32 @@ func Update(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum return constants.ExitCode_Okay, nil } -func Disable(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int) (int, error) { +func Disable(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int, extensionEvents *extensionevents.ExtensionEventManager) (int, error) { isInstalled, err := service.IsInstalled(ctx) if err != nil { + errMessage := fmt.Sprintf("Failed to check if runcommand service is installed: %v", err) + extensionEvents.LogErrorEvent("immediatedisable", errMessage) return constants.ExitCode_DisableInstalledServiceFailed, errors.Wrap(err, "failed to check if runcommand service is installed") } if isInstalled { isEnabled, err := service.IsEnabled(ctx) if err != nil { + errMessage := fmt.Sprintf("Failed to check if service is enabled: %v", err) + extensionEvents.LogErrorEvent("immediatedisable", errMessage) return constants.ExitCode_InstallServiceFailed, errors.Wrap(err, "failed to check if service is enabled") } if isEnabled { - err := service.Disable(ctx) + err := service.Disable(ctx, extensionEvents) if err != nil { + errMessage := fmt.Sprintf("Failed to disable run command service: %v", err) + extensionEvents.LogErrorEvent("immediatedisable", errMessage) return constants.ExitCode_DisableInstalledServiceFailed, errors.Wrap(err, "failed to disable run command service") } } else { ctx.Log("message", "Service installed but already got disabled. Skipping request to disable") + extensionEvents.LogInformationalEvent("immediatedisable", "Service installed but already got disabled. Skipping request to disable") } } @@ -58,51 +72,64 @@ func Install() (int, error) { return constants.ExitCode_Okay, nil } -func Uninstall(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int) (int, error) { +func Uninstall(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int, extensionEvents *extensionevents.ExtensionEventManager) (int, error) { ctx.Log("message", "proceeding to uninstall immediate run command") isInstalled, err := service.IsInstalled(ctx) if err != nil { + errMessage := fmt.Sprintf("Failed to check if runcommand service is installed: %v", err) + extensionEvents.LogErrorEvent("immediatedisable", errMessage) return constants.ExitCode_RemoveDataDirectoryFailed, errors.Wrap(err, "failed to check if runcommand service is installed") } if isInstalled { - error := service.DeRegister(ctx) + error := service.DeRegister(ctx, extensionEvents) if error != nil { + errMessage := fmt.Sprintf("Failed to uninstall run command service: %v", error) + extensionEvents.LogErrorEvent("immediatedisable", errMessage) return constants.ExitCode_UninstallInstalledServiceFailed, errors.Wrap(err, "failed to uninstall run command service") } } return constants.ExitCode_Okay, nil } -func Enable(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int, cfg handlersettings.HandlerSettings) (int, error) { +func Enable(ctx *log.Context, h types.HandlerEnvironment, extName string, seqNum int, cfg handlersettings.HandlerSettings, extensionEvents *extensionevents.ExtensionEventManager) (int, error) { // If installService == true, then install RunCommand as a service if cfg.InstallAsService() { isInstalled, err2 := service.IsInstalled(ctx) if err2 != nil { ctx.Log("message", "could not check if service is already installed. Proceeding to overwrite configuration file to make sure it gets installed.") + extensionEvents.LogErrorEvent("immediateenable", "could not check if service is already installed. Proceeding to overwrite configuration file to make sure it gets installed.") } if !isInstalled { - err3 := service.Register(ctx) + err3 := service.Register(ctx, extensionEvents) if err3 != nil { + errMessage := fmt.Sprintf("Failed to install RunCommand as a service: %v", err3) + extensionEvents.LogErrorEvent("immediateenable", errMessage) return constants.ExitCode_InstallServiceFailed, errors.Wrap(err3, "failed to install RunCommand as a service") } } else { isEnabled, err3 := service.IsEnabled(ctx) if err3 != nil { + errMessage := fmt.Sprintf("Failed to check if service is already enabled: %v", err3) + extensionEvents.LogErrorEvent("immediateenable", errMessage) return constants.ExitCode_InstallServiceFailed, errors.Wrap(err3, "failed to check if service is already enabled") } if !isEnabled { - err4 := service.Enable(ctx) + err4 := service.Enable(ctx, extensionEvents) if err4 != nil { + errMessage := fmt.Sprintf("Failed to enable service: %v", err4) + extensionEvents.LogErrorEvent("immediateenable", errMessage) return constants.ExitCode_InstallServiceFailed, errors.Wrap(err4, "failed to enable service") } - err5 := service.Start(ctx) + err5 := service.Start(ctx, extensionEvents) if err5 != nil { + errMessage := fmt.Sprintf("Failed to start service: %v", err5) + extensionEvents.LogErrorEvent("immediateenable", errMessage) return constants.ExitCode_InstallServiceFailed, errors.Wrap(err5, "failed to start service") } } diff --git a/internal/service/serviceinstall.go b/internal/service/serviceinstall.go index c041f35..1014e2f 100644 --- a/internal/service/serviceinstall.go +++ b/internal/service/serviceinstall.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/Azure/azure-extension-platform/pkg/extensionevents" "github.com/Azure/run-command-handler-linux/internal/constants" "github.com/Azure/run-command-handler-linux/pkg/servicehandler" "github.com/Azure/run-command-handler-linux/pkg/systemd" @@ -33,8 +34,9 @@ StandardError=append:%run_command_output_directory% WantedBy=multi-user.target` ) -func Register(ctx *log.Context) error { +func Register(ctx *log.Context, extensionEvents *extensionevents.ExtensionEventManager) error { if !isSystemdSupported(ctx) { + extensionEvents.LogErrorEvent("register", "Systemd not supported. Failed to register service") return errors.New("Systemd not supported. Failed to register service") } targetVersion := os.Getenv(constants.ExtensionVersionEnvName) @@ -68,6 +70,8 @@ func Register(ctx *log.Context) error { execDirectory := os.Getenv(constants.ExtensionPathEnvName) + "/bin/immediate-run-command-handler" err = os.Chmod(execDirectory, 0744) if err != nil { + errMessage := fmt.Sprintf("Error while marking the immediate run command binary as executable: %v", err) + extensionEvents.LogErrorEvent("register", errMessage) return errors.Wrap(err, "error while marking the immediate run command binary as executable") } @@ -76,16 +80,17 @@ func Register(ctx *log.Context) error { return err } - err = Start(ctx) + err = Start(ctx, extensionEvents) if err != nil { return err } + extensionEvents.LogInformationalEvent("register", "Service registration complete") ctx.Log("message", "Service registration complete") return nil } -func DeRegister(ctx *log.Context) error { +func DeRegister(ctx *log.Context, extensionEvents *extensionevents.ExtensionEventManager) error { if isSystemdSupported(ctx) { serviceHandler := getSystemdHandler(ctx) @@ -95,13 +100,14 @@ func DeRegister(ctx *log.Context) error { return err } + extensionEvents.LogInformationalEvent("deregister", "Service deregistration complete") ctx.Log("message", "Service deregistration complete") } return nil } -func Start(ctx *log.Context) error { +func Start(ctx *log.Context, extensionEvents *extensionevents.ExtensionEventManager) error { if isSystemdSupported(ctx) { serviceHandler := getSystemdHandler(ctx) @@ -111,13 +117,14 @@ func Start(ctx *log.Context) error { return err } + extensionEvents.LogInformationalEvent("start", "Service started") ctx.Log("message", "Service started") } return nil } -func Disable(ctx *log.Context) error { +func Disable(ctx *log.Context, extensionEvents *extensionevents.ExtensionEventManager) error { if isSystemdSupported(ctx) { serviceHandler := getSystemdHandler(ctx) @@ -133,13 +140,15 @@ func Disable(ctx *log.Context) error { if err != nil { return err } + + extensionEvents.LogInformationalEvent("disable", "Service disabled") ctx.Log("message", "Service disabled") } return nil } -func Enable(ctx *log.Context) error { +func Enable(ctx *log.Context, extensionEvents *extensionevents.ExtensionEventManager) error { if isSystemdSupported(ctx) { serviceHandler := getSystemdHandler(ctx) @@ -148,13 +157,15 @@ func Enable(ctx *log.Context) error { if err != nil { return err } + + extensionEvents.LogInformationalEvent("enable", "Service enabled") ctx.Log("message", "Service enabled") } return nil } -func Stop(ctx *log.Context) error { +func Stop(ctx *log.Context, extensionEvents *extensionevents.ExtensionEventManager) error { if isSystemdSupported(ctx) { serviceHandler := getSystemdHandler(ctx) @@ -163,6 +174,8 @@ func Stop(ctx *log.Context) error { if err != nil { return err } + + extensionEvents.LogInformationalEvent("stop", "Service stopped") ctx.Log("message", "Service stopped") } diff --git a/internal/types/handlerenvironment.go b/internal/types/handlerenvironment.go index c2c243e..19b9736 100644 --- a/internal/types/handlerenvironment.go +++ b/internal/types/handlerenvironment.go @@ -6,9 +6,15 @@ type HandlerEnvironment struct { Version float64 `json:"version"` Name string `json:"name"` HandlerEnvironment struct { - HeartbeatFile string `json:"heartbeatFile"` - StatusFolder string `json:"statusFolder"` - ConfigFolder string `json:"configFolder"` - LogFolder string `json:"logFolder"` + HeartbeatFile string `json:"heartbeatFile"` + StatusFolder string `json:"statusFolder"` + ConfigFolder string `json:"configFolder"` + LogFolder string `json:"logFolder"` + EventsFolder string `json:"eventsFolder"` + EventsFolderPreview string `json:"eventsFolder_preview"` + DeploymentID string `json:"deploymentid"` + RoleName string `json:"rolename"` + Instance string `json:"instance"` + HostResolverAddress string `json:"hostResolverAddress"` } } From 553af3ccc95a40358a5dfd65d0020b4e8164e3e6 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Tue, 22 Jul 2025 14:56:17 -0700 Subject: [PATCH 2/7] PR feedback --- internal/cmds/cmds.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index b4bf0db..39beaa6 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -131,13 +131,14 @@ func install(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComm } if err := os.MkdirAll(DataDir, 0755); err != nil { - extensionEvents.LogErrorEvent("install", "Failed to create data dir") - return "", "", errors.Wrap(err, "failed to create data dir"), constants.ExitCode_CreateDataDirectoryFailed + errMessage := fmt.Sprintf("Failed to create data dir: %v due to: %v", DataDir, err) + extensionEvents.LogErrorEvent("install", errMessage) + return "", "", errors.Wrap(err, errMessage), constants.ExitCode_CreateDataDirectoryFailed } ctx.Log("event", "created data dir", "path", DataDir) ctx.Log("event", "installed") - extensionEvents.LogInformationalEvent("uninstall", "created data dir") + extensionEvents.LogInformationalEvent("uninstall", fmt.Sprintf("created data dir: %v")) return "", "", nil, constants.ExitCode_Okay } @@ -152,10 +153,12 @@ func uninstall(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCo ctx = ctx.With("path", constants.DataDir) ctx.Log("event", "removing data dir", "path", constants.DataDir) if err := os.RemoveAll(constants.DataDir); err != nil { - return "", "", errors.Wrap(err, "failed to delete data directory"), constants.ExitCode_RemoveDataDirectoryFailed + errMessage := fmt.Sprintf("Failed to delete data directory: %v due to: %v", DataDir, err) + extensionEvents.LogErrorEvent("uninstall", errMessage) + return "", "", errors.Wrap(err, errMessage), constants.ExitCode_RemoveDataDirectoryFailed } ctx.Log("event", "removed data dir") - extensionEvents.LogInformationalEvent("uninstall", "removed data dir") + extensionEvents.LogInformationalEvent("uninstall", fmt.Sprintf("removed data dir %v", DataDir)) } ctx.Log("event", "uninstalled") return "", "", nil, constants.ExitCode_Okay @@ -200,7 +203,7 @@ func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma dir := filepath.Join(metadata.DownloadPath, fmt.Sprintf("%d", metadata.SeqNum)) scriptFilePath, err := downloadScript(ctx, dir, &cfg) if err != nil { - errMessage := fmt.Sprintf("Failed to download script: %v", err) + errMessage := fmt.Sprintf("Failed to download script: %v due to: %v", download.GetUriForLogging(cfg.ScriptURI()), err) extensionEvents.LogErrorEvent("enable", errMessage) return "", "", @@ -456,7 +459,7 @@ func rehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, h types.Handler } if isProblematicVersion { - message := fmt.Sprintf("Rehydrating mrseq files deleted by from version '%s' using status files", oldExtensionVersion) + message := fmt.Sprintf("Rehydrating mrseq files deleted by version '%s' using status files", oldExtensionVersion) ctx.Log("message", message) extensionEvents.LogInformationalEvent("rehydratemrseq", message) return doRehydrateMrSeqFilesForProblematicUpgrades(ctx, oldExtensionDirectory, newExtensionDirectory, extensionEvents) @@ -474,7 +477,7 @@ func doRehydrateMrSeqFilesForProblematicUpgrades(ctx *log.Context, oldExtensionD extensionStatusDirectoryFDRef, err := os.Open(oldExtensionStatusDirectory) if err != nil { - errMessage := fmt.Sprintf("Failed to open status directory '%s'", oldExtensionStatusDirectory) + errMessage := fmt.Sprintf("Failed to open status directory '%s' due to '%v'", oldExtensionStatusDirectory, err) extensionEvents.LogErrorEvent("rehydratemrseq", errMessage) return errors.Wrap(err, errMessage) } From 0c343c9087cc03c9832d640abae1d6d41ff9f646 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Wed, 23 Jul 2025 13:21:03 -0700 Subject: [PATCH 3/7] PR feedback --- internal/cmds/cmds.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index 39beaa6..884e6b1 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -325,6 +325,7 @@ func createExtensionEventManager(ctx *log.Context, hEnv types.HandlerEnvironment return extensionEvents } +// Converts from the local HandlerEnvironment to the azure-extension-platform HandlerEnvironment func convertToPlatformHandlerEnv(myEnv types.HandlerEnvironment) *handlerenv.HandlerEnvironment { data, err := json.Marshal(myEnv.HandlerEnvironment) if err != nil { From 9b85377ee26720f4d46fe10b13f76a25af212683 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Wed, 23 Jul 2025 14:03:41 -0700 Subject: [PATCH 4/7] Fixes build break --- internal/cmds/cmds.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index 884e6b1..44ac6fd 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -138,7 +138,7 @@ func install(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComm ctx.Log("event", "created data dir", "path", DataDir) ctx.Log("event", "installed") - extensionEvents.LogInformationalEvent("uninstall", fmt.Sprintf("created data dir: %v")) + extensionEvents.LogInformationalEvent("uninstall", fmt.Sprintf("created data dir: %v", DataDir)) return "", "", nil, constants.ExitCode_Okay } From eb4648a01d42ae2344090c0ee5f9b24e05b5ee6b Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Wed, 23 Jul 2025 14:28:27 -0700 Subject: [PATCH 5/7] Fixes unit tests --- internal/cmds/cmds_test.go | 17 +++++++++++++++-- internal/constants/constants.go | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/cmds/cmds_test.go b/internal/cmds/cmds_test.go index 4c4257b..624dd29 100755 --- a/internal/cmds/cmds_test.go +++ b/internal/cmds/cmds_test.go @@ -7,7 +7,6 @@ import ( "net/http" "net/http/httptest" "os" - "path" "path/filepath" "strconv" "strings" @@ -78,8 +77,10 @@ func Test_CopyMrseqFiles_MrseqFilesAreCopied(t *testing.T) { os.Create(filepath.Join(previousStatusDirectory, "ABCD.1.status")) os.Create(filepath.Join(previousStatusDirectory, "abc.cs")) // this should not be copied to currentExtensionVersionDirectory + tempDir, _ := os.MkdirTemp("", "deletecmd") + defer os.RemoveAll(tempDir) handlerEnvironment := handlerenv.HandlerEnvironment{ - EventsFolder: path.Join(currentExtensionVersionDirectory, "events"), + EventsFolder: tempDir, } extensionLogger := logging.New(nil) @@ -188,7 +189,11 @@ func Test_checkAndSaveSeqNum(t *testing.T) { func Test_update_e2e_cmd(t *testing.T) { tempDir, _ := os.MkdirTemp("", "deletecmd") + defer os.RemoveAll(tempDir) + DataDir, _ = os.MkdirTemp("", "datadir") + defer os.RemoveAll(DataDir) + oldVersionDirectory := filepath.Join(tempDir, "Microsoft.CPlat.Core.RunCommandHandlerLinux-1.3.8") newVersionDirectory := filepath.Join(tempDir, "Microsoft.CPlat.Core.RunCommandHandlerLinux-1.3.9") err := os.Mkdir(oldVersionDirectory, 0755) @@ -205,6 +210,7 @@ func Test_update_e2e_cmd(t *testing.T) { fakeEnv := types.HandlerEnvironment{} fakeEnv.HandlerEnvironment.ConfigFolder = oldVersionDirectory fakeEnv.HandlerEnvironment.StatusFolder = oldStatusPath + fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) // We start on the old version os.Setenv(constants.ExtensionPathEnvName, oldVersionDirectory) @@ -225,6 +231,7 @@ func Test_update_e2e_cmd(t *testing.T) { os.Setenv(constants.ExtensionVersionUpdatingFromEnvName, "1.3.8") fakeEnv.HandlerEnvironment.StatusFolder = newStatusPath fakeEnv.HandlerEnvironment.ConfigFolder = newVersionDirectory + fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) update_handler(t, fakeEnv, tempDir) // Now, WALA will uninstall the old extension @@ -240,7 +247,11 @@ func Test_update_e2e_cmd(t *testing.T) { func Test_udpate_e2e_problematic_version(t *testing.T) { tempDir, _ := os.MkdirTemp("", "deletecmd") + defer os.RemoveAll(tempDir) + DataDir, _ = os.MkdirTemp("", "datadir") + defer os.RemoveAll(DataDir) + oldVersionDirectory := filepath.Join(tempDir, "Microsoft.CPlat.Core.RunCommandHandlerLinux-1.3.17") newVersionDirectory := filepath.Join(tempDir, "Microsoft.CPlat.Core.RunCommandHandlerLinux-1.3.18") err := os.Mkdir(oldVersionDirectory, 0755) @@ -257,6 +268,7 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { fakeEnv := types.HandlerEnvironment{} fakeEnv.HandlerEnvironment.ConfigFolder = oldVersionDirectory fakeEnv.HandlerEnvironment.StatusFolder = oldStatusPath + fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) // We start on the old version os.Setenv(constants.ExtensionPathEnvName, oldVersionDirectory) @@ -292,6 +304,7 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { os.Setenv(constants.ExtensionVersionUpdatingFromEnvName, "1.3.17") fakeEnv.HandlerEnvironment.StatusFolder = newStatusPath fakeEnv.HandlerEnvironment.ConfigFolder = newVersionDirectory + fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) update_handler(t, fakeEnv, tempDir) // Now, WALA will uninstall the old extension diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 0d42c47..94bdccd 100755 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -25,6 +25,8 @@ const ( StatusFileDirectory = "status" + ExtensionEventsDirectory = "events" + StatusFileExtension = ".status" // The directory where the immediate run command status that have reached the terminal status are stored. From a31413a49c8168678b467f117d0396bce0754ebd Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Wed, 23 Jul 2025 14:58:14 -0700 Subject: [PATCH 6/7] Creates events directory for tests --- internal/cmds/cmds_test.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/cmds/cmds_test.go b/internal/cmds/cmds_test.go index 624dd29..71ce1aa 100755 --- a/internal/cmds/cmds_test.go +++ b/internal/cmds/cmds_test.go @@ -206,11 +206,17 @@ func Test_update_e2e_cmd(t *testing.T) { newStatusPath := filepath.Join(newVersionDirectory, constants.StatusFileDirectory) err = os.Mkdir(newStatusPath, 0755) require.Nil(t, err, "Could not create new version status subdirectory") + oldEventsPath := filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) + err = os.Mkdir(oldEventsPath, 0755) + require.Nil(t, err, "Could not create old version events subdirectory") + newEventsPath := filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) + err = os.Mkdir(newEventsPath, 0755) + require.Nil(t, err, "Could not create new version events subdirectory") fakeEnv := types.HandlerEnvironment{} fakeEnv.HandlerEnvironment.ConfigFolder = oldVersionDirectory fakeEnv.HandlerEnvironment.StatusFolder = oldStatusPath - fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) + fakeEnv.HandlerEnvironment.EventsFolder = oldEventsPath // We start on the old version os.Setenv(constants.ExtensionPathEnvName, oldVersionDirectory) @@ -231,7 +237,7 @@ func Test_update_e2e_cmd(t *testing.T) { os.Setenv(constants.ExtensionVersionUpdatingFromEnvName, "1.3.8") fakeEnv.HandlerEnvironment.StatusFolder = newStatusPath fakeEnv.HandlerEnvironment.ConfigFolder = newVersionDirectory - fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) + fakeEnv.HandlerEnvironment.EventsFolder = newEventsPath update_handler(t, fakeEnv, tempDir) // Now, WALA will uninstall the old extension @@ -264,11 +270,17 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { newStatusPath := filepath.Join(newVersionDirectory, constants.StatusFileDirectory) err = os.Mkdir(newStatusPath, 0755) require.Nil(t, err, "Could not create new version status subdirectory") + oldEventsPath := filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) + err = os.Mkdir(oldEventsPath, 0755) + require.Nil(t, err, "Could not create old version events subdirectory") + newEventsPath := filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) + err = os.Mkdir(newEventsPath, 0755) + require.Nil(t, err, "Could not create new version events subdirectory") fakeEnv := types.HandlerEnvironment{} fakeEnv.HandlerEnvironment.ConfigFolder = oldVersionDirectory fakeEnv.HandlerEnvironment.StatusFolder = oldStatusPath - fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) + fakeEnv.HandlerEnvironment.EventsFolder = oldEventsPath // We start on the old version os.Setenv(constants.ExtensionPathEnvName, oldVersionDirectory) @@ -304,7 +316,7 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { os.Setenv(constants.ExtensionVersionUpdatingFromEnvName, "1.3.17") fakeEnv.HandlerEnvironment.StatusFolder = newStatusPath fakeEnv.HandlerEnvironment.ConfigFolder = newVersionDirectory - fakeEnv.HandlerEnvironment.EventsFolder = filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) + fakeEnv.HandlerEnvironment.EventsFolder = newEventsPath update_handler(t, fakeEnv, tempDir) // Now, WALA will uninstall the old extension From ad7ba35fd520e3d1a1311cacbeecad044eb07654 Mon Sep 17 00:00:00 2001 From: Joseph Calev Date: Fri, 1 Aug 2025 13:30:56 -0700 Subject: [PATCH 7/7] Fixes unit tests --- internal/cmds/cmds.go | 6 ++-- internal/cmds/cmds_test.go | 61 ++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/internal/cmds/cmds.go b/internal/cmds/cmds.go index 44ac6fd..864c001 100755 --- a/internal/cmds/cmds.go +++ b/internal/cmds/cmds.go @@ -150,9 +150,9 @@ func uninstall(ctx *log.Context, h types.HandlerEnvironment, report *types.RunCo } { // a new context scope with path - ctx = ctx.With("path", constants.DataDir) - ctx.Log("event", "removing data dir", "path", constants.DataDir) - if err := os.RemoveAll(constants.DataDir); err != nil { + ctx = ctx.With("path", DataDir) + ctx.Log("event", "removing data dir", "path", DataDir) + if err := os.RemoveAll(DataDir); err != nil { errMessage := fmt.Sprintf("Failed to delete data directory: %v due to: %v", DataDir, err) extensionEvents.LogErrorEvent("uninstall", errMessage) return "", "", errors.Wrap(err, errMessage), constants.ExitCode_RemoveDataDirectoryFailed diff --git a/internal/cmds/cmds_test.go b/internal/cmds/cmds_test.go index 71ce1aa..205c5ef 100755 --- a/internal/cmds/cmds_test.go +++ b/internal/cmds/cmds_test.go @@ -200,23 +200,13 @@ func Test_update_e2e_cmd(t *testing.T) { require.Nil(t, err, "Could not create old version subdirectory") err = os.Mkdir(newVersionDirectory, 0755) require.Nil(t, err, "Could not create new version subdirectory") - oldStatusPath := filepath.Join(oldVersionDirectory, constants.StatusFileDirectory) - err = os.Mkdir(oldStatusPath, 0755) - require.Nil(t, err, "Could not create old version status subdirectory") - newStatusPath := filepath.Join(newVersionDirectory, constants.StatusFileDirectory) - err = os.Mkdir(newStatusPath, 0755) - require.Nil(t, err, "Could not create new version status subdirectory") - oldEventsPath := filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) - err = os.Mkdir(oldEventsPath, 0755) - require.Nil(t, err, "Could not create old version events subdirectory") - newEventsPath := filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) - err = os.Mkdir(newEventsPath, 0755) - require.Nil(t, err, "Could not create new version events subdirectory") + oldStatusPath := create_folder(t, oldVersionDirectory, constants.StatusFileDirectory) + newStatusPath := create_folder(t, newVersionDirectory, constants.StatusFileDirectory) + oldEventsPath := create_folder(t, oldVersionDirectory, constants.ExtensionEventsDirectory) + newEventsPath := create_folder(t, newVersionDirectory, constants.ExtensionEventsDirectory) fakeEnv := types.HandlerEnvironment{} - fakeEnv.HandlerEnvironment.ConfigFolder = oldVersionDirectory - fakeEnv.HandlerEnvironment.StatusFolder = oldStatusPath - fakeEnv.HandlerEnvironment.EventsFolder = oldEventsPath + update_handler_env(&fakeEnv, oldStatusPath, oldVersionDirectory, oldEventsPath) // We start on the old version os.Setenv(constants.ExtensionPathEnvName, oldVersionDirectory) @@ -235,9 +225,7 @@ func Test_update_e2e_cmd(t *testing.T) { os.Setenv(constants.ExtensionVersionEnvName, "1.3.9") os.Setenv(constants.ExtensionPathEnvName, newVersionDirectory) os.Setenv(constants.ExtensionVersionUpdatingFromEnvName, "1.3.8") - fakeEnv.HandlerEnvironment.StatusFolder = newStatusPath - fakeEnv.HandlerEnvironment.ConfigFolder = newVersionDirectory - fakeEnv.HandlerEnvironment.EventsFolder = newEventsPath + update_handler_env(&fakeEnv, newStatusPath, newVersionDirectory, newEventsPath) update_handler(t, fakeEnv, tempDir) // Now, WALA will uninstall the old extension @@ -264,23 +252,13 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { require.Nil(t, err, "Could not create old version subdirectory") err = os.Mkdir(newVersionDirectory, 0755) require.Nil(t, err, "Could not create new version subdirectory") - oldStatusPath := filepath.Join(oldVersionDirectory, constants.StatusFileDirectory) - err = os.Mkdir(oldStatusPath, 0755) - require.Nil(t, err, "Could not create old version status subdirectory") - newStatusPath := filepath.Join(newVersionDirectory, constants.StatusFileDirectory) - err = os.Mkdir(newStatusPath, 0755) - require.Nil(t, err, "Could not create new version status subdirectory") - oldEventsPath := filepath.Join(oldVersionDirectory, constants.ExtensionEventsDirectory) - err = os.Mkdir(oldEventsPath, 0755) - require.Nil(t, err, "Could not create old version events subdirectory") - newEventsPath := filepath.Join(newVersionDirectory, constants.ExtensionEventsDirectory) - err = os.Mkdir(newEventsPath, 0755) - require.Nil(t, err, "Could not create new version events subdirectory") + oldStatusPath := create_folder(t, oldVersionDirectory, constants.StatusFileDirectory) + newStatusPath := create_folder(t, newVersionDirectory, constants.StatusFileDirectory) + oldEventsPath := create_folder(t, oldVersionDirectory, constants.ExtensionEventsDirectory) + newEventsPath := create_folder(t, newVersionDirectory, constants.ExtensionEventsDirectory) fakeEnv := types.HandlerEnvironment{} - fakeEnv.HandlerEnvironment.ConfigFolder = oldVersionDirectory - fakeEnv.HandlerEnvironment.StatusFolder = oldStatusPath - fakeEnv.HandlerEnvironment.EventsFolder = oldEventsPath + update_handler_env(&fakeEnv, oldStatusPath, oldVersionDirectory, oldEventsPath) // We start on the old version os.Setenv(constants.ExtensionPathEnvName, oldVersionDirectory) @@ -314,9 +292,7 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { os.Setenv(constants.ExtensionVersionEnvName, "1.3.18") os.Setenv(constants.ExtensionPathEnvName, newVersionDirectory) os.Setenv(constants.ExtensionVersionUpdatingFromEnvName, "1.3.17") - fakeEnv.HandlerEnvironment.StatusFolder = newStatusPath - fakeEnv.HandlerEnvironment.ConfigFolder = newVersionDirectory - fakeEnv.HandlerEnvironment.EventsFolder = newEventsPath + update_handler_env(&fakeEnv, newStatusPath, newVersionDirectory, newEventsPath) update_handler(t, fakeEnv, tempDir) // Now, WALA will uninstall the old extension @@ -336,6 +312,19 @@ func Test_udpate_e2e_problematic_version(t *testing.T) { enable_extension(t, fakeEnv, newVersionDirectory, "stubbornChipmunk", true, 1) } +func create_folder(t *testing.T, versionDirectory string, folderName string) string { + folderPath := filepath.Join(versionDirectory, folderName) + err := os.Mkdir(folderPath, 0755) + require.Nil(t, err, "Could not create folder "+folderName) + return folderPath +} + +func update_handler_env(fakeEnv *types.HandlerEnvironment, statusFolder string, configFolder string, eventsFolder string) { + fakeEnv.HandlerEnvironment.StatusFolder = statusFolder + fakeEnv.HandlerEnvironment.ConfigFolder = configFolder + fakeEnv.HandlerEnvironment.EventsFolder = eventsFolder +} + func install_handler(t *testing.T, fakeEnv types.HandlerEnvironment, tempDir string) { generic_handler_call(t, fakeEnv, tempDir, "install", types.CmdInstallTemplate, CmdInstall) }