forked from toolshed/abra
		
	fix: grand ssh, provisioning, perms refactor
See coop-cloud/organising#280. See coop-cloud/organising#273.
This commit is contained in:
		| @ -57,11 +57,11 @@ Supported shells are as follows: | ||||
| 		} | ||||
|  | ||||
| 		autocompletionDir := path.Join(config.ABRA_DIR, "autocompletion") | ||||
| 		if err := os.Mkdir(autocompletionDir, 0644); err != nil { | ||||
| 		if err := os.Mkdir(autocompletionDir, 0764); err != nil { | ||||
| 			if !os.IsExist(err) { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 			logrus.Debugf("'%s' already created, moving on...", autocompletionDir) | ||||
| 			logrus.Debugf("%s already created", autocompletionDir) | ||||
| 		} | ||||
|  | ||||
| 		autocompletionFile := path.Join(config.ABRA_DIR, "autocompletion", shellType) | ||||
|  | ||||
| @ -201,7 +201,7 @@ A new catalogue copy can be published to the recipes repository by passing the | ||||
| 		} | ||||
|  | ||||
| 		if _, err := os.Stat(config.APPS_JSON); err != nil && os.IsNotExist(err) { | ||||
| 			if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil { | ||||
| 			if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0764); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} else { | ||||
| @ -216,7 +216,7 @@ A new catalogue copy can be published to the recipes repository by passing the | ||||
| 				if err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 				if err := ioutil.WriteFile(config.APPS_JSON, updatedRecipesJSON, 0644); err != nil { | ||||
| 				if err := ioutil.WriteFile(config.APPS_JSON, updatedRecipesJSON, 0764); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @ -76,7 +76,7 @@ convenient command-line experience. See "abra autocomplete -h" for more. | ||||
| 		} | ||||
|  | ||||
| 		for _, path := range paths { | ||||
| 			if err := os.Mkdir(path, 0644); err != nil { | ||||
| 			if err := os.Mkdir(path, 0764); err != nil { | ||||
| 				if !os.IsExist(err) { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
|  | ||||
| @ -129,11 +129,11 @@ func NewAction(c *cli.Context) error { | ||||
|  | ||||
| 	sanitisedAppName := config.SanitiseAppName(NewAppName) | ||||
| 	if len(sanitisedAppName) > 45 { | ||||
| 		logrus.Fatalf("'%s' cannot be longer than 45 characters", sanitisedAppName) | ||||
| 		logrus.Fatalf("%s cannot be longer than 45 characters", sanitisedAppName) | ||||
| 	} | ||||
| 	logrus.Debugf("'%s' sanitised as '%s' for new app", NewAppName, sanitisedAppName) | ||||
| 	logrus.Debugf("%s sanitised as %s for new app", NewAppName, sanitisedAppName) | ||||
|  | ||||
| 	if err := config.TemplateAppEnvSample(recipe.Name, NewAppName, NewAppServer, Domain, recipe.Name); err != nil { | ||||
| 	if err := config.TemplateAppEnvSample(recipe.Name, NewAppName, NewAppServer, Domain); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -61,7 +61,7 @@ The new example repository is cloned to ~/.abra/apps/<recipe>. | ||||
| 			path.Join(config.APPS_DIR, recipeName, ".drone.yml"), | ||||
| 		} | ||||
| 		for _, path := range toParse { | ||||
| 			file, err := os.OpenFile(path, os.O_RDWR, 0644) | ||||
| 			file, err := os.OpenFile(path, os.O_RDWR, 0664) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| @ -10,7 +10,6 @@ import ( | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
|  | ||||
| 	abraFormatter "coopcloud.tech/abra/cli/formatter" | ||||
| 	"coopcloud.tech/abra/cli/internal" | ||||
| 	"coopcloud.tech/abra/pkg/client" | ||||
| 	"coopcloud.tech/abra/pkg/config" | ||||
| @ -127,7 +126,7 @@ func installDockerLocal(c *cli.Context) error { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	cmd := exec.Command("bash", "-c", "wget -O- https://install.abra.coopcloud.tech | bash") | ||||
| 	cmd := exec.Command("bash", "-c", "wget -O- https://get.docker.com | bash") | ||||
| 	if err := internal.RunCmd(cmd); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -219,14 +218,21 @@ func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *ssh.Client, d | ||||
| 		prompt := &survey.Confirm{ | ||||
| 			Message: fmt.Sprintf("attempt install docker on %s?", domainName), | ||||
| 		} | ||||
|  | ||||
| 		if err := survey.AskOne(prompt, &response); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if !response { | ||||
| 			logrus.Fatal("exiting as requested") | ||||
| 		} | ||||
|  | ||||
| 		for _, exe := range []string{"wget", "bash"} { | ||||
| 		exes := []string{"wget", "bash"} | ||||
| 		if askSudoPass { | ||||
| 			exes = append(exes, "ssh-askpass") | ||||
| 		} | ||||
|  | ||||
| 		for _, exe := range exes { | ||||
| 			exists, err := ensureRemoteExecutable(exe, sshCl) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| @ -236,29 +242,78 @@ func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *ssh.Client, d | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		cmd := "wget -O- https://install.abra.coopcloud.tech | bash" | ||||
|  | ||||
| 		var sudoPass string | ||||
| 		if askSudoPass { | ||||
| 			cmd := "wget -O- https://get.docker.com | bash" | ||||
|  | ||||
| 			prompt := &survey.Password{ | ||||
| 				Message: "sudo password?", | ||||
| 			} | ||||
|  | ||||
| 			if err := survey.AskOne(prompt, &sudoPass); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
|  | ||||
| 			logrus.Debugf("running %s on %s now with sudo password", cmd, domainName) | ||||
|  | ||||
| 			if sudoPass == "" { | ||||
| 				return fmt.Errorf("missing sudo password but requested --ask-sudo-pass?") | ||||
| 			} | ||||
|  | ||||
| 			logrus.Warn("installing docker, this could take some time...") | ||||
|  | ||||
| 			if err := ssh.RunSudoCmd(cmd, sudoPass, sshCl); err != nil { | ||||
| 				fmt.Print(fmt.Sprintf(` | ||||
| Abra was unable to bootstrap Docker, see below for logs: | ||||
|  | ||||
|  | ||||
| %s | ||||
|  | ||||
| If nothing works, you try running the Docker install script manually on your server: | ||||
|  | ||||
|     wget -O- https://get.docker.com | bash | ||||
|  | ||||
| `, string(err.Error()))) | ||||
| 				logrus.Fatal("Process exited with status 1") | ||||
| 			} | ||||
|  | ||||
| 			logrus.Infof("docker is installed on %s", domainName) | ||||
|  | ||||
| 			remoteUser := sshCl.SSHClient.Conn.User() | ||||
| 			logrus.Infof("adding %s to docker group", remoteUser) | ||||
| 			permsCmd := fmt.Sprintf("sudo usermod -aG docker %s", remoteUser) | ||||
| 			if err := ssh.RunSudoCmd(permsCmd, sudoPass, sshCl); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} else { | ||||
| 			cmd := "wget -O- https://get.docker.com | bash" | ||||
|  | ||||
| 			logrus.Debugf("running %s on %s now without sudo password", cmd, domainName) | ||||
| 			if err := ssh.Exec(cmd, sshCl); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 			logrus.Warn("installing docker, this could take some time...") | ||||
|  | ||||
| 			if out, err := sshCl.Exec(cmd); err != nil { | ||||
| 				fmt.Print(fmt.Sprintf(` | ||||
| Abra was unable to bootstrap Docker, see below for logs: | ||||
|  | ||||
|  | ||||
| %s | ||||
|  | ||||
| This could be due to a number of things but one of the most common is that your | ||||
| server user account does not have sudo access, and if it does, you need to pass | ||||
| "--ask-sudo-pass" in order to supply Abra with your password. | ||||
|  | ||||
| If nothing works, you try running the Docker install script manually on your server: | ||||
|  | ||||
|     wget -O- https://get.docker.com | bash | ||||
|  | ||||
| `, string(out))) | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 	logrus.Infof("docker is already installed on %s", domainName) | ||||
| 			logrus.Infof("docker is installed on %s", domainName) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @ -266,10 +321,12 @@ func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *ssh.Client, d | ||||
| func initSwarmLocal(c *cli.Context, cl *dockerClient.Client, domainName string) error { | ||||
| 	initReq := swarm.InitRequest{ListenAddr: "0.0.0.0:2377"} | ||||
| 	if _, err := cl.SwarmInit(c.Context, initReq); err != nil { | ||||
| 		if !strings.Contains(err.Error(), "is already part of a swarm") { | ||||
| 		if strings.Contains(err.Error(), "is already part of a swarm") || | ||||
| 			strings.Contains(err.Error(), "must specify a listening address") { | ||||
| 			logrus.Infof("swarm mode already initialised on %s", domainName) | ||||
| 		} else { | ||||
| 			return err | ||||
| 		} | ||||
| 		logrus.Info("swarm mode already initialised on local server") | ||||
| 	} else { | ||||
| 		logrus.Infof("initialised swarm mode on local server") | ||||
| 	} | ||||
| @ -298,11 +355,12 @@ func initSwarm(c *cli.Context, cl *dockerClient.Client, domainName string) error | ||||
| 		AdvertiseAddr: ipv4, | ||||
| 	} | ||||
| 	if _, err := cl.SwarmInit(c.Context, initReq); err != nil { | ||||
| 		if !strings.Contains(err.Error(), "is already part of a swarm") || | ||||
| 			!strings.Contains(err.Error(), "must specify a listening address") { | ||||
| 		if strings.Contains(err.Error(), "is already part of a swarm") || | ||||
| 			strings.Contains(err.Error(), "must specify a listening address") { | ||||
| 			logrus.Infof("swarm mode already initialised on %s", domainName) | ||||
| 		} else { | ||||
| 			return err | ||||
| 		} | ||||
| 		logrus.Infof("swarm mode already initialised on %s", domainName) | ||||
| 	} else { | ||||
| 		logrus.Infof("initialised swarm mode on %s", domainName) | ||||
| 	} | ||||
| @ -339,16 +397,8 @@ func deployTraefik(c *cli.Context, cl *dockerClient.Client, domainName string) e | ||||
| 	internal.NewAppName = fmt.Sprintf("%s_%s", "traefik", config.SanitiseAppName(domainName)) | ||||
|  | ||||
| 	appEnvPath := path.Join(config.ABRA_DIR, "servers", internal.Domain, fmt.Sprintf("%s.env", internal.NewAppName)) | ||||
| 	if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) { | ||||
| 		fmt.Println(fmt.Sprintf(` | ||||
| 	You specified "--traefik/-t" and that means that Abra will now try to | ||||
| 	automatically create a new Traefik app on %s. | ||||
| 	`, internal.NewAppServer)) | ||||
|  | ||||
| 		tableCol := []string{"recipe", "domain", "server", "name"} | ||||
| 		table := abraFormatter.CreateTable(tableCol) | ||||
| 		table.Append([]string{internal.RecipeName, internal.Domain, internal.NewAppServer, internal.NewAppName}) | ||||
|  | ||||
| 	if _, err := os.Stat(appEnvPath); os.IsNotExist(err) { | ||||
| 		logrus.Info(fmt.Sprintf("-t/--traefik specified, automatically deploying traefik to %s", internal.NewAppServer)) | ||||
| 		if err := internal.NewAction(c); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| @ -515,7 +565,7 @@ func ensureLocalExecutable(exe string) (bool, error) { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	return string(out) == "", nil | ||||
| 	return string(out) != "", nil | ||||
| } | ||||
|  | ||||
| // ensureRemoteExecutable ensures that an executable is present on a remote machine | ||||
| @ -525,5 +575,5 @@ func ensureRemoteExecutable(exe string, sshCl *ssh.Client) (bool, error) { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	return string(out) == "", nil | ||||
| 	return string(out) != "", nil | ||||
| } | ||||
|  | ||||
| @ -133,7 +133,7 @@ like tears in rain. | ||||
|  | ||||
| 			response := false | ||||
| 			prompt := &survey.Confirm{ | ||||
| 				Message: "are you sure there is no server to delete?", | ||||
| 				Message: "prompt to actual server deletion?", | ||||
| 			} | ||||
| 			if err := survey.AskOne(prompt, &response); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
|  | ||||
| @ -209,7 +209,7 @@ func readRecipeCatalogueWeb(target interface{}) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil { | ||||
| 	if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0764); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -71,7 +71,7 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | ||||
|  | ||||
| 				logrus.Debugf("updating '%s' to '%s' in '%s'", old, new, compose.Filename) | ||||
|  | ||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil { | ||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| @ -137,7 +137,7 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error { | ||||
|  | ||||
| 				logrus.Debugf("updating %s to %s in %s", old, label, compose.Filename) | ||||
|  | ||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0644); err != nil { | ||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
|  | ||||
|  | ||||
| @ -248,22 +248,22 @@ func GetAppNames() ([]string, error) { | ||||
| } | ||||
|  | ||||
| // TemplateAppEnvSample copies the example env file for the app into the users env files | ||||
| func TemplateAppEnvSample(appType, appName, server, domain, recipe string) error { | ||||
| 	envSamplePath := path.Join(ABRA_DIR, "apps", appType, ".env.sample") | ||||
| func TemplateAppEnvSample(recipe, appName, server, domain string) error { | ||||
| 	envSamplePath := path.Join(ABRA_DIR, "apps", recipe, ".env.sample") | ||||
| 	envSample, err := ioutil.ReadFile(envSamplePath) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	appEnvPath := path.Join(ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName)) | ||||
| 	if _, err := os.Stat(appEnvPath); err == nil { | ||||
| 	if _, err := os.Stat(appEnvPath); os.IsExist(err) { | ||||
| 		return fmt.Errorf("%s already exists?", appEnvPath) | ||||
| 	} | ||||
|  | ||||
| 	envSample = []byte(strings.Replace(string(envSample), fmt.Sprintf("%s.example.com", recipe), domain, -1)) | ||||
| 	envSample = []byte(strings.Replace(string(envSample), "example.com", domain, -1)) | ||||
|  | ||||
| 	err = ioutil.WriteFile(appEnvPath, envSample, 0644) | ||||
| 	err = ioutil.WriteFile(appEnvPath, envSample, 0664) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| @ -128,7 +128,7 @@ func GetAllFoldersInDirectory(directory string) ([]string, error) { | ||||
| func EnsureAbraDirExists() error { | ||||
| 	if _, err := os.Stat(ABRA_DIR); os.IsNotExist(err) { | ||||
| 		logrus.Debugf("%s does not exist, creating it", ABRA_DIR) | ||||
| 		if err := os.Mkdir(ABRA_DIR, 0777); err != nil { | ||||
| 		if err := os.Mkdir(ABRA_DIR, 0764); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @ -12,7 +12,7 @@ import ( | ||||
| func CreateServerDir(serverName string) error { | ||||
| 	serverPath := path.Join(config.ABRA_DIR, "servers", serverName) | ||||
|  | ||||
| 	if err := os.Mkdir(serverPath, 0644); err != nil { | ||||
| 	if err := os.Mkdir(serverPath, 0764); err != nil { | ||||
| 		if !os.IsExist(err) { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| @ -111,7 +111,7 @@ type sudoWriter struct { | ||||
|  | ||||
| // Write satisfies the write interface for sudoWriter | ||||
| func (w *sudoWriter) Write(p []byte) (int, error) { | ||||
| 	if string(p) == "sudo_password" { | ||||
| 	if strings.Contains(string(p), "sudo_password") { | ||||
| 		w.stdin.Write([]byte(w.pw + "\n")) | ||||
| 		w.pw = "" | ||||
| 		return len(p), nil | ||||
| @ -131,11 +131,9 @@ func RunSudoCmd(cmd, passwd string, cl *Client) error { | ||||
| 	} | ||||
| 	defer session.Close() | ||||
|  | ||||
| 	cmd = "sudo -p " + "sudo_password" + " -S " + cmd | ||||
| 	sudoCmd := fmt.Sprintf("SSH_ASKPASS=/usr/bin/ssh-askpass; sudo -p sudo_password -S %s", cmd) | ||||
|  | ||||
| 	w := &sudoWriter{ | ||||
| 		pw: passwd, | ||||
| 	} | ||||
| 	w := &sudoWriter{pw: passwd} | ||||
| 	w.stdin, err = session.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @ -144,79 +142,19 @@ func RunSudoCmd(cmd, passwd string, cl *Client) error { | ||||
| 	session.Stdout = w | ||||
| 	session.Stderr = w | ||||
|  | ||||
| 	done := make(chan struct{}) | ||||
| 	scanner := bufio.NewScanner(session.Stdin) | ||||
|  | ||||
| 	go func() { | ||||
| 		for scanner.Scan() { | ||||
| 			line := scanner.Text() | ||||
| 			fmt.Println(line) | ||||
| 		} | ||||
| 		done <- struct{}{} | ||||
| 	}() | ||||
|  | ||||
| 	if err := session.Start(cmd); err != nil { | ||||
| 		return err | ||||
| 	modes := ssh.TerminalModes{ | ||||
| 		ssh.ECHO:          0, | ||||
| 		ssh.TTY_OP_ISPEED: 14400, | ||||
| 		ssh.TTY_OP_OSPEED: 14400, | ||||
| 	} | ||||
|  | ||||
| 	<-done | ||||
|  | ||||
| 	if err := session.Wait(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Exec runs a command on a remote and streams output | ||||
| func Exec(cmd string, cl *Client) error { | ||||
| 	session, err := cl.SSHClient.NewSession() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer session.Close() | ||||
|  | ||||
| 	stdout, err := session.StdoutPipe() | ||||
| 	err = session.RequestPty("xterm", 80, 40, modes) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	stderr, err := session.StdoutPipe() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	stdoutDone := make(chan struct{}) | ||||
| 	stdoutScanner := bufio.NewScanner(stdout) | ||||
|  | ||||
| 	go func() { | ||||
| 		for stdoutScanner.Scan() { | ||||
| 			line := stdoutScanner.Text() | ||||
| 			fmt.Println(line) | ||||
| 		} | ||||
| 		stdoutDone <- struct{}{} | ||||
| 	}() | ||||
|  | ||||
| 	stderrDone := make(chan struct{}) | ||||
| 	stderrScanner := bufio.NewScanner(stderr) | ||||
|  | ||||
| 	go func() { | ||||
| 		for stderrScanner.Scan() { | ||||
| 			line := stderrScanner.Text() | ||||
| 			fmt.Println(line) | ||||
| 		} | ||||
| 		stderrDone <- struct{}{} | ||||
| 	}() | ||||
|  | ||||
| 	if err := session.Start(cmd); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	<-stdoutDone | ||||
| 	<-stderrDone | ||||
|  | ||||
| 	if err := session.Wait(); err != nil { | ||||
| 		return err | ||||
| 	if err := session.Run(sudoCmd); err != nil { | ||||
| 		return fmt.Errorf("%s", string(w.b.Bytes())) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| @ -320,7 +258,7 @@ func HostKeyAddCallback(hostnameAndPort string, remote net.Addr, pubKey ssh.Publ | ||||
|  | ||||
| 	if exists { | ||||
| 		hostname := strings.Split(hostnameAndPort, ":")[0] | ||||
| 		logrus.Debugf("server SSH host key found for %s, moving on", hostname) | ||||
| 		logrus.Debugf("server SSH host key found for %s", hostname) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user