osinfo/osinfo_linux.go (138 lines of code) (raw):

/* Copyright 2017 Google Inc. 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. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License 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 osinfo import ( "bufio" "bytes" "context" "fmt" "io/ioutil" "regexp" "strings" "github.com/GoogleCloudPlatform/osconfig/util" "golang.org/x/sys/unix" ) var ( entRelVerRgx = regexp.MustCompile(`\d+(\.\d+)?(\.\d+)?`) ) var _ Provider = &LinuxOsInfoProvider{} var ( defaultReleaseFilepath = "/etc/os-release" oracleReleaseFilepath = "/etc/oracle-release" redHatReleaseFilepath = "/etc/redhat-release" ) // Get reports OSInfo. func Get() (*OSInfo, error) { // Eventually we will get rid of this function and will use providers directly // Providers should support context to be able to handle cancelation and logging // so far we just create empty context to connect the API. ctx := context.TODO() osInfoProvider, err := NewLinuxOsInfoProvider(getOsNameAndVersionProvider(ctx)) if err != nil { return nil, fmt.Errorf("unable to extract osinfo, err: %w", err) } osInfo, err := osInfoProvider.Get(ctx) if err != nil { return &osInfo, err } return &osInfo, nil } // LinuxOsInfoProvider is a provider of OSInfo for the linux based systems. type LinuxOsInfoProvider struct { nameAndVersionProvider osNameAndVersionProvider uts unix.Utsname } // NewLinuxOsInfoProvider is a constructor function for LinuxOsInfoProvider. func NewLinuxOsInfoProvider(nameAndVersionProvider osNameAndVersionProvider) (*LinuxOsInfoProvider, error) { var uts unix.Utsname if err := unix.Uname(&uts); err != nil { return nil, fmt.Errorf("unable to get unix.Uname, err: %w", err) } return &LinuxOsInfoProvider{ nameAndVersionProvider: nameAndVersionProvider, uts: uts, }, nil } // Get gather all required information and returns OSInfo. func (oip *LinuxOsInfoProvider) Get(ctx context.Context) (OSInfo, error) { short, long, version := oip.nameAndVersionProvider() return OSInfo{ ShortName: short, LongName: long, Version: version, Hostname: oip.hostName(), Architecture: oip.architecture(), KernelRelease: oip.kernelRelease(), KernelVersion: oip.kernelVersion(), }, nil } func (oip *LinuxOsInfoProvider) hostName() string { return stringFromUtsField(oip.uts.Nodename) } func (oip *LinuxOsInfoProvider) architecture() string { return NormalizeArchitecture(stringFromUtsField(oip.uts.Machine)) } func (oip *LinuxOsInfoProvider) kernelRelease() string { return stringFromUtsField(oip.uts.Release) } func (oip *LinuxOsInfoProvider) kernelVersion() string { return stringFromUtsField(oip.uts.Version) } func stringFromUtsField(field [65]byte) string { // unix.Utsname Fields are [65]byte so we need to trim any trailing null characters. return string(bytes.TrimRight(field[:], "\x00")) } func getOsNameAndVersionProvider(_ context.Context) osNameAndVersionProvider { return func() (string, string, string) { var ( extractNameAndVersion func(string) (string, string, string) releaseFile string ) defaultShortName, defaultLongName, defaultVersion := DefaultShortNameLinux, "", "" switch { // Check for /etc/os-release first. case util.Exists(defaultReleaseFilepath): releaseFile = defaultReleaseFilepath extractNameAndVersion = parseOsRelease case util.Exists(oracleReleaseFilepath): releaseFile = oracleReleaseFilepath extractNameAndVersion = parseEnterpriseRelease case util.Exists(redHatReleaseFilepath): releaseFile = redHatReleaseFilepath extractNameAndVersion = parseEnterpriseRelease default: return defaultShortName, defaultLongName, defaultVersion } b, err := ioutil.ReadFile(releaseFile) if err != nil { // TODO: log an error return defaultShortName, defaultLongName, defaultVersion } return extractNameAndVersion(string(b)) } } func parseOsRelease(releaseDetails string) (shortName, longName, version string) { scanner := bufio.NewScanner(strings.NewReader(releaseDetails)) for scanner.Scan() { entry := strings.Split(scanner.Text(), "=") switch entry[0] { case "": continue case "PRETTY_NAME": longName = strings.Trim(entry[1], `"`) case "VERSION_ID": version = strings.Trim(entry[1], `"`) case "ID": shortName = strings.Trim(entry[1], `"`) } // TODO: Replace with binary mask if longName != "" && version != "" && shortName != "" { break } } if shortName == "" { shortName = DefaultShortNameLinux } return shortName, longName, version } func parseEnterpriseRelease(releaseDetails string) (shortName string, longName string, version string) { shortName = DefaultShortNameLinux switch { case strings.Contains(releaseDetails, "CentOS"): shortName = "centos" case strings.Contains(releaseDetails, "Red Hat"): shortName = "rhel" case strings.Contains(releaseDetails, "Oracle"): shortName = "ol" } longName = strings.Replace(releaseDetails, " release ", " ", 1) version = entRelVerRgx.FindString(releaseDetails) return shortName, longName, version }