diff --git a/backend/cmd/gh-updater/main.go b/backend/cmd/gh-updater/main.go index 9f1397e..ee52b78 100644 --- a/backend/cmd/gh-updater/main.go +++ b/backend/cmd/gh-updater/main.go @@ -39,11 +39,6 @@ func main() { slog.Error("error decoding GitHub App private key from base64", slog.Any("error", err)) os.Exit(1) } - - // FIXME: remove this!!! - slog.Debug("DEBUG: ", slog.Int64("GITHUB_APP_ID", githubAppID)) - slog.Debug("DEBUG: ", slog.String("GITHUB_APP_PRIVATE_KEY_BASE64", privateKeyBase64)) - rsaPrivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKey) if err != nil { slog.Error("error parsing GitHub App RSA private key from PEM", slog.Any("error", err)) diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 9368b82..f3ffa6d 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -2,11 +2,17 @@ package main import ( "context" + "crypto/tls" + "encoding/base64" "fmt" + "github.com/bee-ci/bee-ci-system/internal/common/ghservice" + "github.com/golang-jwt/jwt/v5" + "github.com/redis/go-redis/v9" "log/slog" "net/http" "os" "os/signal" + "strconv" "time" influxdb2 "github.com/influxdata/influxdb-client-go/v2" @@ -24,16 +30,25 @@ var jwtSecret = []byte("your-very-secret-key") func main() { ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt) - slog.SetDefault(setUpLogging()) + slog.Debug("server is starting...") serverURL := mustGetenv("SERVER_URL") port := mustGetenv("PORT") - mainDomain := os.Getenv("MAIN_DOMAIN") frontendURL := mustGetenv("FRONTEND_URL") - slog.Debug("server is starting...") + githubAppID := mustGetenvInt64("GITHUB_APP_ID") + privateKeyBase64 := mustGetenv("GITHUB_APP_PRIVATE_KEY_BASE64") + privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64) + if err != nil { + slog.Error("error decoding GitHub App private key from base64", slog.Any("error", err)) + os.Exit(1) + } + rsaPrivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKey) + if err != nil { + slog.Error("error parsing GitHub App RSA private key from PEM", slog.Any("error", err)) + } githubAppClientID := mustGetenv("GITHUB_APP_CLIENT_ID") githubAppWebhookSecret := mustGetenv("GITHUB_APP_WEBHOOK_SECRET") @@ -53,6 +68,28 @@ func main() { } slog.Info("connected to Postgres database", "host", dbHost, "port", dbPort, "user", dbUser, "name", dbName, "options", dbOpts) + redisAddr := mustGetenv("REDIS_ADDRESS") + redisPassword := mustGetenv("REDIS_PASSWORD") + + var tlsConfig *tls.Config + if mustGetenv("REDIS_USE_TLS") == "true" { + tlsConfig = &tls.Config{} + } + + redisDB := redis.NewClient(&redis.Options{ + Addr: redisAddr, + Password: redisPassword, + TLSConfig: tlsConfig, + DB: 0, // use default DB + }) + + err = redisDB.Ping(ctx).Err() + if err != nil { + slog.Error("error connecting to Redis database", slog.Any("error", err)) + os.Exit(1) + } + slog.Info("connected to Redis database", "address", redisAddr) + influxURL := mustGetenv("INFLUXDB_URL") influxToken := mustGetenv("INFLUXDB_TOKEN") influxBucket := mustGetenv("INFLUXDB_BUCKET") @@ -72,7 +109,9 @@ func main() { repoRepo := data.NewPostgresRepoRepo(db) logsRepo := data.NewInfluxLogsRepo(influxClient, influxOrg, influxBucket) - webhooks, err := webhook.NewHandler(userRepo, repoRepo, buildRepo, mainDomain, frontendURL, githubAppClientID, githubAppClientSecret, githubAppWebhookSecret, jwtSecret) + githubService := ghservice.NewGithubService(githubAppID, rsaPrivateKey, redisDB) + + webhooks, err := webhook.NewHandler(userRepo, repoRepo, buildRepo, githubService, mainDomain, frontendURL, githubAppClientID, githubAppClientSecret, githubAppWebhookSecret, jwtSecret) if err != nil { slog.Error("error creating webhook handler", slog.Any("error", err)) os.Exit(1) @@ -151,3 +190,13 @@ func mustGetenv(varname string) string { } return value } + +func mustGetenvInt64(varname string) int64 { + value := mustGetenv(varname) + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + slog.Error(varname+" env var is not a valid int64", slog.Any("error", err)) + os.Exit(1) + } + return i +} diff --git a/backend/internal/common/ghservice/ghservice.go b/backend/internal/common/ghservice/ghservice.go index 21adf18..1624895 100644 --- a/backend/internal/common/ghservice/ghservice.go +++ b/backend/internal/common/ghservice/ghservice.go @@ -74,9 +74,6 @@ func (g GithubService) getInstallationAccessToken(ctx context.Context, installat return "", fmt.Errorf("generate signed jwt: %w", err) } - // FIXME(bartekpacia): Remove this print!!! - g.logger.Debug("DEBUG: generated a signed JWT string", slog.String("jwt", jwtString)) - appClient := http.Client{Transport: &bearerTransport{Token: jwtString}} gh := github.NewClient(&appClient) res, _, err := gh.Apps.CreateInstallationToken(ctx, installationID, nil) diff --git a/backend/internal/server/webhook/webhook.go b/backend/internal/server/webhook/webhook.go index 461ec06..0425f7e 100644 --- a/backend/internal/server/webhook/webhook.go +++ b/backend/internal/server/webhook/webhook.go @@ -7,12 +7,14 @@ import ( "embed" "encoding/json" "fmt" + "github.com/bee-ci/bee-ci-system/internal/common/ghservice" "html/template" "io" "log" "log/slog" "net/http" "net/url" + "slices" "strconv" "time" @@ -28,14 +30,13 @@ import ( var redirectHTMLPage embed.FS type Handler struct { - httpClient *http.Client - userRepo data.UserRepo - repoRepo data.RepoRepo - buildRepo data.BuildRepo - - // The domain where the auth cookie will be placed. For example: - // - .pacia.tech - // - .karolak.cc + httpClient *http.Client + userRepo data.UserRepo + repoRepo data.RepoRepo + buildRepo data.BuildRepo + githubService *ghservice.GithubService + + // The domain where the auth cookie will be placed, for example ".pacia.tech" or .karolak.cc". // // Must be empty for localhost. mainDomain string @@ -57,6 +58,7 @@ func NewHandler( userRepo data.UserRepo, repoRepo data.RepoRepo, buildRepo data.BuildRepo, + githubService *ghservice.GithubService, mainDomain string, frontendURL string, githubAppClientID string, @@ -74,6 +76,7 @@ func NewHandler( userRepo: userRepo, repoRepo: repoRepo, buildRepo: buildRepo, + githubService: githubService, mainDomain: mainDomain, redirectURL: redirectURL, githubAppClientID: githubAppClientID, @@ -367,7 +370,36 @@ func (h Handler) handleWebhook(w http.ResponseWriter, r *http.Request) { slog.Int64("sender.id", userID), ) - // Create build + repoOwner := *event.Repo.Owner.Login + repoName := *event.Repo.Name + + // Check if the config file exists. If it does, parse it. + ghClient, err := h.githubService.GetClientForInstallation(r.Context(), *installation.ID) + if err != nil { + logger.Error("error getting github client", slog.Any("error", err)) + http.Error(w, "error getting github client", http.StatusInternalServerError) + return + } + + // Validate that the config file exists + _, files, _, err := ghClient.Repositories.GetContents(r.Context(), repoOwner, repoName, "/", nil) + if err != nil { + logger.Error("error getting repo's root directory content", slog.Any("error", err)) + http.Error(w, "error getting repo's directory content", http.StatusInternalServerError) + return + } + hasConfigFile := slices.ContainsFunc(files, func(file *github.RepositoryContent) bool { + return *file.Name == ".bee-ci.json" + }) + + if !hasConfigFile { + logger.Debug(".bee-ci.json config file does not exist, skipping execution") + return + } + + logger.Debug(".bee-ci.json config file exists, proceeding with execution...") + + // Create a new build if *event.Action == "requested" || *event.Action == "rerequested" { headSHA := *event.CheckSuite.HeadSHA message := *event.CheckSuite.HeadCommit.Message diff --git a/docker-compose.yaml b/docker-compose.yaml index 6b90c84..dc04d39 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -40,6 +40,9 @@ services: INFLUXDB_TOKEN: ${INFLUXDB_TOKEN} INFLUXDB_ORG: ${INFLUXDB_ORG} INFLUXDB_BUCKET: ${INFLUXDB_BUCKET} + REDIS_ADDRESS: ${REDIS_ADDRESS} + REDIS_PASSWORD: ${REDIS_PASSWORD} + REDIS_USE_TLS: false gh-updater: build: