Maintain OrderState within Order type

This commit is contained in:
Dirkjan Ochtman 2023-03-15 16:26:52 +01:00
parent 652c4815ec
commit a2d4129201
2 changed files with 53 additions and 37 deletions

View File

@ -34,19 +34,20 @@ async fn main() -> anyhow::Result<()> {
// process multiple orders in parallel for a single account. // process multiple orders in parallel for a single account.
let identifier = Identifier::Dns(opts.name); let identifier = Identifier::Dns(opts.name);
let (mut order, state) = account let mut order = account
.new_order(&NewOrder { .new_order(&NewOrder {
identifiers: &[identifier], identifiers: &[identifier],
}) })
.await .await
.unwrap(); .unwrap();
let state = order.state();
info!("order state: {:#?}", state); info!("order state: {:#?}", state);
assert!(matches!(state.status, OrderStatus::Pending)); assert!(matches!(state.status, OrderStatus::Pending));
// Pick the desired challenge type and prepare the response. // Pick the desired challenge type and prepare the response.
let authorizations = order.authorizations(&state.authorizations).await.unwrap(); let authorizations = order.authorizations().await.unwrap();
let mut challenges = Vec::with_capacity(authorizations.len()); let mut challenges = Vec::with_capacity(authorizations.len());
for authz in &authorizations { for authz in &authorizations {
match authz.status { match authz.status {
@ -87,12 +88,12 @@ async fn main() -> anyhow::Result<()> {
let mut tries = 1u8; let mut tries = 1u8;
let mut delay = Duration::from_millis(250); let mut delay = Duration::from_millis(250);
let state = loop { loop {
sleep(delay).await; sleep(delay).await;
let state = order.state().await.unwrap(); let state = order.refresh().await.unwrap();
if let OrderStatus::Ready | OrderStatus::Invalid = state.status { if let OrderStatus::Ready | OrderStatus::Invalid = state.status {
info!("order state: {:#?}", state); info!("order state: {:#?}", state);
break state; break;
} }
delay *= 2; delay *= 2;
@ -104,10 +105,14 @@ async fn main() -> anyhow::Result<()> {
return Err(anyhow::anyhow!("order is not ready")); return Err(anyhow::anyhow!("order is not ready"));
} }
} }
}; }
if state.status == OrderStatus::Invalid { let state = order.state();
return Err(anyhow::anyhow!("order is invalid")); if state.status != OrderStatus::Ready {
return Err(anyhow::anyhow!(
"unexpected order status: {:?}",
state.status
));
} }
let mut names = Vec::with_capacity(challenges.len()); let mut names = Vec::with_capacity(challenges.len());
@ -125,7 +130,7 @@ async fn main() -> anyhow::Result<()> {
// Finalize the order and print certificate chain, private key and account credentials. // Finalize the order and print certificate chain, private key and account credentials.
let cert_chain_pem = order.finalize(&csr, &state.finalize).await.unwrap(); let cert_chain_pem = order.finalize(&csr).await.unwrap();
info!("certficate chain:\n\n{}", cert_chain_pem,); info!("certficate chain:\n\n{}", cert_chain_pem,);
info!("private key:\n\n{}", cert.serialize_private_key_pem()); info!("private key:\n\n{}", cert.serialize_private_key_pem());
info!( info!(

View File

@ -36,7 +36,8 @@ use types::{
pub struct Order { pub struct Order {
account: Arc<AccountInner>, account: Arc<AccountInner>,
nonce: Option<String>, nonce: Option<String>,
order_url: String, url: String,
state: OrderState,
} }
impl Order { impl Order {
@ -55,12 +56,9 @@ impl Order {
/// After the challenges have been set up, check the [`Order::state()`] to see /// After the challenges have been set up, check the [`Order::state()`] to see
/// if the order is ready to be finalized (or becomes invalid). Once it is /// if the order is ready to be finalized (or becomes invalid). Once it is
/// ready, call `Order::finalize()` to get the certificate. /// ready, call `Order::finalize()` to get the certificate.
pub async fn authorizations( pub async fn authorizations(&mut self) -> Result<Vec<Authorization>, Error> {
&mut self, let mut authorizations = Vec::with_capacity(self.state.authorizations.len());
authz_urls: &[String], for url in &self.state.authorizations {
) -> Result<Vec<Authorization>, Error> {
let mut authorizations = Vec::with_capacity(authz_urls.len());
for url in authz_urls {
authorizations.push(self.account.get(&mut self.nonce, url).await?); authorizations.push(self.account.get(&mut self.nonce, url).await?);
} }
Ok(authorizations) Ok(authorizations)
@ -77,15 +75,15 @@ impl Order {
/// Request a certificate from the given Certificate Signing Request (CSR) /// Request a certificate from the given Certificate Signing Request (CSR)
/// ///
/// Creating a CSR is outside of the scope of instant-acme. Make sure you pass in a /// Creating a CSR is outside of the scope of instant-acme. Make sure you pass in a
/// DER representation of the CSR in `csr_der` and the [`OrderState::finalize`] URL /// DER representation of the CSR in `csr_der`. The resulting `String` will contain the
/// in `finalize_url`. The resulting `String` will contain the PEM-encoded certificate chain. /// PEM-encoded certificate chain.
pub async fn finalize(&mut self, csr_der: &[u8], finalize_url: &str) -> Result<String, Error> { pub async fn finalize(&mut self, csr_der: &[u8]) -> Result<String, Error> {
let rsp = self let rsp = self
.account .account
.post( .post(
Some(&FinalizeRequest::new(csr_der)), Some(&FinalizeRequest::new(csr_der)),
self.nonce.take(), self.nonce.take(),
finalize_url, &self.state.finalize,
) )
.await?; .await?;
@ -129,9 +127,28 @@ impl Order {
self.account.get(&mut self.nonce, challenge_url).await self.account.get(&mut self.nonce, challenge_url).await
} }
/// Get the current state of the order /// Refresh the current state of the order
pub async fn state(&mut self) -> Result<OrderState, Error> { pub async fn refresh(&mut self) -> Result<&OrderState, Error> {
self.account.get(&mut self.nonce, &self.order_url).await let rsp = self
.account
.post(None::<&Empty>, self.nonce.take(), &self.url)
.await?;
self.nonce = nonce_from_response(&rsp);
self.state = Problem::check::<OrderState>(rsp).await?;
Ok(&self.state)
}
/// Get the last known state of the order
///
/// Call `refresh()` to get the latest state from the server.
pub fn state(&mut self) -> &OrderState {
&self.state
}
/// Get the URL of the order
pub fn url(&self) -> &str {
&self.url
} }
} }
@ -185,11 +202,8 @@ impl Account {
/// Create a new order based on the given [`NewOrder`] /// Create a new order based on the given [`NewOrder`]
/// ///
/// Returns both an [`Order`] instance and the initial [`OrderState`]. /// Returns an [`Order`] instance. Use the [`Order::state()`] method to inspect its state.
pub async fn new_order<'a>( pub async fn new_order<'a>(&'a self, order: &NewOrder<'_>) -> Result<Order, Error> {
&'a self,
order: &NewOrder<'_>,
) -> Result<(Order, OrderState), Error> {
let rsp = self let rsp = self
.inner .inner
.post(Some(order), None, &self.inner.client.urls.new_order) .post(Some(order), None, &self.inner.client.urls.new_order)
@ -202,15 +216,12 @@ impl Account {
.and_then(|hv| hv.to_str().ok()) .and_then(|hv| hv.to_str().ok())
.map(|s| s.to_owned()); .map(|s| s.to_owned());
let status = Problem::check(rsp).await?; Ok(Order {
Ok(( account: self.inner.clone(),
Order { nonce,
account: self.inner.clone(), url: order_url.ok_or("no order URL found")?,
nonce, state: Problem::check::<OrderState>(rsp).await?,
order_url: order_url.ok_or("no order URL found")?, })
},
status,
))
} }
/// Get the account's credentials, which can be serialized /// Get the account's credentials, which can be serialized