mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:11:49 +00:00
build-tracker: deploy to MSP (#61510)
Bring it into [year 2024] 🎉 will be building on this as part of #60455 OKR, so having a more convenient deploy method would be cool MSP PR: https://github.com/sourcegraph/managed-services/pull/1011 ## Test plan testing this live 👁️
This commit is contained in:
parent
e3891e9153
commit
c3ec21e436
@ -1,16 +1,27 @@
|
||||
load("//dev:go_defs.bzl", "go_test")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
|
||||
load("//dev:oci_defs.bzl", "image_repository", "oci_image", "oci_push", "oci_tarball")
|
||||
load("@rules_pkg//:pkg.bzl", "pkg_tar")
|
||||
load("@container_structure_test//:defs.bzl", "container_structure_test")
|
||||
load("//dev:msp_delivery.bzl", "msp_delivery")
|
||||
|
||||
go_library(
|
||||
name = "build-tracker_lib",
|
||||
srcs = ["main.go"],
|
||||
srcs = [
|
||||
"background.go",
|
||||
"main.go",
|
||||
],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/dev/build-tracker",
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//dev/build-tracker/build",
|
||||
"//dev/build-tracker/config",
|
||||
"//dev/build-tracker/notify",
|
||||
"//internal/goroutine",
|
||||
"//internal/version",
|
||||
"//lib/background",
|
||||
"//lib/errors",
|
||||
"//lib/managedservicesplatform/runtime",
|
||||
"@com_github_gorilla_mux//:mux",
|
||||
"@com_github_sourcegraph_log//:log",
|
||||
],
|
||||
@ -36,9 +47,59 @@ go_test(
|
||||
"//dev/build-tracker/config",
|
||||
"//dev/build-tracker/notify",
|
||||
"//dev/team",
|
||||
"//internal/goroutine",
|
||||
"@com_github_buildkite_go_buildkite_v3//buildkite",
|
||||
"@com_github_gorilla_mux//:mux",
|
||||
"@com_github_sourcegraph_log//logtest",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
pkg_tar(
|
||||
name = "tar_build-tracker",
|
||||
srcs = [":build-tracker"],
|
||||
)
|
||||
|
||||
oci_image(
|
||||
name = "image",
|
||||
base = "@wolfi_base",
|
||||
entrypoint = [
|
||||
"/sbin/tini",
|
||||
"--",
|
||||
"/build-tracker",
|
||||
],
|
||||
tars = [":tar_build-tracker"],
|
||||
user = "sourcegraph",
|
||||
)
|
||||
|
||||
oci_tarball(
|
||||
name = "image_tarball",
|
||||
image = ":image",
|
||||
repo_tags = ["build-tracker:candidate"],
|
||||
)
|
||||
|
||||
container_structure_test(
|
||||
name = "image_test",
|
||||
timeout = "short",
|
||||
configs = ["image_test.yaml"],
|
||||
driver = "docker",
|
||||
image = ":image",
|
||||
tags = [
|
||||
"exclusive",
|
||||
"requires-network",
|
||||
],
|
||||
)
|
||||
|
||||
oci_push(
|
||||
name = "candidate_push",
|
||||
image = ":image",
|
||||
repository = image_repository("build-tracker"),
|
||||
)
|
||||
|
||||
# automatic deployment not enabled for initial testing
|
||||
# msp_delivery(
|
||||
# name = "msp_deploy",
|
||||
# gcp_project = "build-tracker-prod-59bf",
|
||||
# msp_service_id = "build-tracker",
|
||||
# repository = "us.gcr.io/sourcegraph-dev/build-tracker",
|
||||
# )
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
FROM golang:1.19.8-alpine@sha256:841c160ed35923d96c95c52403c4e6db5decd9cbce034aa851e412ade5d4b74f AS build-tracker-build
|
||||
|
||||
ENV GO111MODULE on
|
||||
ENV GOARCH amd64
|
||||
ENV GOOS linux
|
||||
|
||||
COPY . /repo
|
||||
|
||||
WORKDIR /repo/dev/build-tracker
|
||||
|
||||
RUN go build -o /build-tracker .
|
||||
|
||||
FROM sourcegraph/alpine-3.14:213466_2023-04-17_5.0-bdda34a71619@sha256:6354a4ff578b685e36c8fbde81f62125ae0011b047fb2cc22d1b0de616b3c59a AS build-tracker
|
||||
|
||||
RUN apk --no-cache add tzdata
|
||||
COPY --from=build-tracker-build /build-tracker /usr/local/bin/build-tracker
|
||||
ENTRYPOINT ["build-tracker"]
|
||||
29
dev/build-tracker/background.go
Normal file
29
dev/build-tracker/background.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/sourcegraph/log"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/build"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
)
|
||||
|
||||
func deleteOldBuilds(logger log.Logger, store *build.Store, every, window time.Duration) goroutine.BackgroundRoutine {
|
||||
return goroutine.NewPeriodicGoroutine(context.Background(), goroutine.HandlerFunc(func(ctx context.Context) error {
|
||||
oldBuilds := make([]int, 0)
|
||||
now := time.Now()
|
||||
for _, b := range store.FinishedBuilds() {
|
||||
finishedAt := *b.FinishedAt
|
||||
delta := now.Sub(finishedAt.Time)
|
||||
if delta >= window {
|
||||
logger.Debug("build past age window", log.Int("buildNumber", *b.Number), log.Time("FinishedAt", finishedAt.Time), log.Duration("window", window))
|
||||
oldBuilds = append(oldBuilds, *b.Number)
|
||||
}
|
||||
}
|
||||
logger.Info("deleting old builds", log.Int("oldBuildCount", len(oldBuilds)))
|
||||
store.DelByBuildNumber(oldBuilds...)
|
||||
return nil
|
||||
}), goroutine.WithInterval(every))
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# This script builds the build-tracker docker image.
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
set -eu
|
||||
|
||||
IMAGE="us-central1-docker.pkg.dev/sourcegraph-ci/build-tracker/build-tracker"
|
||||
|
||||
echo "--- docker build build-tracker $(pwd)"
|
||||
docker build -f dev/build-tracker/Dockerfile -t "$IMAGE" "$(pwd)" \
|
||||
|
||||
#docker push $IMAGE
|
||||
@ -5,5 +5,5 @@ go_library(
|
||||
srcs = ["config.go"],
|
||||
importpath = "github.com/sourcegraph/sourcegraph/dev/build-tracker/config",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//lib/errors"],
|
||||
deps = ["//lib/managedservicesplatform/runtime"],
|
||||
)
|
||||
|
||||
@ -1,82 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
import "github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime"
|
||||
|
||||
const DefaultChannel = "#william-buildchecker-webhook-test"
|
||||
|
||||
type Config struct {
|
||||
BuildkiteToken string
|
||||
SlackToken string
|
||||
GithubToken string
|
||||
SlackChannel string
|
||||
Production bool
|
||||
DebugPassword string
|
||||
}
|
||||
|
||||
func NewFromEnv() (*Config, error) {
|
||||
var c Config
|
||||
|
||||
err := envVar("BUILDKITE_WEBHOOK_TOKEN", &c.BuildkiteToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = envVar("SLACK_TOKEN", &c.SlackToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = envVar("GITHUB_TOKEN", &c.GithubToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = envVar("SLACK_CHANNEL", &c.SlackChannel)
|
||||
if err != nil {
|
||||
c.SlackChannel = DefaultChannel
|
||||
}
|
||||
|
||||
err = envVar("BUILDTRACKER_PRODUCTION", &c.Production)
|
||||
if err != nil {
|
||||
c.Production = false
|
||||
}
|
||||
func (c *Config) Load(env *runtime.Env) {
|
||||
c.BuildkiteToken = env.Get("BUILDKITE_WEBHOOK_TOKEN", "", "")
|
||||
c.SlackToken = env.Get("SLACK_TOKEN", "", "")
|
||||
c.SlackChannel = env.Get("SLACK_CHANNEL", DefaultChannel, "")
|
||||
c.Production = env.GetBool("BUILDTRACKER_PRODUCTION", "false", "")
|
||||
|
||||
if c.Production {
|
||||
_ = envVar("BUILDTRACKER_DEBUG_PASSWORD", &c.DebugPassword)
|
||||
if c.DebugPassword == "" {
|
||||
return nil, errors.New("BUILDTRACKER_DEBUG_PASSWORD is required when BUILDTRACKER_PRODUCTION is true")
|
||||
}
|
||||
c.DebugPassword = env.Get("BUIDLTRACKER_DEBUG_PASSWORD", "", "")
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func envVar[T any](name string, target *T) error {
|
||||
value, exists := os.LookupEnv(name)
|
||||
if !exists {
|
||||
return errors.Newf("%s not found in environment", name)
|
||||
}
|
||||
|
||||
switch p := any(target).(type) {
|
||||
case *bool:
|
||||
{
|
||||
v, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = v
|
||||
}
|
||||
case *string:
|
||||
{
|
||||
*p = value
|
||||
}
|
||||
default:
|
||||
panic(errors.Newf("unsuporrted target type %T", target))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
15
dev/build-tracker/image_test.yaml
Normal file
15
dev/build-tracker/image_test.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
schemaVersion: "2.0.0"
|
||||
|
||||
commandTests:
|
||||
- name: "binary is runnable"
|
||||
command: "/build-tracker"
|
||||
envVars:
|
||||
- key: "SANITY_CHECK"
|
||||
value: "true"
|
||||
|
||||
- name: "not running as root"
|
||||
command: "/usr/bin/id"
|
||||
args:
|
||||
- -u
|
||||
excludedOutput: ["^0"]
|
||||
exitCode: 0
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/buildkite/go-buildkite/v3/buildkite"
|
||||
@ -15,8 +16,10 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/dev/team"
|
||||
)
|
||||
|
||||
var RunSlackIntegrationTest = flag.Bool("RunSlackIntegrationTest", false, "Run Slack integration tests")
|
||||
var RunGitHubIntegrationTest = flag.Bool("RunGitHubIntegrationTest", false, "Run Github integration tests")
|
||||
var (
|
||||
RunSlackIntegrationTest = flag.Bool("RunSlackIntegrationTest", false, "Run Slack integration tests")
|
||||
RunGitHubIntegrationTest = flag.Bool("RunGitHubIntegrationTest", false, "Run Github integration tests")
|
||||
)
|
||||
|
||||
type TestJobLine struct {
|
||||
title string
|
||||
@ -78,14 +81,9 @@ func TestLargeAmountOfFailures(t *testing.T) {
|
||||
}
|
||||
logger := logtest.NoOp(t)
|
||||
|
||||
conf, err := config.NewFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := notify.NewClient(logger, os.Getenv("SLACK_TOKEN"), config.DefaultChannel)
|
||||
|
||||
client := notify.NewClient(logger, conf.SlackToken, conf.GithubToken, config.DefaultChannel)
|
||||
|
||||
err = client.Send(info)
|
||||
err := client.Send(info)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to send build: %s", err)
|
||||
}
|
||||
@ -120,14 +118,9 @@ func TestGetTeammateFromBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
logger := logtest.NoOp(t)
|
||||
conf, err := config.NewFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conf.SlackChannel = config.DefaultChannel
|
||||
|
||||
t.Run("with nil author, commit author is still retrieved", func(t *testing.T) {
|
||||
client := notify.NewClient(logger, conf.SlackToken, conf.GithubToken, conf.SlackChannel)
|
||||
client := notify.NewClient(logger, os.Getenv("SLACK_TOKEN"), config.DefaultChannel)
|
||||
|
||||
num := 160000
|
||||
commit := "ca7c44f79984ff8d645b580bfaaf08ce9a37a05d"
|
||||
@ -141,7 +134,7 @@ func TestGetTeammateFromBuild(t *testing.T) {
|
||||
Number: &num,
|
||||
Commit: &commit,
|
||||
},
|
||||
Pipeline: &build.Pipeline{buildkite.Pipeline{
|
||||
Pipeline: &build.Pipeline{Pipeline: buildkite.Pipeline{
|
||||
Name: &pipelineID,
|
||||
}},
|
||||
Steps: map[string]*build.Step{},
|
||||
@ -153,7 +146,7 @@ func TestGetTeammateFromBuild(t *testing.T) {
|
||||
require.Equal(t, teammate.Name, "Leo Papaloizos")
|
||||
})
|
||||
t.Run("commit author preferred over build author", func(t *testing.T) {
|
||||
client := notify.NewClient(logger, conf.SlackToken, conf.GithubToken, conf.SlackChannel)
|
||||
client := notify.NewClient(logger, os.Getenv("SLACK_TOKEN"), config.DefaultChannel)
|
||||
|
||||
num := 160000
|
||||
commit := "78926a5b3b836a8a104a5d5adf891e5626b1e405"
|
||||
@ -171,7 +164,7 @@ func TestGetTeammateFromBuild(t *testing.T) {
|
||||
Email: "william.bezuidenhout@sourcegraph.com",
|
||||
},
|
||||
},
|
||||
Pipeline: &build.Pipeline{buildkite.Pipeline{
|
||||
Pipeline: &build.Pipeline{Pipeline: buildkite.Pipeline{
|
||||
Name: &pipelineID,
|
||||
}},
|
||||
Steps: map[string]*build.Step{},
|
||||
@ -182,7 +175,7 @@ func TestGetTeammateFromBuild(t *testing.T) {
|
||||
require.Equal(t, teammate.Name, "Ryan Slade")
|
||||
})
|
||||
t.Run("retrieving teammate for build populates cache", func(t *testing.T) {
|
||||
client := notify.NewClient(logger, conf.SlackToken, conf.GithubToken, conf.SlackChannel)
|
||||
client := notify.NewClient(logger, os.Getenv("SLACK_TOKEN"), config.DefaultChannel)
|
||||
|
||||
num := 160000
|
||||
commit := "78926a5b3b836a8a104a5d5adf891e5626b1e405"
|
||||
@ -200,7 +193,7 @@ func TestGetTeammateFromBuild(t *testing.T) {
|
||||
Email: "william.bezuidenhout@sourcegraph.com",
|
||||
},
|
||||
},
|
||||
Pipeline: &build.Pipeline{buildkite.Pipeline{
|
||||
Pipeline: &build.Pipeline{Pipeline: buildkite.Pipeline{
|
||||
Name: &pipelineID,
|
||||
}},
|
||||
Steps: map[string]*build.Step{},
|
||||
@ -219,12 +212,7 @@ func TestSlackNotification(t *testing.T) {
|
||||
}
|
||||
logger := logtest.NoOp(t)
|
||||
|
||||
conf, err := config.NewFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client := notify.NewClient(logger, conf.SlackToken, conf.GithubToken, config.DefaultChannel)
|
||||
client := notify.NewClient(logger, os.Getenv("SLACK_TOKEN"), config.DefaultChannel)
|
||||
|
||||
// Each child test needs to increment this number, otherwise notifications will be overwritten
|
||||
buildNumber := 160000
|
||||
@ -442,12 +430,13 @@ func TestServerNotify(t *testing.T) {
|
||||
}
|
||||
logger := logtest.NoOp(t)
|
||||
|
||||
conf, err := config.NewFromEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
conf := config.Config{
|
||||
BuildkiteToken: os.Getenv("BUILDKITE_WEBHOOK_TOKEN"),
|
||||
SlackToken: os.Getenv("SLACK_TOKEN"),
|
||||
SlackChannel: os.Getenv("SLACK_CHANNEL"),
|
||||
}
|
||||
|
||||
server := NewServer(logger, *conf)
|
||||
server := NewServer(":8080", logger, conf)
|
||||
|
||||
num := 160000
|
||||
url := "http://www.google.com"
|
||||
@ -474,7 +463,7 @@ func TestServerNotify(t *testing.T) {
|
||||
URL: &url,
|
||||
Commit: &commit,
|
||||
},
|
||||
Pipeline: &build.Pipeline{buildkite.Pipeline{
|
||||
Pipeline: &build.Pipeline{Pipeline: buildkite.Pipeline{
|
||||
Name: &pipelineID,
|
||||
}},
|
||||
Steps: map[string]*build.Step{
|
||||
@ -483,7 +472,7 @@ func TestServerNotify(t *testing.T) {
|
||||
}
|
||||
|
||||
// post a new notification
|
||||
err = server.notifyIfFailed(build)
|
||||
err := server.notifyIfFailed(build)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to send slack notification: %v", err)
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -14,12 +16,17 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/build"
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/config"
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/notify"
|
||||
"github.com/sourcegraph/sourcegraph/internal/version"
|
||||
"github.com/sourcegraph/sourcegraph/lib/background"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime"
|
||||
)
|
||||
|
||||
var ErrInvalidToken = errors.New("buildkite token is invalid")
|
||||
var ErrInvalidHeader = errors.New("Header of request is invalid")
|
||||
var ErrUnwantedEvent = errors.New("Unwanted event received")
|
||||
var (
|
||||
ErrInvalidToken = errors.New("buildkite token is invalid")
|
||||
ErrInvalidHeader = errors.New("Header of request is invalid")
|
||||
ErrUnwantedEvent = errors.New("Unwanted event received")
|
||||
)
|
||||
|
||||
var nowFunc = time.Now
|
||||
|
||||
@ -41,13 +48,13 @@ type Server struct {
|
||||
}
|
||||
|
||||
// NewServer creatse a new server to listen for Buildkite webhook events.
|
||||
func NewServer(logger log.Logger, c config.Config) *Server {
|
||||
func NewServer(addr string, logger log.Logger, c config.Config) *Server {
|
||||
logger = logger.Scoped("server")
|
||||
server := &Server{
|
||||
logger: logger,
|
||||
store: build.NewBuildStore(logger),
|
||||
config: &c,
|
||||
notifyClient: notify.NewClient(logger, c.SlackToken, c.GithubToken, c.SlackChannel),
|
||||
notifyClient: notify.NewClient(logger, c.SlackToken, c.SlackChannel),
|
||||
}
|
||||
|
||||
// Register routes the the server will be responding too
|
||||
@ -60,12 +67,28 @@ func NewServer(logger log.Logger, c config.Config) *Server {
|
||||
|
||||
server.http = &http.Server{
|
||||
Handler: r,
|
||||
Addr: ":8080",
|
||||
Addr: addr,
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func (s *Server) Start() {
|
||||
if err := s.http.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.logger.Error("error stopping server", log.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := s.http.Shutdown(ctx); err != nil {
|
||||
s.logger.Error("error shutting down server", log.Error(err))
|
||||
} else {
|
||||
s.logger.Info("server stopped")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleGetBuild(w http.ResponseWriter, req *http.Request) {
|
||||
if s.config.Production {
|
||||
user, pass, ok := req.BasicAuth()
|
||||
@ -192,40 +215,6 @@ func (s *Server) notifyIfFailed(b *build.Build) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) deleteOldBuilds(window time.Duration) {
|
||||
oldBuilds := make([]int, 0)
|
||||
now := nowFunc()
|
||||
for _, b := range s.store.FinishedBuilds() {
|
||||
finishedAt := *b.FinishedAt
|
||||
delta := now.Sub(finishedAt.Time)
|
||||
if delta >= window {
|
||||
s.logger.Debug("build past age window", log.Int("buildNumber", *b.Number), log.Time("FinishedAt", finishedAt.Time), log.Duration("window", window))
|
||||
oldBuilds = append(oldBuilds, *b.Number)
|
||||
}
|
||||
}
|
||||
s.logger.Info("deleting old builds", log.Int("oldBuildCount", len(oldBuilds)))
|
||||
s.store.DelByBuildNumber(oldBuilds...)
|
||||
}
|
||||
|
||||
func (s *Server) startCleaner(every, window time.Duration) func() {
|
||||
ticker := time.NewTicker(every)
|
||||
done := make(chan interface{})
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.deleteOldBuilds(window)
|
||||
case <-done:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func() { done <- nil }
|
||||
}
|
||||
|
||||
// processEvent processes a BuildEvent received from Buildkite. If the event is for a `build.finished` event we get the
|
||||
// full build which includes all recorded jobs for the build and send a notification.
|
||||
// processEvent delegates the decision to actually send a notifcation
|
||||
@ -285,33 +274,21 @@ func determineBuildStatusNotification(logger log.Logger, b *build.Build) *notify
|
||||
return &info
|
||||
}
|
||||
|
||||
// Serve starts the http server and listens for buildkite build events to be sent on the route "/buildkite"
|
||||
func main() {
|
||||
sync := log.Init(log.Resource{
|
||||
Name: "BuildTracker",
|
||||
Namespace: "CI",
|
||||
})
|
||||
defer sync.Sync()
|
||||
|
||||
logger := log.Scoped("BuildTracker")
|
||||
|
||||
serverConf, err := config.NewFromEnv()
|
||||
if err != nil {
|
||||
logger.Fatal("failed to get config from env", log.Error(err))
|
||||
}
|
||||
logger.Info("config loaded from environment", log.Object("config", log.String("SlackChannel", serverConf.SlackChannel), log.Bool("Production", serverConf.Production)))
|
||||
server := NewServer(logger, *serverConf)
|
||||
|
||||
stopFn := server.startCleaner(CleanUpInterval, BuildExpiryWindow)
|
||||
defer stopFn()
|
||||
|
||||
if server.config.Production {
|
||||
server.logger.Info("server is in production mode!")
|
||||
} else {
|
||||
server.logger.Info("server is in development mode!")
|
||||
}
|
||||
|
||||
if err := server.http.ListenAndServe(); err != nil {
|
||||
logger.Fatal("server exited with error", log.Error(err))
|
||||
}
|
||||
runtime.Start[config.Config](Service{})
|
||||
}
|
||||
|
||||
type Service struct{}
|
||||
|
||||
// Initialize implements runtime.Service.
|
||||
func (s Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.Contract, config config.Config) (background.Routine, error) {
|
||||
logger.Info("config loaded from environment", log.Object("config", log.String("SlackChannel", config.SlackChannel), log.Bool("Production", config.Production)))
|
||||
|
||||
return background.CombinedRoutine{
|
||||
NewServer(fmt.Sprintf(":%d", contract.Port), logger, config),
|
||||
deleteOldBuilds(logger, nil, CleanUpInterval, BuildExpiryWindow),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Service) Name() string { return "build-tracker" }
|
||||
func (s Service) Version() string { return version.Version() }
|
||||
|
||||
@ -18,18 +18,6 @@ import (
|
||||
|
||||
const StepShowLimit = 5
|
||||
|
||||
type cacheItem[T any] struct {
|
||||
Value T
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func newCacheItem[T any](value T) *cacheItem[T] {
|
||||
return &cacheItem[T]{
|
||||
Value: value,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type NotificationClient interface {
|
||||
Send(info *BuildNotification) error
|
||||
GetNotification(buildNumber int) *SlackNotification
|
||||
@ -101,7 +89,7 @@ func NewSlackNotification(id, channel string, info *BuildNotification, author st
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(logger log.Logger, slackToken, githubToken, channel string) *Client {
|
||||
func NewClient(logger log.Logger, slackToken, channel string) *Client {
|
||||
debug := os.Getenv("BUILD_TRACKER_SLACK_DEBUG") == "1"
|
||||
slackClient := slack.New(slackToken, slack.OptionDebug(debug))
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -15,6 +16,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/build"
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/config"
|
||||
"github.com/sourcegraph/sourcegraph/dev/build-tracker/notify"
|
||||
"github.com/sourcegraph/sourcegraph/internal/goroutine"
|
||||
)
|
||||
|
||||
func TestGetBuild(t *testing.T) {
|
||||
@ -23,7 +25,7 @@ func TestGetBuild(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, "/-/debug/1234", nil)
|
||||
req = mux.SetURLVars(req, map[string]string{"buildNumber": "1234"})
|
||||
t.Run("401 Unauthorized when in production mode and incorrect credentials", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{Production: true, DebugPassword: "this is a test"})
|
||||
server := NewServer(":8080", logger, config.Config{Production: true, DebugPassword: "this is a test"})
|
||||
rec := httptest.NewRecorder()
|
||||
server.handleGetBuild(rec, req)
|
||||
|
||||
@ -36,7 +38,7 @@ func TestGetBuild(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("404 for build that does not exist", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
rec := httptest.NewRecorder()
|
||||
server.handleGetBuild(rec, req)
|
||||
|
||||
@ -44,7 +46,7 @@ func TestGetBuild(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("get marshalled json for build", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
num := 1234
|
||||
@ -97,7 +99,7 @@ func TestGetBuild(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("200 with valid credentials in production mode", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{Production: true, DebugPassword: "this is a test"})
|
||||
server := NewServer(":8080", logger, config.Config{Production: true, DebugPassword: "this is a test"})
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
req.SetBasicAuth("devx", server.config.DebugPassword)
|
||||
@ -129,7 +131,7 @@ func TestOldBuildsGetDeleted(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("All old builds get removed", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
b := finishedBuild(1, "passed", time.Now().AddDate(-1, 0, 0))
|
||||
server.store.Set(b)
|
||||
|
||||
@ -139,9 +141,10 @@ func TestOldBuildsGetDeleted(t *testing.T) {
|
||||
b = finishedBuild(3, "failed", time.Now().AddDate(0, 0, -1))
|
||||
server.store.Set(b)
|
||||
|
||||
stopFunc := server.startCleaner(10*time.Millisecond, 24*time.Hour)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go goroutine.MonitorBackgroundRoutines(ctx, deleteOldBuilds(logger, server.store, 10*time.Millisecond, 24*time.Hour))
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
stopFunc()
|
||||
cancel()
|
||||
|
||||
builds := server.store.FinishedBuilds()
|
||||
|
||||
@ -150,7 +153,7 @@ func TestOldBuildsGetDeleted(t *testing.T) {
|
||||
}
|
||||
})
|
||||
t.Run("1 build left after old builds are removed", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
b := finishedBuild(1, "canceled", time.Now().AddDate(-1, 0, 0))
|
||||
server.store.Set(b)
|
||||
|
||||
@ -160,9 +163,10 @@ func TestOldBuildsGetDeleted(t *testing.T) {
|
||||
b = finishedBuild(3, "failed", time.Now())
|
||||
server.store.Set(b)
|
||||
|
||||
stopFunc := server.startCleaner(10*time.Millisecond, 24*time.Hour)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go goroutine.MonitorBackgroundRoutines(ctx, deleteOldBuilds(logger, server.store, 10*time.Millisecond, 24*time.Hour))
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
stopFunc()
|
||||
cancel()
|
||||
|
||||
builds := server.store.FinishedBuilds()
|
||||
|
||||
@ -170,7 +174,6 @@ func TestOldBuildsGetDeleted(t *testing.T) {
|
||||
t.Errorf("Expected one build to be left over. Got %d, wanted %d", len(builds), 1)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
type MockNotificationClient struct {
|
||||
@ -231,7 +234,7 @@ func TestProcessEvent(t *testing.T) {
|
||||
return &build.Event{Name: build.EventBuildFinished, Build: buildkite.Build{State: &state, Number: &buildNumber, Pipeline: pipeline}, Job: job.Job}
|
||||
}
|
||||
t.Run("no send notification on unfinished builds", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
mockNotifyClient := &MockNotificationClient{}
|
||||
server.notifyClient = mockNotifyClient
|
||||
buildNumber := 1234
|
||||
@ -248,7 +251,7 @@ func TestProcessEvent(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("failed build sends notification", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
mockNotifyClient := &MockNotificationClient{}
|
||||
server.notifyClient = mockNotifyClient
|
||||
buildNumber := 1234
|
||||
@ -264,7 +267,7 @@ func TestProcessEvent(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("passed build sends notification", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
mockNotifyClient := &MockNotificationClient{}
|
||||
server.notifyClient = mockNotifyClient
|
||||
buildNumber := 1234
|
||||
@ -280,7 +283,7 @@ func TestProcessEvent(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("failed build, then passed build sends fixed notification", func(t *testing.T) {
|
||||
server := NewServer(logger, config.Config{})
|
||||
server := NewServer(":8080", logger, config.Config{})
|
||||
mockNotifyClient := &MockNotificationClient{}
|
||||
server.notifyClient = mockNotifyClient
|
||||
buildNumber := 1234
|
||||
|
||||
Loading…
Reference in New Issue
Block a user