use std::sync::Arc;
use parking_lot::Mutex;
use ratatui::{
Frame,
layout::Rect,
style::{Style, Stylize},
text::Line,
widgets::{Block, Borders, Paragraph, Wrap},
};
use crate::{
app_data::InspectData,
config::{AppColors, Keymap},
ui::{
GuiState,
draw_blocks::{DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, UP_ARROW},
gui_state::ScrollOffset,
},
};
/// Create a bordered block with a title.
fn title_block<'a>(upper_title: &'a str, lower_title: &'a str, colors: &AppColors) -> Block<'a> {
Block::default()
.borders(Borders::all())
.border_type(ratatui::widgets::BorderType::Rounded)
.border_style(Style::default().fg(colors.borders.selected))
.title(upper_title.bold().into_centered_line())
.title_bottom(lower_title.bold().into_centered_line())
}
/// Create the upper title, with container name, id, and keymap to clear
fn generate_upper_title(data: &InspectData, keymap: &Keymap) -> String {
let mut output = String::from(" inspecting: ");
let name = if data.name.starts_with("/") {
data.name.replacen('/', "", 1)
} else {
data.name.clone()
};
output.push_str(&format!("{} {} ", name, data.id.get_short()));
let mut inspect_key = keymap.inspect.0.to_string();
if let Some(x) = keymap.inspect.1 {
inspect_key.push_str(&format!(" or {x}"));
}
let mut clear_key = keymap.clear.0.to_string();
if let Some(x) = keymap.clear.1 {
clear_key.push_str(&format!(" or {x}"));
}
output.push_str(&format!(" - {clear_key} or {inspect_key} to exit"));
output.push(' ');
output
}
/// Generate the lower title, with the current scroll and the scrolling limits
fn generate_lower_title(length: usize, width: usize, offset: ScrollOffset) -> String {
let length_width = length
.to_string()
.chars()
.count()
.max(offset.y.to_string().chars().count());
let width_width = width
.to_string()
.chars()
.count()
.max(offset.x.to_string().chars().count());
let left_arrow = if offset.x == 0 { " " } else { LEFT_ARROW };
let right_arrow = if offset.x == width { " " } else { RIGHT_ARROW };
let up_arrow = if offset.y == 0 { " " } else { UP_ARROW };
let down_arrow = if offset.y == length { " " } else { DOWN_ARROW };
format!(
" {up_arrow} {:>length_width$}/{:>length_width$} {down_arrow} {left_arrow} {:>width_width$}/{:>width_width$} {right_arrow} ",
offset.y, length, offset.x, width
)
}
/// Generate the Lines, remove lines & chars based on the offset and viewport
fn gen_lines<'a>(data_as_str: &'a str, offset: &ScrollOffset, rect: &Rect) -> Vec<Line<'a>> {
// let first_line_index = offset.y.max(0);
// let first_char_index = offset.x.max(0);
let first_line_index = offset.y;
let first_char_index = offset.x;
let last_char_index = usize::from(rect.width.saturating_sub(2));
let take_lines = usize::from(rect.height);
//todo see if log scrolling does this - What?
data_as_str
.lines()
.skip(first_line_index)
.take(take_lines)
.map(|line| {
Line::from(
line.chars()
.skip(first_char_index)
.take(last_char_index)
.collect::<String>(),
)
})
.collect()
}
/// Draw the InspectContainer widget to the entire screen
pub fn draw(
f: &mut Frame,
colors: AppColors,
data: InspectData,
gui_state: &Arc<Mutex<GuiState>>,
keymap: &Keymap,
) {
let rect = f.area();
let offset = gui_state.lock().get_inspect_offset();
// +2 to account for the border
let height = data
.height
.saturating_sub(usize::from(rect.height))
.saturating_add(2);
let width = data
.width
.saturating_sub(usize::from(rect.width))
.saturating_add(2);
let upper_title = generate_upper_title(&data, keymap);
let lower_title = generate_lower_title(height, width, offset);
gui_state.lock().set_inspect_offset_max(ScrollOffset {
x: width,
y: height,
});
let paragraph = Paragraph::new(gen_lines(&data.as_string, &offset, &rect))
.block(title_block(&upper_title, &lower_title, &colors))
.gray()
.left_aligned()
.wrap(Wrap { trim: false });
f.render_widget(paragraph, rect);
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use std::{collections::HashMap, sync::LazyLock};
use crate::{
app_data::InspectData,
config::{AppColors, Keymap},
ui::draw_blocks::tests::{get_result, test_setup},
};
use bollard::models::{
ContainerConfig, ContainerInspectResponse, ContainerState, ContainerStateStatusEnum,
DriverData, EndpointSettings, HostConfig, HostConfigLogConfig, MountPoint, NetworkSettings,
RestartPolicy, RestartPolicyNameEnum,
};
use crossterm::event::KeyCode;
use insta::assert_snapshot;
use ratatui::style::Color;
static INSPECT_DATA: LazyLock<InspectData> =
LazyLock::new(|| InspectData::from(gen_container_inspect_response()));
#[test]
/// Test a inspect container with default settings, keymap, and position
fn test_draw_blocks_inspect_default_valid() {
let mut setup = test_setup(100, 50, true, true);
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&Keymap::new(),
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Test a inspect container with custom colors
fn test_draw_blocks_inspect_custom_color() {
let mut setup = test_setup(100, 50, true, true);
let mut colors = AppColors::new();
colors.borders.selected = Color::Red;
setup
.terminal
.draw(|f| {
super::draw(
f,
colors,
INSPECT_DATA.clone(),
&setup.gui_state,
&Keymap::new(),
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert custom border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Red);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Test a inspect container with custom keymap for one clear key
fn test_draw_blocks_inspect_custom_keymap_clear_one() {
let mut setup = test_setup(100, 50, true, true);
let mut keymap = Keymap::new();
keymap.clear.0 = KeyCode::Char('F');
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&keymap,
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Test a inspect container with custom keymap for both clear keys
fn test_draw_blocks_inspect_custom_keymap_clear_two() {
let mut setup = test_setup(100, 50, true, true);
let mut keymap = Keymap::new();
keymap.clear.0 = KeyCode::Char('F');
keymap.clear.1 = Some(KeyCode::Char('Z'));
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&keymap,
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Test a inspect container with custom keymap for one inspect key
fn test_draw_blocks_inspect_custom_keymap_inspect_one() {
let mut setup = test_setup(100, 50, true, true);
let mut keymap = Keymap::new();
keymap.inspect.0 = KeyCode::Char('4');
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&keymap,
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Test a inspect container with custom keymap for both inspect keys
fn test_draw_blocks_inspect_custom_keymap_inspect_two() {
let mut setup = test_setup(100, 50, true, true);
let mut keymap = Keymap::new();
keymap.inspect.0 = KeyCode::Char('4');
keymap.inspect.1 = Some(KeyCode::Char('5'));
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&keymap,
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Test a inspect container with all custom keymaps
fn test_draw_blocks_inspect_custom_keymap_all() {
let mut setup = test_setup(100, 50, true, true);
let mut keymap = Keymap::new();
keymap.clear.0 = KeyCode::Char('F');
keymap.clear.1 = Some(KeyCode::Char('Z'));
keymap.inspect.0 = KeyCode::Char('4');
keymap.inspect.1 = Some(KeyCode::Char('5'));
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&keymap,
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
// Assert border colors
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Inspect details are offset 10 in x and y axis
fn test_draw_blocks_inspect_offset() {
let mut setup = test_setup(100, 50, true, true);
// Why does one need to draw first, although it *should* be impossible to scroll before an initial drawing
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&Keymap::new(),
);
})
.unwrap();
{
let mut gui_state = setup.gui_state.lock();
for _ in 0..=9 {
gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Down);
gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Right);
}
}
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&Keymap::new(),
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
#[test]
/// Inspect details are offset to the maximum allowed
fn test_draw_blocks_inspect_offset_max() {
let mut setup = test_setup(100, 50, true, true);
// Why does one need to draw first, although it *should* be impossible to scroll before an initial drawing
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&Keymap::new(),
);
})
.unwrap();
// Lazy way of getting the max offset
{
let mut gui_state = setup.gui_state.lock();
for _ in 0..=1000 {
gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Down);
gui_state.set_inspect_offset(&crate::app_data::ScrollDirection::Right);
}
}
setup
.terminal
.draw(|f| {
super::draw(
f,
AppColors::new(),
INSPECT_DATA.clone(),
&setup.gui_state,
&Keymap::new(),
);
})
.unwrap();
assert_snapshot!(setup.terminal.backend());
for (row_index, result_row) in get_result(&setup) {
for (result_cell_index, result_cell) in result_row.iter().enumerate() {
match (row_index, result_cell_index) {
(0 | 49, _) | (_, 0 | 99) => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::LightCyan);
}
_ => {
assert_eq!(result_cell.bg, Color::Reset);
assert_eq!(result_cell.fg, Color::Gray);
}
}
}
}
}
fn gen_container_inspect_response() -> ContainerInspectResponse {
ContainerInspectResponse {
id: Some("0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7".to_owned()),
created: Some("2026-01-23T22:20:19.927967311Z".to_owned()),
path: Some("docker-entrypoint.sh".to_owned()),
args: Some(vec!["postgres".to_owned()]),
state: Some(ContainerState {
status: Some(ContainerStateStatusEnum::RUNNING),
running: Some(true),
paused: Some(false),
restarting: Some(false),
oom_killed: Some(false),
dead: Some(false),
pid: Some(782),
exit_code: Some(0),
error: Some("".to_owned()),
started_at: Some("2026-01-30T08:09:01.574885915Z".to_owned()),
finished_at: Some("2026-01-30T08:09:01.180567927Z".to_owned()),
health: None,
}),
image: Some("sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352".to_owned()),
resolv_conf_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/resolv.conf".to_owned()),
hostname_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/hostname".to_owned()),
hosts_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/hosts".to_owned()),
log_path: Some("/var/lib/docker/containers/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7/0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7-json.log".to_owned()),
name: Some("/postgres".to_owned()),
restart_count: Some(0),
driver: Some("overlay2".to_owned()),
platform: Some("linux".to_owned()),
image_manifest_descriptor: None,
mount_label: Some("".to_owned()),
process_label: Some("".to_owned()),
app_armor_profile: Some("".to_owned()),
exec_ids: None,
host_config: Some(HostConfig {
cpu_shares: Some(0),
memory: Some(1073741824),
cgroup_parent: Some("".to_owned()),
blkio_weight: Some(0),
blkio_weight_device: None,
blkio_device_read_bps: None,
blkio_device_write_bps: None,
blkio_device_read_iops: None,
blkio_device_write_iops: None,
cpu_period: Some(0),
cpu_quota: Some(0),
cpu_realtime_period: Some(0),
cpu_realtime_runtime: Some(0),
cpuset_cpus: Some("".to_owned()),
cpuset_mems: Some("".to_owned()),
devices: None,
device_cgroup_rules: None,
device_requests: None,
memory_reservation: Some(0),
memory_swap: Some(2147483648),
memory_swappiness: None,
nano_cpus: Some(0),
oom_kill_disable: Some(false),
init: None,
pids_limit: None,
ulimits: None,
cpu_count: Some(0),
cpu_percent: Some(0),
io_maximum_iops: Some(0),
io_maximum_bandwidth: Some(0),
binds: None,
container_id_file: Some("".to_owned()),
log_config: Some(HostConfigLogConfig {
typ: Some("json-file".to_owned()),
config: Some(HashMap::new()),
}),
network_mode: Some("oxker-examaple-net".to_owned()),
port_bindings: Some(HashMap::new()),
restart_policy: Some(RestartPolicy {
name: Some(RestartPolicyNameEnum::ALWAYS),
maximum_retry_count: Some(0),
}),
auto_remove: Some(false),
volume_driver: Some("".to_owned()),
volumes_from: None,
mounts: None,
console_size: Some(vec![0, 0]),
annotations: None,
cap_add: None,
cap_drop: None,
cgroupns_mode: Some(bollard::models::HostConfigCgroupnsModeEnum::HOST),
dns: Some(vec![]),
dns_options: Some(vec![]),
dns_search: Some(vec![]),
extra_hosts: Some(vec![]),
group_add: None,
ipc_mode: Some("private".to_owned()),
cgroup: Some("".to_owned()),
links: None,
oom_score_adj: Some(0),
pid_mode: Some("".to_owned()),
privileged: Some(false),
publish_all_ports: Some(false),
readonly_rootfs: Some(false),
security_opt: None,
storage_opt: None,
tmpfs: None,
uts_mode: Some("".to_owned()),
userns_mode: Some("".to_owned()),
shm_size: Some(268435456),
sysctls: None,
runtime: Some("runc".to_owned()),
isolation: Some(bollard::models::HostConfigIsolationEnum::EMPTY),
masked_paths: Some(vec![
"/proc/acpi".to_owned(),
"/proc/asound".to_owned(),
"/proc/interrupts".to_owned(),
"/proc/kcore".to_owned(),
"/proc/keys".to_owned(),
"/proc/latency_stats".to_owned(),
"/proc/sched_debug".to_owned(),
"/proc/scsi".to_owned(),
"/proc/timer_list".to_owned(),
"/proc/timer_stats".to_owned(),
"/sys/devices/virtual/powercap".to_owned(),
"/sys/firmware".to_owned(),
]),
readonly_paths: Some(vec![
"/proc/bus".to_owned(),
"/proc/fs".to_owned(),
"/proc/irq".to_owned(),
"/proc/sys".to_owned(),
"/proc/sysrq-trigger".to_owned(),
]),
}),
graph_driver: Some(DriverData {
name: "overlay2".to_owned(),
data: HashMap::from([
("LowerDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea-init/diff:/var/lib/docker/overlay2/51b93846f7ba3e00cb1ed86564e3e1d7c30df2bb1cd5a8469d54625f1e5a2eca/diff:/var/lib/docker/overlay2/c1364ead843d3af87ce286013b6301329d3089422b22b001e156e45d29b5b4dd/diff:/var/lib/docker/overlay2/0e6dc322cad77b1db3906a3a4e5e6d6b80fbffd138437e550d8849fcf4f4c1f2/diff:/var/lib/docker/overlay2/cc0f967a7471cf06e0c9ad3d474650c668a4cf0c02efe20e9c250c436f93033b/diff:/var/lib/docker/overlay2/5c59e0919969987c96a5d0e0a512a0a1a0f67ea747596af9a9c14a9566198d91/diff:/var/lib/docker/overlay2/d7709b7685c9704e1e392c515b6155517270541f6ccde426ef784403e1681fca/diff:/var/lib/docker/overlay2/c891528563fff91bffaf07416e77bcd3bdebb03e5d32ed0e3d4ee1ec5e80e880/diff:/var/lib/docker/overlay2/2b25c179a432c35cc599a082cd709c8c9a1523f8d1959f72fda21fc76e50ad00/diff:/var/lib/docker/overlay2/3b409d2f7a2455578148892302823a7f03c7c36482d08bb68fd6c1aeeec05f05/diff:/var/lib/docker/overlay2/55dbb2fab0ae8bb3bfe8183093cdd576686f7333e2b2c41e6e4178a7b6407554/diff".to_owned()),
("MergedDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/merged".to_owned()),
("WorkDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/work".to_owned()),
("ID".to_owned(), "0bdea64212f9c75eb4a1184dd406c2c79a986a7a889a23c85358456cc1bb60c7".to_owned()),
("UpperDir".to_owned(), "/var/lib/docker/overlay2/b8dae7c82251b8dadc084dbcaceec47b3d48a5ba9d055a59934a8b88d18569ea/diff".to_owned()),
]),
}),
storage: None,
size_rw: None,
size_root_fs: None,
mounts: Some(vec![MountPoint {
typ: Some("volume".to_owned()),
name: Some("93bc4e4c8d3823964b58105a99a7b3a7e02c801d5560338bdaf7589966a1b02d".to_owned()),
source: Some("/var/lib/docker/volumes/93bc4e4c8d3823964b58105a99a7b3a7e02c801d5560338bdaf7589966a1b02d/_data".to_owned()),
destination: Some("/var/lib/postgresql/data".to_owned()),
driver: Some("local".to_owned()),
mode: Some("".to_owned()),
rw: Some(true),
propagation: Some("".to_owned()),
}]),
config: Some(ContainerConfig {
hostname: Some("0bdea64212f9".to_owned()),
domainname: Some("".to_owned()),
user: Some("".to_owned()),
attach_stdin: Some(false),
attach_stdout: Some(true),
attach_stderr: Some(true),
exposed_ports: Some(vec!["5432/tcp".to_owned()]),
tty: Some(false),
open_stdin: Some(false),
stdin_once: Some(false),
env: Some(vec![
"POSTGRES_PASSWORD=never_use_this_password_in_production".to_owned(),
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_owned(),
"GOSU_VERSION=1.19".to_owned(),
"LANG=en_US.utf8".to_owned(),
"PG_MAJOR=17".to_owned(),
"PG_VERSION=17.7".to_owned(),
"PG_SHA256=ef9e343302eccd33112f1b2f0247be493cb5768313adeb558b02de8797a2e9b5".to_owned(),
"DOCKER_PG_LLVM_DEPS=llvm19-dev \t\tclang19".to_owned(),
"PGDATA=/var/lib/postgresql/data".to_owned(),
]),
cmd: Some(vec!["postgres".to_owned()]),
healthcheck: None,
args_escaped: None,
image: Some("postgres:17-alpine".to_owned()),
volumes: Some(vec!["/var/lib/postgresql/data".to_owned()]),
working_dir: Some("/".to_owned()),
entrypoint: Some(vec!["docker-entrypoint.sh".to_owned()]),
network_disabled: None,
on_build: None,
labels: Some(HashMap::from([
("com.docker.compose.oneoff".to_owned(), "False".to_owned()),
("com.docker.compose.project.config_files".to_owned(), "/workspaces/oxker/docker/docker-compose.yml".to_owned()),
("com.docker.compose.image".to_owned(), "sha256:aa3668fcbcb5ded731b7d5c27065a4edf545debb7f27bf514c709b1b4e032352".to_owned()),
("com.docker.compose.project.working_dir".to_owned(), "/workspaces/oxker/docker".to_owned()),
("com.docker.compose.service".to_owned(), "postgres".to_owned()),
("com.docker.compose.config-hash".to_owned(), "e06d69ffb3f9b69dd51b356b60c2297df57caf0da16792ccafaabffdb920e443".to_owned()),
("com.docker.compose.depends_on".to_owned(), "".to_owned()),
("com.docker.compose.container-number".to_owned(), "1".to_owned()),
("com.docker.compose.version".to_owned(), "2.40.3".to_owned()),
("com.docker.compose.project".to_owned(), "docker".to_owned()),
])),
stop_signal: Some("SIGINT".to_owned()),
stop_timeout: None,
shell: None,
}),
network_settings: Some(NetworkSettings {
sandbox_id: Some("dab64a66594dd8d06478184e2928c81acdcd9c931f643bd5ca62b7edb6345f8d".to_owned()),
sandbox_key: Some("/var/run/docker/netns/dab64a66594d".to_owned()),
ports: Some(HashMap::from([("5432/tcp".to_owned(), None)])),
networks: Some(HashMap::from([(
"oxker-examaple-net".to_owned(),
EndpointSettings {
ipam_config: None,
links: None,
mac_address: Some("a2:bd:4e:61:25:c7".to_owned()),
aliases: Some(vec!["postgres".to_owned(), "postgres".to_owned()]),
driver_opts: None,
gw_priority: Some(0),
network_id: Some("3cbeb475d81676f89a7aa205d8749ec2ad78d685e45d77b638992956f6dc569a".to_owned()),
endpoint_id: Some("31718069b2a3ea77487f3ece36b014d5d1329bc3294568e2621e5c0999071bed".to_owned()),
gateway: Some("172.19.0.1".to_owned()),
ip_address: Some("172.19.0.4".to_owned()),
ip_prefix_len: Some(16),
ipv6_gateway: Some("".to_owned()),
global_ipv6_address: Some("".to_owned()),
global_ipv6_prefix_len: Some(0),
dns_names: Some(vec!["postgres".to_owned(), "0bdea64212f9".to_owned()]),
},
)])),
}),
}
}
}