converge/cmd/converge/converge.go

167 lines
4.7 KiB
Go

package main
import (
"converge/pkg/comms"
"converge/pkg/models"
"converge/pkg/server/converge"
"converge/pkg/support/websocketutil"
"fmt"
"log"
"math/rand"
"net"
"net/http"
_ "net/http/pprof"
"os"
"regexp"
"strconv"
"strings"
_ "time/tzdata"
)
func parsePublicId(path string) (publicId string, _ error) {
pattern := regexp.MustCompile("^/[^/]+/([^/]+)$")
matches := pattern.FindStringSubmatch(path)
if len(matches) != 2 {
return "", fmt.Errorf("Invalid URL path '%s'", path)
}
return matches[1], nil
}
func catchAllHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/docs", http.StatusFound)
return
}
func printHelp(msg string) {
if msg != "" {
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", msg)
}
helpText := "Usage: converge [options]\n" +
"\n" +
"Converge server is a rendez-vous server for debugging continuous integration\n" +
"jobs be providing the capability to log into the agents where jobs are running.\n" +
"This is achieve by starting an agent in the continuous integration job\n" +
"which connects to Converge using a websocket connection. The end user also connects\n" +
"to Converge using ssh over websockets. The server then matches the end-user with\n" +
"the agent running in the continous integration job (the rendez-vous) and sets up\n" +
"an end-to-end SSH connection between end-user and agent, with the agent providing\n" +
"an embedded SSH server to provide interactive access to the end-user. This works\n" +
"both on linux and on windows.\n" +
"\n" +
"-d <contentdir>: directory where static content of converge is placed"
fmt.Fprintln(os.Stderr, helpText)
os.Exit(1)
}
func main() {
downloadDir := "../static"
args := os.Args[1:]
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
switch args[0] {
case "-d":
if len(args) <= 1 {
printHelp("The -d option expects an argument")
}
downloadDir = args[1]
args = args[1:]
default:
printHelp("Unknown option " + args[0])
}
args = args[1:]
}
log.Println("Content directory", downloadDir)
if len(args) != 0 {
printHelp("")
}
userPassword := comms.UserPassword{
Username: strconv.Itoa(rand.Int()),
Password: strconv.Itoa(rand.Int()),
}
username, ok := os.LookupEnv("CONVERGE_USERNAME")
if ok {
userPassword.Username = username
} else {
os.Setenv("CONVERGE_USERNAME", userPassword.Username)
}
password, ok := os.LookupEnv("CONVERGE_PASSWORD")
if ok {
userPassword.Password = password
} else {
os.Setenv("CONVERGE_PASSWORD", userPassword.Password)
}
log.Printf("Using username '%s' and password '%s'", userPassword.Username, userPassword.Password)
notifications := make(chan *models.State, 10)
admin := converge.NewAdmin(notifications)
websessions := converge.NewWebSessions(notifications)
// For agents connecting
registrationService := websocketutil.WebSocketService{
Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) {
publicId, err := parsePublicId(r.URL.Path)
if err != nil {
log.Printf("Cannot parse public id from url: '%v'\n", err)
return
}
log.Printf("Got registration connection: '%s'\n", publicId)
err = admin.Register(publicId, conn,
userPassword)
if err != nil {
log.Printf("Error %v\n", err)
}
},
}
// For users connecting with ssh
clientService := websocketutil.WebSocketService{
Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) {
publicId, err := parsePublicId(r.URL.Path)
if err != nil {
log.Printf("Cannot parse public id from url: '%v'\n", err)
return
}
log.Printf("Got client connection: '%s'\n", publicId)
err = admin.Connect(publicId, conn)
if err != nil {
log.Printf("Error %v\n", err)
}
},
}
// for the web browser getting live status updates.
sessionService := websocketutil.WebSocketService{
Handler: func(w http.ResponseWriter, r *http.Request, conn net.Conn) {
websession := websessions.NewSession(conn)
defer websessions.SessionClosed(websession)
location, err := converge.GetUserLocation(r)
if err != nil {
panic(err)
}
websession.WriteNotifications(location)
},
Text: true,
}
// websocket endpoints
http.HandleFunc("/agent/", registrationService.Handle)
http.HandleFunc("/client/", clientService.Handle)
http.HandleFunc("/ws/sessions", sessionService.Handle)
// create filehandler with templating for html files.
http.Handle("/docs/", http.StripPrefix("/docs/", http.HandlerFunc(pageHandler)))
http.Handle("/static/", http.StripPrefix("/static/",
http.FileServer(http.Dir(downloadDir))))
http.HandleFunc("/", catchAllHandler)
// Start HTTP server
fmt.Println("Rendez-vous server listening on :8000")
log.Fatal(http.ListenAndServe(":8000", nil))
}