summaryrefslogtreecommitdiff
path: root/src/bin/obnam-restore.rs
blob: 2014ef885f68d4e1c07803a1bfacc5fa902e10d8 (plain)
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()))
}