// processCancelCommandMessage processes a single send command message received from MDS. func (p *Processor) processCancelCommandMessage(context context.T, mdsService service.Service, sendCommandPool task.Pool, msg ssmmds.Message) { log := context.Log() log.Debug("Processing cancel command message ", jsonutil.Indent(*msg.Payload)) var parsedMessage messageContracts.CancelPayload err := json.Unmarshal([]byte(*msg.Payload), &parsedMessage) if err != nil { log.Error("format of received cancel message is invalid ", err) err = mdsService.FailMessage(log, *msg.MessageId, service.InternalHandlerException) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) } return } log.Debugf("ParsedMessage is %v", parsedMessage) //persist in current folder here cancelCmd := initializeCancelCommandState(msg, parsedMessage) commandID := getCommandID(*msg.MessageId) // persist new interim command state in current folder commandStateHelper.PersistData(log, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent, cancelCmd) //remove from pending folder commandStateHelper.RemoveData(log, commandID, *msg.Destination, appconfig.DefaultLocationOfPending) log.Debugf("Canceling job with id %v...", parsedMessage.CancelMessageID) if found := sendCommandPool.Cancel(parsedMessage.CancelMessageID); !found { log.Debugf("Job with id %v not found (possibly completed)", parsedMessage.CancelMessageID) cancelCmd.DebugInfo = fmt.Sprintf("Command %v couldn't be cancelled", cancelCmd.CancelCommandID) cancelCmd.Status = contracts.ResultStatusFailed } else { cancelCmd.DebugInfo = fmt.Sprintf("Command %v cancelled", cancelCmd.CancelCommandID) cancelCmd.Status = contracts.ResultStatusSuccess } //persist the final status of cancel-message in current folder commandStateHelper.PersistData(log, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent, cancelCmd) //persist : commands execution in completed folder (terminal state folder) log.Debugf("Execution of %v is over. Moving interimState file from Current to Completed folder", *msg.MessageId) commandStateHelper.MoveCommandState(log, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent, appconfig.DefaultLocationOfCompleted) log.Debugf("Deleting message") err = mdsService.DeleteMessage(log, *msg.MessageId) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) } }
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 }
//UpdateInstanceInformation calls the UpdateInstanceInformation SSM API. func (svc *sdkService) UpdateInstanceInformation( log log.T, agentVersion string, agentStatus string, ) (response *ssm.UpdateInstanceInformationOutput, err error) { params := ssm.UpdateInstanceInformationInput{ AgentStatus: aws.String(agentStatus), AgentVersion: aws.String(agentVersion), } goOS := runtime.GOOS switch goOS { case "windows": params.PlatformType = aws.String(ssm.PlatformTypeWindows) case "linux": params.PlatformType = aws.String(ssm.PlatformTypeLinux) default: return nil, fmt.Errorf("Cannot report platform type of unrecognized OS. %v", goOS) } if ip, err := platform.IP(); err == nil { params.IPAddress = aws.String(ip) } else { log.Warn(err) } if h, err := platform.Hostname(); err == nil { params.ComputerName = aws.String(h) } else { log.Warn(err) } if instID, err := platform.InstanceID(); err == nil { params.InstanceId = aws.String(instID) } else { log.Warn(err) } if n, err := platform.PlatformName(log); err == nil { params.PlatformName = aws.String(n) } else { log.Warn(err) } if v, err := platform.PlatformVersion(log); err == nil { params.PlatformVersion = aws.String(v) } else { log.Warn(err) } log.Debug("Calling UpdateInstanceInformation with params", params) response, err = svc.sdk.UpdateInstanceInformation(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("UpdateInstanceInformation Response", response) return }
func (p *Processor) processMessage(msg *ssmmds.Message) { // create separate logger that includes messageID with every log message context := p.context.With("[messageID=" + *msg.MessageId + "]") log := context.Log() log.Debug("Processing message") if err := validate(msg); err != nil { log.Error("message not valid, ignoring: ", err) return } err := p.service.AcknowledgeMessage(log, *msg.MessageId) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) return } log.Debugf("Ack done. Received message - messageId - %v, MessageString - %v", *msg.MessageId, msg.GoString()) //persisting received msg in file-system [pending folder] p.persistData(msg, appconfig.DefaultLocationOfPending) log.Debugf("Processing to send a reply to update the document status to InProgress") p.sendDocLevelResponse(*msg.MessageId, contracts.ResultStatusInProgress, "") log.Debugf("SendReply done. Received message - messageId - %v, MessageString - %v", *msg.MessageId, msg.GoString()) switch { case strings.HasPrefix(*msg.Topic, string(SendCommandTopicPrefix)): err := p.sendCommandPool.Submit(log, *msg.MessageId, func(cancelFlag task.CancelFlag) { p.processSendCommandMessage(context, p.service, p.orchestrationRootDir, p.pluginRunner, cancelFlag, p.buildReply, p.sendResponse, *msg) }) if err != nil { log.Error("SendCommand failed", err) return } case strings.HasPrefix(*msg.Topic, string(CancelCommandTopicPrefix)): err := p.cancelCommandPool.Submit(log, *msg.MessageId, func(cancelFlag task.CancelFlag) { p.processCancelCommandMessage(context, p.service, p.sendCommandPool, *msg) }) if err != nil { log.Error("CancelCommand failed", err) return } default: log.Error("unexpected topic name ", *msg.Topic) } }
// updates SSM with the instance health information func (h *HealthCheck) updateHealth() { log := h.context.Log() log.Infof("%s reporting agent health.", name) var err error //TODO when will status become inactive? // If both ssm config and command is inactive => agent is inactive. if _, err = h.service.UpdateInstanceInformation(log, version.Version, "Active"); err != nil { sdkutil.HandleAwsError(log, err, h.healthCheckStopPolicy) } return }
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) } }
func (svc *sdkService) DeleteDocument(log log.T, docName string) (response *ssm.DeleteDocumentOutput, err error) { params := ssm.DeleteDocumentInput{ Name: aws.String(docName), // Required } response, err = svc.sdk.DeleteDocument(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("DeleteDocument Response", response) return }
func (svc *sdkService) CreateDocument(log log.T, docName string, docContent string) (response *ssm.CreateDocumentOutput, err error) { params := ssm.CreateDocumentInput{ Content: aws.String(docContent), Name: aws.String(docName), } response, err = svc.sdk.CreateDocument(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("CreateDocument Response", response) return }
// pollOnce calls GetMessages once and processes the result. func (p *Processor) pollOnce() { log := p.context.Log() log.Debugf("Polling for messages") messages, err := p.service.GetMessages(log, p.config.InstanceID) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) return } log.Debugf("Got %v messages", len(messages.Messages)) for _, msg := range messages.Messages { processMessage(p, msg) } log.Debugf("Done poll once") }
func (svc *sdkService) CancelCommand(log log.T, commandID string, instanceIDs []string) (response *ssm.CancelCommandOutput, err error) { params := ssm.CancelCommandInput{ CommandId: aws.String(commandID), } if len(instanceIDs) > 0 { params.InstanceIds = makeAwsStrings(instanceIDs) } log.Debug("CancelCommand params:", params) response, err = svc.sdk.CancelCommand(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("CancelCommand Response", response) return }
//ListAssociations calls the ListAssociations SSM API. func (svc *sdkService) ListAssociations(log log.T, instanceID string) (response *ssm.ListAssociationsOutput, err error) { params := ssm.ListAssociationsInput{ AssociationFilterList: []*ssm.AssociationFilter{ { Key: aws.String("InstanceId"), Value: aws.String(instanceID), }, }, MaxResults: aws.Int64(1), } response, err = svc.sdk.ListAssociations(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("ListAssociations Response", response) return }
func (svc *sdkService) ListCommands(log log.T, instanceID string) (response *ssm.ListCommandsOutput, err error) { params := ssm.ListCommandsInput{ // Filters: []*ssm.CommandFilter{ // { // Required // Key: aws.String("CommandFilterKey"), // Required // Value: aws.String("CommandFilterValue"), // Required // }, // }, InstanceId: aws.String(instanceID), MaxResults: aws.Int64(25), } response, err = svc.sdk.ListCommands(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("ListCommands Response", response) return }
func (svc *sdkService) ListCommandInvocations(log log.T, instanceID string, commandID string) (response *ssm.ListCommandInvocationsOutput, err error) { params := ssm.ListCommandInvocationsInput{ CommandId: aws.String(commandID), Details: aws.Bool(true), // Filters: []*ssm.CommandFilter{ // { // Required // Key: aws.String("CommandFilterKey"), // Required // Value: aws.String("CommandFilterValue"), // Required // }, // // More values... // }, InstanceId: aws.String(instanceID), MaxResults: aws.Int64(25), // NextToken: aws.String("NextToken"), } response, err = svc.sdk.ListCommandInvocations(¶ms) if err != nil { sdkutil.HandleAwsError(log, err, ssmStopPolicy) return } log.Debug("ListCommandInvocations Response", response) return }
// processSendCommandMessage processes a single send command message received from MDS. func (p *Processor) processSendCommandMessage(context context.T, mdsService service.Service, messagesOrchestrationRootDir string, runPlugins PluginRunner, cancelFlag task.CancelFlag, buildReply replyBuilder, sendResponse engine.SendResponse, msg ssmmds.Message) { commandID := getCommandID(*msg.MessageId) log := context.Log() log.Debug("Processing send command message ", *msg.MessageId) log.Trace("Processing send command message ", jsonutil.Indent(*msg.Payload)) parsedMessage, err := parser.ParseMessageWithParams(log, *msg.Payload) if err != nil { log.Error("format of received message is invalid ", err) err = mdsService.FailMessage(log, *msg.MessageId, service.InternalHandlerException) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) } return } parsedMessageContent, _ := jsonutil.Marshal(parsedMessage) log.Debug("ParsedMessage is ", jsonutil.Indent(parsedMessageContent)) // adapt plugin configuration format from MDS to plugin expected format s3KeyPrefix := path.Join(parsedMessage.OutputS3KeyPrefix, parsedMessage.CommandID, *msg.Destination) messageOrchestrationDirectory := filepath.Join(messagesOrchestrationRootDir, commandID) // Check if it is a managed instance and its executing managed instance incompatible AWS SSM public document. // A few public AWS SSM documents contain code which is not compatible when run on managed instances. // isManagedInstanceIncompatibleAWSSSMDocument makes sure to find such documents at runtime and replace the incompatible code. isMI, err := isManagedInstance() if err != nil { log.Errorf("Error determining managed instance. error: %v", err) } if isMI && isManagedInstanceIncompatibleAWSSSMDocument(parsedMessage.DocumentName) { log.Debugf("Running Incompatible AWS SSM Document %v on managed instance", parsedMessage.DocumentName) if parsedMessage, err = removeDependencyOnInstanceMetadataForManagedInstance(context, parsedMessage); err != nil { return } } pluginConfigurations := getPluginConfigurations( parsedMessage.DocumentContent.RuntimeConfig, messageOrchestrationDirectory, parsedMessage.OutputS3BucketName, s3KeyPrefix, *msg.MessageId) //persist : all information in current folder log.Info("Persisting message in current execution folder") //Data format persisted in Current Folder is defined by the struct - CommandState interimCmdState := initializeCommandState(pluginConfigurations, msg, parsedMessage) // persist new interim command state in current folder commandStateHelper.PersistData(log, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent, interimCmdState) //Deleting from pending folder since the command is getting executed commandStateHelper.RemoveData(log, commandID, *msg.Destination, appconfig.DefaultLocationOfPending) log.Debug("Running plugins...") outputs := runPlugins(context, *msg.MessageId, pluginConfigurations, sendResponse, cancelFlag) pluginOutputContent, _ := jsonutil.Marshal(outputs) log.Debugf("Plugin outputs %v", jsonutil.Indent(pluginOutputContent)) payloadDoc := buildReply("", outputs) //check if document isn't supported by SSM -> update the DocumentLevel status message & send reply accordingly ssmDocName := parsedMessage.DocumentName if IsDocumentNotSupportedBySsmAgent(ssmDocName) { log.Infof("%s is not yet supported by aws-ssm-agent, setting up Document level response accordingly", ssmDocName) payloadDoc.DocumentTraceOutput = fmt.Sprintf("%s document is not yet supported by amazon-ssm-agent.", ssmDocName) p.sendDocLevelResponse(*msg.MessageId, contracts.ResultStatusFailed, payloadDoc.DocumentTraceOutput) } //update documentInfo in interim cmd state file documentInfo := commandStateHelper.GetDocumentInfo(log, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent) // set document level information which wasn't set previously documentInfo.AdditionalInfo = payloadDoc.AdditionalInfo documentInfo.DocumentStatus = payloadDoc.DocumentStatus documentInfo.DocumentTraceOutput = payloadDoc.DocumentTraceOutput documentInfo.RuntimeStatus = payloadDoc.RuntimeStatus //persist final documentInfo. commandStateHelper.PersistDocumentInfo(log, documentInfo, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent) // Skip sending response when the document requires a reboot if documentInfo.DocumentStatus == contracts.ResultStatusSuccessAndReboot { log.Debug("skipping sending response of %v since the document requires a reboot", *msg.MessageId) return } log.Debug("Sending reply on message completion ", outputs) sendResponse(*msg.MessageId, "", outputs) //persist : commands execution in completed folder (terminal state folder) log.Debugf("execution of %v is over. Moving interimState file from Current to Completed folder", *msg.MessageId) commandStateHelper.MoveCommandState(log, commandID, *msg.Destination, appconfig.DefaultLocationOfCurrent, appconfig.DefaultLocationOfCompleted) log.Debugf("Deleting message") isUpdate := false for pluginName := range pluginConfigurations { if pluginName == appconfig.PluginNameAwsAgentUpdate { isUpdate = true } } if !isUpdate { err = mdsService.DeleteMessage(log, *msg.MessageId) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) } } else { log.Debug("MessageDeletion skipped as it will be handled by external process") } }
// runCmdsUsingCmdState takes commandState as an input and executes only those plugins which haven't yet executed. This is functionally // very similar to processSendCommandMessage because everything to do with cmd execution is part of that function right now. func (p *Processor) runCmdsUsingCmdState(context context.T, mdsService service.Service, runPlugins PluginRunner, cancelFlag task.CancelFlag, buildReply replyBuilder, sendResponse engine.SendResponse, command messageContracts.CommandState) { log := context.Log() var pluginConfigurations map[string]*contracts.Configuration pendingPlugins := false pluginConfigurations = make(map[string]*contracts.Configuration) //iterate through all plugins to find all plugins that haven't executed yet. for k, v := range command.PluginsInformation { if v.HasExecuted { log.Debugf("skipping execution of Plugin - %v of command - %v since it has already executed.", k, command.DocumentInformation.CommandID) } else { log.Debugf("Plugin - %v of command - %v will be executed", k, command.DocumentInformation.CommandID) pluginConfigurations[k] = &v.Configuration pendingPlugins = true } } //execute plugins that haven't been executed yet //individual plugins after execution will update interim cmd state file accordingly if pendingPlugins { log.Debugf("executing following plugins of command - %v", command.DocumentInformation.CommandID) for k := range pluginConfigurations { log.Debugf("Plugin: %v", k) } //Since only some plugins of a cmd gets executed here - there is no need to get output from engine & construct the sendReply output. //Instead after all plugins of a command get executed, use persisted data to construct sendReply payload runPlugins(context, command.DocumentInformation.MessageID, pluginConfigurations, sendResponse, cancelFlag) } //read from persisted file newCmdState := commandStateHelper.GetCommandInterimState(log, command.DocumentInformation.CommandID, command.DocumentInformation.Destination, appconfig.DefaultLocationOfCurrent) //construct sendReply payload outputs := make(map[string]*contracts.PluginResult) for k, v := range newCmdState.PluginsInformation { outputs[k] = &v.Result } pluginOutputContent, _ := jsonutil.Marshal(outputs) log.Debugf("plugin outputs %v", jsonutil.Indent(pluginOutputContent)) payloadDoc := buildReply("", outputs) //update interim cmd state file with document level information var documentInfo messageContracts.DocumentInfo // set document level information which wasn't set previously documentInfo.AdditionalInfo = payloadDoc.AdditionalInfo documentInfo.DocumentStatus = payloadDoc.DocumentStatus documentInfo.DocumentTraceOutput = payloadDoc.DocumentTraceOutput documentInfo.RuntimeStatus = payloadDoc.RuntimeStatus //persist final documentInfo. commandStateHelper.PersistDocumentInfo(log, documentInfo, command.DocumentInformation.CommandID, command.DocumentInformation.Destination, appconfig.DefaultLocationOfCurrent) // Skip sending response when the document requires a reboot if documentInfo.DocumentStatus == contracts.ResultStatusSuccessAndReboot { log.Debug("skipping sending response of %v since the document requires a reboot", newCmdState.DocumentInformation.MessageID) return } //send document level reply log.Debug("sending reply on message completion ", outputs) sendResponse(command.DocumentInformation.MessageID, "", outputs) //persist : commands execution in completed folder (terminal state folder) log.Debugf("execution of %v is over. Moving interimState file from Current to Completed folder", newCmdState.DocumentInformation.MessageID) commandStateHelper.MoveCommandState(log, newCmdState.DocumentInformation.CommandID, newCmdState.DocumentInformation.Destination, appconfig.DefaultLocationOfCurrent, appconfig.DefaultLocationOfCompleted) log.Debugf("deleting message") isUpdate := false for pluginName := range pluginConfigurations { if pluginName == appconfig.PluginNameAwsAgentUpdate { isUpdate = true } } if !isUpdate { err := mdsService.DeleteMessage(log, newCmdState.DocumentInformation.MessageID) if err != nil { sdkutil.HandleAwsError(log, err, p.processorStopPolicy) } } else { log.Debug("messageDeletion skipped as it will be handled by external process") } }