Skip to content

Commit df49ae8

Browse files
authored
Merge pull request #929 from afbjorklund/limauser-windows
Add more fallbacks for LimaUser uid/gid/home
2 parents 0cdcefd + 872fadf commit df49ae8

File tree

2 files changed

+123
-7
lines changed

2 files changed

+123
-7
lines changed

pkg/osutil/user.go

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package osutil
22

33
import (
44
"fmt"
5+
"os/exec"
56
"os/user"
7+
"path/filepath"
68
"regexp"
9+
"runtime"
710
"strconv"
11+
"strings"
812
"sync"
913

1014
"github.com/sirupsen/logrus"
@@ -15,6 +19,7 @@ type User struct {
1519
Uid uint32
1620
Group string
1721
Gid uint32
22+
Home string
1823
}
1924

2025
type Group struct {
@@ -46,7 +51,7 @@ func LookupUser(name string) (User, error) {
4651
if err != nil {
4752
return User{}, err
4853
}
49-
users[name] = User{User: u.Username, Uid: uint32(uid), Group: g.Name, Gid: uint32(gid)}
54+
users[name] = User{User: u.Username, Uid: uint32(uid), Group: g.Name, Gid: uint32(gid), Home: u.HomeDir}
5055
}
5156
return users[name], nil
5257
}
@@ -71,31 +76,94 @@ func LookupGroup(name string) (Group, error) {
7176

7277
const (
7378
fallbackUser = "lima"
79+
fallbackUid = 1000
80+
fallbackGid = 1000
7481
)
7582

7683
var cache struct {
7784
sync.Once
78-
u *user.User
79-
err error
80-
warning string
85+
u *user.User
86+
err error
87+
warnings []string
88+
}
89+
90+
func call(args []string) (string, error) {
91+
cmd := exec.Command(args[0], args[1:]...)
92+
out, err := cmd.Output()
93+
if err != nil {
94+
return "", err
95+
}
96+
return strings.TrimSpace(string(out)), nil
8197
}
8298

8399
func LimaUser(warn bool) (*user.User, error) {
100+
cache.warnings = []string{}
84101
cache.Do(func() {
85102
cache.u, cache.err = user.Current()
86103
if cache.err == nil {
87104
// `useradd` only allows user and group names matching the following pattern:
88105
// (it allows a trailing '$', but it feels prudent to map those to the fallback user as well)
89106
validName := "^[a-z_][a-z0-9_-]*$"
90107
if !regexp.MustCompile(validName).Match([]byte(cache.u.Username)) {
91-
cache.warning = fmt.Sprintf("local user %q is not a valid Linux username (must match %q); using %q username instead",
108+
warning := fmt.Sprintf("local user %q is not a valid Linux username (must match %q); using %q username instead",
92109
cache.u.Username, validName, fallbackUser)
110+
cache.warnings = append(cache.warnings, warning)
93111
cache.u.Username = fallbackUser
94112
}
113+
if runtime.GOOS == "windows" {
114+
idu, err := call([]string{"id", "-u"})
115+
if err != nil {
116+
logrus.Debug(err)
117+
}
118+
uid, err := strconv.ParseUint(idu, 10, 32)
119+
if err != nil {
120+
uid = fallbackUid
121+
}
122+
if !regexp.MustCompile("^[0-9]+$").Match([]byte(cache.u.Uid)) {
123+
warning := fmt.Sprintf("local uid %q is not a valid Linux uid (must be integer); using %d uid instead",
124+
cache.u.Uid, uid)
125+
cache.warnings = append(cache.warnings, warning)
126+
cache.u.Uid = fmt.Sprintf("%d", uid)
127+
}
128+
idg, err := call([]string{"id", "-g"})
129+
if err != nil {
130+
logrus.Debug(err)
131+
}
132+
gid, err := strconv.ParseUint(idg, 10, 32)
133+
if err != nil {
134+
gid = fallbackGid
135+
}
136+
if !regexp.MustCompile("^[0-9]+$").Match([]byte(cache.u.Gid)) {
137+
warning := fmt.Sprintf("local gid %q is not a valid Linux gid (must be integer); using %d gid instead",
138+
cache.u.Gid, gid)
139+
cache.warnings = append(cache.warnings, warning)
140+
cache.u.Gid = fmt.Sprintf("%d", gid)
141+
}
142+
home, err := call([]string{"cygpath", cache.u.HomeDir})
143+
if err != nil {
144+
logrus.Debug(err)
145+
}
146+
if home == "" {
147+
drive := filepath.VolumeName(cache.u.HomeDir)
148+
home = filepath.ToSlash(cache.u.HomeDir)
149+
// replace C: with /c
150+
prefix := strings.ToLower(fmt.Sprintf("/%c", drive[0]))
151+
home = strings.Replace(home, drive, prefix, 1)
152+
}
153+
validPath := "^[/a-zA-Z0-9_-]+$"
154+
if !regexp.MustCompile(validPath).Match([]byte(cache.u.HomeDir)) {
155+
warning := fmt.Sprintf("local home %q is not a valid Linux path (must match %q); using %q home instead",
156+
cache.u.HomeDir, validPath, home)
157+
cache.warnings = append(cache.warnings, warning)
158+
cache.u.HomeDir = home
159+
}
160+
}
95161
}
96162
})
97-
if warn && cache.warning != "" {
98-
logrus.Warn(cache.warning)
163+
if warn && len(cache.warnings) > 0 {
164+
for _, warning := range cache.warnings {
165+
logrus.Warn(warning)
166+
}
99167
}
100168
return cache.u, cache.err
101169
}

pkg/osutil/user_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package osutil
2+
3+
import (
4+
"path"
5+
"regexp"
6+
"strconv"
7+
"testing"
8+
9+
"gotest.tools/v3/assert"
10+
)
11+
12+
func TestLimaUserWarn(t *testing.T) {
13+
_, err := LimaUser(true)
14+
assert.NilError(t, err)
15+
}
16+
17+
func validUsername(username string) bool {
18+
validName := "^[a-z_][a-z0-9_-]*$"
19+
return regexp.MustCompile(validName).Match([]byte(username))
20+
}
21+
22+
func TestLimaUsername(t *testing.T) {
23+
user, err := LimaUser(false)
24+
assert.NilError(t, err)
25+
// check for reasonable unix user name
26+
assert.Assert(t, validUsername(user.Username), user.Username)
27+
}
28+
29+
func TestLimaUserUid(t *testing.T) {
30+
user, err := LimaUser(false)
31+
assert.NilError(t, err)
32+
_, err = strconv.Atoi(user.Uid)
33+
assert.NilError(t, err)
34+
}
35+
36+
func TestLimaUserGid(t *testing.T) {
37+
user, err := LimaUser(false)
38+
assert.NilError(t, err)
39+
_, err = strconv.Atoi(user.Gid)
40+
assert.NilError(t, err)
41+
}
42+
43+
func TestLimaHomeDir(t *testing.T) {
44+
user, err := LimaUser(false)
45+
assert.NilError(t, err)
46+
// check for absolute unix path (/home)
47+
assert.Assert(t, path.IsAbs(user.HomeDir), user.HomeDir)
48+
}

0 commit comments

Comments
 (0)