package glue import ( "errors" "fmt" "log" "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, } 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, } 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, } 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 { 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 }