package application
import (
"errors"
"strings"
"fmt"
"github.com/aws/amazon-ssm-agent/agent/context"
"github.com/aws/amazon-ssm-agent/agent/plugins/inventory/model"
)
const (
amazonPublisherName = "amazon"
amazonSsmAgentLinux = "amazon-ssm-agent"
amazonSsmAgentWin = "amazon ssm agent"
amazonSsmAgentMac = "com.amazon.aws.ssm"
awsToolsWindows = "aws tools for windows"
ec2ConfigService = "ec2configservice"
awsCfnBootstrap = "aws-cfn-bootstrap"
awsPVDrivers = "aws pv drivers"
awsAPIToolsPrefix = "aws-apitools-"
awsAMIToolsPrefix = "aws-amitools-"
maxSummaryLength = 100
)
var selectAwsApps map[string]string
var ApplicationData []model.ApplicationData
func init() {
//NOTE:
// For V1 - to filter out aws components from aws applications - we are using a list of all aws components that
// have been identified in various OS - amazon linux, ubuntu, windows etc.
// This is also useful for amazon linux ami - where all packages have Amazon.com as publisher.
selectAwsApps = make(map[string]string)
selectAwsApps[amazonSsmAgentLinux] = amazonPublisherName
selectAwsApps[amazonSsmAgentWin] = amazonPublisherName
selectAwsApps[amazonSsmAgentMac] = amazonPublisherName
selectAwsApps[awsToolsWindows] = amazonPublisherName
selectAwsApps[ec2ConfigService] = amazonPublisherName
selectAwsApps[awsCfnBootstrap] = amazonPublisherName
selectAwsApps[awsPVDrivers] = amazonPublisherName
}
func componentType(applicationName string) model.ComponentType {
formattedName := strings.TrimSpace(applicationName)
formattedName = strings.ToLower(formattedName)
var compType model.ComponentType
//check if application is a known aws component or part of aws-apitool- or aws-amitools- tool set.
if _, found := selectAwsApps[formattedName]; found || strings.Contains(formattedName, awsAPIToolsPrefix) || strings.Contains(formattedName, awsAMIToolsPrefix) {
compType |= model.AWSComponent
}
return compType
}
// CollectApplicationData collects all application data from the system using platform specific queries and merges in applications installed via configurePackage
func CollectApplicationData(context context.T) (appData []model.ApplicationData) {
if len(ApplicationData) > 0 {
return ApplicationData
}
ApplicationData = collectPlatformDependentApplicationData(context)
return ApplicationData
}
// cleanupJSONField converts a text to a json friendly text as follows:
// - converts multi-line fields to single line by removing all but the first line
// - escapes special characters
// - truncates remaining line to length no more than maxSummaryLength
func cleanupJSONField(field string) string {
res := field
endOfLinePos := strings.Index(res, "\n")
if endOfLinePos >= 0 {
res = res[0:endOfLinePos]
}
res = strings.Replace(res, `\`, `\\`, -1)
res = strings.Replace(res, `"`, `\"`, -1)
res = strings.Replace(res, "\t", `\t`, -1)
res = strings.Replace(res, "\x0D", "", -1)
if len(res) > maxSummaryLength {
res = res[0:maxSummaryLength]
}
return res
}
// replaceMarkedFields finds substrings delimited by the start and end markers,
// removes the markers, and replaces the text between the markers with the result
// of calling the fieldReplacer function on that text substring. For example, if
// the input string is: "a string with text marked"
// the startMarker is: ""
// the end marker is: ""
// and fieldReplacer is: strings.ToUpper
// then the output will be: "a string with TEXT marked"
func replaceMarkedFields(str, startMarker, endMarker string, fieldReplacer func(string) string) (newStr string, err error) {
startIndex := strings.Index(str, startMarker)
newStr = ""
for startIndex >= 0 {
newStr += str[:startIndex]
fieldStart := str[startIndex+len(startMarker):]
endIndex := strings.Index(fieldStart, endMarker)
if endIndex < 0 {
err = errors.New("Found startMarker without endMarker!")
return
}
field := fieldStart[:endIndex]
transformedField := fieldReplacer(field)
newStr += transformedField
str = fieldStart[endIndex+len(endMarker):]
startIndex = strings.Index(str, startMarker)
}
newStr += str
return newStr, nil
}
// convertEntriesToJsonArray converts a series of comma separated json objects
// to an array of objects. For example, if entries is:
// {"k1":"v1","k2":"v2"},{"s1":"t1"},
// then this method will return
// [{"k1":"v1","k2":"v2"},{"s1":"t1"}]
func convertEntriesToJsonArray(entries string) string {
//trim spaces
str := strings.TrimSpace(entries)
//remove last ',' from string
str = strings.TrimSuffix(str, ",")
//add "[" in beginning & "]" at the end to create valid json string
str = fmt.Sprintf("[%v]", str)
return str
}
// cleanupNewLines removes all newlines from the given input
func cleanupNewLines(s string) string {
return strings.Replace(strings.Replace(s, "\n", "", -1), "\r", "", -1)
}
// Clean all Ctrl code from UTF-8 string
// -- Check https://rosettacode.org/wiki/Strip_control_codes_and_extended_characters_from_a_string for more details
// about this method
// -- This method will remove all C0 control code from string and not the C1 control code. The reason is UTF-8 still
// allows you to use C1 control characters such as CSI, even though UTF-8 also uses bytes in the range 0x80-0x9F.
// -- For C0 and C1 control code details you can check http://www.cl.cam.ac.uk/~mgk25/unicode.html and
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes
func stripCtlFromUTF8(str string) string {
return strings.Map(func(r rune) rune {
if r >= 32 && r != 127 {
return r
}
return -1
}, str)
}