os_release_darwin.go (3259B)
1 // Copyright The OpenTelemetry Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package resource // import "go.opentelemetry.io/otel/sdk/resource" 16 17 import ( 18 "encoding/xml" 19 "fmt" 20 "io" 21 "os" 22 ) 23 24 type plist struct { 25 XMLName xml.Name `xml:"plist"` 26 Dict dict `xml:"dict"` 27 } 28 29 type dict struct { 30 Key []string `xml:"key"` 31 String []string `xml:"string"` 32 } 33 34 // osRelease builds a string describing the operating system release based on the 35 // contents of the property list (.plist) system files. If no .plist files are found, 36 // or if the required properties to build the release description string are missing, 37 // an empty string is returned instead. The generated string resembles the output of 38 // the `sw_vers` commandline program, but in a single-line string. For more information 39 // about the `sw_vers` program, see: https://www.unix.com/man-page/osx/1/SW_VERS. 40 func osRelease() string { 41 file, err := getPlistFile() 42 if err != nil { 43 return "" 44 } 45 46 defer file.Close() 47 48 values, err := parsePlistFile(file) 49 if err != nil { 50 return "" 51 } 52 53 return buildOSRelease(values) 54 } 55 56 // getPlistFile returns a *os.File pointing to one of the well-known .plist files 57 // available on macOS. If no file can be opened, it returns an error. 58 func getPlistFile() (*os.File, error) { 59 return getFirstAvailableFile([]string{ 60 "/System/Library/CoreServices/SystemVersion.plist", 61 "/System/Library/CoreServices/ServerVersion.plist", 62 }) 63 } 64 65 // parsePlistFile process the file pointed by `file` as a .plist file and returns 66 // a map with the key-values for each pair of correlated <key> and <string> elements 67 // contained in it. 68 func parsePlistFile(file io.Reader) (map[string]string, error) { 69 var v plist 70 71 err := xml.NewDecoder(file).Decode(&v) 72 if err != nil { 73 return nil, err 74 } 75 76 if len(v.Dict.Key) != len(v.Dict.String) { 77 return nil, fmt.Errorf("the number of <key> and <string> elements doesn't match") 78 } 79 80 properties := make(map[string]string, len(v.Dict.Key)) 81 for i, key := range v.Dict.Key { 82 properties[key] = v.Dict.String[i] 83 } 84 85 return properties, nil 86 } 87 88 // buildOSRelease builds a string describing the OS release based on the properties 89 // available on the provided map. It tries to find the `ProductName`, `ProductVersion` 90 // and `ProductBuildVersion` properties. If some of these properties are not found, 91 // it returns an empty string. 92 func buildOSRelease(properties map[string]string) string { 93 productName := properties["ProductName"] 94 productVersion := properties["ProductVersion"] 95 productBuildVersion := properties["ProductBuildVersion"] 96 97 if productName == "" || productVersion == "" || productBuildVersion == "" { 98 return "" 99 } 100 101 return fmt.Sprintf("%s %s (%s)", productName, productVersion, productBuildVersion) 102 }