From be2f789652e17c92e7209ba0caa4bea839f50614 Mon Sep 17 00:00:00 2001 From: Hellx2 Date: Tue, 20 Aug 2024 19:56:50 +1000 Subject: [PATCH] Add docker image support and a 'docker image inspect' interface, and update the changelog --- CHANGELOG.md | 9 +- Cargo.lock | 34 +++++- Cargo.toml | 2 + src/containers/core.rs | 223 +++++++++++++++++++++++++++++++++++++++ src/containers/images.rs | 169 ++++++++++++++++++----------- src/containers/mod.rs | 1 + src/main.rs | 1 - src/style.css | 20 ++-- 8 files changed, 382 insertions(+), 77 deletions(-) create mode 100644 src/containers/core.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 21efefc..77f8205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,4 +12,11 @@ Commit 2 - Added `src/drivers/mod.rs` and `src/drivers/objects.rs` Commit 3 -- Fixed crash on package description fetch \ No newline at end of file +- Fixed crash on package description fetch + +Commit 4 +- Added very basic support for Docker images. + +Commit 5 +- Added almost full support for Docker images. +- Created an interface between Rust and the `docker image inspect` command using Serde. diff --git a/Cargo.lock b/Cargo.lock index da584e4..800ae65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,6 +414,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "libc" version = "0.2.153" @@ -441,6 +447,8 @@ version = "0.1.0" dependencies = [ "gtk4", "itertools", + "serde", + "serde_json", ] [[package]] @@ -521,6 +529,12 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "semver" version = "1.0.22" @@ -529,24 +543,36 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.5" diff --git a/Cargo.toml b/Cargo.toml index 742bcb1..b553def 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,5 @@ edition = "2021" [dependencies] gtk = { version = "0.9.0", package = "gtk4" } itertools = "0.13.0" +serde = { version = "1.0.208", features = ["serde_derive"] } +serde_json = "1.0.125" diff --git a/src/containers/core.rs b/src/containers/core.rs new file mode 100644 index 0000000..289d4e1 --- /dev/null +++ b/src/containers/core.rs @@ -0,0 +1,223 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +/** +A struct for handling the output of the `docker image inspect` +command. +*/ +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct ImageInspectData { + pub id: String, + pub repo_tags: Vec, + pub repo_digests: Vec, + pub parent: String, + pub comment: String, + pub created: String, + pub docker_version: String, + pub author: String, + pub config: IIConfig, + pub architecture: String, + pub os: String, + pub size: u64, + pub graph_driver: IIGraphDriver, + pub rootfs: IIRootFS, + pub metadata: IIMetadata, +} + +impl Default for ImageInspectData { + fn default() -> Self { + ImageInspectData { + id: "".to_owned(), + repo_tags: vec![], + repo_digests: vec![], + parent: "".to_owned(), + comment: "".to_owned(), + created: "".to_owned(), + docker_version: "".to_owned(), + author: "".to_owned(), + config: IIConfig::default(), + architecture: "amd64".to_owned(), + os: "".to_owned(), + size: 0, + graph_driver: IIGraphDriver::default(), + rootfs: IIRootFS::default(), + metadata: IIMetadata::default(), + } + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct IIConfig { + pub hostname: String, + pub domain_name: String, + pub user: String, + pub attach_stdin: bool, + pub attach_stdout: bool, + pub attach_stderr: bool, + pub tty: bool, + pub open_stdin: bool, + pub stdin_once: bool, + pub env: Vec, + pub cmd: Vec, + pub image: String, + pub working_dir: String, + + // Unsure if these are correct, it just provided null + pub volumes: Option>, + pub entrypoint: Option, + pub on_build: Option, + pub labels: Option, +} + +impl Default for IIConfig { + fn default() -> Self { + IIConfig { + hostname: "".to_owned(), + domain_name: "".to_owned(), + user: "".to_owned(), + attach_stdin: true, + attach_stdout: true, + attach_stderr: true, + tty: false, + open_stdin: false, + stdin_once: false, + env: vec![], + cmd: vec![], + image: "".to_owned(), + working_dir: "".to_owned(), + volumes: None, + entrypoint: None, + on_build: None, + labels: None, + } + } +} + +pub type IIConfigLabels = BTreeMap; + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct IIGraphDriver { + pub data: GraphDriverData, + pub name: String, +} + +impl Default for IIGraphDriver { + fn default() -> Self { + IIGraphDriver { + data: GraphDriverData::default(), + name: "".to_owned(), + } + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct GraphDriverData { + pub merged_dir: String, + pub upper_dir: String, + pub work_dir: String, +} +impl Default for GraphDriverData { + fn default() -> GraphDriverData { + GraphDriverData { + merged_dir: "".to_owned(), + upper_dir: "".to_owned(), + work_dir: "".to_owned(), + } + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct IIRootFS { + pub r#type: String, + pub layers: Vec, +} + +impl Default for IIRootFS { + fn default() -> Self { + IIRootFS { + r#type: "".to_owned(), + layers: vec![], + } + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct IIMetadata { + pub last_tag_time: String, +} + +impl Default for IIMetadata { + fn default() -> Self { + IIMetadata { + last_tag_time: "".to_owned(), + } + } +} + +/** +Function for changing all of the keys that aren't +interpretable with the given structs into ones that +are valid and standard usable variable names. +*/ +pub fn change_keys(json: String) -> String { + let mut value = json.clone(); + + let switches = [ + ("Id", "id"), + ("RepoTags", "repo_tags"), + ("RepoDigests", "repo_digests"), + ("Parent", "parent"), + ("Comment", "comment"), + ("Created", "created"), + ("DockerVersion", "docker_version"), + ("Author", "author"), + ("Config", "config"), + // Parts of Config + ("Hostname", "hostname"), + ("Domainname", "domain_name"), + ("User", "user"), + ("AttachStdin", "attach_stdin"), + ("AttachStdout", "attach_stdout"), + ("AttachStderr", "attach_stderr"), + ("Tty", "tty"), + ("OpenStdin", "open_stdin"), + ("StdinOnce", "stdin_once"), + ("Env", "env"), + ("Cmd", "cmd"), + ("Image", "image"), + ("Volumes", "volumes"), + ("WorkingDir", "working_dir"), + ("Entrypoint", "entrypoint"), + ("OnBuild", "on_build"), + ("Labels", "labels"), + // Back to normal parts + ("Architecture", "architecture"), + ("Os", "os"), + ("Size", "size"), + ("GraphDriver", "graph_driver"), + // Parts of GraphDriver + ("Data", "data"), + // Parts of Data + ("MergedDir", "merged_dir"), + ("UpperDir", "upper_dir"), + ("WorkDir", "work_dir"), + // Back to GraphDriver + ("Name", "name"), + // Back to normal parts + ("RootFS", "rootfs"), + // Parts of RootFS + ("Type", "type"), + ("Layers", "layers"), + // Back to normal parts + ("Metadata", "metadata"), + // Parts of Metadata + ("LastTagTime", "last_tag_time"), + ]; + + for i in switches { + value = value.replacen(i.0, i.1, 1); + } + + value +} diff --git a/src/containers/images.rs b/src/containers/images.rs index a8e29ce..bcc62af 100644 --- a/src/containers/images.rs +++ b/src/containers/images.rs @@ -1,4 +1,12 @@ -use gtk::{prelude::BoxExt, Label}; +use gtk::{ + prelude::{BoxExt, ButtonExt, Cast, GtkWindowExt, ListModelExtManual, WidgetExt}, + Button, Label, Window, +}; + +use crate::{ + containers::{self, core::ImageInspectData}, + pkexec, +}; use super::Image; @@ -11,82 +19,121 @@ pub fn images() -> gtk::Box { Using unwrap here is allowed since this will always return a Some when the second param is false */ - let images = crate::pkexec("docker images".to_owned(), false) + let mut images = crate::pkexec("docker images".to_owned(), false) .unwrap() .lines() .skip(1) .map(|x| x.parse::().unwrap()) .collect::>(); + images.push(Image { + repository: "Test".to_owned(), + tag: "something".to_owned(), + id: "tag".to_owned(), + created: "some date".to_owned(), + size: "100MB".to_owned(), + }); + for image in images { let image_box = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .css_classes(["image-box"]) .build(); - let repo_box = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) + image_box.append(&gen_box("Repository", &image.repository)); + image_box.append(&gen_box("Tag", &image.tag)); + image_box.append(&gen_box("Image ID", &image.id)); + image_box.append(&gen_box("Created At", &image.created)); + image_box.append(&gen_box("Size", &image.size)); + + let image_button = Button::builder() + .child(&image_box) + .css_classes(["image-button"]) .build(); - repo_box.append( - &Label::builder() - .label("Repository: ") - .css_classes(["repo-label"]) - .build(), - ); - repo_box.append(&Label::builder().label(&image.repository).build()); - let tag_box = gtk::Box::builder() - .orientation(gtk::Orientation::Horizontal) - .build(); - tag_box.append( - &Label::builder() - .label("Tag: ") - .css_classes(["tag-label"]) - .build(), - ); - tag_box.append(&Label::builder().label(&image.tag).build()); + image_button.connect_clicked(|x| { + show_image( + x.child() + .expect("Failed to get image_button child!") + .downcast::() + .expect("Failed to cast image_button child to box.") + .observe_children() + .iter::() + .nth(2) + .expect("Failed to get third child.") + .expect("Failed to get third child.") + .downcast::() + .expect("Failed to cast child to box.") + .last_child() + .expect("Failed to get first child.") + .downcast::