// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not // use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, // either express or implied. See the License for the specific language governing // permissions and limitations under the License. // Package processor contains the methods for update ssm agent. // It also provides methods for sendReply and updateInstanceInfo package processor import ( "encoding/json" "fmt" "sync" "time" "github.com/aws/amazon-ssm-agent/agent/appconfig" "github.com/aws/amazon-ssm-agent/agent/context" "github.com/aws/amazon-ssm-agent/agent/contracts" "github.com/aws/amazon-ssm-agent/agent/framework/processor/executer/iohandler" "github.com/aws/amazon-ssm-agent/agent/log" messageContracts "github.com/aws/amazon-ssm-agent/agent/runcommand/contracts" messageService "github.com/aws/amazon-ssm-agent/agent/runcommand/mds" "github.com/aws/amazon-ssm-agent/agent/times" "github.com/aws/amazon-ssm-agent/agent/updateutil/updateconstants" ) var msgSvc messageService.Service var msgSvcOnce sync.Once var newMsgSvc = messageService.NewService var getAppConfig = appconfig.Config // Service is an interface represents for SendReply, UpdateInstanceInfo type Service interface { SendReply(log log.T, update *UpdateDetail) error DeleteMessage(log log.T, update *UpdateDetail) error UpdateHealthCheck(log log.T, update *UpdateDetail, errorCode string) error } type svcManager struct { context context.T } // SendReply sends message back to the service func (s *svcManager) SendReply(log log.T, update *UpdateDetail) (err error) { var svc messageService.Service payloadB := []byte{} value := prepareReplyPayload(s.context, update) if payloadB, err = json.Marshal(value); err != nil { return fmt.Errorf("could not marshal reply payload %v", err.Error()) } if svc, err = getMsgSvc(s.context); err != nil { return fmt.Errorf("could not load message service %v", err.Error()) } payload := string(payloadB) return svc.SendReply(log, update.MessageID, payload) } // DeleteMessage calls the DeleteMessage MDS API. func (s *svcManager) DeleteMessage(log log.T, update *UpdateDetail) (err error) { var svc messageService.Service if svc, err = getMsgSvc(s.context); err != nil { return fmt.Errorf("could not load message service %v", err) } return svc.DeleteMessage(log, update.MessageID) } // getMsgSvc gets cached message service func getMsgSvc(context context.T) (svc messageService.Service, err error) { msgSvcOnce.Do(func() { connectionTimeout := time.Duration(context.AppConfig().Mds.StopTimeoutMillis) * time.Millisecond msgSvc = newMsgSvc( context, connectionTimeout) }) if msgSvc == nil { return nil, fmt.Errorf("couldn't create message service") } return msgSvc, nil } // prepareReplyPayload setups the reply payload func prepareReplyPayload(context context.T, update *UpdateDetail) (payload *messageContracts.SendReplyPayload) { config := context.AppConfig() runtimeStatuses := make(map[string]*contracts.PluginRuntimeStatus) rs := prepareRuntimeStatus(update) if isV22DocUpdate(context.Identity(), context.Log(), update) { rs.Name = appconfig.PluginNameAwsAgentUpdate runtimeStatuses[updateconstants.DefaultOutputFolder] = &rs } else { runtimeStatuses[appconfig.PluginNameAwsAgentUpdate] = &rs } agentInfo := contracts.AgentInfo{ Lang: config.Os.Lang, Name: config.Agent.Name, Version: config.Agent.Version, Os: config.Os.Name, OsVersion: config.Os.Version, } payload = &messageContracts.SendReplyPayload{ AdditionalInfo: contracts.AdditionalInfo{ Agent: agentInfo, DateTime: times.ToIso8601UTC(time.Now()), }, DocumentStatus: rs.Status, DocumentTraceOutput: "", RuntimeStatus: runtimeStatuses, } return payload } // prepareRuntimeStatus creates the structure for the runtimeStatus section of the payload of SendReply // for a particular plugin. func prepareRuntimeStatus(update *UpdateDetail) contracts.PluginRuntimeStatus { // Set default as failed, this will help us catch issues more proactively pluginStatus := update.Result code := 0 if pluginStatus == contracts.ResultStatusFailed { code = 1 } output := iohandler.TruncateOutput(update.StandardOut, update.StandardError, iohandler.MaximumPluginOutputSize) return contracts.PluginRuntimeStatus{ Code: code, Status: pluginStatus, Output: output, OutputS3BucketName: update.OutputS3BucketName, OutputS3KeyPrefix: update.OutputS3KeyPrefix, StartDateTime: times.ToIso8601UTC(update.StartDateTime), EndDateTime: times.ToIso8601UTC(time.Now()), } } // prepareAgentResult prepares the payload for MGS update replies. func prepareAgentResult(context context.T, update *UpdateDetail) contracts.DocumentResult { return contracts.DocumentResult{ MessageID: update.MessageID, Status: update.Result, NPlugins: 1, PluginResults: preparePluginResults(context, update), ResultType: contracts.RunCommandResult, RelatedDocumentType: contracts.SendCommand, } } // prepareRuntimeStatus creates the structure for the pluginResult section of the payload of and MGS update reply. func preparePluginResults(context context.T, update *UpdateDetail) map[string]*contracts.PluginResult { code := 0 if update.Result == contracts.ResultStatusFailed { code = 1 } output := iohandler.TruncateOutput(update.StandardOut, update.StandardError, iohandler.MaximumPluginOutputSize) pluginResult := &contracts.PluginResult{ Status: update.Result, Code: code, Output: output, StartDateTime: update.StartDateTime, EndDateTime: time.Now(), OutputS3BucketName: update.OutputS3BucketName, OutputS3KeyPrefix: update.OutputS3KeyPrefix, StandardOutput: update.StandardOut, StandardError: update.StandardError, } pluginResults := make(map[string]*contracts.PluginResult) if isV22DocUpdate(context.Identity(), context.Log(), update) { pluginResult.PluginName = appconfig.PluginNameAwsAgentUpdate pluginResults[updateconstants.DefaultOutputFolder] = pluginResult } else { pluginResults[appconfig.PluginNameAwsAgentUpdate] = pluginResult } return pluginResults }