commands/register.go (247 lines of code) (raw):
package commands
import (
"bufio"
"os"
"os/signal"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/urfave/cli"
"gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
"gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers/ssh"
"gitlab.com/gitlab-org/gitlab-ci-multi-runner/network"
)
type RegisterCommand struct {
context *cli.Context
network common.Network
reader *bufio.Reader
registered bool
configOptions
TagList string `long:"tag-list" env:"RUNNER_TAG_LIST" description:"Tag list"`
NonInteractive bool `short:"n" long:"non-interactive" env:"REGISTER_NON_INTERACTIVE" description:"Run registration unattended"`
LeaveRunner bool `long:"leave-runner" env:"REGISTER_LEAVE_RUNNER" description:"Don't remove runner if registration fails"`
RegistrationToken string `short:"r" long:"registration-token" env:"REGISTRATION_TOKEN" description:"Runner's registration token"`
RunUntagged bool `long:"run-untagged" env:"REGISTER_RUN_UNTAGGED" description:"Register to run untagged builds; defaults to 'true' when 'tag-list' is empty"`
Locked bool `long:"locked" env:"REGISTER_LOCKED" description:"Lock Runner for current project, defaults to 'true'"`
common.RunnerConfig
}
func (s *RegisterCommand) askOnce(prompt string, result *string, allowEmpty bool) bool {
println(prompt)
if *result != "" {
print("["+*result, "]: ")
}
if s.reader == nil {
s.reader = bufio.NewReader(os.Stdin)
}
data, _, err := s.reader.ReadLine()
if err != nil {
panic(err)
}
newResult := string(data)
newResult = strings.TrimSpace(newResult)
if newResult != "" {
*result = newResult
return true
}
if allowEmpty || *result != "" {
return true
}
return false
}
func (s *RegisterCommand) ask(key, prompt string, allowEmptyOptional ...bool) string {
allowEmpty := len(allowEmptyOptional) > 0 && allowEmptyOptional[0]
result := s.context.String(key)
result = strings.TrimSpace(result)
if s.NonInteractive || prompt == "" {
if result == "" && !allowEmpty {
log.Panicln("The", key, "needs to be entered")
}
return result
}
for {
if s.askOnce(prompt, &result, allowEmpty) {
break
}
}
return result
}
func (s *RegisterCommand) askExecutor() {
for {
names := common.GetExecutors()
executors := strings.Join(names, ", ")
s.Executor = s.ask("executor", "Please enter the executor: "+executors+":", true)
if common.GetExecutor(s.Executor) != nil {
return
}
message := "Invalid executor specified"
if s.NonInteractive {
log.Panicln(message)
} else {
log.Errorln(message)
}
}
}
func (s *RegisterCommand) askDocker() {
if s.Docker == nil {
s.Docker = &common.DockerConfig{}
}
s.Docker.Image = s.ask("docker-image", "Please enter the default Docker image (e.g. ruby:2.1):")
s.Docker.Volumes = append(s.Docker.Volumes, "/cache")
}
func (s *RegisterCommand) askParallels() {
s.Parallels.BaseName = s.ask("parallels-base-name", "Please enter the Parallels VM (e.g. my-vm):")
}
func (s *RegisterCommand) askVirtualBox() {
s.VirtualBox.BaseName = s.ask("virtualbox-base-name", "Please enter the VirtualBox VM (e.g. my-vm):")
}
func (s *RegisterCommand) askSSHServer() {
s.SSH.Host = s.ask("ssh-host", "Please enter the SSH server address (e.g. my.server.com):")
s.SSH.Port = s.ask("ssh-port", "Please enter the SSH server port (e.g. 22):", true)
}
func (s *RegisterCommand) askSSHLogin() {
s.SSH.User = s.ask("ssh-user", "Please enter the SSH user (e.g. root):")
s.SSH.Password = s.ask("ssh-password", "Please enter the SSH password (e.g. docker.io):", true)
s.SSH.IdentityFile = s.ask("ssh-identity-file", "Please enter path to SSH identity file (e.g. /home/user/.ssh/id_rsa):", true)
}
func (s *RegisterCommand) addRunner(runner *common.RunnerConfig) {
s.config.Runners = append(s.config.Runners, runner)
}
func (s *RegisterCommand) askRunner() {
s.URL = s.ask("url", "Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):")
if s.Token != "" {
log.Infoln("Token specified trying to verify runner...")
log.Warningln("If you want to register use the '-r' instead of '-t'.")
if !s.network.VerifyRunner(s.RunnerCredentials) {
log.Panicln("Failed to verify this runner. Perhaps you are having network problems")
}
} else {
// we store registration token as token, since we pass that to RunnerCredentials
s.Token = s.ask("registration-token", "Please enter the gitlab-ci token for this runner:")
s.Name = s.ask("name", "Please enter the gitlab-ci description for this runner:")
s.TagList = s.ask("tag-list", "Please enter the gitlab-ci tags for this runner (comma separated):", true)
if s.TagList == "" {
s.RunUntagged = true
} else {
runUntagged, err := strconv.ParseBool(s.ask("run-untagged", "Whether to run untagged builds [true/false]:", true))
if err != nil {
log.Panicf("Failed to parse option 'run-untagged': %v", err)
} else {
s.RunUntagged = runUntagged
}
}
locked, err := strconv.ParseBool(s.ask("locked", "Whether to lock the Runner to current project [true/false]:", true))
if err != nil {
log.Panicf("Failed to parse option 'locked': %v", err)
}
result := s.network.RegisterRunner(s.RunnerCredentials, s.Name, s.TagList, s.RunUntagged, locked)
if result == nil {
log.Panicln("Failed to register this runner. Perhaps you are having network problems")
}
s.Token = result.Token
s.registered = true
}
}
func (s *RegisterCommand) askExecutorOptions() {
kubernetes := s.Kubernetes
machine := s.Machine
docker := s.Docker
ssh := s.SSH
parallels := s.Parallels
virtualbox := s.VirtualBox
s.Kubernetes = nil
s.Machine = nil
s.Docker = nil
s.SSH = nil
s.Parallels = nil
s.VirtualBox = nil
switch s.Executor {
case "kubernetes":
s.Kubernetes = kubernetes
case "docker+machine":
s.Machine = machine
s.Docker = docker
s.askDocker()
case "docker-ssh+machine":
s.Machine = machine
s.Docker = docker
s.SSH = ssh
s.askDocker()
s.askSSHLogin()
case "docker":
s.Docker = docker
s.askDocker()
case "docker-ssh":
s.Docker = docker
s.SSH = ssh
s.askDocker()
s.askSSHLogin()
case "ssh":
s.SSH = ssh
s.askSSHServer()
s.askSSHLogin()
case "parallels":
s.SSH = ssh
s.Parallels = parallels
s.askParallels()
s.askSSHServer()
case "virtualbox":
s.SSH = ssh
s.VirtualBox = virtualbox
s.askVirtualBox()
s.askSSHLogin()
}
}
func (s *RegisterCommand) Execute(context *cli.Context) {
userModeWarning(true)
s.context = context
err := s.loadConfig()
if err != nil {
log.Panicln(err)
}
s.askRunner()
if !s.LeaveRunner {
defer func() {
// De-register runner on panic
if r := recover(); r != nil {
if s.registered {
s.network.UnregisterRunner(s.RunnerCredentials)
}
// pass panic to next defer
panic(r)
}
}()
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
go func() {
signal := <-signals
s.network.UnregisterRunner(s.RunnerCredentials)
log.Fatalf("RECEIVED SIGNAL: %v", signal)
}()
}
if s.config.Concurrent < s.Limit {
log.Warningf("Specified limit (%d) larger then current concurrent limit (%d). Concurrent limit will not be enlarged.", s.Limit, s.config.Concurrent)
}
s.askExecutor()
s.askExecutorOptions()
s.addRunner(&s.RunnerConfig)
s.saveConfig()
log.Printf("Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!")
}
func getHostname() string {
hostname, _ := os.Hostname()
return hostname
}
func init() {
common.RegisterCommand2("register", "register a new runner", &RegisterCommand{
RunnerConfig: common.RunnerConfig{
Name: getHostname(),
RunnerSettings: common.RunnerSettings{
Kubernetes: &common.KubernetesConfig{},
Cache: &common.CacheConfig{},
Machine: &common.DockerMachine{},
Docker: &common.DockerConfig{},
SSH: &ssh.Config{},
Parallels: &common.ParallelsConfig{},
VirtualBox: &common.VirtualBoxConfig{},
},
},
network: network.NewGitLabClient(),
})
}