// 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 }
// initializeBookkeepingLocations - initializes all folder locations required for bookkeeping func initializeBookkeepingLocations(log logger.T, instanceID string) bool { //Create folders pending, current, completed, corrupt under the location DefaultLogDirPath/<instanceId> log.Info("Initializing bookkeeping folders") initStatus := true folders := []string{ appconfig.DefaultLocationOfPending, appconfig.DefaultLocationOfCurrent, appconfig.DefaultLocationOfCompleted, appconfig.DefaultLocationOfCorrupt} for _, folder := range folders { directoryName := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, folder) err := fileutil.MakeDirs(directoryName) if err != nil { log.Errorf("Encountered error while creating folders for internal state management. %v", err) initStatus = false break } } return initStatus }
// 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 }
// clearRegistration clears any existing registration data func clearRegistration(log logger.T) (exitCode int) { err := registration.UpdateServerInfo("", "", "", "") if err == nil { log.Info("Registration information has been removed from the instance.") return 0 } log.Errorf("error clearing the instance registration information. %v\nTry running as sudo/administrator.", err) return 1 }
// Run as a single process. Used by Unix systems and when running agent from console. func run(log logger.T) { // run core manager cpm, err := start(log, instanceIDPtr, regionPtr) if err != nil { log.Errorf("error occured when starting amazon-ssm-agent: %v", err) return } blockUntilSignaled(log) stop(log, cpm) }
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 }
// 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 }
// 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) } }
// getCmdState reads commandState from given file func getCmdState(log log.T, fileName string) message.CommandState { var commandState message.CommandState err := jsonutil.UnmarshalFile(fileName, &commandState) if err != nil { log.Errorf("encountered error with message %v while reading Interim state of command from file - %v", err, fileName) } else { //logging interim state as read from the file jsonString, err := jsonutil.Marshal(commandState) if err != nil { log.Errorf("encountered error with message %v while marshalling %v to string", err, commandState) } else { log.Tracef("interim CommandState read from file-system - %v", jsonutil.Indent(jsonString)) } } return commandState }
// runJob executes a job and then sends a signal on the given channel func runJob(log log.T, job func(), doneChannel chan struct{}) { defer func() { // recover in case the job panics if msg := recover(); msg != nil { log.Errorf("Job failed with message %v", msg) } doneChannel <- struct{}{} }() job() }
// RemoveData deletes the fileName from locationFolder under defaultLogDir/instanceID func RemoveData(log log.T, commandID, instanceID, locationFolder string) { absoluteFileName := getCmdStateFileName(commandID, instanceID, locationFolder) err := fileutil.DeleteFile(absoluteFileName) if err != nil { log.Errorf("encountered error %v while deleting file %v", err, absoluteFileName) } else { log.Debugf("successfully deleted file %v", absoluteFileName) } }
// ValidParameters checks if parameter names are valid. Returns valid parameters only. func ValidParameters(log log.T, params map[string]interface{}) map[string]interface{} { validParams := make(map[string]interface{}) for paramName, paramValue := range params { if validName(paramName) { validParams[paramName] = paramValue } else { log.Errorf("invalid parameter name %v", paramName) } } return validParams }
// CreateScriptFile creates a script containing the given commands. func CreateScriptFile(log log.T, scriptPath string, runCommand []string) (err error) { var sourceFile *os.File var scriptFile *os.File // create script file mode := int(appconfig.ReadWriteExecuteAccess) scriptFile, err = os.OpenFile(scriptPath, os.O_CREATE|os.O_WRONLY, os.FileMode(mode)) if err != nil { log.Errorf("failed to create local script file %v, err %v", scriptPath, err) return } defer func() { cerr := scriptFile.Close() if err == nil { err = cerr } }() // write source commands to file if sourceFile != nil { if _, err = io.Copy(scriptFile, sourceFile); err != nil { log.Errorf("failed to write source scripts to file %v", scriptPath) return } _, err = scriptFile.WriteString("\n") if err != nil { log.Errorf("failed to write source scripts scripts to file %v", scriptPath) return } } // write source commands to file _, err = scriptFile.WriteString(strings.Join(runCommand, "\n") + "\n") if err != nil { log.Errorf("failed to write runcommand scripts to file %v", scriptPath) return } return }
// finalizeUpdateAndSendReply completes the update and send reply to message service func (u *updateManager) finalizeUpdateAndSendReply(log log.T, context *UpdateContext, errorCode string) (err error) { update := context.Current update.EndDateTime = time.Now().UTC() // resolve context location base on the UpdateRoot contextLocation := updateutil.UpdateContextFilePath(update.UpdateRoot) if err = u.ctxMgr.saveUpdateContext(log, context, contextLocation); err != nil { return err } // send reply if update.HasMessageID() { if err = u.svc.SendReply(log, update); err != nil { log.Errorf(err.Error()) } if err = u.svc.DeleteMessage(log, update); err != nil { log.Errorf(err.Error()) } } // update health information if err = u.svc.UpdateHealthCheck(log, update, errorCode); err != nil { log.Errorf(err.Error()) } // upload output to s3 bucket log.Debugf("output s3 bucket name is %v", update.OutputS3BucketName) if update.OutputS3BucketName != "" { u.ctxMgr.uploadOutput(log, context) } context.cleanUpdate() if err = u.ctxMgr.saveUpdateContext(log, context, contextLocation); err != nil { return err } 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 }
// AppendError appends messages to UpdateContext StandardError and StandardOut func (update *UpdateDetail) AppendError(log log.T, format string, params ...interface{}) { message := fmt.Sprintf(format, params...) log.Errorf(message) if update.StandardOut != "" { update.StandardOut = fmt.Sprintf("%v\n%v", update.StandardOut, message) } else { update.StandardOut = message } if update.StandardError != "" { update.StandardError = fmt.Sprintf("%v\n%v", update.StandardError, message) } else { update.StandardError = message } }
// 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 }
// LoadParametersAsList returns properties as a list and appropriate PluginResult if error is encountered func LoadParametersAsList(log log.T, prop interface{}) ([]interface{}, contracts.PluginResult) { var properties []interface{} var res contracts.PluginResult if err := jsonutil.Remarshal(prop, &properties); err != nil { log.Errorf("unable to parse plugin configuration") res.Output = "Execution failed because agent is unable to parse plugin configuration" res.Code = 1 res.Status = contracts.ResultStatusFailed } return properties, res }
// inProgress sets update to inProgressing with given new UpdateState func (u *updateManager) inProgress(context *UpdateContext, log log.T, state UpdateState) (err error) { update := context.Current update.State = state update.Result = contracts.ResultStatusInProgress // resolve context location base on the UpdateRoot contextLocation := updateutil.UpdateContextFilePath(update.UpdateRoot) if err = u.ctxMgr.saveUpdateContext(log, context, contextLocation); err != nil { return err } if update.HasMessageID() { err = u.svc.SendReply(log, update) if err != nil { log.Errorf(err.Error()) } } if err = u.svc.UpdateHealthCheck(log, update, ""); err != nil { log.Errorf(err.Error()) } return nil }
// 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 }
// setCmdState persists given commandState func setCmdState(log log.T, commandState message.CommandState, absoluteFileName, locationFolder string) { content, err := jsonutil.Marshal(commandState) if err != nil { log.Errorf("encountered error with message %v while marshalling %v to string", err, commandState) } else { if fileutil.Exists(absoluteFileName) { log.Debugf("overwriting contents of %v", absoluteFileName) } log.Tracef("persisting interim state %v in file %v", jsonutil.Indent(content), absoluteFileName) if s, err := fileutil.WriteIntoFileWithPermissions(absoluteFileName, jsonutil.Indent(content), os.FileMode(int(appconfig.ReadWriteAccess))); s && err == nil { log.Debugf("successfully persisted interim state in %v", locationFolder) } else { log.Debugf("persisting interim state in %v failed with error %v", locationFolder, err) } } }
// 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 }
// 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 }
// PersistData stores the given object in the file-system in pretty Json indented format // This will override the contents of an already existing file func PersistData(log log.T, commandID, instanceID, locationFolder string, object interface{}) { lockDocument(commandID) defer unlockDocument(commandID) absoluteFileName := getCmdStateFileName(commandID, instanceID, locationFolder) content, err := jsonutil.Marshal(object) if err != nil { log.Errorf("encountered error with message %v while marshalling %v to string", err, object) } else { if fileutil.Exists(absoluteFileName) { log.Debugf("overwriting contents of %v", absoluteFileName) } log.Tracef("persisting interim state %v in file %v", jsonutil.Indent(content), absoluteFileName) if s, err := fileutil.WriteIntoFileWithPermissions(absoluteFileName, jsonutil.Indent(content), os.FileMode(int(appconfig.ReadWriteAccess))); s && err == nil { log.Debugf("successfully persisted interim state in %v", locationFolder) } else { log.Debugf("persisting interim state in %v failed with error %v", locationFolder, err) } } }
// s3Download attempts to download a file via the aws sdk. func s3Download(log log.T, amazonS3URL s3util.AmazonS3URL, destFile string) (output DownloadOutput, err error) { log.Debugf("attempting to download as s3 download %v", destFile) eTagFile := destFile + ".etag" config := &aws.Config{} var appConfig appconfig.SsmagentConfig appConfig, err = appconfig.Config(false) if err != nil { log.Error("failed to read appconfig.") } else { creds, err1 := appConfig.ProfileCredentials() if err1 != nil { config.Credentials = creds } } config.S3ForcePathStyle = aws.Bool(amazonS3URL.IsPathStyle) config.Region = aws.String(amazonS3URL.Region) params := &s3.GetObjectInput{ Bucket: aws.String(amazonS3URL.Bucket), Key: aws.String(amazonS3URL.Key), } if fileutil.Exists(destFile) == true && fileutil.Exists(eTagFile) == true { var existingETag string existingETag, err = fileutil.ReadAllText(eTagFile) if err != nil { log.Debugf("failed to read etag file %v, %v", eTagFile, err) return } params.IfNoneMatch = aws.String(existingETag) } s3client := s3.New(session.New(config)) req, resp := s3client.GetObjectRequest(params) err = req.Send() if err != nil { if req.HTTPResponse == nil || req.HTTPResponse.StatusCode != http.StatusNotModified { log.Debug("failed to download from s3, ", err) fileutil.DeleteFile(destFile) fileutil.DeleteFile(eTagFile) return } log.Debugf("Unchanged file.") output.IsUpdated = false output.LocalFilePath = destFile return output, nil } if *resp.ETag != "" { log.Debug("files etag is ", *resp.ETag) err = fileutil.WriteAllText(eTagFile, *resp.ETag) if err != nil { log.Errorf("failed to write eTagfile %v, %v ", eTagFile, err) return } } defer resp.Body.Close() _, err = FileCopy(log, destFile, resp.Body) if err == nil { output.LocalFilePath = destFile output.IsUpdated = true } else { log.Errorf("failed to write destFile %v, %v ", destFile, err) } return }
// 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 }
// runCommands executes one set of commands and returns their output. func (p *Plugin) runCommands(log log.T, pluginInput PSModulePluginInput, orchestrationDirectory string, cancelFlag task.CancelFlag, outputS3BucketName string, outputS3KeyPrefix string) (out PSModulePluginOutput) { var err error // if no orchestration directory specified, create temp directory var useTempDirectory = (orchestrationDirectory == "") var tempDir string if useTempDirectory { if tempDir, err = ioutil.TempDir("", "Ec2RunCommand"); err != nil { out.Errors = append(out.Errors, err.Error()) log.Error(err) return } orchestrationDirectory = tempDir } orchestrationDir := fileutil.RemoveInvalidChars(filepath.Join(orchestrationDirectory, pluginInput.ID)) log.Debugf("Running commands %v in workingDirectory %v; orchestrationDir %v ", pluginInput.RunCommand, pluginInput.WorkingDirectory, orchestrationDir) // create orchestration dir if needed if err = fileutil.MakeDirsWithExecuteAccess(orchestrationDir); err != nil { log.Debug("failed to create orchestrationDir directory", orchestrationDir) out.Errors = append(out.Errors, err.Error()) return } // Create script file path scriptPath := filepath.Join(orchestrationDir, pluginutil.RunCommandScriptName) log.Debugf("Writing commands %v to file %v", pluginInput, scriptPath) // Create script file if err = pluginutil.CreateScriptFile(log, scriptPath, pluginInput.RunCommand); err != nil { out.Errors = append(out.Errors, err.Error()) log.Errorf("failed to create script file. %v", err) return } // Download file from source if available downloadOutput, err := pluginutil.DownloadFileFromSource(log, pluginInput.Source, pluginInput.SourceHash, pluginInput.SourceHashType) if err != nil || downloadOutput.IsHashMatched == false || downloadOutput.LocalFilePath == "" { errorString := fmt.Errorf("failed to download file reliably %v", pluginInput.Source) out.MarkAsFailed(log, errorString) return } else { // Uncompress the zip file received fileutil.Uncompress(downloadOutput.LocalFilePath, PowerShellModulesDirectory) } // Set execution time executionTimeout := pluginutil.ValidateExecutionTimeout(log, pluginInput.TimeoutSeconds) // Create output file paths stdoutFilePath := filepath.Join(orchestrationDir, p.StdoutFileName) stderrFilePath := filepath.Join(orchestrationDir, p.StderrFileName) log.Debugf("stdout file %v, stderr file %v", stdoutFilePath, stderrFilePath) // Construct Command Name and Arguments commandName := pluginutil.GetShellCommand() commandArguments := append(pluginutil.GetShellArguments(), scriptPath, pluginutil.ExitCodeTrap) // Execute Command stdout, stderr, exitCode, errs := p.ExecuteCommand(log, pluginInput.WorkingDirectory, stdoutFilePath, stderrFilePath, cancelFlag, executionTimeout, commandName, commandArguments) // Set output status out.ExitCode = exitCode out.Status = pluginutil.GetStatus(out.ExitCode, cancelFlag) if len(errs) > 0 { for _, err := range errs { out.Errors = append(out.Errors, err.Error()) if out.Status != contracts.ResultStatusCancelled && out.Status != contracts.ResultStatusTimedOut && out.Status != contracts.ResultStatusSuccessAndReboot { log.Error("failed to run commands: ", err) out.Status = contracts.ResultStatusFailed } } } // read (a prefix of) the standard output/error out.Stdout, err = pluginutil.ReadPrefix(stdout, p.MaxStdoutLength, p.OutputTruncatedSuffix) if err != nil { out.Errors = append(out.Errors, err.Error()) log.Error(err) } out.Stderr, err = pluginutil.ReadPrefix(stderr, p.MaxStderrLength, p.OutputTruncatedSuffix) if err != nil { out.Errors = append(out.Errors, err.Error()) log.Error(err) } // Upload output to S3 uploadOutputToS3BucketErrors := p.ExecuteUploadOutputToS3Bucket(log, pluginInput.ID, orchestrationDir, outputS3BucketName, outputS3KeyPrefix, useTempDirectory, tempDir, out.Stdout, out.Stderr) out.Errors = append(out.Errors, uploadOutputToS3BucketErrors...) // Return Json indented response responseContent, _ := jsonutil.Marshal(out) log.Debug("Returning response:\n", jsonutil.Indent(responseContent)) 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 }
// NewCoreManager creates a new core plugin manager. func NewCoreManager(instanceIdPtr *string, regionPtr *string, log logger.T) (cm *CoreManager, err error) { // initialize appconfig var config appconfig.SsmagentConfig if config, err = appconfig.Config(false); err != nil { log.Errorf("Could not load config file: %v", err) return } // initialize region if *regionPtr != "" { if err = platform.SetRegion(*regionPtr); err != nil { log.Errorf("error occured setting the region, %v", err) return } } var region string if region, err = platform.Region(); err != nil { log.Errorf("error fetching the region, %v", err) return } log.Debug("Using region:", region) // initialize instance ID if *instanceIdPtr != "" { if err = platform.SetInstanceID(*instanceIdPtr); err != nil { log.Errorf("error occured setting the instance ID, %v", err) return } } var instanceId string if instanceId, err = platform.InstanceID(); err != nil { log.Errorf("error fetching the instanceID, %v", err) return } log.Debug("Using instanceID:", instanceId) if err = fileutil.HardenDataFolder(); err != nil { log.Errorf("error initializing SSM data folder with hardened ACL, %v", err) return } //Initialize all folders where interim states of executing commands will be stored. if !initializeBookkeepingLocations(log, instanceId) { log.Error("unable to initialize. Exiting") return } context := context.Default(log, config).With("[instanceID=" + instanceId + "]") corePlugins := coreplugins.RegisteredCorePlugins(context) return &CoreManager{ context: context, corePlugins: *corePlugins, }, nil }
// httpDownload attempts to download a file via http/s call func httpDownload(log log.T, fileURL string, destFile string) (output DownloadOutput, err error) { log.Debugf("attempting to download as http/https download %v", destFile) eTagFile := destFile + ".etag" var check http.Client var request *http.Request request, err = http.NewRequest("GET", fileURL, nil) if err != nil { return } if fileutil.Exists(destFile) == true && fileutil.Exists(eTagFile) == true { var existingETag string existingETag, err = fileutil.ReadAllText(eTagFile) request.Header.Add("If-None-Match", existingETag) } check = http.Client{ CheckRedirect: func(r *http.Request, via []*http.Request) error { r.URL.Opaque = r.URL.Path return nil }, } var resp *http.Response resp, err = check.Do(request) if err != nil { log.Debug("failed to download from http/https, ", err) fileutil.DeleteFile(destFile) fileutil.DeleteFile(eTagFile) return } if resp.StatusCode == http.StatusNotModified { log.Debugf("Unchanged file.") output.IsUpdated = false output.LocalFilePath = destFile return output, nil } else if resp.StatusCode != http.StatusOK { log.Debug("failed to download from http/https, ", err) fileutil.DeleteFile(destFile) fileutil.DeleteFile(eTagFile) err = fmt.Errorf("http request failed. status:%v statuscode:%v", resp.Status, resp.StatusCode) return } defer resp.Body.Close() eTagValue := resp.Header.Get("Etag") if eTagValue != "" { log.Debug("file eTagValue is ", eTagValue) err = fileutil.WriteAllText(eTagFile, eTagValue) if err != nil { log.Errorf("failed to write eTagfile %v, %v ", eTagFile, err) return } } _, err = FileCopy(log, destFile, resp.Body) if err == nil { output.LocalFilePath = destFile output.IsUpdated = true } else { log.Errorf("failed to write destFile %v, %v ", destFile, err) } return }