Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
c7ed0f148f | |||
ec1f0d3f3a | |||
3d559fc94d | |||
0bc10b7154 | |||
0d81ffaa86 | |||
a94ccbc554 | |||
08f213e0de | |||
231441f980 | |||
622dd91ba6 | |||
bc875b83a4 | |||
27152bad72 | |||
27948ee5b6 | |||
8c5a419efc | |||
88262a27d8 | |||
088b6bdcf6 | |||
5f80da8cbd | |||
2077271306 | |||
9e82bfc2b5 | |||
e0b5832e75 | |||
863d857283 | |||
1c5b8d8f50 | |||
bc4b6c7bff | |||
45899f3b0c | |||
5513da35b3 | |||
31ba1ce5a3 |
16 changed files with 2324 additions and 591 deletions
|
@ -11,7 +11,7 @@ jobs:
|
||||||
deploy-production:
|
deploy-production:
|
||||||
runs-on: [amd64, prod]
|
runs-on: [amd64, prod]
|
||||||
env:
|
env:
|
||||||
NODES: node-22.rosti.cz node-23.rosti.cz node-24.rosti.cz node-25.rosti.cz
|
NODES: node-22.rosti.cz node-23.rosti.cz node-24.rosti.cz node-25.rosti.cz node-28.rosti.cz node-29.rosti.cz
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: deploy
|
- name: deploy
|
||||||
|
|
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unittests:
|
unittests:
|
||||||
runs-on: [amd64, moon]
|
runs-on: [amd64, dev]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
|
@ -36,11 +36,14 @@ jobs:
|
||||||
# - 9001:9001
|
# - 9001:9001
|
||||||
# options: server /data --console-address :9001
|
# options: server /data --console-address :9001
|
||||||
deploy-dev:
|
deploy-dev:
|
||||||
runs-on: [amd64, moon]
|
runs-on: [amd64, dev]
|
||||||
env:
|
env:
|
||||||
NODES: "192.168.1.236"
|
NODES: "192.168.1.199"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
- uses: webfactory/ssh-agent@v0.9.0
|
||||||
|
with:
|
||||||
|
ssh-private-key: ${{ secrets.SSH_MASTER_KEY }}
|
||||||
- name: deploy
|
- name: deploy
|
||||||
run: |
|
run: |
|
||||||
# echo LS1
|
# echo LS1
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (a *AppsProcessor) Update(name string, SSHPort int, HTTPPort int, image str
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateResources updates various metrics saved in the database
|
// UpdateResources updates various metrics saved in the database
|
||||||
func (a *AppsProcessor) UpdateResources(name string, state string, OOMKilled bool, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int, flags detector.Flags) error {
|
func (a *AppsProcessor) UpdateResources(name string, state string, OOMKilled bool, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int, flags detector.Flags, isPasswordSet bool) error {
|
||||||
err := a.DB.Model(&App{}).Where("name = ?", name).Updates(App{
|
err := a.DB.Model(&App{}).Where("name = ?", name).Updates(App{
|
||||||
State: state,
|
State: state,
|
||||||
OOMKilled: OOMKilled,
|
OOMKilled: OOMKilled,
|
||||||
|
@ -124,6 +124,7 @@ func (a *AppsProcessor) UpdateResources(name string, state string, OOMKilled boo
|
||||||
DiskUsageBytes: diskUsageBytes,
|
DiskUsageBytes: diskUsageBytes,
|
||||||
DiskUsageInodes: diskUsageInodes,
|
DiskUsageInodes: diskUsageInodes,
|
||||||
Flags: flags.String(),
|
Flags: flags.String(),
|
||||||
|
IsPasswordSet: isPasswordSet,
|
||||||
}).Error
|
}).Error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ func TestAppsProcessorUpdateResources(t *testing.T) {
|
||||||
err := processor.New("updateresources_1224", 1002, 1003, "testimage", 2, 256)
|
err := processor.New("updateresources_1224", 1002, 1003, "testimage", 2, 256)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = processor.UpdateResources("updateresources_1224", "running", true, 1000, 256, 100, 200, detector.Flags{"test"})
|
err = processor.UpdateResources("updateresources_1224", "running", true, 1000, 256, 100, 200, detector.Flags{"test"}, true)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
app, err := processor.Get("updateresources_1224")
|
app, err := processor.Get("updateresources_1224")
|
||||||
|
@ -107,6 +107,7 @@ func TestAppsProcessorUpdateResources(t *testing.T) {
|
||||||
assert.Equal(t, 256, app.MemoryUsage)
|
assert.Equal(t, 256, app.MemoryUsage)
|
||||||
assert.Equal(t, 100, app.DiskUsageBytes)
|
assert.Equal(t, 100, app.DiskUsageBytes)
|
||||||
assert.Equal(t, 200, app.DiskUsageInodes)
|
assert.Equal(t, 200, app.DiskUsageInodes)
|
||||||
|
assert.Equal(t, true, app.IsPasswordSet)
|
||||||
assert.Contains(t, app.Flags, "test")
|
assert.Contains(t, app.Flags, "test")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ type AppState struct {
|
||||||
DiskUsageBytes int `json:"disk_usage_bytes"`
|
DiskUsageBytes int `json:"disk_usage_bytes"`
|
||||||
DiskUsageInodes int `json:"disk_usage_inodes"`
|
DiskUsageInodes int `json:"disk_usage_inodes"`
|
||||||
Flags detector.Flags `json:"flags"`
|
Flags detector.Flags `json:"flags"`
|
||||||
|
IsPasswordSet bool `json:"is_password_set"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apps is list of applications
|
// Apps is list of applications
|
||||||
|
@ -95,6 +96,7 @@ type App struct {
|
||||||
DiskUsageInodes int `json:"disk_usage_inodes"`
|
DiskUsageInodes int `json:"disk_usage_inodes"`
|
||||||
// Flags from detector of problems in the container
|
// Flags from detector of problems in the container
|
||||||
Flags string `json:"flags"` // flags are separated by comma
|
Flags string `json:"flags"` // flags are separated by comma
|
||||||
|
IsPasswordSet bool `json:"is_password_set"` // True if the password is set in the container (file with the password exists)
|
||||||
|
|
||||||
// this is gathered in docker package and has to be assembled externally
|
// this is gathered in docker package and has to be assembled externally
|
||||||
Techs AppTechs `json:"techs,omitempty" gorm:"-"` // list of available technologies in the image
|
Techs AppTechs `json:"techs,omitempty" gorm:"-"` // list of available technologies in the image
|
||||||
|
|
|
@ -202,13 +202,13 @@ func (d *Driver) Remove(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := time.Duration(dockerTimeout * time.Second)
|
timeout := dockerTimeout
|
||||||
err = cli.ContainerStop(context.TODO(), containerID, &timeout)
|
err = cli.ContainerStop(context.TODO(), containerID, container.StopOptions{Timeout: &timeout})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cli.ContainerRemove(context.TODO(), containerID, types.ContainerRemoveOptions{})
|
err = cli.ContainerRemove(context.TODO(), containerID, container.RemoveOptions{})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -246,8 +246,8 @@ func (d *Driver) Stop(name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := time.Duration(dockerTimeout * time.Second)
|
timeout := dockerTimeout
|
||||||
err = cli.ContainerStop(context.TODO(), containerID, &timeout)
|
err = cli.ContainerStop(context.TODO(), containerID, container.StopOptions{Timeout: &timeout})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -338,10 +338,13 @@ func (d *Driver) Create(name string, image string, volumePath string, HTTPPort i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OOMKillDisable := false
|
// OOMKillDisable := false
|
||||||
if memory < 1500 {
|
// if memory < 1500 {
|
||||||
OOMKillDisable = true
|
// OOMKillDisable = true
|
||||||
}
|
// }
|
||||||
|
// We disable OOM killer because it keeps containers in resource heavy loop
|
||||||
|
// This is from some discussion: If OOM-killer is disabled, tasks under cgroup will hang/sleep in memory cgroup's OOM-waitqueue when they request accountable memory
|
||||||
|
OOMKillDisable := true
|
||||||
|
|
||||||
envList := []string{}
|
envList := []string{}
|
||||||
for key, value := range env {
|
for key, value := range env {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
const appUsername = "app"
|
const appUsername = "app"
|
||||||
const owner = "app:app"
|
const owner = "app:app"
|
||||||
const passwordFile = "/srv/.rosti"
|
const passwordFile = "/srv/.rosti"
|
||||||
|
const passwordFileNoPath = ".rosti"
|
||||||
const deployKeyType = "ed25519"
|
const deployKeyType = "ed25519"
|
||||||
const deployKeyPrefix = "rosti_deploy"
|
const deployKeyPrefix = "rosti_deploy"
|
||||||
|
|
||||||
|
@ -96,6 +98,11 @@ func (c *Container) GetState() (*apps.AppState, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPasswordSet, err := c.IsPasswordSet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
state := apps.AppState{
|
state := apps.AppState{
|
||||||
State: status.Status,
|
State: status.Status,
|
||||||
OOMKilled: status.OOMKilled,
|
OOMKilled: status.OOMKilled,
|
||||||
|
@ -106,6 +113,7 @@ func (c *Container) GetState() (*apps.AppState, error) {
|
||||||
DiskUsageBytes: bytes,
|
DiskUsageBytes: bytes,
|
||||||
DiskUsageInodes: inodes,
|
DiskUsageInodes: inodes,
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
|
IsPasswordSet: isPasswordSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &state, nil
|
return &state, nil
|
||||||
|
@ -135,6 +143,19 @@ func (c *Container) DiskUsage() (int, int, error) {
|
||||||
return du(c.VolumeHostPath())
|
return du(c.VolumeHostPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPasswordSet returns true if the password is set for the container (file with the password exists)
|
||||||
|
func (c *Container) IsPasswordSet() (bool, error) {
|
||||||
|
_, err := os.Stat(path.Join(c.VolumeHostPath(), passwordFileNoPath))
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ResourceUsage returns amount of memory in B and CPU in % that the app occupies
|
// ResourceUsage returns amount of memory in B and CPU in % that the app occupies
|
||||||
func (c *Container) ResourceUsage() (float64, int, error) {
|
func (c *Container) ResourceUsage() (float64, int, error) {
|
||||||
driver := c.getDriver()
|
driver := c.getDriver()
|
||||||
|
@ -242,6 +263,23 @@ func (c *Container) SetPassword(password string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearPassword removes password for system user app in the container
|
||||||
|
func (c *Container) ClearPassword() error {
|
||||||
|
driver := c.getDriver()
|
||||||
|
|
||||||
|
_, err := driver.Exec(c.App.Name, []string{"passwd", "-d", "app"}, "", []string{}, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = driver.Exec(c.App.Name, []string{"rm", "-f", passwordFile}, "", []string{}, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Generate SSH keys and copies it into authorized keys
|
// Generate SSH keys and copies it into authorized keys
|
||||||
// Returns true if the key was generated in this call and error if there is any.
|
// Returns true if the key was generated in this call and error if there is any.
|
||||||
// The container has to run for this to work.
|
// The container has to run for this to work.
|
||||||
|
@ -430,7 +468,7 @@ func (c *Container) SetTechnology(tech string, version string) error {
|
||||||
output, err = driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech + " " + version}, "", []string{}, false)
|
output, err = driver.Exec(c.App.Name, []string{"su", "app", "-c", "rosti " + tech + " " + version}, "", []string{}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("DEBUG: enable tech for %s output: %s", c.App.Name, string(*output))
|
log.Printf("DEBUG: enable tech %s/%s for %s output: %s", tech, version, c.App.Name, string(*output))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -525,9 +563,16 @@ func (c *Container) GetPrimaryTech() (apps.AppTech, error) {
|
||||||
parts := strings.Split(string(*stdouterr), "/")
|
parts := strings.Split(string(*stdouterr), "/")
|
||||||
if len(parts) == 5 {
|
if len(parts) == 5 {
|
||||||
rawTech := parts[3]
|
rawTech := parts[3]
|
||||||
|
if rawTech == "default" {
|
||||||
|
return apps.AppTech{
|
||||||
|
Name: "default",
|
||||||
|
Version: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
techParts := strings.Split(rawTech, "-")
|
techParts := strings.Split(rawTech, "-")
|
||||||
if len(techParts) != 2 {
|
if len(techParts) != 2 {
|
||||||
return tech, errors.New("wrong number of tech parts")
|
return tech, errors.New("wrong number of tech parts (" + rawTech + ")")
|
||||||
}
|
}
|
||||||
return apps.AppTech{
|
return apps.AppTech{
|
||||||
Name: techParts[0],
|
Name: techParts[0],
|
||||||
|
|
85
glue/main.go
85
glue/main.go
|
@ -100,6 +100,9 @@ func (p *Processor) waitForApp() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if status.Status == "running" {
|
if status.Status == "running" {
|
||||||
|
if i > 0 {
|
||||||
|
time.Sleep(sleepFor) // We wait a little bit more to make sure the container is fully started
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +177,12 @@ func (p *Processor) Get(noUpdate bool) (apps.App, error) {
|
||||||
AppsPath: p.AppsPath,
|
AppsPath: p.AppsPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPasswordSet, err := container.IsPasswordSet()
|
||||||
|
if err != nil {
|
||||||
|
return app, err
|
||||||
|
}
|
||||||
|
app.IsPasswordSet = isPasswordSet
|
||||||
|
|
||||||
status, err := container.Status()
|
status, err := container.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return app, err
|
return app, err
|
||||||
|
@ -422,6 +431,25 @@ func (p *Processor) Update(appTemplate apps.App) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup technology if it's noted in the request
|
||||||
|
if len(appTemplate.Setup.Tech) > 0 {
|
||||||
|
err := p.waitForApp()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.EnableTech(appTemplate.Setup.Tech, appTemplate.Setup.TechVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable tech: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We restart the container so everything can use the new tech
|
||||||
|
err = container.Restart()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,6 +459,7 @@ func (p *Processor) Delete() error {
|
||||||
container, err := p.getContainer()
|
container, err := p.getContainer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("ERROR: delete app:", err.Error())
|
log.Println("ERROR: delete app:", err.Error())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := container.Status()
|
status, err := container.Status()
|
||||||
|
@ -571,6 +600,26 @@ func (p *Processor) SetPassword(password string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearPassword removes password from the SSH user
|
||||||
|
func (p *Processor) ClearPassword() error {
|
||||||
|
err := p.waitForApp()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := p.getContainer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = container.ClearPassword()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Generate SSH key and adds it into authorized_keys
|
// Generate SSH key and adds it into authorized_keys
|
||||||
// These pair of keys is used for deployment.
|
// These pair of keys is used for deployment.
|
||||||
// Returns private key, pubkey and error.
|
// Returns private key, pubkey and error.
|
||||||
|
@ -634,6 +683,42 @@ func (p *Processor) GetHostKey() (string, error) {
|
||||||
return hostKey, nil
|
return hostKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save meta data about app into a file
|
||||||
|
func (p *Processor) SaveMetadata(metadata string) error {
|
||||||
|
container, err := p.getContainer()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
volumePath := container.VolumeHostPath()
|
||||||
|
|
||||||
|
f, err := os.Create(path.Join(volumePath, ".metadata.json"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.Write([]byte(metadata))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set permissions
|
||||||
|
err = os.Chmod(path.Join(volumePath, ".metadata.json"), 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set owner
|
||||||
|
err = os.Chown(path.Join(volumePath, ".metadata.json"), ownerUID, ownerGID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Processes returns list of supervisord processes
|
// Processes returns list of supervisord processes
|
||||||
func (p *Processor) Processes() ([]docker.Process, error) {
|
func (p *Processor) Processes() ([]docker.Process, error) {
|
||||||
container, err := p.getContainer()
|
container, err := p.getContainer()
|
||||||
|
|
|
@ -59,6 +59,7 @@ func (s *StatsProcessor) UpdateUsage(name string) error {
|
||||||
state.DiskUsageBytes,
|
state.DiskUsageBytes,
|
||||||
state.DiskUsageInodes,
|
state.DiskUsageInodes,
|
||||||
state.Flags,
|
state.Flags,
|
||||||
|
state.IsPasswordSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -2,6 +2,9 @@ package glue
|
||||||
|
|
||||||
import "github.com/rosti-cz/node-api/apps"
|
import "github.com/rosti-cz/node-api/apps"
|
||||||
|
|
||||||
|
const ownerUID = 1000
|
||||||
|
const ownerGID = 1000
|
||||||
|
|
||||||
// Path where authorized keys are
|
// Path where authorized keys are
|
||||||
const sshPubKeysLocation = "/srv/.ssh/authorized_keys"
|
const sshPubKeysLocation = "/srv/.ssh/authorized_keys"
|
||||||
|
|
||||||
|
|
23
go.mod
23
go.mod
|
@ -3,30 +3,31 @@ module github.com/rosti-cz/node-api
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.4.18 // indirect
|
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
|
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||||
github.com/containerd/containerd v1.5.9 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/docker/docker v20.10.12+incompatible
|
github.com/distribution/reference v0.5.0 // indirect
|
||||||
|
github.com/docker/docker v25.0.3+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
|
github.com/docker/go-units v0.5.0 // indirect
|
||||||
github.com/getsentry/sentry-go v0.26.0
|
github.com/getsentry/sentry-go v0.26.0
|
||||||
github.com/gobuffalo/packr v1.30.1
|
github.com/gobuffalo/packr v1.30.1
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
|
||||||
github.com/jinzhu/gorm v1.9.14
|
github.com/jinzhu/gorm v1.9.14
|
||||||
github.com/kelseyhightower/envconfig v1.4.0
|
github.com/kelseyhightower/envconfig v1.4.0
|
||||||
github.com/labstack/echo/v4 v4.10.0
|
github.com/labstack/echo/v4 v4.10.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
|
||||||
github.com/minio/minio-go/v7 v7.0.14
|
github.com/minio/minio-go/v7 v7.0.14
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/moby/term v0.5.0 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/nats-io/nats.go v1.23.0
|
github.com/nats-io/nats.go v1.23.0
|
||||||
github.com/opencontainers/image-spec v1.0.2
|
github.com/opencontainers/image-spec v1.0.2
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/satori/go.uuid v1.2.0
|
github.com/satori/go.uuid v1.2.0
|
||||||
github.com/shirou/gopsutil v2.20.6+incompatible
|
github.com/shirou/gopsutil v2.20.6+incompatible
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.4
|
||||||
google.golang.org/grpc v1.44.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||||
gorm.io/driver/mysql v1.4.7
|
gorm.io/driver/mysql v1.4.7
|
||||||
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
|
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55
|
||||||
gotest.tools/v3 v3.1.0 // indirect
|
gotest.tools/v3 v3.5.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
167
handlers.go
167
handlers.go
|
@ -2,7 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -211,11 +211,31 @@ func setPasswordHandler(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, Message{Message: "ok"})
|
return c.JSON(http.StatusOK, Message{Message: "ok"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear password for the app user in the container
|
||||||
|
func clearPasswordHandler(c echo.Context) error {
|
||||||
|
name := c.Param("name")
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: name,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
err := processor.ClearPassword()
|
||||||
|
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
|
// Copies body of the request into /srv/.ssh/authorized_keys
|
||||||
func setKeysHandler(c echo.Context) error {
|
func setKeysHandler(c echo.Context) error {
|
||||||
name := c.Param("name")
|
name := c.Param("name")
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(c.Request().Body)
|
body, err := io.ReadAll(c.Request().Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||||
}
|
}
|
||||||
|
@ -362,6 +382,33 @@ func getOrphansHander(c echo.Context) error {
|
||||||
return c.JSON(http.StatusOK, []string{})
|
return c.JSON(http.StatusOK, []string{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save metadata for the app
|
||||||
|
func saveMetadataHandler(c echo.Context) error {
|
||||||
|
name := c.Param("name")
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: name,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(c.Request().Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading request body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = processor.SaveMetadata(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error while save metadata: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Return info about the node including performance index
|
// Return info about the node including performance index
|
||||||
func getNodeInfoHandler(c echo.Context) error {
|
func getNodeInfoHandler(c echo.Context) error {
|
||||||
processor := glue.Processor{
|
processor := glue.Processor{
|
||||||
|
@ -447,3 +494,119 @@ func metricsHandler(c echo.Context) error {
|
||||||
|
|
||||||
return c.String(http.StatusOK, metrics)
|
return c.String(http.StatusOK, metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateSnapshotHandler(c echo.Context) error {
|
||||||
|
name := c.Param("name")
|
||||||
|
|
||||||
|
body := createSnapshotBody{}
|
||||||
|
err := c.Bind(body)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: name,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = processor.CreateSnapshot(body.Labels)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSONPretty(http.StatusOK, Message{Message: "ok"}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RestoreFromSnapshotHandler(c echo.Context) error {
|
||||||
|
name := c.Param("name")
|
||||||
|
snapshot := c.Param("snapshot")
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: name,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := processor.RestoreFromSnapshot(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSONPretty(http.StatusOK, Message{Message: "ok"}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListSnapshotsHandler(c echo.Context) error {
|
||||||
|
name := c.Param("name")
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: name,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshots, err := processor.ListSnapshots()
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, snapshots)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListAppsSnapshotsHandler(c echo.Context) error {
|
||||||
|
name := c.Param("name")
|
||||||
|
apps := []string{}
|
||||||
|
|
||||||
|
err := c.Bind(&apps)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusBadRequest, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: name,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
snapshots, err := processor.ListAppsSnapshots(apps)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, snapshots)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListSnapshotsByLabelHandler(c echo.Context) error {
|
||||||
|
label := c.Param("label")
|
||||||
|
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: "",
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
snapshots, err := processor.ListSnapshotsByLabel(label)
|
||||||
|
if err != nil {
|
||||||
|
return c.JSONPretty(http.StatusInternalServerError, Message{Message: err.Error()}, JSONIndent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, snapshots)
|
||||||
|
}
|
||||||
|
|
|
@ -57,12 +57,14 @@ func _messageHandler(m *nats.Msg) error {
|
||||||
"get_active_tech": getActiveTechHandler,
|
"get_active_tech": getActiveTechHandler,
|
||||||
"update_keys": updateKeysEventHandler,
|
"update_keys": updateKeysEventHandler,
|
||||||
"set_password": setPasswordEventHandler,
|
"set_password": setPasswordEventHandler,
|
||||||
|
"clear_password": clearPasswordEventHandler,
|
||||||
"processes": processesEventHandler,
|
"processes": processesEventHandler,
|
||||||
"enable_tech": enableTechEventHandler,
|
"enable_tech": enableTechEventHandler,
|
||||||
"rebuild": rebuildEventHandler,
|
"rebuild": rebuildEventHandler,
|
||||||
"add_label": addLabelEventHandler,
|
"add_label": addLabelEventHandler,
|
||||||
"remove_label": removeLabelEventHandler,
|
"remove_label": removeLabelEventHandler,
|
||||||
"list_orphans": listOrphansEventHandler,
|
"list_orphans": listOrphansEventHandler,
|
||||||
|
"save_metadata": saveMetadataEventHandler,
|
||||||
"node": getNodeEventHandler,
|
"node": getNodeEventHandler,
|
||||||
"create_snapshot": createSnapshotEventHandler,
|
"create_snapshot": createSnapshotEventHandler,
|
||||||
"restore_from_snapshot": restoreFromSnapshotEventHandler,
|
"restore_from_snapshot": restoreFromSnapshotEventHandler,
|
||||||
|
@ -537,6 +539,30 @@ func setPasswordEventHandler(m *nats.Msg, message *RequestMessage) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear password for the app user in the container
|
||||||
|
func clearPasswordEventHandler(m *nats.Msg, message *RequestMessage) error {
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: message.AppName,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
err := processor.ClearPassword()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ERROR password clearing problem: " + err.Error())
|
||||||
|
publish(message.AppName, "backend problem", true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
publish(message.AppName, "password deleted", false)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Application processes
|
// Application processes
|
||||||
func processesEventHandler(m *nats.Msg, message *RequestMessage) error {
|
func processesEventHandler(m *nats.Msg, message *RequestMessage) error {
|
||||||
processor := glue.Processor{
|
processor := glue.Processor{
|
||||||
|
@ -719,6 +745,26 @@ func listOrphansEventHandler(m *nats.Msg, message *RequestMessage) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save metadata for the app
|
||||||
|
func saveMetadataEventHandler(m *nats.Msg, message *RequestMessage) error {
|
||||||
|
processor := glue.Processor{
|
||||||
|
AppName: message.AppName,
|
||||||
|
DB: common.GetDBConnection(),
|
||||||
|
SnapshotProcessor: &snapshotProcessor,
|
||||||
|
DockerSock: config.DockerSocket,
|
||||||
|
BindIPHTTP: config.AppsBindIPHTTP,
|
||||||
|
BindIPSSH: config.AppsBindIPSSH,
|
||||||
|
AppsPath: config.AppsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := processor.SaveMetadata(message.Payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error while save metadata: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
getNodeEventHandler returns info about the node including performance index
|
getNodeEventHandler returns info about the node including performance index
|
||||||
*/
|
*/
|
||||||
|
|
13
main.go
13
main.go
|
@ -174,6 +174,9 @@ func main() {
|
||||||
// Set password for the app user in the container
|
// Set password for the app user in the container
|
||||||
e.PUT("/v1/apps/:name/password", setPasswordHandler)
|
e.PUT("/v1/apps/:name/password", setPasswordHandler)
|
||||||
|
|
||||||
|
// Clear password for the app user in the container
|
||||||
|
e.DELETE("/v1/apps/:name/password", clearPasswordHandler)
|
||||||
|
|
||||||
// Copies body of the request into /srv/.ssh/authorized_keys
|
// Copies body of the request into /srv/.ssh/authorized_keys
|
||||||
e.PUT("/v1/apps/:name/keys", setKeysHandler)
|
e.PUT("/v1/apps/:name/keys", setKeysHandler)
|
||||||
|
|
||||||
|
@ -183,6 +186,9 @@ func main() {
|
||||||
// Rebuilds existing app, it keeps the data but creates the container again
|
// Rebuilds existing app, it keeps the data but creates the container again
|
||||||
e.PUT("/v1/apps/:name/rebuild", rebuildAppHandler)
|
e.PUT("/v1/apps/:name/rebuild", rebuildAppHandler)
|
||||||
|
|
||||||
|
// Save metadata about app
|
||||||
|
e.POST("/v1/apps/:name/metadata", saveMetadataHandler)
|
||||||
|
|
||||||
// Adds new label
|
// Adds new label
|
||||||
e.POST("/v1/apps/:name/labels", addLabelHandler)
|
e.POST("/v1/apps/:name/labels", addLabelHandler)
|
||||||
|
|
||||||
|
@ -192,6 +198,13 @@ func main() {
|
||||||
// Delete one app
|
// Delete one app
|
||||||
e.DELETE("/v1/apps/:name", deleteAppHandler)
|
e.DELETE("/v1/apps/:name", deleteAppHandler)
|
||||||
|
|
||||||
|
// Snapshots
|
||||||
|
e.POST("/v1/apps/:name/snapshots", CreateSnapshotHandler)
|
||||||
|
e.POST("/v1/apps/:name/snapshots/restore/:snapshot", RestoreFromSnapshotHandler)
|
||||||
|
e.GET("/v1/apps/:name/snapshots", ListSnapshotsHandler)
|
||||||
|
e.GET("/v1/snapshots", ListAppsSnapshotsHandler)
|
||||||
|
e.GET("/v1/snapshots/by-label", ListSnapshotsByLabelHandler)
|
||||||
|
|
||||||
// Orphans returns directories in /srv that doesn't match any hosted application
|
// Orphans returns directories in /srv that doesn't match any hosted application
|
||||||
e.GET("/v1/orphans", getOrphansHander)
|
e.GET("/v1/orphans", getOrphansHander)
|
||||||
|
|
||||||
|
|
5
types.go
5
types.go
|
@ -56,6 +56,7 @@ type QuickServices struct {
|
||||||
PHP bool `json:"php"`
|
PHP bool `json:"php"`
|
||||||
Ruby bool `json:"ruby"`
|
Ruby bool `json:"ruby"`
|
||||||
Deno bool `json:"deno"`
|
Deno bool `json:"deno"`
|
||||||
|
Bun bool `json:"bun"`
|
||||||
Memcached bool `json:"memcached"`
|
Memcached bool `json:"memcached"`
|
||||||
Redis bool `json:"redis"`
|
Redis bool `json:"redis"`
|
||||||
}
|
}
|
||||||
|
@ -65,3 +66,7 @@ type Technology struct {
|
||||||
Name string
|
Name string
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type createSnapshotBody struct {
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue