-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
219 lines (191 loc) · 6.22 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
uuid "github.com/satori/go.uuid"
logrus "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
jaeger "github.com/uber/jaeger-client-go/config"
"gopkg.in/go-playground/webhooks.v5/github"
"github.com/gospotcheck/protofact/pkg/config"
"github.com/gospotcheck/protofact/pkg/filesys"
"github.com/gospotcheck/protofact/pkg/git"
"github.com/gospotcheck/protofact/pkg/metrics"
"github.com/gospotcheck/protofact/pkg/services/npm"
"github.com/gospotcheck/protofact/pkg/services/release"
"github.com/gospotcheck/protofact/pkg/services/ruby"
"github.com/gospotcheck/protofact/pkg/services/scala"
"github.com/gospotcheck/protofact/pkg/webhook"
)
var configFilePath string
func init() {
flag.StringVarP(&configFilePath, "config", "c", "", "path to config file, default is none")
}
type languageProcessor interface {
Process(ctx context.Context, payload github.PushPayload)
}
type parser interface {
ValidateAndParsePushEvent(r *http.Request) (github.PushPayload, error)
IsPingEvent(r *http.Request) bool
}
func main() {
conf, err := config.Read(configFilePath)
if err != nil {
errors.Wrap(err, "could not read in config:\n")
log.Fatalf("%+v", err)
}
// setup logger
// can add to this as other log level statements are added
switch conf.LogLevel {
case "debug":
logrus.SetLevel(logrus.DebugLevel)
default:
logrus.SetLevel(logrus.ErrorLevel)
}
// at the top level make sure the language is added to every log
logger := logrus.WithFields(logrus.Fields{
"language": conf.Language,
})
// set up a context that can be passed to all goroutines
// with cancel so they can be cleaned up if a sig is received
var ctx context.Context
{
var cancel context.CancelFunc
ctx = context.Background()
ctx, cancel = context.WithCancel(ctx)
defer cancel()
// if we receive a sigint or sigterm, we call cancel,
// which should cause all goroutines to return
// then we kill the app
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigc
cancel()
fmt.Println("received a SIGINT or SIGTERM")
logger.Fatal(sig)
}()
}
// setup prometheus
var counters *metrics.Counters
{
// only custom metric for now, but setup provides for more to be added as needed
counters = &metrics.Counters{
PackagingErrorCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "error_total",
}, []string{"language", "type", "app"}).MustCurryWith(prometheus.Labels{"language": conf.Language, "app": "proto-pkg"}),
PackagingProcessDuration: promauto.NewCounterVec(prometheus.CounterOpts{
Name: "bulk_process_duration_secs",
}, []string{"language", "app"}).MustCurryWith(prometheus.Labels{"language": conf.Language, "app": "proto-pkg"}),
}
}
http.Handle("/metrics", promhttp.Handler())
// setup tracing
{
var tags []opentracing.Tag
tags = append(tags, opentracing.Tag{
Key: "language",
Value: conf.Language,
})
cfg, err := jaeger.FromEnv()
if err != nil {
err = errors.Wrap(err, "error reading in jaeger config")
logger.Fatalf("%+v\n", err)
}
cfg.ServiceName = conf.Name
cfg.Tags = tags
tracer, closer, err := cfg.NewTracer()
if err != nil {
err = errors.Wrap(err, "error creating new tracer")
logger.Fatalf("%+v\n", err)
}
defer closer.Close()
opentracing.SetGlobalTracer(tracer)
}
// setup webhook parser
var prsr parser
{
var err error
prsr, err = webhook.NewParser(false, conf.Webhook)
if err != nil {
err = errors.Wrap(err, "error creating new parser")
logger.Fatalf("%+v\n", err)
}
}
// set up other service dependencies
fs := &filesys.FS{}
repo := git.New(ctx, conf.Git, logger)
// based on language of container, setup the processor to use the correct service
var svc languageProcessor
{
var err error
switch conf.Language {
case "npm":
svc = npm.New(conf.NPM, fs, repo, logger, counters, opentracing.GlobalTracer())
case "scala":
svc = scala.New(conf.Scala, fs, repo, logger, counters, opentracing.GlobalTracer())
case "ruby":
svc = ruby.New(conf.Ruby, fs, repo, logger, counters, opentracing.GlobalTracer())
case "release":
svc = release.New(fs, repo, logger, counters, opentracing.GlobalTracer())
err = repo.SetGitConfig()
if err != nil {
logger.Fatal("%+v\n", err)
}
default:
err = errors.New("LANGUAGE configuration did not match any supported language")
logger.Fatalf("%+v\n", err)
}
}
// one route that receives all webhook requests
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
// start a span that can be added to the context for reference in the goroutine
requestID := uuid.NewV4()
span := opentracing.StartSpan("handle_webhook")
span.SetTag("request_id", requestID)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
// check push event
payload, err := prsr.ValidateAndParsePushEvent(r)
if err != nil {
// if the request is bad log it and send it back
// so Github can register the error
w.WriteHeader(http.StatusBadRequest)
err = errors.Wrap(err, "error validating and parsing push event")
logger.Errorf("%+v\n", err)
return
}
// if the request is good set 200 header and send it back
// Github may not wait as long as it takes to do this processing
// so we want to handle failures in the app separately from
// failures in receiving the event
w.WriteHeader(http.StatusOK)
// ignore tags, as the release package pushes them, so otherwise
// it gets into a loop, and we end up packaging everything
// in other languages twice.
if strings.Contains(payload.Ref, "tags") {
return
}
// this is spun off as a cancelable goroutine
// so it is not blocking on the response to Github
go svc.Process(ctx, payload)
return
})
// basic health check endpoint
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
port := fmt.Sprintf(":%s", conf.Port)
log.Fatal(http.ListenAndServe(port, nil))
}