From e3d9669037a751528ce34209390b26120cd6d5d8 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sat, 8 Oct 2022 08:12:37 -0600 Subject: [PATCH 01/17] Fix unused --- lunanode_macros/src/lib.rs | 9 ++++----- src/main.rs | 9 +-------- src/requests.rs | 8 -------- src/responses.rs | 4 ---- src/types.rs | 2 +- 5 files changed, 6 insertions(+), 26 deletions(-) diff --git a/lunanode_macros/src/lib.rs b/lunanode_macros/src/lib.rs index c27b579..dffd477 100644 --- a/lunanode_macros/src/lib.rs +++ b/lunanode_macros/src/lib.rs @@ -10,11 +10,10 @@ /// /// TODO: Improve error messages. -use std::collections::HashMap; use proc_macro::TokenStream; use quote::quote; -use syn::parse::{Parse, ParseStream, Parser, Result}; -use syn::{parse, parse_macro_input, punctuated::Punctuated, Attribute, AttributeArgs, Expr, Ident, ItemStruct, Local, Lit, LitStr, NestedMeta, Meta, MetaNameValue, Pat, Stmt, Type, Token, DeriveInput}; +use syn::parse::Parser; +use syn::{parse_macro_input, AttributeArgs, ItemStruct, Lit, NestedMeta, Meta, MetaNameValue, Type}; #[derive(Debug, Hash, Eq, PartialEq)] enum LunanodeRequestParam { @@ -33,7 +32,7 @@ impl LunanodeRequestParam { } #[proc_macro_attribute] -pub fn lunanode_response(attr: TokenStream, input: TokenStream) -> TokenStream { +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 :) @@ -67,7 +66,7 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream { .into_iter() .filter_map(|nm| match nm { // Only select certain tokens - NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, eq_token, lit: Lit::Str(lstr) })) => Some( + 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() diff --git a/src/main.rs b/src/main.rs index e0a363f..8247da5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,14 +4,7 @@ pub mod requests; pub mod responses; pub mod types; -use serde::{ - Serialize, - Deserialize, -}; -use requests::{ - Args, - VMListRequest, -}; +use requests::Args; use clap::Parser; fn main() -> Result<(), Box> { diff --git a/src/requests.rs b/src/requests.rs index 7b1b490..8c0e81f 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,6 +1,5 @@ use crate::{ responses::{ - IPAddress, VMListResponse, BillingCreditResponse, LNImageListResponse, @@ -38,17 +37,10 @@ use crate::{ }, }; use lunanode_macros::lunanode_request; -use parse_display_derive::Display; -use ureq::post; use serde::{ Serialize, - Serializer, Deserialize, - de::DeserializeOwned, }; -use serde_with::serde_as; -use hmac::Hmac; -use sha2::Sha512; #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] pub enum SshKeySubArgs { diff --git a/src/responses.rs b/src/responses.rs index ea588f7..28a1975 100644 --- a/src/responses.rs +++ b/src/responses.rs @@ -1,19 +1,15 @@ -use ureq; use crate::success; use crate::external; use crate::types::{ LunaNodeResponse, - LunaNodeRequest, }; use lunanode_macros::lunanode_response; -use serde_json; use serde::{Serialize, Deserialize}; use serde_with::{ DisplayFromStr, serde_as, }; -use uuid; #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Debug)] diff --git a/src/types.rs b/src/types.rs index c6d4e5e..a259839 100644 --- a/src/types.rs +++ b/src/types.rs @@ -114,7 +114,7 @@ fn make_request(req: &S) -> Result println!("RESP: {}", resp_str); match serde_json::from_str::(&resp_str) { Ok(e) => return Err(LNError::LunaNodeError(e)), - Err(e) => false, + Err(_e) => false, }; match serde_json::from_str::(&resp_str) { Ok(s) => Ok(s), From 3eafdc3d42366873d00035ef5a479b7b601b6128 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sat, 8 Oct 2022 13:12:49 -0600 Subject: [PATCH 02/17] Add deny(missing_docs) and document everything, for better or worse --- lunanode_macros/src/lib.rs | 1 + src/LNTypes.rs | 14 +- src/external.rs | 8 +- src/main.rs | 4 + src/requests.rs | 104 +++++++++++++-- src/responses.rs | 260 +++++++++++++++++++++++++++++++++---- src/success.rs | 5 + src/types.rs | 67 +++++++--- 8 files changed, 400 insertions(+), 63 deletions(-) diff --git a/lunanode_macros/src/lib.rs b/lunanode_macros/src/lib.rs index dffd477..e53f1ee 100644 --- a/lunanode_macros/src/lib.rs +++ b/lunanode_macros/src/lib.rs @@ -101,6 +101,7 @@ pub fn lunanode_request(attr: TokenStream, input: TokenStream) -> TokenStream { .parse2(quote! { #[serde(flatten)] #[clap(flatten)] + /// A set of keys that must be set by the user if ANY request is to be made. pub keys: ApiKeys }) .unwrap(), diff --git a/src/LNTypes.rs b/src/LNTypes.rs index 0f3ad1d..d3abc20 100644 --- a/src/LNTypes.rs +++ b/src/LNTypes.rs @@ -268,30 +268,30 @@ pub struct BillingCreditResponse { } #[derive(Serialize, Deserialize, Debug)] -pub struct LNErrorResponse { +pub struct LunaNodeErrorResponse { #[serde(with="success")] success: bool, error: String, // proper type, full accoutnign of the error from the API } -impl std::fmt::Display for LNErrorResponse { +impl std::fmt::Display for LunaNodeErrorResponse { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An API error has occured! {}", self.error) } } -impl std::error::Error for LNErrorResponse {} +impl std::error::Error for LunaNodeErrorResponse {} #[derive(Debug)] -pub enum LNError { +pub enum LunaNodeError { RequestError(ureq::Error), DeserializationError(std::io::Error), TimeError(std::time::SystemTimeError), - LunaNodeError(LNErrorResponse), + LunaNodeError(LunaNodeErrorResponse), SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string GetFuckedError, } -impl std::fmt::Display for LNError { +impl std::fmt::Display for LunaNodeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An error has occured! {}", self) } } -impl std::error::Error for LNError {} +impl std::error::Error for LunaNodeError {} diff --git a/src/external.rs b/src/external.rs index d495388..d933f7b 100644 --- a/src/external.rs +++ b/src/external.rs @@ -1,3 +1,7 @@ +//! Convert serialized "1"/"" to true/false. +//! This behaviour is extremely odd, and seemingly unique to Lunanode, so I had to use a custom +//! serialize/deserialize pair of functions. + use serde::{ de::{ self, @@ -8,7 +12,7 @@ use serde::{ Deserialize, }; -/// Seiralize bool into "yes"/"no", just like the LunaNode API does. +/// Seiralize bool into "1"/"", just like the LunaNode API does. pub fn serialize(succ: &bool, serializer: S) -> Result where S: Serializer { @@ -18,7 +22,7 @@ where } } -/// Deserialize bool from String with custom value mapping "yes" => true, "no" => false +/// Deserialize bool from String with custom value mapping "1" => true, "" => false pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/src/main.rs b/src/main.rs index 8247da5..69e9e62 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +//! The main program which runs using any of the various commands defined in +//! [`crate::requests::Args`]. +#![deny(missing_docs)] + pub mod success; pub mod external; pub mod requests; diff --git a/src/requests.rs b/src/requests.rs index 8c0e81f..0733c37 100644 --- a/src/requests.rs +++ b/src/requests.rs @@ -1,8 +1,10 @@ +//! All possible requests which can be made to the Lunanode API. + use crate::{ responses::{ - VMListResponse, + VmListResponse, BillingCreditResponse, - LNImageListResponse, + ImageListResponse, VolumeListResponse, FloatingIpListResponse, NetworkListResponse, @@ -31,7 +33,7 @@ use crate::{ VmIpListResponse, }, types::{ - LNError, + LunaNodeError, LunaNodeRequest, ApiKeys, }, @@ -43,53 +45,77 @@ use serde::{ }; #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `ssh-key` subcommands. pub enum SshKeySubArgs { + /// `ssh-key list` List(SshKeyRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `dyn` subcommands. pub enum DynSubArgs { + /// `dyn list` List(DynListRequest), + /// `dyn remove` Remove(DynRemoveRequest), + /// `dyn add` Add(DynAddRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `zone` subcommands. pub enum ZoneSubArgs { + /// `zone add` Add(ZoneAddRequest), + /// `zone list` List(ZoneListRequest), + /// `zone remove` Remove(ZoneRemoveRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `email domain` subcommands. pub enum EmailDomainSubArgs { + /// `email domain list` List(EmailDomainListRequest), } + #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `email user` subcommands. pub enum EmailUserSubArgs { + /// `email user list` List(EmailUserListRequest), + /// `email user add` Add(EmailUserAddRequest), + /// `email user remove` Remove(EmailUserRemoveRequest), + /// `email user set-password` SetPassword(EmailUserSetPasswordRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All possible `email` subcommands. pub enum EmailSubArgs { #[clap(subcommand)] + /// `email domain` Domain(EmailDomainSubArgs), + /// `email usage` Usage(EmailUsageRequest), #[clap(subcommand)] + /// `email user` User(EmailUserSubArgs), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All possible `floating` subcommands. pub enum FloatingSubArgs { /// List all images on my account. List(FloatingIpListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All possible `image` subcommands. pub enum ImageSubArgs { /// List all images on my account. List(ImageListRequest), @@ -97,21 +123,26 @@ pub enum ImageSubArgs { #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All possible `record` subcommands. pub enum RecordSubArgs { + /// List all records in a domain. List(RecordListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `vm ip` subcommands. pub enum VmIpSubArgs { + /// `vm ip list` List(VmIpListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `vm` subcommands. pub enum VmSubArgs { /// List all VMs on my account. - List(VMListRequest), + List(VmListRequest), /// Restart a VM, given an ID. Reboot(VmRebootRequest), /// Stop a VM, given an ID. @@ -124,12 +155,14 @@ pub enum VmSubArgs { } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `volume` subcommands. pub enum VolumeSubArgs { /// List all volumes I'm paying for. List(VolumeListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `billing` subcommands. pub enum BillingSubArgs { /// How much money is left in my account. Credit(BillingCreditRequest), @@ -137,42 +170,56 @@ pub enum BillingSubArgs { #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] #[serde(untagged)] +/// All `network` subcommands. pub enum NetworkSubArgs { /// Networks on your accounts List(NetworkListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `plan` subcommands. pub enum PlanSubArgs { + /// `plan list` List(PlanListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `region` subcommands. pub enum RegionSubArgs { + /// `region list` List(RegionListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `monitor check` subcommands. pub enum MonitorCheckSubArgs { + /// `monitor check list` List(MonitorCheckListRequest), + /// `monitor check types` Types(MonitorCheckTypesRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `monitor contact` subcommands. pub enum MonitorContactSubArgs { + /// `monitor contact list` List(MonitorContactListRequest), } #[derive(Serialize, Deserialize, Debug, clap::Subcommand)] +/// All `monitor` subcommands. pub enum MonitorSubArgs { #[clap(subcommand)] + /// All `monitor check` subcommands. Check(MonitorCheckSubArgs), #[clap(subcommand)] + /// All `monitor contact` subcommands. Contact(MonitorContactSubArgs), } #[derive(Serialize, Deserialize, Debug, clap::Parser)] #[serde(untagged)] +/// All possible commands that may be used at the command line. pub enum Args { #[clap(subcommand)] /// See `lunanode image help` @@ -219,7 +266,7 @@ pub enum Args { } /// Specifies an internal function which can only be called on LunaNodeRequests whoes response type implements Debug. This could be set in the structure itself, and probably should be. -fn print_req(req: &T) -> Result<(), LNError> +fn print_req(req: &T) -> Result<(), LunaNodeError> where ::Response: std::fmt::Debug { @@ -228,7 +275,9 @@ where Ok(()) } impl Args { - pub fn make_request(&self) -> Result<(), LNError> { + /// A generic make_request that unwraps the inner `impl LunaNodeRequest` type, and runs + /// make_request on those. + pub fn make_request(&self) -> Result<(), LunaNodeError> { match self { Self::Monitor(MonitorSubArgs::Contact(MonitorContactSubArgs::List(contact_list))) => print_req(contact_list)?, Self::Monitor(MonitorSubArgs::Check(MonitorCheckSubArgs::Types(monitor_check_types))) => print_req(monitor_check_types)?, @@ -265,7 +314,7 @@ impl Args { } /// ImageListRequest is used to create a new request for the /image/list endpoint. -#[lunanode_request(response="LNImageListResponse", endpoint="image/list/")] +#[lunanode_request(response="ImageListResponse", endpoint="image/list/")] pub struct ImageListRequest {} /// BillingCreditRequest handles the /billing/credits/ endpoint. It will produce a BillingCreditResponse. @@ -273,8 +322,8 @@ pub struct ImageListRequest {} pub struct BillingCreditRequest {} /// 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="VmListResponse", endpoint="vm/list/")] +pub struct VmListRequest {} /// VolumeListRequest is used to create a new request for the /volume/list endpoint. #[lunanode_request(response="VolumeListResponse", endpoint="volume/list/")] @@ -303,80 +352,112 @@ pub struct RecordListRequest { pub struct DynListRequest {} #[lunanode_request(response="SshKeyResponse", endpoint="sshkey/list/")] +/// A request to show all SSH public keys stored on the user's Lunanode account. pub struct SshKeyRequest {} #[lunanode_request(response="PlanListResponse", endpoint="plan/list/")] +/// A request to list all possible plans the user may create a new VM with. pub struct PlanListRequest {} #[lunanode_request(response="RegionListResponse", endpoint="region/list/")] +/// A request so show all regions available for the user to choose from. pub struct RegionListRequest {} #[lunanode_request(response="EmailUsageResponse", endpoint="email/usage/")] +/// A request to show an email usage report. pub struct EmailUsageRequest {} #[lunanode_request(response="EmailDomainListResponse", endpoint="email/domain-list/")] +/// A request to show all email domains managed by the user's Lunanode account. pub struct EmailDomainListRequest {} #[lunanode_request(response="MonitorCheckListResponse", endpoint="monitor/check-list/")] +/// A request to show all monitor checks (active and inactive). pub struct MonitorCheckListRequest {} #[lunanode_request(response="MonitorCheckTypesResponse", endpoint="monitor/check-types/")] +/// A request to check all possible monitor check types. pub struct MonitorCheckTypesRequest {} +/// A request to show all contacts on the monitor list. #[lunanode_request(response="MonitorContactListResponse", endpoint="monitor/contact-list/")] pub struct MonitorContactListRequest {} #[lunanode_request(response="EmailUserListResponse", endpoint="email/user-list/")] +/// A request to show all email users in a domain. pub struct EmailUserListRequest { + /// The domain ID to list users in. domain_id: i32, } #[lunanode_request(response="VmStartResponse", endpoint="vm/start/")] +/// A request to start a VM. pub struct VmStartRequest { + /// The ID of the VM to start. See [`VmListRequest`] vm_id: String, } #[lunanode_request(response="VmStopResponse", endpoint="vm/stop/")] +/// A request to stop a VM. pub struct VmStopRequest { + /// The ID of the VM to stop. See [`VmListRequest`] vm_id: String, } #[lunanode_request(response="VmRebootResponse", endpoint="vm/reboot/")] +/// A request to reboot a VM. pub struct VmRebootRequest { + /// The ID of the VM to reboot. See [`VmListRequest`] vm_id: String, } #[lunanode_request(response="EmailUserAddResponse", endpoint="email/user-add/")] +/// A request to add a new email account to a domain. pub struct EmailUserAddRequest { + /// The domain ID to add the user to. See [`EmailDomainListRequest`] domain_id: i32, + /// The username of the new user. username: String, + /// The password of the new user. password: String, } #[lunanode_request(response="EmailUserRemoveResponse", endpoint="email/user-remove/")] +/// A request to remove a user from a domain's email. pub struct EmailUserRemoveRequest { + /// An ID of the domain to remove the user from. domain_id: i32, + /// The ID of the user to remove. See [`EmailUserListRequest`] user_id: i32, } #[lunanode_request(response="EmailUserSetPasswordResponse", endpoint="email/user-set-password/")] +/// A request to set an email user's password on a specific domain. pub struct EmailUserSetPasswordRequest { + /// The ID of the domain the user is on. domain_id: i32, + /// The user ID of the user on the domain. user_id: i32, + /// The new password. password: String, } #[lunanode_request(response="VmIpListResponse", endpoint="vm/iplist/")] +/// A request to list all IPs associated with a VM. pub struct VmIpListRequest { + /// The VM whoes IPs to list. vm_id: String, } #[lunanode_request(response="DynRemoveResponse", endpoint="dns/dyn-remove/")] +/// A request to remove a dyn record. pub struct DynRemoveRequest { + /// The ID of the dyn record to remove. See [`DynListrRequest`] dyn_id: i32, } #[lunanode_request(response="DynAddResponse", endpoint="dns/dyn-add/")] +/// A request to add a new dyn record. pub struct DynAddRequest { /// The data string for the record. This is usually a FQDN, followed by a period. If you wanted to set a record for `dev.example.org`, you would put `dev.example.com.` in this field. name: String, @@ -385,6 +466,7 @@ pub struct DynAddRequest { } #[lunanode_request(response="ZoneAddResponse", endpoint="dns2/zone-add/")] +/// A request to add a new zone. pub struct ZoneAddRequest { /// A new zone, a FQDN. name: String, @@ -393,6 +475,10 @@ pub struct ZoneAddRequest { } #[lunanode_request(response="ZoneRemoveResponse", endpoint="dns2/zone-remove/")] +/// A request to remove a zone entirely from Luanode's control. +/// Be very careful doing this, as this will likely remove ***ALL*** of your records associated +/// with the domain. pub struct ZoneRemoveRequest { + /// The zone to remove. See also: [`ZoneListRequest`] zone_id: i32, } diff --git a/src/responses.rs b/src/responses.rs index 28a1975..48fb2fd 100644 --- a/src/responses.rs +++ b/src/responses.rs @@ -1,3 +1,7 @@ +//! All responses which are recieved by this application from the Lunanode API. +//! These responses are all strictly typed, and documented. Although in some cases it is noted that +//! it should have an even stricter type, or that the field meaning is unkown. + use crate::success; use crate::external; use crate::types::{ @@ -48,42 +52,53 @@ pub struct VirtualMachine { os_status: String, // should be nmore strict } -#[derive(Serialize, Deserialize, Debug)] -pub struct VMListResponse { +#[lunanode_response] +/// The result of requesting a list of VMs on the user's Lunanode account. +/// See also: [`crate::requests::VmListRequest`] +pub struct VmListResponse { + /// A list of VMs. vms: Vec, - #[serde(with="success")] - success: bool, // should be more strict "yes" or "no" as optiobs } -impl ToString for VMListResponse { - fn to_string(&self) -> String { - "N/A".to_string() - } -} -impl LunaNodeResponse for VMListResponse {} #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all="lowercase")] +/// An enum defining whether an IP is attached to a VM or is simply allocated without being +/// attached to any VM in particular. +/// See also: [`FloatingIp`] enum AttachmentType { + /// An unattached floating IP. Unattached, + /// An attached floating IP. Vm, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about a floating IP struct FloatingIp { + /// A String with a VM UUID if attached, otherwise None. attached_id: Option, + /// A String with the VM's name if attached, otherwise None. attached_name: Option, + /// Vm/Unattached attached_type: AttachmentType, + /// The hostname of the floating IP address, usually something like + /// xxx-xxx-xxx-xxx.rdns.lunanode.com hostname: String, + /// The IP address of the floating IP. ip: std::net::Ipv4Addr, + /// The region in which the IP address has been allocated. region: LNRegion, + /// The rDNS domain, if specified, otherwise, None. reverse: Option, #[serde_as(as="DisplayFromStr")] + /// The last time the record was updated. time_updated: chrono::DateTime, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Extra detailed information for a VM. pub struct VMInfoExtra { #[serde_as(as="DisplayFromStr")] /// bandwidth allowed over a month in GB @@ -122,36 +137,50 @@ pub struct VMInfoExtra { #[serde(untagged)] /// A generic IP address type, with two variants: V4, and V6. This is used for generic types returned from the API. For example, a list of IP addresses without the IP type specified. pub enum IPAddress { + /// IPv4 variant V4(std::net::Ipv4Addr), + /// IPv6 variant V6(std::net::Ipv6Addr), } #[derive(Serialize, Deserialize, Debug)] +/// An IP address type enum, specifying either V4 or V6. pub enum IPAddressType { #[serde(rename="4")] + /// IPv4 variant V4, #[serde(rename="6")] + /// IPv6 variant V6, } #[derive(Serialize, Deserialize, Debug)] +/// VM IP address pub struct VMAddress { + /// The IP address of the VM. addr: IPAddress, #[serde(with="external")] + /// Whether the IP is an internal or external kind. external: bool, + /// The version of IP address used. version: IPAddressType, #[serde(rename="reverse")] - /// The reverse DNS assigned to the VM. This is optional. + /// The reverse DNS assigned to the VM, if specified. Otherwise, None. rdns: Option, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Detailed VM info. pub struct VMInfo { #[serde(rename="additionalip")] + /// A list of additional IPs (which are not the primary IP) which have been assigned to the VM. additional_ip: Vec, #[serde(rename="additionalprivateip")] + /// Any additional private IPs assigned to the VM. additional_private_ip: Vec, + /// A list of addresses inherently part of the VM. Think of a primary private IP, or where + /// outgoing internet traffic is routed. addresses: Vec, /// a possibly empty string containing an error message error_detail: Option, @@ -194,80 +223,114 @@ pub struct VMInfo { volumes: String, } -#[derive(Serialize, Deserialize, Debug)] +#[lunanode_response] +/// The result from a [`crate::requests::VmInfoRequest`]. pub struct VMInfoResponse { + /// Extra information (cached) extra: VMInfoExtra, + /// Detailed VM info (uncached) info: VMInfo, - #[serde(with="success")] - success: bool, } #[derive(Serialize, Deserialize, Debug)] -pub enum LNPlanCategory { +/// A plan category. +pub enum PlanCategory { #[serde(rename="Compute-Optimized")] + /// For CPU-bound tasks. ComputeOptimized, #[serde(rename="General Purpose")] + /// Pretty good for most projects. GeneralPurpose, #[serde(rename="Memory-Optimized")] + /// For memory-bound tasks. MemoryOptimized, #[serde(rename="SSD-Cached High-Memory")] + /// For IO/memory-bound tasks. SSDCacheHighMemory, #[serde(rename="SSD-Cached Standard")] + /// For IO-bound tasks. SSDCacheStandard, } #[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq)] #[serde(rename_all = "lowercase")] +/// Regions where Lunanode is supported. pub enum LNRegion { + /// Montreal, Quebec, Canada. Montreal, + /// Roubaix, France. Roubaix, + /// Toronto, Ontario, Canada. Toronto } #[serde_as] #[derive(Serialize, Deserialize, Debug)] -pub struct LNPlan { +/// A VM plan/level of serivce. +pub struct Plan { #[serde_as(as="DisplayFromStr")] + /// Does the plan support all regions? all_regions: i32, // may need to be more strict? #[serde_as(as="DisplayFromStr")] + /// The maxmimum bandwidth in Mbps bandwidth: i32, // in Mbps - category: LNPlanCategory, + /// Category of plan + category: PlanCategory, #[serde_as(as="DisplayFromStr")] + /// Lunanode uses a CPU points system to determine how powerful the CPU is. cpu_points: f32, // no idea what this meas + /// The name of the plan, set by Lunanode themselves. name: String, // plan name, this could potentially be strictly typed as the types of plans don't often change "s.half", "s.1", "m.1", "m.2", etc. #[serde_as(as="DisplayFromStr")] + /// The plan ID. Could be useful if creating a new VM. plan_id: i32, // can be strictly typed, if needed #[serde_as(as="DisplayFromStr")] + /// The cost in USD per hour. price: f32, // up to 7 decmial points (f32), and this is the number of US dollars per hour + /// The price is USD per month, with a $ attached. price_monthly_nice: String, // instead of calculating it on your own, this provides a nice reading of the price for clients + /// The price in USD per hour, with a $ attached. price_nice: String, // same as above, but for the hour #[serde_as(as="DisplayFromStr")] + /// The amount of RAM in MB. ram: i32, // in MB + /// Regions where the plan can be dpeloyed. regions: Vec, // list of regions by name + /// Regions where the plan can be deployed, separated by a comma. regions_nice: String, // list of regions concatonated with commans #[serde_as(as="DisplayFromStr")] + /// Storage in GB. storage: i32, // per GB #[serde_as(as="DisplayFromStr")] + /// Number of vCPU cores. vcpu: i32, // number of vCPU cores } #[lunanode_response] +/// A list of plans from a [`crate::requests::PlanListRequest`] pub struct PlanListResponse { - plans: Vec, + /// List of plans; see [`Plan`] for more detail. + plans: Vec, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] -pub struct LNImage { +/// An ISO or QCOW2 image stored on Lunanode. +pub struct Image { #[serde_as(as="DisplayFromStr")] + /// ID of the image. This is useful for later if you would like to interact with it in some way. image_id: i32, // id of the image + /// Name of the Image set by the user, or Lunanode. name: String, // set by the user or LunaNode + /// The region in which the image is stored. region: LNRegion, + /// The status of the image; at least "active" and "inactive" are possible. status: String, // should be stricter, at least "active", "inactive"(?) } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A storage volume on the user's Lunanode account. pub struct Volume { #[serde_as(as="DisplayFromStr")] /// The personal ID used for the volume. @@ -294,20 +357,37 @@ pub struct Volume { #[serde_as] #[derive(Serialize, Deserialize, Debug)] -pub struct LNImageDetails { +/// Details of a Lunanode image +pub struct ImageDetails { + /// This should be more strict. At least one possible value is "writeback". cache_mode: String, // should be stricter, at least "writeback", + /// This should be more strict; this should be a strict checksum type. checksum: String, // should be stricter, to check of checksum type + /// This should be more strict. At least one possible value is "iso". disk_format: String, // should be stricter, at least "iso" + /// This should be more strict. I think the only possible values are "ide" and "sata". hw_disk_bus: String, // should be stricter, at least "ide", + /// It's not clear exactly what this means; the Lunanode documentation isn't extremely well done. + /// But at least one possible value here is "cirrus"; unsure if other values are possible, or + /// what they are. hw_video_model: String, // could, in theory, be stricter, at least: "cirrus", + /// The model of the virtual interface (I think) hw_vif_model: String, // appropriately vague + /// A read only image? #[serde(with="success")] is_read_only: bool, // "yes"/"no" + /// There should not be many possible values, but one of them is "host-model". libvrt_cpu_mode: String, // should be stricter, at least: "host-model", + /// Unkown list of metadata. metadata: Vec<()>, // vec of what? + /// The name of the image; often set by the user. name: String, // sufficiently vague + /// The region of the image. region: LNRegion, // sufficiently typed + /// The size of the image in MB. size: i32, // in MB, maybe? + /// In theory this could be stricter, but Lunanode does not enumerate all possible return types. + /// Possible values are at least: "active". status: String, // should be stricter, at least: "active", #[serde_as(as="DisplayFromStr")] /// An (explicitly UTC) datetime of when the VM was created @@ -315,16 +395,21 @@ pub struct LNImageDetails { } #[lunanode_response] -pub struct LNImageDetailResponse { - details: LNImageDetails, +/// A response from [`crate::requests::ImageDetailRequest`] +pub struct ImageDetailResponse { + /// Details of a specific image. + details: ImageDetails, } #[lunanode_response] -pub struct LNImageListResponse { - images: Vec, +/// See also: [`crate::requests::ImageListRequest`] +pub struct ImageListResponse { + /// List of images and their information. + images: Vec, } #[lunanode_response] +/// See also: [`crate::requests::BillingCreditRequest`] pub struct BillingCreditResponse { /// Money left in the account in USD #[serde_as(as="DisplayFromStr")] @@ -332,33 +417,42 @@ pub struct BillingCreditResponse { } #[lunanode_response] +/// See also: [`crate::requests::VolumeListRequest`] pub struct VolumeListResponse { + /// List of volumes and their associated information. volumes: Vec, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A network with a subnet. pub struct Network { /// The name set by the user when creating the network. name: String, #[serde_as(as="DisplayFromStr")] + /// The ID of the network; this is useful if you'd like to modify it in another request. 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] +/// A response from [`crate::requests::NetworkListRequest`] pub struct NetworkListResponse { + /// A list of networks. networks: Vec, } #[lunanode_response] +/// A response from [`crate::requests::FloatingIpListRequest`] pub struct FloatingIpListResponse { + /// A list of [`FloatingIp`]s. ips: Vec, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A domain "zone". This is the same as a root domain. pub struct Zone { #[serde_as(as="DisplayFromStr")] /// The lunanode id of the zone. @@ -371,26 +465,54 @@ pub struct Zone { } #[lunanode_response] +/// A response from [`crate::requests::ZoneListRequest`] pub struct ZoneListResponse { + /// A map of all zones, with their IDs as their keys. zones: std::collections::HashMap, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename="UPPERCASE")] +/// Possible record types for [`Record`] enum RecordType { + /// An A record is an IPv4 pointer with a key (domain or subdomain) and value (IP address). A, + /// An AAAA record (usually said "quadrupal A") is an IPv6 pointer with a key (domain or subdomain) and + /// value (IP address). AAAA, + /// A CNAME record basically forwards requests from a domain or subdomain to a second domain or + /// subdomain. It is very common to see this in places where www.example.com and ftp.example.com + /// use the same server (and IP address) at example.com; now, when switching IP addresses, the address only + /// needs to be updated for example.com, and all the subdomains will still function like normal. CNAME, + /// An ALIAS record states that writing a domain one way, for example: ca.example.com, is the + /// same as another, for example: canada.example.com. ALIAS, + /// An MX record tells email providers where to send incoming mail. MX, + /// An NS record decides the nameserver record for the domain. This is necessary to use in the + /// case of Lunanode, since you cannot purchase a domain through them. You must purchase the + /// domain elsewhere, then set your NS records at your registrar to point to lunanode's + /// nameservers. NS, + /// A TXT record is a static record generally used for verification of some kind or another. Some + /// email providers (ProtonMail), will require you to set up a TXT record to prove that you own + /// the domain. TXT, + /// An SPF record is a deprecated form of email authentication record. SPF, + /// An SRV record can be used so that a port is not necessary when running multiple (similar or + /// the same) services on different ports. For example, suppose you run two web servers, one at + /// 80 and 443 (standard HTTP and HTTPS ports) and one on 8080 and 4443 (commonly used in live + /// updating prototypes. An SRV record can help you use a subdomain instead of a port number + /// at the end of the domain. So beta.example.com and www.example.com could point a browser to + /// different ports of the same IP. SRV } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A DNS record. pub struct Record { /// The record id #[serde_as(as="DisplayFromStr")] @@ -441,26 +563,37 @@ pub struct Record { } #[lunanode_response] +/// The result of requesting all DNS records for a domain. +/// See also: [`crate::requests::RecordListRequest`], [`ZoneListResponse`] pub struct RecordListResponse { records: std::collections::HashMap, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A detailed dyn record. pub struct DynRecord { #[serde_as(as="DisplayFromStr")] + /// An id you may need later to edit this. + /// See also: [`DynUpdateResponse`], [`DynRemoveResponse`] id: i32, + /// The name, or key (usually a subddomain) of the dyn record. name: String, + /// An IP address (a value). ip: IPAddress, } #[lunanode_response] +/// The result of requesting all dyn records on a domain. +/// See also: [`crate::requests::DynListRequest`] pub struct DynListResponse { dyns: std::collections::HashMap, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about an SSH key. +/// See also: [`SshKeyResponse`] pub struct SshKey { #[serde_as(as="DisplayFromStr")] id: i32, @@ -471,11 +604,16 @@ pub struct SshKey { } #[lunanode_response] +/// The result of requesting a list of all regions. +/// See also: [`crate::requests::RegionListRequest`] pub struct RegionListResponse { + /// A map of all regions supported by Lunanode. regions: std::collections::HashMap, } #[lunanode_response] +/// The result of requesting all ssh keys connected to an account. +/// See also: [`crate::requests::SshKeyRequest`] pub struct SshKeyResponse { #[serde(flatten)] /// List of keys stored in the LunaNode cloud. The String is just an index that I can't get to flatten :grumpy programmer noises: @@ -483,8 +621,10 @@ pub struct SshKeyResponse { } #[lunanode_response] +/// The reuslt of requesting email usage statistics from Lunanode. pub struct EmailUsageResponse { #[serde_as(as="DisplayFromStr")] + /// The number of domains being used by the account. domains: i32, /// Usage of email in GB, this should be an i32. storage: String, @@ -496,21 +636,31 @@ pub struct EmailUsageResponse { #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// The result of requesting an email's domain information. pub struct EmailDomainInfo { #[serde_as(as="DisplayFromStr")] + /// An id for the domain's email info. This will be useful later if you want to change + /// information about how the email domain operates. id: i32, + /// A String of the domain. name: String, + /// Storage used storage: String, + /// Storage used in MB/GB, with " MB/GB" attached. storage_nice: String, } #[lunanode_response] +/// The result of requesting a list of email domains on the Lunanode account. pub struct EmailDomainListResponse { #[serde(flatten)] - domains: std::collections::HashMap, + /// Key/value pairs where the key is a String of the domain. + domains: std::collections::HashMap, } #[lunanode_response] +/// The result of requesting all monitor checks (active or inactive). +/// See also: [`crate::requests::MonitorCheckListRequest`] pub struct MonitorCheckListResponse { /// This is not done yet. A Check type will be required. TODO checks: Vec, @@ -518,45 +668,74 @@ pub struct MonitorCheckListResponse { #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] +/// The type of parameter being asked for by the monitor check. +/// See also: [`Param`] pub enum ParamType { + /// A single, simple parameter. Single(String), + /// Posssible options in the case of a fixed set of choices. Generally speaking, the key/value + /// Strings will be the same, unless the choice has a space or some other character that would + /// make it annoying to parse over an HTTP request. Options(std::collections::HashMap), } #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about a parameter of a monitor check. +/// See also [`MonitorTypes`] pub struct Param { #[serde(with="external")] + /// Is the parameter required when creating this monitor check. required: bool, + /// What kind of parameter is this. r#type: ParamType, + /// The name of the parameter, this would be the name of a field in a request. name: String, + /// A placeholder, or example of what may by used in this parameter. placeholder: Option, } #[derive(Serialize, Deserialize, Debug)] +/// Detailed information about various monitor types in a category. +/// See also: [`MonitorCheckTypesResponse`] pub struct MonitorTypes { /// The name of the monitor type. name: String, /// The short name of the monitor type. short_name: String, + /// A list of parameters for the given monitor type. params: std::collections::HashMap } #[lunanode_response] +/// The result of asking for possible monitor check types. +/// See also: [`crate::requests::MonitorCheckTypesRequest`] pub struct MonitorCheckTypesResponse { + /// All possible monitor check types as supported by Lunanode. + /// Note that the String key matches a category name, and all monitor types under that category + /// will be listed under each. types: std::collections::HashMap, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all="lowercase")] +/// The type of contact as stated by lunanode as possible options. This type helps decide how to +/// use the contact in the case of a monitor trip. +/// See also: [`Contact`] pub enum ContactType { + /// A (presumably) valid email. Email, + /// A (presumably) valid SMS-captable phone or VOIP number. Sms, + /// A (presumably) valid voice-captable phone or VOIP number. Voice, + /// A (presumably) valid HTTP(S) URL. Http, } #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A contact for monitoring purposes. +/// See also: [`Contacts`], [`MonitorContactListResponse`] pub struct Contact { #[serde_as(as="DisplayFromStr")] id: i32, @@ -566,6 +745,8 @@ pub struct Contact { #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] +/// Possible values for contacts listed in Lunanode. This is required because what the API sends +/// back an empty list when there are no contacts, but a hashmap if there is one or more contacts. pub enum Contacts { /// Need an empty vec of some kind to hold the blank value, which appears as []. Empty(Vec), @@ -574,6 +755,8 @@ pub enum Contacts { } #[lunanode_response] +/// The result of requesting a contact list for monitoring purposes. +/// See also: [`crate::requests::MonitorContactListRequest`] pub struct MonitorContactListResponse { /// A list of contacts in your LunaNode account. This should be deserialized as Option>, but I haven't gotten around to serializing this in that way. TODO. contacts: Contacts, @@ -581,6 +764,7 @@ pub struct MonitorContactListResponse { #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// An email user on the Lunanode email system. pub struct EmailUser { /// The user's ID. #[serde_as(as="DisplayFromStr")] @@ -595,31 +779,47 @@ pub struct EmailUser { } #[lunanode_response] +/// The result of requesting a list of email users in a certain domain. +/// See also: [`crate::requests::EmailUserListRequest`] pub struct EmailUserListResponse { #[serde(flatten)] + /// The users of a domain's email. The String is an id (and should be an i32 TODO). users: std::collections::HashMap, } #[lunanode_response] +/// The result of sending a start command to a VM. +/// See also: [`crate::requests::VmStartRequest`] pub struct VmStartResponse {} #[lunanode_response] +/// The result of sending a stop command to a VM. +/// See also: [`crate::requests::VmStopRequest`] pub struct VmStopResponse {} #[lunanode_response] +/// The result of sending a reboot command to a VM. +/// See also: [`crate::requests::VmRebootRequest`] pub struct VmRebootResponse {} #[lunanode_response] +/// The result of adding an email user to a domain. +/// See also: [`crate::requests::EmailUserAddRequest`] pub struct EmailUserAddResponse {} #[lunanode_response] +/// The result of removing an email user from a domain. +/// See also: [`crate::requests::EmailUserRemoveRequest`] pub struct EmailUserRemoveResponse {} #[lunanode_response] +/// The result of setting an email user's password in the specified domain. +/// See also: `EmailUserSetPasswordRequest` pub struct EmailUserSetPasswordResponse {} #[serde_as] #[derive(Serialize, Deserialize, Debug)] +/// A VM-specific IP pub struct VmIp { floating: std::net::Ipv4Addr, /// A network UUID @@ -633,18 +833,28 @@ pub struct VmIp { } #[lunanode_response] +/// The result of querying for a list of ips in a specific VM. +/// See also: [`crate::requests::VmIpListRequest`] pub struct VmIpListResponse { ips: std::collections::HashMap, } #[lunanode_response] +/// the result of removing a dynamic record. +/// See also: [`crate::requests::DynRemoveRequest`] pub struct DynRemoveResponse {} #[lunanode_response] +/// the result of adding a dynamic record. +/// See also: [`crate::requests::DynAddRequest`] pub struct DynAddResponse {} #[lunanode_response] +/// the result of adding a zone. +/// See also: [`crate::requests::ZoneAddRequest`] pub struct ZoneAddResponse {} #[lunanode_response] +/// the result of removing a zone. +/// See also: [`crate::requests::ZoneRemoveRequest`] pub struct ZoneRemoveResponse {} diff --git a/src/success.rs b/src/success.rs index 338de68..28b776d 100644 --- a/src/success.rs +++ b/src/success.rs @@ -1,3 +1,8 @@ +//! A module to convert "yes"/"no" from a string to a bool. +//! This is exceptionally strange behaviour from an API. Just use `bool`. +//! But anyway, since Lunanode does indeed use this system, a custom module to serialize and +//! deserialize their "yes"/"no" responses was necessary. + use serde::{ de::{ self, diff --git a/src/types.rs b/src/types.rs index a259839..12dea21 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,14 @@ +//! This crate handles all the common types used from request and response, as well as how they +//! should be linked together. +//! For example, an `impl LunaNodeRequest` has an associated type `Response` that must be set to a +//! valid LunaNodeResponse on creation. +//! +//! Although this can all be done automatically through macros, it is important that the +//! relationships be defined somewhere. +//! This is the location for those relationships. +//! +//! If you'd like to see how to implement the macros, check the the `lunanode_macros` subcrate. + use crate::success; use clap; use parse_display_derive::Display; @@ -29,7 +40,15 @@ pub struct ApiKeys { pub api_partialkey: String, } +/// A trait that must be implemented by any response type. pub trait LunaNodeResponse: DeserializeOwned {} +/// A trait that must be implemented by any request type. +/// This can be done with: +/// ```rust +/// // struct ResponseType {} +/// #[lunanode_request(response="ResponseType", endpoint="vm/list/")] +/// pub struct VmListRequest {} +/// ``` 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. @@ -44,53 +63,61 @@ pub trait LunaNodeRequest: Serialize + std::fmt::Debug { /// The only correct way: "vm/list/". fn url_endpoint(&self) -> String; /// A generic function to recieve a specific relsponse type from the server. - /// 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 { + /// 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 LunaNodeError::SerdeError(serde::Error, String), with the String being a raw response from the server. + /// You may also recieve any other error defined in LunaNodeError, including timezone errors due to not being able to create a nonce value for the request. + /// See LunaNodeError. + fn make_request(&self) -> Result { make_request::(self) } } #[derive(Serialize, Deserialize, Debug)] -pub struct LNErrorResponse { +/// This response is given if the request was successful (technically), but some error has occured +/// to make the request not possible from Lunanode's standpoint. +pub struct LunaNodeErrorResponse { #[serde(with="success")] success: bool, error: String, // proper type, full accoutnign of the error from the API } -impl std::fmt::Display for LNErrorResponse { +impl std::fmt::Display for LunaNodeErrorResponse { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An API error has occured! {}", self.error) } } -impl std::error::Error for LNErrorResponse {} +impl std::error::Error for LunaNodeErrorResponse {} #[derive(Debug)] -pub enum LNError { +/// A common error type for the entire crate. +pub enum LunaNodeError { + /// Pass through a request error from ureq. RequestError(ureq::Error), + /// Pass through a deserializeation error; this is just an std::io::Error that occurs when it is + /// not possible to read the string where deserialization would occur. DeserializationError(std::io::Error), + /// Pass through an error when finding the system time. TimeError(std::time::SystemTimeError), - LunaNodeError(LNErrorResponse), + /// An error from the Lunanode API + LunaNodeError(LunaNodeErrorResponse), + /// Pass through a serde error to the user. SerdeError(serde_json::Error, String), // the serde error, accompanied by a raw string - GetFuckedError, } -impl std::fmt::Display for LNError { +impl std::fmt::Display for LunaNodeError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "An error has occured! {}", self) } } -impl std::error::Error for LNError {} +impl std::error::Error for LunaNodeError {} -fn make_request(req: &S) -> Result +fn make_request(req: &S) -> Result where S: LunaNodeRequest + ?Sized, D: LunaNodeResponse { let json_req_data = match serde_json::to_string(req) { Ok(jrd) => jrd, - Err(e) => return Err(LNError::SerdeError(e, format!("{:?}", req))), + Err(e) => return Err(LunaNodeError::SerdeError(e, format!("{:?}", req))), }; let epoch = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { - Err(e) => return Err(LNError::TimeError(e)), + Err(e) => return Err(LunaNodeError::TimeError(e)), Ok(ue) => ue, }.as_secs(); let nonce = format!("{}", epoch); @@ -105,20 +132,20 @@ fn make_request(req: &S) -> Result ("signature", &signature), ("nonce", &nonce), ]) { - Err(e) => Err(LNError::RequestError(e)), + Err(e) => Err(LunaNodeError::RequestError(e)), Ok(resp) => { let resp_str = match resp.into_string() { - Err(e) => return Err(LNError::DeserializationError(e)), + Err(e) => return Err(LunaNodeError::DeserializationError(e)), Ok(s) => s, }; println!("RESP: {}", resp_str); - match serde_json::from_str::(&resp_str) { - Ok(e) => return Err(LNError::LunaNodeError(e)), + match serde_json::from_str::(&resp_str) { + Ok(e) => return Err(LunaNodeError::LunaNodeError(e)), Err(_e) => false, }; match serde_json::from_str::(&resp_str) { Ok(s) => Ok(s), - Err(e) => Err(LNError::SerdeError(e, resp_str)), + Err(e) => Err(LunaNodeError::SerdeError(e, resp_str)), } }, } From f9a66d6457aa2aeaf702ae385f986e8df561b618 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sat, 8 Oct 2022 13:20:30 -0600 Subject: [PATCH 03/17] Add release info to Cargo.toml --- Cargo.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f285ccd..f716ab0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,17 @@ name = "lunanode" version = "0.1.0" edition = "2021" +authors = ["Tait Hoyem "] +description = "Tired of using a web interface for your VPS, Email, and domain provider? Check out this tool! It's a CLI tool for Lunanode: A Canadian VPS hosting company." +license = "agpl-3-only" +readme = "README.md" +repository = "https://git.tait.tech/tait/lunanode/" +keywords = ["vps", "cli", "email", "domain", "hosting", "api"] +categories = ["cli", "api"] + +[package.metadata.release] +release = true +publish = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 348fda54be09acff091953b1ed5386fbcd684661 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sat, 8 Oct 2022 13:20:39 -0600 Subject: [PATCH 04/17] Update readme with new TODOs --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d40c9a2..394d6c1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This is an API to work with the LunaNode's OpenStack-compatible [citation needed ## API Keys -Add the `LUNANODE_API_KEY` and `LUNANODE_KEY_ID` to your environment variables. +Add the `LUNANODE_API_KEY`, `LUNANODE_KEY_ID` and `LUNANODE_API_PARTIALKEY` (the first 64 chars of `LUNANODE_API_KEY`) to your environment variables. ## Usage @@ -33,9 +33,10 @@ See section help for more details, i.e.: `lunanode image help` ## TODO (looking for contributors) -* [ ] Simplify code so there is less boilerplate. In particular, writing out every enum variant is pretty annoying when I need the same info out of them either way. +* [ ] Create more detailed documentation. `#[deny(missing_docs)]` is on, but some of the decisions in the code aren't explained very well. * [ ] Tests!!! I've written some, but this should be comprehensively tested. * [ ] Write tests that involve talking with a live server! This will enable breaking changes from LunaNode to be seen before they are messed with. + * [ ] Write a local test server for faster, more easily created edge-case testing. * [ ] Stricter typing. Certain attributes, even though they are recieved as Strings, should really be some kind of `enum`. Make every non-String type some kind of enum. * [ ] UUIDs should be UUID types and not string. The only library I could find to do this, Uuid, seems to serialize them without the dashes, which screws up Lunanode... annoyingly. * [ ] Subnets should be more strictly types. They should always be an IP/subnet combo as two fields (std::net::Ipv4Addr, i32(0..32)). From 15f288a73250d976cf9ea053ea17bfe0c539c375 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sat, 8 Oct 2022 13:28:51 -0600 Subject: [PATCH 05/17] Start work on documenting supported and unsupported use cases. --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 394d6c1..c0c1f7e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,31 @@ # `lunanode` This is an API to work with the LunaNode's OpenStack-compatible [citation needed] API. +This crate currently covers only the most basic functionality from the Lunanode API. Currently 8/21 use cases. + +* VMs [8/21] + * List [X] + * Start [X] + * Stop [X] + * Reboot [X] + * Diskswap [ ] + * Rescue [ ] + * Shelve [ ] + * Unshelve [ ] + * Delete [ ] + * Info [X] + * Reimage [ ] + * Resize [ ] + * VNC [ ] + * IP [ ] + * Floaitng IP Add [ ] + * Floating IP Delete [ ] + * IP List [X] + * IP Add [X] + * IP Delete [X] + * Security group Add [ ] + * Security group Delete [ ] +* TODO: write more of the endpoints. ## Installation @@ -34,6 +59,7 @@ See section help for more details, i.e.: `lunanode image help` ## TODO (looking for contributors) * [ ] Create more detailed documentation. `#[deny(missing_docs)]` is on, but some of the decisions in the code aren't explained very well. +* [ ] Cover EVERY API endpoint. * [ ] Tests!!! I've written some, but this should be comprehensively tested. * [ ] Write tests that involve talking with a live server! This will enable breaking changes from LunaNode to be seen before they are messed with. * [ ] Write a local test server for faster, more easily created edge-case testing. From fec6e6b92745b164e71f04a72e43cb5260266552 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:43:12 -0600 Subject: [PATCH 06/17] Add limitations, additional use cases and TODOs. --- README.md | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c0c1f7e..fb3ebbc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # `lunanode` This is an API to work with the LunaNode's OpenStack-compatible [citation needed] API. -This crate currently covers only the most basic functionality from the Lunanode API. Currently 8/21 use cases. +This crate currently covers only the most basic functionality from the Lunanode API. Currently 33/98 (34%) API calls. +If you'd like to contribute additional functionality, please feel free to make an MR. -* VMs [8/21] +* VM [8/21] * List [X] * Start [X] * Stop [X] @@ -17,7 +18,7 @@ This crate currently covers only the most basic functionality from the Lunanode * Reimage [ ] * Resize [ ] * VNC [ ] - * IP [ ] + * IP [1/3] * Floaitng IP Add [ ] * Floating IP Delete [ ] * IP List [X] @@ -25,7 +26,106 @@ This crate currently covers only the most basic functionality from the Lunanode * IP Delete [X] * Security group Add [ ] * Security group Delete [ ] -* TODO: write more of the endpoints. +* Image [4/7] + * Fetch [ ] + * List [X] + * Details [X] + * Delete [X] + * Replicate [X] + * Rename [ ] + * Retrieve [ ] +* Volume [1/12] + * Create [ ] + * List [X] + * Info [ ] + * Attach [ ] + * Detach [ ] + * Extend [ ] + * Rename [ ] + * Delete [ ] + * Snapshot [0/4] + * Create [ ] + * List [ ] + * Replicate [ ] + * Delete [ ] +* Floating IP [1/3] + * List [X] + * Add [ ] + * Delete [ ] +* Network [1/3] + * List [X] + * Add [ ] + * Delete [ ] +* Security Group [0/7] + * List [ ] + * Create [ ] + * Delete [ ] + * Rename [ ] + * Rule List [ ] + * Rule Insert [ ] + * Rule Delete [ ] +* Script [0/5] + * List [ ] + * Get [ ] + * Create [ ] + * Update [ ] + * Delete [ ] +* SSH Key [1/3] + * List [X] + * Add [ ] + * Remove [ ] +* Plan [1/1] + * List [X] +* Region [1/1] + * List [X] +* Monitor [3/10] + * Check [2/4] + * List [X] + * Types [X] + * Add [ ] + * Remove [ ] + * Contact [1/4] + * List [X] + * Add [ ] + * Remove [ ] + * Alert [0/3] + * List [ ] + * Add [ ] + * Remove [ ] +* Email [6/14] + * Usage [X] + * Domain [1/5] + * List [X] + * Add [ ] + * Remove [ ] + * DKIM [0/2] + * Set [ ] + * Unset [ ] + * User [4/4] + * List [X] + * Add [X] + * Remove [X] + * Set Password [X] + * Alias [0/3] + * List [ ] + * Add [ ] + * Remove [ ] +* DNS [6/10] + * Zone [2/3] + * List [X] + * Add [X] + * Remove [ ] + * Record [1/3] + * List [X] + * Add [ ] + * Remove [ ] + * Dyn [3/4] + * List [X] + * Add [X] + * Update [ ] + * Remove [X] +* Billing [1/1] + * Credit [X] ## Installation @@ -67,6 +167,8 @@ See section help for more details, i.e.: `lunanode image help` * [ ] UUIDs should be UUID types and not string. The only library I could find to do this, Uuid, seems to serialize them without the dashes, which screws up Lunanode... annoyingly. * [ ] Subnets should be more strictly types. They should always be an IP/subnet combo as two fields (std::net::Ipv4Addr, i32(0..32)). * [ ] Automatically create IDs from names, or other identifiying info instead of using the RESP API's id system. + * For example: `email user add tait@tait.tech` should automatically fetch the domain ID required to make this call normally: `email user add DOMAIN_ID tait@tait.tech`. + * Another example: `vm shutdown my_webserver` would automatically exapnd to find the VM with the name my_webserver and have it shutdown expanding the name into the UUID: `vm shutdown ffff-ffff-ffff-ffffffffffffffff`. ## Support This Project From 697989c7ba19faae1ae1fae010cecb59732b90c2 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:47:27 -0600 Subject: [PATCH 07/17] Add publish options --- lunanode_macros/Cargo.toml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lunanode_macros/Cargo.toml b/lunanode_macros/Cargo.toml index 786b805..92f87f9 100644 --- a/lunanode_macros/Cargo.toml +++ b/lunanode_macros/Cargo.toml @@ -2,8 +2,17 @@ name = "lunanode_macros" version = "0.1.0" edition = "2021" +authors = ["Tait Hoyem "] +description = "Macros to assist in creation of the lunanode crate." +license = "agpl-3-only" +readme = "REAMDE.md" +repository = "https://git.tait.tech/tait/lunanode/" +keywords = ["macros", "helper"] +categories = ["proc_macro", "helper"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[package.metadata.release] +release = true +publish = true [lib] proc_macro = true From f81c26e9a0bcdab931bf263c61cde8ff4e730214 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:50:53 -0600 Subject: [PATCH 08/17] Fix syn dep features --- lunanode_macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lunanode_macros/Cargo.toml b/lunanode_macros/Cargo.toml index 92f87f9..5c3e30b 100644 --- a/lunanode_macros/Cargo.toml +++ b/lunanode_macros/Cargo.toml @@ -18,5 +18,5 @@ publish = true proc_macro = true [dependencies] -syn = "1.0" +syn = { version = "1.0.102", features = ["full"] } quote = "1.0" From cdb96eb69553f4ceb402acaa5dfbd9e5f5226f9a Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:51:16 -0600 Subject: [PATCH 09/17] Remove unused derives --- lunanode_macros/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lunanode_macros/src/lib.rs b/lunanode_macros/src/lib.rs index e53f1ee..aa14627 100644 --- a/lunanode_macros/src/lib.rs +++ b/lunanode_macros/src/lib.rs @@ -3,7 +3,7 @@ /// Usages: /// ```rust /// #[lunanode_request(response="ImageListResponse", endpoint="image/list/")] -/// #[derive(Serialize, Deserialize, Debug, ...)] +/// #[derive(Serialize, Deserialize, Debug)] /// struct MyStruct { /// ... /// ``` @@ -15,7 +15,6 @@ use quote::quote; use syn::parse::Parser; use syn::{parse_macro_input, AttributeArgs, ItemStruct, Lit, NestedMeta, Meta, MetaNameValue, Type}; -#[derive(Debug, Hash, Eq, PartialEq)] enum LunanodeRequestParam { Invalid, Response(Type), From 1a863f7d7bb3c49206d39543ba5ecc000804dd44 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:53:01 -0600 Subject: [PATCH 10/17] Add readme --- lunanode_macros/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lunanode_macros/README.md diff --git a/lunanode_macros/README.md b/lunanode_macros/README.md new file mode 100644 index 0000000..3b5262e --- /dev/null +++ b/lunanode_macros/README.md @@ -0,0 +1,3 @@ +# `lunanode_macros` + +This crate contains macros for help with the [`lunanode` crate](https://crates.io/crates/lunanode). From c2958c0a01f2a701afe3915c1a0bdbc7455876db Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:53:40 -0600 Subject: [PATCH 11/17] Fix readme filename --- lunanode_macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lunanode_macros/Cargo.toml b/lunanode_macros/Cargo.toml index 5c3e30b..2cd6ed2 100644 --- a/lunanode_macros/Cargo.toml +++ b/lunanode_macros/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" authors = ["Tait Hoyem "] description = "Macros to assist in creation of the lunanode crate." license = "agpl-3-only" -readme = "REAMDE.md" +readme = "README.md" repository = "https://git.tait.tech/tait/lunanode/" keywords = ["macros", "helper"] categories = ["proc_macro", "helper"] From aa7314f8d7e81f675182fe90c62637efa4c9a21a Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:54:33 -0600 Subject: [PATCH 12/17] Fix license name --- lunanode_macros/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lunanode_macros/Cargo.toml b/lunanode_macros/Cargo.toml index 2cd6ed2..116d1d3 100644 --- a/lunanode_macros/Cargo.toml +++ b/lunanode_macros/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" authors = ["Tait Hoyem "] description = "Macros to assist in creation of the lunanode crate." -license = "agpl-3-only" +license = "AGPL-3.0-only" readme = "README.md" repository = "https://git.tait.tech/tait/lunanode/" keywords = ["macros", "helper"] From 247917f54c1b60b25e5a6a9d327aea976a4f423e Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:55:08 -0600 Subject: [PATCH 13/17] Remove warning categories --- lunanode_macros/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/lunanode_macros/Cargo.toml b/lunanode_macros/Cargo.toml index 116d1d3..86fdff4 100644 --- a/lunanode_macros/Cargo.toml +++ b/lunanode_macros/Cargo.toml @@ -8,7 +8,6 @@ license = "AGPL-3.0-only" readme = "README.md" repository = "https://git.tait.tech/tait/lunanode/" keywords = ["macros", "helper"] -categories = ["proc_macro", "helper"] [package.metadata.release] release = true From 40a6a26e9ad4eb5e2e3496d9fc2206a236aba4d8 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:55:46 -0600 Subject: [PATCH 14/17] Use version for macro crate --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f716ab0..28863ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,6 @@ categories = ["cli", "api"] release = true publish = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] clap = { version = "4.0.7", features = ["env", "derive"] } chrono = { version = "0.4.22", features = ["serde"] } @@ -23,7 +21,7 @@ envy = "0.4.2" getset = "0.1.2" hex = "0.4.3" hmac = "0.12.1" -lunanode_macros = { path = "./lunanode_macros" } +lunanode_macros = "0.1.0" parse-display-derive = "0.6.0" sha2 = "0.10.5" serde = { version = "1.0.0", features = ["derive"] } From 2bf17db87ee7bdd4ca7d7a0ebf7985a651a6edc5 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:57:48 -0600 Subject: [PATCH 15/17] Remove a keyword --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 28863ef..562e8ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ description = "Tired of using a web interface for your VPS, Email, and domain pr license = "agpl-3-only" readme = "README.md" repository = "https://git.tait.tech/tait/lunanode/" -keywords = ["vps", "cli", "email", "domain", "hosting", "api"] +keywords = ["vps", "cli", "email", "domain", "hosting"] categories = ["cli", "api"] [package.metadata.release] From e62ba9a0b632aad035db017dba2965002c9d7e2b Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 08:58:57 -0600 Subject: [PATCH 16/17] Fix license name --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 562e8ab..b17cc84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" authors = ["Tait Hoyem "] description = "Tired of using a web interface for your VPS, Email, and domain provider? Check out this tool! It's a CLI tool for Lunanode: A Canadian VPS hosting company." -license = "agpl-3-only" +license = "AGPL-3.0-only" readme = "README.md" repository = "https://git.tait.tech/tait/lunanode/" keywords = ["vps", "cli", "email", "domain", "hosting"] From b56e61d2df06cdfb04ab6a780c96031797138215 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sun, 9 Oct 2022 09:01:00 -0600 Subject: [PATCH 17/17] Remove warning categories --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b17cc84..e8abd2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ license = "AGPL-3.0-only" readme = "README.md" repository = "https://git.tait.tech/tait/lunanode/" keywords = ["vps", "cli", "email", "domain", "hosting"] -categories = ["cli", "api"] [package.metadata.release] release = true