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) }