Skip to content

Commit 375718c

Browse files
committed
Report OS id
1 parent 8a9e027 commit 375718c

8 files changed

Lines changed: 263 additions & 4 deletions

File tree

internal/api/submit/request.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type System struct {
66

77
type OS struct {
88
Architecture string `json:"architecture"`
9+
Id string `json:"id"`
910
}
1011

1112
type Pacman struct {
@@ -30,6 +31,7 @@ type PackageManager interface {
3031
type SystemInfo interface {
3132
GetCpuArchitecture() (string, error)
3233
GetArchitecture() (string, error)
34+
GetOSID(...string) (string, error)
3335
}
3436

3537
func CreateRequest(p PackageManager, s SystemInfo) (*Request, error) {
@@ -51,10 +53,15 @@ func CreateRequest(p PackageManager, s SystemInfo) (*Request, error) {
5153
return nil, err
5254
}
5355

56+
osId, err := s.GetOSID()
57+
if err != nil {
58+
return nil, err
59+
}
60+
5461
return &Request{
5562
Version: Version,
5663
System: System{Architecture: cpuArchitecture},
57-
OS: OS{Architecture: architecture},
64+
OS: OS{Architecture: architecture, Id: osId},
5865
Pacman: Pacman{Packages: packages, Mirror: mirror},
5966
}, nil
6067
}

internal/system/system_goos.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ func (s *System) GetArchitecture() (string, error) {
1616
return runtime.GOARCH, nil
1717
}
1818
}
19+
20+
func (s *System) GetOSID(_ ...string) (string, error) {
21+
return runtime.GOOS, nil
22+
}

internal/system/system_linux.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package system
22

33
import (
44
"bytes"
5+
"os"
6+
"runtime"
7+
"strings"
58

69
"golang.org/x/sys/unix"
710
)
@@ -15,3 +18,43 @@ func (s *System) GetArchitecture() (string, error) {
1518

1619
return string(utsname.Machine[:bytes.IndexByte(utsname.Machine[:], 0)]), nil
1720
}
21+
22+
func (s *System) GetOSID(osReleasePaths ...string) (string, error) {
23+
var id string
24+
25+
if len(osReleasePaths) == 0 {
26+
osReleasePaths = []string{"/etc/os-release", "/usr/lib/os-release"}
27+
}
28+
29+
for _, osReleasePath := range osReleasePaths {
30+
content, err := os.ReadFile(osReleasePath)
31+
if err != nil {
32+
continue
33+
}
34+
35+
lines := strings.Split(string(content), "\n")
36+
const keyValueParts = 2
37+
for _, line := range lines {
38+
if trimmedLine := strings.TrimSpace(line); trimmedLine == "" || strings.HasPrefix(trimmedLine, "#") {
39+
continue
40+
}
41+
42+
parts := strings.SplitN(line, "=", keyValueParts)
43+
if len(parts) != keyValueParts {
44+
continue
45+
}
46+
47+
key := strings.TrimSpace(parts[0])
48+
if key == "ID" {
49+
id = strings.Trim(strings.TrimSpace(parts[1]), `"'`)
50+
}
51+
}
52+
break
53+
}
54+
55+
if id == "" {
56+
id = runtime.GOOS
57+
}
58+
59+
return id, nil
60+
}

tests/integration/integration_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ func TestShowInformationToBeSent(t *testing.T) {
4343
if err != nil {
4444
t.Fatal(err)
4545
}
46+
osId, err := s.GetOSID()
47+
if err != nil {
48+
t.Fatal(err)
49+
}
4650

4751
output, err := pkgstats(t, []string{"submit", "--dump-json"}, WithPkgBlocklist([]string{"secret-*"}), WithMirrorBlocklist([]string{"secret.com"}))
4852
if err != nil {
@@ -61,6 +65,9 @@ func TestShowInformationToBeSent(t *testing.T) {
6165
if request.OS.Architecture != osArchitecture {
6266
t.Errorf("Expected OS architecture '%s', got %v", osArchitecture, request.OS.Architecture)
6367
}
68+
if request.OS.Id != osId {
69+
t.Errorf("Expected OS id '%s', got %v", osId, request.OS.Id)
70+
}
6471
if !strings.HasPrefix(request.Pacman.Mirror, "https://") {
6572
t.Errorf("Expected pacman mirror to start with 'https://', got %v", request.Pacman.Mirror)
6673
}

tests/integration/mock_server_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ func validateSubmitRequest(w http.ResponseWriter, request *submit.Request) {
108108
http.Error(w, err.Error(), http.StatusInternalServerError)
109109
return
110110
}
111+
osId, err := s.GetOSID()
112+
if err != nil {
113+
http.Error(w, err.Error(), http.StatusInternalServerError)
114+
return
115+
}
111116

112117
if request.Version != submit.Version {
113118
http.Error(w, fmt.Sprintf("Expected version %s, got %s", submit.Version, request.Version), http.StatusBadRequest)
@@ -117,6 +122,10 @@ func validateSubmitRequest(w http.ResponseWriter, request *submit.Request) {
117122
http.Error(w, fmt.Sprintf("Expected OS architecture %s, got %s", osArchitecture, request.OS.Architecture), http.StatusBadRequest)
118123
return
119124
}
125+
if request.OS.Id != osId {
126+
http.Error(w, fmt.Sprintf("Expected OS id %s, got %s", osId, request.OS.Id), http.StatusBadRequest)
127+
return
128+
}
120129
if request.System.Architecture != cpuArchitecture {
121130
http.Error(w, fmt.Sprintf("Expected CPU architecture %s, got %s", cpuArchitecture, request.System.Architecture), http.StatusBadRequest)
122131
return

tests/unit/internal/api/submit/client_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"io"
77
"net/http"
88
"net/http/httptest"
9+
"runtime"
910
"strings"
1011
"testing"
1112

@@ -32,7 +33,7 @@ func TestSendRequest(t *testing.T) {
3233
request := &submit.Request{
3334
Version: submit.Version,
3435
System: submit.System{Architecture: system.X86_64},
35-
OS: submit.OS{Architecture: system.I686},
36+
OS: submit.OS{Architecture: system.I686, Id: runtime.GOOS},
3637
Pacman: submit.Pacman{Packages: []string{"pacman", "linux"}, Mirror: mirror},
3738
}
3839
err := client.SendRequest(*request)
@@ -74,6 +75,9 @@ func validateRequest(t *testing.T, req *http.Request) {
7475
if request.OS.Architecture != system.I686 {
7576
t.Error("Invalid arch value")
7677
}
78+
if request.OS.Id != runtime.GOOS {
79+
t.Error("Invalid id value")
80+
}
7781
if request.Pacman.Mirror != mirror {
7882
t.Error("Invalid mirror value")
7983
}
@@ -101,7 +105,7 @@ func TestSendRequestFollowsRedirect(t *testing.T) {
101105
request := &submit.Request{
102106
Version: submit.Version,
103107
System: submit.System{Architecture: system.X86_64},
104-
OS: submit.OS{Architecture: system.I686},
108+
OS: submit.OS{Architecture: system.I686, Id: runtime.GOOS},
105109
Pacman: submit.Pacman{Packages: []string{"pacman", "linux"}, Mirror: mirror},
106110
}
107111
err := client.SendRequest(*request)
@@ -128,7 +132,7 @@ func TestReturnServerErrorOnFailure(t *testing.T) {
128132
request := &submit.Request{
129133
Version: submit.Version,
130134
System: submit.System{Architecture: system.X86_64},
131-
OS: submit.OS{Architecture: system.I686},
135+
OS: submit.OS{Architecture: system.I686, Id: runtime.GOOS},
132136
Pacman: submit.Pacman{Packages: []string{"pacman", "linux"}, Mirror: mirror},
133137
}
134138
err := client.SendRequest(*request)

tests/unit/internal/system/system_goos_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ func TestGetArchitecture(t *testing.T) {
1818
t.Error(cpuArch)
1919
}
2020
}
21+
22+
func TestGetOSID(t *testing.T) {
23+
osId, err := system.NewSystem().GetOSID("")
24+
if err != nil {
25+
t.Error(err)
26+
}
27+
if osId != runtime.GOOS {
28+
t.Error(osId)
29+
}
30+
}

tests/unit/internal/system/system_linux_test.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package system_test
22

33
import (
4+
"os"
45
"runtime"
56
"slices"
67
"strings"
@@ -33,3 +34,177 @@ func TestGetMachine(t *testing.T) {
3334
t.Error(arch)
3435
}
3536
}
37+
38+
func createOsReleaseFile(t *testing.T, content string) string {
39+
t.Helper()
40+
41+
tmpfile, err := os.CreateTemp("", "os-release-test-")
42+
if err != nil {
43+
t.Fatalf("failed to create temp file: %v", err)
44+
}
45+
t.Cleanup(func() {
46+
if err := os.Remove(tmpfile.Name()); err != nil {
47+
t.Fatalf("failed to remove temp file: %v", err)
48+
}
49+
})
50+
51+
if _, err := tmpfile.WriteString(content); err != nil {
52+
t.Fatalf("failed to write to temp file: %v", err)
53+
}
54+
if err := tmpfile.Close(); err != nil {
55+
t.Fatalf("failed to close temp file: %v", err)
56+
}
57+
58+
return tmpfile.Name()
59+
}
60+
61+
func TestGetOSID(t *testing.T) {
62+
testCases := []struct {
63+
name string
64+
content string
65+
expectedOSID string
66+
}{
67+
{
68+
name: "should return ID from simple file",
69+
content: `
70+
NAME="Test OS"
71+
VERSION="1.0"
72+
ID=testos
73+
ID_LIKE=anotheros
74+
`,
75+
expectedOSID: "testos",
76+
},
77+
{
78+
name: "should return ID from quoted syntac with whitespaces",
79+
content: `
80+
NAME="Test OS"
81+
VERSION="1.0"
82+
ID = "testos"
83+
ID_LIKE=anotheros
84+
`,
85+
expectedOSID: "testos",
86+
},
87+
{
88+
name: "should return ID with whitespaces",
89+
content: `
90+
NAME="Test OS"
91+
VERSION="1.0"
92+
ID = testos
93+
ID_LIKE=anotheros
94+
`,
95+
expectedOSID: "testos",
96+
},
97+
{
98+
name: "should return ID from quoted syntac with whitespaces",
99+
content: `
100+
NAME="Test OS"
101+
VERSION="1.0"
102+
ID='testos'
103+
ID_LIKE=anotheros
104+
`,
105+
expectedOSID: "testos",
106+
},
107+
{
108+
name: "should return the last ID when duplicates exist",
109+
content: `
110+
ID=firstid
111+
NAME="Test OS"
112+
ID=secondid
113+
VERSION="1.0"
114+
ID=lastid
115+
`,
116+
expectedOSID: "lastid",
117+
},
118+
{
119+
name: "should return default string for empty file",
120+
content: "",
121+
expectedOSID: runtime.GOOS,
122+
},
123+
{
124+
name: "should return default string for file with no ID",
125+
content: `
126+
NAME="Test OS"
127+
VERSION="1.0"
128+
`,
129+
expectedOSID: runtime.GOOS,
130+
},
131+
}
132+
133+
for _, tc := range testCases {
134+
t.Run(tc.name, func(t *testing.T) {
135+
osReleaseFile := createOsReleaseFile(t, tc.content)
136+
sys := system.NewSystem()
137+
osID, err := sys.GetOSID(osReleaseFile)
138+
if err != nil {
139+
t.Fatalf("unexpected error getting OSID: %v", err)
140+
}
141+
142+
if osID != tc.expectedOSID {
143+
t.Errorf("expected OSID %q, got %q", tc.expectedOSID, osID)
144+
}
145+
})
146+
}
147+
}
148+
149+
func TestGetOSIDMultipleFiles(t *testing.T) {
150+
t.Run("should return default GOOS when no path is provided", func(t *testing.T) {
151+
sys := system.NewSystem()
152+
osID, err := sys.GetOSID()
153+
if err != nil {
154+
t.Fatalf("unexpected error getting OSID: %v", err)
155+
}
156+
157+
_, err1 := os.Stat("/etc/os-release")
158+
_, err2 := os.Stat("/usr/lib/os-release")
159+
160+
if os.IsNotExist(err1) && os.IsNotExist(err2) {
161+
// Neither file exists, so we should get the default GOOS
162+
if osID != runtime.GOOS {
163+
t.Errorf("expected OSID %q, got %q", runtime.GOOS, osID)
164+
}
165+
} else {
166+
// At least one file exists, so we should get a non-empty string
167+
if osID == "" {
168+
t.Error("expected a non-empty OSID, but it was empty")
169+
}
170+
}
171+
})
172+
173+
t.Run("should return ID from the second file if the first does not exist", func(t *testing.T) {
174+
osReleaseFile := createOsReleaseFile(t, "ID=second")
175+
sys := system.NewSystem()
176+
osID, err := sys.GetOSID("/non/existent/file", osReleaseFile)
177+
if err != nil {
178+
t.Fatalf("unexpected error getting OSID: %v", err)
179+
}
180+
if osID != "second" {
181+
t.Errorf("expected OSID %q, got %q", "second", osID)
182+
}
183+
})
184+
185+
t.Run("should return default GOOS if the first file has no ID", func(t *testing.T) {
186+
firstFile := createOsReleaseFile(t, "NAME=No ID here")
187+
secondFile := createOsReleaseFile(t, "ID=second")
188+
sys := system.NewSystem()
189+
osID, err := sys.GetOSID(firstFile, secondFile)
190+
if err != nil {
191+
t.Fatalf("unexpected error getting OSID: %v", err)
192+
}
193+
if osID != runtime.GOOS {
194+
t.Errorf("expected OSID %q, got %q", runtime.GOOS, osID)
195+
}
196+
})
197+
198+
t.Run("should return ID from the first file if both exist", func(t *testing.T) {
199+
firstFile := createOsReleaseFile(t, "ID=first")
200+
secondFile := createOsReleaseFile(t, "ID=second")
201+
sys := system.NewSystem()
202+
osID, err := sys.GetOSID(firstFile, secondFile)
203+
if err != nil {
204+
t.Fatalf("unexpected error getting OSID: %v", err)
205+
}
206+
if osID != "first" {
207+
t.Errorf("expected OSID %q, got %q", "first", osID)
208+
}
209+
})
210+
}

0 commit comments

Comments
 (0)