// 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 model contains contracts for inventory package model import ( "sort" "strings" "github.com/aws/amazon-ssm-agent/agent/versionutil" ) const ( // AWSInstanceInformation is inventory type of instance information AWSInstanceInformation = "AWS:InstanceInformation" // Enabled represents constant string used to enable various components of inventory plugin Enabled = "Enabled" // ErrorThreshold represents error threshold for inventory plugin ErrorThreshold = 10 // InventoryPolicyDocName represents name of inventory policy doc InventoryPolicyDocName = "policy.json" // SizeLimitKBPerInventoryType represents size limit in KB for 1 inventory data type // Bump up to 3MB for agent. We have more strict size limit rule in the micro service. SizeLimitKBPerInventoryType = 3072 // TotalSizeLimitKB represents size limit in KB for 1 PutInventory API call TotalSizeLimitKB = 10240 // Standard name for 64-bit architecture Arch64Bit = "x86_64" // Standard name for 32-bit architecture Arch32Bit = "i386" ) // Item encapsulates an inventory item type Item struct { Name string //content depends on inventory type - hence set as interface{} here. //e.g: for application - it will contain []ApplicationData, //for instanceInformation - it will contain []InstanceInformation. Content interface{} ContentHash string SchemaVersion string CaptureTime string } // InstanceInformation captures all attributes present in AWS:InstanceInformation inventory type type InstanceInformation struct { AgentStatus string AgentVersion string ComputerName string PlatformName string PlatformType string PlatformVersion string // SSM Inventory expects it InstanceId and not InstanceID InstanceId string // SSM Inventory expects it IpAddress and not IPAddress IpAddress string } // ComponentType is a flags enum that data providers can set and gatherers can filter on type ComponentType uint const ( AWSComponent ComponentType = 1 << iota ) // ApplicationData captures all attributes present in AWS:Application inventory type type ApplicationData struct { Name string Publisher string Version string Release string `json:",omitempty"` Epoch string `json:",omitempty"` InstalledTime string `json:",omitempty"` ApplicationType string `json:",omitempty"` Architecture string URL string `json:",omitempty"` Summary string `json:",omitempty"` PackageId string `json:",omitempty"` CompType ComponentType `json:"-"` } // FileData captures all attributes present in AWS:File inventory type type FileData struct { Name string Size string Description string FileVersion string InstalledDate string ModificationTime string LastAccessTime string ProductName string InstalledDir string CompanyName string ProductVersion string ProductLanguage string } type RoleData struct { Name string DisplayName string Description string Installed string InstalledState string FeatureType string Path string SubFeatures string ServerComponentDescriptor string DependsOn string Parent string } type ServiceData struct { Name string DisplayName string Status string DependentServices string ServicesDependedOn string ServiceType string StartType string } type RegistryData struct { ValueName string ValueType string KeyPath string Value string } // BillingInfoData captures all attributes present in AWS:BillingInfo inventory type type BillingInfoData struct { BillingProductId string } // NetworkData captures all attributes present in AWS:Network inventory type type NetworkData struct { Name string SubnetMask string `json:",omitempty"` Gateway string `json:",omitempty"` DHCPServer string `json:",omitempty"` DNSServer string `json:",omitempty"` MacAddress string IPV4 string IPV6 string } // WindowsUpdateData captures all attributes present in AWS:WindowsUpdate inventory type type WindowsUpdateData struct { // SSM Inventory expects it HotFixId and not HotFixID HotFixId string Description string InstalledTime string InstalledBy string } // InstanceDetailedInformation captures all attributes present in AWS:InstanceDetailedInformation inventory type type InstanceDetailedInformation struct { CPUModel string CPUSpeedMHz string CPUs string CPUSockets string CPUCores string CPUHyperThreadEnabled string OSServicePack string KernelVersion string } // Config captures all various properties (including optional) that can be supplied to a gatherer. // NOTE: Not all properties will be applicable to all gatherers. // E.g: Applications gatherer uses Collection, Files use Filters, Custom uses Collection & Location. type Config struct { Collection string `json:"Collection"` Filters string `json:"Filters"` Location string `json:"Location"` } // Policy defines how an inventory policy document looks like type Policy struct { InventoryPolicy map[string]Config } // CustomInventoryItem represents the schema of custom inventory item type CustomInventoryItem struct { TypeName string SchemaVersion string Content interface{} } // FormatArchitecture converts different architecture values to the standard inventory value func FormatArchitecture(arch string) string { arch = strings.ToLower(strings.TrimSpace(arch)) if arch == "amd64" { return Arch64Bit } if arch == "386" { return Arch32Bit } return arch } // ByNamePublisherVersion implements sorting ApplicationData elements by name (case insensitive) then by publisher (case insensitive) then version (by component) type ByNamePublisherVersion []ApplicationData func (s ByNamePublisherVersion) Len() int { return len(s) } func (s ByNamePublisherVersion) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByNamePublisherVersion) Less(i, j int) bool { //we need to compare string by ignoring it's case return compareApplicationData(s[i], s[j], true) < 0 } func compareApplicationData(this ApplicationData, other ApplicationData, strictSort bool) int { if nameResult := compareName(this.Name, other.Name); nameResult != 0 { return nameResult } if publisherResult := comparePublisher(this.Publisher, other.Publisher, strictSort); publisherResult != 0 { return publisherResult } return versionutil.Compare(this.Version, other.Version, strictSort) } func compareName(this string, other string) int { return strings.Compare(strings.ToLower(this), strings.ToLower(other)) } func comparePublisher(this string, other string, strictSort bool) int { if !strictSort && (this == "" || other == "") { // If either publisher is blank and this isn't a strict sort, assume a match because publisher isn't required return 0 } else { return strings.Compare(strings.ToLower(this), strings.ToLower(other)) } } // MergeLists combines a list of application data from a secondary source with a list from a primary source and returns a sorted result func MergeLists(primary []ApplicationData, secondary []ApplicationData) []ApplicationData { //sorts the data based on application-name sort.Sort(ByNamePublisherVersion(primary)) sort.Sort(ByNamePublisherVersion(secondary)) if len(primary) == 0 { return secondary } if len(secondary) == 0 { return primary } //merge the arrays result := make([]ApplicationData, 0) indexPrimary := 0 indexSecondary := 0 for indexPrimary < len(primary) && indexSecondary < len(secondary) { compareResult := compareApplicationData(primary[indexPrimary], secondary[indexSecondary], false) switch { case compareResult < 0: result = append(result, primary[indexPrimary]) indexPrimary++ case compareResult > 0: result = append(result, secondary[indexSecondary]) indexSecondary++ default: result = append(result, mergeItems(primary[indexPrimary], secondary[indexSecondary])) indexPrimary++ indexSecondary++ } } // append any remaining primary items if indexPrimary < len(primary) { result = append(result, primary[indexPrimary:]...) } // append any remaining secondary items if indexSecondary < len(secondary) { result = append(result, secondary[indexSecondary:]...) } return result } // mergeItems merges values from a secondary source of application data into a matching primary source func mergeItems(primary ApplicationData, secondary ApplicationData) ApplicationData { merged := primary if primary.ApplicationType == "" { merged.ApplicationType = secondary.ApplicationType } if primary.Architecture == "" { merged.Architecture = secondary.Architecture } if primary.CompType == ComponentType(0) { merged.CompType = secondary.CompType } if primary.InstalledTime == "" { merged.InstalledTime = secondary.InstalledTime } if primary.Publisher == "" { merged.Publisher = secondary.Publisher } if primary.URL == "" { merged.URL = secondary.URL } return merged }