1
1
use std:: {
2
- collections:: HashMap ,
2
+ collections:: { HashMap , HashSet } ,
3
3
path:: { Path , PathBuf } ,
4
4
} ;
5
5
@@ -11,6 +11,8 @@ use anyhow::{bail, Context};
11
11
use docker:: DockerService ;
12
12
use python:: PythonService ;
13
13
14
+ pub use docker:: DockerImage ;
15
+
14
16
/// All the services that are running for a test.
15
17
#[ derive( Default ) ]
16
18
pub struct Services {
@@ -19,25 +21,21 @@ pub struct Services {
19
21
20
22
impl Services {
21
23
/// Start all the required services given a path to service definitions
22
- pub fn start ( config : & ServicesConfig , working_dir : & Path ) -> anyhow:: Result < Self > {
24
+ pub fn start ( config : ServicesConfig , working_dir : & Path ) -> anyhow:: Result < Self > {
25
+ let lock_dir = working_dir. join ( ".service-locks" ) ;
26
+ std:: fs:: create_dir ( & lock_dir) . context ( "could not create service lock dir" ) ?;
23
27
let mut services = Vec :: new ( ) ;
24
- for required_service in & config. services {
25
- let service_definition_extension =
26
- config. definitions . get ( required_service) . map ( |e| e. as_str ( ) ) ;
27
- let mut service: Box < dyn Service > = match service_definition_extension {
28
- Some ( "py" ) => Box :: new ( PythonService :: start (
29
- required_service,
30
- & config. definitions_path ,
28
+ for service_def in config. service_definitions {
29
+ let mut service: Box < dyn Service > = match service_def. kind {
30
+ ServiceKind :: Python { script } => Box :: new ( PythonService :: start (
31
+ & service_def. name ,
32
+ & script,
31
33
working_dir,
34
+ & lock_dir,
32
35
) ?) ,
33
- Some ( "Dockerfile" ) => Box :: new ( DockerService :: start (
34
- required_service,
35
- & config. definitions_path ,
36
- ) ?) ,
37
- Some ( extension) => {
38
- bail ! ( "service definitions with the '{extension}' extension are not supported" )
36
+ ServiceKind :: Docker { image } => {
37
+ Box :: new ( DockerService :: start ( & service_def. name , image, & lock_dir) ?)
39
38
}
40
- None => bail ! ( "no service definition found for '{required_service}'" ) ,
41
39
} ;
42
40
service. ready ( ) ?;
43
41
services. push ( service) ;
@@ -88,37 +86,46 @@ impl<'a> IntoIterator for &'a Services {
88
86
}
89
87
90
88
pub struct ServicesConfig {
91
- services : Vec < String > ,
92
- definitions_path : PathBuf ,
93
- definitions : HashMap < String , String > ,
89
+ /// Definitions of all services to be used.
90
+ service_definitions : Vec < ServiceDefinition > ,
94
91
}
95
92
96
93
impl ServicesConfig {
97
- /// Create a new services config a list of services to start.
94
+ /// Create a new services config with a list of built-in services to start.
98
95
///
99
- /// The services are expected to have a definition file in the `services` directory with the same name as the service.
100
- pub fn new ( services : Vec < String > ) -> anyhow:: Result < Self > {
101
- let definitions = PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "services" ) ;
102
- let service_definitions = service_definitions ( & definitions) ?;
96
+ /// The built-in services are expected to have a definition file in the `services` directory with the same name as the service.
97
+ pub fn new < ' a > ( builtins : impl Into < Vec < & ' a str > > ) -> anyhow:: Result < Self > {
98
+ let definitions_path = PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "services" ) ;
99
+ let service_definitions = get_builtin_service_definitions (
100
+ builtins. into ( ) . into_iter ( ) . collect ( ) ,
101
+ & definitions_path,
102
+ ) ?;
103
103
Ok ( Self {
104
- services,
105
- definitions_path : definitions,
106
- definitions : service_definitions,
104
+ service_definitions,
107
105
} )
108
106
}
109
107
108
+ pub fn add_service ( & mut self , service : ServiceDefinition ) {
109
+ self . service_definitions . push ( service) ;
110
+ }
111
+
110
112
/// Configure no services
111
113
pub fn none ( ) -> Self {
112
114
Self {
113
- services : Vec :: new ( ) ,
114
- definitions_path : PathBuf :: new ( ) ,
115
- definitions : HashMap :: new ( ) ,
115
+ service_definitions : Vec :: new ( ) ,
116
116
}
117
117
}
118
118
}
119
119
120
120
/// Get all of the service definitions returning a HashMap of the service name to the service definition file extension.
121
- fn service_definitions ( service_definitions_path : & Path ) -> anyhow:: Result < HashMap < String , String > > {
121
+ fn get_builtin_service_definitions (
122
+ mut builtins : HashSet < & str > ,
123
+ service_definitions_path : & Path ,
124
+ ) -> anyhow:: Result < Vec < ServiceDefinition > > {
125
+ if builtins. is_empty ( ) {
126
+ return Ok ( Vec :: new ( ) ) ;
127
+ }
128
+
122
129
std:: fs:: read_dir ( service_definitions_path)
123
130
. with_context ( || {
124
131
format ! (
@@ -139,10 +146,43 @@ fn service_definitions(service_definitions_path: &Path) -> anyhow::Result<HashMa
139
146
. context ( "service definition did not have an extension" ) ?;
140
147
Ok ( ( file_name. to_owned ( ) , file_extension. to_owned ( ) ) )
141
148
} )
142
- . filter ( |r| !matches ! ( r , Ok ( ( _, extension) ) if extension == "lock" ) )
149
+ . filter ( |r| !matches ! ( r, Ok ( ( _, extension) ) if extension == "lock" ) )
150
+ . filter ( move |r| match r {
151
+ Ok ( ( service, _) ) => builtins. remove ( service. as_str ( ) ) ,
152
+ _ => false ,
153
+ } )
154
+ . map ( |r| {
155
+ let ( name, extension) = r?;
156
+ Ok ( ServiceDefinition {
157
+ name : name. clone ( ) ,
158
+ kind : match extension. as_str ( ) {
159
+ "py" => ServiceKind :: Python {
160
+ script : service_definitions_path. join ( format ! ( "{}.py" , name) ) ,
161
+ } ,
162
+ "Dockerfile" => ServiceKind :: Docker {
163
+ image : docker:: DockerImage :: FromDockerfile (
164
+ service_definitions_path. join ( format ! ( "{}.Dockerfile" , name) ) ,
165
+ ) ,
166
+ } ,
167
+ _ => bail ! ( "unsupported service definition extension '{}'" , extension) ,
168
+ } ,
169
+ } )
170
+ } )
143
171
. collect ( )
144
172
}
145
173
174
+ /// A service definition.
175
+ pub struct ServiceDefinition {
176
+ pub name : String ,
177
+ pub kind : ServiceKind ,
178
+ }
179
+
180
+ /// The kind of service.
181
+ pub enum ServiceKind {
182
+ Python { script : PathBuf } ,
183
+ Docker { image : DockerImage } ,
184
+ }
185
+
146
186
/// An external service a test may depend on.
147
187
pub trait Service {
148
188
/// The name of the service.
0 commit comments