1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
// Fetch a backup generation's chunks, write to stdout.
use log::{debug, info, trace};
use obnam::chunk::{DataChunk, GenerationChunk};
use obnam::chunkid::ChunkId;
//use obnam::chunkmeta::ChunkMeta;
use serde::Deserialize;
use std::io::{stdout, Write};
use std::path::{Path, PathBuf};
use structopt::StructOpt;
#[derive(Debug, thiserror::Error)]
enum ClientError {
#[error("Server does not have generation {0}")]
GenerationNotFound(String),
#[error("Server does not have chunk {0}")]
ChunkNotFound(String),
}
#[derive(Debug, StructOpt)]
#[structopt(name = "obnam-backup", about = "Simplistic backup client")]
struct Opt {
#[structopt(parse(from_os_str))]
config: PathBuf,
#[structopt()]
gen_id: String,
}
fn main() -> anyhow::Result<()> {
pretty_env_logger::init();
let opt = Opt::from_args();
let config = Config::read_config(&opt.config).unwrap();
info!("obnam-restore starts up");
info!("opt: {:?}", opt);
info!("config: {:?}", config);
let client = reqwest::blocking::Client::builder()
.danger_accept_invalid_certs(true)
.build()?;
let mut stdout = stdout();
let gen = fetch_generation(&client, &config, &opt.gen_id)?;
debug!("gen: {:?}", gen);
for id in gen.chunk_ids() {
let chunk = fetch_chunk(&client, &config, id)?;
debug!("got chunk: {}", id);
stdout.write_all(chunk.data())?;
}
Ok(())
}
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
pub server_name: String,
pub server_port: u16,
}
impl Config {
pub fn read_config(filename: &Path) -> anyhow::Result<Config> {
let config = std::fs::read_to_string(filename)?;
let config: Config = serde_yaml::from_str(&config)?;
Ok(config)
}
}
fn fetch_generation(
client: &reqwest::blocking::Client,
config: &Config,
gen_id: &str,
) -> anyhow::Result<GenerationChunk> {
let url = format!(
"http://{}:{}/chunks/{}",
config.server_name, config.server_port, gen_id,
);
trace!("fetch_generation: url={:?}", url);
let req = client.get(&url).build()?;
let res = client.execute(req)?;
debug!("fetch_generation: status={}", res.status());
if res.status() != 200 {
debug!("fetch_generation: error from server");
return Err(ClientError::GenerationNotFound(gen_id.to_string()).into());
}
let text = res.text()?;
debug!("fetch_generation: text={:?}", text);
let gen = serde_json::from_str(&text)?;
Ok(gen)
}
fn fetch_chunk(
client: &reqwest::blocking::Client,
config: &Config,
chunk_id: &ChunkId,
) -> anyhow::Result<DataChunk> {
let url = format!(
"http://{}:{}/chunks/{}",
config.server_name, config.server_port, chunk_id,
);
trace!("fetch_chunk: url={:?}", url);
let req = client.get(&url).build()?;
let res = client.execute(req)?;
debug!("fetch_chunk: status={}", res.status());
if res.status() != 200 {
debug!("fetch_chunk: error from server");
return Err(ClientError::ChunkNotFound(chunk_id.to_string()).into());
}
let body = res.bytes()?;
Ok(DataChunk::new(body.to_vec()))
}
|