Skip to content

Commit

Permalink
Merge pull request #46 from kate-goldenring/create-default-sql-database
Browse files Browse the repository at this point in the history
`sqlite` commands
  • Loading branch information
kate-goldenring committed Jul 21, 2023
2 parents 8ad1179 + 59bc0be commit db5ba96
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 55 deletions.
40 changes: 39 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ bindle = { git = "https://github.com/fermyon/bindle", tag = "v0.8.2", default-fe
chrono = "0.4"
clap = { version = "3.2.24", features = ["derive", "env"] }
cloud = { path = "crates/cloud" }
cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi", rev = "98e7bd2ca97eab25c88dae5d6c65609577d36992" }
cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi", rev = "dbf4657ef4e70433aea17210a539202443e2ce96" }
dirs = "5.0"
dialoguer = "0.10"
tokio = { version = "1.23", features = ["full"] }
tracing = { workspace = true }
rand = "0.8"
Expand Down
2 changes: 1 addition & 1 deletion crates/cloud/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = { workspace = true }

[dependencies]
anyhow = "1.0"
cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi", rev = "98e7bd2ca97eab25c88dae5d6c65609577d36992" }
cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi", rev = "dbf4657ef4e70433aea17210a539202443e2ce96" }
mime_guess = { version = "2.0" }
reqwest = { version = "0.11", features = ["stream"] }
semver = "1.0"
Expand Down
54 changes: 48 additions & 6 deletions crates/cloud/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use cloud_openapi::{
device_codes_api::api_device_codes_post,
key_value_pairs_api::api_key_value_pairs_post,
revisions_api::{api_revisions_get, api_revisions_post},
sql_databases_api::{
api_sql_databases_delete, api_sql_databases_get, api_sql_databases_post,
},
variable_pairs_api::{
api_variable_pairs_delete, api_variable_pairs_get, api_variable_pairs_post,
},
Expand All @@ -20,9 +23,10 @@ use cloud_openapi::{
models::{
AppItemPage, ChannelItem, ChannelItemPage, ChannelRevisionSelectionStrategy,
CreateAppCommand, CreateChannelCommand, CreateDeviceCodeCommand, CreateKeyValuePairCommand,
CreateVariablePairCommand, DeleteVariablePairCommand, DeviceCodeItem, GetChannelLogsVm,
GetVariablesQuery, RefreshTokenCommand, RegisterRevisionCommand, RevisionItemPage,
TokenInfo, UpdateEnvironmentVariableDto,
CreateSqlDatabaseCommand, CreateVariablePairCommand, Database, DeleteSqlDatabaseCommand,
DeleteVariablePairCommand, DeviceCodeItem, EnvironmentVariableItem, GetChannelLogsVm,
GetSqlDatabasesQuery, GetVariablesQuery, RefreshTokenCommand, RegisterRevisionCommand,
RevisionItemPage, TokenInfo,
},
};
use reqwest::header;
Expand Down Expand Up @@ -125,12 +129,18 @@ impl Client {
.map_err(format_response_error)
}

pub async fn add_app(&self, name: &str, storage_id: &str) -> Result<Uuid> {
pub async fn add_app(
&self,
name: &str,
storage_id: &str,
create_default_database: bool,
) -> Result<Uuid> {
api_apps_post(
&self.configuration,
CreateAppCommand {
name: name.to_string(),
storage_id: storage_id.to_string(),
create_default_database: Some(create_default_database),
},
None,
)
Expand Down Expand Up @@ -211,7 +221,7 @@ impl Client {
revision_selection_strategy: Option<ChannelRevisionSelectionStrategy>,
range_rule: Option<String>,
active_revision_id: Option<Uuid>,
environment_variables: Option<Vec<UpdateEnvironmentVariableDto>>,
environment_variables: Option<Vec<EnvironmentVariableItem>>,
) -> anyhow::Result<()> {
let patch_channel_command = PatchChannelCommand {
channel_id: Some(id),
Expand Down Expand Up @@ -374,6 +384,38 @@ impl Client {
.map_err(format_response_error)?;
Ok(list.vars)
}

pub async fn create_database(&self, app_id: Option<Uuid>, name: String) -> anyhow::Result<()> {
api_sql_databases_post(
&self.configuration,
CreateSqlDatabaseCommand {
name,
app_id: Some(app_id),
},
None,
)
.await
.map_err(format_response_error)
}

pub async fn delete_database(&self, name: String) -> anyhow::Result<()> {
api_sql_databases_delete(&self.configuration, DeleteSqlDatabaseCommand { name }, None)
.await
.map_err(format_response_error)
}

pub async fn get_databases(&self, app_id: Option<Uuid>) -> anyhow::Result<Vec<Database>> {
let list = api_sql_databases_get(
&self.configuration,
GetSqlDatabasesQuery {
app_id: Some(app_id),
},
None,
)
.await
.map_err(format_response_error)?;
Ok(list.databases)
}
}

#[derive(Deserialize, Debug)]
Expand Down Expand Up @@ -414,7 +456,7 @@ pub struct PatchChannelCommand {
rename = "environmentVariables",
skip_serializing_if = "Option::is_none"
)]
pub environment_variables: Option<Vec<UpdateEnvironmentVariableDto>>,
pub environment_variables: Option<Vec<EnvironmentVariableItem>>,
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(
Expand Down
54 changes: 46 additions & 8 deletions src/commands/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use chrono::{DateTime, Utc};
use clap::Parser;
use cloud::client::{Client as CloudClient, ConnectionConfig};
use cloud_openapi::models::ChannelRevisionSelectionStrategy as CloudChannelRevisionSelectionStrategy;
use cloud_openapi::models::Database;
use rand::Rng;
use semver::BuildMetadata;
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -42,6 +43,7 @@ use crate::{

const SPIN_DEPLOY_CHANNEL_NAME: &str = "spin-deploy";
const SPIN_DEFAULT_KV_STORE: &str = "default";
const SPIN_DEFAULT_DATABASE: &str = "default";
const BINDLE_REGISTRY_URL_PATH: &str = "api/registry";

/// Package and upload an application to the Fermyon Cloud.
Expand Down Expand Up @@ -183,6 +185,9 @@ impl DeployCommand {
// via only `add_revision` if bindle naming schema is updated so bindles can be deterministically ordered by Cloud.
let channel_id = match self.get_app_id_cloud(&client, name.clone()).await {
Ok(app_id) => {
if uses_default_db(&cfg) {
create_default_database_if_does_not_exist(&name, app_id, &client).await?;
}
CloudClient::add_revision(
&client,
name.clone(),
Expand Down Expand Up @@ -224,7 +229,8 @@ impl DeployCommand {
existing_channel_id
}
Err(_) => {
let app_id = CloudClient::add_app(&client, &name, &name)
let create_default_db = uses_default_db(&cfg);
let app_id = CloudClient::add_app(&client, &name, &name, create_default_db)
.await
.context("Unable to create app")?;

Expand Down Expand Up @@ -527,20 +533,52 @@ fn validate_cloud_app(app: &RawAppManifest) -> Result<()> {
.key_value_stores
.iter()
.flatten()
.find(|store| *store != "default")
.find(|store| *store != SPIN_DEFAULT_KV_STORE)
{
bail!("Invalid store {invalid_store:?} for component {:?}. Cloud currently supports only the 'default' store.", component.id);
}

if let Some(dbs) = component.wasm.sqlite_databases.as_ref() {
if !dbs.is_empty() {
bail!("Component {:?} uses SQLite database storage, which is not yet supported in Cloud.", component.id);
}
if let Some(invalid_db) = component
.wasm
.sqlite_databases
.iter()
.flatten()
.find(|db| *db != SPIN_DEFAULT_DATABASE)
{
bail!("Invalid database {invalid_db:?} for component {:?}. Cloud currently supports only the 'default' SQLite databases.", component.id);
}
}
Ok(())
}

async fn create_default_database_if_does_not_exist(
app_name: &str,
app_id: Uuid,
client: &CloudClient,
) -> Result<()> {
let default_db = client
.get_databases(Some(app_id))
.await?
.into_iter()
.find(|d| d.default);

if default_db.is_none() {
client
.create_database(Some(app_id), SPIN_DEFAULT_DATABASE.to_string())
.await?;
}
Ok(())
}

fn uses_default_db(cfg: &config::RawAppManifestImpl<TriggerConfig>) -> bool {
cfg.components
.iter()
.cloned()
.filter_map(|c| c.wasm.sqlite_databases)
.flatten()
.any(|db| db == SPIN_DEFAULT_DATABASE)
}

fn random_buildinfo() -> BuildMetadata {
let random_bytes: [u8; 4] = rand::thread_rng().gen();
let random_hex: String = random_bytes.iter().map(|b| format!("{:x}", b)).collect();
Expand Down Expand Up @@ -807,9 +845,9 @@ pub async fn login_connection(deployment_env_id: Option<&str>) -> Result<LoginCo
Ok(login_connection)
}

pub async fn get_app_id_cloud(cloud_client: &CloudClient, name: String) -> Result<Uuid> {
pub async fn get_app_id_cloud(cloud_client: &CloudClient, name: &str) -> Result<Uuid> {
let apps_vm = CloudClient::list_apps(cloud_client).await?;
let app = apps_vm.items.iter().find(|&x| x.name == name.clone());
let app = apps_vm.items.iter().find(|&x| x.name == name);
match app {
Some(a) => Ok(a.id),
None => bail!("No app with name: {}", name),
Expand Down
15 changes: 15 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
pub mod deploy;
pub mod login;
pub mod sqlite;
pub mod variables;

use crate::commands::deploy::login_connection;
use anyhow::Result;
use cloud::client::{Client as CloudClient, ConnectionConfig};

pub(crate) async fn create_cloud_client(deployment_env_id: Option<&str>) -> Result<CloudClient> {
let login_connection = login_connection(deployment_env_id).await?;
let connection_config = ConnectionConfig {
url: login_connection.url.to_string(),
insecure: login_connection.danger_accept_invalid_certs,
token: login_connection.token,
};
Ok(CloudClient::new(connection_config))
}
Loading

0 comments on commit db5ba96

Please sign in to comment.