44 "crypto/md5"
55 "encoding/hex"
66 "encoding/json"
7+ "errors"
78 "fmt"
8- "io/ioutil "
9+ "io"
910 "log"
1011 "net/http"
1112 "os"
@@ -17,51 +18,39 @@ import (
1718 "github.com/spf13/cobra"
1819)
1920
20- // This is the default value that should be overridden in the
21+ // Version is the default value that should be overridden in the
2122// build/release process.
2223var Version = "dev"
2324
24- func home (w http.ResponseWriter , req * http.Request ) {
25- fmt .Fprintf (w , `# GitLab Honeycomb Buildevents Webhooks Sink
25+ func home (w http.ResponseWriter , _ * http.Request ) {
26+ _ , err := fmt .Fprintf (w , `# GitLab Honeycomb Buildevents Webhooks Sink
2627
2728GET /healthz: healthcheck
2829
2930POST /api/message: receive array of notifications
3031` )
32+ if err != nil {
33+ log .Printf ("home: failed to write to http response writer: %s" , err )
34+ }
3135}
3236
33- func healthz (w http.ResponseWriter , req * http.Request ) {
34- fmt . Fprintf ( w , "OK \n " )
37+ func healthz (w http.ResponseWriter , _ * http.Request ) {
38+ w . WriteHeader ( http . StatusOK )
3539}
3640
37- func createEvent (cfg * libhoney.Config ) * libhoney.Event {
41+ func createEvent (cfg * libhoney.Config ) ( * libhoney.Event , error ) {
3842 libhoney .UserAgentAddition = fmt .Sprintf ("buildevents/%s" , Version )
3943 libhoney .UserAgentAddition += fmt .Sprintf (" (%s)" , "GitLab-CI" )
4044
4145 if cfg .APIKey == "" {
4246 cfg .Transmission = & transmission.WriterSender {}
4347 }
44- libhoney .Init (* cfg )
4548
4649 ev := libhoney .NewEvent ()
4750 ev .AddField ("ci_provider" , "GitLab-CI" )
4851 ev .AddField ("meta.version" , Version )
4952
50- return ev
51- }
52-
53- func parseTime (dt string ) (* time.Time , error ) {
54- var timestamp time.Time
55- // Try GitLab upstream datetime format
56- timestamp , err := time .Parse ("2006-01-02 15:04:05 MST" , dt )
57- if err != nil {
58- // Try our GitLab Enterprise datetime format
59- timestamp , err = time .Parse ("2006-01-02 15:04:05 -0700" , dt )
60- if err != nil {
61- return nil , err
62- }
63- }
64- return & timestamp , nil
53+ return ev , nil
6554}
6655
6756func createTraceFromPipeline (cfg * libhoney.Config , p Pipeline ) (* libhoney.Event , error ) {
@@ -72,10 +61,14 @@ func createTraceFromPipeline(cfg *libhoney.Config, p Pipeline) (*libhoney.Event,
7261 return nil , nil
7362 }
7463 traceID := fmt .Sprint (p .ObjectAttributes .ID )
75- ev := createEvent (cfg )
64+ ev , err := createEvent (cfg )
65+ if err != nil {
66+ return nil , err
67+ }
68+
7669 defer ev .Send ()
7770 buildURL := fmt .Sprintf ("%s/-/pipelines/%d" , p .Project .WebURL , p .ObjectAttributes .ID )
78- ev .Add (map [string ]interface {}{
71+ err = ev .Add (map [string ]interface {}{
7972 // Basic trace information
8073 "service_name" : "pipeline" ,
8174 "trace.span_id" : traceID ,
@@ -98,16 +91,15 @@ func createTraceFromPipeline(cfg *libhoney.Config, p Pipeline) (*libhoney.Event,
9891 "duration_ms" : p .ObjectAttributes .Duration * 1000 ,
9992 "queued_duration_ms" : p .ObjectAttributes .QueuedDuration * 1000 ,
10093 })
101-
102- timestamp , err := parseTime (p .ObjectAttributes .CreatedAt )
103- // This error handling is a bit janky, I should tidy it up
10494 if err != nil {
105- log .Println ("Failed to parse timestamp:" , err )
106- fmt .Printf ("%+v\n " , ev )
107- return ev , err
95+ return nil , fmt .Errorf ("failed to add fields to event: %w" , err )
10896 }
109- ev .Timestamp = * timestamp
110- fmt .Printf ("%+v\n " , ev )
97+
98+ if p .ObjectAttributes .CreatedAt .IsZero () {
99+ return nil , errors .New ("Pipeline.ObjectAttributes.CreatedAt is zero" )
100+ }
101+ ev .Timestamp = p .ObjectAttributes .CreatedAt
102+ log .Printf ("%+v\n " , ev )
111103 return ev , nil
112104}
113105
@@ -122,9 +114,13 @@ func createTraceFromJob(cfg *libhoney.Config, j Job) (*libhoney.Event, error) {
122114 md5HashInBytes := md5 .Sum ([]byte (j .BuildName ))
123115 md5HashInString := hex .EncodeToString (md5HashInBytes [:])
124116 spanID := md5HashInString
125- ev := createEvent (cfg )
117+ ev , err := createEvent (cfg )
118+ if err != nil {
119+ return nil , err
120+ }
121+
126122 defer ev .Send ()
127- ev .Add (map [string ]interface {}{
123+ err = ev .Add (map [string ]interface {}{
128124 // Basic trace information
129125 "service_name" : "job" ,
130126 "trace.span_id" : spanID ,
@@ -149,15 +145,15 @@ func createTraceFromJob(cfg *libhoney.Config, j Job) (*libhoney.Event, error) {
149145 "duration_ms" : j .BuildDuration * 1000 ,
150146 "queued_duration_ms" : j .BuildQueuedDuration * 1000 ,
151147 })
152- timestamp , err := parseTime (j .BuildStartedAt )
153- // This error handling is a bit janky, I should tidy it up
154148 if err != nil {
155- log .Println ("Failed to parse timestamp:" , err )
156- fmt .Printf ("%+v\n " , ev )
157- return ev , err
149+ return nil , fmt .Errorf ("failed to add fields to event: %w" , err )
158150 }
159- ev .Timestamp = * timestamp
160- fmt .Printf ("%+v\n " , ev )
151+
152+ if j .BuildStartedAt .IsZero () {
153+ return nil , errors .New ("BuildStartedAt time is not set" )
154+ }
155+ ev .Timestamp = j .BuildStartedAt
156+ log .Printf ("%+v\n " , ev )
161157 return ev , nil
162158}
163159
@@ -166,7 +162,7 @@ func handlePipeline(cfg *libhoney.Config, w http.ResponseWriter, body []byte) {
166162 var pipeline Pipeline
167163 err := json .Unmarshal (body , & pipeline )
168164 if err != nil {
169- log .Print ("Error unmarshalling request body." )
165+ log .Printf ("Error unmarshalling request body: %s" , err )
170166 _ , printErr := fmt .Fprintf (w , "Error unmarshalling request body." )
171167 if printErr != nil {
172168 log .Print ("Error printing error on error unmarshalling request body." )
@@ -176,10 +172,17 @@ func handlePipeline(cfg *libhoney.Config, w http.ResponseWriter, body []byte) {
176172 _ , err = createTraceFromPipeline (cfg , pipeline )
177173 if err != nil {
178174 w .WriteHeader (http .StatusInternalServerError )
179- fmt .Fprintf (w , "Error creating trace from pipeline object: %s" , err )
175+ _ , respErr := fmt .Fprintf (w , "Error creating trace from pipeline object: %s" , err )
176+ if respErr != nil {
177+ log .Printf ("failed to write error response: %s" , respErr )
178+ }
180179 return
181180 }
182- fmt .Fprintf (w , "Thanks!\n " )
181+
182+ _ , respErr := fmt .Fprint (w , "Thanks!\n " )
183+ if respErr != nil {
184+ log .Printf ("failed to write success response: %s" , respErr )
185+ }
183186}
184187
185188// buildevents step $CI_PIPELINE_ID $STEP_SPAN_ID $STEP_START $CI_JOB_NAME
@@ -198,27 +201,37 @@ func handleJob(cfg *libhoney.Config, w http.ResponseWriter, body []byte) {
198201 _ , err = createTraceFromJob (cfg , job )
199202 if err != nil {
200203 w .WriteHeader (http .StatusInternalServerError )
201- fmt .Fprintf (w , "Error creating trace from job object: %s" , err )
204+ _ , respErr := fmt .Fprintf (w , "Error creating trace from job object: %s" , err )
205+ if respErr != nil {
206+ log .Printf ("failed to write error response: %s" , respErr )
207+ }
202208 return
203209 }
204- fmt .Fprintf (w , "Thanks!\n " )
210+
211+ _ , respErr := fmt .Fprint (w , "Thanks!\n " )
212+ if respErr != nil {
213+ log .Printf ("failed to write success response: %s" , respErr )
214+ }
205215}
206216
207217func handleRequest (cfg * libhoney.Config , w http.ResponseWriter , req * http.Request ) {
208218 if req .Method != http .MethodPost {
209219 http .Error (w , "Unsupported method" , http .StatusMethodNotAllowed )
210220 return
211221 }
212- eventHeaders := req .Header ["X-Gitlab-Event" ]
213- if len ( eventHeaders ) < 1 {
222+ eventHeaders , exists := req .Header ["X-Gitlab-Event" ]
223+ if ! exists {
214224 http .Error (w , "Missing header: X-Giitlab-Event" , http .StatusBadRequest )
215225 return
216- } else if len (eventHeaders ) > 1 {
226+ }
227+
228+ if len (eventHeaders ) > 1 {
217229 http .Error (w , "Invalid header: X-Gitlab-Event" , http .StatusBadRequest )
218230 return
219231 }
232+
220233 eventType := eventHeaders [0 ]
221- body , err := ioutil .ReadAll (req .Body )
234+ body , err := io .ReadAll (req .Body )
222235 if err != nil {
223236 log .Print ("Error reading request body." )
224237 _ , printErr := fmt .Fprintf (w , "Error reading request body." )
@@ -227,15 +240,16 @@ func handleRequest(cfg *libhoney.Config, w http.ResponseWriter, req *http.Reques
227240 }
228241 return
229242 }
230- if eventType == "Pipeline Hook" {
231- fmt .Println ("Received pipeline webhook:" , string (body ))
243+
244+ switch eventType {
245+ case "Pipeline Hook" :
246+ log .Println ("Received pipeline webhook:" , string (body ))
232247 handlePipeline (cfg , w , body )
233- } else if eventType == "Job Hook" {
234- fmt .Println ("Received job webhook:" , string (body ))
248+ case "Job Hook" :
249+ log .Println ("Received job webhook:" , string (body ))
235250 handleJob (cfg , w , body )
236- } else {
251+ default :
237252 http .Error (w , fmt .Sprintf ("Invalid event type: %s" , eventType ), http .StatusBadRequest )
238- return
239253 }
240254}
241255
@@ -252,17 +266,26 @@ about your Continuous Integration builds.`,
252266 root .PersistentFlags ().StringVarP (& cfg .APIKey , "apikey" , "k" , "" , "[env.BUILDEVENT_APIKEY] the Honeycomb authentication token" )
253267 if apikey , ok := os .LookupEnv ("BUILDEVENT_APIKEY" ); ok {
254268 // https://github.com/spf13/viper/issues/461#issuecomment-366831834
255- root .PersistentFlags ().Lookup ("apikey" ).Value .Set (apikey )
269+ err := root .PersistentFlags ().Lookup ("apikey" ).Value .Set (apikey )
270+ if err != nil {
271+ log .Fatalf ("failed to configure `apikey`: %s" , err )
272+ }
256273 }
257274
258275 root .PersistentFlags ().StringVarP (& cfg .Dataset , "dataset" , "d" , "buildevents" , "[env.BUILDEVENT_DATASET] the name of the Honeycomb dataset to which to send these events" )
259276 if dataset , ok := os .LookupEnv ("BUILDEVENT_DATASET" ); ok {
260- root .PersistentFlags ().Lookup ("dataset" ).Value .Set (dataset )
277+ err := root .PersistentFlags ().Lookup ("dataset" ).Value .Set (dataset )
278+ if err != nil {
279+ log .Fatalf ("failed to configure `dataset`: %s" , err )
280+ }
261281 }
262282
263283 root .PersistentFlags ().StringVarP (& cfg .APIHost , "apihost" , "a" , "https://api.honeycomb.io" , "[env.BUILDEVENT_APIHOST] the hostname for the Honeycomb API server to which to send this event" )
264284 if apihost , ok := os .LookupEnv ("BUILDEVENT_APIHOST" ); ok {
265- root .PersistentFlags ().Lookup ("apihost" ).Value .Set (apihost )
285+ err := root .PersistentFlags ().Lookup ("apihost" ).Value .Set (apihost )
286+ if err != nil {
287+ log .Fatalf ("failed to configure `apihost`: %s" , err )
288+ }
266289 }
267290
268291 return root
@@ -279,6 +302,12 @@ func main() {
279302 libhoney .Close ()
280303 os .Exit (1 )
281304 }
305+
306+ err := libhoney .Init (config )
307+ if err != nil {
308+ log .Fatalf ("failed to initialise libhoney: %s" , err )
309+ }
310+
282311 log .SetOutput (os .Stdout )
283312 mux := http .NewServeMux ()
284313 mux .HandleFunc ("/healthz" , healthz )
@@ -297,7 +326,7 @@ func main() {
297326 ReadTimeout : 5 * time .Second ,
298327 WriteTimeout : 10 * time .Second ,
299328 }
300- fmt .Printf ("Starting server on http://%s\n " , srv .Addr )
329+ log .Printf ("Starting server on http://%s\n " , srv .Addr )
301330 log .Fatal (srv .ListenAndServe ())
302331}
303332
@@ -332,9 +361,9 @@ type Build struct {
332361 Stage string `json:"stage"`
333362 Name string `json:"name"`
334363 Status string `json:"status"`
335- CreatedAt string `json:"created_at"`
336- StartedAt * string `json:"started_at"`
337- FinishedAt * string `json:"finished_at"`
364+ CreatedAt time. Time `json:"created_at"`
365+ StartedAt time. Time `json:"started_at"`
366+ FinishedAt time. Time `json:"finished_at"`
338367 When string `json:"when"`
339368 Manual bool `json:"manual"`
340369 AllowFailure bool `json:"allow_failure"`
@@ -405,8 +434,8 @@ type PipelineObjectAttributes struct {
405434 Source string `json:"source"`
406435 Status string `json:"status"`
407436 Stages []string `json:"stages"`
408- CreatedAt string `json:"created_at"`
409- FinishedAt string `json:"finished_at"`
437+ CreatedAt time. Time `json:"created_at"`
438+ FinishedAt time. Time `json:"finished_at"`
410439 Duration int64 `json:"duration"`
411440 QueuedDuration int64 `json:"queued_duration"`
412441 Variables []Variable `json:"variables"`
@@ -457,9 +486,9 @@ type Job struct {
457486 BuildName string `json:"build_name"`
458487 BuildStage string `json:"build_stage"`
459488 BuildStatus string `json:"build_status"`
460- BuildCreatedAt string `json:"build_created_at"`
461- BuildStartedAt string `json:"build_started_at"`
462- BuildFinishedAt string `json:"build_finished_at"`
489+ BuildCreatedAt time. Time `json:"build_created_at"`
490+ BuildStartedAt time. Time `json:"build_started_at"`
491+ BuildFinishedAt time. Time `json:"build_finished_at"`
463492 BuildDuration float64 `json:"build_duration"`
464493 BuildQueuedDuration float64 `json:"build_queued_duration"`
465494 BuildAllowFailure bool `json:"build_allow_failure"`
0 commit comments