Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sozo): upload world/models/contracts metadata only if changed #2691

Merged
merged 8 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 2 additions & 0 deletions bin/sozo/src/commands/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use super::options::starknet::StarknetOptions;
use super::options::transaction::TransactionOptions;
use super::options::world::WorldOptions;
use crate::commands::options::ipfs::IpfsOptions;

#[derive(Debug, Args)]
pub struct DevArgs {
Expand Down Expand Up @@ -87,6 +88,7 @@
starknet: self.starknet,
account: self.account,
transaction: self.transaction,
ipfs: IpfsOptions::default(), // no need for IPFS metadata upload here.

Check warning on line 91 in bin/sozo/src/commands/dev.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/dev.rs#L91

Added line #L91 was not covered by tests
remybar marked this conversation as resolved.
Show resolved Hide resolved
};

remybar marked this conversation as resolved.
Show resolved Hide resolved
let _ = migrate_args.clone().run(config);
Expand Down
47 changes: 44 additions & 3 deletions bin/sozo/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use colored::Colorize;
use dojo_utils::{self, TxnConfig};
use dojo_world::contracts::WorldContract;
use dojo_world::metadata::IpfsMetadataService;
use scarb::core::{Config, Workspace};
use sozo_ops::migrate::{Migration, MigrationResult};
use sozo_ops::migration_ui::MigrationUi;
Expand All @@ -14,6 +15,7 @@
use tracing::trace;

use super::options::account::AccountOptions;
use super::options::ipfs::IpfsOptions;
use super::options::starknet::StarknetOptions;
use super::options::transaction::TransactionOptions;
use super::options::world::WorldOptions;
Expand All @@ -32,6 +34,9 @@

#[command(flatten)]
pub account: AccountOptions,

#[command(flatten)]
pub ipfs: IpfsOptions,
}

