...

Source file src/go.opentelemetry.io/otel/sdk/resource/os_release_unix.go

Documentation: go.opentelemetry.io/otel/sdk/resource

     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  //go:build aix || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
    16  // +build aix dragonfly freebsd linux netbsd openbsd solaris zos
    17  
    18  package resource // import "go.opentelemetry.io/otel/sdk/resource"
    19  
    20  import (
    21  	"bufio"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"strings"
    26  )
    27  
    28  // osRelease builds a string describing the operating system release based on the
    29  // properties of the os-release file. If no os-release file is found, or if the
    30  // required properties to build the release description string are missing, an empty
    31  // string is returned instead. For more information about os-release files, see:
    32  // https://www.freedesktop.org/software/systemd/man/os-release.html
    33  func osRelease() string {
    34  	file, err := getOSReleaseFile()
    35  	if err != nil {
    36  		return ""
    37  	}
    38  
    39  	defer file.Close()
    40  
    41  	values := parseOSReleaseFile(file)
    42  
    43  	return buildOSRelease(values)
    44  }
    45  
    46  // getOSReleaseFile returns a *os.File pointing to one of the well-known os-release
    47  // files, according to their order of preference. If no file can be opened, it
    48  // returns an error.
    49  func getOSReleaseFile() (*os.File, error) {
    50  	return getFirstAvailableFile([]string{"/etc/os-release", "/usr/lib/os-release"})
    51  }
    52  
    53  // parseOSReleaseFile process the file pointed by `file` as an os-release file and
    54  // returns a map with the key-values contained in it. Empty lines or lines starting
    55  // with a '#' character are ignored, as well as lines with the missing key=value
    56  // separator. Values are unquoted and unescaped.
    57  func parseOSReleaseFile(file io.Reader) map[string]string {
    58  	values := make(map[string]string)
    59  	scanner := bufio.NewScanner(file)
    60  
    61  	for scanner.Scan() {
    62  		line := scanner.Text()
    63  
    64  		if skip(line) {
    65  			continue
    66  		}
    67  
    68  		key, value, ok := parse(line)
    69  		if ok {
    70  			values[key] = value
    71  		}
    72  	}
    73  
    74  	return values
    75  }
    76  
    77  // skip returns true if the line is blank or starts with a '#' character, and
    78  // therefore should be skipped from processing.
    79  func skip(line string) bool {
    80  	line = strings.TrimSpace(line)
    81  
    82  	return len(line) == 0 || strings.HasPrefix(line, "#")
    83  }
    84  
    85  // parse attempts to split the provided line on the first '=' character, and then
    86  // sanitize each side of the split before returning them as a key-value pair.
    87  func parse(line string) (string, string, bool) {
    88  	k, v, found := strings.Cut(line, "=")
    89  
    90  	if !found || len(k) == 0 {
    91  		return "", "", false
    92  	}
    93  
    94  	key := strings.TrimSpace(k)
    95  	value := unescape(unquote(strings.TrimSpace(v)))
    96  
    97  	return key, value, true
    98  }
    99  
   100  // unquote checks whether the string `s` is quoted with double or single quotes
   101  // and, if so, returns a version of the string without them. Otherwise it returns
   102  // the provided string unchanged.
   103  func unquote(s string) string {
   104  	if len(s) < 2 {
   105  		return s
   106  	}
   107  
   108  	if (s[0] == '"' || s[0] == '\'') && s[0] == s[len(s)-1] {
   109  		return s[1 : len(s)-1]
   110  	}
   111  
   112  	return s
   113  }
   114  
   115  // unescape removes the `\` prefix from some characters that are expected
   116  // to have it added in front of them for escaping purposes.
   117  func unescape(s string) string {
   118  	return strings.NewReplacer(
   119  		`\$`, `$`,
   120  		`\"`, `"`,
   121  		`\'`, `'`,
   122  		`\\`, `\`,
   123  		"\\`", "`",
   124  	).Replace(s)
   125  }
   126  
   127  // buildOSRelease builds a string describing the OS release based on the properties
   128  // available on the provided map. It favors a combination of the `NAME` and `VERSION`
   129  // properties as first option (falling back to `VERSION_ID` if `VERSION` isn't
   130  // found), and using `PRETTY_NAME` alone if some of the previous are not present. If
   131  // none of these properties are found, it returns an empty string.
   132  //
   133  // The rationale behind not using `PRETTY_NAME` as first choice was that, for some
   134  // Linux distributions, it doesn't include the same detail that can be found on the
   135  // individual `NAME` and `VERSION` properties, and combining `PRETTY_NAME` with
   136  // other properties can produce "pretty" redundant strings in some cases.
   137  func buildOSRelease(values map[string]string) string {
   138  	var osRelease string
   139  
   140  	name := values["NAME"]
   141  	version := values["VERSION"]
   142  
   143  	if version == "" {
   144  		version = values["VERSION_ID"]
   145  	}
   146  
   147  	if name != "" && version != "" {
   148  		osRelease = fmt.Sprintf("%s %s", name, version)
   149  	} else {
   150  		osRelease = values["PRETTY_NAME"]
   151  	}
   152  
   153  	return osRelease
   154  }
   155  

View as plain text