Skip to content

Commit

Permalink
get ipfs credentials from env/command-line
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed Nov 19, 2024
1 parent 864e272 commit e63a17c
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 30 deletions.
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::account::AccountOptions;
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 @@ impl DevArgs {
starknet: self.starknet,
account: self.account,
transaction: self.transaction,
ipfs: IpfsOptions::default(), // no need for IPFS metadata upload here.
};

let _ = migrate_args.clone().run(config);
Expand Down
60 changes: 41 additions & 19 deletions bin/sozo/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@ use tabled::{Table, Tabled};
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;
use crate::utils;

// TODO: to remove and to be read from environment variables
const IPFS_CLIENT_URL: &str = "https://ipfs.infura.io:5001";
const IPFS_USERNAME: &str = "2EBrzr7ZASQZKH32sl2xWauXPSA";
const IPFS_PASSWORD: &str = "12290b883db9138a8ae3363b6739d220";

#[derive(Debug, Clone, Args)]
pub struct MigrateArgs {
#[command(flatten)]
Expand All @@ -38,6 +34,9 @@ pub struct MigrateArgs {

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

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

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

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

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

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

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

match IpfsMetadataService::new(IPFS_CLIENT_URL, IPFS_USERNAME, IPFS_PASSWORD) {
Ok(mut metadata_service) => {
migration
.upload_metadata(&mut spinner, &mut metadata_service)
.await
.context("Metadata upload failed.")?;
}
_ => {
// Unable to instanciate IPFS service so metadata upload is ignored.
// TODO: add a message.
}
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(),
)?;

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();
};

spinner.update_text("Writing manifest...");
Expand All @@ -100,9 +110,21 @@ impl MigrateArgs {
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
),
)
} else {
("🪨 ", format!("No changes for world at address {:#066x}", world_address))
(
"🪨 ",
format!(
"No changes for world at address {:#066x}{metadata_upload_text}",
world_address
),
)
};

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>,
}

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
3 changes: 3 additions & 0 deletions crates/dojo/utils/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ pub const DOJO_KEYSTORE_PATH_ENV_VAR: &str = "DOJO_KEYSTORE_PATH";
pub const DOJO_KEYSTORE_PASSWORD_ENV_VAR: &str = "DOJO_KEYSTORE_PASSWORD";
pub const DOJO_ACCOUNT_ADDRESS_ENV_VAR: &str = "DOJO_ACCOUNT_ADDRESS";
pub const DOJO_WORLD_ADDRESS_ENV_VAR: &str = "DOJO_WORLD_ADDRESS";
pub const IPFS_URL_ENV_VAR: &str = "DOJO_IPFS_URL";
pub const IPFS_USERNAME_ENV_VAR: &str = "DOJO_IPFS_USERNAME";
pub const IPFS_PASSWORD_ENV_VAR: &str = "DOJO_IPFS_PASSWORD";
15 changes: 15 additions & 0 deletions crates/dojo/world/src/config/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ pub struct Environment {
pub world_address: Option<String>,
pub world_block: Option<u64>,
pub http_headers: Option<Vec<HttpHeader>>,
pub ipfs_url: Option<String>,
pub ipfs_username: Option<String>,
pub ipfs_password: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -42,4 +45,16 @@ impl Environment {
pub fn keystore_password(&self) -> Option<&str> {
self.keystore_password.as_deref()
}

pub fn ipfs_url(&self) -> Option<&str> {
self.ipfs_url.as_deref()
}

pub fn ipfs_username(&self) -> Option<&str> {
self.ipfs_username.as_deref()
}

pub fn ipfs_password(&self) -> Option<&str> {
self.ipfs_password.as_deref()
}
}
4 changes: 2 additions & 2 deletions crates/dojo/world/src/metadata/ipfs_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ impl IpfsMetadataService {
/// instanciated or a Anyhow error if not.
pub fn new(client_url: &str, username: &str, password: &str) -> Result<Self> {
if client_url.is_empty() || username.is_empty() || password.is_empty() {
anyhow::bail!("Invalid credentials: empty values not allowed");
anyhow::bail!("Invalid IPFS credentials: empty values not allowed");
}
if !client_url.starts_with("http://") && !client_url.starts_with("https://") {
anyhow::bail!("Invalid client URL: must start with http:// or https://");
anyhow::bail!("Invalid IPFS URL: must start with http:// or https://");
}

Ok(Self {
Expand Down
3 changes: 3 additions & 0 deletions examples/spawn-and-move/dojo_dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ rpc_url = "http://localhost:5050/"
account_address = "0x2af9427c5a277474c079a1283c880ee8a6f0f8fbf73ce969c08d88befec1bba"
private_key = "0x1800000000300000180000000000030000000000003006001800006600"
world_address = "0x52ee4d3cba58d1a0462bbfb6813bf5aa1b35078c3b859cded2b727c1d9469ea"
ipfs_url = "https://ipfs.infura.io:5001"
ipfs_username = "2EBrzr7ZASQZKH32sl2xWauXPSA"
ipfs_password = "12290b883db9138a8ae3363b6739d220"

[init_call_args]
"ns-others" = ["0xff"]
Expand Down
Loading

0 comments on commit e63a17c

Please sign in to comment.