impl MigrateArgs {
Expand All @@ -43,7 +48,7 @@
ws.profile_check()?;
ws.ensure_profile_artifacts()?;

let MigrateArgs { world, starknet, account, .. } = self;
let MigrateArgs { world, starknet, account, ipfs, .. } = self;

Check warning on line 51 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L51

Added line #L51 was not covered by tests

config.tokio_handle().block_on(async {
print_banner(&ws, &starknet).await?;
Expand All @@ -60,6 +65,7 @@
.await?;

let world_address = world_diff.world_info.address;
let profile_config = ws.load_profile_config()?;

Check warning on line 68 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L68

Added line #L68 was not covered by tests

let mut txn_config: TxnConfig = self.transaction.try_into()?;
txn_config.wait = true;
Expand All @@ -75,15 +81,50 @@
let MigrationResult { manifest, has_changes } =
migration.migrate(&mut spinner).await.context("Migration failed.")?;

let ipfs_url = ipfs.url(profile_config.env.as_ref());
let ipfs_username = ipfs.username(profile_config.env.as_ref());
let ipfs_password = ipfs.password(profile_config.env.as_ref());

let mut metadata_upload_text = String::new();

if ipfs_url.is_some() && ipfs_username.is_some() && ipfs_password.is_some() {
let mut metadata_service = IpfsMetadataService::new(
&ipfs_url.unwrap(),
&ipfs_username.unwrap(),
&ipfs_password.unwrap(),
)?;

Check warning on line 95 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L84-L95

Added lines #L84 - L95 were not covered by tests
remybar marked this conversation as resolved.
Show resolved Hide resolved

migration
.upload_metadata(&mut spinner, &mut metadata_service)
.await
.context("Metadata upload failed.")?;
} else {
metadata_upload_text = "\nMetadata: No IPFS credentials has been found => \
metadata upload has been ignored."
.to_string();
};

Check warning on line 105 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L97-L105

Added lines #L97 - L105 were not covered by tests

spinner.update_text("Writing manifest...");
ws.write_manifest_profile(manifest).context("🪦 Failed to write manifest.")?;

let colored_address = format!("{:#066x}", world_address).green();

let (symbol, end_text) = if has_changes {
("⛩️ ", format!("Migration successful with world at address {}", colored_address))
(
"⛩️ ",
format!(
"Migration successful with world at address {}{metadata_upload_text}",
colored_address
),
)

Check warning on line 119 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L113-L119

Added lines #L113 - L119 were not covered by tests
} else {
("🪨 ", format!("No changes for world at address {:#066x}", world_address))
(
"🪨 ",
format!(
"No changes for world at address {:#066x}{metadata_upload_text}",
world_address
),
)

Check warning on line 127 in bin/sozo/src/commands/migrate.rs

View check run for this annotation

Codecov / codecov/patch

bin/sozo/src/commands/migrate.rs#L121-L127

Added lines #L121 - L127 were not covered by tests
};

spinner.stop_and_persist_boxed(symbol, end_text);
Expand Down
170 changes: 170 additions & 0 deletions bin/sozo/src/commands/options/ipfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use clap::Args;
use dojo_utils::env::{IPFS_PASSWORD_ENV_VAR, IPFS_URL_ENV_VAR, IPFS_USERNAME_ENV_VAR};
use dojo_world::config::Environment;
use tracing::trace;
use url::Url;

#[derive(Debug, Default, Args, Clone)]
#[command(next_help_heading = "IPFS options")]
pub struct IpfsOptions {
#[arg(long, env = IPFS_URL_ENV_VAR)]
#[arg(value_name = "URL")]
#[arg(help = "The IPFS URL.")]
#[arg(global = true)]
pub ipfs_url: Option<Url>,

#[arg(long, env = IPFS_USERNAME_ENV_VAR)]
#[arg(value_name = "USERNAME")]
#[arg(help = "The IPFS username.")]
#[arg(global = true)]
pub ipfs_username: Option<String>,

#[arg(long, env = IPFS_PASSWORD_ENV_VAR)]
#[arg(value_name = "PASSWORD")]
#[arg(help = "The IPFS password.")]
#[arg(global = true)]
pub ipfs_password: Option<String>,
remybar marked this conversation as resolved.
Show resolved Hide resolved
}

impl IpfsOptions {
pub fn url(&self, env_metadata: Option<&Environment>) -> Option<String> {
trace!("Retrieving URL for IpfsOptions.");

if let Some(url) = self.ipfs_url.as_ref() {
trace!(?url, "Using IPFS URL from command line.");
Some(url.to_string())
} else if let Some(url) = env_metadata.and_then(|env| env.ipfs_url()) {
trace!(url, "Using IPFS URL from environment metadata.");
Some(url.to_string())
} else {
trace!("No default IPFS URL.");
None
}
}

pub fn username(&self, env_metadata: Option<&Environment>) -> Option<String> {
trace!("Retrieving username for IpfsOptions.");

if let Some(username) = self.ipfs_username.as_ref() {
trace!(?username, "Using IPFS username from command line.");
Some(username.clone())
} else if let Some(username) = env_metadata.and_then(|env| env.ipfs_username()) {
trace!(username, "Using IPFS username from environment metadata.");
Some(username.to_string())
} else {
trace!("No default IPFS username.");
None
}
}

pub fn password(&self, env_metadata: Option<&Environment>) -> Option<String> {
trace!("Retrieving password for IpfsOptions.");

if let Some(password) = self.ipfs_password.as_ref() {
trace!(?password, "Using IPFS password from command line.");
Some(password.clone())
} else if let Some(password) = env_metadata.and_then(|env| env.ipfs_password()) {
trace!(password, "Using IPFS password from environment metadata.");
Some(password.to_string())
} else {
trace!("No default IPFS password.");
None
}
}
}

#[cfg(test)]
mod tests {
use clap::Parser;
use dojo_utils::env::{IPFS_PASSWORD_ENV_VAR, IPFS_URL_ENV_VAR, IPFS_USERNAME_ENV_VAR};

use super::IpfsOptions;

#[derive(clap::Parser)]
struct Command {
#[clap(flatten)]
options: IpfsOptions,
}

const ENV_IPFS_URL: &str = "http://ipfs.service/";
const ENV_IPFS_USERNAME: &str = "johndoe";
const ENV_IPFS_PASSWORD: &str = "123456";

#[test]
fn options_read_from_env_variable() {
std::env::set_var(IPFS_URL_ENV_VAR, ENV_IPFS_URL);
std::env::set_var(IPFS_USERNAME_ENV_VAR, ENV_IPFS_USERNAME);
std::env::set_var(IPFS_PASSWORD_ENV_VAR, ENV_IPFS_PASSWORD);

let cmd = Command::parse_from([""]);
assert_eq!(cmd.options.url(None).unwrap().as_str(), ENV_IPFS_URL);
assert_eq!(cmd.options.username(None).unwrap(), ENV_IPFS_USERNAME);
assert_eq!(cmd.options.password(None).unwrap(), ENV_IPFS_PASSWORD);
}

#[test]
fn options_exist_in_env_but_not_in_args() {
let env_metadata = dojo_world::config::Environment {
ipfs_url: Some(ENV_IPFS_URL.into()),
ipfs_username: Some(ENV_IPFS_USERNAME.into()),
ipfs_password: Some(ENV_IPFS_PASSWORD.into()),
..Default::default()
};

let cmd = Command::parse_from([""]);
assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_URL);
assert_eq!(cmd.options.username(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_USERNAME);
assert_eq!(cmd.options.password(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_PASSWORD);
}

#[test]
fn options_doesnt_exist_in_env_but_exist_in_args() {
let env_metadata = dojo_world::config::Environment::default();
let cmd = Command::parse_from([
"sozo",
"--ipfs-url",
ENV_IPFS_URL,
"--ipfs-username",
ENV_IPFS_USERNAME,
"--ipfs-password",
ENV_IPFS_PASSWORD,
]);

assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_URL);
assert_eq!(cmd.options.username(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_USERNAME);
assert_eq!(cmd.options.password(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_PASSWORD);
}

#[test]
fn options_exists_in_both() {
let env_metadata = dojo_world::config::Environment {
ipfs_url: Some(ENV_IPFS_URL.into()),
ipfs_username: Some(ENV_IPFS_USERNAME.into()),
ipfs_password: Some(ENV_IPFS_PASSWORD.into()),
..Default::default()
};

let cmd = Command::parse_from([
"sozo",
"--ipfs-url",
ENV_IPFS_URL,
"--ipfs-username",
ENV_IPFS_USERNAME,
"--ipfs-password",
ENV_IPFS_PASSWORD,
]);

assert_eq!(cmd.options.url(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_URL);
assert_eq!(cmd.options.username(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_USERNAME);
assert_eq!(cmd.options.password(Some(&env_metadata)).unwrap().as_str(), ENV_IPFS_PASSWORD);
}

#[test]
fn url_exists_in_neither() {
let env_metadata = dojo_world::config::Environment::default();
let cmd = Command::parse_from([""]);
assert_eq!(cmd.options.url(Some(&env_metadata)), None);
assert_eq!(cmd.options.username(Some(&env_metadata)), None);
assert_eq!(cmd.options.password(Some(&env_metadata)), None);
}
}
1 change: 1 addition & 0 deletions bin/sozo/src/commands/options/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod account;
pub mod ipfs;
pub mod signer;
pub mod starknet;
pub mod transaction;
Expand Down
Loading
Loading