Replace DeployErrors with a better system(?) (fix #42)

This commit is contained in:
Noah 2024-06-11 14:00:32 -05:00
parent 20b955231b
commit a122598484
5 changed files with 71 additions and 99 deletions

View File

@ -1,30 +0,0 @@
// package main is the main package for the LapisDeploy program
package main
import (
"fmt"
)
// DeployError contains important information about an error if something went wrong.
type DeployError struct {
code int
where string
details string
command_output string
}
// SendOverMatrix provides an easy way to send the contents of a DeployError over Matrix
func (deploy_error *DeployError) SendOverMatrix() {
sendMessage(MatrixMessage{text: deploy_error.ToString()})
}
// ToString converts a DeployError into a human readable string.
func (deploy_error *DeployError) ToString() string {
return fmt.Sprintf("%sError in `%s`! (%s) Code: %d",
Circles["red"], deploy_error.where, deploy_error.details, deploy_error.code)
}
// newDeployError creates a DeployError
func newDeployError(code int, where string, details string, command_output string) DeployError {
return DeployError{code: code, where: where, details: details, command_output: command_output}
}

46
main.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
@ -12,7 +13,7 @@ import (
"github.com/akamensky/argparse" "github.com/akamensky/argparse"
) )
// Circle emoji storage. // Circle emoji
var Circles = map[string]string{ var Circles = map[string]string{
"white": "⚪️ ", "white": "⚪️ ",
"green": "🟢 ", "green": "🟢 ",
@ -21,9 +22,6 @@ var Circles = map[string]string{
"orange": "🟠 ", "orange": "🟠 ",
} }
// Create a configuration struct
var configuration = Configuration{}
// fileExists returns whether the given file or directory exists. // fileExists returns whether the given file or directory exists.
func fileExists(path string) (bool, error) { func fileExists(path string) (bool, error) {
_, err := os.Stat(path) _, err := os.Stat(path)
@ -36,51 +34,51 @@ func fileExists(path string) (bool, error) {
return false, err return false, err
} }
// Create a configuration struct
var configuration = Configuration{}
// handler is in charge of handling requests after the JSON has been parsed // handler is in charge of handling requests after the JSON has been parsed
func handler(data map[string]interface{}) { func handler(data map[string]interface{}) {
repository := data["repository"].(map[string]interface{}) repository := data["repository"].(map[string]interface{})
log.Default().Printf("Repo: %s", repository["full_name"]) log.Default().Printf("Repo: %s", repository["full_name"])
sendMessage(MatrixMessage{text: fmt.Sprintf("%sHandling webhook for `%s`...", sendInfo(fmt.Sprintf("Handling webhook for `%s`...", repository["full_name"]))
Circles["white"], repository["full_name"])})
repo_name := repository["name"].(string) repo_name := repository["name"].(string)
site, exists, err := getSite(repo_name) site, exists, err := getSite(repo_name)
if err != nil { if err != nil {
deploy_error := newDeployError(1, "handler", fmt.Sprintf("Failed to check if site '%s' exists", repo_name), fmt.Sprint(err)) sendError(errors.New(fmt.Sprintf("Failed to check if site '%s' exists: %s", repo_name, err)))
deploy_error.SendOverMatrix()
return return
} }
if exists { if exists {
sendMessage(MatrixMessage{text: Circles["white"] + "Updating repository..."}) sendInfo("Updating repository...")
if deploy_error := site.Update(); deploy_error.code != 0 { if err := site.Update(); err != nil {
deploy_error.SendOverMatrix() sendError(err)
return return
} }
new_site, exists, err := getSite(site.name) new_site, exists, err := getSite(site.name)
if err != nil || !exists { if err != nil || !exists {
newDeployError(1, "main", "Failed to update site data on a site we just created!", fmt.Sprint(err)) sendError(errors.New("Failed to update site data on a site we just updated: " + repo_name))
} }
site = new_site site = new_site
sendMessage(MatrixMessage{text: Circles["white"] + "Restarting server..."}) sendInfo("Restarting server...")
if deploy_error := site.Restart(); deploy_error.code != 0 { if error := site.Restart(); error != nil {
deploy_error.SendOverMatrix() sendError(error)
return return
} }
} else { } else {
sendMessage(MatrixMessage{text: Circles["white"] + "Cloning repository..."}) sendInfo("Cloning repositiory...")
if deploy_error := CloneSite(repository["ssh_url"].(string), repo_name); deploy_error.code != 0 { if error := CloneSite(repository["ssh_url"].(string), repo_name); error != nil {
deploy_error.SendOverMatrix() sendError(error)
return return
} }
sendMessage(MatrixMessage{text: Circles["white"] + "Starting server..."}) sendInfo("Starting server...")
if site, exists, err = getSite(repo_name); err != nil { if site, exists, err = getSite(repo_name); err != nil {
deploy_error := newDeployError(1, "handler", sendError(err)
fmt.Sprintf("Failed to get site '%s' after creation!", repo_name), fmt.Sprint(err)) return
deploy_error.SendOverMatrix()
} }
if deploy_error := site.Start(); deploy_error.code != 0 { if error := site.Start(); error != nil {
deploy_error.SendOverMatrix() sendError(err)
return return
} }
defer sendMessage(MatrixMessage{text: "🚀 Launched for the first time!"}) defer sendMessage(MatrixMessage{text: "🚀 Launched for the first time!"})

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt"
"log" "log"
"strings" "strings"
"time" "time"
@ -29,15 +30,29 @@ type MatrixCommand struct {
description string description string
} }
// Output queue // Output queue.
var MatrixOut []MatrixMessage var MatrixOut []MatrixMessage
// sendMessage sends a message through Matrix // sendMessage sends a message through Matrix.
func sendMessage(msg MatrixMessage) { func sendMessage(msg MatrixMessage) {
log.Print(msg.text) log.Print(msg.text)
MatrixOut = append(MatrixOut, msg) MatrixOut = append(MatrixOut, msg)
} }
// sendError sends an error through Matrix.
func sendError(err error) {
sendMessage(MatrixMessage{
text: fmt.Sprintf("%s %s", Circles["red"], err),
})
}
// sendInfo sends a bit of info text through Matrix.
func sendInfo(text string) {
sendMessage(MatrixMessage{
text: fmt.Sprintf("%s %s", Circles["white"], text),
})
}
// initMatrix starts a Matrix bot. // initMatrix starts a Matrix bot.
func initMatrix() { func initMatrix() {
client, err := mautrix.NewClient(configuration.matrix.homeserver, "", "") client, err := mautrix.NewClient(configuration.matrix.homeserver, "", "")

View File

@ -16,9 +16,9 @@ func getSitesByString(text string) ([]Site, []string) {
sites := []Site{} // sites holds every site we need sites := []Site{} // sites holds every site we need
// Determine which sites to get // Determine which sites to get
if text == "all" { // If we need to get all sites, (!restart all) if text == "all" { // If we need to get all sites, (!restart all)
var err DeployError var err error
sites, err = getAllSites() // Get all sites sites, err = getAllSites() // Get all sites
if err.code != 0 { // Check for errors if err != nil { // Check for errors
errors = append(errors, "Failed to get all sites!") errors = append(errors, "Failed to get all sites!")
} }
} else { // If we need to get a specific site, (!restart LapisDeploy-TestApp) } else { // If we need to get a specific site, (!restart LapisDeploy-TestApp)
@ -53,7 +53,7 @@ func registerChatCommands() {
callback: func(ctx context.Context, evt *event.Event, args []string) string { callback: func(ctx context.Context, evt *event.Event, args []string) string {
text := Circles["white"] + "Getting sites...\n" text := Circles["white"] + "Getting sites...\n"
sites, err := getAllSites() sites, err := getAllSites()
if err.code != 0 { if err != nil {
return text + Circles["red"] + "Failed to get sites!" return text + Circles["red"] + "Failed to get sites!"
} }
for _, site := range sites { for _, site := range sites {
@ -82,9 +82,8 @@ func registerChatCommands() {
text += Circles["red"] + error + "\n" text += Circles["red"] + error + "\n"
} }
for _, site := range sites { // For every site queued to be restarted, for _, site := range sites { // For every site queued to be restarted,
if err := site.Restart(); err.code != 0 { // Attempt to restart it if err := site.Restart(); err != nil { // Attempt to restart it
text += fmt.Sprintf("%sRestarting '%s'... failed!\n", Circles["red"], site.getName()) text += fmt.Sprintf("%sRestarting '%s'... failed!\n", Circles["red"], site.getName())
text += err.ToString()
} else { // Otherwise, success. } else { // Otherwise, success.
text += fmt.Sprintf("%sRestarting '%s'... success", Circles["green"], site.getName()) text += fmt.Sprintf("%sRestarting '%s'... success", Circles["green"], site.getName())
} }
@ -105,9 +104,8 @@ func registerChatCommands() {
text += Circles["red"] + error + "\n" text += Circles["red"] + error + "\n"
} }
for _, site := range sites { for _, site := range sites {
if err := site.Start(); err.code != 0 { if err := site.Start(); err != nil {
text += fmt.Sprintf("%sStarting '%s'... failed!\n", Circles["red"], site.getName()) text += fmt.Sprintf("%sStarting '%s'... failed!\n", Circles["red"], site.getName())
text += err.ToString()
} else { } else {
text += fmt.Sprintf("%sStarting '%s'... success!\n", Circles["green"], site.getName()) text += fmt.Sprintf("%sStarting '%s'... success!\n", Circles["green"], site.getName())
} }
@ -128,9 +126,8 @@ func registerChatCommands() {
text += Circles["red"] + error + "\n" text += Circles["red"] + error + "\n"
} }
for _, site := range sites { for _, site := range sites {
if err := site.Stop(); err.code != 0 { if err := site.Stop(); err != nil {
text += fmt.Sprintf("%sStopping '%s'... failed!", Circles["red"], site.getName()) text += fmt.Sprintf("%sStopping '%s'... failed: %s", Circles["red"], site.getName(), err)
text += err.ToString()
} else { } else {
text += fmt.Sprintf("%sStopping '%s'... success", Circles["green"], site.getName()) text += fmt.Sprintf("%sStopping '%s'... success", Circles["green"], site.getName())
} }

58
site.go
View File

@ -98,13 +98,12 @@ func (site *Site) getName() string {
} }
// getAllSites gets every site installed. // getAllSites gets every site installed.
func getAllSites() ([]Site, DeployError) { func getAllSites() ([]Site, error) {
sites_dir := configuration.sites_dir sites_dir := configuration.sites_dir
files, err := os.ReadDir(sites_dir) files, err := os.ReadDir(sites_dir)
var sites []Site var sites []Site
if err != nil { if err != nil {
return sites, newDeployError(1, "startAllSites", return sites, errors.New(fmt.Sprintf("Failed to read sites: %s", err))
"Failed to read sites", fmt.Sprint(err))
} }
for _, file := range files { for _, file := range files {
if !file.IsDir() { if !file.IsDir() {
@ -112,30 +111,30 @@ func getAllSites() ([]Site, DeployError) {
} }
site, exists, err := getSite(file.Name()) site, exists, err := getSite(file.Name())
if err != nil || !exists { if err != nil || !exists {
return sites, newDeployError(1, "getAllSites", "Failed to read sites", fmt.Sprint(err)) return sites, errors.New(fmt.Sprintf("Failed to read sites: %s", err))
} }
sites = append(sites, site) sites = append(sites, site)
} }
return sites, DeployError{} return sites, nil
} }
// startAllSites attempts to start every site // startAllSites attempts to start every site
func startAllSites() DeployError { func startAllSites() error {
sites, err := getAllSites() sites, err := getAllSites()
if err.code != 0 { if err != nil {
return err return err
} }
for _, site := range sites { for _, site := range sites {
err = site.Start() err = site.Start()
if err.code != 0 { if err != nil {
return err return err
} }
} }
return DeployError{} return nil
} }
// Start attempts to start a Lapis server in the given repository. // Start attempts to start a Lapis server in the given repository.
func (site *Site) Start() DeployError { func (site *Site) Start() error {
log.Printf("Starting Lapis server in %s...", site.getName()) log.Printf("Starting Lapis server in %s...", site.getName())
old_cwd, _ := os.Getwd() old_cwd, _ := os.Getwd()
@ -144,18 +143,17 @@ func (site *Site) Start() DeployError {
cmd := exec.Command("lapis", "serve") cmd := exec.Command("lapis", "serve")
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return newDeployError(1, "startSite", return errors.New(fmt.Sprintf("Failed to start Lapis server in '%s': %s", site.getName(), err))
fmt.Sprintf("Failed to start Lapis server in '%s'", site.getName()), "")
} }
go func() { go func() {
cmd.Wait() cmd.Wait()
}() }()
return DeployError{} return nil
} }
// Stop attempts to stop a Lapis server in the given repository. // Stop attempts to stop a Lapis server in the given repository.
func (site *Site) Stop() DeployError { func (site *Site) Stop() error {
log.Printf("Stopping Lapis server %s...", site.getName()) log.Printf("Stopping Lapis server %s...", site.getName())
old_cwd, _ := os.Getwd() old_cwd, _ := os.Getwd()
@ -164,17 +162,16 @@ func (site *Site) Stop() DeployError {
cmd := exec.Command("lapis", "term") cmd := exec.Command("lapis", "term")
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
return newDeployError(1, "stopSite", return errors.New(fmt.Sprintf("Failed to stop Lapis server in '%s': %s", site.getName(), err))
fmt.Sprintf("Failed to stop Lapis server in '%s'", site.getName()), "")
} }
go func() { go func() {
cmd.Wait() cmd.Wait()
}() }()
return DeployError{} return nil
} }
// Restart attempts to restart the Lapis server in the given repository. // Restart attempts to restart the Lapis server in the given repository.
func (site *Site) Restart() DeployError { func (site *Site) Restart() error {
log.Printf("Restarting Lapis server in %s...", site.getName()) log.Printf("Restarting Lapis server in %s...", site.getName())
old_cwd, _ := os.Getwd() old_cwd, _ := os.Getwd()
@ -183,38 +180,33 @@ func (site *Site) Restart() DeployError {
out, err := exec.Command("lapis", "build").CombinedOutput() out, err := exec.Command("lapis", "build").CombinedOutput()
if err != nil { if err != nil {
return newDeployError(1, "restartSite", return errors.New(fmt.Sprintf("Failed to run 'lapis build' in '%s': %s", site.getName(), err))
fmt.Sprintf("Failed to run 'lapis build' in '%s'", site.getName()), string(out))
} }
log.Printf("[lapis build] %s", out) log.Printf("[lapis build] %s", out)
if !strings.Contains(string(out), "HUP") { if !strings.Contains(string(out), "HUP") {
return newDeployError(1, "restartSite", return errors.New(fmt.Sprintf("Failed to restart Lapis server! (Lapis not running?): %s", string(out)))
"Failed to restart Lapis server! (Lapis not running?)", string(out))
} }
return DeployError{} return nil
} }
// Update attempts to pull or clone a repository using the given name and url // Update attempts to pull or clone a repository using the given name and url
func (site *Site) Update() DeployError { func (site *Site) Update() error {
log.Printf("Pulling down repository %s...", site.getName()) log.Printf("Pulling down repository %s...", site.getName())
repo, err := git.Open(configuration.sites_dir + "/" + site.name) repo, err := git.Open(configuration.sites_dir + "/" + site.name)
if err != nil { if err != nil {
return newDeployError(1, "updateSite", return errors.New(fmt.Sprintf("Failed to open git repository '%s': %s", site.name, err))
fmt.Sprintf("Failed to open git repository '%s'", site.name), fmt.Sprint(err))
} }
if err = repo.Pull(); err != nil { if err = repo.Pull(); err != nil {
return newDeployError(1, "updateSite", return errors.New(fmt.Sprintf("Failed to pull down git repository '%s': %s", site.getName(), err))
fmt.Sprintf("Failed to pull down git repository '%s'", site.getName()), fmt.Sprint(err))
} }
return DeployError{} return nil
} }
// CloneSite will clone a site and put it in the configured 'sites_dir' // CloneSite will clone a site and put it in the configured 'sites_dir'
func CloneSite(url string, name string) DeployError { func CloneSite(url string, name string) error {
log.Printf("Cloning repository %s...", name) log.Printf("Cloning repository %s...", name)
if err := git.Clone(url, configuration.sites_dir+"/"+name); err != nil { if err := git.Clone(url, configuration.sites_dir+"/"+name); err != nil {
return newDeployError(1, "cloneSite", return errors.New(fmt.Sprintf("Failed to pull down repository '%s': %s", name, err))
fmt.Sprintf("Failed to pull down repository '%s'", name), fmt.Sprint(err))
} }
return DeployError{} return nil
} }