File: user.go

package info (click to toggle)
tiup 1.16.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 6,384 kB
  • sloc: sh: 1,988; makefile: 138; sql: 16
file content (139 lines) | stat: -rw-r--r-- 4,127 bytes parent folder | download
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
// Copyright 2020 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package module

import (
	"context"
	"fmt"

	"github.com/pingcap/tiup/pkg/cluster/ctxt"
)

const (
	defaultShell = "/bin/bash"

	// UserActionAdd add user.
	UserActionAdd = "add"
	// UserActionDel delete user.
	UserActionDel = "del"
	// UserActionModify = "modify"

	// TODO: in RHEL/CentOS, the commands are in /usr/sbin, but in some
	// other distros they may be in other location such as /usr/bin, we'll
	// need to check and find the proper path of commands in the future.
	useraddCmd  = "/usr/sbin/useradd"
	userdelCmd  = "/usr/sbin/userdel"
	groupaddCmd = "/usr/sbin/groupadd"
	// usermodCmd = "/usr/sbin/usermod"
)

var (
	errNSUser = errNS.NewSubNamespace("user")
	// ErrUserAddFailed is ErrUserAddFailed
	ErrUserAddFailed = errNSUser.NewType("user_add_failed")
	// ErrUserDeleteFailed is ErrUserDeleteFailed
	ErrUserDeleteFailed = errNSUser.NewType("user_delete_failed")
)

// UserModuleConfig is the configurations used to initialize a UserModule
type UserModuleConfig struct {
	Action string // add, del or modify user
	Name   string // username
	Group  string // group name
	Home   string // home directory of user
	Shell  string // login shell of the user
	Sudoer bool   // when true, the user will be added to sudoers list
}

// UserModule is the module used to control systemd units
type UserModule struct {
	config UserModuleConfig
	cmd    string // the built command
}

// NewUserModule builds and returns a UserModule object base on given config.
func NewUserModule(config UserModuleConfig) *UserModule {
	cmd := ""

	switch config.Action {
	case UserActionAdd:
		cmd = useraddCmd
		// You have to use -m, otherwise no home directory will be created. If you want to specify the path of the home directory, use -d and specify the path
		// useradd -m -d /PATH/TO/FOLDER
		cmd += " -m"
		if config.Home != "" {
			cmd += " -d" + config.Home
		}

		// set user's login shell
		if config.Shell != "" {
			cmd = fmt.Sprintf("%s -s %s", cmd, config.Shell)
		} else {
			cmd = fmt.Sprintf("%s -s %s", cmd, defaultShell)
		}

		// set user's group
		if config.Group == "" {
			config.Group = config.Name
		}

		// groupadd -f <group-name>
		groupAdd := fmt.Sprintf("%s -f %s", groupaddCmd, config.Group)

		// useradd -g <group-name> <user-name>
		cmd = fmt.Sprintf("%s -g %s %s", cmd, config.Group, config.Name)

		// prevent errors when username already in use
		cmd = fmt.Sprintf("id -u %s > /dev/null 2>&1 || (%s && %s)", config.Name, groupAdd, cmd)

		// add user to sudoers list
		if config.Sudoer {
			sudoLine := fmt.Sprintf("%s ALL=(ALL) NOPASSWD:ALL",
				config.Name)
			cmd = fmt.Sprintf("%s && %s",
				cmd,
				fmt.Sprintf("echo '%s' > /etc/sudoers.d/%s", sudoLine, config.Name))
		}

	case UserActionDel:
		cmd = fmt.Sprintf("%s -r %s", userdelCmd, config.Name)
		// prevent errors when user does not exist
		cmd = fmt.Sprintf("%s || [ $? -eq 6 ]", cmd)

		//	case UserActionModify:
		//		cmd = usermodCmd
	}

	return &UserModule{
		config: config,
		cmd:    cmd,
	}
}

// Execute passes the command to executor and returns its results, the executor
// should be already initialized.
func (mod *UserModule) Execute(ctx context.Context, exec ctxt.Executor) ([]byte, []byte, error) {
	a, b, err := exec.Execute(ctx, mod.cmd, true)
	if err != nil {
		switch mod.config.Action {
		case UserActionAdd:
			return a, b, ErrUserAddFailed.
				Wrap(err, "Failed to create new system user '%s' on remote host", mod.config.Name)
		case UserActionDel:
			return a, b, ErrUserDeleteFailed.
				Wrap(err, "Failed to delete system user '%s' on remote host", mod.config.Name)
		}
	}
	return a, b, nil
}