forked from asteris-llc/converge
/
user.go
339 lines (302 loc) · 10.9 KB
/
user.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// Copyright © 2016 Asteris, LLC
//
// 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 user
import (
"fmt"
"os/user"
"github.com/asteris-llc/converge/resource"
)
// State type for User
type State string
const (
// StatePresent indicates the user should be present
StatePresent State = "present"
// StateAbsent indicates the user should be absent
StateAbsent State = "absent"
)
// User manages user users
type User struct {
Username string
UID string
GroupName string
GID string
Name string
HomeDir string
State State
system SystemUtils
}
// AddUserOptions are the options specified in the configuration to be used
// when adding a user
type AddUserOptions struct {
UID string
Group string
Comment string
Directory string
}
// SystemUtils provides system utilities for user
type SystemUtils interface {
AddUser(userName string, options *AddUserOptions) error
DelUser(userName string) error
Lookup(userName string) (*user.User, error)
LookupID(userID string) (*user.User, error)
LookupGroup(groupName string) (*user.Group, error)
LookupGroupID(groupID string) (*user.Group, error)
}
// ErrUnsupported is used when a system is not supported
var ErrUnsupported = fmt.Errorf("user: not supported on this system")
// NewUser constructs and returns a new User
func NewUser(system SystemUtils) *User {
return &User{
system: system,
}
}
// Check if a user user exists
func (u *User) Check(resource.Renderer) (resource.TaskStatus, error) {
var (
userByID *user.User
uidErr error
)
// lookup the user by name and lookup the user by uid
// the lookups return ErrUnsupported if the system is not supported
// Lookup returns user.UnknownUserError if the user is not found
// LookupID returns user.UnknownUserIdError if the uid is not found
userByName, nameErr := u.system.Lookup(u.Username)
if u.UID != "" {
userByID, uidErr = u.system.LookupID(u.UID)
}
status := &resource.Status{}
if nameErr == ErrUnsupported {
status.Level = resource.StatusFatal
return status, ErrUnsupported
}
switch u.State {
case StatePresent:
switch {
case u.UID == "":
_, nameNotFound := nameErr.(user.UnknownUserError)
switch {
case userByName != nil:
status.Output = append(status.Output, fmt.Sprintf("user %s already exists", u.Username))
case nameNotFound:
switch {
case u.GroupName != "":
_, err := u.system.LookupGroup(u.GroupName)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group %s does not exist", u.GroupName))
return status, fmt.Errorf("will not add user %s", u.Username)
}
case u.GID != "":
_, err := u.system.LookupGroupID(u.GID)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group gid %s does not exist", u.GID))
return status, fmt.Errorf("will not add user %s", u.Username)
}
}
status.Level = resource.StatusWillChange
status.Output = append(status.Output, "user does not exist")
status.AddDifference("user", string(StateAbsent), fmt.Sprintf("user %s", u.Username), "")
}
case u.UID != "":
_, nameNotFound := nameErr.(user.UnknownUserError)
_, uidNotFound := uidErr.(user.UnknownUserIdError)
switch {
case nameNotFound && uidNotFound:
switch {
case u.GroupName != "":
_, err := u.system.LookupGroup(u.GroupName)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group %s does not exist", u.GroupName))
return status, fmt.Errorf("will not add user %s with uid %s", u.Username, u.UID)
}
case u.GID != "":
_, err := u.system.LookupGroupID(u.GID)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("group gid %s does not exist", u.GID))
return status, fmt.Errorf("will not add user %s with uid %s", u.Username, u.UID)
}
}
status.Level = resource.StatusWillChange
status.Output = append(status.Output, "user name and uid do not exist")
status.AddDifference("user", string(StateAbsent), fmt.Sprintf("user %s with uid %s", u.Username, u.UID), "")
case nameNotFound:
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("user uid %s already exists", u.UID))
case uidNotFound:
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("user %s already exists", u.Username))
case userByName != nil && userByID != nil && userByName.Name != userByID.Name || userByName.Uid != userByID.Uid:
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("user %s and uid %s belong to different users", u.Username, u.UID))
case userByName != nil && userByID != nil && *userByName == *userByID:
status.Output = append(status.Output, fmt.Sprintf("user %s with uid %s already exists", u.Username, u.UID))
}
}
case StateAbsent:
switch {
case u.UID == "":
_, nameNotFound := nameErr.(user.UnknownUserError)
switch {
case nameNotFound:
status.Output = append(status.Output, fmt.Sprintf("user %s does not exist", u.Username))
case userByName != nil:
status.Level = resource.StatusWillChange
status.AddDifference("user", fmt.Sprintf("user %s", u.Username), string(StateAbsent), "")
}
case u.UID != "":
_, nameNotFound := nameErr.(user.UnknownUserError)
_, uidNotFound := uidErr.(user.UnknownUserIdError)
switch {
case nameNotFound && uidNotFound:
status.Output = append(status.Output, "user name and uid do not exist")
case nameNotFound:
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("user %s does not exist", u.Username))
case uidNotFound:
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("user uid %s does not exist", u.UID))
case userByName != nil && userByID != nil && userByName.Name != userByID.Name || userByName.Uid != userByID.Uid:
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("user %s and uid %s belong to different users", u.Username, u.UID))
case userByName != nil && userByID != nil && *userByName == *userByID:
status.Level = resource.StatusWillChange
status.AddDifference("user", fmt.Sprintf("user %s with uid %s", u.Username, u.UID), string(StateAbsent), "")
}
}
default:
status.Level = resource.StatusFatal
return status, fmt.Errorf("user: unrecognized state %v", u.State)
}
return status, nil
}
// Apply changes for user
func (u *User) Apply() (resource.TaskStatus, error) {
var (
userByID *user.User
uidErr error
)
// lookup the user by name and lookup the user by uid
// the lookups return ErrUnsupported if the system is not supported
// Lookup returns user.UnknownUserError if the user is not found
// LookupID returns user.UnknownUserIdError if the uid is not found
userByName, nameErr := u.system.Lookup(u.Username)
if u.UID != "" {
userByID, uidErr = u.system.LookupID(u.UID)
}
status := &resource.Status{}
if nameErr == ErrUnsupported {
status.Level = resource.StatusFatal
return status, ErrUnsupported
}
switch u.State {
case StatePresent:
switch {
case u.UID == "":
_, nameNotFound := nameErr.(user.UnknownUserError)
switch {
case nameNotFound:
options := SetAddUserOptions(u)
err := u.system.AddUser(u.Username, options)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("error adding user %s", u.Username))
return status, err
}
status.Output = append(status.Output, fmt.Sprintf("added user %s", u.Username))
default:
status.Level = resource.StatusFatal
return status, fmt.Errorf("will not attempt add: user %s", u.Username)
}
case u.UID != "":
_, nameNotFound := nameErr.(user.UnknownUserError)
_, uidNotFound := uidErr.(user.UnknownUserIdError)
switch {
case nameNotFound && uidNotFound:
options := SetAddUserOptions(u)
err := u.system.AddUser(u.Username, options)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("error adding user %s with uid %s", u.Username, u.UID))
return status, err
}
status.Output = append(status.Output, fmt.Sprintf("added user %s with uid %s", u.Username, u.UID))
default:
status.Level = resource.StatusFatal
return status, fmt.Errorf("will not attempt add: user %s with uid %s", u.Username, u.UID)
}
}
case StateAbsent:
switch {
case u.UID == "":
_, nameNotFound := nameErr.(user.UnknownUserError)
switch {
case !nameNotFound && userByName != nil:
err := u.system.DelUser(u.Username)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("error deleting user %s", u.Username))
return status, err
}
status.Output = append(status.Output, fmt.Sprintf("deleted user %s", u.Username))
default:
status.Level = resource.StatusFatal
return status, fmt.Errorf("will not attempt delete: user %s", u.Username)
}
case u.UID != "":
_, nameNotFound := nameErr.(user.UnknownUserError)
_, uidNotFound := uidErr.(user.UnknownUserIdError)
switch {
case !nameNotFound && !uidNotFound && userByName != nil && userByID != nil && *userByName == *userByID:
err := u.system.DelUser(u.Username)
if err != nil {
status.Level = resource.StatusFatal
status.Output = append(status.Output, fmt.Sprintf("error deleting user %s with uid %s", u.Username, u.UID))
return status, err
}
status.Output = append(status.Output, fmt.Sprintf("deleted user %s with uid %s", u.Username, u.UID))
default:
status.Level = resource.StatusFatal
return status, fmt.Errorf("will not attempt delete: user %s with uid %s", u.Username, u.UID)
}
}
default:
status.Level = resource.StatusFatal
return status, fmt.Errorf("user: unrecognized state %s", u.State)
}
return status, nil
}
// SetAddUserOptions returns a AddUserOptions struct with the options
// specified in the configuration for adding a user
func SetAddUserOptions(u *User) *AddUserOptions {
options := new(AddUserOptions)
if u.UID != "" {
options.UID = u.UID
}
switch {
case u.GroupName != "":
options.Group = u.GroupName
case u.GID != "":
options.Group = u.GID
}
if u.Name != "" {
options.Comment = u.Name
}
if u.HomeDir != "" {
options.Directory = u.HomeDir
}
return options
}