jx/internal/resource/user.go

165 lines
3.9 KiB
Go
Raw Normal View History

2024-03-20 19:25:25 +00:00
// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
2024-03-22 17:39:06 +00:00
2024-03-20 16:15:27 +00:00
package resource
import (
2024-03-25 20:31:06 +00:00
"context"
"fmt"
"gopkg.in/yaml.v3"
2024-04-05 17:22:17 +00:00
"log/slog"
2024-03-25 20:31:06 +00:00
"net/url"
2024-04-05 17:22:17 +00:00
_ "os"
2024-03-25 20:31:06 +00:00
"os/exec"
"os/user"
2024-04-05 17:22:17 +00:00
"io"
2024-03-25 20:31:06 +00:00
"strconv"
"strings"
2024-03-20 16:15:27 +00:00
)
type User struct {
Name string `json:"name" yaml:"name"`
UID int `json:"uid,omitempty" yaml:"uid,omitempty"`
Group string `json:"group,omitempty" yaml:"group,omitempty"`
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
Gecos string `json:"gecos,omitempty" yaml:"gecos,omitempty"`
Home string `json:"home" yaml:"home"`
CreateHome bool `json:"createhome,omitempty" yaml:"createhome,omitempty"`
Shell string `json:"shell,omitempty" yaml:"shell,omitempty"`
2024-03-25 20:31:06 +00:00
State string `json:"state" yaml:"state"`
2024-03-20 16:15:27 +00:00
}
func NewUser() *User {
2024-04-05 17:22:17 +00:00
return &User{}
2024-03-20 16:15:27 +00:00
}
2024-03-22 04:35:17 +00:00
func init() {
2024-03-25 20:31:06 +00:00
ResourceTypes.Register("user", func(u *url.URL) Resource {
user := NewUser()
user.Name = u.Path
user.UID, _ = LookupUID(u.Path)
return user
})
2024-03-22 04:35:17 +00:00
}
2024-03-20 19:25:25 +00:00
func (u *User) URI() string {
2024-03-25 20:31:06 +00:00
return fmt.Sprintf("user://%s", u.Name)
2024-03-20 19:25:25 +00:00
}
func (u *User) ResolveId(ctx context.Context) string {
2024-03-25 20:31:06 +00:00
return LookupUIDString(u.Name)
2024-03-20 19:25:25 +00:00
}
2024-03-20 16:15:27 +00:00
func (u *User) Apply() error {
2024-03-25 20:31:06 +00:00
switch u.State {
case "present":
_, NoUserExists := LookupUID(u.Name)
if NoUserExists != nil {
var userCommandName string = "useradd"
args := make([]string, 0, 7)
if u.UID >= 0 {
args = append(args, "-u", fmt.Sprintf("%d", u.UID))
}
if _, pathErr := exec.LookPath("useradd"); pathErr != nil {
if _, addUserPathErr := exec.LookPath("adduser"); addUserPathErr == nil {
userCommandName = "adduser"
2024-04-03 19:27:16 +00:00
if addUserCommandErr := u.AddUserCommand(&args); addUserCommandErr != nil {
return addUserCommandErr
}
2024-03-25 20:31:06 +00:00
}
} else {
2024-04-03 19:27:16 +00:00
if userAddCommandErr := u.UserAddCommand(&args); userAddCommandErr != nil {
return userAddCommandErr
}
2024-03-25 20:31:06 +00:00
}
args = append(args, u.Name)
cmd := exec.Command(userCommandName, args...)
cmdOutput, cmdErr := cmd.CombinedOutput()
2024-04-05 17:22:17 +00:00
slog.Info("user command", "command", cmd.String(), "output", string(cmdOutput))
2024-03-25 20:31:06 +00:00
return cmdErr
}
case "absent":
var userDelCommandName string = "userdel"
args := make([]string, 0, 7)
if _, pathErr := exec.LookPath("userdel"); pathErr != nil {
if _, delUserPathErr := exec.LookPath("deluser"); delUserPathErr == nil {
userDelCommandName = "deluser"
}
}
args = append(args, u.Name)
cmd := exec.Command(userDelCommandName, args...)
cmdOutput, cmdErr := cmd.CombinedOutput()
2024-04-05 17:22:17 +00:00
slog.Info("user command", "command", cmd.String(), "output", string(cmdOutput))
2024-03-25 20:31:06 +00:00
return cmdErr
}
return nil
2024-03-20 16:15:27 +00:00
}
2024-04-05 17:22:17 +00:00
func (u *User) Load(r io.Reader) error {
c := NewYAMLDecoder(r)
return c.Decode(u)
}
func (u *User) LoadDecl(yamlResourceDeclaration string) error {
c := NewYAMLStringDecoder(yamlResourceDeclaration)
return c.Decode(u)
2024-03-20 16:15:27 +00:00
}
func (u *User) AddUserCommand(args *[]string) error {
2024-03-25 20:31:06 +00:00
*args = append(*args, "-D")
if u.Group != "" {
*args = append(*args, "-G", u.Group)
}
if u.Home != "" {
*args = append(*args, "-h", u.Home)
}
return nil
2024-03-20 16:15:27 +00:00
}
func (u *User) UserAddCommand(args *[]string) error {
2024-03-25 20:31:06 +00:00
if u.Group != "" {
*args = append(*args, "-g", u.Group)
}
if len(u.Groups) > 0 {
*args = append(*args, "-G", strings.Join(u.Groups, ","))
}
if u.Home != "" {
*args = append(*args, "-d", u.Home)
}
if u.CreateHome {
*args = append(*args, "-m")
}
return nil
2024-03-20 16:15:27 +00:00
}
2024-03-20 19:25:25 +00:00
func (u *User) Type() string { return "user" }
2024-03-22 04:35:17 +00:00
func (u *User) Read(ctx context.Context) ([]byte, error) {
2024-03-25 20:31:06 +00:00
var readUser *user.User
var e error
if u.Name != "" {
readUser, e = user.Lookup(u.Name)
}
if u.UID >= 0 {
readUser, e = user.LookupId(strconv.Itoa(u.UID))
}
if e != nil {
panic(e)
}
u.Name = readUser.Username
u.UID, _ = strconv.Atoi(readUser.Uid)
if readGroup, groupErr := user.LookupGroupId(readUser.Gid); groupErr == nil {
u.Group = readGroup.Name
} else {
panic(groupErr)
}
u.Home = readUser.HomeDir
u.Gecos = readUser.Name
return yaml.Marshal(u)
2024-03-22 04:35:17 +00:00
}