actions-runner-controller/pkg/hookdeliveryforwarder/multiforwarder.go

144 lines
2.6 KiB
Go

package hookdeliveryforwarder
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"github.com/actions-runner-controller/actions-runner-controller/github"
gogithub "github.com/google/go-github/v39/github"
)
type MultiForwarder struct {
client *github.Client
Rules []Rule
Checkpointer Checkpointer
logger
}
type RuleConfig struct {
Repo []string `json:"from"`
Target string `json:"to"`
Hook gogithub.Hook `json:"hook"`
}
type Rule struct {
Repo string
Target string
Hook gogithub.Hook
}
func New(client *github.Client, rules []string) (*MultiForwarder, error) {
var srv MultiForwarder
for _, r := range rules {
var rule RuleConfig
if err := json.Unmarshal([]byte(r), &rule); err != nil {
return nil, fmt.Errorf("failed unmarshalling %s: %w", r, err)
}
if len(rule.Repo) == 0 {
return nil, fmt.Errorf("there must be one or more sources configured via `--repo \"from=SOURCE1,SOURCE2,... to=DEST1,DEST2,...\". got %q", r)
}
if rule.Target == "" {
return nil, fmt.Errorf("there must be one destination configured via `--repo \"from=SOURCE to=DEST1,DEST2,...\". got %q", r)
}
for _, repo := range rule.Repo {
srv.Rules = append(srv.Rules, Rule{
Repo: repo,
Target: rule.Target,
Hook: rule.Hook,
})
}
}
srv.client = client
srv.Checkpointer = NewInMemoryLogPositionProvider()
return &srv, nil
}
func (f *MultiForwarder) Run(ctx context.Context) error {
var wg sync.WaitGroup
errs := make(chan error, len(f.Rules))
for _, r := range f.Rules {
r := r
wg.Add(1)
go func() {
defer wg.Done()
errs <- f.run(ctx, r)
}()
}
wg.Wait()
select {
case err := <-errs:
return err
default:
return nil
}
}
func (f *MultiForwarder) run(ctx context.Context, rule Rule) error {
i := &Forwarder{
Repo: rule.Repo,
Target: rule.Target,
Hook: rule.Hook,
Client: f.client,
Checkpointer: f.Checkpointer,
}
return i.Run(ctx)
}
func (f *MultiForwarder) HandleReadyz(w http.ResponseWriter, r *http.Request) {
var (
ok bool
err error
)
defer func() {
if !ok {
w.WriteHeader(http.StatusInternalServerError)
if err != nil {
msg := err.Error()
if _, err := w.Write([]byte(msg)); err != nil {
f.Errorf("failed writing http error response: %v", err)
}
}
}
}()
defer func() {
if r.Body != nil {
r.Body.Close()
}
}()
// respond ok to GET / e.g. for health check
if r.Method == http.MethodGet {
fmt.Fprintln(w, "webhook server is running")
return
}
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte("ok")); err != nil {
f.Errorf("failed writing http response: %v", err)
}
}