Skip to content

Commit

Permalink
feat: Support graphref and sdl modes in `rover dev --supergraph-c…
Browse files Browse the repository at this point in the history
…onfig` (#1673)

Closes #1663

---------

Co-authored-by: Dylan Anthony <dbanty@users.noreply.github.com>
Co-authored-by: Avery Harnish <avery@apollographql.com>
  • Loading branch information
3 people authored Jul 13, 2023
1 parent 8691c59 commit 5c68e71
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 37 deletions.
5 changes: 5 additions & 0 deletions docs/source/commands/dev.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ subgraphs:
reviews:
schema:
subgraph_url: http://localhost:4001 # Schema provided via introspection, routing_url can be omitted
users:
routing_url: http://localhost:4002 # <- omit to point at the URL from the GraphOS registry
schema: # Schema downloaded from GraphOS registry, does not poll for updates
graphref: mygraph@current
subgraph: actors
```
You provide this file to `rover dev` like so:
Expand Down
1 change: 1 addition & 0 deletions src/command/dev/do_dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ impl Dev {
&client_config,
follower_messenger.clone(),
self.opts.subgraph_opts.subgraph_polling_interval,
&self.opts.plugin_opts.profile,
)
.transpose()
.unwrap_or_else(|| {
Expand Down
73 changes: 47 additions & 26 deletions src/command/dev/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use std::{net::SocketAddr, time::Duration};
use anyhow::anyhow;
use apollo_federation_types::config::SchemaSource;
use reqwest::Url;
use rover_client::blocking::StudioClient;
use rover_std::Fs;

use crate::command::supergraph::expand_supergraph_yaml;
use crate::options::ProfileOpt;
use crate::{
command::dev::{
netstat::normalize_loopback_urls, protocol::FollowerMessenger,
Expand Down Expand Up @@ -96,6 +98,7 @@ impl SupergraphOpts {
client_config: &StudioClientConfig,
follower_messenger: FollowerMessenger,
polling_interval: u64,
profile_opt: &ProfileOpt,
) -> RoverResult<Option<Vec<SubgraphSchemaWatcher>>> {
let config_path = if let Some(path) = &self.supergraph_config_path {
path
Expand All @@ -113,47 +116,65 @@ impl SupergraphOpts {
.get_builder()
.with_timeout(Duration::from_secs(5))
.build()?;
let mut studio_client: Option<StudioClient> = None;
supergraph_config
.into_iter()
.map(|(name, subgraph_config)| {
.map(|(yaml_subgraph_name, subgraph_config)| {
let routing_url = subgraph_config
.routing_url
.map(|url_str| Url::parse(&url_str).map_err(RoverError::from))
.or_else(|| {
if let SchemaSource::SubgraphIntrospection {
subgraph_url,
..
} = &subgraph_config.schema
{
Some(Ok(subgraph_url.clone()))
} else {
None
}
})
.unwrap_or_else(|| {
Err(RoverError::new(anyhow!("routing_url must be declared for every subgraph not using introspection")))
})?;
.transpose()?;
match subgraph_config.schema {
SchemaSource::File { file } => SubgraphSchemaWatcher::new_from_file_path(
(name, routing_url),
file,
follower_messenger.clone(),
),
SchemaSource::File { file } => {
let routing_url = routing_url.ok_or_else(|| {
anyhow!("`routing_url` must be set when using a local schema file")
})?;
SubgraphSchemaWatcher::new_from_file_path(
(yaml_subgraph_name, routing_url),
file,
follower_messenger.clone(),
)
}
SchemaSource::SubgraphIntrospection {
subgraph_url,
introspection_headers,
} => SubgraphSchemaWatcher::new_from_url(
(name, subgraph_url),
(yaml_subgraph_name, subgraph_url),
client.clone(),
follower_messenger.clone(),
polling_interval,
introspection_headers,
),
SchemaSource::Sdl { .. } | SchemaSource::Subgraph { .. } => {
Err(RoverError::new(anyhow!(
"Detected an invalid `graphref` or `sdl` schema source in {file}. rover dev only supports sourcing schemas via introspection and schema files. see https://www.apollographql.com/docs/rover/commands/supergraphs/#yaml-configuration-file for more information.",
file = config_path.to_string()
)))
SchemaSource::Sdl { sdl } => {
let routing_url = routing_url.ok_or_else(|| {
anyhow!("`routing_url` must be set when providing SDL directly")
})?;
SubgraphSchemaWatcher::new_from_sdl(
(yaml_subgraph_name, routing_url),
sdl,
follower_messenger.clone(),
)
}
SchemaSource::Subgraph {
graphref,
subgraph: graphos_subgraph_name,
} => {
let studio_client = if let Some(studio_client) = studio_client.as_ref() {
studio_client
} else {
let client = client_config.get_authenticated_client(profile_opt)?;
studio_client = Some(client);
studio_client.as_ref().unwrap()
};

SubgraphSchemaWatcher::new_from_graph_ref(
&graphref,
graphos_subgraph_name,
routing_url,
yaml_subgraph_name,
follower_messenger.clone(),
studio_client,
)
}
}
})
Expand Down
85 changes: 83 additions & 2 deletions src/command/dev/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ use crate::{
introspect::{IntrospectRunnerKind, UnknownIntrospectRunner},
protocol::{FollowerMessenger, SubgraphKey},
},
RoverError, RoverResult,
RoverError, RoverErrorSuggestion, RoverResult,
};
use anyhow::{anyhow, Context};
use std::collections::HashMap;
use std::str::FromStr;

use apollo_federation_types::build::SubgraphDefinition;
use camino::{Utf8Path, Utf8PathBuf};
use crossbeam_channel::unbounded;
use reqwest::blocking::Client;
use rover_client::blocking::StudioClient;
use rover_client::operations::subgraph::fetch;
use rover_client::operations::subgraph::fetch::SubgraphFetchInput;
use rover_client::shared::GraphRef;
use rover_std::{Emoji, Fs};
use url::Url;

#[derive(Debug)]
pub struct SubgraphSchemaWatcher {
Expand Down Expand Up @@ -55,6 +62,67 @@ impl SubgraphSchemaWatcher {
)
}

pub fn new_from_sdl(
subgraph_key: SubgraphKey,
sdl: String,
message_sender: FollowerMessenger,
) -> RoverResult<Self> {
Ok(Self {
schema_watcher_kind: SubgraphSchemaWatcherKind::Once(sdl),
subgraph_key,
message_sender,
})
}

pub fn new_from_graph_ref(
graph_ref: &str,
graphos_subgraph_name: String,
routing_url: Option<Url>,
yaml_subgraph_name: String,
message_sender: FollowerMessenger,
client: &StudioClient,
) -> RoverResult<Self> {
// given a graph_ref and subgraph, run subgraph fetch to
// obtain SDL and add it to subgraph_definition.
let response = fetch::run(
SubgraphFetchInput {
graph_ref: GraphRef::from_str(graph_ref)?,
subgraph_name: graphos_subgraph_name.clone(),
},
client,
)
.map_err(RoverError::from)?;
let routing_url = match (routing_url, response.sdl.r#type) {
(Some(routing_url), _) => routing_url,
(
None,
rover_client::shared::SdlType::Subgraph {
routing_url: Some(graph_registry_routing_url),
},
) => graph_registry_routing_url.parse().context(format!(
"Could not parse graph registry routing url {}",
graph_registry_routing_url
))?,
(None, _) => {
return Err(RoverError::new(anyhow!(
"Could not find routing URL in GraphOS for subgraph {graphos_subgraph_name}"
))
.with_suggestion(RoverErrorSuggestion::AddRoutingUrlToSupergraphYaml)
.with_suggestion(
RoverErrorSuggestion::PublishSubgraphWithRoutingUrl {
subgraph_name: yaml_subgraph_name,
graph_ref: graph_ref.to_string(),
},
));
}
};
Self::new_from_sdl(
(yaml_subgraph_name, routing_url),
response.sdl.contents,
message_sender,
)
}

pub fn new_from_introspect_runner(
subgraph_key: SubgraphKey,
introspect_runner: IntrospectRunnerKind,
Expand Down Expand Up @@ -102,6 +170,7 @@ impl SubgraphSchemaWatcher {
let sdl = Fs::read_file(file_path)?;
(sdl, None)
}
SubgraphSchemaWatcherKind::Once(sdl) => (sdl.clone(), None),
};

let subgraph_definition = SubgraphDefinition::new(name, url, sdl);
Expand Down Expand Up @@ -152,6 +221,10 @@ impl SubgraphSchemaWatcher {
Ok(maybe_update_message)
}

/// Start checking for subgraph updates and sending them to the main process.
///
/// This function will block forever for `SubgraphSchemaWatcherKind` that poll for changes—so it
/// should be started in a separate thread.
pub fn watch_subgraph_for_changes(&mut self) -> RoverResult<()> {
let mut last_message = None;
match self.schema_watcher_kind.clone() {
Expand Down Expand Up @@ -189,7 +262,11 @@ impl SubgraphSchemaWatcher {
last_message = self.update_subgraph(last_message.as_ref())?;
}
}
};
SubgraphSchemaWatcherKind::Once(_) => {
self.update_subgraph(None)?;
}
}
Ok(())
}

pub fn set_schema_refresher(&mut self, new_refresher: SubgraphSchemaWatcherKind) {
Expand All @@ -203,6 +280,10 @@ impl SubgraphSchemaWatcher {

#[derive(Debug, Clone)]
pub enum SubgraphSchemaWatcherKind {
/// Poll an endpoint via introspection
Introspect(IntrospectRunnerKind, u64),
/// Watch a file on disk
File(Utf8PathBuf),
/// Don't ever update, schema is only pulled once
Once(String),
}
2 changes: 1 addition & 1 deletion src/command/supergraph/resolve_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ pub(crate) fn resolve_supergraph_yaml(
"{} while resolving the schema for the '{}' subgraph",
message, subgraph_name
);
if let Some(suggestion) = error.suggestion() {
for suggestion in error.suggestions() {
message = format!("{}\n {}", message, suggestion)
}
BuildError::config_error(error.code().map(|c| format!("{}", c)), Some(message))
Expand Down
6 changes: 3 additions & 3 deletions src/error/metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use serde::Serialize;
pub struct RoverErrorMetadata {
// skip serializing for now until we can appropriately strip color codes
#[serde(skip_serializing)]
pub suggestion: Option<RoverErrorSuggestion>,
pub suggestions: Vec<RoverErrorSuggestion>,
pub code: Option<RoverErrorCode>,

// anyhow's debug implementation prints the error cause, most of the time we want this
Expand Down Expand Up @@ -290,7 +290,7 @@ impl From<&mut anyhow::Error> for RoverErrorMetadata {
};
return RoverErrorMetadata {
json_version: JsonVersion::default(),
suggestion,
suggestions: suggestion.into_iter().collect(),
code,
skip_printing_cause,
};
Expand Down Expand Up @@ -357,7 +357,7 @@ impl From<&mut anyhow::Error> for RoverErrorMetadata {
};
return RoverErrorMetadata {
json_version: JsonVersion::default(),
suggestion,
suggestions: suggestion.into_iter().collect(),
code,
skip_printing_cause,
};
Expand Down
13 changes: 12 additions & 1 deletion src/error/metadata/suggestion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ pub enum RoverErrorSuggestion {
graph_id: String,
frontend_url_root: String,
},
AddRoutingUrlToSupergraphYaml,
PublishSubgraphWithRoutingUrl {
subgraph_name: String,
graph_ref: String,
},
}

impl Display for RoverErrorSuggestion {
Expand Down Expand Up @@ -240,7 +245,13 @@ UpgradePlan => "Rover has likely reached rate limits while running graph or subg
}
CreateOrFindValidPersistedQueryList { graph_id, frontend_url_root } => {
format!("Find existing persisted query lists associated with '{graph_id}' or create a new one by heading to {frontend_url_root}/graph/{graph_id}/persisted-queries")
}
},
AddRoutingUrlToSupergraphYaml => {
String::from("Try specifying a routing URL in the supergraph YAML file. See https://www.apollographql.com/docs/rover/commands/supergraphs/#yaml-configuration-file for more details.")
},
PublishSubgraphWithRoutingUrl { graph_ref, subgraph_name } => {
format!("Try publishing the subgraph with a routing URL like so `rover subgraph publish {graph_ref} --name {subgraph_name} --routing-url <url>`")
},
};
write!(formatter, "{}", &suggestion)
}
Expand Down
13 changes: 9 additions & 4 deletions src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,16 @@ impl RoverError {
}

pub fn set_suggestion(&mut self, suggestion: RoverErrorSuggestion) {
self.metadata.suggestion = Some(suggestion);
self.metadata.suggestions.push(suggestion);
}

pub fn suggestion(&self) -> Option<RoverErrorSuggestion> {
self.metadata.suggestion.clone()
pub fn with_suggestion(mut self, suggestion: RoverErrorSuggestion) -> Self {
self.set_suggestion(suggestion);
self
}

pub fn suggestions(&self) -> &[RoverErrorSuggestion] {
&self.metadata.suggestions
}

pub fn message(&self) -> String {
Expand Down Expand Up @@ -139,7 +144,7 @@ impl Display for RoverError {
writeln!(formatter, "{} {:?}", error_descriptor, &self.error)?;
}

if let Some(suggestion) = &self.metadata.suggestion {
for suggestion in &self.metadata.suggestions {
writeln!(formatter, " {}", suggestion)?;
}
Ok(())
Expand Down

0 comments on commit 5c68e71

Please sign in to comment.