Skip to content

Commit

Permalink
Merge pull request MaterializeInc#21871 from MaterializeInc/roshan/re…
Browse files Browse the repository at this point in the history
…quest-headers

Update MZ CLI to use new Region API version 1 semantics
  • Loading branch information
rjobanp authored Sep 25, 2023
2 parents 2a9eee0 + 5034fae commit 630c7c9
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 28 deletions.
28 changes: 23 additions & 5 deletions src/cloud-api/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
//! Frontegg client is used to request and manage the access token.
use std::sync::Arc;

use reqwest::{Method, RequestBuilder, StatusCode, Url};
use reqwest::{header::HeaderMap, Method, RequestBuilder, StatusCode, Url};
use serde::de::DeserializeOwned;
use serde::Deserialize;

use crate::config::API_VERSION_HEADER;
use crate::error::{ApiError, Error};

use self::cloud_provider::CloudProvider;
Expand Down Expand Up @@ -57,26 +58,39 @@ impl Client {
P: IntoIterator,
P::Item: AsRef<str>,
{
self.build_request(method, path, self.endpoint.clone())
self.build_request(method, path, self.endpoint.clone(), None)
.await
}

/// Builds a request towards the `Client`'s endpoint
/// The function requires a [CloudProvider] as parameter
/// since it contains the api url (Region API url)
/// to interact with the region.
/// Specify an api_version corresponding to the request/response
/// schema your code will handle. Refer to the Region API docs
/// for schema information.
async fn build_region_request<P>(
&self,
method: Method,
path: P,
cloud_provider: &CloudProvider,
api_version: Option<u16>,
) -> Result<RequestBuilder, Error>
where
P: IntoIterator,
P::Item: AsRef<str>,
{
self.build_request(method, path, cloud_provider.url.clone())
.await
self.build_request(
method,
path,
cloud_provider.url.clone(),
api_version.and_then(|api_ver| {
let mut headers = HeaderMap::with_capacity(1);
headers.insert(API_VERSION_HEADER, api_ver.into());
Some(headers)
}),
)
.await
}

/// Builds a request towards the `Client`'s endpoint
Expand All @@ -85,6 +99,7 @@ impl Client {
method: Method,
path: P,
mut domain: Url,
headers: Option<HeaderMap>,
) -> Result<RequestBuilder, Error>
where
P: IntoIterator,
Expand All @@ -96,7 +111,10 @@ impl Client {
.clear()
.extend(path);

let req = self.inner.request(method, domain);
let mut req = self.inner.request(method, domain);
if let Some(header_map) = headers {
req = req.headers(header_map);
}
let token = self.auth_client.auth().await?;

Ok(req.bearer_auth(token))
Expand Down
41 changes: 35 additions & 6 deletions src/cloud-api/src/client/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ use crate::client::{Client, Error};
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Region {
/// The connection info and metadata corresponding to this Region
/// may not be set if the region is in the process
/// of being created
/// of being created (see [RegionState] for details)
pub region_info: Option<RegionInfo>,

/// The state of this Region
pub region_state: RegionState,
}

/// Connection details for an active region
Expand All @@ -51,16 +55,41 @@ pub struct RegionInfo {
pub enabled_at: Option<DateTime<Utc>>,
}

/// The state of a customer region
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum RegionState {
/// Enabled region
Enabled,

/// Enablement Pending
/// [region_info][Region::region_info] field will be `null` while the region is in this state
EnablementPending,

/// Deletion Pending
/// [region_info][Region::region_info] field will be `null` while the region is in this state
DeletionPending,

/// Soft deleted; Pending hard deletion
/// [region_info][Region::region_info] field will be `null` while the region is in this state
SoftDeleted,
}

impl Client {
/// Get a customer region in a partciular cloud region for the current user.
pub async fn get_region(&self, provider: CloudProvider) -> Result<Region, Error> {
// Send request to the subdomain
let req = self
.build_region_request(Method::GET, ["api", "region"], &provider)
.build_region_request(Method::GET, ["api", "region"], &provider, Some(1))
.await?;

match self.send_request(req).await {
Ok(region) => Ok(region),
match self.send_request::<Region>(req).await {
Ok(region) => match region.region_state {
RegionState::SoftDeleted => Err(Error::EmptyRegion),
RegionState::DeletionPending => Err(Error::EmptyRegion),
RegionState::Enabled => Ok(region),
RegionState::EnablementPending => Ok(region),
},
Err(Error::SuccesfullButNoContent) => Err(Error::EmptyRegion),
Err(e) => Err(e),
}
Expand Down Expand Up @@ -110,7 +139,7 @@ impl Client {
};

let req = self
.build_region_request(Method::PATCH, ["api", "region"], &cloud_provider)
.build_region_request(Method::PATCH, ["api", "region"], &cloud_provider, Some(1))
.await?;
let req = req.json(&body);
// Creating a region can take some time
Expand Down Expand Up @@ -149,7 +178,7 @@ impl Client {
loops += 1;

let req = self
.build_region_request(Method::DELETE, ["api", "region"], &cloud_provider)
.build_region_request(Method::DELETE, ["api", "region"], &cloud_provider, Some(1))
.await?;

// This timeout corresponds to the same in our cloud services tests.
Expand Down
3 changes: 3 additions & 0 deletions src/cloud-api/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ use crate::client::Client;
pub static DEFAULT_ENDPOINT: Lazy<Url> =
Lazy::new(|| "https://api.cloud.materialize.com".parse().unwrap());

/// The header used by the Region API to specify which API version this client supports
pub static API_VERSION_HEADER: &str = "X-Materialize-Api-Version";

/// Configures the required parameters of a [`Client`].
pub struct ClientConfig {
/// The authorization client to get the authorization token.
Expand Down
1 change: 0 additions & 1 deletion src/mz/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

All notable changes to the `mz` CLI will be documented in this file.


## [0.2.1] - 2023-09-07

This version only implements changes in the release process.
Expand Down
2 changes: 1 addition & 1 deletion src/mz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Field | Type | Description
`app-password` | string | *Secret.* The app password to use for this profile.
`region` | string | The default region to use for this profile.
`vault` | string | The vault to use for this profile. See [Global parameters](#global-parameters) above.
`api-endpoint` | string | *Internal use only.* The Materialize API endpoint to use.
`cloud-endpoint` | string | *Internal use only.* The Materialize API endpoint to use.
`admin-endpoint` | string | *Internal use only.* The Materialize administration endpoint to use.
Expand Down
42 changes: 27 additions & 15 deletions src/mz/src/command/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::time::Duration;

use crate::{context::RegionContext, error::Error};

use mz_cloud_api::client::cloud_provider::CloudProvider;
use mz_cloud_api::client::{cloud_provider::CloudProvider, region::RegionState};
use mz_ore::retry::Retry;
use serde::{Deserialize, Serialize};
use tabled::Tabled;
Expand Down Expand Up @@ -50,24 +50,36 @@ pub async fn enable(cx: RegionContext, version: Option<String>) -> Result<(), Er
.max_duration(Duration::from_secs(720))
.clamp_backoff(Duration::from_secs(1))
.retry_async(|_| async {
let region_info = cx.get_region().await?.region_info;
let region = cx.get_region().await?;

match region_info {
Some(region_info) => {
match region.region_state {
RegionState::EnablementPending => {
loading_spinner.set_message("Waiting for the region to be ready...");
if region_info.resolvable {
if cx
.sql_client()
.is_ready(&region_info, cx.admin_client().claims().await?.email)?
{
return Ok(());
Err(Error::NotReadyRegion)
}
RegionState::DeletionPending => Err(Error::CommandExecutionError(
"This region is pending deletion!".to_string(),
)),
RegionState::SoftDeleted => Err(Error::CommandExecutionError(
"This region has been marked soft-deleted!".to_string(),
)),
RegionState::Enabled => match region.region_info {
Some(region_info) => {
loading_spinner.set_message("Waiting for the region to be resolvable...");
if region_info.resolvable {
if cx
.sql_client()
.is_ready(&region_info, cx.admin_client().claims().await?.email)?
{
return Ok(());
}
Err(Error::NotPgReadyError)
} else {
Err(Error::NotResolvableRegion)
}
Err(Error::NotPgReadyError)
} else {
Err(Error::NotResolvableRegion)
}
}
None => Err(Error::NotReadyRegion),
None => Err(Error::NotReadyRegion),
},
}
})
.await
Expand Down

0 comments on commit 630c7c9

Please sign in to comment.