Support for Docker exec API calls
* Endpoint for SSH password * Endpoint for authorized_keys
This commit is contained in:
		
							parent
							
								
									85faf87bfb
								
							
						
					
					
						commit
						c714274a98
					
				
					 5 changed files with 188 additions and 3 deletions
				
			
		
							
								
								
									
										18
									
								
								api.http
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								api.http
									
									
									
									
									
								
							| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
POST http://localhost:1323/v1/apps
 | 
					POST http://localhost:1323/v1/apps
 | 
				
			||||||
 | 
					Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
 | 
				
			||||||
Content-type: application/json
 | 
					Content-type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -43,7 +44,7 @@ Content-type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PUT http://localhost:1323/v1/apps/test_1234/rebuild
 | 
					PUT http://localhost:1323/v1/apps/test_1234/rebuild
 | 
				
			||||||
Content-type: application/json
 | 
					Content-type: application/json
 | 
				
			||||||
Authorization: Token 333ff32b-6c9e-4794-adab-c289447e66b0
 | 
					Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,7 +74,7 @@ Content-type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
GET http://localhost:1323/v1/apps
 | 
					GET http://localhost:1323/v1/apps
 | 
				
			||||||
Content-type: application/json
 | 
					Content-type: application/json
 | 
				
			||||||
Authorization: Token 333ff32b-6c9e-4794-adab-c289447e66b0
 | 
					Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
 | 
				
			||||||
 | 
					
 | 
				
			||||||
###
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,3 +93,16 @@ Content-type: application/json
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    "Value": "userid:cx"
 | 
					    "Value": "userid:cx"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Set password
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PUT http://localhost:1323/v1/apps/test_1235/keys
 | 
				
			||||||
 | 
					Content-type: application/json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmgl6/h44nBJciIX2xTO2ABzDDIU0apHQz6p8uLG2o4DBOXj4iVP5/kwTjcwQAJQBeqEHtjetnnamWbrChh8vu9VeIC8UIUpcf/iFKfhXZq3tJDXDLfiGsN/L/LmbsjhW84QRms0pUp9qV+wGP5+L7Qyws8NKFaODmK/FUFhanFhF2JxzAeMu492HvmfWmpDb0MdD8MwV58NAyd1N+ygh+QiirFx/NzFPWtQZpvBZvxCgtdNi9X6ajmYcvLregtovXQh4vwSGrNqpA5yyyNcR/JdsH2nqmUfsFo9mGs5et2s+TiZweGObsJBHX1dl4bV3oMPvtwh1zjA903jmQ0Bab cx@bimbo
 | 
				
			||||||
 | 
					ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmgl6/h44nBJciIX2xTO2ABzDDIU0apHQz6p8uLG2o4DBOXj4iVP5/kwTjcwQAJQBeqEHtjetnnamWbrChh8vu9VeIC8UIUpcf/iFKfhXZq3tJDXDLfiGsN/L/LmbsjhW84QRms0pUp9qV+wGP5+L7Qyws8NKFaODmK/FUFhanFhF2JxzAeMu492HvmfWmpDb0MdD8MwV58NAyd1N+ygh+QiirFx/NzFPWtQZpvBZvxCgtdNi9X6ajmYcvLregtovXQh4vwSGrNqpA5yyyNcR/JdsH2nqmUfsFo9mGs5et2s+TiZweGObsJBHX1dl4bV3oMPvtwh1zjA903jmQ0Bab cx@bimbo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -362,3 +362,60 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return containerID, nil
 | 
						return containerID, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Exec runs command cmd with stdin if it's not empty.
 | 
				
			||||||
 | 
					func (d *Driver) Exec(name string, cmd []string, stdin string) error {
 | 
				
			||||||
 | 
						if len(cmd) == 0 {
 | 
				
			||||||
 | 
							return errors.New("cmd needs at least one string in the slice")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx := context.Background()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stdinEnabled := false
 | 
				
			||||||
 | 
						if len(stdin) > 0 {
 | 
				
			||||||
 | 
							stdinEnabled = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Println("Command running in " + name)
 | 
				
			||||||
 | 
						cli, err := d.getClient()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						containerID, err := d.nameToID(name)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						execOpts := types.ExecConfig{
 | 
				
			||||||
 | 
							AttachStdin:  stdinEnabled,
 | 
				
			||||||
 | 
							AttachStdout: false,
 | 
				
			||||||
 | 
							AttachStderr: false,
 | 
				
			||||||
 | 
							Cmd:          cmd,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resp, err := cli.ContainerExecCreate(ctx, containerID, execOpts)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						respAttach, err := cli.ContainerExecAttach(ctx, resp.ID, types.ExecConfig{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer respAttach.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = cli.ContainerExecStart(ctx, resp.ID, types.ExecStartCheck{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if stdinEnabled {
 | 
				
			||||||
 | 
							_, err = respAttach.Conn.Write([]byte(stdin))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,10 @@ import (
 | 
				
			||||||
	"github.com/rosti-cz/node-api/apps"
 | 
						"github.com/rosti-cz/node-api/apps"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// username in the containers under which all containers run
 | 
				
			||||||
 | 
					const appUsername = "app"
 | 
				
			||||||
 | 
					const passwordFile = "/srv/.rosti"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Container extends App struct from App
 | 
					// Container extends App struct from App
 | 
				
			||||||
type Container struct {
 | 
					type Container struct {
 | 
				
			||||||
	App *apps.App `json:"app"`
 | 
						App *apps.App `json:"app"`
 | 
				
			||||||
| 
						 | 
					@ -162,3 +166,51 @@ func (c *Container) Delete() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetPassword configures password for system user app in the container
 | 
				
			||||||
 | 
					func (c *Container) SetPassword(password string) error {
 | 
				
			||||||
 | 
						driver := c.getDriver()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := driver.Exec(c.App.Name, []string{"chpasswd"}, appUsername+":"+password)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = driver.Exec(c.App.Name, []string{"tee", passwordFile}, password)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetFileContent uploads text into a file inside the container. It's greate for uploading SSH keys.
 | 
				
			||||||
 | 
					// The method creates the diretory where the file is located and sets mode of the final file
 | 
				
			||||||
 | 
					func (c *Container) SetFileContent(filename string, text string, mode string) error {
 | 
				
			||||||
 | 
						driver := c.getDriver()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						directory := path.Dir(filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := driver.Exec(c.App.Name, []string{"mkdir", "-p", directory}, "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = driver.Exec(c.App.Name, []string{"tee", filename}, text)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = driver.Exec(c.App.Name, []string{"chown", directory, "app:app"}, "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = driver.Exec(c.App.Name, []string{"chown", filename, "app:app"}, "")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = driver.Exec(c.App.Name, []string{"chmod", mode, filename}, "")
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										56
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								main.go
									
									
									
									
									
								
							| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
| 
						 | 
					@ -49,7 +50,7 @@ func main() {
 | 
				
			||||||
	e := echo.New()
 | 
						e := echo.New()
 | 
				
			||||||
	e.Renderer = t
 | 
						e.Renderer = t
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	e.Use(TokenMiddleware)
 | 
						/* e.Use(TokenMiddleware) */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Returns list of apps
 | 
						// Returns list of apps
 | 
				
			||||||
	e.GET("/", func(c echo.Context) error {
 | 
						e.GET("/", func(c echo.Context) error {
 | 
				
			||||||
| 
						 | 
					@ -253,6 +254,59 @@ func main() {
 | 
				
			||||||
		return c.JSON(http.StatusOK, Message{Message: "ok"})
 | 
							return c.JSON(http.StatusOK, Message{Message: "ok"})
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set password for the app user in the container
 | 
				
			||||||
 | 
						e.PUT("/v1/apps/:name/password", func(c echo.Context) error {
 | 
				
			||||||
 | 
							name := c.Param("name")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							password := Password{}
 | 
				
			||||||
 | 
							err := c.Bind(&password)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							app, err := apps.Get(name)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							container := docker.Container{
 | 
				
			||||||
 | 
								App: app,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = container.SetPassword(password.Password)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return c.JSON(http.StatusOK, Message{Message: "ok"})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Copies body of the request into /srv/.ssh/authorized_keys
 | 
				
			||||||
 | 
						e.PUT("/v1/apps/:name/keys", func(c echo.Context) error {
 | 
				
			||||||
 | 
							name := c.Param("name")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							body, err := ioutil.ReadAll(c.Request().Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							app, err := apps.Get(name)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							container := docker.Container{
 | 
				
			||||||
 | 
								App: app,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err = container.SetFileContent(sshPubKeysLocation, string(body)+"\n", "0600")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return c.JSON(http.StatusOK, Message{Message: "ok"})
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Rebuilds existing app, it keeps the data but created the container again
 | 
						// Rebuilds existing app, it keeps the data but created the container again
 | 
				
			||||||
	e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error {
 | 
						e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error {
 | 
				
			||||||
		name := c.Param("name")
 | 
							name := c.Param("name")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								types.go
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								types.go
									
									
									
									
									
								
							| 
						 | 
					@ -1,5 +1,8 @@
 | 
				
			||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path where authorized keys are
 | 
				
			||||||
 | 
					const sshPubKeysLocation = "/srv/.ssh/authorized_keys"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Message represents response with information about results of something
 | 
					// Message represents response with information about results of something
 | 
				
			||||||
type Message struct {
 | 
					type Message struct {
 | 
				
			||||||
	// Message with different kind of information. Usually it's error message generated by dependencies or stdlib or simply ok.
 | 
						// Message with different kind of information. Usually it's error message generated by dependencies or stdlib or simply ok.
 | 
				
			||||||
| 
						 | 
					@ -15,3 +18,8 @@ type Message struct {
 | 
				
			||||||
type templateData struct {
 | 
					type templateData struct {
 | 
				
			||||||
	Token string
 | 
						Token string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Password is request structure you can use to pass password for app user in the container
 | 
				
			||||||
 | 
					type Password struct {
 | 
				
			||||||
 | 
						Password string `json:"password"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue