-
Notifications
You must be signed in to change notification settings - Fork 324
/
aggregator.go
127 lines (109 loc) · 4.96 KB
/
aggregator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// 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 contracts helps persist documents state to disk
package contracts
import (
"fmt"
"github.com/aws/amazon-ssm-agent/agent/log"
"github.com/aws/amazon-ssm-agent/agent/times"
)
// TODO move part of the function to service?
// prepareRuntimeStatus creates the structure for the runtimeStatus section of the payload of SendReply
// for a particular plugin.
func prepareRuntimeStatus(log log.T, pluginResult PluginResult) PluginRuntimeStatus {
var resultAsString string
if pluginResult.Error == "" {
resultAsString = fmt.Sprintf("%v", pluginResult.Output)
} else {
resultAsString = pluginResult.Error
}
runtimeStatus := PluginRuntimeStatus{
Code: pluginResult.Code,
Name: pluginResult.PluginName,
Status: pluginResult.Status,
Output: resultAsString,
StartDateTime: times.ToIso8601UTC(pluginResult.StartDateTime),
EndDateTime: times.ToIso8601UTC(pluginResult.EndDateTime),
StandardOutput: pluginResult.StandardOutput,
StandardError: pluginResult.StandardError,
}
if pluginResult.OutputS3BucketName != "" {
runtimeStatus.OutputS3BucketName = pluginResult.OutputS3BucketName
if pluginResult.OutputS3KeyPrefix != "" {
runtimeStatus.OutputS3KeyPrefix = pluginResult.OutputS3KeyPrefix
}
runtimeStatus.StepName = pluginResult.StepName
}
if runtimeStatus.Status == ResultStatusFailed && runtimeStatus.Code == 0 {
runtimeStatus.Code = 1
}
return runtimeStatus
}
// DocumentResultAggregator aggregates the result from the plugins to construct the agent response
func DocumentResultAggregator(log log.T,
pluginID string,
pluginOutputs map[string]*PluginResult) (documentStatus ResultStatus, runtimeStatusCounts map[string]int,
runtimeStatusesFiltered map[string]*PluginRuntimeStatus, runtimeStatuses map[string]*PluginRuntimeStatus) {
runtimeStatuses = make(map[string]*PluginRuntimeStatus)
for pluginID, pluginResult := range pluginOutputs {
rs := prepareRuntimeStatus(log, *pluginResult)
runtimeStatuses[pluginID] = &rs
}
// TODO instance this needs to be revised to be in parity with ec2config
documentStatus = ResultStatusSuccess
runtimeStatusCounts = map[string]int{}
pluginCounts := len(runtimeStatuses)
for _, pluginResult := range runtimeStatuses {
runtimeStatusCounts[string(pluginResult.Status)]++
}
if pluginID == "" {
// New precedence order of plugin states
// Failed > TimedOut > Cancelled > Success > Cancelling > InProgress > Pending
// The above order is a contract between SSM service and agent and hence for the calculation of aggregate
// status of a (command) document, we follow the above precedence order.
//
// Note:
// A command could have been failed/cancelled even before a plugin started executing, during which pendingItems > 0
// but overallResult.Status would be Failed/Cancelled. That's the reason we check for OverallResult status along
// with number of failed/cancelled items.
// TODO : We need to handle above to be able to send document traceoutput in case of document level errors.
// Skipped is a form of success
successCounts := runtimeStatusCounts[string(ResultStatusSuccess)] + runtimeStatusCounts[string(ResultStatusSkipped)]
if runtimeStatusCounts[string(ResultStatusSuccessAndReboot)] > 0 {
documentStatus = ResultStatusSuccessAndReboot
} else if runtimeStatusCounts[string(ResultStatusFailed)] > 0 {
documentStatus = ResultStatusFailed
} else if runtimeStatusCounts[string(ResultStatusTimedOut)] > 0 {
documentStatus = ResultStatusTimedOut
} else if runtimeStatusCounts[string(ResultStatusCancelled)] > 0 {
documentStatus = ResultStatusCancelled
} else if successCounts == pluginCounts {
documentStatus = ResultStatusSuccess
} else {
documentStatus = ResultStatusInProgress
}
} else {
documentStatus = ResultStatusInProgress
}
runtimeStatusesFiltered = make(map[string]*PluginRuntimeStatus)
// When pluginID isn't empty, the agent is sending update information about the current plugin.
// When it's empty, the agent is sending update information about the document.
// When it's empty, the agent only sends plugin status if it's a single plugin invocation.
if pluginID != "" {
runtimeStatusesFiltered[pluginID] = runtimeStatuses[pluginID]
runtimeStatuses = runtimeStatusesFiltered
} else if len(runtimeStatuses) <= 1 {
runtimeStatusesFiltered = runtimeStatuses
}
return documentStatus, runtimeStatusCounts, runtimeStatusesFiltered, runtimeStatuses
}