Compare commits
No commits in common. "8ab2690d179884785a1f21859ecbf245896870f9" and "13350f872a35ef015d323b0d6bdb59e08579a656" have entirely different histories.
8ab2690d17
...
13350f872a
@ -20,7 +20,7 @@ pipeline:
|
||||
# http://plugins.drone.io/drone-plugins/drone-github-release/
|
||||
image: plugins/github-release
|
||||
files: target/release/reel-moby
|
||||
secrets: [github_api_key]
|
||||
secrets: [github_release_api_key]
|
||||
when:
|
||||
event: tag
|
||||
tag: v*
|
||||
|
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -20,12 +20,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
@ -681,7 +675,6 @@ dependencies = [
|
||||
name = "reel-moby"
|
||||
version = "1.2.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
@ -690,7 +683,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"structopt",
|
||||
"termion",
|
||||
"thiserror",
|
||||
"tui",
|
||||
]
|
||||
|
||||
@ -929,26 +921,6 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
|
@ -16,8 +16,6 @@ termion = "1.5"
|
||||
regex = "1.5.4"
|
||||
lazy_static = "1.4.0"
|
||||
structopt = "0.3.23"
|
||||
thiserror = "1.0.32"
|
||||
anyhow = "1.0.59"
|
||||
|
||||
[profile.release]
|
||||
lto = "yes"
|
||||
|
13
src/main.rs
13
src/main.rs
@ -1,13 +1,12 @@
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
mod common;
|
||||
mod repo;
|
||||
mod repository;
|
||||
mod ui;
|
||||
mod widget;
|
||||
|
||||
use anyhow::Result;
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// helps you searching or updating tags of your used docker images
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub struct Opt {
|
||||
@ -20,10 +19,8 @@ pub struct Opt {
|
||||
repo: Option<String>,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fn main() {
|
||||
//parse parameter
|
||||
let opt = Opt::from_args();
|
||||
ui::create_ui(&opt)?;
|
||||
|
||||
Ok(())
|
||||
ui::create_ui(&opt);
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ pub struct Images {
|
||||
}
|
||||
|
||||
impl Images {
|
||||
pub fn from_tag(images: &Self) -> super::Tag {
|
||||
pub fn convert(&self) -> super::Tag {
|
||||
super::Tag {
|
||||
name: images.tag_name.clone(),
|
||||
last_updated: Some(images.last_updated.clone()),
|
||||
details: images
|
||||
name: self.tag_name.clone(),
|
||||
last_updated: Some(self.last_updated.clone()),
|
||||
details: self
|
||||
.images
|
||||
.iter()
|
||||
.map(|d| super::TagDetails {
|
||||
@ -69,7 +69,7 @@ impl DockerHub {
|
||||
}
|
||||
|
||||
Ok(super::Repo {
|
||||
tags: tags.results.iter().map(Images::from_tag).collect(),
|
||||
tags: tags.results.iter().map(|t| t.convert()).collect(),
|
||||
next_page: tags.next_page,
|
||||
})
|
||||
}
|
||||
|
72
src/repository/ghcr.rs
Normal file
72
src/repository/ghcr.rs
Normal file
@ -0,0 +1,72 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::repository::Error;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Token {
|
||||
token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Ghcr {
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl Ghcr {
|
||||
/// fetches tag information with a repository name in the form of organization/repository or library/repository in the case of official images from docker
|
||||
pub fn create_repo(repo: &str) -> Result<super::Repo, Error> {
|
||||
let request_token = format!("https://ghcr.io/token?scope=repository:{}:pull", repo);
|
||||
let response = match reqwest::blocking::get(request_token) {
|
||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||
Ok(response) => response,
|
||||
};
|
||||
|
||||
let token = match response.json::<Token>() {
|
||||
Err(e) => return Err(Error::Converting(format!("invalid token json: {}", e))),
|
||||
Ok(token) => token.token,
|
||||
};
|
||||
|
||||
let request = format!("https://ghcr.io/v2/{}/tags/list?n=100", repo);
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = match client
|
||||
.get(request)
|
||||
.header(reqwest::header::AUTHORIZATION, format!("Bearer {}", token))
|
||||
.send()
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Fetching(format!("reqwest error: {}", e))),
|
||||
};
|
||||
|
||||
//convert it to json
|
||||
let tags = match response.json::<Self>() {
|
||||
Ok(result) => result,
|
||||
Err(e) => return Err(Error::Converting(format!("invalid json: {}", e))),
|
||||
};
|
||||
|
||||
if tags.tags.is_empty() {
|
||||
return Err(Error::NoTagsFound);
|
||||
}
|
||||
|
||||
Ok(super::Repo {
|
||||
tags: tags
|
||||
.tags
|
||||
.iter()
|
||||
.map(|t| super::Tag {
|
||||
name: t.clone(),
|
||||
details: vec![],
|
||||
last_updated: None,
|
||||
})
|
||||
.collect(),
|
||||
next_page: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ghcr;
|
||||
#[test]
|
||||
fn test_ghcr() {
|
||||
Ghcr::create_repo("ghcr.io/linuxserver/beets").unwrap();
|
||||
}
|
||||
}
|
@ -1,24 +1,33 @@
|
||||
mod dockerhub;
|
||||
mod ghcr;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use chrono::DateTime;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::common::display_duration_ext::DisplayDurationExt;
|
||||
use crate::repo;
|
||||
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
/// couldn't fetch json with reqwest
|
||||
#[error("Fetching error: {0}")]
|
||||
Fetching(String),
|
||||
/// a serde error
|
||||
#[error("Converting error: {0}")]
|
||||
Converting(String),
|
||||
/// invalid repos show a valid json with 0 tags
|
||||
#[error("Given Repo does not exists or has 0 tags.")]
|
||||
NoTagsFound,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::Fetching(s) => write!(f, "Fetching error: {}", s),
|
||||
Error::Converting(s) => write!(f, "Converting error: {}", s),
|
||||
Error::NoTagsFound => write!(f, "Given Repo has 0 tags. Is it valid?"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct TagDetails {
|
||||
pub arch: Option<String>,
|
||||
@ -74,10 +83,10 @@ impl Repo {
|
||||
Err(e) => return Err(Error::Converting(format!("{}", e))),
|
||||
};
|
||||
|
||||
if registry.unwrap_or_default().is_empty() {
|
||||
dockerhub::DockerHub::create_repo(&repo)
|
||||
if registry.unwrap_or_default() == "ghcr.io" {
|
||||
ghcr::Ghcr::create_repo(&repo)
|
||||
} else {
|
||||
Err(Error::Converting("This registry is not supported".into()))
|
||||
dockerhub::DockerHub::create_repo(&repo)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,17 @@
|
||||
use anyhow::Result;
|
||||
use std::{io, thread};
|
||||
|
||||
use crate::Opt;
|
||||
use termion::event::Key;
|
||||
use termion::raw::IntoRawMode;
|
||||
use tui::backend::TermionBackend;
|
||||
use tui::layout::{Constraint, Direction, Layout};
|
||||
use tui::Terminal;
|
||||
|
||||
use std::{io, thread};
|
||||
|
||||
use crate::repository;
|
||||
use crate::widget::info;
|
||||
use crate::widget::repo_entry;
|
||||
use crate::widget::service_switcher;
|
||||
use crate::widget::tag_list;
|
||||
use crate::Opt;
|
||||
|
||||
pub struct Ui {
|
||||
state: State,
|
||||
@ -54,14 +53,14 @@ impl std::iter::Iterator for State {
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn run(opt: &Opt, switcher: service_switcher::ServiceSwitcher) -> Result<()> {
|
||||
pub fn run(opt: &Opt) {
|
||||
let repo_id = opt.repo.as_deref();
|
||||
|
||||
let mut ui = Ui {
|
||||
state: State::SelectService,
|
||||
repo: repo_entry::RepoEntry::new(repo_id),
|
||||
tags: tag_list::TagList::with_status("Tags are empty"),
|
||||
services: switcher,
|
||||
services: service_switcher::ServiceSwitcher::new(&opt.file).unwrap(),
|
||||
details: crate::widget::details::Details::new(),
|
||||
info: info::Info::new("Select image of edit Repository"),
|
||||
};
|
||||
@ -71,9 +70,9 @@ impl Ui {
|
||||
}
|
||||
|
||||
//setup tui
|
||||
let stdout = io::stdout().into_raw_mode()?;
|
||||
let stdout = io::stdout().into_raw_mode().unwrap();
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
//setup input thread
|
||||
let receiver = super::spawn_stdin_channel();
|
||||
@ -81,7 +80,8 @@ impl Ui {
|
||||
//core interaction loop
|
||||
'core: loop {
|
||||
//draw
|
||||
terminal.draw(|rect| {
|
||||
terminal
|
||||
.draw(|rect| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
@ -106,7 +106,8 @@ impl Ui {
|
||||
rect.render_stateful_widget(list, more_chunks[0], state);
|
||||
rect.render_widget(ui.details.render(), more_chunks[1]);
|
||||
rect.render_widget(ui.info.render(), chunks[3]);
|
||||
})?;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
//handle input
|
||||
match receiver.try_recv() {
|
||||
@ -218,8 +219,6 @@ impl Ui {
|
||||
thread::sleep(std::time::Duration::from_millis(32));
|
||||
}
|
||||
|
||||
terminal.clear()?;
|
||||
|
||||
Ok(())
|
||||
terminal.clear().unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,20 @@
|
||||
mod default;
|
||||
mod no_yaml;
|
||||
|
||||
use anyhow::Result;
|
||||
use termion::input::TermRead;
|
||||
|
||||
use crate::widget::service_switcher;
|
||||
use crate::Opt;
|
||||
|
||||
use std::sync::mpsc;
|
||||
use std::{io, thread};
|
||||
|
||||
pub fn create_ui(opt: &Opt) -> Result<()> {
|
||||
use crate::Opt;
|
||||
use termion::input::TermRead;
|
||||
|
||||
use crate::widget::service_switcher;
|
||||
|
||||
pub fn create_ui(opt: &Opt) {
|
||||
let service_result = service_switcher::ServiceSwitcher::new(&opt.file);
|
||||
match service_result {
|
||||
None => no_yaml::NoYaml::run(opt),
|
||||
Some(switcher) => default::Ui::run(opt, switcher),
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
Some(_) => default::Ui::run(opt),
|
||||
}
|
||||
}
|
||||
|
||||
/// create a thread for catching input and send them to core loop
|
||||
|
@ -1,12 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use std::{io, thread};
|
||||
|
||||
use termion::event::Key;
|
||||
use termion::raw::IntoRawMode;
|
||||
use tui::backend::TermionBackend;
|
||||
use tui::layout::{Constraint, Direction, Layout};
|
||||
use tui::Terminal;
|
||||
|
||||
use std::{io, thread};
|
||||
|
||||
use crate::widget::details;
|
||||
use crate::widget::info;
|
||||
use crate::widget::repo_entry;
|
||||
@ -49,7 +48,7 @@ pub struct NoYaml {
|
||||
}
|
||||
|
||||
impl NoYaml {
|
||||
pub fn run(opt: &Opt) -> Result<()> {
|
||||
pub fn run(opt: &Opt) {
|
||||
let repo_id = opt.repo.as_deref();
|
||||
|
||||
let mut ui = NoYaml {
|
||||
@ -66,9 +65,9 @@ impl NoYaml {
|
||||
}
|
||||
|
||||
//setup tui
|
||||
let stdout = io::stdout().into_raw_mode()?;
|
||||
let stdout = io::stdout().into_raw_mode().unwrap();
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
//setup input thread
|
||||
let receiver = super::spawn_stdin_channel();
|
||||
@ -76,7 +75,8 @@ impl NoYaml {
|
||||
//core interaction loop
|
||||
'core: loop {
|
||||
//draw
|
||||
terminal.draw(|rect| {
|
||||
terminal
|
||||
.draw(|rect| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
@ -98,7 +98,8 @@ impl NoYaml {
|
||||
rect.render_stateful_widget(list, more_chunks[0], state);
|
||||
rect.render_widget(ui.details.render(), more_chunks[1]);
|
||||
rect.render_widget(ui.info.render(), chunks[2]);
|
||||
})?;
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
//handle input
|
||||
match receiver.try_recv() {
|
||||
@ -155,7 +156,6 @@ impl NoYaml {
|
||||
thread::sleep(std::time::Duration::from_millis(32));
|
||||
}
|
||||
|
||||
terminal.clear()?;
|
||||
Ok(())
|
||||
terminal.clear().unwrap();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user