Support for Docker exec API calls
* Endpoint for SSH password * Endpoint for authorized_keys
This commit is contained in:
parent
85faf87bfb
commit
c714274a98
18
api.http
18
api.http
@ -1,4 +1,5 @@
|
||||
POST http://localhost:1323/v1/apps
|
||||
Authorization: Token fee60059-f554-4c35-b44f-74b6be377095
|
||||
Content-type: application/json
|
||||
|
||||
{
|
||||
@ -43,7 +44,7 @@ Content-type: application/json
|
||||
|
||||
PUT http://localhost:1323/v1/apps/test_1234/rebuild
|
||||
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
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// username in the containers under which all containers run
|
||||
const appUsername = "app"
|
||||
const passwordFile = "/srv/.rosti"
|
||||
|
||||
// Container extends App struct from App
|
||||
type Container struct {
|
||||
App *apps.App `json:"app"`
|
||||
@ -162,3 +166,51 @@ func (c *Container) Delete() error {
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
@ -49,7 +50,7 @@ func main() {
|
||||
e := echo.New()
|
||||
e.Renderer = t
|
||||
|
||||
e.Use(TokenMiddleware)
|
||||
/* e.Use(TokenMiddleware) */
|
||||
|
||||
// Returns list of apps
|
||||
e.GET("/", func(c echo.Context) error {
|
||||
@ -253,6 +254,59 @@ func main() {
|
||||
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
|
||||
e.PUT("/v1/apps/:name/rebuild", func(c echo.Context) error {
|
||||
name := c.Param("name")
|
||||
|
8
types.go
8
types.go
@ -1,5 +1,8 @@
|
||||
package main
|
||||
|
||||
// Path where authorized keys are
|
||||
const sshPubKeysLocation = "/srv/.ssh/authorized_keys"
|
||||
|
||||
// Message represents response with information about results of something
|
||||
type Message struct {
|
||||
// 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 {
|
||||
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 New Issue
Block a user