diff --git a/components/cluster/command/deploy.go b/components/cluster/command/deploy.go index 1b93b0613f..bca25f31cc 100644 --- a/components/cluster/command/deploy.go +++ b/components/cluster/command/deploy.go @@ -86,6 +86,7 @@ func newDeploy() *cobra.Command { } cmd.Flags().StringVarP(&opt.User, "user", "u", tiuputils.CurrentUser(), "The user name to login via SSH. The user must has root (or sudo) privilege.") + cmd.Flags().BoolVarP(&opt.SkipCreateUser, "skip-create-user", "", false, "Skip creating the user specified in topology.") cmd.Flags().StringVarP(&opt.IdentityFile, "identity_file", "i", opt.IdentityFile, "The path of the SSH identity file. If specified, public key authentication will be used.") cmd.Flags().BoolVarP(&opt.UsePassword, "password", "p", false, "Use password of target hosts. If specified, password authentication will be used.") cmd.Flags().BoolVarP(&opt.IgnoreConfigCheck, "ignore-config-check", "", opt.IgnoreConfigCheck, "Ignore the config check result") diff --git a/components/cluster/command/scale_out.go b/components/cluster/command/scale_out.go index 581e151373..5f5d0dc065 100644 --- a/components/cluster/command/scale_out.go +++ b/components/cluster/command/scale_out.go @@ -63,6 +63,7 @@ func newScaleOutCmd() *cobra.Command { } cmd.Flags().StringVarP(&opt.User, "user", "u", tiuputils.CurrentUser(), "The user name to login via SSH. The user must has root (or sudo) privilege.") + cmd.Flags().BoolVarP(&opt.SkipCreateUser, "skip-create-user", "", false, "Skip creating the user specified in topology.") cmd.Flags().StringVarP(&opt.IdentityFile, "identity_file", "i", opt.IdentityFile, "The path of the SSH identity file. If specified, public key authentication will be used.") cmd.Flags().BoolVarP(&opt.UsePassword, "password", "p", false, "Use password of target hosts. If specified, password authentication will be used.") diff --git a/examples/topology.example.yaml b/examples/topology.example.yaml index 1a3a0e33f1..2267ab22ee 100644 --- a/examples/topology.example.yaml +++ b/examples/topology.example.yaml @@ -3,6 +3,8 @@ global: user: "tidb" + # # group is used to specify the group name the user belong to. + # group: "tidb" ssh_port: 22 deploy_dir: "/tidb-deploy" data_dir: "/tidb-data" diff --git a/pkg/cluster/manager.go b/pkg/cluster/manager.go index 87f3139453..ce3c223a9f 100644 --- a/pkg/cluster/manager.go +++ b/pkg/cluster/manager.go @@ -867,15 +867,17 @@ func (m *Manager) Patch(clusterName string, packagePath string, opt operator.Opt // ScaleOutOptions contains the options for scale out. type ScaleOutOptions struct { - User string // username to login to the SSH server - IdentityFile string // path to the private key file - UsePassword bool // use password instead of identity file for ssh connection + User string // username to login to the SSH server + SkipCreateUser bool // don't create user + IdentityFile string // path to the private key file + UsePassword bool // use password instead of identity file for ssh connection } // DeployOptions contains the options for scale out. // TODO: merge ScaleOutOptions, should check config too when scale out. type DeployOptions struct { User string // username to login to the SSH server + SkipCreateUser bool // don't create the user IdentityFile string // path to the private key file UsePassword bool // use password instead of identity file for ssh connection IgnoreConfigCheck bool // ignore config check result @@ -1004,7 +1006,7 @@ func (m *Manager) Deploy( sshTimeout, nativeSSH, ). - EnvInit(inst.GetHost(), globalOptions.User). + EnvInit(inst.GetHost(), globalOptions.User, globalOptions.Group, opt.SkipCreateUser || globalOptions.User == opt.User). Mkdir(globalOptions.User, inst.GetHost(), dirs...). BuildAsStep(fmt.Sprintf(" - Prepare %s:%d", inst.GetHost(), inst.GetSSHPort())) envInitTasks = append(envInitTasks, t) @@ -1702,7 +1704,7 @@ func buildScaleOutTask( sshTimeout, nativeSSH, ). - EnvInit(instance.GetHost(), base.User). + EnvInit(instance.GetHost(), base.User, base.Group, opt.SkipCreateUser || globalOptions.User == opt.User). Mkdir(globalOptions.User, instance.GetHost(), dirs...). Build() envInitTasks = append(envInitTasks, t) diff --git a/pkg/cluster/module/user.go b/pkg/cluster/module/user.go index d2bc51247c..2521dbf82f 100644 --- a/pkg/cluster/module/user.go +++ b/pkg/cluster/module/user.go @@ -31,8 +31,9 @@ const ( // 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" + useraddCmd = "/usr/sbin/useradd" + userdelCmd = "/usr/sbin/userdel" + groupaddCmd = "/usr/sbin/groupadd" //usermodCmd = "/usr/sbin/usermod" ) @@ -48,6 +49,7 @@ var ( 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 @@ -80,10 +82,19 @@ func NewUserModule(config UserModuleConfig) *UserModule { cmd = fmt.Sprintf("%s -s %s", cmd, defaultShell) } - cmd = fmt.Sprintf("%s %s", cmd, config.Name) + // set user's group + if config.Group == "" { + config.Group = config.Name + } + + // groupadd -f + groupAdd := fmt.Sprintf("%s -f %s", groupaddCmd, config.Group) + + // useradd -g + 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", config.Name, cmd) + cmd = fmt.Sprintf("id -u %s > /dev/null 2>&1 || (%s && %s)", config.Name, groupAdd, cmd) // add user to sudoers list if config.Sudoer { @@ -93,6 +104,7 @@ func NewUserModule(config UserModuleConfig) *UserModule { 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 diff --git a/pkg/cluster/spec/spec.go b/pkg/cluster/spec/spec.go index 7b57cb68c5..7d530641f4 100644 --- a/pkg/cluster/spec/spec.go +++ b/pkg/cluster/spec/spec.go @@ -53,6 +53,7 @@ type ( // specification in topology.yaml GlobalOptions struct { User string `yaml:"user,omitempty" default:"tidb"` + Group string `yaml:"group,omitempty"` SSHPort int `yaml:"ssh_port,omitempty" default:"22" validate:"ssh_port:editable"` DeployDir string `yaml:"deploy_dir,omitempty" default:"deploy"` DataDir string `yaml:"data_dir,omitempty" default:"data"` @@ -134,6 +135,7 @@ type Topology interface { // BaseMeta is the base info of metadata. type BaseMeta struct { User string + Group string Version string OpsVer *string `yaml:"last_ops_ver,omitempty"` // the version of ourself that updated the meta last time } diff --git a/pkg/cluster/task/builder.go b/pkg/cluster/task/builder.go index 587d6430d2..0b4bc28f57 100644 --- a/pkg/cluster/task/builder.go +++ b/pkg/cluster/task/builder.go @@ -225,10 +225,12 @@ func (b *Builder) SSHKeySet(privKeyPath, pubKeyPath string) *Builder { } // EnvInit appends a EnvInit task to the current task collection -func (b *Builder) EnvInit(host, deployUser string) *Builder { +func (b *Builder) EnvInit(host, deployUser string, userGroup string, skipCreateUser bool) *Builder { b.tasks = append(b.tasks, &EnvInit{ - host: host, - deployUser: deployUser, + host: host, + deployUser: deployUser, + userGroup: userGroup, + skipCreateUser: skipCreateUser, }) return b } diff --git a/pkg/cluster/task/env_init.go b/pkg/cluster/task/env_init.go index f7e9216ea9..74c6e41e31 100644 --- a/pkg/cluster/task/env_init.go +++ b/pkg/cluster/task/env_init.go @@ -35,8 +35,10 @@ var ( // 1. Generate SSH key // 2. ssh-copy-id type EnvInit struct { - host string - deployUser string + host string + deployUser string + userGroup string + skipCreateUser bool } // Execute implements the Task interface @@ -54,15 +56,18 @@ func (e *EnvInit) execute(ctx *Context) error { panic(ErrNoExecutor) } - um := module.NewUserModule(module.UserModuleConfig{ - Action: module.UserActionAdd, - Name: e.deployUser, - Sudoer: true, - }) - - _, _, errx := um.Execute(exec) - if errx != nil { - return wrapError(errx) + if !e.skipCreateUser { + um := module.NewUserModule(module.UserModuleConfig{ + Action: module.UserActionAdd, + Name: e.deployUser, + Group: e.userGroup, + Sudoer: true, + }) + + _, _, errx := um.Execute(exec) + if errx != nil { + return wrapError(errx) + } } pubKey, err := ioutil.ReadFile(ctx.PublicKeyPath) diff --git a/pkg/cluster/task/mkdir.go b/pkg/cluster/task/mkdir.go index d177094e26..55101c7e1c 100644 --- a/pkg/cluster/task/mkdir.go +++ b/pkg/cluster/task/mkdir.go @@ -31,7 +31,7 @@ type Mkdir struct { func (m *Mkdir) Execute(ctx *Context) error { exec, found := ctx.GetExecutor(m.host) if !found { - return ErrNoExecutor + panic(ErrNoExecutor) } for _, dir := range m.dirs { if !strings.HasPrefix(dir, "/") { @@ -52,7 +52,7 @@ func (m *Mkdir) Execute(ctx *Context) error { continue } cmd := fmt.Sprintf( - `test -d %[1]s || (mkdir -p %[1]s && chown %[2]s:%[2]s %[1]s)`, + `test -d %[1]s || (mkdir -p %[1]s && chown %[2]s:$(id -g -n %[2]s) %[1]s)`, strings.Join(xs[:i+1], "/"), m.user, ) diff --git a/tests/tiup-cluster/script/cmd_subtest.sh b/tests/tiup-cluster/script/cmd_subtest.sh index 79ab05224b..42b7e77042 100755 --- a/tests/tiup-cluster/script/cmd_subtest.sh +++ b/tests/tiup-cluster/script/cmd_subtest.sh @@ -23,7 +23,13 @@ function cmd_subtest() { tiup-cluster $client --yes check $topo -i ~/.ssh/id_rsa + # This should fail because there is no such user: tidb + ! tiup-cluster $client --yes deploy $name $version $topo -i ~/.ssh/id_rsa --skip-create-user + # This is a normal deploy tiup-cluster $client --yes deploy $name $version $topo -i ~/.ssh/id_rsa + # Cleanup cluster meta and test --skip-create-user again, this should success + rm -rf ~/.tiup/storage/cluster/clusters/$name + tiup-cluster $client --yes deploy $name $version $topo -i ~/.ssh/id_rsa --skip-create-user tiup-cluster $client list | grep "$name" diff --git a/tests/tiup-cluster/topo/full.yaml b/tests/tiup-cluster/topo/full.yaml index 31a569c3ff..30bc3cc583 100644 --- a/tests/tiup-cluster/topo/full.yaml +++ b/tests/tiup-cluster/topo/full.yaml @@ -1,3 +1,7 @@ +global: + user: tidb + group: pingcap + server_configs: tidb: binlog.enable: true