node-api/docker/types.go

280 lines
6.6 KiB
Go
Raw Normal View History

2020-07-09 22:27:23 +00:00
package docker
import (
2020-07-11 21:14:45 +00:00
"log"
2020-07-09 22:27:23 +00:00
"path"
"strings"
2020-07-09 22:27:23 +00:00
2020-07-13 22:01:42 +00:00
"github.com/rosti-cz/node-api/apps"
"github.com/rosti-cz/node-api/common"
2020-07-09 22:27:23 +00:00
)
// username in the containers under which all containers run
const appUsername = "app"
const passwordFile = "/srv/.rosti"
// Process contains info about background application usually running in supervisor
type Process struct {
2020-08-27 21:46:03 +00:00
Name string `json:"name"`
State string `json:"state"`
}
2020-07-09 22:27:23 +00:00
// Container extends App struct from App
type Container struct {
2020-07-11 21:14:45 +00:00
App *apps.App `json:"app"`
2020-07-11 11:04:37 +00:00
}
func (c *Container) getDriver() *Driver {
config := common.GetConfig()
driver := &Driver{
BindIPHTTP: config.AppsBindIPHTTP,
BindIPSSH: config.AppsBindIPSSH,
}
2020-07-11 11:04:37 +00:00
return driver
2020-07-09 22:27:23 +00:00
}
// volumeHostPath each container has one volume mounted into it,
func (c *Container) volumeHostPath() string {
config := common.GetConfig()
return path.Join(config.AppsPath, c.App.Name)
2020-07-09 22:27:23 +00:00
}
// GetRawResourceStats returns RAW CPU and memory usage directly from Docker API
func (c *Container) GetRawResourceStats() (int64, int, error) {
driver := c.getDriver()
cpu, memory, err := driver.RawStats(c.App.Name)
return cpu, memory, err
}
// GetState returns app state object with populated state fields
2020-07-16 17:05:38 +00:00
func (c *Container) GetState() (*apps.AppState, error) {
2020-07-11 21:14:45 +00:00
status, err := c.Status()
if err != nil {
return nil, err
}
// TODO: this implementation takes more than one hour for 470 containers. It needs to be implemented differently.
// cpu, memory, err := c.ResourceUsage()
// if err != nil {
// return nil, err
// }
2020-07-11 21:14:45 +00:00
2020-07-15 21:32:28 +00:00
bytes, inodes, err := c.DiskUsage()
if err != nil {
return nil, err
}
state := apps.AppState{
State: status,
// CPUUsage: cpu,
// MemoryUsage: memory,
CPUUsage: -1.0,
MemoryUsage: -1.0,
2020-07-15 21:32:28 +00:00
DiskUsageBytes: bytes,
2020-07-16 17:05:38 +00:00
DiskUsageInodes: inodes,
2020-07-15 21:32:28 +00:00
}
2020-07-11 21:14:45 +00:00
2020-07-15 21:32:28 +00:00
return &state, nil
2020-07-11 21:14:45 +00:00
}
2020-07-09 22:27:23 +00:00
// Status returns state of the container
2020-07-11 21:14:45 +00:00
// Possible values: running, stopped, no-container, unknown
2020-07-09 22:27:23 +00:00
func (c *Container) Status() (string, error) {
2020-07-11 11:04:37 +00:00
status := "unknown"
// config := common.GetConfig()
// if _, err := os.Stat(path.Join(config.AppsPath, c.App.Name)); !os.IsNotExist(err) {
2020-07-11 11:04:37 +00:00
// status = "data-only"
// }
driver := c.getDriver()
containerStatus, err := driver.Status(c.App.Name)
2020-07-11 21:14:45 +00:00
if err != nil && err.Error() == "no container found" {
return "no-container", nil
}
2020-07-11 11:04:37 +00:00
if err != nil {
return status, err
}
2020-07-11 21:14:45 +00:00
status = containerStatus
2020-07-11 11:04:37 +00:00
return status, nil
2020-07-09 22:27:23 +00:00
}
// DiskUsage returns number of bytes and inodes used by the container in it's mounted volume
2020-07-11 11:04:37 +00:00
func (c *Container) DiskUsage() (int, int, error) {
2020-07-09 22:27:23 +00:00
return du(c.volumeHostPath())
}
2020-07-16 21:24:09 +00:00
// ResourceUsage returns amount of memory in B and CPU in % that the app occupies
2020-07-11 11:04:37 +00:00
func (c *Container) ResourceUsage() (float64, int, error) {
driver := c.getDriver()
cpu, memory, err := driver.Stats(c.App.Name)
if err != nil {
return 0.0, 0, err
}
return cpu, memory, nil
}
2020-07-09 22:27:23 +00:00
// Create creates the container
func (c *Container) Create() error {
2020-07-11 11:04:37 +00:00
driver := c.getDriver()
_, err := driver.Create(
c.App.Name,
c.App.Image,
c.volumeHostPath(),
c.App.HTTPPort,
c.App.SSHPort,
c.App.CPU,
c.App.Memory,
[]string{},
)
return err
2020-07-09 22:27:23 +00:00
}
// Start starts the container
func (c *Container) Start() error {
2020-07-11 11:04:37 +00:00
driver := c.getDriver()
return driver.Start(c.App.Name)
2020-07-09 22:27:23 +00:00
}
// Stop stops the container
func (c *Container) Stop() error {
2020-07-11 11:04:37 +00:00
driver := c.getDriver()
return driver.Stop(c.App.Name)
2020-07-09 22:27:23 +00:00
}
2020-07-11 11:04:37 +00:00
// Restart restarts the container
func (c *Container) Restart() error {
driver := c.getDriver()
err := driver.Stop(c.App.Name)
if err != nil {
return err
}
return driver.Start(c.App.Name)
2020-07-09 22:27:23 +00:00
}
// Destroy removes the container but keeps the data so it can be created again
func (c *Container) Destroy() error {
2020-07-11 11:04:37 +00:00
driver := c.getDriver()
return driver.Remove(c.App.Name)
2020-07-09 22:27:23 +00:00
}
// Delete removes both data and the container
func (c *Container) Delete() error {
status, err := c.Status()
2020-07-11 11:04:37 +00:00
if err != nil {
return err
}
// It's questionable to have this here. The problem is this method
// does two things, deleting the container and the data and when
// the deleted container doesn't exist we actually don't care
// and we can continue to remove the data.
if status != "no-container" {
err = c.Destroy()
if err != nil {
return err
}
}
config := common.GetConfig()
volumePath := path.Join(config.AppsPath, c.App.Name)
2020-07-11 11:04:37 +00:00
err = removeDirectory(volumePath)
2020-07-11 21:14:45 +00:00
if err != nil {
log.Println(err)
}
2020-07-11 11:04:37 +00:00
2020-07-11 21:14:45 +00:00
return nil
2020-07-09 22:27:23 +00:00
}
// 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, []string{}, false)
if err != nil {
return err
}
_, err = driver.Exec(c.App.Name, []string{"tee", passwordFile}, password, []string{}, false)
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}, "", []string{}, false)
if err != nil {
return err
}
_, err = driver.Exec(c.App.Name, []string{"tee", filename}, text, []string{}, false)
if err != nil {
return err
}
2020-08-31 10:29:34 +00:00
_, err = driver.Exec(c.App.Name, []string{"chown", "app:app", directory}, "", []string{}, false)
if err != nil {
return err
}
2020-08-31 10:29:34 +00:00
_, err = driver.Exec(c.App.Name, []string{"chown", "app:app", filename}, "", []string{}, false)
if err != nil {
return err
}
_, err = driver.Exec(c.App.Name, []string{"chmod", mode, filename}, "", []string{}, false)
return err
}
// SetTechnology prepares container for given technology (Python, PHP, Node.js, ...)
// Where tech can be php, python or node and latest available version is used.
func (c *Container) SetTechnology(tech string) error {
driver := c.getDriver()
_, err := driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech}, "", []string{}, false)
return err
}
// GetProcessList returns list of processes managed by supervisor.
func (c *Container) GetProcessList() (*[]Process, error) {
driver := c.getDriver()
processes := []Process{}
stdouterr, err := driver.Exec(c.App.Name, []string{"supervisorctl", "status"}, "", []string{}, true)
if err != nil {
return &processes, nil
}
trimmed := strings.TrimSpace(string(*stdouterr))
for _, row := range strings.Split(trimmed, "\n") {
fields := strings.Fields(row)
if len(fields) > 2 {
processes = append(processes, Process{
Name: fields[0],
State: fields[1],
})
}
}
return &processes, nil
}