Files
komodo/bin/periphery/src/docker/container.rs
T
2025-12-10 21:39:56 -08:00

644 lines
20 KiB
Rust

use std::collections::HashMap;
use anyhow::Context;
use bollard::query_parameters::{
InspectContainerOptions, ListContainersOptions,
};
use komodo_client::entities::docker::{container::*, *};
use crate::state::container_stats;
use super::{
DockerClient, convert_health_config, convert_mount,
convert_mount_point_type, convert_resources_ulimits,
};
impl DockerClient {
pub async fn list_containers(
&self,
) -> anyhow::Result<Vec<ContainerListItem>> {
let containers = self
.docker
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
.await?;
let stats = container_stats().load();
let mut containers = containers
.into_iter()
.flat_map(|container| {
let name = container
.names
.context("no names on container")?
.pop()
.context("no names on container (empty vec)")?
.replace('/', "");
let stats = stats.get(&name).cloned();
anyhow::Ok(ContainerListItem {
server_id: None,
name,
stats,
id: container.id,
image: container.image,
image_id: container.image_id,
created: container.created,
size_rw: container.size_rw,
size_root_fs: container.size_root_fs,
state: convert_summary_container_state(
container.state.context("no container state")?,
),
status: container.status,
network_mode: container
.host_config
.and_then(|config| config.network_mode),
networks: container
.network_settings
.and_then(|settings| {
settings.networks.map(|networks| {
let mut keys =
networks.into_keys().collect::<Vec<_>>();
keys.sort();
keys
})
})
.unwrap_or_default(),
ports: container
.ports
.map(|ports| {
ports.into_iter().map(convert_port).collect()
})
.unwrap_or_default(),
volumes: container
.mounts
.map(|settings| {
settings
.into_iter()
.filter_map(|mount| mount.name)
.collect()
})
.unwrap_or_default(),
labels: container.labels.unwrap_or_default(),
})
})
.collect::<Vec<_>>();
let container_id_to_network = containers
.iter()
.filter_map(|c| Some((c.id.clone()?, c.network_mode.clone()?)))
.collect::<HashMap<_, _>>();
// Fix containers which use `container:container_id` network_mode,
// by replacing with the referenced network mode.
containers.iter_mut().for_each(|container| {
let Some(network_name) = &container.network_mode else {
return;
};
let Some(container_id) =
network_name.strip_prefix("container:")
else {
return;
};
container.network_mode =
container_id_to_network.get(container_id).cloned();
});
containers.sort_by(|a, b| {
a.state.cmp(&b.state).then_with(|| a.name.cmp(&b.name))
});
Ok(containers)
}
pub async fn inspect_container(
&self,
container_name: &str,
) -> anyhow::Result<Container> {
let container = self
.docker
.inspect_container(
container_name,
InspectContainerOptions { size: true }.into(),
)
.await?;
Ok(Container {
id: container.id,
created: container.created,
path: container.path,
args: container.args.unwrap_or_default(),
state: container.state.map(|state| ContainerState {
status: state
.status
.map(convert_container_state_status)
.unwrap_or_default(),
running: state.running,
paused: state.paused,
restarting: state.restarting,
oom_killed: state.oom_killed,
dead: state.dead,
pid: state.pid,
exit_code: state.exit_code,
error: state.error,
started_at: state.started_at,
finished_at: state.finished_at,
health: state.health.map(|health| ContainerHealth {
status: health
.status
.map(convert_health_status)
.unwrap_or_default(),
failing_streak: health.failing_streak,
log: health
.log
.map(|log| {
log
.into_iter()
.map(convert_health_check_result)
.collect()
})
.unwrap_or_default(),
}),
}),
image: container.image,
resolv_conf_path: container.resolv_conf_path,
hostname_path: container.hostname_path,
hosts_path: container.hosts_path,
log_path: container.log_path,
name: container.name,
restart_count: container.restart_count,
driver: container.driver,
platform: container.platform,
mount_label: container.mount_label,
process_label: container.process_label,
app_armor_profile: container.app_armor_profile,
exec_ids: container.exec_ids.unwrap_or_default(),
host_config: container.host_config.map(|config| HostConfig {
cpu_shares: config.cpu_shares,
memory: config.memory,
cgroup_parent: config.cgroup_parent,
blkio_weight: config.blkio_weight,
blkio_weight_device: config
.blkio_weight_device
.unwrap_or_default()
.into_iter()
.map(|device| ResourcesBlkioWeightDevice {
path: device.path,
weight: device.weight,
})
.collect(),
blkio_device_read_bps: config
.blkio_device_read_bps
.unwrap_or_default()
.into_iter()
.map(|bp| ThrottleDevice {
path: bp.path,
rate: bp.rate,
})
.collect(),
blkio_device_write_bps: config
.blkio_device_write_bps
.unwrap_or_default()
.into_iter()
.map(|bp| ThrottleDevice {
path: bp.path,
rate: bp.rate,
})
.collect(),
blkio_device_read_iops: config
.blkio_device_read_iops
.unwrap_or_default()
.into_iter()
.map(|iops| ThrottleDevice {
path: iops.path,
rate: iops.rate,
})
.collect(),
blkio_device_write_iops: config
.blkio_device_write_iops
.unwrap_or_default()
.into_iter()
.map(|iops| ThrottleDevice {
path: iops.path,
rate: iops.rate,
})
.collect(),
cpu_period: config.cpu_period,
cpu_quota: config.cpu_quota,
cpu_realtime_period: config.cpu_realtime_period,
cpu_realtime_runtime: config.cpu_realtime_runtime,
cpuset_cpus: config.cpuset_cpus,
cpuset_mems: config.cpuset_mems,
devices: config
.devices
.unwrap_or_default()
.into_iter()
.map(|device| DeviceMapping {
path_on_host: device.path_on_host,
path_in_container: device.path_in_container,
cgroup_permissions: device.cgroup_permissions,
})
.collect(),
device_cgroup_rules: config
.device_cgroup_rules
.unwrap_or_default(),
device_requests: config
.device_requests
.unwrap_or_default()
.into_iter()
.map(|request| DeviceRequest {
driver: request.driver,
count: request.count,
device_ids: request.device_ids.unwrap_or_default(),
capabilities: request.capabilities.unwrap_or_default(),
options: request.options.unwrap_or_default(),
})
.collect(),
kernel_memory_tcp: config.kernel_memory_tcp,
memory_reservation: config.memory_reservation,
memory_swap: config.memory_swap,
memory_swappiness: config.memory_swappiness,
nano_cpus: config.nano_cpus,
oom_kill_disable: config.oom_kill_disable,
init: config.init,
pids_limit: config.pids_limit,
ulimits: config
.ulimits
.unwrap_or_default()
.into_iter()
.map(convert_resources_ulimits)
.collect(),
cpu_count: config.cpu_count,
cpu_percent: config.cpu_percent,
io_maximum_iops: config.io_maximum_iops,
io_maximum_bandwidth: config.io_maximum_bandwidth,
binds: config.binds.unwrap_or_default(),
container_id_file: config.container_id_file,
log_config: config.log_config.map(|config| {
HostConfigLogConfig {
typ: config.typ,
config: config.config.unwrap_or_default(),
}
}),
network_mode: config.network_mode,
port_bindings: config
.port_bindings
.unwrap_or_default()
.into_iter()
.map(|(k, v)| {
(
k,
v.unwrap_or_default()
.into_iter()
.map(|v| PortBinding {
host_ip: v.host_ip,
host_port: v.host_port,
})
.collect(),
)
})
.collect(),
restart_policy: config.restart_policy.map(|policy| {
RestartPolicy {
name: policy
.name
.map(convert_restart_policy)
.unwrap_or_default(),
maximum_retry_count: policy.maximum_retry_count,
}
}),
auto_remove: config.auto_remove,
volume_driver: config.volume_driver,
volumes_from: config.volumes_from.unwrap_or_default(),
mounts: config
.mounts
.unwrap_or_default()
.into_iter()
.map(convert_mount)
.collect(),
console_size: config
.console_size
.map(|v| v.into_iter().map(|s| s as i32).collect())
.unwrap_or_default(),
annotations: config.annotations.unwrap_or_default(),
cap_add: config.cap_add.unwrap_or_default(),
cap_drop: config.cap_drop.unwrap_or_default(),
cgroupns_mode: config
.cgroupns_mode
.map(convert_cgroupns_mode),
dns: config.dns.unwrap_or_default(),
dns_options: config.dns_options.unwrap_or_default(),
dns_search: config.dns_search.unwrap_or_default(),
extra_hosts: config.extra_hosts.unwrap_or_default(),
group_add: config.group_add.unwrap_or_default(),
ipc_mode: config.ipc_mode,
cgroup: config.cgroup,
links: config.links.unwrap_or_default(),
oom_score_adj: config.oom_score_adj,
pid_mode: config.pid_mode,
privileged: config.privileged,
publish_all_ports: config.publish_all_ports,
readonly_rootfs: config.readonly_rootfs,
security_opt: config.security_opt.unwrap_or_default(),
storage_opt: config.storage_opt.unwrap_or_default(),
tmpfs: config.tmpfs.unwrap_or_default(),
uts_mode: config.uts_mode,
userns_mode: config.userns_mode,
shm_size: config.shm_size,
sysctls: config.sysctls.unwrap_or_default(),
runtime: config.runtime,
isolation: config
.isolation
.map(convert_isolation_mode)
.unwrap_or_default(),
masked_paths: config.masked_paths.unwrap_or_default(),
readonly_paths: config.readonly_paths.unwrap_or_default(),
}),
graph_driver: container.graph_driver.map(|driver| {
GraphDriverData {
name: driver.name,
data: driver.data,
}
}),
size_rw: container.size_rw,
size_root_fs: container.size_root_fs,
mounts: container
.mounts
.unwrap_or_default()
.into_iter()
.map(|mount| MountPoint {
typ: mount
.typ
.map(convert_mount_point_type)
.unwrap_or_default(),
name: mount.name,
source: mount.source,
destination: mount.destination,
driver: mount.driver,
mode: mount.mode,
rw: mount.rw,
propagation: mount.propagation,
})
.collect(),
config: container.config.map(|config| ContainerConfig {
hostname: config.hostname,
domainname: config.domainname,
user: config.user,
attach_stdin: config.attach_stdin,
attach_stdout: config.attach_stdout,
attach_stderr: config.attach_stderr,
exposed_ports: config
.exposed_ports
.unwrap_or_default()
.into_keys()
.map(|k| (k, Default::default()))
.collect(),
tty: config.tty,
open_stdin: config.open_stdin,
stdin_once: config.stdin_once,
env: config.env.unwrap_or_default(),
cmd: config.cmd.unwrap_or_default(),
healthcheck: config.healthcheck.map(convert_health_config),
args_escaped: config.args_escaped,
image: config.image,
volumes: config
.volumes
.unwrap_or_default()
.into_keys()
.map(|k| (k, Default::default()))
.collect(),
working_dir: config.working_dir,
entrypoint: config.entrypoint.unwrap_or_default(),
network_disabled: config.network_disabled,
mac_address: config.mac_address,
on_build: config.on_build.unwrap_or_default(),
labels: config.labels.unwrap_or_default(),
stop_signal: config.stop_signal,
stop_timeout: config.stop_timeout,
shell: config.shell.unwrap_or_default(),
}),
network_settings: container.network_settings.map(|settings| {
NetworkSettings {
bridge: settings.bridge,
sandbox_id: settings.sandbox_id,
ports: settings
.ports
.unwrap_or_default()
.into_iter()
.map(|(k, v)| {
(
k,
v.unwrap_or_default()
.into_iter()
.map(|v| PortBinding {
host_ip: v.host_ip,
host_port: v.host_port,
})
.collect(),
)
})
.collect(),
sandbox_key: settings.sandbox_key,
networks: settings
.networks
.unwrap_or_default()
.into_iter()
.map(|(k, v)| {
(
k,
EndpointSettings {
ipam_config: v.ipam_config.map(|ipam| {
EndpointIpamConfig {
ipv4_address: ipam.ipv4_address,
ipv6_address: ipam.ipv6_address,
link_local_ips: ipam
.link_local_ips
.unwrap_or_default(),
}
}),
links: v.links.unwrap_or_default(),
mac_address: v.mac_address,
aliases: v.aliases.unwrap_or_default(),
network_id: v.network_id,
endpoint_id: v.endpoint_id,
gateway: v.gateway,
ip_address: v.ip_address,
ip_prefix_len: v.ip_prefix_len,
ipv6_gateway: v.ipv6_gateway,
global_ipv6_address: v.global_ipv6_address,
global_ipv6_prefix_len: v.global_ipv6_prefix_len,
driver_opts: v.driver_opts.unwrap_or_default(),
dns_names: v.dns_names.unwrap_or_default(),
},
)
})
.collect(),
}
}),
})
}
}
fn convert_summary_container_state(
state: bollard::secret::ContainerSummaryStateEnum,
) -> ContainerStateStatusEnum {
match state {
bollard::secret::ContainerSummaryStateEnum::EMPTY => {
ContainerStateStatusEnum::Empty
}
bollard::secret::ContainerSummaryStateEnum::CREATED => {
ContainerStateStatusEnum::Created
}
bollard::secret::ContainerSummaryStateEnum::RUNNING => {
ContainerStateStatusEnum::Running
}
bollard::secret::ContainerSummaryStateEnum::PAUSED => {
ContainerStateStatusEnum::Paused
}
bollard::secret::ContainerSummaryStateEnum::RESTARTING => {
ContainerStateStatusEnum::Restarting
}
bollard::secret::ContainerSummaryStateEnum::EXITED => {
ContainerStateStatusEnum::Exited
}
bollard::secret::ContainerSummaryStateEnum::REMOVING => {
ContainerStateStatusEnum::Removing
}
bollard::secret::ContainerSummaryStateEnum::DEAD => {
ContainerStateStatusEnum::Dead
}
}
}
fn convert_container_state_status(
state: bollard::secret::ContainerStateStatusEnum,
) -> ContainerStateStatusEnum {
match state {
bollard::secret::ContainerStateStatusEnum::EMPTY => {
ContainerStateStatusEnum::Empty
}
bollard::secret::ContainerStateStatusEnum::CREATED => {
ContainerStateStatusEnum::Created
}
bollard::secret::ContainerStateStatusEnum::RUNNING => {
ContainerStateStatusEnum::Running
}
bollard::secret::ContainerStateStatusEnum::PAUSED => {
ContainerStateStatusEnum::Paused
}
bollard::secret::ContainerStateStatusEnum::RESTARTING => {
ContainerStateStatusEnum::Restarting
}
bollard::secret::ContainerStateStatusEnum::EXITED => {
ContainerStateStatusEnum::Exited
}
bollard::secret::ContainerStateStatusEnum::REMOVING => {
ContainerStateStatusEnum::Removing
}
bollard::secret::ContainerStateStatusEnum::DEAD => {
ContainerStateStatusEnum::Dead
}
}
}
fn convert_port_type(
typ: bollard::secret::PortTypeEnum,
) -> PortTypeEnum {
match typ {
bollard::secret::PortTypeEnum::EMPTY => PortTypeEnum::EMPTY,
bollard::secret::PortTypeEnum::TCP => PortTypeEnum::TCP,
bollard::secret::PortTypeEnum::UDP => PortTypeEnum::UDP,
bollard::secret::PortTypeEnum::SCTP => PortTypeEnum::SCTP,
}
}
fn convert_port(port: bollard::secret::Port) -> Port {
Port {
ip: port.ip,
private_port: port.private_port,
public_port: port.public_port,
typ: port.typ.map(convert_port_type).unwrap_or_default(),
}
}
fn convert_health_status(
status: bollard::secret::HealthStatusEnum,
) -> HealthStatusEnum {
match status {
bollard::secret::HealthStatusEnum::EMPTY => {
HealthStatusEnum::Empty
}
bollard::secret::HealthStatusEnum::NONE => HealthStatusEnum::None,
bollard::secret::HealthStatusEnum::STARTING => {
HealthStatusEnum::Starting
}
bollard::secret::HealthStatusEnum::HEALTHY => {
HealthStatusEnum::Healthy
}
bollard::secret::HealthStatusEnum::UNHEALTHY => {
HealthStatusEnum::Unhealthy
}
}
}
fn convert_health_check_result(
check: bollard::secret::HealthcheckResult,
) -> HealthcheckResult {
HealthcheckResult {
start: check.start,
end: check.end,
exit_code: check.exit_code,
output: check.output,
}
}
fn convert_restart_policy(
policy: bollard::secret::RestartPolicyNameEnum,
) -> RestartPolicyNameEnum {
match policy {
bollard::secret::RestartPolicyNameEnum::EMPTY => {
RestartPolicyNameEnum::Empty
}
bollard::secret::RestartPolicyNameEnum::NO => {
RestartPolicyNameEnum::No
}
bollard::secret::RestartPolicyNameEnum::ALWAYS => {
RestartPolicyNameEnum::Always
}
bollard::secret::RestartPolicyNameEnum::UNLESS_STOPPED => {
RestartPolicyNameEnum::UnlessStopped
}
bollard::secret::RestartPolicyNameEnum::ON_FAILURE => {
RestartPolicyNameEnum::OnFailure
}
}
}
fn convert_cgroupns_mode(
mode: bollard::secret::HostConfigCgroupnsModeEnum,
) -> HostConfigCgroupnsModeEnum {
match mode {
bollard::secret::HostConfigCgroupnsModeEnum::EMPTY => {
HostConfigCgroupnsModeEnum::Empty
}
bollard::secret::HostConfigCgroupnsModeEnum::PRIVATE => {
HostConfigCgroupnsModeEnum::Private
}
bollard::secret::HostConfigCgroupnsModeEnum::HOST => {
HostConfigCgroupnsModeEnum::Host
}
}
}
fn convert_isolation_mode(
isolation: bollard::secret::HostConfigIsolationEnum,
) -> HostConfigIsolationEnum {
match isolation {
bollard::secret::HostConfigIsolationEnum::EMPTY => {
HostConfigIsolationEnum::Empty
}
bollard::secret::HostConfigIsolationEnum::DEFAULT => {
HostConfigIsolationEnum::Default
}
bollard::secret::HostConfigIsolationEnum::PROCESS => {
HostConfigIsolationEnum::Process
}
bollard::secret::HostConfigIsolationEnum::HYPERV => {
HostConfigIsolationEnum::Hyperv
}
}
}