Skip to content

Commit

Permalink
Add authorization support for remote persistence
Browse files Browse the repository at this point in the history
Specify a command (and args) to be run in the remote persist config
```
persist: {
  url: "https://persist-service/path",
  authorization_header: {
    command: "echo",
    args: ["hello"],
  },
}
```
And it will be used as the value of the `Authorization` header
```
POST https://persist-service/path
Content-Type: application/x-www-form-urlencoded
Authorization: hello
```
  • Loading branch information
tomgasson committed Sep 19, 2022
1 parent 54ba473 commit b71894f
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 46 deletions.
73 changes: 32 additions & 41 deletions compiler/Cargo.lock

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

11 changes: 11 additions & 0 deletions compiler/crates/persist-query/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,15 @@ pub enum PersistError {
#[from]
source: std::io::Error,
},

#[error("Authorization header command: {error}")]
AuthorizationCommandError {
error: Box<dyn std::error::Error + Send>,
},

#[error("Authorization header command returned non-zero exit code")]
AuthorizationCommandFailed,

#[error("Authorization header command returned invalid utf-8 content")]
AuthorizationCommandInvalidUtf8Error,
}
10 changes: 9 additions & 1 deletion compiler/crates/relay-compiler/src/build_project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,20 @@ pub async fn commit_project(
return Err(BuildProjectFailure::Cancelled);
}

if let Some(operation_persister) = config
if let Some(mut operation_persister) = config
.create_operation_persister
.as_ref()
.and_then(|create_fn| create_fn(project_config))
{
let persist_operations_timer = log_event.start("persist_operations_time");

operation_persister.setup().await.map_err(|error| {
BuildProjectFailure::Error(BuildProjectError::PersistErrors {
errors: vec!(error),
project_name: project_config.name,
})
})?;

persist_operations::persist_operations(
&mut artifacts,
&config.root_dir,
Expand Down
2 changes: 2 additions & 0 deletions compiler/crates/relay-compiler/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,8 @@ pub struct ArtifactForPersister {
pub trait OperationPersister {
async fn persist_artifact(&self, artifact: ArtifactForPersister) -> PersistResult<PersistId>;

async fn setup(&mut self) -> PersistResult<()>;

fn finalize(&self) -> PersistResult<()> {
Ok(())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ impl LocalPersister {

#[async_trait]
impl OperationPersister for LocalPersister {
async fn setup(&mut self) -> Result<(), PersistError> {
Ok(())
}

async fn persist_artifact(
&self,
artifact: ArtifactForPersister,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

use std::iter::empty;
use std::process::Command;

use async_trait::async_trait;
use persist_query::persist;
Expand All @@ -19,30 +19,61 @@ use crate::OperationPersister;
pub struct RemotePersister {
pub config: RemotePersistConfig,
semaphore: Option<Semaphore>,
authorization_header: Option<String>,
}

impl RemotePersister {
pub fn new(config: RemotePersistConfig) -> Self {
let semaphore = config.semaphore_permits.map(Semaphore::new);
Self { config, semaphore }
Self {
config,
semaphore,
authorization_header: None,
}
}
}

#[async_trait]
impl OperationPersister for RemotePersister {
async fn setup(&mut self) -> Result<(), PersistError> {
if let Some(command) = &self.config.authorization_header {
let output = Command::new(&command.command)
.args(&command.args)
.output()
.map_err(|err| PersistError::AuthorizationCommandError {
error: Box::new(err),
})?;

if output.status.success() {
let header_value = String::from_utf8(output.stdout)
.map_err(|_| PersistError::AuthorizationCommandInvalidUtf8Error)?;
self.authorization_header = Some(header_value.trim().to_string());
} else {
return Err(PersistError::AuthorizationCommandFailed);
}
}
Ok(())
}
async fn persist_artifact(
&self,
artifact: ArtifactForPersister,
) -> Result<String, PersistError> {
let params = &self.config.params;
let authorization = "Authorization".to_string();
let headers = if let Some(auth) = &self.authorization_header {
vec![(&authorization, auth)]
} else {
vec![]
};

let url = &self.config.url;
if let Some(semaphore) = &self.semaphore {
let permit = (*semaphore).acquire().await.unwrap();
let result = persist(&artifact.text, url, params, empty()).await;
let result = persist(&artifact.text, url, params, headers).await;
drop(permit);
result
} else {
persist(&artifact.text, url, params, empty()).await
persist(&artifact.text, url, params, headers).await
}
}
}
14 changes: 14 additions & 0 deletions compiler/crates/relay-config/src/project_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ pub struct RemotePersistConfig {
deserialize_with = "deserialize_semaphore_permits"
)]
pub semaphore_permits: Option<usize>,

#[serde(default)]
pub authorization_header: Option<AuthorizationCommand>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AuthorizationCommand {
/// A command to be run to get a value for the Authorization header
pub command: String,

/// Arguments for the command
#[serde(default)]
pub args: Vec<String>
}

fn deserialize_semaphore_permits<'de, D>(d: D) -> Result<Option<usize>, D::Error>
Expand Down

0 comments on commit b71894f

Please sign in to comment.