- Moved `httpconnection` import from `clients` to `core`. - Changed `tfgrid-sdk-ts` dashboard to playground. - Added ipaddr to node_local(). - Added public keyword to OpenSSLGenerateArgs. - Improved DockerEngine image and container loading. - Added utils.contains_ssh_port. - Improved error handling in DockerEngine. - Improved Docker registry handling. Co-authored-by: mariobassem12 <mariobassem12@gmail.com> Co-authored-by: omda <mahmmoud.hassanein@gmail.com>
286 lines
7.4 KiB
V
286 lines
7.4 KiB
V
module docker
|
|
|
|
import freeflowuniverse.herolib.osal { exec }
|
|
import freeflowuniverse.herolib.core.texttools
|
|
import freeflowuniverse.herolib.virt.utils
|
|
import freeflowuniverse.herolib.core
|
|
import time
|
|
|
|
// import freeflowuniverse.herolib.installers.swarm
|
|
|
|
// https://docs.docker.com/reference/
|
|
|
|
@[heap]
|
|
pub struct DockerEngine {
|
|
name string
|
|
pub mut:
|
|
sshkeys_allowed []string // all keys here have access over ssh into the machine, when ssh enabled
|
|
images []DockerImage
|
|
containers []DockerContainer
|
|
buildpath string
|
|
localonly bool
|
|
cache bool = true
|
|
push bool
|
|
platform []BuildPlatformType // used to build
|
|
registries []DockerRegistry // one or more supported DockerRegistries
|
|
prefix string
|
|
}
|
|
|
|
pub enum BuildPlatformType {
|
|
linux_arm64
|
|
linux_amd64
|
|
}
|
|
|
|
// check docker has been installed & enabled on node
|
|
pub fn (mut e DockerEngine) init() ! {
|
|
if e.buildpath == '' {
|
|
e.buildpath = '/tmp/builder'
|
|
exec(cmd: 'mkdir -p ${e.buildpath}', stdout: false)!
|
|
}
|
|
if e.platform == [] {
|
|
if core.platform()! == .ubuntu && core.cputype()! == .intel {
|
|
e.platform = [.linux_amd64]
|
|
} else if core.platform()! == .osx && core.cputype()! == .arm {
|
|
e.platform = [.linux_arm64]
|
|
} else {
|
|
return error('only implemented ubuntu on amd and osx on arm for now for docker engine.')
|
|
}
|
|
}
|
|
e.load()!
|
|
}
|
|
|
|
// reload the state from system
|
|
pub fn (mut e DockerEngine) load() ! {
|
|
e.images_load()!
|
|
e.containers_load()!
|
|
}
|
|
|
|
// load all images, they can be consulted in e.images
|
|
// see obj: DockerImage as result in e.images
|
|
pub fn (mut e DockerEngine) images_load() ! {
|
|
e.images = []DockerImage{}
|
|
mut lines := osal.execute_silent("docker images --format '{{.ID}}||{{.Repository}}||{{.Tag}}||{{.Digest}}||{{.Size}}||{{.CreatedAt}}'")!
|
|
for line in lines.split_into_lines() {
|
|
fields := line.split('||').map(utils.clear_str)
|
|
if fields.len != 6 {
|
|
panic('docker image needs to output 6 parts.\n${fields}')
|
|
}
|
|
mut image := DockerImage{
|
|
engine: &e
|
|
}
|
|
image.id = fields[0]
|
|
image.repo = fields[1]
|
|
image.tag = fields[2]
|
|
image.digest = utils.parse_digest(fields[3]) or { '' }
|
|
image.size = utils.parse_size_mb(fields[4]) or { 0 }
|
|
image.created = utils.parse_time(fields[5]) or { time.now() }
|
|
e.images << image
|
|
}
|
|
}
|
|
|
|
// load all containers, they can be consulted in e.containers
|
|
// see obj: DockerContainer as result in e.containers
|
|
pub fn (mut e DockerEngine) containers_load() ! {
|
|
e.containers = []DockerContainer{}
|
|
mut ljob := exec(
|
|
// we used || because sometimes the command has | in it and this will ruin all subsequent columns
|
|
cmd: "docker ps -a --no-trunc --format '{{.ID}}||{{.Names}}||{{.Image}}||{{.Command}}||{{.CreatedAt}}||{{.Ports}}||{{.State}}||{{.Size}}||{{.Mounts}}||{{.Networks}}||{{.Labels}}'"
|
|
ignore_error_codes: [6]
|
|
stdout: false
|
|
)!
|
|
lines := ljob.output
|
|
for line in lines.split_into_lines() {
|
|
if line.trim_space() == '' {
|
|
continue
|
|
}
|
|
fields := line.split('||').map(utils.clear_str)
|
|
if fields.len < 11 {
|
|
panic('docker ps needs to output 11 parts.\n${fields}')
|
|
}
|
|
id := fields[0]
|
|
mut container := DockerContainer{
|
|
engine: &e
|
|
image: e.image_get(id: fields[2])!
|
|
}
|
|
|
|
container.id = id
|
|
container.name = texttools.name_fix(fields[1])
|
|
container.command = fields[3]
|
|
container.created = utils.parse_time(fields[4])!
|
|
container.ports = utils.parse_ports(fields[5])!
|
|
container.status = utils.parse_container_state(fields[6])!
|
|
container.memsize = utils.parse_size_mb(fields[7])!
|
|
container.mounts = utils.parse_mounts(fields[8])!
|
|
container.networks = utils.parse_networks(fields[9])!
|
|
container.labels = utils.parse_labels(fields[10])!
|
|
container.ssh_enabled = utils.contains_ssh_port(container.ports)
|
|
// console.print_debug(container)
|
|
e.containers << container
|
|
}
|
|
}
|
|
|
|
// EXISTS, GET
|
|
|
|
@[params]
|
|
pub struct ContainerGetArgs {
|
|
pub mut:
|
|
name string
|
|
id string
|
|
image_id string
|
|
// tag string
|
|
// digest string
|
|
}
|
|
|
|
pub struct ContainerGetError {
|
|
Error
|
|
pub:
|
|
args ContainerGetArgs
|
|
notfound bool
|
|
toomany bool
|
|
}
|
|
|
|
pub fn (err ContainerGetError) msg() string {
|
|
if err.notfound {
|
|
return 'Could not find image with args:\n${err.args}'
|
|
}
|
|
if err.toomany {
|
|
return 'Found more than 1 container with args:\n${err.args}'
|
|
}
|
|
panic('unknown error for ContainerGetError')
|
|
}
|
|
|
|
pub fn (err ContainerGetError) code() int {
|
|
if err.notfound {
|
|
return 1
|
|
}
|
|
if err.toomany {
|
|
return 2
|
|
}
|
|
panic('unknown error for ContainerGetError')
|
|
}
|
|
|
|
// get containers from memory
|
|
// params:
|
|
// name string (can also be a glob e.g. use *,? and [])
|
|
// id string
|
|
// image_id string
|
|
pub fn (mut e DockerEngine) containers_get(args_ ContainerGetArgs) ![]&DockerContainer {
|
|
mut args := args_
|
|
e.containers_load()!
|
|
mut res := []&DockerContainer{}
|
|
for i, c in e.containers {
|
|
container := c
|
|
if args.name.contains('*') || args.name.contains('?') || args.name.contains('[') {
|
|
if container.name.match_glob(args.name) {
|
|
res << &e.containers[i]
|
|
continue
|
|
}
|
|
} else {
|
|
if container.name == args.name || container.id == args.id {
|
|
res << &e.containers[i]
|
|
continue
|
|
}
|
|
}
|
|
if args.image_id.len > 0 && container.image.id == args.image_id {
|
|
res << &e.containers[i]
|
|
}
|
|
}
|
|
if res.len == 0 {
|
|
return ContainerGetError{
|
|
args: args
|
|
notfound: true
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
// get container from memory, can use match_glob see https://modules.vlang.io/index.html#string.match_glob
|
|
pub fn (mut e DockerEngine) container_get(args_ ContainerGetArgs) !&DockerContainer {
|
|
mut args := args_
|
|
mut res := e.containers_get(args)!
|
|
|
|
if res.len > 1 {
|
|
return ContainerGetError{
|
|
args: args
|
|
notfound: true
|
|
}
|
|
}
|
|
return res[0]
|
|
}
|
|
|
|
pub fn (mut e DockerEngine) container_exists(args ContainerGetArgs) !bool {
|
|
e.container_get(args) or {
|
|
if err.code() == 1 {
|
|
return false
|
|
}
|
|
return err
|
|
}
|
|
return true
|
|
}
|
|
|
|
pub fn (mut e DockerEngine) container_delete(args ContainerGetArgs) ! {
|
|
mut c := e.container_get(args)!
|
|
c.delete()!
|
|
e.load()!
|
|
}
|
|
|
|
// remove one or more container
|
|
pub fn (mut e DockerEngine) containers_delete(args ContainerGetArgs) ! {
|
|
mut cs := e.containers_get(args)!
|
|
for mut c in cs {
|
|
c.delete()!
|
|
}
|
|
e.load()!
|
|
}
|
|
|
|
// import a container into an image, run docker container with it
|
|
// image_repo examples ['myimage', 'myimage:latest']
|
|
// if DockerContainerCreateArgs contains a name, container will be created and restarted
|
|
pub fn (mut e DockerEngine) container_import(path string, args DockerContainerCreateArgs) !&DockerContainer {
|
|
mut image := args.image_repo
|
|
if args.image_tag != '' {
|
|
image = image + ':${args.image_tag}'
|
|
}
|
|
|
|
exec(cmd: 'docker import ${path} ${image}', stdout: false)!
|
|
// make sure we start from loaded image
|
|
return e.container_create(args)
|
|
}
|
|
|
|
// reset all images & containers, CAREFUL!
|
|
pub fn (mut e DockerEngine) reset_all() ! {
|
|
for mut container in e.containers.clone() {
|
|
container.delete()!
|
|
}
|
|
for mut image in e.images.clone() {
|
|
image.delete(true)!
|
|
}
|
|
exec(cmd: 'docker image prune -a -f', stdout: false) or { panic(err) }
|
|
exec(cmd: 'docker builder prune -a -f', stdout: false) or { panic(err) }
|
|
osal.done_reset()!
|
|
e.load()!
|
|
}
|
|
|
|
// Get free port
|
|
pub fn (mut e DockerEngine) get_free_port() ?int {
|
|
mut used_ports := []int{}
|
|
mut range := []int{}
|
|
|
|
for c in e.containers {
|
|
for p in c.forwarded_ports {
|
|
used_ports << p.split(':')[0].int()
|
|
}
|
|
}
|
|
|
|
for i in 20000 .. 40000 {
|
|
if i !in used_ports {
|
|
range << i
|
|
}
|
|
}
|
|
// arrays.shuffle<int>(mut range, 0)
|
|
if range.len == 0 {
|
|
return none
|
|
}
|
|
return range[0]
|
|
}
|