Adam Štrauch
35a0c5acd1
All checks were successful
continuous-integration/drone/push Build is passing
661 lines
13 KiB
Go
661 lines
13 KiB
Go
package glue
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jinzhu/gorm"
|
|
"github.com/rosti-cz/node-api/apps"
|
|
"github.com/rosti-cz/node-api/containers"
|
|
docker "github.com/rosti-cz/node-api/containers"
|
|
"github.com/rosti-cz/node-api/detector"
|
|
"github.com/rosti-cz/node-api/node"
|
|
)
|
|
|
|
// Processor separates logic of apps, containers, detector and node from handlers.
|
|
// It defines common interface for both types of handlers, HTTP and the events.
|
|
type Processor struct {
|
|
AppName string
|
|
DB *gorm.DB
|
|
SnapshotProcessor *apps.SnapshotProcessor
|
|
NodeProcessor *node.Processor
|
|
WaitForAppLoops uint // each loop is five seconds
|
|
DockerSock string
|
|
BindIPHTTP string
|
|
BindIPSSH string
|
|
AppsPath string
|
|
}
|
|
|
|
// Return prepared Container instance
|
|
func (p *Processor) getContainer() (containers.Container, error) {
|
|
container := containers.Container{}
|
|
|
|
processor := p.getAppProcessor()
|
|
app, err := processor.Get(p.AppName)
|
|
if err != nil {
|
|
return container, err
|
|
}
|
|
|
|
container = docker.Container{
|
|
App: &app,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
return container, nil
|
|
}
|
|
|
|
// returns instance of getAppProcessor
|
|
func (p *Processor) getAppProcessor() apps.AppsProcessor {
|
|
processor := apps.AppsProcessor{
|
|
DB: p.DB,
|
|
}
|
|
processor.Init()
|
|
return processor
|
|
}
|
|
|
|
// waits until app is ready
|
|
func (p *Processor) waitForApp() error {
|
|
sleepFor := 5 * time.Second
|
|
loops := 6
|
|
if p.WaitForAppLoops != 0 {
|
|
loops = int(p.WaitForAppLoops)
|
|
}
|
|
|
|
statsProcessor := StatsProcessor{
|
|
DB: p.DB,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
for i := 0; i < loops; i++ {
|
|
err := statsProcessor.UpdateState(p.AppName)
|
|
if err != nil {
|
|
time.Sleep(sleepFor)
|
|
continue
|
|
}
|
|
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if status == "running" {
|
|
return nil
|
|
}
|
|
|
|
time.Sleep(sleepFor)
|
|
}
|
|
|
|
return errors.New("timeout reached")
|
|
}
|
|
|
|
// List returns list of apps
|
|
func (p *Processor) List() (apps.Apps, error) {
|
|
appList := apps.Apps{}
|
|
|
|
statsProcessor := StatsProcessor{
|
|
DB: p.DB,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
err := statsProcessor.GatherStates()
|
|
if err != nil {
|
|
return appList, fmt.Errorf("backend error: %v", err)
|
|
}
|
|
|
|
processor := p.getAppProcessor()
|
|
appList, err = processor.List()
|
|
|
|
if err != nil {
|
|
return appList, fmt.Errorf("backend error: %v", err)
|
|
}
|
|
|
|
return appList, err
|
|
}
|
|
|
|
// Get returns one app
|
|
func (p *Processor) Get() (apps.App, error) {
|
|
app := apps.App{}
|
|
|
|
statsProcessor := StatsProcessor{
|
|
DB: p.DB,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
err := statsProcessor.UpdateState(p.AppName)
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
processor := p.getAppProcessor()
|
|
app, err = processor.Get(p.AppName)
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
// Gather runtime info about the container
|
|
container := docker.Container{
|
|
App: &app,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
if status == "running" {
|
|
var err error
|
|
app.Techs, err = container.GetTechs()
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
app.PrimaryTech, err = container.GetPrimaryTech()
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
processList, err := container.GetSystemProcesses()
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
|
|
flags, err := detector.Check(processList)
|
|
if err != nil {
|
|
return app, err
|
|
}
|
|
app.Flags = flags.String()
|
|
}
|
|
|
|
return app, nil
|
|
}
|
|
|
|
// Create creates a single app in the system
|
|
func (p *Processor) Create(appTemplate apps.App) error {
|
|
err := p.Register(appTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
container := docker.Container{
|
|
App: &appTemplate,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
err = container.Create()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Restore from snapshot if it's noted in the request
|
|
if len(appTemplate.Snapshot) > 0 {
|
|
log.Printf("App %s is going to be created from %s snapshot\n", appTemplate.Name, appTemplate.Snapshot)
|
|
|
|
// Restore the data
|
|
err = p.SnapshotProcessor.RestoreSnapshot(appTemplate.Snapshot, appTemplate.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = container.Start()
|
|
return err
|
|
}
|
|
|
|
// Register registers app without creating a container for it
|
|
// Returns app name and an error
|
|
func (p *Processor) Register(appTemplate apps.App) error {
|
|
processor := p.getAppProcessor()
|
|
err := processor.New(appTemplate.Name, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory)
|
|
if err != nil {
|
|
if validationError, ok := err.(apps.ValidationError); ok {
|
|
return fmt.Errorf("validation error: %v", validationError.Error())
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update updates application
|
|
func (p *Processor) Update(appTemplate apps.App) error {
|
|
processor := p.getAppProcessor()
|
|
app, err := processor.Update(appTemplate.Name, appTemplate.SSHPort, appTemplate.HTTPPort, appTemplate.Image, appTemplate.CPU, appTemplate.Memory)
|
|
if err != nil {
|
|
if validationError, ok := err.(apps.ValidationError); ok {
|
|
return fmt.Errorf("validation error: %v", validationError.Error())
|
|
}
|
|
return err
|
|
}
|
|
|
|
container := docker.Container{
|
|
App: app,
|
|
DockerSock: p.DockerSock,
|
|
BindIPHTTP: p.BindIPHTTP,
|
|
BindIPSSH: p.BindIPSSH,
|
|
AppsPath: p.AppsPath,
|
|
}
|
|
|
|
err = container.Destroy()
|
|
if err != nil && err.Error() == "no container found" {
|
|
// We don't care if the container didn't exist anyway
|
|
err = nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.Create()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Delete removes app from the system
|
|
func (p *Processor) Delete() error {
|
|
processor := p.getAppProcessor()
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
log.Println("ERROR: delete app:", err.Error())
|
|
}
|
|
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if status != "no-container" {
|
|
// We stop the container first
|
|
err = container.Stop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Then delete it
|
|
err = container.Delete()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = processor.Delete(p.AppName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop stops app
|
|
func (p *Processor) Stop() error {
|
|
container, err := p.getContainer()
|
|
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Stop the container only when it exists
|
|
if status != "no-container" {
|
|
err = container.Stop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Start starts app
|
|
func (p *Processor) Start() error {
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if status == "no-container" {
|
|
err = container.Create()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = container.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Restart restarts app
|
|
func (p *Processor) Restart() error {
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.Restart()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateKeys uploads SSH keys into the container
|
|
// keys parameters is just a string, one key per line
|
|
func (p *Processor) UpdateKeys(keys string) error {
|
|
err := p.waitForApp()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.SetFileContent(sshPubKeysLocation, keys+"\n", "0600")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetPassword sets up a new password to access the SSH user
|
|
func (p *Processor) SetPassword(password string) error {
|
|
err := p.waitForApp()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.SetPassword(password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Processes returns list of supervisord processes
|
|
func (p *Processor) Processes() ([]docker.Process, error) {
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
processes, err := container.GetProcessList()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return processes, nil
|
|
}
|
|
|
|
// EnableTech sets up runtime for new tech or new version a tech
|
|
func (p *Processor) EnableTech(service, version string) error {
|
|
err := p.waitForApp()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.SetTechnology(service, version)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Rebuild recreates container for app
|
|
func (p *Processor) Rebuild() error {
|
|
container, err := p.getContainer()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.Destroy()
|
|
if err != nil && !strings.Contains(err.Error(), "no container found") {
|
|
return err
|
|
}
|
|
|
|
err = container.Create()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = container.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddLabel adds a label to the app
|
|
func (p *Processor) AddLabel(label string) error {
|
|
appProcessor := p.getAppProcessor()
|
|
err := appProcessor.AddLabel(p.AppName, label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveLabel removes a label from the app
|
|
func (p *Processor) RemoveLabel(label string) error {
|
|
appProcessor := p.getAppProcessor()
|
|
err := appProcessor.RemoveLabel(p.AppName, label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetNote returns node's info
|
|
func (p *Processor) GetNode() (*node.Node, error) {
|
|
node, err := p.NodeProcessor.GetNodeInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
// CreateSnapshot creates a snapshot of given app
|
|
func (p *Processor) CreateSnapshot(labels []string) error {
|
|
_, err := p.SnapshotProcessor.CreateSnapshot(p.AppName, labels)
|
|
return err
|
|
}
|
|
|
|
// RestoreFromSnapshot restores app from given snapshot
|
|
func (p *Processor) RestoreFromSnapshot(snapshotName string) error {
|
|
container, err := p.getContainer()
|
|
|
|
// Stop the container
|
|
status, err := container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Stop the container only when it exists
|
|
if status != "no-container" {
|
|
err = container.Stop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Restore the data
|
|
err = p.SnapshotProcessor.RestoreSnapshot(snapshotName, p.AppName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Start the container
|
|
status, err = container.Status()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if status == "no-container" {
|
|
err = container.Create()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = container.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListSnapshots returns list of snapshots
|
|
func (p *Processor) ListSnapshots() (SnapshotsMetadata, error) {
|
|
snapshots, err := p.SnapshotProcessor.ListAppSnapshots(p.AppName)
|
|
if err != nil {
|
|
return SnapshotsMetadata{}, err
|
|
}
|
|
|
|
var output SnapshotsMetadata
|
|
for _, snapshot := range snapshots {
|
|
output = append(output, SnapshotMetadata{
|
|
Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel),
|
|
Metadata: snapshot,
|
|
})
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// ListAppsSnapshots returns list of snapshots for given app list
|
|
func (p *Processor) ListAppsSnapshots(appNames []string) (SnapshotsMetadata, error) {
|
|
snapshots, err := p.SnapshotProcessor.ListAppsSnapshots(appNames)
|
|
if err != nil {
|
|
return SnapshotsMetadata{}, err
|
|
}
|
|
|
|
var output SnapshotsMetadata
|
|
for _, snapshot := range snapshots {
|
|
output = append(output, SnapshotMetadata{
|
|
Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel),
|
|
Metadata: snapshot,
|
|
})
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// ListSnapshotsByLabel returns list of snapshots by given label
|
|
func (p *Processor) ListSnapshotsByLabel(label string) (SnapshotsMetadata, error) {
|
|
snapshots, err := p.SnapshotProcessor.ListAppsSnapshotsByLabel(label)
|
|
if err != nil {
|
|
return SnapshotsMetadata{}, err
|
|
}
|
|
|
|
var output SnapshotsMetadata
|
|
for _, snapshot := range snapshots {
|
|
output = append(output, SnapshotMetadata{
|
|
Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel),
|
|
Metadata: snapshot,
|
|
})
|
|
}
|
|
|
|
return output, nil
|
|
}
|
|
|
|
// GetSnapshot returns info about a single snapshot
|
|
func (p *Processor) GetSnapshot(snapshotName string) (SnapshotMetadata, error) {
|
|
snapshot, err := p.SnapshotProcessor.GetSnapshot(snapshotName)
|
|
if err != nil {
|
|
return SnapshotMetadata{}, err
|
|
}
|
|
output := SnapshotMetadata{
|
|
Key: snapshot.KeyName(p.SnapshotProcessor.IndexLabel),
|
|
Metadata: snapshot,
|
|
}
|
|
|
|
return output, nil
|
|
|
|
}
|
|
|
|
// GetSnapshotDownloadLink return download link for a snapshot
|
|
func (p *Processor) GetSnapshotDownloadLink(snapshotName string) (string, error) {
|
|
link, err := p.SnapshotProcessor.GetDownloadLink(snapshotName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return link, nil
|
|
}
|
|
|
|
// DeleteSnapshot deletes a snapshot
|
|
func (p *Processor) DeleteSnapshot(snapshotName string) error {
|
|
err := p.SnapshotProcessor.DeleteSnapshot(snapshotName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteAppSnapshots deletes all snapshot of given app
|
|
func (p *Processor) DeleteAppSnapshots() error {
|
|
err := p.SnapshotProcessor.DeleteAppSnapshots(p.AppName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|