This commit is contained in:
parent
2a34177ab8
commit
a213fac34a
10 changed files with 181 additions and 9 deletions
4
Makefile
4
Makefile
|
@ -2,6 +2,8 @@
|
|||
test:
|
||||
go test -v apps/*.go
|
||||
go test -v apps/drivers/*.go
|
||||
go test -v detector/*.go
|
||||
go test -v docker/*.go
|
||||
|
||||
build:
|
||||
#podman run --rm --privileged -ti -v ${shell pwd}:/srv docker.io/library/golang:1.14-stretch /bin/sh -c "cd /srv && go build"
|
||||
|
@ -18,7 +20,7 @@ minio:
|
|||
-p 9001:9001 \
|
||||
-e MINIO_ROOT_USER=test \
|
||||
-e MINIO_ROOT_PASSWORD=testtest \
|
||||
minio/minio server /data --console-address ":9001"
|
||||
docker.io/minio/minio:latest server /data --console-address ":9001"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
|
10
README.md
10
README.md
|
@ -4,3 +4,13 @@
|
|||
|
||||
Node API is an microservice that runs on node servers. It provides interface between
|
||||
Docker and the admin site.
|
||||
|
||||
|
||||
## Test
|
||||
|
||||
On Fedora run podman API:
|
||||
|
||||
Root: sudo systemctl enable --now podman.socket
|
||||
Rootless: podman system service -t 0 --log-level=debug
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package apps
|
|||
import (
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/rosti-cz/node-api/detector"
|
||||
)
|
||||
|
||||
// AppsProcessor encapsulates functions for apps manipulation
|
||||
|
@ -109,13 +110,14 @@ func (a *AppsProcessor) Update(name string, SSHPort int, HTTPPort int, image str
|
|||
}
|
||||
|
||||
// UpdateResources updates various metrics saved in the database
|
||||
func (a *AppsProcessor) UpdateResources(name string, state string, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int) error {
|
||||
func (a *AppsProcessor) UpdateResources(name string, state string, CPUUsage float64, memory int, diskUsageBytes int, diskUsageInodes int, flags detector.Flags) error {
|
||||
err := a.DB.Model(&App{}).Where("name = ?", name).Updates(App{
|
||||
State: state,
|
||||
CPUUsage: CPUUsage,
|
||||
MemoryUsage: memory,
|
||||
DiskUsageBytes: diskUsageBytes,
|
||||
DiskUsageInodes: diskUsageInodes,
|
||||
Flags: flags,
|
||||
}).Error
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
// This is line from GORM documentation that imports database dialect
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/rosti-cz/node-api/detector"
|
||||
)
|
||||
|
||||
// ValidationError is error that holds multiple validation error messages
|
||||
|
@ -31,11 +32,12 @@ type Label struct {
|
|||
|
||||
// AppState contains info about runnint application, it's not saved in the database
|
||||
type AppState struct {
|
||||
State string `json:"state"`
|
||||
CPUUsage float64 `json:"cpu_usage"` // in percents
|
||||
MemoryUsage int `json:"memory_usage"` // in MB
|
||||
DiskUsageBytes int `json:"disk_usage_bytes"`
|
||||
DiskUsageInodes int `json:"disk_usage_inodes"`
|
||||
State string `json:"state"`
|
||||
CPUUsage float64 `json:"cpu_usage"` // in percents
|
||||
MemoryUsage int `json:"memory_usage"` // in MB
|
||||
DiskUsageBytes int `json:"disk_usage_bytes"`
|
||||
DiskUsageInodes int `json:"disk_usage_inodes"`
|
||||
Flags detector.Flags `json:"flags"`
|
||||
}
|
||||
|
||||
// Apps is list of applications
|
||||
|
@ -83,6 +85,8 @@ type App struct {
|
|||
DiskUsageBytes int `json:"disk_usage_bytes"`
|
||||
// Disk usage in inodes
|
||||
DiskUsageInodes int `json:"disk_usage_inodes"`
|
||||
// Flags from detector of problems in the container
|
||||
Flags detector.Flags `json:"flags"`
|
||||
|
||||
// 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
|
||||
|
|
37
detector/main.go
Normal file
37
detector/main.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package detector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Flags is list of strings describing problems found among the processes
|
||||
type Flags []string
|
||||
|
||||
// Check goes over patterns and tries to flag given list of processes with flags.
|
||||
func Check(processes []string) (Flags, error) {
|
||||
flags := Flags{}
|
||||
tmpFlags := make(map[string]bool)
|
||||
|
||||
for _, process := range processes {
|
||||
for flag, patternSet := range patterns {
|
||||
for _, pattern := range patternSet {
|
||||
matched, err := regexp.MatchString(".*"+pattern+".*", process)
|
||||
if err != nil {
|
||||
return flags, err
|
||||
}
|
||||
if matched {
|
||||
tmpFlags[flag] = true
|
||||
break
|
||||
}
|
||||
fmt.Println(process, pattern, flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for flag, _ := range tmpFlags {
|
||||
flags = append(flags, flag)
|
||||
}
|
||||
|
||||
return flags, nil
|
||||
}
|
35
detector/main_test.go
Normal file
35
detector/main_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package detector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
flags, err := Check([]string{
|
||||
"sleep",
|
||||
"apache2",
|
||||
"miner",
|
||||
"verus-solve",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, flags, "miner")
|
||||
|
||||
flags, err = Check([]string{
|
||||
"sleep",
|
||||
"apache2",
|
||||
"hellminer",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, flags, "miner")
|
||||
|
||||
flags, err = Check([]string{
|
||||
"sleep",
|
||||
"apache2",
|
||||
"miner", // This is not among patterns map
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.NotContains(t, flags, "miner")
|
||||
|
||||
}
|
8
detector/patterns.go
Normal file
8
detector/patterns.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package detector
|
||||
|
||||
var patterns map[string][]string = map[string][]string{
|
||||
"miner": {
|
||||
`verus\-solve`,
|
||||
`hellminer`,
|
||||
},
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
"github.com/docker/docker/api/types/network"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/rosti-cz/node-api/detector"
|
||||
)
|
||||
|
||||
// Stats delay in seconds
|
||||
|
@ -27,7 +29,7 @@ const dockerTimeout = 10
|
|||
|
||||
// DOCKER_SOCK tells where to connect to docker, it will be always local sock
|
||||
const dockerSock = "/var/run/docker.sock"
|
||||
const podmanSock = "/run/podman/podman.sock"
|
||||
const podmanSock = "/run/user/1000/podman/podman.sock"
|
||||
|
||||
// DOCKER_API_VERSION set API version of Docker, 1.40 belongs to Docker 19.03.11
|
||||
const dockerAPIVersion = "1.38"
|
||||
|
@ -47,7 +49,10 @@ func (d *Driver) getClient() (*dockerClient.Client, error) {
|
|||
}
|
||||
|
||||
cli, err := dockerClient.NewClient("unix://"+connectTo, dockerAPIVersion, nil, nil)
|
||||
return cli, err
|
||||
if err != nil {
|
||||
return cli, fmt.Errorf("get docker client error: %v", err)
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
// ConnectionStatus checks connection to the Docker daemon
|
||||
|
@ -448,3 +453,43 @@ func (d *Driver) Exec(name string, cmd []string, stdin string, env []string, att
|
|||
|
||||
return &stdouterr, err
|
||||
}
|
||||
|
||||
// GetProcesses return list of processes running under this container
|
||||
func (d *Driver) GetProcesses(name string) ([]string, error) {
|
||||
processes := []string{}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
cli, err := d.getClient()
|
||||
if err != nil {
|
||||
return processes, err
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
processList, err := cli.ContainerTop(ctx, name, []string{"-eo", "args"})
|
||||
if err != nil {
|
||||
return processes, fmt.Errorf("docker container top call error: %v", err)
|
||||
}
|
||||
|
||||
for _, process := range processList.Processes {
|
||||
if len(process) > 0 {
|
||||
processes = append(processes, process[0])
|
||||
}
|
||||
}
|
||||
|
||||
return processes, nil
|
||||
}
|
||||
|
||||
// GetFlags returns list of flags with problems found in the container, mainly used to detect miners or viruses
|
||||
func (d *Driver) GetFlags(name string) (detector.Flags, error) {
|
||||
processes, err := d.GetProcesses(name)
|
||||
if err != nil {
|
||||
return detector.Flags{}, err
|
||||
}
|
||||
flags, err := detector.Check(processes)
|
||||
if err != nil {
|
||||
return flags, err
|
||||
}
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
|
|
28
docker/docker_test.go
Normal file
28
docker/docker_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetProcesses(t *testing.T) {
|
||||
driver := Driver{
|
||||
BindIPHTTP: "127.0.0.1",
|
||||
BindIPSSH: "127.0.0.1",
|
||||
}
|
||||
|
||||
driver.Remove("test")
|
||||
|
||||
_, err := driver.Create("test", "docker.io/library/busybox", "/tmp", 8990, 8922, 1, 128, []string{"sleep", "3600"})
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = driver.Start("test")
|
||||
assert.Nil(t, err)
|
||||
|
||||
processes, err := driver.GetProcesses("test")
|
||||
assert.Nil(t, err)
|
||||
assert.Contains(t, processes, "sleep 3600")
|
||||
|
||||
driver.Remove("test")
|
||||
}
|
1
stats.go
1
stats.go
|
@ -35,6 +35,7 @@ func updateUsage(name string) error {
|
|||
state.MemoryUsage,
|
||||
state.DiskUsageBytes,
|
||||
state.DiskUsageInodes,
|
||||
state.Flags,
|
||||
)
|
||||
|
||||
return err
|
||||
|
|
Loading…
Reference in a new issue