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
}