agent/platform/platform.go (219 lines of code) (raw):

// 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 platform contains platform specific utilities. package platform import ( "fmt" "net" "sort" "sync" "time" "unicode/utf8" "github.com/aws/amazon-ssm-agent/agent/log" ) const ( gettingPlatformDetailsMessage = "getting platform details" notAvailableMessage = "NotAvailable" commandOutputMessage = "Command output %v" //map keys for cached platform data platformNameKey = "platform_name" platformTypeKey = "platform_type" platformVersionKey = "platform_version" platformSkuKey = "platform_sku" ) var ( getPlatformDataFn = getPlatformData cache = InitCache(time.Hour.Milliseconds()) ) // IsPlatformWindowsServer2012OrEarlier represents whether it is Windows 2012 and earlier or not func IsPlatformWindowsServer2012OrEarlier(log log.T) (bool, error) { return isPlatformWindowsServer2012OrEarlier(log) } // IsPlatformWindowsServer2025OrLater returns true if current platform is Windows Server 2025 or later func IsPlatformWindowsServer2025OrLater(log log.T) (bool, error) { return isPlatformWindowsServer2025OrLater(log) } // IsWindowsServer2025OrLater returns true if passed platformVersion is the same as of Windows Server 2025 or later func IsWindowsServer2025OrLater(platformVersion string, log log.T) (bool, error) { return isWindowsServer2025OrLater(platformVersion, log) } func IsPlatformNanoServer(log log.T) (bool, error) { return isPlatformNanoServer(log) } // PlatformName gets the OS specific platform name. func PlatformName(log log.T) (name string, err error) { // get cached value if exists if platformName, found := cache.Get(platformNameKey); found { return platformName, nil } return retrievePlatformName(log) } func retrievePlatformName(log log.T) (string, error) { if platformData, err := initPlatformDataCache(log); err != nil { return platformData.Name, err } else { name := platformData.Name platformName := "" for i := range name { runeVal, _ := utf8.DecodeRuneInString(name[i:]) if runeVal == utf8.RuneError { // runeVal = rune(value[i]) - using this will convert \xa9 to valid unicode code point continue } platformName = platformName + fmt.Sprintf("%c", runeVal) } return platformName, nil } } // PlatformVersion gets the OS specific platform version. func PlatformVersion(log log.T) (version string, err error) { // get cached value if exists if platformVersion, found := cache.Get(platformVersionKey); found { return platformVersion, nil } // cache platform data platformData, err := initPlatformDataCache(log) return platformData.Version, err } // PlatformSku gets the OS specific platform SKU number func PlatformSku(log log.T) (string, error) { // get cached value if exists if platformSku, found := cache.Get(platformSkuKey); found { return platformSku, nil } // cache platform data platformData, err := initPlatformDataCache(log) return platformData.Sku, err } // PlatformType gets the OS specific platform type. func PlatformType(log log.T) string { // get cached value if exists if platformType, found := cache.Get(platformTypeKey); found { return platformType } // cache platform data platformData, _ := initPlatformDataCache(log) return platformData.Type } func initPlatformDataCache(log log.T) (platformData PlatformData, err error) { if platformData, err = getPlatformDataFn(log); err == nil { cache.Put(platformNameKey, platformData.Name) cache.Put(platformVersionKey, platformData.Version) cache.Put(platformSkuKey, platformData.Sku) cache.Put(platformTypeKey, platformData.Type) } else { log.Warnf("Failed to get platform data: %v", err) } return platformData, err } func GetSystemInfo(log log.T, paramKey string) (string, error) { // get cached value if exists if systemInfo, found := cache.Get(paramKey); found { return systemInfo, nil } // cache system info return initSystemInfoCache(log, paramKey) } // Hostname of the computer. func Hostname(log log.T) string { return fullyQualifiedDomainName(log) } // IP of the network interface func IP() (selected string, err error) { var interfaces []net.Interface if interfaces, err = net.Interfaces(); err == nil { interfaces = filterInterface(interfaces) sort.Sort(byIndex(interfaces)) candidates := make([]net.IP, 0) for _, i := range interfaces { var addrs []net.Addr if addrs, err = i.Addrs(); err != nil { continue } for _, addr := range addrs { switch v := addr.(type) { case *net.IPAddr: candidates = append(candidates, v.IP.To4()) candidates = append(candidates, v.IP.To16()) case *net.IPNet: candidates = append(candidates, v.IP.To4()) candidates = append(candidates, v.IP.To16()) } } } selectedIp, err := selectIp(candidates) if err == nil { selected = selectedIp.String() } } else { err = fmt.Errorf("failed to load network interfaces: %v", err) } if err != nil { err = fmt.Errorf("failed to determine IP address: %v", err) } return } // Selects a single IP address to be reported for this instance. func selectIp(candidates []net.IP) (result net.IP, err error) { for _, ip := range candidates { if ip != nil && !ip.IsUnspecified() { if result == nil { result = ip } else if isLoopbackOrLinkLocal(result) { // Prefer addresses that are not loopbacks or link-local if !isLoopbackOrLinkLocal(ip) { result = ip } } else if !isLoopbackOrLinkLocal(ip) { // Among addresses that are not loopback or link-local, prefer IPv4 if !isIpv4(result) && isIpv4(ip) { result = ip } } } } if result == nil { err = fmt.Errorf("no IP addresses found") } return } func isLoopbackOrLinkLocal(ip net.IP) bool { return ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() } func isIpv4(ip net.IP) bool { return ip.To4() != nil } // filterInterface removes interface that's not up or is a loopback/p2p func filterInterface(interfaces []net.Interface) (i []net.Interface) { for _, v := range interfaces { if (v.Flags&net.FlagUp != 0) && (v.Flags&net.FlagLoopback == 0) && (v.Flags&net.FlagPointToPoint == 0) { i = append(i, v) } } return } // byIndex implements sorting for net.Interface. type byIndex []net.Interface func (b byIndex) Len() int { return len(b) } func (b byIndex) Less(i, j int) bool { return b[i].Index < b[j].Index } func (b byIndex) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func ClearCache() { cache.Flush() } type PlatformData struct { Name string Version string Sku string Type string } type PlatformCache struct { //cached data data map[string]*PlatformCacheItem //time to live for cache data in milliseconds ttl int64 //synced access to data lock sync.Mutex } type PlatformCacheItem struct { value string //time in milliseconds when the data was saved to cache cachedTime int64 } func InitCache(ttl int64) *PlatformCache { return &PlatformCache{ data: make(map[string]*PlatformCacheItem), ttl: ttl, } } func (cache *PlatformCache) Put(k, v string) { cache.lock.Lock() defer cache.lock.Unlock() cacheItem := &PlatformCacheItem{value: v, cachedTime: time.Now().UnixMilli()} cache.data[k] = cacheItem } func (cache *PlatformCache) Get(k string) (v string, found bool) { cache.lock.Lock() defer cache.lock.Unlock() if cacheItem, hit := cache.data[k]; hit { if time.Now().UnixMilli()-cacheItem.cachedTime < cache.ttl { v = cacheItem.value found = true } else { delete(cache.data, k) //remove stale cache data } } return } func (cache *PlatformCache) Flush() { cache.lock.Lock() defer cache.lock.Unlock() cache.data = make(map[string]*PlatformCacheItem) }