// processRegistration handles flags related to the registration category func processRegistration(log logger.T) (exitCode int) { if activationCode == "" || activationID == "" || region == "" { // clear registration if clear { return clearRegistration(log) } flagUsage() return 1 } // check if previously registered if !force && registration.InstanceID() != "" { confirmation, err := askForConfirmation() if err != nil { log.Errorf("Registration failed due to %v", err) return 1 } if !confirmation { log.Info("Registration canceled by user") return 1 } } managedInstanceID, err := registerManagedInstance() if err != nil { log.Errorf("Registration failed due to %v", err) return 1 } log.Infof("Successfully registered the instance with AWS SSM using Managed instance-id: %s", managedInstanceID) return 0 }
// validateManifest makes sure all the fields are provided. func validateManifest(log log.T, parsedManifest *Manifest, context *updateutil.InstanceContext, packageName string) error { if len(parsedManifest.URIFormat) == 0 { return fmt.Errorf("folder format cannot be null in the Manifest file") } fileName := context.FileName(packageName) foundPackage := false foundFile := false for _, p := range parsedManifest.Packages { if p.Name == packageName { log.Infof("found package %v", packageName) foundPackage = true for _, f := range p.Files { if f.Name == fileName { foundFile = true if len(f.AvailableVersions) == 0 { return fmt.Errorf("at least one available version is required for the %v", fileName) } log.Infof("found file %v", fileName) break } } } } if !foundPackage { return fmt.Errorf("cannot find the %v information in the Manifest file", packageName) } if !foundFile { return fmt.Errorf("cannot find the %v information in the Manifest file", fileName) } return nil }
// install executes the install script for the specific version of agent func installAgent(mgr *updateManager, log log.T, version string, context *UpdateContext) (err error) { log.Infof("Initiating %v %v installation", context.Current.PackageName, version) // find the path for the install script installerPath := updateutil.InstallerFilePath( context.Current.UpdateRoot, context.Current.PackageName, version) // calculate work directory workDir := updateutil.UpdateArtifactFolder( context.Current.UpdateRoot, context.Current.PackageName, version) // Install version if err = mgr.util.ExeCommand( log, installerPath, workDir, context.Current.UpdateRoot, context.Current.StdoutFileName, context.Current.StderrFileName, false); err != nil { return err } log.Infof("%v %v installed successfully", context.Current.PackageName, version) return nil }
// processFingerprint handles flags related to the fingerprint category func processFingerprint(log logger.T) (exitCode int) { if err := fingerprint.SetSimilarityThreshold(similarityThreshold); err != nil { log.Errorf("Error setting the SimilarityThreshold. %v", err) return 1 } log.Infof("Fingerprint SimilarityTHreshold set to %v", similarityThreshold) return 0 }
func start(log logger.T, instanceIDPtr *string, regionPtr *string) (cpm *coremanager.CoreManager, err error) { log.Infof("Starting Agent: %v", version.String()) log.Infof("OS: %s, Arch: %s", runtime.GOOS, runtime.GOARCH) log.Flush() if cpm, err = coremanager.NewCoreManager(instanceIDPtr, regionPtr, log); err != nil { log.Errorf("error occured when starting core manager: %v", err) return } cpm.Start() return }
// verifyInstallation checks installation result, verifies if agent is running func verifyInstallation(mgr *updateManager, log log.T, context *UpdateContext, isRollback bool) (err error) { // Check if agent is running var isRunning = false var instanceContext *updateutil.InstanceContext if instanceContext, err = mgr.util.CreateInstanceContext(log); err != nil { return mgr.failed(context, log, updateutil.ErrorEnvironmentIssue, err.Error(), false) } log.Infof("Initiating update health check") isRunning, err = mgr.util.IsServiceRunning(log, instanceContext) if err != nil || !isRunning { if !isRollback { message := updateutil.BuildMessage(err, "failed to update %v to %v, %v", context.Current.PackageName, context.Current.TargetVersion, "failed to start the agent") context.Current.AppendError(log, message) context.Current.AppendInfo( log, "Initiating rollback %v to %v", context.Current.PackageName, context.Current.SourceVersion) // Update state to rollback if err = mgr.inProgress(context, log, Rollback); err != nil { return err } return mgr.rollback(mgr, log, context) } message := updateutil.BuildMessage(err, "failed to rollback %v to %v, %v", context.Current.PackageName, context.Current.SourceVersion, "failed to start the agent") // Rolled back, but service cannot start, Update failed. return mgr.failed(context, log, updateutil.ErrorCannotStartService, message, false) } log.Infof("%v is running", context.Current.PackageName) if !isRollback { return mgr.succeeded(context, log) } message := fmt.Sprintf("rolledback %v to %v", context.Current.PackageName, context.Current.SourceVersion) log.Infof("message is %v", message) return mgr.failed(context, log, updateutil.ErrorCannotStartService, message, false) }
// reboot is performed by running the following command // shutdown -r -t 60 // The above command will cause the machine to reboot after 60 seconds func reboot(log log.T) (err error) { log.Infof("rebooting the machine in %v seconds..", timeOutInSecondsBeforeReboot) command := exec.Command("shutdown", "-r", "-t", timeOutInSecondsBeforeReboot) var stdout, stderr bytes.Buffer command.Stderr = &stderr command.Stdout = &stdout err = command.Start() log.Infof("shutdown output: %v\n", stdout.String()) if stderr.Len() != 0 { log.Errorf("shutdown error: %v\n", stderr.String()) } return }
// reboot is performed by running the following command // /sbin/shutdown -r +1 // The above command will cause the machine to reboot after 1 minute func reboot(log log.T) (err error) { log.Infof("Rebooting the machine in %v Minutes..", timeOutInMinutesBeforeReboot) command := exec.Command("/sbin/shutdown", "-r", timeOutInMinutesBeforeReboot) command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} var stdout, stderr bytes.Buffer command.Stderr = &stderr command.Stdout = &stdout err = command.Start() log.Infof("shutdown output: %v\n", stdout.String()) if stderr.Len() != 0 { log.Errorf("shutdown error: %v\n", stderr.String()) } return }
// downloadAndUnzipArtifact downloads installation package and unzips it func downloadAndUnzipArtifact( mgr *updateManager, log log.T, downloadInput artifact.DownloadInput, context *UpdateContext, version string) (err error) { log.Infof("Preparing source for version %v", version) // download installation zip files downloadOutput, err := downloadArtifact(log, downloadInput) if err != nil || downloadOutput.IsHashMatched == false || downloadOutput.LocalFilePath == "" { if err != nil { return fmt.Errorf("failed to download file reliably, %v", err.Error()) } return fmt.Errorf("failed to download file reliably") } // downloaded successfully, append message context.Current.AppendInfo(log, "Successfully downloaded %v", downloadInput.SourceURL) // uncompress installation package if err = uncompress( downloadOutput.LocalFilePath, updateutil.UpdateArtifactFolder(context.Current.UpdateRoot, context.Current.PackageName, version)); err != nil { return fmt.Errorf("failed to uncompress installation package, %v", err.Error()) } return nil }
// prepareInstallationPackages downloads artifacts from public s3 storage func prepareInstallationPackages(mgr *updateManager, log log.T, context *UpdateContext) (err error) { log.Infof("Initiating download %v", context.Current.PackageName) var instanceContext *updateutil.InstanceContext updateDownload := "" if instanceContext, err = mgr.util.CreateInstanceContext(log); err != nil { return mgr.failed(context, log, updateutil.ErrorEnvironmentIssue, err.Error(), false) } if err = validateUpdateVersion(log, context.Current, instanceContext); err != nil { return mgr.failed(context, log, updateutil.ErrorEnvironmentIssue, err.Error(), true) } if updateDownload, err = mgr.util.CreateUpdateDownloadFolder(); err != nil { message := updateutil.BuildMessage( err, "failed to create download folder %v %v", context.Current.PackageName, context.Current.TargetVersion) return mgr.failed(context, log, updateutil.ErrorEnvironmentIssue, message, true) } // Download source downloadInput := artifact.DownloadInput{ SourceURL: context.Current.SourceLocation, SourceHashValue: context.Current.SourceHash, SourceHashType: updateutil.HashType, DestinationDirectory: updateDownload, } if err = mgr.download(mgr, log, downloadInput, context, context.Current.SourceVersion); err != nil { return mgr.failed(context, log, updateutil.ErrorInvalidPackage, err.Error(), true) } // Download target downloadInput = artifact.DownloadInput{ SourceURL: context.Current.TargetLocation, SourceHashValue: context.Current.TargetHash, SourceHashType: updateutil.HashType, DestinationDirectory: updateDownload, } if err = mgr.download(mgr, log, downloadInput, context, context.Current.TargetVersion); err != nil { return mgr.failed(context, log, updateutil.ErrorInvalidPackage, err.Error(), true) } // Update stdout context.Current.AppendInfo( log, "Initiating %v update to %v", context.Current.PackageName, context.Current.TargetVersion) // Update state to Staged if err = mgr.inProgress(context, log, Staged); err != nil { return err } // Process update return mgr.update(mgr, log, context) }
// HandleAwsError logs an AWS error. func HandleAwsError(log log.T, err error, stopPolicy *StopPolicy) { if err != nil { // notice that we're using 1, so it will actually log the where // the error happened, 0 = this function, we don't want that. pc, fn, line, _ := runtime.Caller(1) log.Debugf("error in %s[%s:%d] %v", runtime.FuncForPC(pc).Name(), fn, line, err) // In case this is aws error, update the stop policy as well. if aErr, ok := err.(awserr.Error); ok { // Generic AWS Error with Code, Message, and original error (if any) log.Debugf("AWS error. Code: %v, Message: %v, origerror: %v ", aErr.Code(), aErr.Message(), aErr.OrigErr()) // special treatment for Timeout exception - as this is expected if aErr.Code() == "RequestError" && aErr.OrigErr() != nil && strings.Contains(aErr.OrigErr().Error(), "Client.Timeout") { // resetting the error count to 0 - as these exceptions are all expected if stopPolicy != nil { resetStopPolicy(stopPolicy) } return } } log.Errorf("error when calling AWS APIs. error details - %v", err) if stopPolicy != nil { log.Infof("increasing error count by 1") stopPolicy.AddErrorCount(1) } } else { // there is no error, resetStopPolicy(stopPolicy) } }
// AppendInfo appends messages to UpdateContext StandardOut func (update *UpdateDetail) AppendInfo(log log.T, format string, params ...interface{}) { message := fmt.Sprintf(format, params...) log.Infof(message) if update.StandardOut != "" { update.StandardOut = fmt.Sprintf("%v\n%v", update.StandardOut, message) } else { update.StandardOut = message } }
func isUpdateSupported(log log.T) (bool, error) { var sku string var err error // Get platform sku information if sku, err = getPlatformSku(log); err != nil { log.Infof("Failed to fetch sku - %v", err) return false, err } log.Infof("sku: %v", sku) // If sku represents nano server, return false if sku == ProductDataCenterNanoServer || sku == ProductStandardNanoServer { return false, nil } return true, nil }
// IsDiskSpaceSufficientForUpdate loads disk space info and checks the available bytes // Returns true if the system has at least 100 Mb for available disk space or false if it is less than 100 Mb func (util *Utility) IsDiskSpaceSufficientForUpdate(log log.T) (bool, error) { var diskSpaceInfo fileutil.DiskSpaceInfo var err error // Get the available disk space if diskSpaceInfo, err = getDiskSpaceInfo(); err != nil { log.Infof("Failed to load disk space info - %v", err) return false, err } // Return false if available disk space is less than 100 Mb if diskSpaceInfo.AvailBytes < MinimumDiskSpaceForUpdate { log.Infof("Insufficient available disk space - %d Mb", diskSpaceInfo.AvailBytes/int64(1024*1024)) return false, nil } // Return true otherwise return true, nil }
// ValidateExecutionTimeout validates the supplied input interface and converts it into a valid int value. func ValidateExecutionTimeout(log log.T, input interface{}) int { var num int switch input.(type) { case string: num = extractIntFromString(log, input.(string)) case int: num = input.(int) case float64: f := input.(float64) num = int(f) log.Infof("Unexpected 'TimeoutSeconds' float value %v received. Applying 'TimeoutSeconds' as %v", f, num) default: log.Errorf("Unexpected 'TimeoutSeconds' value %v received. Setting 'TimeoutSeconds' to default value %v", input, defaultExecutionTimeoutInSeconds) } if num < minExecutionTimeoutInSeconds || num > maxExecutionTimeoutInSeconds { log.Infof("'TimeoutSeconds' value should be between %v and %v. Setting 'TimeoutSeconds' to default value %v", minExecutionTimeoutInSeconds, maxExecutionTimeoutInSeconds, defaultExecutionTimeoutInSeconds) num = defaultExecutionTimeoutInSeconds } return num }
// uploadOutput uploads the stdout and stderr file to S3 func (c *contextManager) uploadOutput(log log.T, context *UpdateContext) (err error) { awsConfig := sdkutil.AwsConfig() var config appconfig.SsmagentConfig config, err = appconfig.Config(false) if err != nil { return fmt.Errorf("could not load config file: %v", err) } // If customers have provided override in app config, honor that. if config.S3.Region != "" { awsConfig.Region = &config.S3.Region } log.Infof("Uploading output files to region: %v", *awsConfig.Region) s3 := s3.New(session.New(awsConfig)) // upload outputs (if any) to s3 uploader := s3util.NewManager(s3) uploadOutputsToS3 := func() { // delete temp outputDir once we're done defer pluginutil.DeleteDirectory(log, updateutil.UpdateOutputDirectory(context.Current.UpdateRoot)) // get stdout file path stdoutPath := updateutil.UpdateStandOutPath(context.Current.UpdateRoot, context.Current.StdoutFileName) s3Key := path.Join(context.Current.OutputS3KeyPrefix, context.Current.StdoutFileName) log.Debugf("Uploading %v to s3://%v/%v", stdoutPath, context.Current.OutputS3BucketName, s3Key) err = uploader.S3Upload(context.Current.OutputS3BucketName, s3Key, stdoutPath) if err != nil { log.Errorf("failed uploading %v to s3://%v/%v \n err:%v", stdoutPath, context.Current.OutputS3BucketName, s3Key, err) } // get stderr file path stderrPath := updateutil.UpdateStandOutPath(context.Current.UpdateRoot, context.Current.StderrFileName) s3Key = path.Join(context.Current.OutputS3KeyPrefix, context.Current.StderrFileName) log.Debugf("Uploading %v to s3://%v/%v", stderrPath, context.Current.OutputS3BucketName, s3Key) err = uploader.S3Upload(context.Current.OutputS3BucketName, s3Key, stderrPath) if err != nil { log.Errorf("failed uploading %v to s3://%v/%v \n err:%v", stderrPath, context.Current.StderrFileName, s3Key, err) } } uploadOutputsToS3() return nil }
// FileCopy copies the content from reader to destinationPath file func FileCopy(log log.T, destinationPath string, src io.Reader) (written int64, err error) { var file *os.File file, err = os.Create(destinationPath) if err != nil { log.Errorf("failed to create file. %v", err) return } defer file.Close() var size int64 size, err = io.Copy(file, src) log.Infof("%s with %v bytes downloaded", destinationPath, size) return }
// proceedUpdate starts update process func proceedUpdate(mgr *updateManager, log log.T, context *UpdateContext) (err error) { log.Infof( "Attemping to upgrade from %v to %v", context.Current.SourceVersion, context.Current.TargetVersion) // Uninstall only when the target version is lower than the source version if context.Current.RequiresUninstall { if err = mgr.uninstall(mgr, log, context.Current.SourceVersion, context); err != nil { message := updateutil.BuildMessage( err, "failed to uninstall %v %v", context.Current.PackageName, context.Current.SourceVersion) return mgr.failed(context, log, updateutil.ErrorUninstallFailed, message, true) } } if err = mgr.install(mgr, log, context.Current.TargetVersion, context); err != nil { // Install target failed with err // log the error and initiating rollback to the source version message := updateutil.BuildMessage(err, "failed to install %v %v", context.Current.PackageName, context.Current.TargetVersion) context.Current.AppendError(log, message) context.Current.AppendInfo( log, "Initiating rollback %v to %v", context.Current.PackageName, context.Current.SourceVersion) // Update state to Rollback to indicate updater has initiated the rollback process if err = mgr.inProgress(context, log, Rollback); err != nil { return err } // Rollback return mgr.rollback(mgr, log, context) } // Update state to installed to indicate there is no error occur during installation // Updater has installed the new version and started the verify process if err = mgr.inProgress(context, log, Installed); err != nil { return err } // verify target version return mgr.verify(mgr, log, context, false) }
// IsUpdateInProgress represents if the another update is running func (context *UpdateContext) IsUpdateInProgress(log log.T) bool { //System will check the start time of the last update //If current system time minus start time is bigger than the MaxAllowedUpdateTime, which means update has been interrupted. //Allow system to resume update if context.Current == nil { return false } else if string(context.Current.State) == "" { return false } else { duration := time.Since(context.Current.StartDateTime) log.Infof("Attemping to retry update after %v seconds", duration.Seconds()) if duration.Seconds() > maxAllowedUpdateDuration { return false } } return true }
// extractIntFromString extracts a valid int value from a string. func extractIntFromString(log log.T, input string) int { var iNum int var fNum float64 var err error iNum, err = strconv.Atoi(input) if err == nil { return iNum } fNum, err = strconv.ParseFloat(input, 64) if err == nil { iNum = int(fNum) log.Infof("Unexpected 'TimeoutSeconds' float value %v received. Applying 'TimeoutSeconds' as %v", fNum, iNum) } else { log.Errorf("Unexpected 'TimeoutSeconds' string value %v received. Setting 'TimeoutSeconds' to default value %v", input, defaultExecutionTimeoutInSeconds) iNum = defaultExecutionTimeoutInSeconds } return iNum }
// Execute is a mocked method that just returns what mock tells it to. func (m *MockCommandExecuter) Execute(log log.T, workingDir string, stdoutFilePath string, stderrFilePath string, cancelFlag task.CancelFlag, executionTimeout int, commandName string, commandArguments []string) (stdout io.Reader, stderr io.Reader, exitCode int, errs []error) { args := m.Called(log, workingDir, stdoutFilePath, stderrFilePath, cancelFlag, executionTimeout, commandName, commandArguments) log.Infof("args are %v", args) return args.Get(0).(io.Reader), args.Get(1).(io.Reader), args.Get(2).(int), args.Get(3).([]error) }
// UploadOutputToS3Bucket uploads outputs (if any) to s3 func (p *DefaultPlugin) UploadOutputToS3Bucket(log log.T, pluginID string, orchestrationDir string, outputS3BucketName string, outputS3KeyPrefix string, useTempDirectory bool, tempDir string, Stdout string, Stderr string) []string { var uploadOutputToS3BucketErrors []string if outputS3BucketName != "" { uploadOutputsToS3 := func() { uploadToS3 := true var testUploadError error if region, err := platform.Region(); err == nil && region != s3Bjs { p.Uploader.SetS3ClientRegion(S3RegionUSStandard) } log.Infof("uploading a test file to s3 bucket - %v , s3 key - %v with S3Client using region endpoint - %v", outputS3BucketName, outputS3KeyPrefix, p.Uploader.GetS3ClientRegion()) testUploadError = p.Uploader.UploadS3TestFile(log, outputS3BucketName, outputS3KeyPrefix) if testUploadError != nil { //Check if the error is related to Access Denied - i.e missing permissions if p.Uploader.IsS3ErrorRelatedToAccessDenied(testUploadError.Error()) { log.Debugf("encountered access denied related error - can't upload to S3 due to missing permissions -%v", testUploadError.Error()) uploadToS3 = false //since we don't have permissions - no S3 calls will go through no matter what } else if p.Uploader.IsS3ErrorRelatedToWrongBucketRegion(testUploadError.Error()) { //check if error is related to different bucket region log.Debugf("encountered error related to wrong bucket region while uploading test file to S3 - %v. parsing the message to get expected region", testUploadError.Error()) expectedBucketRegion := p.Uploader.GetS3BucketRegionFromErrorMsg(log, testUploadError.Error()) //set the region to expectedBucketRegion p.Uploader.SetS3ClientRegion(expectedBucketRegion) } else { log.Debugf("encountered unexpected error while uploading test file to S3 - %v, no need to modify s3client", testUploadError.Error()) } } else { //there were no errors while uploading a test file to S3 - our s3client should continue to use "us-east-1" log.Debugf("there were no errors while uploading a test file to S3 in region - %v. S3 client will continue to use region - %v", S3RegionUSStandard, p.Uploader.GetS3ClientRegion()) } if uploadToS3 { log.Infof("uploading logs to S3 with client configured to use region - %v", p.Uploader.GetS3ClientRegion()) if useTempDirectory { // delete temp directory once we're done defer DeleteDirectory(log, tempDir) } if Stdout != "" { localPath := filepath.Join(orchestrationDir, p.StdoutFileName) s3Key := path.Join(outputS3KeyPrefix, pluginID, p.StdoutFileName) log.Debugf("Uploading %v to s3://%v/%v", localPath, outputS3BucketName, s3Key) err := p.Uploader.S3Upload(outputS3BucketName, s3Key, localPath) if err != nil { log.Errorf("failed uploading %v to s3://%v/%v err:%v", localPath, outputS3BucketName, s3Key, err) if p.UploadToS3Sync { // if we are in synchronous mode, we can also return the error uploadOutputToS3BucketErrors = append(uploadOutputToS3BucketErrors, err.Error()) } } } if Stderr != "" { localPath := filepath.Join(orchestrationDir, p.StderrFileName) s3Key := path.Join(outputS3KeyPrefix, pluginID, p.StderrFileName) log.Debugf("Uploading %v to s3://%v/%v", localPath, outputS3BucketName, s3Key) err := p.Uploader.S3Upload(outputS3BucketName, s3Key, localPath) if err != nil { log.Errorf("failed uploading %v to s3://%v/%v err:%v", localPath, outputS3BucketName, s3Key, err) if p.UploadToS3Sync { // if we are in synchronous mode, we can also return the error uploadOutputToS3BucketErrors = append(uploadOutputToS3BucketErrors, err.Error()) } } } } else { //TODO:Bubble this up to engine - so that document level status reply can be sent stating no permissions to perform S3 upload - similar to ec2config } } if p.UploadToS3Sync { uploadOutputsToS3() } else { go uploadOutputsToS3() } } //return out.Errors return uploadOutputToS3BucketErrors }
// UploadOutputToS3Bucket is a mocked method that just returns what mock tells it to. func (m *MockDefaultPlugin) UploadOutputToS3Bucket(log log.T, pluginID string, orchestrationDir string, outputS3BucketName string, outputS3KeyPrefix string, useTempDirectory bool, tempDir string, Stdout string, Stderr string) []string { args := m.Called(log, pluginID, orchestrationDir, outputS3BucketName, outputS3KeyPrefix, useTempDirectory, tempDir, Stdout, Stderr) log.Infof("args are %v", args) return args.Get(0).([]string) }
// ExeCommand executes shell command func (util *Utility) ExeCommand( log log.T, cmd string, workingDir string, updaterRoot string, stdOut string, stdErr string, isAsync bool) (err error) { parts := strings.Fields(cmd) if isAsync { command := execCommand(parts[0], parts[1:]...) command.Dir = workingDir prepareProcess(command) // Start command asynchronously err = cmdStart(command) if err != nil { return } } else { tempCmd := setPlatformSpecificCommand(parts) command := execCommand(tempCmd[0], tempCmd[1:]...) command.Dir = workingDir stdoutWriter, stderrWriter, exeErr := setExeOutErr(updaterRoot, stdOut, stdErr) if exeErr != nil { return exeErr } defer stdoutWriter.Close() defer stderrWriter.Close() command.Stdout = stdoutWriter command.Stderr = stderrWriter err = cmdStart(command) if err != nil { return } timer := time.NewTimer(time.Duration(DefaultUpdateExecutionTimeoutInSeconds) * time.Second) go killProcessOnTimeout(log, command, timer) err = command.Wait() timedOut := !timer.Stop() if err != nil { log.Debugf("command failed to run %v", err) if exitErr, ok := err.(*exec.ExitError); ok { // The program has exited with an exit code != 0 if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { exitCode := status.ExitStatus() if exitCode == -1 && timedOut { // set appropriate exit code based on cancel or timeout exitCode = pluginutil.CommandStoppedPreemptivelyExitCode log.Infof("The execution of command was timedout.") } err = fmt.Errorf("The execution of command returned Exit Status: %d \n %v", exitCode, err.Error()) } } return err } } return nil }
// updateAgent downloads the installation packages and update the agent func runUpdateAgent( p *Plugin, config contracts.Configuration, log log.T, manager pluginHelper, util updateutil.T, rawPluginInput interface{}, cancelFlag task.CancelFlag, outputS3BucketName string, outputS3KeyPrefix string, startTime time.Time) (out UpdatePluginOutput) { var pluginInput UpdatePluginInput var err error var context *updateutil.InstanceContext if isUpdateSupported, err := util.IsPlatformSupportedForUpdate(log); err == nil && !isUpdateSupported { out.Failed(log, fmt.Errorf("Unsupported platform for update")) return } if err = jsonutil.Remarshal(rawPluginInput, &pluginInput); err != nil { out.Failed(log, fmt.Errorf("invalid format in plugin properties %v;\nerror %v", rawPluginInput, err)) return } if context, err = util.CreateInstanceContext(log); err != nil { out.Failed(log, err) return } //Use default manifest location is the override is not present if len(pluginInput.Source) == 0 { pluginInput.Source = p.ManifestLocation } //Calculate manifest location base on current instance's region pluginInput.Source = strings.Replace(pluginInput.Source, updateutil.RegionHolder, context.Region, -1) //Calculate updater package name base on agent name pluginInput.UpdaterName = pluginInput.AgentName + updateutil.UpdaterPackageNamePrefix //Generate update output targetVersion := pluginInput.TargetVersion if len(targetVersion) == 0 { targetVersion = "latest" } out.AppendInfo(log, "Updating %v from %v to %v", pluginInput.AgentName, version.Version, targetVersion) //Download manifest file manifest, downloadErr := manager.downloadManifest(log, util, &pluginInput, context, &out) if downloadErr != nil { out.Failed(log, downloadErr) return } //Validate update details noNeedToUpdate := false if noNeedToUpdate, err = manager.validateUpdate(log, &pluginInput, context, manifest, &out); noNeedToUpdate { if err != nil { out.Failed(log, err) } return } //Download updater and retrieve the version number updaterVersion := "" if updaterVersion, err = manager.downloadUpdater( log, util, pluginInput.UpdaterName, manifest, &out, context); err != nil { out.Failed(log, err) return } //Generate update command base on the update detail cmd := "" if cmd, err = manager.generateUpdateCmd(log, manifest, &pluginInput, context, updateutil.UpdaterFilePath(appconfig.UpdaterArtifactsRoot, pluginInput.UpdaterName, updaterVersion), config.MessageId, p.StdoutFileName, p.StderrFileName, outputS3KeyPrefix, outputS3BucketName); err != nil { out.Failed(log, err) return } log.Debugf("Update command %v", cmd) //Save update plugin result to local file, updater will read it during agent update updatePluginResult := &updateutil.UpdatePluginResult{ StandOut: out.Stdout, StartDateTime: startTime, } if err = util.SaveUpdatePluginResult(log, appconfig.UpdaterArtifactsRoot, updatePluginResult); err != nil { out.Failed(log, err) return } // If disk space is not sufficient, fail the update to prevent installation and notify user in output // If loading disk space fails, continue to update (agent update is backed by rollback handler) log.Infof("Checking available disk space ...") if isDiskSpaceSufficient, err := util.IsDiskSpaceSufficientForUpdate(log); err == nil && !isDiskSpaceSufficient { out.Failed(log, errors.New("Insufficient available disk space")) return } log.Infof("Start Installation") log.Infof("Hand over update process to %v", pluginInput.UpdaterName) //Execute updater, hand over the update process workDir := updateutil.UpdateArtifactFolder( appconfig.UpdaterArtifactsRoot, pluginInput.UpdaterName, updaterVersion) if err = util.ExeCommand( log, cmd, workDir, appconfig.UpdaterArtifactsRoot, p.StdoutFileName, p.StderrFileName, true); err != nil { out.Failed(log, err) return } out.Pending() return }
// RunCommand runs the given commands using the given working directory. // Standard output and standard error are sent to the given writers. func RunCommand(log log.T, cancelFlag task.CancelFlag, workingDir string, stdoutWriter io.Writer, stderrWriter io.Writer, executionTimeout int, commandName string, commandArguments []string, ) (exitCode int, err error) { command := exec.Command(commandName, commandArguments...) command.Dir = workingDir command.Stdout = stdoutWriter command.Stderr = stderrWriter exitCode = 0 // configure OS-specific process settings prepareProcess(command) log.Debug() log.Debugf("Running in directory %v, command: %v %v.", workingDir, commandName, commandArguments) log.Debug() if err = command.Start(); err != nil { log.Error("error occurred starting the command", err) exitCode = 1 return } go killProcessOnCancel(log, command, cancelFlag) timer := time.NewTimer(time.Duration(executionTimeout) * time.Second) go killProcessOnTimeout(log, command, timer) err = command.Wait() timedOut := !timer.Stop() // returns false if called previously - indicates timedOut. if err != nil { exitCode = 1 log.Debugf("command failed to run %v", err) if exiterr, ok := err.(*exec.ExitError); ok { // The program has exited with an exit code != 0 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { exitCode = status.ExitStatus() // First try to handle Cancel and Timeout scenarios // SIGKILL will result in an exitcode of -1 if exitCode == -1 { if cancelFlag.Canceled() { // set appropriate exit code based on cancel or timeout exitCode = pluginutil.CommandStoppedPreemptivelyExitCode log.Infof("The execution of command was cancelled.") } else if timedOut { // set appropriate exit code based on cancel or timeout exitCode = pluginutil.CommandStoppedPreemptivelyExitCode log.Infof("The execution of command was timedout.") } } else { log.Infof("The execution of command returned Exit Status: %d", exitCode) } } } } else { // check if cancellation or timeout failed to kill the process // This will not occur as we do a SIGKILL, which is not recoverable. if cancelFlag.Canceled() { // This is when the cancellation failed and the command completed successfully log.Errorf("the cancellation failed to stop the process.") // do not return as the command could have been cancelled and also timedout } if timedOut { // This is when the timeout failed and the command completed successfully log.Errorf("the timeout failed to stop the process.") } } log.Debug("Done waiting!") return }