blob: f3dae41f127b9d613c56c27e1861a1182d023866 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"flag"
"github.com/bradfitz/go-smtpd"
"github.com/sirupsen/logrus"
"k8s.io/test-infra/prow/config"
"k8s.io/test-infra/prow/kube"
"log"
"os"
"prow-lkml/lkml"
"strings"
"time"
)
// mail server over smtp port 25
const smtpListen = ":25"
type mail struct {
from smtpd.MailAddress
body strings.Builder
conn smtpd.Connection
hasRcpt bool
messageQueue chan<- string
}
// possibly filter connection by address
func onNewConnection(c smtpd.Connection) error {
log.Printf("smtpd: new connection from %v", c.Addr())
return nil
}
func (m *mail) AddRecipient(rcpt smtpd.MailAddress) error {
m.hasRcpt = true
log.Printf("To: email:%s", rcpt.Email())
return nil
}
func (m *mail) BeginData() error {
if !m.hasRcpt {
return smtpd.SMTPError("554 5.5.1 Error: no valid recipients")
}
return nil
}
func (m *mail) Write(line []byte) error {
m.body.Write(line)
return nil
}
func (m *mail) Close() error {
str := m.body.String()
m.messageQueue <- str
return nil
}
type options struct {
configPath string
jobConfigPath string
sourceRepo string
sourceBranch string
jobURI string
}
func (o *options) validate() error {
return nil
}
func gatherOptions() options {
o := options{}
flag.StringVar(&o.configPath, "config-path", "", "Path to config.yaml.")
// TODO: support muliple mailing lists each mapped to multiple repo/branches
// one concern is that this might put too much overhead on one container
// might be better to split over multiple instances of the lkml pod
flag.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs")
flag.StringVar(&o.sourceRepo, "source-repo", "", "Repo to patch to")
flag.StringVar(&o.sourceBranch, "source-branch", "", "Branch to patch to")
flag.StringVar(&o.jobURI, "job-uri", "", "Mapping for presubmit job")
flag.Parse()
return o
}
func main() {
o := gatherOptions()
if err := o.validate(); err != nil {
log.Fatal("Args to LKML component were not valid: %v", err)
}
// config agent for fetching current configuration
ca := &config.Agent{}
if err := ca.Start(o.configPath, o.jobConfigPath); err != nil {
logrus.WithError(err).Fatal("Error starting config agent.")
}
// start kube client needed to trigger Jobs
log.Printf("Namespace is %s", ca.Config().ProwJobNamespace)
kc, err := kube.NewClientInCluster(ca.Config().ProwJobNamespace)
if err != nil {
logrus.WithError(err).Fatal("Error getting kube client.")
}
// get ClusterIP through Service Environment Variable.
// need Service deployed before pod for this to work
host := os.Getenv("GIT_SERVICE_HOST")
if host == "" {
log.Fatal("IP address for git server is invalid. Redeploy Pod after" +
"Service is live")
}
// queue to pass patches from smtp to lkml controller
var messageQueue = make(chan string, lkml.QueueSize)
c := lkml.New(
messageQueue,
o.sourceRepo,
o.sourceBranch,
o.jobURI,
host,
ca,
kc,
)
// closure for accessing message channel from mail
onNewMail := func(c smtpd.Connection, from smtpd.MailAddress) (smtpd.Envelope, error) {
log.Printf("Email From: email:%s", from.Email())
return &mail{
from: from,
conn: c,
messageQueue: messageQueue,
}, nil
}
// start smtp server
go func() {
log.Printf("running smtp server on %s", smtpListen)
s := &smtpd.Server{
Addr: smtpListen,
OnNewMail: onNewMail,
OnNewConnection: onNewConnection,
ReadTimeout: time.Minute,
}
err := s.ListenAndServe()
log.Printf("SMTP server died: %v", err)
// process remaining patches and jobs before closing
close(messageQueue)
}()
if err := c.Run(); err != nil {
log.Fatal("Error from lkml controller: %v", err)
}
}