Skip to content
This repository has been archived by the owner on Oct 23, 2022. It is now read-only.

Commit

Permalink
Merge #421
Browse files Browse the repository at this point in the history
421: feat(http): create Profile abstraction to allow port choice r=koivunej a=niklaslong

This PR introduces a `Profile` abstraction to allow the customisation of port selection as discussed in #402.

Two profiles are supported: 
- `Test` for use with conformance tests (ephemeral port selection)
- `Default` serves on `4004`

Co-authored-by: Niklas Long <[email protected]>
  • Loading branch information
bors[bot] and niklaslong authored Oct 27, 2020
2 parents e3e1cc2 + 3d677c6 commit 83e73ab
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 22 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Next

* feat(http): create `Profile` abstraction [#421]

[#421]: https://github.com/rs-ipfs/rust-ipfs/pull/421

# 0.2.1

* fix: restore_bootstrappers doesn't enable content discovery [#406]
Expand Down
55 changes: 41 additions & 14 deletions http/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! go-ipfs compatible configuration file handling and setup.

use parity_multiaddr::Multiaddr;
use parity_multiaddr::{multiaddr, Multiaddr};
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::num::NonZeroU16;
use std::path::Path;
use std::str::FromStr;
use structopt::StructOpt;
use thiserror::Error;

/// Temporary module required to de/ser config files base64'd protobuf rsa private key format.
Expand All @@ -13,6 +15,25 @@ mod keys_proto {
include!(concat!(env!("OUT_DIR"), "/keys_proto.rs"));
}

#[derive(Debug, StructOpt)]
pub enum Profile {
Test,
Default,
}

// Required for structopt.
impl FromStr for Profile {
type Err = InitializationError;

fn from_str(profile: &str) -> Result<Self, Self::Err> {
match profile {
"test" => Ok(Profile::Test),
"default" => Ok(Profile::Default),
_ => Err(InitializationError::InvalidProfile(profile.to_string())),
}
}
}

/// The way things can go wrong when calling [`initialize`].
#[derive(Error, Debug)]
pub enum InitializationError {
Expand All @@ -23,7 +44,7 @@ pub enum InitializationError {
#[error("invalid RSA key length given: {0}")]
InvalidRsaKeyLength(u16),
#[error("unsupported profiles selected: {0:?}")]
InvalidProfiles(Vec<String>),
InvalidProfile(String),
#[error("key generation failed: {0}")]
KeyGeneration(Box<dyn std::error::Error + 'static>),
#[error("key encoding failed: {0}")]
Expand All @@ -37,8 +58,14 @@ pub enum InitializationError {
pub fn initialize(
ipfs_path: &Path,
bits: NonZeroU16,
profiles: Vec<String>,
profiles: Vec<Profile>,
) -> Result<(), InitializationError> {
// This check is done here to avoid an empty config file being created in the case of an
// unsupported input.
if profiles.len() != 1 {
unimplemented!("Multiple profiles are currently unsupported!");
}

let config_path = ipfs_path.join("config");

fs::create_dir_all(&ipfs_path)
Expand All @@ -52,27 +79,24 @@ pub fn initialize(
fn create(
config: File,
bits: NonZeroU16,
profiles: Vec<String>,
profiles: Vec<Profile>,
) -> Result<(), InitializationError> {
use multibase::Base::Base64Pad;
use prost::Message;
use std::io::BufWriter;

let api_addr = match profiles[0] {
Profile::Test => multiaddr!(Ip4([127, 0, 0, 1]), Tcp(0u16)),
Profile::Default => multiaddr!(Ip4([127, 0, 0, 1]), Tcp(4004u16)),
};

let bits = bits.get();

if bits < 2048 || bits > 16 * 1024 {
// ring will not accept a less than 2048 key
return Err(InitializationError::InvalidRsaKeyLength(bits));
}

if profiles.len() != 1 || profiles[0] != "test" {
// profiles are expected to be (comma separated) "test" as there are no bootstrap peer
// handling yet. the conformance test cases seem to init `go-ipfs` in this profile where
// it does not have any bootstrap nodes, and multi node tests later call swarm apis to
// dial the nodes together.
return Err(InitializationError::InvalidProfiles(profiles));
}

let pk = openssl::rsa::Rsa::generate(bits as u32)
.map_err(|e| InitializationError::KeyGeneration(Box::new(e)))?;

Expand Down Expand Up @@ -118,6 +142,7 @@ fn create(
},
addresses: Addresses {
swarm: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
api: api_addr,
},
};

Expand Down Expand Up @@ -147,7 +172,7 @@ pub enum LoadingError {
/// Returns only the keypair and listening addresses or [`LoadingError`] but this should be
/// extended to contain the bootstrap nodes at least later when we need to support those for
/// testing purposes.
pub fn load(config: File) -> Result<(ipfs::Keypair, Vec<Multiaddr>), LoadingError> {
pub fn load(config: File) -> Result<(ipfs::Keypair, Vec<Multiaddr>, Multiaddr), LoadingError> {
use std::io::BufReader;

let CompatibleConfigFile {
Expand All @@ -167,7 +192,7 @@ pub fn load(config: File) -> Result<(ipfs::Keypair, Vec<Multiaddr>), LoadingErro
});
}

Ok((kp, addresses.swarm))
Ok((kp, addresses.swarm, addresses.api))
}

/// Converts a PEM format to DER where PEM is a container for Base64 data with padding, starting on
Expand Down Expand Up @@ -245,6 +270,8 @@ struct CompatibleConfigFile {
#[serde(rename_all = "PascalCase")]
struct Addresses {
swarm: Vec<Multiaddr>,
#[serde(rename = "API")]
api: Multiaddr,
}

#[derive(Debug, Serialize, Deserialize)]
Expand Down
34 changes: 26 additions & 8 deletions http/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use structopt::StructOpt;

use ipfs::{Ipfs, IpfsOptions, IpfsTypes, UninitializedIpfs};
use ipfs_http::{config, v0};
use parity_multiaddr::{Multiaddr, Protocol};

#[macro_use]
extern crate tracing;
Expand All @@ -16,9 +17,12 @@ enum Options {
/// Generated key length
#[structopt(long)]
bits: NonZeroU16,
/// List of configuration profiles to apply
/// List of configuration profiles to apply. Currently only the `Test` and `Default`
/// profiles are supported.
///
/// `Test` uses ephemeral ports (necessary for conformance tests), `Default` uses `4004`.
#[structopt(long, use_delimiter = true)]
profile: Vec<String>,
profile: Vec<config::Profile>,
},
/// Start the IPFS node in the foreground (not detaching from parent process).
Daemon,
Expand Down Expand Up @@ -59,7 +63,7 @@ fn main() {

let config_path = home.join("config");

let (keypair, listening_addrs) = match opts {
let (keypair, listening_addrs, api_listening_addr) = match opts {
Options::Init { bits, profile } => {
println!("initializing IPFS node at {:?}", home);

Expand All @@ -73,7 +77,7 @@ fn main() {

match result {
Ok(_) => {
let (kp, _) = std::fs::File::open(config_path)
let (kp, _, _) = std::fs::File::open(config_path)
.map_err(config::LoadingError::ConfigurationFileOpening)
.and_then(config::load)
.unwrap();
Expand Down Expand Up @@ -101,8 +105,8 @@ fn main() {
eprintln!("This is a fake version of ipfs cli which does not support much");
std::process::exit(1);
}
Err(config::InitializationError::InvalidProfiles(profiles)) => {
eprintln!("Error: unsupported profile selection: {:?}", profiles);
Err(config::InitializationError::InvalidProfile(profile)) => {
eprintln!("Error: unsupported profile selection: {:?}", profile);
eprintln!("This is a fake version of ipfs cli which does not support much");
std::process::exit(1);
}
Expand Down Expand Up @@ -153,7 +157,8 @@ fn main() {
tokio::spawn(task);

let api_link_file = home.join("api");
let (addr, server) = serve(&ipfs);

let (addr, server) = serve(&ipfs, api_listening_addr);

// shutdown future will handle signalling the exit
drop(ipfs);
Expand Down Expand Up @@ -185,17 +190,30 @@ fn main() {

fn serve<Types: IpfsTypes>(
ipfs: &Ipfs<Types>,
listening_addr: Multiaddr,
) -> (std::net::SocketAddr, impl std::future::Future<Output = ()>) {
use std::net::SocketAddr;
use tokio::stream::StreamExt;
use warp::Filter;

let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);

let routes = v0::routes(ipfs, shutdown_tx);
let routes = routes.with(warp::log(env!("CARGO_PKG_NAME")));

let ipfs = ipfs.clone();

warp::serve(routes).bind_with_graceful_shutdown(([127, 0, 0, 1], 0), async move {
let components = listening_addr.iter().collect::<Vec<_>>();

let socket_addr = match components.as_slice() {
[Protocol::Ip4(ip), Protocol::Tcp(port)] => SocketAddr::new(ip.clone().into(), *port),
_ => panic!(
"Couldn't convert MultiAddr into SocketAddr: {}",
listening_addr
),
};

warp::serve(routes).bind_with_graceful_shutdown(socket_addr, async move {
shutdown_rx.next().await;
info!("Shutdown trigger received; starting shutdown");
ipfs.exit_daemon().await;
Expand Down

0 comments on commit 83e73ab

Please sign in to comment.