From 0bcae4c0837f47a9b22bfdba4230398acec453f5 Mon Sep 17 00:00:00 2001 From: Florian Klink Date: Thu, 15 May 2025 19:58:43 +0300 Subject: [PATCH] fix(ops): drop clbot This removes the old clbot, which kept an SSH connection to gerrit open. Change-Id: If8faecdd018b45dd087b7332fe3d3a8280947358 Reviewed-on: https://cl.snix.dev/c/snix/+/30525 Tested-by: besadii Reviewed-by: Ryan Lahfa --- fun/clbot/backoffutil/backoffutil.go | 43 ---- fun/clbot/clbot.go | 288 --------------------- fun/clbot/default.nix | 24 -- fun/clbot/gerrit/gerritevents/events.go | 321 ------------------------ fun/clbot/gerrit/gerritevents/time.go | 38 --- fun/clbot/gerrit/gerritevents/types.go | 221 ---------------- fun/clbot/gerrit/watcher.go | 252 ------------------- fun/clbot/gerrit/watcher_test.go | 190 -------------- fun/clbot/go.mod | 12 - fun/clbot/go.sum | 31 --- ops/machines/meta01/default.nix | 23 -- ops/machines/public01/default.nix | 26 -- ops/modules/clbot.nix | 76 ------ 13 files changed, 1545 deletions(-) delete mode 100644 fun/clbot/backoffutil/backoffutil.go delete mode 100644 fun/clbot/clbot.go delete mode 100644 fun/clbot/default.nix delete mode 100644 fun/clbot/gerrit/gerritevents/events.go delete mode 100644 fun/clbot/gerrit/gerritevents/time.go delete mode 100644 fun/clbot/gerrit/gerritevents/types.go delete mode 100644 fun/clbot/gerrit/watcher.go delete mode 100644 fun/clbot/gerrit/watcher_test.go delete mode 100644 fun/clbot/go.mod delete mode 100644 fun/clbot/go.sum delete mode 100644 ops/modules/clbot.nix diff --git a/fun/clbot/backoffutil/backoffutil.go b/fun/clbot/backoffutil/backoffutil.go deleted file mode 100644 index 1b1ea5f9d..000000000 --- a/fun/clbot/backoffutil/backoffutil.go +++ /dev/null @@ -1,43 +0,0 @@ -// Package backoffutil provides useful utilities for backoff. -package backoffutil - -import ( - "time" - - backoff "github.com/cenkalti/backoff/v4" -) - -// ZeroStartingBackOff is a backoff.BackOff that returns "0" as the first Duration after a reset. -// This is useful for constructing loops and just enforcing a backoff duration on every loop, rather than incorporating this logic into the loop directly. -type ZeroStartingBackOff struct { - bo backoff.BackOff - initial bool -} - -// NewZeroStartingBackOff creates a new ZeroStartingBackOff. -func NewZeroStartingBackOff(bo backoff.BackOff) *ZeroStartingBackOff { - return &ZeroStartingBackOff{bo: bo, initial: true} -} - -// NewDefaultBackOff creates a sensibly configured BackOff that starts at zero. -func NewDefaultBackOff() backoff.BackOff { - ebo := backoff.NewExponentialBackOff() - ebo.MaxElapsedTime = 0 - return NewZeroStartingBackOff(ebo) -} - -// NextBackOff returns the next back off duration to use. -// For the first call after a call to Reset(), this is 0. For each subsequent duration, the underlying BackOff is consulted. -func (bo *ZeroStartingBackOff) NextBackOff() time.Duration { - if bo.initial == true { - bo.initial = false - return 0 - } - return bo.bo.NextBackOff() -} - -// Reset resets to the initial state, and also passes a Reset through to the underlying BackOff. -func (bo *ZeroStartingBackOff) Reset() { - bo.initial = true - bo.bo.Reset() -} diff --git a/fun/clbot/clbot.go b/fun/clbot/clbot.go deleted file mode 100644 index 82e2c4ea1..000000000 --- a/fun/clbot/clbot.go +++ /dev/null @@ -1,288 +0,0 @@ -package main - -import ( - "context" - "crypto/tls" - "flag" - "fmt" - "net" - "os" - "os/signal" - "strings" - "time" - - "code.tvl.fyi/fun/clbot/backoffutil" - "code.tvl.fyi/fun/clbot/gerrit" - "code.tvl.fyi/fun/clbot/gerrit/gerritevents" - log "github.com/golang/glog" - "golang.org/x/crypto/ssh" - "gopkg.in/irc.v3" -) - -var ( - gerritAddr = flag.String("gerrit_host", "cl.tvl.fyi:29418", "Gerrit SSH host:port") - gerritSSHHostKey = flag.String("gerrit_ssh_pubkey", "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIUNYBYPCCBNDFSd0BuCR+8kgeuJ7IA5S2nTNQmkQUYNyXK+ot5os7rHtCk96+grd5+J8jFCuFBWisUe8h8NC0Q=", "Gerrit SSH public key") - gerritSSHTimeout = flag.Duration("gerrit_tcp_timeout", 5*time.Second, "Gerrit SSH TCP connect timeout") - - gerritAuthUsername = flag.String("gerrit_ssh_auth_username", "", "Gerrit SSH username") - gerritAuthKeyPath = flag.String("gerrit_ssh_auth_key", "", "Gerrit SSH private key path") - - ircServer = flag.String("irc_server", "irc.hackint.org:6697", "IRC server to connect to") - ircTls = flag.Bool("irc_tls", false, "Does the server connection need TLS?") - ircNick = flag.String("irc_nick", "clbot", "Nick to use when connecting to IRC") - ircUser = flag.String("irc_user", "clbot", "User string to use for IRC") - ircName = flag.String("irc_name", "clbot", "Name string to use for IRC") - ircChannel = flag.String("irc_channel", "#tvl", "Channel to send messages to") - ircPassword = flag.String("irc_pass", "", "Password to use for IRC") - ircSendLimit = flag.Duration("irc_send_limit", 100*time.Millisecond, "Delay between messages") - ircSendBurst = flag.Int("irc_send_burst", 10, "Number of messages which can be sent in a burst") - - notifyRepo = flag.String("notify_repo", "depot", "Repo name to notify about") - notifyBranches = stringSetFlag{} - - neverPing = flag.String("never_ping", "marcus", "Comma-separated terms that should never ping users") -) - -func init() { - flag.Var(¬ifyBranches, "notify_branches", "Branch names (comma-separated, or repeated flags, or both) to notify users about") -} - -type stringSetFlag map[string]bool - -func (f stringSetFlag) String() string { - return fmt.Sprintf("%v", map[string]bool(f)) -} -func (f stringSetFlag) Set(s string) error { - if s == "" { - return nil - } - for _, k := range strings.Split(s, ",") { - if k != "" { - f[k] = true - } - } - return nil -} - -func mustFixedHostKey(f string) ssh.HostKeyCallback { - pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(f)) - if err != nil { - log.Exitf("ParseAuthorizedKey(%q): %v", f, err) - } - return ssh.FixedHostKey(pk) -} - -func mustPrivateKey(p string) ssh.AuthMethod { - pkBytes, err := os.ReadFile(p) - if err != nil { - log.Exitf("reading SSH private key from %q: %v", p, err) - } - pk, err := ssh.ParsePrivateKey(pkBytes) - if err != nil { - log.Exitf("parsing private key from %q: %v", p, err) - } - return ssh.PublicKeys(pk) -} - -var shutdownFuncs []func() - -func callOnShutdown(f func()) { - shutdownFuncs = append(shutdownFuncs, f) -} - -// Unicode U+200B zero-width-space, to avoid triggering other bots -// or highlighting people on IRC. -const zeroWidthSpace = "\u200b" - -func runIRC(ctx context.Context, ircCfg irc.ClientConfig, sendMsg <-chan string) { - bo := backoffutil.NewDefaultBackOff() - ircCfg.Handler = irc.HandlerFunc(func(c *irc.Client, m *irc.Message) { - if m.Command == "NOTICE" && m.Prefix.Name == "NickServ" && strings.Contains(m.Trailing(), "dentified") { - // We're probably identified now, go join the channel. - c.Writef("JOIN %s", *ircChannel) - } - }) - for { - timer := time.NewTimer(bo.NextBackOff()) - select { - case <-ctx.Done(): - timer.Stop() - return - case <-timer.C: - break - } - - (func() { - connectedStart := time.Now() - - var ircConn net.Conn - var err error - - if *ircTls { - ircConn, err = tls.Dial("tcp", *ircServer, nil) - } else { - ircConn, err = net.Dial("tcp", *ircServer) - } - - if err != nil { - log.Errorf("connecting to IRC at tcp/%s (tls: %v): %v", *ircServer, *ircTls, err) - return - } - - ircClient := irc.NewClient(ircConn, ircCfg) - ircClientCtx, cancel := context.WithCancel(ctx) - defer cancel() - go func() { - for { - select { - case <-ircClientCtx.Done(): - return - case msg := <-sendMsg: - log.Infof("sending message %q to %v", msg, *ircChannel) - ircClient.Writef("PRIVMSG %s :%s%s", *ircChannel, zeroWidthSpace, msg) - } - } - }() - log.Infof("connecting to IRC on tcp/%s", *ircServer) - if err := ircClient.RunContext(ircClientCtx); err != nil { - connectedEnd := time.Now() - connectedFor := connectedEnd.Sub(connectedStart) - if connectedFor > 60*time.Second { - bo.Reset() - } - log.Errorf("IRC RunContext: %v", err) - return - } - })() - } -} - -func username(a gerritevents.Account) string { - options := []string{ - a.Username, - a.Name, - a.Email, - } - for _, opt := range options { - if opt != "" { - return opt - } - } - return "UNKNOWN USER" -} - -// noping inserts a Unicode zero-width space between the first and rest characters of `user` -// in an effort to avoid pinging that user on IRC. -func noping(user string) string { - un := []rune(user) - return string(un[0:1]) + zeroWidthSpace + string(un[1:]) -} - -// Apply noping to each instance of the username in the supplied -// message. With this users will not be pinged for their own CLs, but -// they will be notified if someone else writes a CL that includes -// their username. -// -// Also applies noping to all instances of the words in `neverPing`. -func nopingAll(username, message string) string { - for _, word := range strings.Split(*neverPing, ",") { - message = strings.ReplaceAll(message, word, noping(word)) - } - - return strings.ReplaceAll(message, username, noping(username)) -} - -func patchSetURL(c gerritevents.Change, p gerritevents.PatchSet) string { - return fmt.Sprintf("https://cl.snix.dev/%d", c.Number) -} - -func main() { - flag.Parse() - failed := false - if *gerritAuthUsername == "" { - log.Errorf("gerrit_ssh_auth_username must be set") - failed = true - } - if *gerritAuthKeyPath == "" { - log.Errorf("gerrit_ssh_auth_key must be set") - failed = true - } - if failed { - os.Exit(2) - } - - shutdownCh := make(chan os.Signal) - signal.Notify(shutdownCh, os.Interrupt) - go func() { - <-shutdownCh - signal.Reset(os.Interrupt) - for n := len(shutdownFuncs) - 1; n >= 0; n-- { - shutdownFuncs[n]() - } - }() - - ctx, cancel := context.WithCancel(context.Background()) - callOnShutdown(cancel) - cfg := &ssh.ClientConfig{ - User: *gerritAuthUsername, - Auth: []ssh.AuthMethod{mustPrivateKey(*gerritAuthKeyPath)}, - HostKeyCallback: mustFixedHostKey(*gerritSSHHostKey), - Timeout: *gerritSSHTimeout, - } - cfg.SetDefaults() - - gw, err := gerrit.New(ctx, "tcp", *gerritAddr, cfg) - if err != nil { - log.Exitf("gerrit.New(%q): %v", *gerritAddr, err) - } - callOnShutdown(func() { - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - gw.Close(ctx) - }) - - sendMsgChan := make(chan string, 5) - go func() { - for e := range gw.Events() { - var parsedMsg string - switch e := e.(type) { - case *gerritevents.PatchSetCreated: - if e.Change.Project != *notifyRepo || !notifyBranches[e.Change.Branch] || e.PatchSet.Number != 1 { - continue - } - user := username(e.PatchSet.Uploader) - parsedMsg = nopingAll(user, fmt.Sprintf("CL/%d proposed by %s - %s - %s", e.Change.Number, user, e.Change.Subject, patchSetURL(e.Change, e.PatchSet))) - case *gerritevents.ChangeMerged: - if e.Change.Project != *notifyRepo || !notifyBranches[e.Change.Branch] { - continue - } - owner := username(e.Change.Owner) - submitter := e.Submitter.Username - url := patchSetURL(e.Change, e.PatchSet) - - if submitter != owner && submitter == "clbot" { - parsedMsg = nopingAll(owner, fmt.Sprintf("CL/%d by %s autosubmitted - %s - %s", e.Change.Number, owner, e.Change.Subject, url)) - } else { - parsedMsg = nopingAll(owner, fmt.Sprintf("CL/%d applied by %s - %s - %s", e.Change.Number, owner, e.Change.Subject, url)) - } - } - if parsedMsg != "" { - sendMsgChan <- parsedMsg - } - } - }() - - ircCtx, ircCancel := context.WithCancel(ctx) - callOnShutdown(ircCancel) - go runIRC(ircCtx, irc.ClientConfig{ - Nick: *ircNick, - User: *ircUser, - Name: *ircName, - Pass: *ircPassword, - - SendLimit: *ircSendLimit, - SendBurst: *ircSendBurst, - }, sendMsgChan) - - <-ctx.Done() -} diff --git a/fun/clbot/default.nix b/fun/clbot/default.nix deleted file mode 100644 index ebfe7ce7f..000000000 --- a/fun/clbot/default.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ pkgs, ... }@args: - -let - inherit (pkgs) lib; -in - -pkgs.buildGoModule { - name = "clbot"; - src = lib.fileset.toSource { - root = ./.; - fileset = lib.fileset.unions [ - ./clbot.go - ./go.mod - ./go.sum - ./backoffutil - ./gerrit - ]; - }; - vendorHash = - # Assert the expected go.sum hash matches so we don't forget to update the FOD hash on dependency changes. - assert builtins.hashFile "sha256" ./go.sum - == "f999a34979af2113b867446a445a4d8c066d68f945cd4470fe33fe4fead6d15b"; - "sha256-IvFg+/lwBsJiJoLCRP5KU5+tRuHDLpwWHHkmt67yJd8="; -} diff --git a/fun/clbot/gerrit/gerritevents/events.go b/fun/clbot/gerrit/gerritevents/events.go deleted file mode 100644 index c02b30f76..000000000 --- a/fun/clbot/gerrit/gerritevents/events.go +++ /dev/null @@ -1,321 +0,0 @@ -package gerritevents - -import ( - "encoding/json" - "fmt" -) - -var events = map[string]func() Event{} - -func registerEvent(e func() Event) { - t := e().EventType() - if _, ok := events[t]; ok { - panic(fmt.Sprintf("%s already registered", t)) - } - events[t] = e -} - -// These events are taken from https://cl.tvl.fyi/Documentation/cmd-stream-events.html. - -// Event is implemented by Gerrit event structs. -type Event interface { - EventType() string -} - -type simpleEvent struct { - Type string `json:"type"` -} - -// Parse parses a Gerrit event from JSON. -func Parse(bs []byte) (Event, error) { - var s simpleEvent - if err := json.Unmarshal(bs, &s); err != nil { - return nil, fmt.Errorf("unmarshalling %q as Gerrit Event: %v", string(bs), err) - } - ef, ok := events[s.Type] - if !ok { - return nil, fmt.Errorf("unknown event type %q", s.Type) - } - e := ef() - if err := json.Unmarshal(bs, e); err != nil { - return nil, fmt.Errorf("unmarshalling %q as Gerrit Event %q: %v", string(bs), e.EventType(), err) - } - return e, nil -} - -// AssigneeChanged indicates that a change's assignee has been changed. -type AssigneeChanged struct { - Type string `json:"type"` - Change Change `json:"change"` - Changer Account `json:"changer"` - OldAssignee Account `json:"oldAssignee"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (AssigneeChanged) EventType() string { return "assignee-changed" } - -func init() { - registerEvent(func() Event { return &AssigneeChanged{} }) -} - -// ChangeAbandoned indicates that a change has been abandoned. -type ChangeAbandoned struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Abandoner Account `json:"abandoner"` - Reason string `json:"reason"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (ChangeAbandoned) EventType() string { return "change-abandoned" } - -func init() { - registerEvent(func() Event { return &ChangeAbandoned{} }) -} - -// ChangeDeleted indicates that a change has been deleted. -type ChangeDeleted struct { - Type string `json:"type"` - Change Change `json:"change"` - Deleter Account `json:"deleter"` -} - -// EventType implements Event. -func (ChangeDeleted) EventType() string { return "change-deleted" } - -func init() { - registerEvent(func() Event { return &ChangeDeleted{} }) -} - -// ChangeMerged indicates that a change has been merged into the target branch. -type ChangeMerged struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Submitter Account `json:"submitter"` - NewRev string `json:"newRev"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (ChangeMerged) EventType() string { return "change-merged" } - -func init() { - registerEvent(func() Event { return &ChangeMerged{} }) -} - -// ChangeRestored indicates a change has been restored (i.e. un-abandoned). -type ChangeRestored struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Restorer Account `json:"restorer"` - Reason string `json:"reason"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (ChangeRestored) EventType() string { return "change-restored" } - -func init() { - registerEvent(func() Event { return &ChangeRestored{} }) -} - -// CommentAdded indicates someone has commented on a patchset. -type CommentAdded struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Author Account `json:"author"` - Approvals []Approval `json:"approvals"` - Comment string `json:"comment"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (CommentAdded) EventType() string { return "comment-added" } - -func init() { - registerEvent(func() Event { return &CommentAdded{} }) -} - -// DroppedOutput indicates that some events may be missing from the stream. -type DroppedOutput struct { - Type string `json:"type"` -} - -// EventType implements Event. -func (DroppedOutput) EventType() string { return "dropped-output" } - -func init() { - registerEvent(func() Event { return &DroppedOutput{} }) -} - -// HashtagsChanged indicates that someone has added or removed hashtags from a change. -type HashtagsChanged struct { - Type string `json:"type"` - Change Change `json:"change"` - Editor Account `json:"editor"` - Added []string `json:"added"` - Removed []string `json:"removed"` - Hashtags []string `json:"hashtags"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (HashtagsChanged) EventType() string { return "hashtags-changed" } - -func init() { - registerEvent(func() Event { return &HashtagsChanged{} }) -} - -// ProjectCreated indicates that a new project has been created. -type ProjectCreated struct { - Type string `json:"type"` - ProjectName string `json:"projectName"` - ProjectHead string `json:"projectHead"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (ProjectCreated) EventType() string { return "project-created" } - -func init() { - registerEvent(func() Event { return &ProjectCreated{} }) -} - -// PatchSetCreated indicates that a new patchset has been added to a change. -type PatchSetCreated struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Uploader Account `json:"uploader"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (PatchSetCreated) EventType() string { return "patchset-created" } - -func init() { - registerEvent(func() Event { return &PatchSetCreated{} }) -} - -// RefUpdated indicates that a ref has been updated. -type RefUpdated struct { - Type string `json:"type"` - Submitter Account `json:"submitter"` - RefUpdate RefUpdate `json:"refUpdate"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (RefUpdated) EventType() string { return "ref-updated" } - -func init() { - registerEvent(func() Event { return &RefUpdated{} }) -} - -// ReviewerAdded indicates that a reviewer has been added to a change. -type ReviewerAdded struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Reviewer Account `json:"reviewer"` - Adder Account `json:"adder"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (ReviewerAdded) EventType() string { return "reviewer-added" } - -func init() { - registerEvent(func() Event { return &ReviewerAdded{} }) -} - -// ReviewerDeleted indicates that a reviewer has been removed from a change, possibly removing one or more approvals. -type ReviewerDeleted struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Reviewer Account `json:"reviewer"` - Remover Account `json:"remover"` - Approvals []Approval `json:"approvals"` - Comment string `json:"comment"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (ReviewerDeleted) EventType() string { return "reviewer-deleted" } - -func init() { - registerEvent(func() Event { return &ReviewerDeleted{} }) -} - -// TopicChanged indicates that the topic attached to a change has been changed. -type TopicChanged struct { - Type string `json:"type"` - Change Change `json:"change"` - Changer Account `json:"changer"` - OldTopic string `json:"oldTopic"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (TopicChanged) EventType() string { return "topic-changed" } - -func init() { - registerEvent(func() Event { return &TopicChanged{} }) -} - -// WIPStateChanged indicates that the work-in-progress state of a change has changed. -type WIPStateChanged struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Changer Account `json:"changer"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (WIPStateChanged) EventType() string { return "wip-state-changed" } - -func init() { - registerEvent(func() Event { return &WIPStateChanged{} }) -} - -// PrivateStateChanged indicates that the private state of a change has changed. -type PrivateStateChanged struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Changer Account `json:"changer"` - EventCreatedOn Time `json:"eventCreatedOn"` -} - -// EventType implements Event. -func (PrivateStateChanged) EventType() string { return "private-state-changed" } - -func init() { - registerEvent(func() Event { return &PrivateStateChanged{} }) -} - -// VoteDeleted indicates that an approval vote has been deleted from a change. -type VoteDeleted struct { - Type string `json:"type"` - Change Change `json:"change"` - PatchSet PatchSet `json:"patchSet"` - Reviewer Account `json:"reviewer"` - Remover Account `json:"remover"` - Approvals []Approval `json:"approvals"` - Comment string `json:"comment"` -} - -// EventType implements Event. -func (VoteDeleted) EventType() string { return "vote-deleted" } - -func init() { - registerEvent(func() Event { return &VoteDeleted{} }) -} diff --git a/fun/clbot/gerrit/gerritevents/time.go b/fun/clbot/gerrit/gerritevents/time.go deleted file mode 100644 index 7fbfaa3f5..000000000 --- a/fun/clbot/gerrit/gerritevents/time.go +++ /dev/null @@ -1,38 +0,0 @@ -package gerritevents - -import ( - "fmt" - "strconv" - "time" -) - -// Time is a time.Time that is formatted as a Unix timestamp in JSON. -type Time struct { - time.Time -} - -// UnmarshalJSON unmarshals a Unix timestamp into a Time. -func (t *Time) UnmarshalJSON(bs []byte) error { - if string(bs) == "null" { - return nil - } - u, err := strconv.ParseInt(string(bs), 10, 64) - if err != nil { - return err - } - t.Time = time.Unix(u, 0) - return nil -} - -// MarshalJSON marshals a Time into a Unix timestamp. -func (t *Time) MarshalJSON() ([]byte, error) { - if t.IsZero() { - return []byte("null"), nil - } - return []byte(fmt.Sprintf("%d", t.Unix())), nil -} - -// IsSet returns true if the time.Time is non-zero. -func (t *Time) IsSet() bool { - return !t.IsZero() -} diff --git a/fun/clbot/gerrit/gerritevents/types.go b/fun/clbot/gerrit/gerritevents/types.go deleted file mode 100644 index 75987a2da..000000000 --- a/fun/clbot/gerrit/gerritevents/types.go +++ /dev/null @@ -1,221 +0,0 @@ -package gerritevents - -// These types are taken from https://cl.tvl.fyi/Documentation/json.html. - -// Account is a Gerrit account (or just a Git name+email pair). -type Account struct { - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` -} - -// ChangeStatus represents the states a change can be in. -type ChangeStatus string - -const ( - // ChangeStatusNew is the state a change is in during review. - ChangeStatusNew ChangeStatus = "NEW" - - // ChangeStatusMerged indicates a change was merged to the target branch. - ChangeStatusMerged ChangeStatus = "MERGED" - - // ChangeStatusAbandoned indicates a change was marked as abandoned. - ChangeStatusAbandoned ChangeStatus = "ABANDONED" -) - -// Message is a message left by a reviewer. -type Message struct { - Timestamp Time `json:"timestamp"` - Reviewer Account `json:"reviewer"` - Message string `json:"message"` -} - -// TrackingID allows storing identifiers from external systems, i.e. bug trackers. -type TrackingID struct { - System string `json:"system"` - ID string `json:"id"` -} - -// ChangeKind indicates the different changes that can be made to a change. -type ChangeKind string - -const ( - // ChangeKindRework indicates a non-trivial content change. - ChangeKindRework ChangeKind = "REWORK" - - // ChangeKindTrivialRebase indicates a conflict-free merge between the new parent and the prior patch set. - ChangeKindTrivialRebase ChangeKind = "TRIVIAL_REBASE" - - // ChangeKindMergeFirstParentUpdate indicates a conflict-free change of the first parent of a merge commit. - ChangeKindMergeFirstParentUpdate ChangeKind = "MERGE_FIRST_PARENT_UPDATE" - - // ChangeKindNoCodeChange indicates no code change (the tree and parent trees are unchanged) - commit message probably changed. - ChangeKindNoCodeChange ChangeKind = "NO_CODE_CHANGE" - - // ChangeKindNoChange indicates nothing changes: the commit message, tree, and parent tree are unchanged. - ChangeKindNoChange ChangeKind = "NO_CHANGE" -) - -// Approval represents the current and past state of an approval label. -type Approval struct { - Type string `json:"type"` - Description string `json:"description"` - Value string `json:"value"` - OldValue *string `json:"oldValue"` - GrantedOn *Time `json:"grantedOn"` - By *Account `json:"by"` -} - -// PatchSetComment is a single comment left on a patchset. -type PatchSetComment struct { - File string `json:"file"` - Line int `json:"line"` - Reviewer Account `json:"reviewer"` - Message string `json:"message"` -} - -// FilePatchType represents the different modifications that can be made to a file by a patchset. -type FilePatchType string - -const ( - // FilePatchTypeAdded indicates the file did not exist, and this patchset adds it to the tree. - FilePatchTypeAdded FilePatchType = "ADDED" - - // FilePatchTypeModified indicates the file exists before and after this patchset. - FilePatchTypeModified FilePatchType = "MODIFIED" - - // FilePatchTypeDeleted indicates the file is removed by this patchset. - FilePatchTypeDeleted FilePatchType = "DELETED" - - // FilePatchTypeRenamed indicates the file has a different name before this patchset than after. - FilePatchTypeRenamed FilePatchType = "RENAMED" - - // FilePatchTypeCopied indicates the file was copied from a different file. - FilePatchTypeCopied FilePatchType = "COPIED" - - // FilePatchTypeRewrite indicates the file had a significant quantity of content changed. - FilePatchTypeRewrite FilePatchType = "REWRITE" -) - -// File represents a file in a patchset as well as how it is being modified. -type File struct { - File string `json:"file"` - FileOld string `json:"fileOld"` - Type FilePatchType `json:"type"` -} - -// PatchSet represents a single patchset within a change. -type PatchSet struct { - Number int `json:"number"` - Revision string `json:"revision"` - Parents []string `json:"parents"` - Ref string `json:"ref"` - Uploader Account `json:"uploader"` - Author Account `json:"author"` - CreatedOn Time `json:"createdOn"` - Kind ChangeKind `json:"kind"` - Approvals []Approval `json:"approvals"` - Comments []PatchSetComment `json:"comments"` - Files []File `json:"file"` - SizeInsertions int `json:"sizeInsertions"` - SizeDeletions int `json:"sizeDeletions"` -} - -// Dependency represents a change on which this change is dependent. -type Dependency struct { - ID string `json:"id"` - Number int `json:"number"` - Revision string `json:"revision"` - Ref string `json:"ref"` - IsCurrentPatchSet bool `json:"isCurrentPatchSet"` -} - -// SubmitStatus indicates whether this change has met the submit conditions and is ready to submit. -type SubmitStatus string - -const ( - // SubmitStatusOK indicates this change is ready to submit - all submit requirements are met. - SubmitStatusOK SubmitStatus = "OK" - - // SubmitStatusNotReady indicates this change cannot yet be submitted. - SubmitStatusNotReady SubmitStatus = "NOT_READY" - - // SubmitStatusRuleError indicates the submit rules could not be evaluted. Administrator intervention is required. - SubmitStatusRuleError SubmitStatus = "RULE_ERROR" -) - -// LabelStatus indicates whether this label permits submission and if the label can be granted by anyone. -type LabelStatus string - -const ( - // LabelStatusOK indicates that this label provides what is necessary for submission (e.g. CR+2). - LabelStatusOK LabelStatus = "OK" - - // LabelStatusReject indicates this label prevents submission (e.g. CR-2). - LabelStatusReject LabelStatus = "REJECT" - - // LabelStatusNeed indicates this label is required for submission, but has not been satisfied (e.g. CR0). - LabelStatusNeed LabelStatus = "NEED" - - // LabelStatusMay indicates this label is not required for submission. It may or may not be set. - LabelStatusMay LabelStatus = "MAY" - - // LabelStatusImpossible indicates this label is required for submission, but cannot be satisfied. The ACLs on this label may be set incorrectly. - LabelStatusImpossible LabelStatus = "IMPOSSIBLE" -) - -// Label represents the status of a particular label. -type Label struct { - Label string `json:"label"` - Status LabelStatus `json:"status"` - By Account `json:"by"` -} - -// Requirement represents a submit requirement. -type Requirement struct { - FallbackText string `json:"fallbackText"` - Type string `json:"type"` - // TODO(lukegb): data -} - -// SubmitRecord represents the current submission state of a change. -type SubmitRecord struct { - Status SubmitStatus `json:"status"` - Labels []Label `json:"labels"` - Requirements []Requirement `json:"requirements"` -} - -// Change represents a Gerrit CL. -type Change struct { - Project string `json:"project"` - Branch string `json:"branch"` - Topic string `json:"topic"` - ID string `json:"id"` - Number int `json:"number"` - Subject string `json:"subject"` - Owner Account `json:"owner"` - URL string `json:"url"` - CommitMessage string `json:"commitMessage"` - CreatedOn Time `json:"createdOn"` - LastUpdated *Time `json:"lastUpdated"` - Open bool `json:"open"` - Status ChangeStatus `json:"status"` - Private bool `json:"private"` - WIP bool `json:"wip"` - Comments []Message `json:"comments"` - TrackingIDs []TrackingID `json:"trackingIds"` - CurrentPatchSet *PatchSet `json:"currentPatchSet"` - PatchSets []PatchSet `json:"patchSets"` - DependsOn []Dependency `json:"dependsOn"` - NeededBy []Dependency `json:"neededBy"` - SubmitRecords []SubmitRecord `json:"submitRecord"` - AllReviewers []Account `json:"allReviewers"` -} - -// RefUpdate represents a change in a ref. -type RefUpdate struct { - OldRev string `json:"oldRev"` - NewRev string `json:"newRev"` - RefName string `json:"refName"` - Project string `json:"project"` -} diff --git a/fun/clbot/gerrit/watcher.go b/fun/clbot/gerrit/watcher.go deleted file mode 100644 index d45876129..000000000 --- a/fun/clbot/gerrit/watcher.go +++ /dev/null @@ -1,252 +0,0 @@ -// Package gerrit implements a watcher for Gerrit events. -package gerrit - -import ( - "context" - "errors" - "fmt" - "net" - "strings" - "time" - - "code.tvl.fyi/fun/clbot/backoffutil" - "code.tvl.fyi/fun/clbot/gerrit/gerritevents" - log "github.com/golang/glog" - "golang.org/x/crypto/ssh" -) - -// closer provides an embeddable implementation of Close which awaits a main loop acknowledging it has stopped. -type closer struct { - stop chan struct{} - stopped chan struct{} -} - -// newCloser returns a closer with the channels initialised. -func newCloser() closer { - return closer{ - stop: make(chan struct{}), - stopped: make(chan struct{}), - } -} - -// Close stops the main loop, waiting for the main loop to stop until it stops or the context is cancelled, whichever happens first. -func (c *closer) Close(ctx context.Context) error { - select { - case <-c.stopped: - return nil - case <-c.stop: - return nil - case <-ctx.Done(): - return ctx.Err() - default: - } - close(c.stop) - select { - case <-c.stopped: - return nil - case <-ctx.Done(): - return ctx.Err() - } -} - -// lineWriter is an io.Writer which splits on \n and outputs each line (with no trailing newline) to its output channel. -type lineWriter struct { - buf string - out chan string -} - -// Write accepts a slice of bytes containing zero or more new lines. -// If the contained channel is non-buffering or is full, this will block. -func (w *lineWriter) Write(p []byte) (n int, err error) { - w.buf += string(p) - pieces := strings.Split(w.buf, "\n") - w.buf = pieces[len(pieces)-1] - for n := 0; n < len(pieces)-1; n++ { - w.out <- pieces[n] - } - return len(p), nil -} - -// restartingClient is a simple SSH client that repeatedly connects to an SSH server, runs a command, and outputs the lines output by it on stdout onto a channel. -type restartingClient struct { - closer - - network string - addr string - cfg *ssh.ClientConfig - - exec string - output chan string - shutdown func() -} - -var ( - errStopConnect = errors.New("gerrit: told to stop reconnecting by remote server") -) - -func (c *restartingClient) runOnce() error { - netConn, err := net.Dial(c.network, c.addr) - if err != nil { - return fmt.Errorf("connecting to %v/%v: %w", c.network, c.addr, err) - } - defer netConn.Close() - - sshConn, newCh, newReq, err := ssh.NewClientConn(netConn, c.addr, c.cfg) - if err != nil { - return fmt.Errorf("creating SSH connection to %v/%v: %w", c.network, c.addr, err) - } - defer sshConn.Close() - - goAway := false - passedThroughReqs := make(chan *ssh.Request) - go func() { - defer close(passedThroughReqs) - for req := range newReq { - if req.Type == "goaway" { - goAway = true - log.Warningf("remote end %v/%v told me to go away!", c.network, c.addr) - sshConn.Close() - netConn.Close() - } - passedThroughReqs <- req - } - }() - - cl := ssh.NewClient(sshConn, newCh, passedThroughReqs) - - sess, err := cl.NewSession() - if err != nil { - return fmt.Errorf("NewSession on %v/%v: %w", c.network, c.addr, err) - } - defer sess.Close() - - sess.Stdout = &lineWriter{out: c.output} - - if err := sess.Start(c.exec); err != nil { - return fmt.Errorf("Start(%q) on %v/%v: %w", c.exec, c.network, c.addr, err) - } - - log.Infof("connected to %v/%v", c.network, c.addr) - - done := make(chan struct{}) - go func() { - sess.Wait() - close(done) - }() - go func() { - select { - case <-c.stop: - sess.Close() - case <-done: - } - return - }() - <-done - - if goAway { - return errStopConnect - } - return nil -} - -func (c *restartingClient) run() { - defer close(c.stopped) - bo := backoffutil.NewDefaultBackOff() - for { - timer := time.NewTimer(bo.NextBackOff()) - select { - case <-c.stop: - timer.Stop() - return - case <-timer.C: - break - } - if err := c.runOnce(); err == errStopConnect { - if c.shutdown != nil { - c.shutdown() - return - } - } else if err != nil { - log.Errorf("SSH: %v", err) - } else { - bo.Reset() - } - } -} - -// Output returns the channel on which each newline-delimited string output by the executed command's stdout can be received. -func (c *restartingClient) Output() <-chan string { - return c.output -} - -// dialRestartingClient creates a new restartingClient. -func dialRestartingClient(network, addr string, config *ssh.ClientConfig, exec string, shutdown func()) (*restartingClient, error) { - c := &restartingClient{ - closer: newCloser(), - network: network, - addr: addr, - cfg: config, - exec: exec, - output: make(chan string), - shutdown: shutdown, - } - go c.run() - return c, nil -} - -// Watcher watches -type Watcher struct { - closer - c *restartingClient - - output chan gerritevents.Event -} - -// Close shuts down the SSH client connection, if any, and closes the output channel. -// It blocks until shutdown is complete or until the context is cancelled, whichever comes first. -func (w *Watcher) Close(ctx context.Context) { - w.c.Close(ctx) - w.closer.Close(ctx) -} - -func (w *Watcher) run() { - defer close(w.stopped) - defer close(w.output) - for { - select { - case <-w.stop: - return - case o := <-w.c.Output(): - ev, err := gerritevents.Parse([]byte(o)) - if err != nil { - log.Errorf("failed to parse event %v: %v", o, err) - continue - } - w.output <- ev - } - } -} - -// Events returns the channel upon which parsed Gerrit events can be received. -func (w *Watcher) Events() <-chan gerritevents.Event { - return w.output -} - -// New returns a running Watcher from which events can be read. -// It will begin connecting to the provided address immediately. -func New(ctx context.Context, network, addr string, cfg *ssh.ClientConfig) (*Watcher, error) { - wc := newCloser() - rc, err := dialRestartingClient(network, addr, cfg, "gerrit stream-events", func() { - wc.Close(context.Background()) - }) - if err != nil { - return nil, fmt.Errorf("dialRestartingClient: %w", err) - } - w := &Watcher{ - closer: wc, - c: rc, - output: make(chan gerritevents.Event), - } - go w.run() - return w, nil -} diff --git a/fun/clbot/gerrit/watcher_test.go b/fun/clbot/gerrit/watcher_test.go deleted file mode 100644 index ae69b2fc4..000000000 --- a/fun/clbot/gerrit/watcher_test.go +++ /dev/null @@ -1,190 +0,0 @@ -package gerrit - -import ( - "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/subtle" - "fmt" - "net" - "testing" - "time" - - "code.tvl.fyi/fun/clbot/gerrit/gerritevents" - log "github.com/golang/glog" - "github.com/google/go-cmp/cmp" - "golang.org/x/crypto/ssh" -) - -var ( - sshServerSigner, sshServerPublicKey = mustNewKey() - sshClientSigner, sshClientPublicKey = mustNewKey() -) - -func mustNewKey() (ssh.Signer, ssh.PublicKey) { - key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) - if err != nil { - panic(err) - } - signer, err := ssh.NewSignerFromKey(key) - if err != nil { - panic(err) - } - publicKey, err := ssh.NewPublicKey(key.Public()) - if err != nil { - panic(err) - } - return signer, publicKey -} - -func newSSHServer(lines string) (addr string, cleanup func(), err error) { - config := &ssh.ServerConfig{ - PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { - pkBytes := pubKey.Marshal() - wantPKBytes := sshClientPublicKey.Marshal() - if subtle.ConstantTimeCompare(pkBytes, wantPKBytes) == 0 { - return nil, fmt.Errorf("unauthorized") - } - return &ssh.Permissions{}, nil - }, - } - config.AddHostKey(sshServerSigner) - - ln, err := net.Listen("tcp", ":0") - if err != nil { - log.Fatalf("Listen on tcp/:0: %v", err) - } - handle := func(conn net.Conn) { - defer conn.Close() - - sc, newchch, newreqch, err := ssh.NewServerConn(conn, config) - if err != nil { - log.Fatalf("NewServerConn: %v", err) - } - go ssh.DiscardRequests(newreqch) - for newCh := range newchch { - if newCh.ChannelType() != "session" { - newCh.Reject(ssh.UnknownChannelType, "unknown channel type") - continue - } - - channel, reqs, err := newCh.Accept() - if err != nil { - log.Fatalf("Could not accept channel: %v", err) - } - go func(in <-chan *ssh.Request) { - for req := range in { - req.Reply(req.Type == "exec", nil) - } - }(reqs) - channel.Write([]byte(lines)) - sc.SendRequest("goaway", false, nil) - } - } - go func() { - for { - conn, err := ln.Accept() - if err != nil { - return - } - go handle(conn) - } - }() - - cleanup = func() { - ln.Close() - } - return ln.Addr().String(), cleanup, err -} - -func ts(s string) gerritevents.Time { - t, err := time.Parse("2006-01-02 15:04:05 -0700 MST", s) - if err != nil { - panic(err) - } - return gerritevents.Time{t} -} - -func optStr(s string) *string { return &s } - -func TestWatcher(t *testing.T) { - tcs := []struct { - name string - lines string - want []gerritevents.Event - }{{ - name: "no events", - }, { - name: "single test event", - lines: `{"author":{"name":"tazjin","email":"mail@tazj.in","username":"tazjin"},"approvals":[{"type":"Code-Review","description":"Code-Review","value":"2","oldValue":"0"}],"comment":"Patch Set 3: Code-Review+2","patchSet":{"number":3,"revision":"6fe272d3f82c6efdfe1167fab98bf918efc03fe5","parents":["d984b6018cf68c7e8b7169b475d90134fbcee767"],"ref":"refs/changes/44/244/3","uploader":{"name":"tazjin","email":"mail@tazj.in","username":"tazjin"},"createdOn":1592081910,"author":{"name":"tazjin","email":"mail@tazj.in","username":"tazjin"},"kind":"REWORK","sizeInsertions":83,"sizeDeletions":-156},"change":{"project":"depot","branch":"master","id":"I546c701145fa204b7ba7518a8a56a783588629e0","number":244,"subject":"refactor(ops/nixos): Move my NixOS configurations to //users/tazjin","owner":{"name":"tazjin","email":"mail@tazj.in","username":"tazjin"},"url":"https://cl.tvl.fyi/c/depot/+/244","commitMessage":"refactor(ops/nixos): Move my NixOS configurations to //users/tazjin\n\nNixOS modules move one level up because it\u0027s unlikely that //ops/nixos\nwill contain actual systems at this point (they\u0027re user-specific).\n\nThis is the first users folder, so it is also added to the root\nreadTree invocation for the repository.\n\nChange-Id: I546c701145fa204b7ba7518a8a56a783588629e0\n","createdOn":1592081577,"status":"NEW"},"project":"depot","refName":"refs/heads/master","changeKey":{"id":"I546c701145fa204b7ba7518a8a56a783588629e0"},"type":"comment-added","eventCreatedOn":1592081929} -`, - want: []gerritevents.Event{ - &gerritevents.CommentAdded{ - Type: "comment-added", - Change: gerritevents.Change{ - Project: "depot", - Branch: "master", - ID: "I546c701145fa204b7ba7518a8a56a783588629e0", - Number: 244, - Subject: "refactor(ops/nixos): Move my NixOS configurations to //users/tazjin", - Owner: gerritevents.Account{Name: "tazjin", Email: "mail@tazj.in", Username: "tazjin"}, - URL: "https://cl.tvl.fyi/c/depot/+/244", - CommitMessage: "refactor(ops/nixos): Move my NixOS configurations to //users/tazjin\n\nNixOS modules move one level up because it's unlikely that //ops/nixos\nwill contain actual systems at this point (they're user-specific).\n\nThis is the first users folder, so it is also added to the root\nreadTree invocation for the repository.\n\nChange-Id: I546c701145fa204b7ba7518a8a56a783588629e0\n", - CreatedOn: ts("2020-06-13 21:52:57 +0100 BST"), - Status: "NEW", - }, - PatchSet: gerritevents.PatchSet{ - Number: 3, - Revision: "6fe272d3f82c6efdfe1167fab98bf918efc03fe5", - Parents: []string{"d984b6018cf68c7e8b7169b475d90134fbcee767"}, - Ref: "refs/changes/44/244/3", - Uploader: gerritevents.Account{Name: "tazjin", Email: "mail@tazj.in", Username: "tazjin"}, - Author: gerritevents.Account{Name: "tazjin", Email: "mail@tazj.in", Username: "tazjin"}, - CreatedOn: ts("2020-06-13 21:58:30 +0100 BST"), - Kind: "REWORK", - SizeInsertions: 83, - SizeDeletions: -156, - }, - Author: gerritevents.Account{Name: "tazjin", Email: "mail@tazj.in", Username: "tazjin"}, - Approvals: []gerritevents.Approval{{Type: "Code-Review", Description: "Code-Review", Value: "2", OldValue: optStr("0")}}, - Comment: "Patch Set 3: Code-Review+2", - EventCreatedOn: ts("2020-06-13 21:58:49 +0100 BST"), - }, - }, - }} - for _, tc := range tcs { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - serverAddr, cleanup, err := newSSHServer(tc.lines) - if err != nil { - t.Fatalf("newSSHServer: %v", err) - } - t.Cleanup(cleanup) - - config := &ssh.ClientConfig{ - User: "bert", - Auth: []ssh.AuthMethod{ssh.PublicKeys(sshClientSigner)}, - HostKeyCallback: ssh.FixedHostKey(sshServerPublicKey), - Timeout: 10 * time.Millisecond, - } - w, err := New(ctx, "tcp", serverAddr, config) - if err != nil { - t.Fatalf("New: %v", err) - } - - var gotEvents []gerritevents.Event - for ev := range w.Events() { - gotEvents = append(gotEvents, ev) - } - if diff := cmp.Diff(gotEvents, tc.want); diff != "" { - t.Errorf("got events != want events: diff:\n%v", diff) - } - }) - } -} diff --git a/fun/clbot/go.mod b/fun/clbot/go.mod deleted file mode 100644 index f1e366ba9..000000000 --- a/fun/clbot/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module code.tvl.fyi/fun/clbot - -go 1.14 - -require ( - github.com/cenkalti/backoff/v4 v4.0.2 - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/google/go-cmp v0.4.1 - golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 - gopkg.in/irc.v3 v3.1.3 -) diff --git a/fun/clbot/go.sum b/fun/clbot/go.sum deleted file mode 100644 index d5434526e..000000000 --- a/fun/clbot/go.sum +++ /dev/null @@ -1,31 +0,0 @@ -github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/irc.v3 v3.1.3 h1:yeTiJ365882L8h4AnBKYfesD92y5R5ZhGiylu9DfcPY= -gopkg.in/irc.v3 v3.1.3/go.mod h1:shO2gz8+PVeS+4E6GAny88Z0YVVQSxQghdrMVGQsR9s= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/ops/machines/meta01/default.nix b/ops/machines/meta01/default.nix index 80669f492..4bde42356 100644 --- a/ops/machines/meta01/default.nix +++ b/ops/machines/meta01/default.nix @@ -15,7 +15,6 @@ in (mod "o11y/tempo.nix") (mod "o11y/alertmanager-irc-relay.nix") (mod "known-hosts.nix") - (mod "clbot.nix") (mod "irccat.nix") (mod "www/mimir.snix.dev.nix") @@ -56,28 +55,6 @@ in prometheus.enable = true; loki.enable = true; tempo.enable = true; - clbot = { - enable = false; - channels = { - "#snix" = { }; - - flags = { - gerrit_host = "cl.snix.dev:29418"; - gerrit_ssh_auth_username = "clbot"; - # gerrit_ssh_auth_key = config.age.secrets.clbot-ssh-private-key.path; - - irc_server = "irc.hackint.org:6697"; - irc_tls = true; - irc_user = "snixbot"; - irc_nick = "snixbot"; - - notify_branches = "canon,refs/meta/config"; - notify_repo = "snix"; - - irc_pass = "$CLBOT_PASS"; - }; - }; - }; }; services.irccat = { diff --git a/ops/machines/public01/default.nix b/ops/machines/public01/default.nix index d88fe221c..a056c04dc 100644 --- a/ops/machines/public01/default.nix +++ b/ops/machines/public01/default.nix @@ -134,32 +134,6 @@ in restic-bucket-credentials.file = secretFile "restic-bucket-credentials"; }; - # Start the Gerrit->IRC bot - # services.depot.clbot = { - # enable = true; - # channels = { - # "#snix-dev" = { }; - # }; - - # # See //fun/clbot for details. - # flags = { - # gerrit_host = "cl.tvl.fyi:29418"; - # gerrit_ssh_auth_username = "clbot"; - # gerrit_ssh_auth_key = config.age.secretsDir + "/clbot-ssh"; - - # irc_server = "localhost:${toString config.services.znc.config.Listener.l.Port}"; - # irc_user = "tvlbot"; - # irc_nick = "tvlbot"; - - # notify_branches = "canon,refs/meta/config"; - # notify_repo = "depot"; - - # # This secret is read from an environment variable, which is - # # populated by a systemd EnvironmentFile. - # irc_pass = "$CLBOT_PASS"; - # }; - # }; - services.fail2ban.enable = true; environment.systemPackages = with pkgs; [ diff --git a/ops/modules/clbot.nix b/ops/modules/clbot.nix deleted file mode 100644 index f671de26f..000000000 --- a/ops/modules/clbot.nix +++ /dev/null @@ -1,76 +0,0 @@ -# Module that configures CLBot, our Gerrit->IRC info bridge. -{ depot, config, lib, utils, ... }: - -let - inherit (builtins) attrValues concatStringsSep mapAttrs; - - inherit (lib) - listToAttrs - mapAttrsToList - mkEnableOption - mkIf - mkOption - types; - - description = "Bot to forward CL notifications"; - cfg = config.services.depot.clbot; - - mkFlags = flags: - concatStringsSep " " - (attrValues (mapAttrs (key: value: "-${key} \"${toString value}\"") flags)); - - mkUnit = channel: channelFlags: { - name = "clbot-${utils.escapeSystemdPath channel}"; - value = { - description = "${description} to ${channel}"; - wantedBy = [ "multi-user.target" ]; - - script = "${depot.fun.clbot}/bin/clbot ${mkFlags (cfg.flags // channelFlags // { - irc_channel = channel; - })} -alsologtostderr"; - - serviceConfig = { - User = "clbot"; - EnvironmentFile = cfg.secretsFile; - Restart = "always"; - }; - }; - }; -in -{ - options.services.depot.clbot = { - enable = mkEnableOption description; - - flags = mkOption { - type = types.attrsOf types.str; - description = "Key value pairs for command line flags"; - }; - - channels = mkOption { - type = with types; attrsOf (attrsOf str); - description = "Channels in which to post (generates one unit per channel); nested attrs are used as extra flags to the service, which override the attrs in `flags`"; - }; - - secretsFile = mkOption { - type = types.str; - description = "EnvironmentFile from which to load secrets"; - default = config.age.secretsDir + "/clbot"; - }; - }; - - config = mkIf cfg.enable { - # This does not use DynamicUser because we need to make some files - # (notably the SSH private key) readable by this user outside of - # the module. - users = { - groups.clbot = { }; - - users.clbot = { - group = "clbot"; - isSystemUser = true; - }; - }; - - systemd.services = listToAttrs (mapAttrsToList mkUnit cfg.channels); - }; -}