Skip to content

Commit c90dbd6

Browse files
authored
Refactor OS Info package (#809)
* Tests for package pretty & small refactoring * Refactor osinfo package, improve code coverage. * Refactor osinfo package to make it more testable * Improve coverage of osinfo package for linux. * Improve coverage of pretty package. * Fix function name * Switch to t.Cleanup for test environment cleanup Integrate t.Cleanup to the testing environment cleanup, small fixes on naming side.
1 parent d5f29d0 commit c90dbd6

File tree

10 files changed

+518
-146
lines changed

10 files changed

+518
-146
lines changed

osinfo/osinfo.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,29 @@ limitations under the License.
1515
// Linux.
1616
package osinfo
1717

18+
import (
19+
"context"
20+
)
21+
1822
const (
19-
// Linux is the default shortname used for a Linux system.
20-
Linux = "linux"
21-
// Windows is the default shortname used for Windows system.
22-
Windows = "windows"
23+
// DefaultShortNameLinux is the default shortname used for a Linux system.
24+
DefaultShortNameLinux = "linux"
25+
// DefaultShortNameWindows is the default shortname used for Windows system.
26+
DefaultShortNameWindows = "windows"
2327
)
2428

29+
// Provider is an interface for OSInfo extraction on different systems.
30+
type Provider interface {
31+
Get(context.Context) (OSInfo, error)
32+
}
33+
2534
// OSInfo describes an operating system.
2635
type OSInfo struct {
2736
Hostname, LongName, ShortName, Version, KernelVersion, KernelRelease, Architecture string
2837
}
2938

30-
// Architecture attempts to standardize architecture naming.
31-
func Architecture(arch string) string {
39+
// NormalizeArchitecture attempts to standardize architecture naming.
40+
func NormalizeArchitecture(arch string) string {
3241
switch arch {
3342
case "amd64", "64-bit":
3443
arch = "x86_64"
@@ -39,3 +48,5 @@ func Architecture(arch string) string {
3948
}
4049
return arch
4150
}
51+
52+
type osNameAndVersionProvider func() (shortName string, longName string, version string)

osinfo/osinfo_linux.go

Lines changed: 138 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package osinfo
1616
import (
1717
"bufio"
1818
"bytes"
19+
"context"
1920
"fmt"
2021
"io/ioutil"
2122
"regexp"
@@ -29,94 +30,167 @@ var (
2930
entRelVerRgx = regexp.MustCompile(`\d+(\.\d+)?(\.\d+)?`)
3031
)
3132

32-
const (
33-
osRelease = "/etc/os-release"
34-
oRelease = "/etc/oracle-release"
35-
rhRelease = "/etc/redhat-release"
33+
var _ Provider = &LinuxOsInfoProvider{}
34+
35+
var (
36+
defaultReleaseFilepath = "/etc/os-release"
37+
oracleReleaseFilepath = "/etc/oracle-release"
38+
redHatReleaseFilepath = "/etc/redhat-release"
3639
)
3740

38-
func parseOsRelease(releaseDetails string) *OSInfo {
39-
oi := &OSInfo{}
41+
// Get reports OSInfo.
42+
func Get() (*OSInfo, error) {
43+
// Eventually we will get rid of this function and will use providers directly
44+
// Providers should support context to be able to handle cancelation and logging
45+
// so far we just create empty context to connect the API.
46+
ctx := context.TODO()
47+
48+
osInfoProvider, err := NewLinuxOsInfoProvider(getOsNameAndVersionProvider(ctx))
49+
if err != nil {
50+
return nil, fmt.Errorf("unable to extract osinfo, err: %w", err)
51+
}
52+
53+
osInfo, err := osInfoProvider.Get(ctx)
54+
if err != nil {
55+
return &osInfo, err
56+
}
57+
58+
return &osInfo, nil
59+
}
60+
61+
// LinuxOsInfoProvider is a provider of OSInfo for the linux based systems.
62+
type LinuxOsInfoProvider struct {
63+
nameAndVersionProvider osNameAndVersionProvider
64+
uts unix.Utsname
65+
}
66+
67+
// NewLinuxOsInfoProvider is a constructor function for LinuxOsInfoProvider.
68+
func NewLinuxOsInfoProvider(nameAndVersionProvider osNameAndVersionProvider) (*LinuxOsInfoProvider, error) {
69+
var uts unix.Utsname
70+
if err := unix.Uname(&uts); err != nil {
71+
return nil, fmt.Errorf("unable to get unix.Uname, err: %w", err)
72+
}
73+
74+
return &LinuxOsInfoProvider{
75+
nameAndVersionProvider: nameAndVersionProvider,
76+
uts: uts,
77+
}, nil
78+
}
79+
80+
// Get gather all required information and returns OSInfo.
81+
func (oip *LinuxOsInfoProvider) Get(ctx context.Context) (OSInfo, error) {
82+
short, long, version := oip.nameAndVersionProvider()
83+
84+
return OSInfo{
85+
ShortName: short,
86+
LongName: long,
87+
Version: version,
88+
89+
Hostname: oip.hostName(),
90+
Architecture: oip.architecture(),
91+
KernelRelease: oip.kernelRelease(),
92+
KernelVersion: oip.kernelVersion(),
93+
}, nil
94+
}
95+
96+
func (oip *LinuxOsInfoProvider) hostName() string {
97+
return stringFromUtsField(oip.uts.Nodename)
98+
}
99+
100+
func (oip *LinuxOsInfoProvider) architecture() string {
101+
return NormalizeArchitecture(stringFromUtsField(oip.uts.Machine))
102+
}
103+
func (oip *LinuxOsInfoProvider) kernelRelease() string {
104+
return stringFromUtsField(oip.uts.Release)
105+
}
106+
107+
func (oip *LinuxOsInfoProvider) kernelVersion() string {
108+
return stringFromUtsField(oip.uts.Version)
109+
}
110+
111+
func stringFromUtsField(field [65]byte) string {
112+
// unix.Utsname Fields are [65]byte so we need to trim any trailing null characters.
113+
return string(bytes.TrimRight(field[:], "\x00"))
114+
}
115+
116+
func getOsNameAndVersionProvider(_ context.Context) osNameAndVersionProvider {
117+
return func() (string, string, string) {
118+
var (
119+
extractNameAndVersion func(string) (string, string, string)
120+
releaseFile string
121+
)
122+
123+
defaultShortName, defaultLongName, defaultVersion := DefaultShortNameLinux, "", ""
124+
125+
switch {
126+
// Check for /etc/os-release first.
127+
case util.Exists(defaultReleaseFilepath):
128+
releaseFile = defaultReleaseFilepath
129+
extractNameAndVersion = parseOsRelease
130+
case util.Exists(oracleReleaseFilepath):
131+
releaseFile = oracleReleaseFilepath
132+
extractNameAndVersion = parseEnterpriseRelease
133+
case util.Exists(redHatReleaseFilepath):
134+
releaseFile = redHatReleaseFilepath
135+
extractNameAndVersion = parseEnterpriseRelease
136+
default:
137+
return defaultShortName, defaultLongName, defaultVersion
138+
}
139+
140+
b, err := ioutil.ReadFile(releaseFile)
141+
if err != nil {
142+
// TODO: log an error
143+
return defaultShortName, defaultLongName, defaultVersion
144+
}
145+
146+
return extractNameAndVersion(string(b))
147+
}
148+
}
149+
150+
func parseOsRelease(releaseDetails string) (shortName, longName, version string) {
151+
scanner := bufio.NewScanner(strings.NewReader(releaseDetails))
40152

41-
scanner := bufio.NewScanner(bytes.NewReader([]byte(releaseDetails)))
42153
for scanner.Scan() {
43154
entry := strings.Split(scanner.Text(), "=")
44155
switch entry[0] {
45156
case "":
46157
continue
47158
case "PRETTY_NAME":
48-
oi.LongName = strings.Trim(entry[1], `"`)
159+
longName = strings.Trim(entry[1], `"`)
49160
case "VERSION_ID":
50-
oi.Version = strings.Trim(entry[1], `"`)
161+
version = strings.Trim(entry[1], `"`)
51162
case "ID":
52-
oi.ShortName = strings.Trim(entry[1], `"`)
163+
shortName = strings.Trim(entry[1], `"`)
53164
}
54-
if oi.LongName != "" && oi.Version != "" && oi.ShortName != "" {
165+
166+
// TODO: Replace with binary mask
167+
if longName != "" && version != "" && shortName != "" {
55168
break
56169
}
57170
}
58171

59-
if oi.ShortName == "" {
60-
oi.ShortName = Linux
172+
if shortName == "" {
173+
shortName = DefaultShortNameLinux
61174
}
62175

63-
return oi
176+
return shortName, longName, version
64177
}
65178

66-
func parseEnterpriseRelease(releaseDetails string) *OSInfo {
67-
rel := releaseDetails
179+
func parseEnterpriseRelease(releaseDetails string) (shortName string, longName string, version string) {
180+
shortName = DefaultShortNameLinux
68181

69-
var sn string
70182
switch {
71-
case strings.Contains(rel, "CentOS"):
72-
sn = "centos"
73-
case strings.Contains(rel, "Red Hat"):
74-
sn = "rhel"
75-
case strings.Contains(rel, "Oracle"):
76-
sn = "ol"
183+
case strings.Contains(releaseDetails, "CentOS"):
184+
shortName = "centos"
185+
case strings.Contains(releaseDetails, "Red Hat"):
186+
shortName = "rhel"
187+
case strings.Contains(releaseDetails, "Oracle"):
188+
shortName = "ol"
77189
}
78190

79-
return &OSInfo{
80-
ShortName: sn,
81-
LongName: strings.Replace(rel, " release ", " ", 1),
82-
Version: entRelVerRgx.FindString(rel),
83-
}
84-
}
85-
86-
// Get reports OSInfo.
87-
func Get() (*OSInfo, error) {
88-
var oi *OSInfo
89-
var parseReleaseFunc func(string) *OSInfo
90-
var releaseFile string
91-
switch {
92-
// Check for /etc/os-release first.
93-
case util.Exists(osRelease):
94-
releaseFile = osRelease
95-
parseReleaseFunc = parseOsRelease
96-
case util.Exists(oRelease):
97-
releaseFile = oRelease
98-
parseReleaseFunc = parseEnterpriseRelease
99-
case util.Exists(rhRelease):
100-
releaseFile = rhRelease
101-
parseReleaseFunc = parseEnterpriseRelease
102-
}
191+
longName = strings.Replace(releaseDetails, " release ", " ", 1)
103192

104-
b, err := ioutil.ReadFile(releaseFile)
105-
if err != nil {
106-
oi = &OSInfo{ShortName: Linux}
107-
} else {
108-
oi = parseReleaseFunc(string(b))
109-
}
110-
111-
var uts unix.Utsname
112-
if err := unix.Uname(&uts); err != nil {
113-
return oi, fmt.Errorf("unix.Uname error: %v", err)
114-
}
115-
// unix.Utsname Fields are [65]byte so we need to trim any trailing null characters.
116-
oi.Hostname = string(bytes.TrimRight(uts.Nodename[:], "\x00"))
117-
oi.Architecture = Architecture(string(bytes.TrimRight(uts.Machine[:], "\x00")))
118-
oi.KernelVersion = string(bytes.TrimRight(uts.Version[:], "\x00"))
119-
oi.KernelRelease = string(bytes.TrimRight(uts.Release[:], "\x00"))
193+
version = entRelVerRgx.FindString(releaseDetails)
120194

121-
return oi, nil
195+
return shortName, longName, version
122196
}

0 commit comments

Comments
 (0)