Add lunanode_resposne macro, add some DNS queries

master
Tait Hoyem 2 years ago
parent dad9d5f8c7
commit 37cba73953

@ -32,34 +32,70 @@ impl LunanodeRequestParam {
}
}
#[proc_macro_attribute]
pub fn lunanode_response(attr: TokenStream, input: TokenStream) -> TokenStream {
// Get the structs below the macro
let mut item_struct = parse_macro_input!(input as ItemStruct);
// clone the name to keep compiler happy :)
let name = item_struct.ident.clone();
// Add one field to the struct, see quote! macro below
if let syn::Fields::Named(ref mut fields) = item_struct.fields {
fields.named.push(
syn::Field::parse_named
.parse2(quote! {
#[serde(with="success")]
/// Wheather the request was successful or not. This can be either true or false. If it is false, then .make_request() will produce an Err(LNError::LunaNodeError(LNErrorResponse)).
pub success: bool
})
.unwrap(),
);
}
return quote! {
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
#item_struct
impl LunaNodeResponse for #name {}
}.into();
}
#[proc_macro_attribute]
pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream {
// Get the structs below the macro
let mut item_struct = parse_macro_input!(input as ItemStruct);
// parse the arguments using a custom type
let args_parsed: Vec<LunanodeRequestParam> = parse_macro_input!(attr as AttributeArgs)
.into_iter()
.filter_map(|nm| match nm {
// Only select certain tokens
NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, eq_token, lit: Lit::Str(lstr) })) => Some(
// Convert the sigment of the path to a string
(path.segments
.into_iter()
.map(|seg| seg.ident.to_string())
.collect::<Vec<String>>()
.swap_remove(0),
// get the raw value of the LitStr
lstr.value())
),
_ => None
})
// convert the (String, LitStr) tuple to a custom type which only accepts certain key/value pairs
.map(|(k,v)| LunanodeRequestParam::from(k, v))
.collect();
// if the first item in the list is not a response type
let response_type = match args_parsed.get(0).expect("There must be two argument for the macro.") {
LunanodeRequestParam::Response(res) => res,
_ => panic!("The response parameter must be a type; it must also be first."),
};
// if the second item in the list is not a url nedpoint
let url_endpoint = match args_parsed.get(1).expect("There must be two arguments for the macro.") {
LunanodeRequestParam::EndPoint(ep) => ep,
_ => panic!("The endpoint parameter must be a string; it must also be last.")
};
// clone the name to keep compiler happy :)
let name = item_struct.ident.clone();
// Add one field to the struct, see quote! macro below
if let syn::Fields::Named(ref mut fields) = item_struct.fields {
fields.named.push(
syn::Field::parse_named
@ -72,10 +108,12 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream {
);
}
// return this entire section as generated code
return quote! {
#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args)]
#item_struct
impl LunaNodeRequest for #name {
type response = #response_type; // TODO: Last section that needs to be dynamic
type Response = #response_type; // TODO: Last section that needs to be dynamic
fn get_keys(&self) -> ApiKeys {
self.keys.clone()
}

@ -4,21 +4,7 @@ pub mod requests;
pub mod responses;
pub mod types;
use ureq::{
json,
post
};
use sha2::Sha512;
use hmac::{Hmac, Mac};
use responses::{
VMListResponse,
PlanListResponse,
LNImageListResponse,
BillingCreditResponse,
VMInfoResponse,
};
use serde::{
de::DeserializeOwned,
Serialize,
Deserialize,
};
@ -26,46 +12,13 @@ use requests::{
Args,
VMListRequest,
};
use types::{
ApiKeys,
};
use clap::Parser;
/*
Define types for all different types of API requests.
Every valid enum value is the type of API request, and within it, a structure representing that API call.
No inner struct is required for requests that do not take arguments.
*/
#[derive(Serialize, Deserialize, Eq, PartialEq, Hash, Debug)]
enum LunaNodeAPIRequest {
BillingCredit,
ImageList,
PlanList,
RegionList,
VmInfo,
VmList(VMListRequest),
}
impl LunaNodeAPIRequest {
fn url_endpoint(&self) -> &str {
match self {
Self::BillingCredit => "billing/credit/",
Self::ImageList => "image/list/",
Self::PlanList => "plan/list/",
Self::RegionList => "region/list/",
Self::VmInfo => "vm/info/",
Self::VmList(_) => "vm/list/",
}
}
}
impl ToString for LunaNodeAPIRequest {
// TODO: don't do this; this should actually serialzie the object somehow
fn to_string(&self) -> String {
self.url_endpoint().to_string()
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let _ = args.make_request();
match args.make_request() {
Ok(info) => println!("{:?}", info),
Err(e) => println!("Error: {:?}", e),
};
Ok(())
}

@ -5,12 +5,13 @@ use crate::{
LNImageListResponse,
VolumeListResponse,
FloatingIpListResponse,
NetworkListResponse,
ZoneListResponse,
RecordListResponse,
},
types::{
LNError,
LNErrorResponse,
LunaNodeRequest,
LunaNodeResponse,
ApiKeys,
},
};
@ -22,9 +23,14 @@ use serde::{
Deserialize,
de::DeserializeOwned,
};
use hmac::{Hmac, Mac};
use hmac::Hmac;
use sha2::Sha512;
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
pub enum ZoneSubArgs {
List(ZoneListRequest),
}
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)]
pub enum FloatingSubArgs {
@ -37,6 +43,13 @@ pub enum ImageSubArgs {
/// List all images on my account.
List(ImageListRequest),
}
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)]
pub enum RecordSubArgs {
List(RecordListRequest),
}
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)]
pub enum VmSubArgs {
@ -56,6 +69,13 @@ pub enum BillingSubArgs {
Credit(BillingCreditRequest),
}
#[derive(Serialize, Deserialize, Debug, clap::Subcommand)]
#[serde(untagged)]
pub enum NetworkSubArgs {
/// Networks on your accounts
List(NetworkListRequest),
}
#[derive(Serialize, Deserialize, Debug, clap::Parser)]
#[serde(untagged)]
pub enum Args {
@ -66,6 +86,9 @@ pub enum Args {
/// See `lunanode ip help`
Floating(FloatingSubArgs),
#[clap(subcommand)]
/// See `lunanode network help`
Network(NetworkSubArgs),
#[clap(subcommand)]
/// See `lunanode vm help`
Vm(VmSubArgs),
#[clap(subcommand)]
@ -74,10 +97,24 @@ pub enum Args {
#[clap(subcommand)]
/// See `lunanode volume help`
Volume(VolumeSubArgs),
#[clap(subcommand)]
/// See `lunanode zone help`
Zone(ZoneSubArgs),
#[clap(subcommand)]
/// See `lunanode record help`
Record(RecordSubArgs),
}
impl Args {
pub fn make_request(&self) -> Result<(), LNError> {
match self {
Self::Record(RecordSubArgs::List(rec_list)) => {
let list = rec_list.make_request()?;
println!("{:#?}", list);
},
Self::Network(NetworkSubArgs::List(net_list)) => {
let list = net_list.make_request()?;
println!("{:#?}", list);
},
Self::Floating(FloatingSubArgs::List(ip_list)) => {
let list = ip_list.make_request()?;
println!("{:#?}", list);
@ -98,32 +135,51 @@ impl Args {
let credit =billing_credit.make_request()?;
println!("{:#?}", credit);
},
Self::Zone(ZoneSubArgs::List(zone_list)) => {
let list = zone_list.make_request()?;
println!("{:#?}", list);
},
}
Ok(())
}
}
#[lunanode_request(response="LNImageListResponse", endpoint="image/list/")]
#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args)]
/// ImageListRequest is used to create a new request for the /image/list endpoint.
#[lunanode_request(response="LNImageListResponse", endpoint="image/list/")]
pub struct ImageListRequest {}
#[lunanode_request(response="BillingCreditResponse", endpoint="billing/credit/")]
#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args, Display)]
/// BillingCreditRequest handles the /billing/credits/ endpoint. It will produce a BillingCreditResponse.
#[lunanode_request(response="BillingCreditResponse", endpoint="billing/credit/")]
pub struct BillingCreditRequest {}
#[lunanode_request(response="VMListResponse", endpoint="vm/list/")]
#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args, Display)]
/// VMListRequest is used to create a new request for the /vm/list endpoint.
#[lunanode_request(response="VMListResponse", endpoint="vm/list/")]
pub struct VMListRequest {}
#[lunanode_request(response="VolumeListResponse", endpoint="volume/list/")]
#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args, Display)]
/// VolumeListRequest is used to create a new request for the /volume/list endpoint.
#[lunanode_request(response="VolumeListResponse", endpoint="volume/list/")]
pub struct VolumeListRequest {}
#[lunanode_request(response="FloatingIpListResponse", endpoint="floating/list/")]
#[derive(Serialize, Deserialize, Debug, Hash, PartialEq, Eq, clap::Args, Display)]
/// IpListRequest is used to create a new request for the /ip/list endpoint.
#[lunanode_request(response="FloatingIpListResponse", endpoint="floating/list/")]
pub struct FloatingIpListRequest {}
/// NetworkListRequest is used to create a new request for the /network/list endpoint.
#[lunanode_request(response="NetworkListResponse", endpoint="network/list/")]
pub struct NetworkListRequest {}
/// ZoneListRequest is used to generate a request for the dns2/zone-list/ endpoint.
#[lunanode_request(response="ZoneListResponse", endpoint="dns2/zone-list/")]
pub struct ZoneListRequest {}
/// RecordListRequest is used to get information about a specific DNS zone (usually an entire domain, but could also be a subdomain).
#[lunanode_request(response="RecordListResponse", endpoint="dns2/record-list/")]
pub struct RecordListRequest {
zone_id: i32
}
/*
/// DynListRequest is used to get information about the dynamic DNS records set for all lunanode zones.
#[lunanode_request(response="DynListResponse", endpoint="dns/dyn-list/")]
pub struct DynListRequest {}
*/

@ -6,6 +6,7 @@ use crate::types::{
LunaNodeRequest,
};
use lunanode_macros::lunanode_response;
use serde_json;
use serde::{Serialize, Deserialize};
use serde_with::{
@ -255,11 +256,9 @@ pub struct LNPlan {
vcpu: i32, // number of vCPU cores
}
#[derive(Serialize, Deserialize, Debug)]
#[lunanode_response]
pub struct PlanListResponse {
plans: Vec<LNPlan>,
#[serde(with="success")]
success: bool, // should be more strict: "yes" or "no" as options
}
#[serde_as]
@ -274,7 +273,7 @@ pub struct LNImage {
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
pub struct VolumeResponse {
pub struct Volume {
#[serde_as(as="DisplayFromStr")]
/// The personal ID used for the volume.
id: i32,
@ -319,46 +318,134 @@ pub struct LNImageDetails {
/// An (explicitly UTC) datetime of when the VM was created
time_created: chrono::DateTime<chrono::Utc>, // should be a datetime
}
#[derive(Serialize, Deserialize, Debug)]
#[lunanode_response]
pub struct LNImageDetailResponse {
#[serde(with="success")]
success: bool,
details: LNImageDetails,
}
#[derive(Serialize, Deserialize, Debug)]
#[lunanode_response]
pub struct LNImageListResponse {
images: Vec<LNImage>,
#[serde(with="success")]
success: bool,
}
impl LunaNodeResponse for LNImageListResponse {}
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
#[lunanode_response]
pub struct BillingCreditResponse {
/// Money left in the account in USD
#[serde_as(as="DisplayFromStr")]
credit: f32,
#[serde(with="success")]
success: bool, // this should be stricter
}
impl LunaNodeResponse for BillingCreditResponse {}
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
#[lunanode_response]
pub struct VolumeListResponse {
volumes: Vec<VolumeResponse>,
#[serde(with="success")]
success: bool,
volumes: Vec<Volume>,
}
impl LunaNodeResponse for VolumeListResponse {}
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
pub struct Network {
/// The name set by the user when creating the network.
name: String,
#[serde_as(as="DisplayFromStr")]
net_id: i32,
/// The network subnet. This should be strictly typed as an IP address w/ a subnet attached. TODO: subnet type
subnet: String,
}
#[lunanode_response]
pub struct NetworkListResponse {
networks: Vec<Network>,
}
#[lunanode_response]
pub struct FloatingIpListResponse {
ips: Vec<FloatingIp>,
#[serde(with="success")]
success: bool,
}
impl LunaNodeResponse for FloatingIpListResponse {}
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
pub struct Zone {
#[serde_as(as="DisplayFromStr")]
/// The lunanode id of the zone.
id: i32,
/// The domain (or subdomain) being managed by the zone.
name: String,
/// The default TTL used for this zone. This can be overridden manually when specifying new records.
#[serde_as(as="DisplayFromStr")]
ttl: i32,
}
#[lunanode_response]
pub struct ZoneListResponse {
zones: std::collections::HashMap<i32, Zone>,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename="UPPERCASE")]
enum RecordType {
A,
AAAA,
CNAME,
ALIAS,
MX,
NS,
TXT,
SPF,
SRV
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug)]
pub struct Record {
/// The record id
#[serde_as(as="DisplayFromStr")]
id: i32,
/// The record type, one of A, AAAA, CNAME, ALIAS, MX, NS, TXT, SPF, SRV
#[serde(rename="type")]
record_type: RecordType,
/// The name of the record (for example, if setting an ip address for example.org, this field would show: example.org."
name: String,
/// The data assigned to this record. It shoudl always be a IPv4/v6 address, unless it's an NS
data: String,
#[serde_as(as="DisplayFromStr")]
/// The auxiliary value, only used on some record types.
aux: i32,
#[serde_as(as="DisplayFromStr")]
/// The TTL for the record.
ttl: i32,
/// Unknown value
policy: String,
/// The region the record applies to, if specificed. Otherwise None. Currently a String due to type issues.
region: String,
#[serde(rename="regiongroup")]
/// This value can be any region group defined by the user or auto. This could be stricter.
region_group: String,
/// This value can be either Canada or France, and it should be stricter.
country: String,
/// The continent as set by the user for this record. May be none or auto.
continent: String,
#[serde_as(as="DisplayFromStr")]
/// Is the record for everywhere (global); this should be a bool
global: i32,
#[serde_as(as="DisplayFromStr")]
/// The latitude of the record, if given.
latitude: f32,
#[serde_as(as="DisplayFromStr")]
/// Longitude of a record, if given.
longitude: f32,
#[serde(with="external")]
/// Is active in inactive record.
status: bool,
#[serde_as(as="DisplayFromStr")]
/// Monitor id, if given.
monitor_id: i32,
/// Optional check name?
check_name: Option<String>,
/// The "nice" version of the status as a string.
status_nice: String,
}
#[lunanode_response]
pub struct RecordListResponse {
records: std::collections::HashMap<i32, Record>,
}

@ -15,15 +15,15 @@ type HmacSHA512 = Hmac<Sha512>;
#[display("{api_id}")]
/// Used to specify API credentials. These are not required as long as LUNANODE_KEY_ID and LUNANODE_API_KEY are specified in the environment.
pub struct ApiKeys {
#[clap(long, takes_value=true, env="LUNANODE_KEY_ID", help="The API key ID from the Lunanode Dashboard.", long_help="The API key ID from the Lunanode dashboard. You may also specify this option via the LUNANODE_KEY_ID environment variable.")]
#[clap(long, takes_value=true, env="LUNANODE_KEY_ID", help="The API key ID from the Lunanode Dashboard.", long_help="The API key ID from the Lunanode dashboard. You may also specify this option via the LUNANODE_KEY_ID environment variable.", required=false, min_values=0, max_values=1, multiple_values=false)]
/// The API id string as used by LunaNode. This should be exactly 16 bytes. This will get enforced at a later date.
pub api_id: String,
#[serde(skip)]
#[clap(long, takes_value=true, env="LUNANODE_API_KEY", help="The Lunanode API key received from the dashboard.", long_help="The Lunanode API key recieved from the dashboard. This option may also be set by the LUNANODE_API_KEY environemnt variable.")]
#[clap(long, takes_value=true, env="LUNANODE_API_KEY", help="The Lunanode API key received from the dashboard.", long_help="The Lunanode API key recieved from the dashboard. This option may also be set by the LUNANODE_API_KEY environemnt variable.", required=false, min_values=0, max_values=1, multiple_values=false)]
/// Used for convenience. It is not serialized nor deserializes; this means that this object can NOT be deserialized at all, since this is not an Option<String>.
/// This is used to fill in the api_paritlakey field, and to give the request a way to access the api key locally.
pub api_key: String,
#[clap(long, takes_value=true, env="LUNANODE_API_PARTIALKEY", help="The Lunanode API key received from the dashboard, but only the first 64 bytes of it.", long_help="The Lunanode API key recieved from the dashboard. This option may also be set by the LUNANODE_API_PARTIALKEY environemnt variable. This should be autogenerated based on the LUNANODE_API_KEY, but it isn't. TODO.")]
#[clap(long, takes_value=true, env="LUNANODE_API_PARTIALKEY", help="The Lunanode API key received from the dashboard, but only the first 64 bytes of it.", long_help="The Lunanode API key recieved from the dashboard. This option may also be set by the LUNANODE_API_PARTIALKEY environemnt variable. This should be autogenerated based on the LUNANODE_API_KEY, but it isn't. TODO.", required=false, min_values=0, max_values=1, multiple_values=false)]
/// The api_paritalkey field as used by LunaNode (the first 64 bytes of the api_key). Note that this field is autofilled.
/// Since this should always be a 64 byte array, this will be enforced at some point in the future. TODO
pub api_partialkey: String,
@ -34,7 +34,7 @@ pub trait LunaNodeRequest: Serialize + std::fmt::Debug {
/// The resposne type you expect after making this request.
/// Setting this will allow acces to a generic function, make_request, that will expect this type to be recieved.
/// See: make_request
type response: LunaNodeResponse;
type Response: LunaNodeResponse;
/// Can't genericize this one out. You need to implenent a function which can return a set of API keys for creating an API requst.
fn get_keys(&self) -> ApiKeys;
@ -47,8 +47,8 @@ pub trait LunaNodeRequest: Serialize + std::fmt::Debug {
/// The Self::response type is the type you're expecting. In the case that this type is incorrect, or the server returns bad data, you will receive an LNError::SerdeError(serde::Error, String), with the String being a raw response from the server.
/// You may also recieve any other error defined in LNError, including timezone errors due to not being able to create a nonce value for the request.
/// See LNError.
fn make_request(&self) -> Result<Self::response, LNError> {
make_request::<Self, Self::response>(self)
fn make_request(&self) -> Result<Self::Response, LNError> {
make_request::<Self, Self::Response>(self)
}
}
@ -111,6 +111,7 @@ fn make_request<S, D>(req: &S) -> Result<D, LNError>
Err(e) => return Err(LNError::DeserializationError(e)),
Ok(s) => s,
};
println!("RESP: {}", resp_str);
match serde_json::from_str::<LNErrorResponse>(&resp_str) {
Ok(e) => return Err(LNError::LunaNodeError(e)),
Err(e) => false,

Loading…
Cancel
Save