// 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 }
// MoveCommandState moves the CommandState object func MoveCommandState(log log.T, commandID, instanceID, srcLocationFolder, dstLocationFolder string) { //get a lock for documentID specific lock lockDocument(commandID) absoluteSource := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, srcLocationFolder) absoluteDestination := path.Join(appconfig.DefaultDataStorePath, instanceID, appconfig.DefaultCommandRootDirName, appconfig.DefaultLocationOfState, dstLocationFolder) if s, err := fileutil.MoveFile(commandID, absoluteSource, absoluteDestination); s && err == nil { log.Debugf("moved file %v from %v to %v successfully", commandID, srcLocationFolder, dstLocationFolder) } else { log.Debugf("moving file %v from %v to %v failed with error %v", commandID, srcLocationFolder, dstLocationFolder, err) } //release documentID specific lock - before deleting the entry from the map unlockDocument(commandID) //delete documentID specific lock if document has finished executing. This is to avoid documentLock growing too much in memory. //This is done by ensuring that as soon as document finishes executing it is removed from documentLock //Its safe to assume that document has finished executing if it is being moved to appconfig.DefaultLocationOfCompleted if dstLocationFolder == appconfig.DefaultLocationOfCompleted { deleteLock(commandID) } }
// process launches one job in a separate go routine and waits // for either the job to finish or for a cancel to be requested. // If cancel is requested, this function waits for some time to allow the // job to complete. If the job does not complete by the timeout, the go routine // of the job is abandoned, and this function returns. func process(log log.T, job Job, cancelFlag *ChanneledCancelFlag, cancelWait time.Duration, clock times.Clock) { // Make a buffered channel to avoid blocking on send. This helps // in case the job fails to cancel on time and we give up on it. // If the job finally ends, it will succeed to send a signal // on the channel and then it will terminate. This will allow // the garbage collector to collect the go routine's resources // and the channel. doneChan := make(chan struct{}, 1) go runJob(log, func() { job(cancelFlag) }, doneChan) done := waitEither(doneChan, cancelFlag.ch) if done { // task done, set the flag to wake up waiting routines cancelFlag.Set(Completed) return } log.Debugf("Execution has been canceled, waiting up to %v to finish", cancelWait) done = waitEither(doneChan, clock.After(cancelWait)) if done { // job completed within cancel waiting window cancelFlag.Set(Completed) return } log.Debugf("Job failed to terminate within %v!", cancelWait) }
// LatestVersion returns latest version for specific package func (m *Manifest) LatestVersion(log log.T, context *updateutil.InstanceContext, packageName string) (result string, err error) { var version = minimumVersion var compareResult = 0 for _, p := range m.Packages { if p.Name == packageName { for _, f := range p.Files { if f.Name == context.FileName(packageName) { for _, v := range f.AvailableVersions { if compareResult, err = updateutil.VersionCompare(v.Version, version); err != nil { return version, err } if compareResult > 0 { version = v.Version } } } } } } if version == minimumVersion { log.Debugf("Filename: %v", context.FileName(packageName)) log.Debugf("Package Name: %v", packageName) log.Debugf("Manifest: %v", m) return version, fmt.Errorf("cannot find the latest version for package %v", packageName) } return version, nil }
// 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 }
// 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) } }
// 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 }
func (svc *sdkService) SendCommand(log log.T, documentName string, instanceIDs []string, parameters map[string][]*string, timeoutSeconds *int64, outputS3BucketName *string, outputS3KeyPrefix *string) (response *ssm.SendCommandOutput, err error) { params := ssm.SendCommandInput{ DocumentName: aws.String(documentName), InstanceIds: makeAwsStrings(instanceIDs), Comment: aws.String("Comment"), OutputS3BucketName: outputS3BucketName, OutputS3KeyPrefix: outputS3KeyPrefix, Parameters: parameters, TimeoutSeconds: timeoutSeconds, } log.Debug("SendCommand params:", params) response, err = svc.sdk.SendCommand(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("SendCommand Response", response) return }
// 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) }
// 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) } }
func killProcessOnTimeout(log log.T, command *exec.Cmd, timer *time.Timer) { <-timer.C log.Debug("Process exceeded timeout. Attempting to kill process!") // task has been exceeded the allowed execution timeout, kill process if err := command.Process.Kill(); err != nil { log.Error(err) return } log.Debug("Done kill process!") }
func processSendReply(log log.T, messageID string, mdsService service.Service, payloadDoc messageContracts.SendReplyPayload, processorStopPolicy *sdkutil.StopPolicy) { payloadB, err := json.Marshal(payloadDoc) if err != nil { log.Error("could not marshal reply payload!", err) } payload := string(payloadB) log.Info("Sending reply ", jsonutil.Indent(payload)) err = mdsService.SendReply(log, messageID, payload) if err != nil { sdkutil.HandleAwsError(log, err, processorStopPolicy) } }
// killProcessOnTimeout waits for a timeout. // When the timeout is reached, this method kills the underlying // process of the command. This will unblock the command.Wait() call. // If the task completed successfully this method returns with no action. func killProcessOnTimeout(log log.T, command *exec.Cmd, timer *time.Timer) { <-timer.C log.Debug("Process exceeded timeout. Attempting to stop process.") // task has been exceeded the allowed execution timeout, kill process if err := killProcess(command.Process); err != nil { log.Error(err) return } log.Debug("Process stopped successfully") }
func getPlatformDetail(log log.T, param string) (value string, err error) { var contentsBytes []byte value = notAvailableMessage log.Debugf(gettingPlatformDetailsMessage) if contentsBytes, err = exec.Command(platformDetailsCommand, param).Output(); err != nil { log.Debugf(errorOccurredMessage, platformDetailsCommand, err) return } value = strings.TrimSpace(string(contentsBytes)) log.Debugf(commandOutputMessage, value) return }
// LoadUpdateContext loads update context info from local storage, set current update with new update detail func LoadUpdateContext(log log.T, source string) (context *UpdateContext, err error) { log.Debugf("file %v", source) if _, err := os.Stat(source); os.IsNotExist(err) { log.Debugf("UpdateContext file doesn't exist, creating new one") context = &UpdateContext{} } else { log.Debugf("UpdateContext file exists") if context, err = parseContext(log, source); err != nil { return context, err } } return context, nil }
// ReplaceParameters traverses an arbitrarily complex input object (maps/slices/strings/etc.) // and tries to replace parameters given as {{parameter}} with their values from the parameters map. // // Strings like "{{ parameter }}" are replaced directly with the value associated with // the parameter. That value need not be a string. // // Strings like "a {{ parameter1 }} within a string" are replaced with strings where the parameters // are replaced by a marshaled version of their values. In this case, the resulting object is always a string. // // Note: this only works on composite types []interface{} and map[string]interface{} which are what json.Unmarshal // produces by default. If your object contains []string, for example, the object will be returned as is. // // Returns a new object with replaced parameters. func ReplaceParameters(input interface{}, parameters map[string]interface{}, logger log.T) interface{} { switch input := input.(type) { case string: // handle single parameter case first for parameterName, parameterValue := range parameters { if isSingleParameterString(input, parameterName) { return parameterValue } } // look for multiple parameter strings for parameterName, parameterValue := range parameters { var parameterValueString string var err error if parameterValueString, err = convertToString(parameterValue); err != nil { logger.Error(err) } input = ReplaceParameter(input, parameterName, parameterValueString) } return input case []interface{}: // for slices, recursively replace parameters on each element of the slice out := make([]interface{}, len(input)) for i, v := range input { out[i] = ReplaceParameters(v, parameters, logger) } return out case []map[string]interface{}: // this case is not caught by the one above because map cannot be converted to interface{} out := make([]map[string]interface{}, len(input)) for i, v := range input { out[i] = ReplaceParameters(v, parameters, logger).(map[string]interface{}) } return out case map[string]interface{}: // for maps, recursively replace parameters on each value in the map out := make(map[string]interface{}) for k, v := range input { out[k] = ReplaceParameters(v, parameters, logger) } return out default: // any other type, return as is return input } }
// DeleteMessage calls the DeleteMessage MDS API. func (mds *sdkService) DeleteMessage(log log.T, messageID string) (err error) { params := &ssmmds.DeleteMessageInput{ MessageId: aws.String(messageID), // Required } log.Debug("Calling DeleteMessage with params", params) req, resp := mds.sdk.DeleteMessageRequest(params) if err = mds.sendRequest(req); err != nil { err = fmt.Errorf("DeleteMessage Error: %v", err) log.Debug(err) } else { log.Debug("DeleteMessage Response", resp) } return }
// RebootMachine reboots the machine func RebootMachine(log log.T) { log.Info("Executing reboot request...") if RebootInitiated() { return } syncObject.Lock() defer syncObject.Unlock() if err := reboot(log); err != nil { log.Error("error in rebooting the machine", err) return } rebootInitiated = true }
// 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 }
// 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 }
// 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 }
// killProcessOnCancel waits for a cancel request. // If a cancel request is received, this method kills the underlying // process of the command. This will unblock the command.Wait() call. // If the task completed successfully this method returns with no action. func killProcessOnCancel(log log.T, command *exec.Cmd, cancelFlag task.CancelFlag) { cancelFlag.Wait() if cancelFlag.Canceled() { log.Debug("Process cancelled. Attempting to stop process.") // task has been asked to cancel, kill process if err := killProcess(command.Process); err != nil { log.Error(err) return } log.Debug("Process stopped successfully.") } }
// 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 }
func stop(log logger.T, cpm *coremanager.CoreManager) { log.Info("Stopping agent") log.Flush() cpm.Stop() log.Info("Bye.") log.Flush() }
// 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) }
// processParams smartly divides the input parameter string into valid string blocks func processParams(log log.T, str string) []string { // Sample transformation: // str = "/v value "some path" myproperty=value" // result: []string{"/v", "value", "some path", "myproperty=value"} // contains the last split location of the string lastbit := 0 params := []string{} // true if first quote was encountered else false quoteInit := false // Iterate through each character in str for i, c := range str { // Look for quotes or spaces // By default we split a string using space as a delimiter // If a quote(") is encountered then wait for the next quote irrespective of any spaces in between if c == '"' { if quoteInit { quoteInit = false params = append(params, str[lastbit:i+1]) lastbit = i + 1 } else { quoteInit = true lastbit = i } } else if c == ' ' && !quoteInit { if lastbit != i { params = append(params, str[lastbit:i]) } lastbit = i + 1 } } // This handles the last word in str if lastbit < len(str) { params = append(params, str[lastbit:len(str)]) } log.Debug("Parameters after processing...") for _, param := range params { log.Debug(param) } return params }
// FailMessage calls the FailMessage MDS API. func (mds *sdkService) FailMessage(log log.T, messageID string, failureType FailureType) (err error) { params := &ssmmds.FailMessageInput{ FailureType: aws.String(string(failureType)), // Required MessageId: aws.String(messageID), // Required } log.Debug("Calling FailMessage with params", params) req, resp := mds.sdk.FailMessageRequest(params) if err = mds.sendRequest(req); err != nil { err = fmt.Errorf("FailMessage Error: %v", err) log.Debug(err) } else { log.Debug("FailMessage Response", resp) } 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 }