Skip to content

Commit 92ab036

Browse files
Kitt3120Murasko
andauthored
Feat/hetzner provider (#17)
* feat: adds hetzner provider for currently implemented get command # Conflicts: # src/main.rs * todo * feat: adds yaml config directory structure feat: adds generate example config command * fix: fixes usage order in help output of get command * WIP * refactor: move config reading into own function # Conflicts: # src/main.rs * feat: adds hetzner provider for currently implemented get command # Conflicts: # src/main.rs * feat: adds yaml config directory structure feat: adds generate example config command * chore: fixed copilot feedack --------- Co-authored-by: Murasko <git@murasko.de>
1 parent 2b90498 commit 92ab036

24 files changed

Lines changed: 1089 additions & 259 deletions

docs/example-config.yaml

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
provider_name: Hetzner1
2+
domains: []
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
provider_name: Nitrado1
2+
domains: []
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: Hetzner1
2+
api_key: your_api_key
3+
api_base_url: https://dns.hetzner.com/api/v1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name: Nitrado1
2+
api_key: your_api_key
3+
api_base_url: https://api.nitrado.net

docs/example-config/resolver.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ipv4:
2+
url: https://ip.cancom.io
3+
type: Raw
4+
ipv6:
5+
url: https://ipv6.cancom.io
6+
type: Raw

src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod auto;
22
pub mod command;
3+
pub mod generate_config;
34
pub mod get;
45

56
use std::future::Future;

src/cli/command.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ use thiserror::Error;
55

66
use crate::{
77
Config,
8-
cli::{ExecutableCommand, auto, get},
8+
cli::{ExecutableCommand, auto, generate_config, get},
99
};
1010

1111
#[derive(Debug, ClapSubcommand)]
1212
#[command(version, about, long_about = None)]
1313
pub enum Subcommand<'a> {
1414
Auto(auto::Command<'a>),
1515
Get(get::Command<'a>),
16+
GenerateConfig(generate_config::Command<'a>),
1617
}
1718

1819
#[derive(Debug)]
@@ -27,6 +28,9 @@ pub enum Error {
2728

2829
#[error("Failed to execute get subcommand: {0}")]
2930
Get(#[from] get::Error),
31+
32+
#[error("Failed to execute generate-config subcommand: {0}")]
33+
GenerateConfig(#[from] generate_config::Error),
3034
}
3135

3236
/// dnrs
@@ -62,6 +66,10 @@ impl<'command> ExecutableCommand<'command> for Command<'command> {
6266
let input = get::Input { config, reqwest };
6367
subcommand.execute(&input).await?;
6468
}
69+
Subcommand::GenerateConfig(subcommand) => {
70+
let input = generate_config::Input { config };
71+
subcommand.execute(&input).await?;
72+
}
6573
}
6674

6775
Ok(())

src/cli/generate_config.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use std::{io, marker::PhantomData};
2+
3+
use clap::Parser;
4+
use lum_log::info;
5+
use thiserror::Error;
6+
7+
use crate::{Config, cli::ExecutableCommand};
8+
9+
#[derive(Debug)]
10+
pub struct Input<'config> {
11+
pub config: &'config Config,
12+
}
13+
14+
#[derive(Debug, Error)]
15+
pub enum Error {
16+
#[error("IO error: {0}")]
17+
Io(#[from] io::Error),
18+
19+
#[error("YAML serialization error: {0}")]
20+
Yaml(#[from] serde_yaml_ng::Error),
21+
22+
#[error("Config error: {0}")]
23+
Config(#[from] anyhow::Error),
24+
}
25+
26+
/// Generate configuration directory structure
27+
#[derive(Debug, Parser)]
28+
#[command(version, about, long_about = None, propagate_version = true)]
29+
pub struct Command<'command> {
30+
#[clap(skip)]
31+
_phantom: PhantomData<&'command ()>,
32+
33+
/// Output directory path (defaults to ./config)
34+
#[clap(short, long, default_value = "config")]
35+
pub output: String,
36+
37+
/// Force overwrite existing files
38+
#[clap(short, long, default_value = "false")]
39+
pub force: bool,
40+
}
41+
42+
impl<'command> ExecutableCommand<'command> for Command<'command> {
43+
type I = Input<'command>;
44+
type R = Result<(), Error>;
45+
46+
async fn execute(&self, _input: &'command Self::I) -> Self::R {
47+
let config_dir = std::path::Path::new(&self.output);
48+
49+
if config_dir.exists() && !self.force {
50+
info!(
51+
"Configuration directory {:?} already exists. Use --force to overwrite.",
52+
config_dir
53+
);
54+
return Ok(());
55+
}
56+
57+
Config::create_example_structure(config_dir)?;
58+
59+
info!("Configuration structure created in {:?}", config_dir);
60+
61+
Ok(())
62+
}
63+
}

src/cli/get.rs

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use std::marker::PhantomData;
22

33
use clap::{Args, Parser};
4+
use lum_log::{error, info};
45
use thiserror::Error;
56

67
use crate::{
78
Config,
89
cli::ExecutableCommand,
910
config::provider::Provider as ProviderConfig,
10-
provider::{GetAllRecordsInput, GetRecordsInput, Provider, nitrado::NitradoProvider},
11+
provider::{
12+
GetAllRecordsInput, GetRecordsInput, Provider, hetzner::HetznerProvider,
13+
netcup::NetcupProvider, nitrado::NitradoProvider,
14+
},
1115
};
1216

1317
#[derive(Debug)]
@@ -25,15 +29,14 @@ pub enum Error {
2529
ProviderError(#[from] anyhow::Error),
2630
}
2731

28-
//TODO: Fix order of usage message (provider should come first)
2932
#[derive(Debug, Args)]
30-
#[group(required = true, multiple = false)]
3133
pub struct SubdomainArgs {
3234
/// Subdomains to get records for
35+
#[clap(display_order = 3)]
3336
subdomains: Vec<String>,
3437

3538
/// Get all records
36-
#[clap(short, long, default_value = "false")]
39+
#[clap(short, long, default_value = "false", display_order = 3)]
3740
pub all: bool,
3841
}
3942

@@ -45,9 +48,11 @@ pub struct Command<'command> {
4548
_phantom: PhantomData<&'command ()>,
4649

4750
/// Name of the provider to get records from
51+
#[clap(display_order = 1)]
4852
provider: String,
4953

5054
/// Domain to get records for
55+
#[clap(display_order = 2)]
5156
domain: String,
5257

5358
#[command(flatten)]
@@ -59,13 +64,23 @@ fn get_provider<'config>(
5964
name: &str,
6065
config: &'config Config,
6166
) -> Option<Box<dyn Provider + 'config>> {
62-
for provider_file_config in config.providers.iter() {
63-
match &provider_file_config.provider {
67+
for provider in config.providers.iter() {
68+
match provider {
6469
ProviderConfig::Nitrado(nitrado_config) => {
6570
if name == nitrado_config.name {
6671
return Some(Box::new(NitradoProvider::new(nitrado_config)));
6772
}
6873
}
74+
ProviderConfig::Hetzner(hetzner_config) => {
75+
if name == hetzner_config.name {
76+
return Some(Box::new(HetznerProvider::new(hetzner_config)));
77+
}
78+
}
79+
ProviderConfig::Netcup(netcup_config) => {
80+
if name == netcup_config.name {
81+
return Some(Box::new(NetcupProvider::new(netcup_config)));
82+
}
83+
}
6984
}
7085
}
7186

@@ -77,6 +92,20 @@ impl<'command> ExecutableCommand<'command> for Command<'command> {
7792
type R = Result<(), Error>;
7893

7994
async fn execute(&self, input: &'command Self::I) -> Self::R {
95+
if self.subdomain_args.all && !self.subdomain_args.subdomains.is_empty() {
96+
error!("Cannot specify both --all and specific subdomains");
97+
return Err(Error::ProviderError(anyhow::anyhow!(
98+
"Cannot specify both --all and specific subdomains"
99+
)));
100+
}
101+
102+
if !self.subdomain_args.all && self.subdomain_args.subdomains.is_empty() {
103+
error!("Must specify either --all or specific subdomains");
104+
return Err(Error::ProviderError(anyhow::anyhow!(
105+
"Must specify either --all or specific subdomains"
106+
)));
107+
}
108+
80109
let config = input.config;
81110
let provider_name = self.provider.as_str();
82111

@@ -109,13 +138,13 @@ impl<'command> ExecutableCommand<'command> for Command<'command> {
109138

110139
let records = match results {
111140
Err(e) => {
112-
eprintln!("Error: {}", e);
141+
error!("Error: {}", e);
113142
return Err(e.into());
114143
}
115144
Ok(records) => records,
116145
};
117146

118-
println!("Records: {:#?}", records);
147+
info!("Records: {:#?}", records);
119148
Ok(())
120149
}
121150
}

0 commit comments

Comments
 (0)