diff --git a/apps/types.go b/apps/types.go index a1445d8..f768a01 100644 --- a/apps/types.go +++ b/apps/types.go @@ -111,6 +111,7 @@ type App struct { Tech string `json:"tech"` TechVersion string `json:"tech_version"` Password string `json:"password"` + ArchiveURL string `json:"archive_url"` // Archive with content of /srv } `json:"setup,omitempty" gorm:"-"` } diff --git a/containers/types.go b/containers/types.go index c3a5ebd..0ab0f9f 100644 --- a/containers/types.go +++ b/containers/types.go @@ -57,7 +57,7 @@ func (c *Container) getDriver() *Driver { } // volumeHostPath each container has one volume mounted into it, -func (c *Container) volumeHostPath() string { +func (c *Container) VolumeHostPath() string { return path.Join(c.AppsPath, c.App.Name) } @@ -132,7 +132,7 @@ func (c *Container) Status() (ContainerStatus, error) { // DiskUsage returns number of bytes and inodes used by the container in it's mounted volume func (c *Container) DiskUsage() (int, int, error) { - return du(c.volumeHostPath()) + return du(c.VolumeHostPath()) } // ResourceUsage returns amount of memory in B and CPU in % that the app occupies @@ -153,7 +153,7 @@ func (c *Container) Create() error { _, err := driver.Create( c.App.Name, c.App.Image, - c.volumeHostPath(), + c.VolumeHostPath(), c.App.HTTPPort, c.App.SSHPort, c.App.CPU, @@ -586,7 +586,7 @@ func (c *Container) GetTechs() (apps.AppTechs, error) { // Returns info about active technology func (c *Container) GetActiveTech() (*TechInfo, error) { - info, err := getTechAndVersion(path.Join(c.volumeHostPath(), "bin", "primary_tech")) + info, err := getTechAndVersion(path.Join(c.VolumeHostPath(), "bin", "primary_tech")) if err != nil { return info, err } diff --git a/glue/main.go b/glue/main.go index e6e343d..badd005 100644 --- a/glue/main.go +++ b/glue/main.go @@ -3,7 +3,11 @@ package glue import ( "errors" "fmt" + "io" "log" + "net/http" + "os" + "os/exec" "strings" "time" @@ -200,6 +204,64 @@ func (p *Processor) Get(noUpdate bool) (apps.App, error) { return app, nil } +// Takes URL with an tar archive and prepares container's volume from it. +func (p *Processor) volumeFromURL(url string, container *docker.Container) error { + // Validation, check if url ends with tar.zstd + if !strings.HasSuffix(url, ".tar.zstd") { + return fmt.Errorf("archive has to end with .tar.zstd") + } + + volumePath := container.VolumeHostPath() + + // Prepare volume path + err := os.MkdirAll(volumePath, 0755) + if err != nil { + return fmt.Errorf("failed to create volume path: %v", err) + } + + // Download the archive + log.Printf("%s: downloading archive from %s\n", container.App.Name, url) + f, err := os.Create(volumePath + "/archive.tar.zstd") + if err != nil { + return fmt.Errorf("failed to create archive file: %v", err) + } + defer f.Close() + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("failed to download archive: %v", err) + } + defer resp.Body.Close() + + n, err := io.Copy(f, resp.Body) + if err != nil { + return fmt.Errorf("failed to download archive: %v", err) + } + log.Printf("downloaded %d bytes\n", n) + + // Extract the archive + log.Printf("%s: extracting archive\n", container.App.Name) + + // Call tar xf archive.tar.zstd -C /volume + cmd := exec.Command("tar", "-I", "zstd", "-xf", "archive.tar.zstd", "-C", volumePath) + err = cmd.Run() + if err != nil { + log.Printf("%s: failed to extract archive: %v", container.App.Name, err) + return err + } + + // Remove archive + log.Printf("%s: removing archive\n", container.App.Name) + err = os.Remove(volumePath + "/archive.tar.zstd") + if err != nil { + return fmt.Errorf("failed to remove archive: %v", err) + } + + log.Printf("%s: volume preparing done\n", container.App.Name) + + return nil +} + // Create creates a single app in the system func (p *Processor) Create(appTemplate apps.App) error { if appTemplate.EnvRaw == nil { @@ -219,6 +281,17 @@ func (p *Processor) Create(appTemplate apps.App) error { AppsPath: p.AppsPath, } + if len(appTemplate.Snapshot) > 0 && len(appTemplate.Setup.ArchiveURL) > 0 { + return fmt.Errorf("snapshot and archive_url cannot be used together") + } + + if len(appTemplate.Setup.ArchiveURL) > 0 { + err = p.volumeFromURL(appTemplate.Setup.ArchiveURL, &container) + if err != nil { + return fmt.Errorf("failed to prepare volume: %v", err) + } + } + err = container.Create() if err != nil { return err