Add docker image support and a 'docker image inspect' interface, and update the changelog
This commit is contained in:
parent
a9826a410d
commit
be2f789652
8 changed files with 382 additions and 77 deletions
|
@ -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
34
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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
223
src/containers/core.rs
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::str::FromStr;
|
|||
use gtk::{prelude::BoxExt, Stack, StackSidebar};
|
||||
use images::images;
|
||||
|
||||
pub mod core;
|
||||
pub mod images;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue