@@ -10,6 +10,7 @@ use router::Router;
1010use server_config:: { ServerConfig , Error , Environment } ;
1111use signature:: Signature ;
1212use std:: env;
13+ use std:: fmt:: Display ;
1314use std:: fs:: File ;
1415use std:: io:: { Read , Write } ;
1516use std:: path:: Path ;
@@ -18,10 +19,27 @@ use task_manager::TaskManager;
1819use uuid:: Uuid ;
1920
2021const ENV_CONFIG_KEY : & ' static str = "HOOKSHOT_CONFIG" ;
22+ const ENV_INSECURE_KEY : & ' static str = "HOOKSHOT_INSECURE" ;
2123
2224header ! { ( XHubSignature , "X-Hub-Signature" ) => [ String ] }
2325header ! { ( XSignature , "X-Signature" ) => [ String ] }
2426
27+ struct TaskStatusPrinter {
28+ task_id : Uuid
29+ }
30+ impl TaskStatusPrinter {
31+ fn print < T : AsRef < str > + Display > ( & self , msg : T ) {
32+ println ! ( "[{}]: {}" , self . task_id, msg) ;
33+ }
34+ }
35+
36+ fn skip_signature_check ( ) -> bool {
37+ match ENV_INSECURE_KEY {
38+ "true" | "t" | "1" => true ,
39+ _ => false ,
40+ }
41+ }
42+
2543fn print_usage ( program : & str , opts : Options ) {
2644 let brief = format ! ( "Usage: {} [options]" , program) ;
2745 print ! ( "{}" , opts. usage( & brief) ) ;
@@ -38,15 +56,12 @@ pub fn main() {
3856 let matches = match opts. parse ( & args[ 1 ..] ) {
3957 Ok ( m) => m,
4058 Err ( f) => {
41- println ! ( "[error]: {}" , f. to_string( ) ) ;
42- print_usage ( & program, opts) ;
43- return ;
59+ println ! ( "[error]: {}" , f) ;
60+ return print_usage ( & program, opts) ;
4461 }
4562 } ;
4663 if matches. opt_present ( "h" ) {
47- println ! ( "printing out usage" ) ;
48- print_usage ( & program, opts) ;
49- return ;
64+ return print_usage ( & program, opts) ;
5065 }
5166 let config_file = match matches. opt_str ( "c" ) {
5267 Some ( file) => file,
@@ -55,10 +70,9 @@ pub fn main() {
5570 match env:: var ( ENV_CONFIG_KEY ) {
5671 Ok ( file) => file,
5772 Err ( _) => {
58- println ! ( "[error]: Could not load config from environment or command \
59- line.\n \n Pass --config <FILE> option or set the HOOKSHOT_CONFIG \
60- environment variable") ;
61- return ;
73+ return println ! ( "[error]: Could not load config from environment or command \
74+ line.\n \n Pass --config <FILE> option or set the HOOKSHOT_CONFIG \
75+ environment variable") ;
6276 }
6377 }
6478 }
@@ -68,18 +82,15 @@ pub fn main() {
6882 Ok ( config) => start_server ( config) ,
6983 Err ( e) => match e {
7084 Error :: FileOpenError | Error :: FileReadError => {
71- println ! ( "[error]: Error opening or reading config file {}" ,
72- config_file) ;
73- return ;
85+ return println ! ( "[error]: Error opening or reading config file {}" ,
86+ config_file) ;
7487 }
7588 Error :: ParseError => {
76- println ! ( "[error]: Could not parse {}, make sure it is valid TOML" ,
77- config_file) ;
78- return ;
89+ return println ! ( "[error]: Could not parse {}, make sure it is valid TOML" ,
90+ config_file) ;
7991 }
8092 _ => {
81- println ! ( "[error]: Could not validate file: {}" , e) ;
82- return ;
93+ return println ! ( "[error]: Could not validate file: {}" , e) ;
8394 }
8495 } ,
8596 }
@@ -102,6 +113,8 @@ fn start_server(config: ServerConfig) {
102113 Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) , status:: Ok , "okay" ) ) )
103114 } ) ;
104115
116+ // Show the status of a specific task by UUID. If there is no log file by
117+ // that name or if the log file can't be read for any reason return a 404.
105118 let config_clone = config. clone ( ) ;
106119 router. get ( "/tasks/:uuid" , move |req : & mut Request | {
107120 let file_not_found = Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
@@ -116,19 +129,14 @@ fn start_server(config: ServerConfig) {
116129 let logfile_path = Path :: new ( & config_clone. log_root . to_string ( ) )
117130 . join ( format ! ( "{}.log" , uuid. to_string( ) ) ) ;
118131
119- let mut file = {
120- match File :: open ( & logfile_path) {
121- Ok ( file) => file,
122- Err ( _) => return file_not_found,
123- }
132+ let mut file = match File :: open ( & logfile_path) {
133+ Ok ( file) => file,
134+ Err ( _) => return file_not_found,
124135 } ;
125136
126- let content = {
127- let mut content = String :: new ( ) ;
128- match file. read_to_string ( & mut content) {
129- Ok ( _) => content,
130- Err ( _) => return file_not_found,
131- }
137+ let mut content = String :: new ( ) ;
138+ if let Err ( _) = file. read_to_string ( & mut content) {
139+ return file_not_found;
132140 } ;
133141
134142 Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) , status:: Ok , content) ) )
@@ -139,75 +147,83 @@ fn start_server(config: ServerConfig) {
139147 let checkout_root = config. checkout_root . to_string ( ) ;
140148 let config_clone = config. clone ( ) ;
141149
142- router. post ( "/hookshot " , move |req : & mut Request | {
150+ router. post ( "/tasks " , move |req : & mut Request | {
143151 let task_id = Uuid :: new_v4 ( ) ;
152+ let task_status = TaskStatusPrinter { task_id : task_id } ;
144153 let log_root = & config_clone. log_root . to_string ( ) ;
145- println ! ( "[{}]: request received, processing" , task_id) ;
146-
147- // Get the signature from the header. We support both `X-Hub-Signature` and
148- // `X-Signature` but they both represent the same type underneath, a
149- // string. It might eventually be better to put this functionality on the
150- // Signature type itself.
151- println ! ( "[{}]: looking up signature" , task_id) ;
152- let signature = {
153- let possible_headers = ( req. headers . get :: < XSignature > ( ) ,
154- req. headers . get :: < XHubSignature > ( ) ) ;
155-
156- let signature_string = match possible_headers {
157- ( Some ( h) , None ) => h. to_string ( ) ,
158- ( None , Some ( h) ) => h. to_string ( ) ,
159- ( None , None ) => {
160- println ! ( "[{}]: missing signature" , task_id) ;
161- return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
162- status:: Unauthorized ,
163- "missing signature" ) ) ) ;
164- }
165- ( Some ( _) , Some ( _) ) => {
166- println ! ( "[{}]: too many signatures" , task_id) ;
167- return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
168- status:: Unauthorized ,
169- "too many signatures" ) ) ) ;
170- }
171- } ;
172154
173- match Signature :: from_str ( & signature_string) {
174- Some ( signature) => signature,
175- None => {
176- println ! ( "[{}]: could not parse signature" , task_id) ;
177- return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
178- status:: Unauthorized ,
179- "could not parse signature" ) ) ) ;
155+ task_status. print ( "request received, processing" ) ;
156+
157+ let mut signature = None ;
158+ if !skip_signature_check ( ) {
159+ task_status. print ( "looking up signature" ) ;
160+
161+ // Get the signature from the header. We support both `X-Hub-Signature` and
162+ // `X-Signature` but they both represent the same type underneath, a
163+ // string. It might eventually be better to put this functionality on the
164+ // Signature type itself.
165+ signature = {
166+ let possible_headers = ( req. headers . get :: < XSignature > ( ) ,
167+ req. headers . get :: < XHubSignature > ( ) ) ;
168+
169+ let signature_string = match possible_headers {
170+ ( Some ( h) , None ) => h. to_string ( ) ,
171+ ( None , Some ( h) ) => h. to_string ( ) ,
172+ ( None , None ) => {
173+ task_status. print ( "missing signature" ) ;
174+ return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
175+ status:: Unauthorized ,
176+ "missing signature" ) ) ) ;
177+ }
178+ ( Some ( _) , Some ( _) ) => {
179+ task_status. print ( "too many signatures" ) ;
180+ return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
181+ status:: Unauthorized ,
182+ "too many signatures" ) ) ) ;
183+ }
184+ } ;
185+
186+ match Signature :: from_str ( & signature_string) {
187+ Some ( signature) => Some ( signature) ,
188+ None => {
189+ task_status. print ( "could not parse signature" ) ;
190+ return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
191+ status:: Unauthorized ,
192+ "could not parse signature" ) ) ) ;
193+ }
180194 }
181- }
182- } ;
195+ } ;
196+ }
183197
184- println ! ( "[{}]: loading body into string", task_id ) ;
198+ task_status . print ( " loading body into string") ;
185199 let mut payload = String :: new ( ) ;
186200 if req. body . read_to_string ( & mut payload) . is_err ( ) {
187- println ! ( "[{}]: could not read body into string", task_id ) ;
201+ task_status . print ( " could not read body into string") ;
188202 return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) , status:: InternalServerError ) ) ) ;
189203 }
190204
191- // Bail out if the signature doesn't match what we're expecting.
192- println ! ( "[{}]: signature found, verifying" , task_id) ;
193- if signature. verify ( & payload, & config_clone. secret ) == false {
194- println ! ( "[{}]: signature mismatch" , task_id) ;
195- return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
196- status:: Unauthorized ,
197- "signature doesn't match" ) ) ) ;
205+ if !skip_signature_check ( ) {
206+ // Bail out if the signature doesn't match what we're expecting.
207+ task_status. print ( "signature found, verifying" ) ;
208+ if signature. unwrap ( ) . verify ( & payload, & config_clone. secret ) == false {
209+ task_status. print ( "signature mismatch" ) ;
210+ return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
211+ status:: Unauthorized ,
212+ "signature doesn't match" ) ) ) ;
213+ }
198214 }
199215
200216 // Try to parse the message.
201217 // TODO: we can be smarter about this. If we see the XHubSignature
202218 // above, we should try to parse as a github message, otherwise go
203219 // simple message.
204- println ! ( "[{}]: attempting to parse message from payload", task_id ) ;
220+ task_status . print ( " attempting to parse message from payload") ;
205221 let repo = match SimpleMessage :: from_str ( & payload) {
206222 Ok ( message) => GitRepo :: from ( message, & checkout_root) ,
207223 Err ( _) => match GitHubMessage :: from_str ( & payload) {
208224 Ok ( message) => GitRepo :: from ( message, & checkout_root) ,
209225 Err ( _) => {
210- println ! ( "[{}]: could not parse message", task_id ) ;
226+ task_status . print ( " could not parse message") ;
211227 return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
212228 status:: BadRequest ,
213229 "could not parse message" ) ) ) ;
@@ -220,9 +236,8 @@ fn start_server(config: ServerConfig) {
220236 & repo. refstring ) {
221237 Ok ( environment) => environment,
222238 Err ( _) => {
223- println ! ( "[{}]: warning: error loading environment for {}, definition flawed" ,
224- task_id,
225- repo. fully_qualified_branch( ) ) ;
239+ task_status. print ( format ! ( "warning: error loading environment for {}, definition flawed" ,
240+ repo. fully_qualified_branch( ) ) ) ;
226241 Environment :: new ( )
227242 }
228243 } ;
@@ -234,7 +249,7 @@ fn start_server(config: ServerConfig) {
234249 let mut logfile = match File :: create ( & logfile_path) {
235250 Ok ( file) => file,
236251 Err ( e) => {
237- println ! ( "[{}]: could not open logfile for writing: {}" , task_id , e ) ;
252+ task_status . print ( format ! ( "could not open logfile for writing: {}" , e ) ) ;
238253 return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
239254 status:: InternalServerError ) ) ) ;
240255 }
@@ -249,26 +264,28 @@ fn start_server(config: ServerConfig) {
249264 secret : config_clone. secret . clone ( ) ,
250265 } ;
251266
252- println ! ( "[{}]: acquiring task manager lock", task_id ) ;
267+ task_status . print ( " acquiring task manager lock") ;
253268 {
254269 let mut task_manager = shared_manager. lock ( ) . unwrap ( ) ;
255270 let key = task_manager. ensure_queue ( task. repo . fully_qualified_branch ( ) ) ;
256271
257- println ! ( "[{}]: attempting to schedule", task_id ) ;
272+ task_status . print ( " attempting to schedule") ;
258273 match task_manager. add_task ( & key, task) {
259- Ok ( _) => println ! ( "[{}]: scheduled", task_id ) ,
274+ Ok ( _) => task_status . print ( " scheduled") ,
260275 Err ( _) => {
261- println ! ( "[{}]: could not add task to queue", task_id ) ;
276+ task_status . print ( " could not add task to queue") ;
262277 return Ok ( Response :: with ( ( Header ( Connection :: close ( ) ) ,
263278 status:: ServiceUnavailable ) ) ) ;
264279 }
265280 }
266281 }
267- println ! ( "[{}]: releasing task manager lock", task_id ) ;
268- println ! ( "[{}]: request complete", task_id ) ;
282+ task_status . print ( " releasing task manager lock") ;
283+ task_status . print ( " request complete") ;
269284
270285 logfile. write_all ( b"task pending" ) ;
271286
287+ // TODO: probably shouldn't hardcode http://, someone might want to run
288+ // this behind HTTPS someday.
272289 let location = format ! ( "http://{}:{}/tasks/{}" ,
273290 config_clone. hostname,
274291 config_clone. port,
0 commit comments