1
- use crate :: { anyhow, command:: RoverStdout , Result } ;
1
+ use crate :: utils:: { client:: StudioClientConfig , parsers:: parse_graph_ref} ;
2
+ use crate :: { anyhow, command:: RoverStdout , error:: RoverError , Result , Suggestion } ;
2
3
3
4
use ansi_term:: Colour :: Red ;
4
5
use camino:: Utf8PathBuf ;
6
+ use harmonizer:: ServiceDefinition as SubgraphDefinition ;
7
+ use rover_client:: {
8
+ blocking:: Client ,
9
+ query:: subgraph:: { fetch, introspect} ,
10
+ } ;
5
11
use serde:: Serialize ;
12
+ use std:: { collections:: HashMap , fs} ;
6
13
use structopt:: StructOpt ;
7
14
8
- use super :: config;
15
+ use super :: config:: { self , SchemaSource , SupergraphConfig } ;
9
16
10
17
#[ derive( Debug , Serialize , StructOpt ) ]
11
18
pub struct Compose {
12
19
/// The relative path to the supergraph configuration file.
13
20
#[ structopt( long = "config" ) ]
14
21
#[ serde( skip_serializing) ]
15
22
config_path : Utf8PathBuf ,
23
+
24
+ /// Name of configuration profile to use
25
+ #[ structopt( long = "profile" , default_value = "default" ) ]
26
+ #[ serde( skip_serializing) ]
27
+ profile_name : String ,
16
28
}
17
29
18
30
impl Compose {
19
- pub fn run ( & self ) -> Result < RoverStdout > {
31
+ pub fn run ( & self , client_config : StudioClientConfig ) -> Result < RoverStdout > {
20
32
let supergraph_config = config:: parse_supergraph_config ( & self . config_path ) ?;
21
- let subgraph_definitions = supergraph_config. get_subgraph_definitions ( & self . config_path ) ?;
33
+ let subgraph_definitions = get_subgraph_definitions (
34
+ supergraph_config,
35
+ & self . config_path ,
36
+ client_config,
37
+ & self . profile_name ,
38
+ ) ?;
22
39
23
40
match harmonizer:: harmonize ( subgraph_definitions) {
24
41
Ok ( core_schema) => Ok ( RoverStdout :: CoreSchema ( core_schema) ) ,
@@ -43,3 +60,200 @@ impl Compose {
43
60
}
44
61
}
45
62
}
63
+
64
+ pub ( crate ) fn get_subgraph_definitions (
65
+ supergraph_config : SupergraphConfig ,
66
+ config_path : & Utf8PathBuf ,
67
+ client_config : StudioClientConfig ,
68
+ profile_name : & str ,
69
+ ) -> Result < Vec < SubgraphDefinition > > {
70
+ let mut subgraphs = Vec :: new ( ) ;
71
+
72
+ for ( subgraph_name, subgraph_data) in & supergraph_config. subgraphs {
73
+ match & subgraph_data. schema {
74
+ SchemaSource :: File { file } => {
75
+ let relative_schema_path = match config_path. parent ( ) {
76
+ Some ( parent) => {
77
+ let mut schema_path = parent. to_path_buf ( ) ;
78
+ schema_path. push ( file) ;
79
+ schema_path
80
+ }
81
+ None => file. clone ( ) ,
82
+ } ;
83
+
84
+ let schema = fs:: read_to_string ( & relative_schema_path) . map_err ( |e| {
85
+ let err = anyhow ! ( "Could not read \" {}\" : {}" , & relative_schema_path, e) ;
86
+ let mut err = RoverError :: new ( err) ;
87
+ err. set_suggestion ( Suggestion :: ValidComposeFile ) ;
88
+ err
89
+ } ) ?;
90
+
91
+ let url = & subgraph_data. routing_url . clone ( ) . ok_or_else ( || {
92
+ let err = anyhow ! ( "No routing_url found for schema file." ) ;
93
+ let mut err = RoverError :: new ( err) ;
94
+ err. set_suggestion ( Suggestion :: ValidComposeRoutingUrl ) ;
95
+ err
96
+ } ) ?;
97
+
98
+ let subgraph_definition = SubgraphDefinition :: new ( subgraph_name, url, & schema) ;
99
+ subgraphs. push ( subgraph_definition) ;
100
+ }
101
+ SchemaSource :: SubgraphIntrospection { subgraph_url } => {
102
+ // given a federated introspection URL, use subgraph introspect to
103
+ // obtain SDL and add it to subgraph_definition.
104
+ let client = Client :: new ( & subgraph_url. to_string ( ) ) ;
105
+
106
+ let introspection_response = introspect:: run ( & client, & HashMap :: new ( ) ) ?;
107
+ let schema = introspection_response. result ;
108
+
109
+ // We don't require a routing_url for this variant of a schema,
110
+ // if none are provided, just use an empty string.
111
+ let url = & subgraph_data
112
+ . routing_url
113
+ . clone ( )
114
+ . unwrap_or_else ( || subgraph_url. to_string ( ) ) ;
115
+
116
+ let subgraph_definition = SubgraphDefinition :: new ( subgraph_name, url, & schema) ;
117
+ subgraphs. push ( subgraph_definition) ;
118
+ }
119
+ SchemaSource :: Subgraph { graphref, subgraph } => {
120
+ // given a graphref and subgraph, run subgraph fetch to
121
+ // obtain SDL and add it to subgraph_definition.
122
+ let client = client_config. get_client ( & profile_name) ?;
123
+ let graphref = parse_graph_ref ( graphref) ?;
124
+ let schema = fetch:: run (
125
+ fetch:: fetch_subgraph_query:: Variables {
126
+ graph_id : graphref. name . clone ( ) ,
127
+ variant : graphref. variant . clone ( ) ,
128
+ } ,
129
+ & client,
130
+ subgraph,
131
+ ) ?;
132
+
133
+ // We don't require a routing_url for this variant of a schema,
134
+ // if none are provided, just use an empty string.
135
+ //
136
+ // TODO: this should eventually get the url from the registry
137
+ // and use that when no routing_url is provided.
138
+ let url = & subgraph_data. routing_url . clone ( ) . unwrap_or_default ( ) ;
139
+
140
+ let subgraph_definition = SubgraphDefinition :: new ( subgraph_name, url, & schema) ;
141
+ subgraphs. push ( subgraph_definition) ;
142
+ }
143
+ }
144
+ }
145
+
146
+ Ok ( subgraphs)
147
+ }
148
+
149
+ #[ cfg( test) ]
150
+ mod tests {
151
+ use super :: * ;
152
+ use assert_fs:: TempDir ;
153
+ use houston as houston_config;
154
+ use houston_config:: Config ;
155
+ use std:: convert:: TryFrom ;
156
+
157
+ fn get_studio_config ( ) -> StudioClientConfig {
158
+ let tmp_home = TempDir :: new ( ) . unwrap ( ) ;
159
+ let tmp_path = Utf8PathBuf :: try_from ( tmp_home. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
160
+ StudioClientConfig :: new ( None , Config :: new ( Some ( & tmp_path) , None ) . unwrap ( ) )
161
+ }
162
+
163
+ #[ test]
164
+ fn it_errs_on_invalid_subgraph_path ( ) {
165
+ let raw_good_yaml = r#"subgraphs:
166
+ films:
167
+ routing_url: https://films.example.com
168
+ schema:
169
+ file: ./films-do-not-exist.graphql
170
+ people:
171
+ routing_url: https://people.example.com
172
+ schema:
173
+ file: ./people-do-not-exist.graphql"# ;
174
+ let tmp_home = TempDir :: new ( ) . unwrap ( ) ;
175
+ let mut config_path = Utf8PathBuf :: try_from ( tmp_home. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
176
+ config_path. push ( "config.yaml" ) ;
177
+ fs:: write ( & config_path, raw_good_yaml) . unwrap ( ) ;
178
+ let supergraph_config = config:: parse_supergraph_config ( & config_path) . unwrap ( ) ;
179
+ assert ! ( get_subgraph_definitions(
180
+ supergraph_config,
181
+ & config_path,
182
+ get_studio_config( ) ,
183
+ "profile"
184
+ )
185
+ . is_err( ) )
186
+ }
187
+
188
+ #[ test]
189
+ fn it_can_get_subgraph_definitions_from_fs ( ) {
190
+ let raw_good_yaml = r#"subgraphs:
191
+ films:
192
+ routing_url: https://films.example.com
193
+ schema:
194
+ file: ./films.graphql
195
+ people:
196
+ routing_url: https://people.example.com
197
+ schema:
198
+ file: ./people.graphql"# ;
199
+ let tmp_home = TempDir :: new ( ) . unwrap ( ) ;
200
+ let mut config_path = Utf8PathBuf :: try_from ( tmp_home. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
201
+ config_path. push ( "config.yaml" ) ;
202
+ fs:: write ( & config_path, raw_good_yaml) . unwrap ( ) ;
203
+ let tmp_dir = config_path. parent ( ) . unwrap ( ) . to_path_buf ( ) ;
204
+ let films_path = tmp_dir. join ( "films.graphql" ) ;
205
+ let people_path = tmp_dir. join ( "people.graphql" ) ;
206
+ fs:: write ( films_path, "there is something here" ) . unwrap ( ) ;
207
+ fs:: write ( people_path, "there is also something here" ) . unwrap ( ) ;
208
+ let supergraph_config = config:: parse_supergraph_config ( & config_path) . unwrap ( ) ;
209
+ assert ! ( get_subgraph_definitions(
210
+ supergraph_config,
211
+ & config_path,
212
+ get_studio_config( ) ,
213
+ "profile"
214
+ )
215
+ . is_ok( ) )
216
+ }
217
+
218
+ #[ test]
219
+ fn it_can_compute_relative_schema_paths ( ) {
220
+ let raw_good_yaml = r#"subgraphs:
221
+ films:
222
+ routing_url: https://films.example.com
223
+ schema:
224
+ file: ../../films.graphql
225
+ people:
226
+ routing_url: https://people.example.com
227
+ schema:
228
+ file: ../../people.graphql"# ;
229
+ let tmp_home = TempDir :: new ( ) . unwrap ( ) ;
230
+ let tmp_dir = Utf8PathBuf :: try_from ( tmp_home. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
231
+ let mut config_path = tmp_dir. clone ( ) ;
232
+ config_path. push ( "layer" ) ;
233
+ config_path. push ( "layer" ) ;
234
+ fs:: create_dir_all ( & config_path) . unwrap ( ) ;
235
+ config_path. push ( "config.yaml" ) ;
236
+ fs:: write ( & config_path, raw_good_yaml) . unwrap ( ) ;
237
+ let films_path = tmp_dir. join ( "films.graphql" ) ;
238
+ let people_path = tmp_dir. join ( "people.graphql" ) ;
239
+ fs:: write ( films_path, "there is something here" ) . unwrap ( ) ;
240
+ fs:: write ( people_path, "there is also something here" ) . unwrap ( ) ;
241
+ let supergraph_config = config:: parse_supergraph_config ( & config_path) . unwrap ( ) ;
242
+ let subgraph_definitions = get_subgraph_definitions (
243
+ supergraph_config,
244
+ & config_path,
245
+ get_studio_config ( ) ,
246
+ "profile" ,
247
+ )
248
+ . unwrap ( ) ;
249
+ let film_subgraph = subgraph_definitions. get ( 0 ) . unwrap ( ) ;
250
+ let people_subgraph = subgraph_definitions. get ( 1 ) . unwrap ( ) ;
251
+
252
+ assert_eq ! ( film_subgraph. name, "films" ) ;
253
+ assert_eq ! ( film_subgraph. url, "https://films.example.com" ) ;
254
+ assert_eq ! ( film_subgraph. type_defs, "there is something here" ) ;
255
+ assert_eq ! ( people_subgraph. name, "people" ) ;
256
+ assert_eq ! ( people_subgraph. url, "https://people.example.com" ) ;
257
+ assert_eq ! ( people_subgraph. type_defs, "there is also something here" ) ;
258
+ }
259
+ }
0 commit comments