mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License-AGPL.txt in the project root for license information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
flag "github.com/spf13/pflag"
|
|
)
|
|
|
|
var (
|
|
dbUser string
|
|
dbPass string
|
|
dbHost string
|
|
dbName string
|
|
command string
|
|
period time.Duration
|
|
timeout time.Duration
|
|
readyEndpointAddr string
|
|
)
|
|
|
|
func init() {
|
|
flag.StringVar(&dbHost, "db-host", "db:3306", "database hostname")
|
|
flag.StringVar(&dbName, "db-name", "gitpod", "database name")
|
|
flag.StringVar(&dbUser, "db-user", "root", "database username")
|
|
flag.StringVar(&dbPass, "db-pass", "test", "database password")
|
|
flag.DurationVar(&timeout, "timeout", 4*time.Hour, "time until the dev-staging installation is removed - must be a valid duration")
|
|
flag.DurationVar(&period, "period", 1*time.Minute, "time between activity checks - must be a valid duration")
|
|
flag.StringVarP(&command, "command", "c", "echo time is up", "command to execute once we've timed out")
|
|
flag.StringVar(&readyEndpointAddr, "ready-endpoint-addr", ":8080", "address where to serve the Kubernetes ready endpoint")
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
log.Printf("sweeper started")
|
|
|
|
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s", dbUser, dbPass, dbHost, dbName))
|
|
if err != nil {
|
|
log.Fatalf("cannot connect to DB: %+v", err)
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprintf(w, "ready")
|
|
})
|
|
|
|
log.Printf("serving ready endpoint on %s", readyEndpointAddr)
|
|
log.Fatal(http.ListenAndServe(readyEndpointAddr, nil))
|
|
}()
|
|
|
|
tick := time.NewTicker(period)
|
|
for {
|
|
t0 := getLastActivity(db)
|
|
if t0 == nil {
|
|
log.Fatalf("cannot determine last activity")
|
|
return
|
|
}
|
|
dt := time.Since(*t0)
|
|
log.Printf("last activity: %v (%s ago, %s until timeout)", t0.Format(time.RFC3339), dt.String(), (timeout - dt).String())
|
|
|
|
if dt > timeout {
|
|
log.Printf("timeout after %s, executing command: %s", dt.String(), command)
|
|
|
|
segs := strings.Split(command, " ")
|
|
cmd := exec.Command(segs[0], segs[1:]...)
|
|
cmd.Env = os.Environ()
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
log.Printf("cannot run command \"%s\": %+v", command, err)
|
|
}
|
|
}
|
|
|
|
<-tick.C
|
|
}
|
|
}
|
|
|
|
func getLastActivity(db *sql.DB) (lastActivity *time.Time) {
|
|
log.Printf("attempting to determine last time of activity")
|
|
|
|
srcs := []struct {
|
|
Name string
|
|
Query string
|
|
Format string
|
|
}{
|
|
{"latest instance", "SELECT creationTime FROM d_b_workspace_instance ORDER BY creationTime DESC LIMIT 1", time.RFC3339},
|
|
{"latest user", "SELECT creationDate FROM d_b_user ORDER BY creationDate DESC LIMIT 1", time.RFC3339},
|
|
{"heartbeat", "SELECT lastSeen FROM d_b_workspace_instance_user ORDER BY lastSeen DESC LIMIT 1", "2006-01-02 15:04:05.999999"},
|
|
}
|
|
var errors []error
|
|
for _, src := range srcs {
|
|
var rt string
|
|
err := db.QueryRow(src.Query).Scan(&rt)
|
|
if err != nil {
|
|
log.Printf("cannot query %s: %+v", src.Name, err)
|
|
errors = append(errors, err)
|
|
continue
|
|
}
|
|
|
|
var t time.Time
|
|
t, err = time.Parse(src.Format, rt)
|
|
if err != nil {
|
|
log.Printf("cannot parse %s: %+v", src.Name, err)
|
|
errors = append(errors, err)
|
|
continue
|
|
}
|
|
|
|
if lastActivity == nil || t.After(*lastActivity) {
|
|
lastActivity = &t
|
|
}
|
|
}
|
|
|
|
return lastActivity
|
|
}
|