Add docker image support and a 'docker image inspect' interface, and update the changelog

This commit is contained in:
Hellx2 2024-08-20 19:56:50 +10:00
parent a9826a410d
commit be2f789652
8 changed files with 382 additions and 77 deletions

View file

@ -12,4 +12,11 @@ Commit 2
- Added `src/drivers/mod.rs` and `src/drivers/objects.rs`
Commit 3
- Fixed crash on package description fetch
- 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.

34
Cargo.lock generated
View file

@ -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"

View file

@ -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"

223
src/containers/core.rs Normal file
View file

@ -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<String>,
pub repo_digests: Vec<String>,
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<String>,
pub cmd: Vec<String>,
pub image: String,
pub working_dir: String,
// Unsure if these are correct, it just provided null
pub volumes: Option<Vec<String>>,
pub entrypoint: Option<String>,
pub on_build: Option<String>,
pub labels: Option<IIConfigLabels>,
}
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<String, String>;
#[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<String>,
}
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
}

View file

@ -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::<Image>().unwrap())
.collect::<Vec<Image>>();
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::<gtk::Box>()
.expect("Failed to cast image_button child to box.")
.observe_children()
.iter::<gtk::glib::Object>()
.nth(2)
.expect("Failed to get third child.")
.expect("Failed to get third child.")
.downcast::<gtk::Box>()
.expect("Failed to cast child to box.")
.last_child()
.expect("Failed to get first child.")
.downcast::<Label>()
.expect("Failed to cast to label.")
.label()
.as_str(),
)
});
let id_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.build();
id_box.append(
&Label::builder()
.label("Image ID: ")
.css_classes(["id-label"])
.build(),
);
id_box.append(&Label::builder().label(&image.id).build());
let created_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.build();
created_box.append(
&Label::builder()
.label("Created at: ")
.css_classes(["created-label"])
.build(),
);
created_box.append(&Label::builder().label(&image.created).build());
let size_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.build();
size_box.append(
&Label::builder()
.label("Size: ")
.css_classes(["size-label"])
.build(),
);
size_box.append(&Label::builder().label(&image.size).build());
image_box.append(&repo_box);
image_box.append(&tag_box);
image_box.append(&id_box);
image_box.append(&created_box);
image_box.append(&size_box);
images_box.append(&image_box);
images_box.append(&image_button);
}
images_box
}
fn show_image(image_id: &str) {
let image_details: Vec<ImageInspectData> =
serde_json::from_str::<Vec<ImageInspectData>>(&containers::core::change_keys(
pkexec("docker image inspect ".to_owned() + image_id, false)
.expect("This error should never occur"),
))
.expect("Failed to get data");
let image_details = image_details[0].clone();
// TODO: Display the data.
let image_window = Window::builder()
.title(format!("Image ID {image_id}").as_str())
.default_width(600)
.default_height(400)
.build();
let main_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.css_classes(["image-showbox"])
.build();
main_box.append(&gen_box("Author", &image_details.author));
main_box.append(&gen_box("Comment", &image_details.comment));
main_box.append(&gen_box("Id", &image_details.id));
main_box.append(&gen_box("Parent", &image_details.parent));
main_box.append(&gen_box("Created At", &image_details.created));
main_box.append(&gen_box("Architecture", &image_details.architecture));
main_box.append(&gen_box("OS", &image_details.os));
main_box.append(&gen_box(
"Size",
&((image_details.size / 1_000_000).to_string() + "MB"),
));
image_window.set_child(Some(&main_box));
image_window.present();
}
pub fn gen_box(name: &str, content: &str) -> gtk::Box {
let return_box = gtk::Box::builder()
.orientation(gtk::Orientation::Horizontal)
.build();
let content = if content.is_empty() {
"Unknown"
} else {
content
};
return_box.append(
&Label::builder()
.label(format!("{name}: "))
.css_classes(["box-label"])
.build(),
);
return_box.append(&Label::new(Some(content)));
return_box
}

View file

@ -3,6 +3,7 @@ use std::str::FromStr;
use gtk::{prelude::BoxExt, Stack, StackSidebar};
use images::images;
pub mod core;
pub mod images;
/**

View file

@ -98,7 +98,6 @@ fn virtualization() -> gtk::Box {
}
pub fn pkexec(cmd: String, spawn: bool) -> Option<String> {
dbg!(cmd.split_whitespace().next());
if spawn {
std::process::Command::new("pkexec")
.args(cmd.split_whitespace())

View file

@ -30,19 +30,19 @@
.license-label,
.version-label,
.size-label,
.description-label,
/* Containers */
.repo-label,
.tag-label,
.id-label,
.created-label,
.size-label {
.description-label {
font-weight: bold;
}
.image-box {
border-radius: 10px;
box-shadow: 1px -1px #000000;
margin: 10px;
padding: 10px;
}
.image-showbox {
padding: 10px;
}
.box-label {
font-weight: bold;
}