diff --git a/api/module.go b/api/module.go index 78d0226..72c6cce 100644 --- a/api/module.go +++ b/api/module.go @@ -13,12 +13,12 @@ import ( "github.com/voxpupuli/webhook-go/lib/queue" ) -// Module Controller +// ModuleController handles module deployment. type ModuleController struct{} -// DeployModule takes int the current Gin context and parses the request -// data into a variable then executes the r10k module deploy either through -// a direct local execution of the r10k deploy module command +// DeployModule handles the deployment of a Puppet module via r10k. +// It parses the incoming webhook data, constructs the r10k command, +// and either queues the deployment or executes it immediately. func (m ModuleController) DeployModule(c *gin.Context) { var data parsers.Data var h helpers.Helper @@ -35,6 +35,7 @@ func (m ModuleController) DeployModule(c *gin.Context) { // Parse the data from the request and error if parsing fails err := data.ParseData(c) if err != nil { + // Respond with error if parsing fails, notify ChatOps if enabled. c.JSON(http.StatusInternalServerError, gin.H{"message": "Error Parsing Webhook", "error": err}) c.Abort() if conf.ChatOps.Enabled { @@ -43,6 +44,7 @@ func (m ModuleController) DeployModule(c *gin.Context) { return } + // Handle optional branch parameter, fallback to default branch if not provided. useBranch := c.Query("branch_only") if useBranch != "" { branch := "" @@ -55,12 +57,13 @@ func (m ModuleController) DeployModule(c *gin.Context) { cmd = append(cmd, branch) } + // Validate module name with optional override from query parameters. module := data.ModuleName overrideModule := c.Query("module_name") - // Restrictions to Puppet module names are: 1) begin with lowercase letter, 2) contain lowercase, digits or underscores if overrideModule != "" { match, _ := regexp.MatchString("^[a-z][a-z0-9_]*$", overrideModule) if !match { + // Invalid module name, respond with error and notify ChatOps. c.JSON(http.StatusInternalServerError, gin.H{"message": "Invalid module name"}) c.Abort() err = fmt.Errorf("invalid module name: module name does not match the expected pattern; got: %s, pattern: ^[a-z][a-z0-9_]*$", overrideModule) @@ -72,26 +75,16 @@ func (m ModuleController) DeployModule(c *gin.Context) { module = overrideModule } - // Append module name and r10k configuration to the cmd string slice + // Append module name and r10k configuration to the command string slice. cmd = append(cmd, module) cmd = append(cmd, fmt.Sprintf("--config=%s", h.GetR10kConfig())) - // Set additional optional r10k flags if they are set + // Set additional optional r10k flags if they are enabled. if conf.R10k.Verbose { cmd = append(cmd, "-v") } - // Pass the command to the execute function and act on the result and any error - // that is returned - // - // On an error this will: - // * Log the error and command - // * Respond with an HTTP 500 error and return the command result in JSON format - // * Abort the request - // * Notify ChatOps service if enabled - // - // On success this will: - // * Respond with an HTTP 202 and the result in JSON format + // Execute or queue the command based on server configuration. var res interface{} if conf.Server.Queue.Enabled { res, err = queue.AddToQueue("module", data.ModuleName, cmd) @@ -103,6 +96,7 @@ func (m ModuleController) DeployModule(c *gin.Context) { } } + // Handle error response, notify ChatOps if enabled. if err != nil { c.JSON(http.StatusInternalServerError, res) c.Abort() @@ -112,6 +106,7 @@ func (m ModuleController) DeployModule(c *gin.Context) { return } + // On success, respond with HTTP 202 and notify ChatOps if enabled. c.JSON(http.StatusAccepted, res) if conf.ChatOps.Enabled { conn.PostMessage(http.StatusAccepted, data.ModuleName, res) diff --git a/cmd/root.go b/cmd/root.go index 4b52c0d..025f9de 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,10 +8,14 @@ import ( "github.com/voxpupuli/webhook-go/config" ) +// cfgFile is the path to the configuration file, given by the user as a flag var cfgFile string +// version is the current version of the application var version = "0.0.0" +// rootCmd is the root command for the application +// It is used to set up the application, and is the entry point for the Cobra CLI var rootCmd = &cobra.Command{ Use: "webhook-go", Version: version, @@ -21,6 +25,7 @@ var rootCmd = &cobra.Command{ API endpoint.`, } +// Execute is the main entry point for the application, called from main.go, and is used to execute the root command func Execute() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) @@ -28,16 +33,18 @@ func Execute() { } } +// init is called when the package loads, and is used to set up the root command, and the configuration file flag func init() { - cobra.OnInitialize(initConfig) + cobra.OnInitialize(initConfig) // tells Cobra to call the initConfig function before executing any command. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./webhook.yml)") + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./webhook.yml)") // adds a flag to the root command that allows the user to specify a configuration file } +// initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { - config.Init(&cfgFile) + config.Init(&cfgFile) // Expecting a path to a configuration file } else { - config.Init(nil) + config.Init(nil) // No path given, use defaults } } diff --git a/cmd/server.go b/cmd/server.go index ca73c43..25b7e90 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -1,6 +1,5 @@ /* Copyright © 2022 NAME HERE - */ package cmd @@ -9,17 +8,16 @@ import ( "github.com/voxpupuli/webhook-go/server" ) -// serverCmd represents the server command +// serverCmd starts the Webhook-go server, allowing it to process webhook requests. var serverCmd = &cobra.Command{ Use: "server", Short: "Start the Webhook-go server", - Long: ``, Run: startServer, } +// init adds serverCmd to the root command. func init() { rootCmd.AddCommand(serverCmd) - // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags which will work for this command @@ -31,6 +29,7 @@ func init() { // serverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } +// startServer initializes and starts the server. func startServer(cmd *cobra.Command, args []string) { server.Init() } diff --git a/config/config.go b/config/config.go index 640575e..5271226 100644 --- a/config/config.go +++ b/config/config.go @@ -9,6 +9,7 @@ import ( var config Config +// Config is a struct that holds the configuration for the application type Config struct { Server struct { Protected bool `mapstructure:"protected"` @@ -47,10 +48,12 @@ type Config struct { } `mapstructure:"r10k"` } +// Init reads in the configuration file and populates the Config struct func Init(path *string) { var err error - v := viper.New() + v := viper.New() // creates a new Viper instance + // If a path is given, use it, otherwise, use the default if path != nil { v.SetConfigFile(*path) } else { @@ -61,19 +64,20 @@ func Init(path *string) { v.AddConfigPath("../config/") v.AddConfigPath("config/") } - err = v.ReadInConfig() + err = v.ReadInConfig() // reads the configuration file if err != nil { log.Fatalf("error on parsing config file: %v", err) } - v = setDefaults(v) + v = setDefaults(v) // sets the default values for the configuration - err = v.Unmarshal(&config) + err = v.Unmarshal(&config) // converts the configuration into the Config struct if err != nil { log.Fatalf("Unable to read config file: %v", err) } } +// Provides defualt values in case of config file doesn't define some fields func setDefaults(v *viper.Viper) *viper.Viper { v.SetDefault("server.port", 4000) v.SetDefault("server.protected", false) @@ -94,6 +98,8 @@ func setDefaults(v *viper.Viper) *viper.Viper { return v } +// This utility function adjusts relative paths. +// If a path doesn't start with / (indicating it’s not an absolute path), it prepends the basedir to make it a proper path. func relativePath(basedir string, path *string) { p := *path if len(p) > 0 && p[0] != '/' { @@ -101,6 +107,7 @@ func relativePath(basedir string, path *string) { } } +// This function simply returns the currently loaded configuration func GetConfig() Config { return config } diff --git a/lib/chatops/chatops.go b/lib/chatops/chatops.go index bb7d51c..d999323 100644 --- a/lib/chatops/chatops.go +++ b/lib/chatops/chatops.go @@ -5,28 +5,34 @@ import ( "strconv" ) +// ChatOps defines the configuration for interacting with various chat services. type ChatOps struct { - Service string - Channel string - User string - AuthToken string - ServerURI *string - TestMode bool - TestURL *string + Service string // Chat service (e.g., "slack", "rocketchat", "teams"). + Channel string // Target channel or room. + User string // User initiating the action. + AuthToken string // Authentication token for the chat service. + ServerURI *string // Optional server URI for self-hosted services. + TestMode bool // Indicates if the operation is in test mode. + TestURL *string // URL for testing purposes, if applicable. } +// ChatOpsResponse captures the response details from a chat service after a message is posted. type ChatOpsResponse struct { Timestamp string Channel string } +// ChatAttachment represents the structure of a message attachment in chat services like Slack. type ChatAttachment struct { AuthorName string Title string Text string - Color string + Color string // Color to indicate status (e.g., success, failure). } +// PostMessage sends a formatted message to the configured chat service based on the HTTP status code +// and target environment. It returns a ChatOpsResponse or an error if posting fails. +// Supports Slack, Rocket.Chat, and Microsoft Teams. func (c *ChatOps) PostMessage(code int, target string, output interface{}) (*ChatOpsResponse, error) { var resp ChatOpsResponse @@ -56,6 +62,8 @@ func (c *ChatOps) PostMessage(code int, target string, output interface{}) (*Cha return &resp, nil } +// formatMessage generates a ChatAttachment based on the HTTP status code and target environment. +// The message is used to notify the result of a Puppet environment deployment. func (c *ChatOps) formatMessage(code int, target string) ChatAttachment { var message ChatAttachment diff --git a/lib/chatops/rocketchat.go b/lib/chatops/rocketchat.go index 980153f..a990727 100644 --- a/lib/chatops/rocketchat.go +++ b/lib/chatops/rocketchat.go @@ -8,21 +8,28 @@ import ( "github.com/pandatix/gocket-chat/api/chat" ) +// rocketChat posts a message to a Rocket.Chat channel using the provided HTTP status code and target environment. +// It returns a PostMessageResponse or an error if the operation fails. +// ServerURI must be provided as part of the ChatOps configuration. func (c *ChatOps) rocketChat(code int, target string) (*chat.PostMessageResponse, error) { + // Ensure ServerURI is set before proceeding. if c.ServerURI == nil { return nil, fmt.Errorf("A ServerURI must be specified to use RocketChat") } client := &http.Client{} + // Initialize RocketChat client with the provided ServerURI, AuthToken, and User credentials. rc, err := gochat.NewRocketClient(client, *c.ServerURI, c.AuthToken, c.User) if err != nil { return nil, err } - var attachments []chat.Attachement + // Format the message based on the HTTP status code and target environment. msg := c.formatMessage(code, target) + // Prepare attachments for the message. + var attachments []chat.Attachement attachments = append(attachments, chat.Attachement{ AuthorName: msg.AuthorName, Title: msg.Title, @@ -30,11 +37,13 @@ func (c *ChatOps) rocketChat(code int, target string) (*chat.PostMessageResponse Text: msg.Text, }) + // Set the parameters for posting the message to the specified channel. pmp := chat.PostMessageParams{ Channel: c.Channel, Attachements: &attachments, } + // Post the message to RocketChat. res, err := chat.PostMessage(rc, pmp) if err != nil { return nil, err diff --git a/lib/chatops/slack.go b/lib/chatops/slack.go index bb651f9..f259b03 100644 --- a/lib/chatops/slack.go +++ b/lib/chatops/slack.go @@ -4,6 +4,8 @@ import ( "github.com/slack-go/slack" ) +// slack posts a message to a Slack channel based on the HTTP status code and target. +// Returns the channel, timestamp, or an error if the operation fails. func (c *ChatOps) slack(code int, target string) (*string, *string, error) { var sapi *slack.Client if c.TestMode { diff --git a/lib/helpers/branch.go b/lib/helpers/branch.go index e51462c..ee24dd3 100644 --- a/lib/helpers/branch.go +++ b/lib/helpers/branch.go @@ -4,6 +4,7 @@ import ( "github.com/voxpupuli/webhook-go/lib/parsers" ) +// GetBranch returns the branch name from the parsed data. If the branch was deleted, it returns the defaultBranch. func (h *Helper) GetBranch(data parsers.Data, defaultBranch string) string { if data.Deleted { return defaultBranch diff --git a/lib/helpers/environment.go b/lib/helpers/environment.go index 05b4169..4f5d720 100644 --- a/lib/helpers/environment.go +++ b/lib/helpers/environment.go @@ -4,6 +4,8 @@ import ( "fmt" ) +// GetEnvironment constructs and returns an environment name by combining the prefix and branch. +// If either is empty, it normalizes the branch name. Allows optional uppercase transformation. func (h *Helper) GetEnvironment(branch, prefix string, allowUppercase bool) string { if prefix == "" || branch == "" { return h.Normalize(allowUppercase, branch) diff --git a/lib/helpers/r10k-config.go b/lib/helpers/r10k-config.go index fa67494..29a1d71 100644 --- a/lib/helpers/r10k-config.go +++ b/lib/helpers/r10k-config.go @@ -4,6 +4,8 @@ import "github.com/voxpupuli/webhook-go/config" const ConfigFile = "/etc/puppetlabs/r10k/r10k.yaml" +// GetR10kConfig retrieves the R10k configuration file path. +// If no custom path is set in the configuration, it returns the default path. func (h *Helper) GetR10kConfig() string { conf := config.GetConfig().R10k confPath := conf.ConfigPath diff --git a/lib/parsers/azure-devops.go b/lib/parsers/azure-devops.go index e18f127..8bfc6f7 100644 --- a/lib/parsers/azure-devops.go +++ b/lib/parsers/azure-devops.go @@ -10,6 +10,8 @@ import ( "github.com/mcdafydd/go-azuredevops/azuredevops" ) +// parseAzureDevops processes an Azure DevOps webhook, extracting event details such as branch, module name, and repository info. +// It handles the PushEvent type and marks the data as completed and successful upon successful parsing. func (d *Data) parseAzureDevops(c *gin.Context) error { payload, err := ioutil.ReadAll(c.Request.Body) if err != nil { @@ -42,6 +44,7 @@ func (d *Data) parseAzureDevops(c *gin.Context) error { return nil } +// parseRawResource unmarshals the raw payload of a Git push event from Azure DevOps. func (d *Data) parseRawResource(e *azuredevops.Event) (payload *azuredevops.GitPush, err error) { payload = &azuredevops.GitPush{} @@ -54,10 +57,12 @@ func (d *Data) parseRawResource(e *azuredevops.Event) (payload *azuredevops.GitP return payload, nil } +// azureDevopsDeleted checks if the push event represents a branch deletion in Azure DevOps. func (d *Data) azureDevopsDeleted(e *azuredevops.GitPush) bool { return *e.RefUpdates[0].NewObjectID == "0000000000000000000000000000000000000000" } +// parseBranch extracts the branch name from the push event, removing the ref prefix. func (d *Data) parseBranch(e *azuredevops.GitPush) string { return strings.TrimPrefix(*e.RefUpdates[0].Name, prefix) } diff --git a/lib/parsers/bitbucket-server.go b/lib/parsers/bitbucket-server.go index 98c3b92..46de182 100644 --- a/lib/parsers/bitbucket-server.go +++ b/lib/parsers/bitbucket-server.go @@ -8,6 +8,8 @@ import ( bitbucketserver "github.com/go-playground/webhooks/v6/bitbucket-server" ) +// parseBitbucketServer processes a Bitbucket Server webhook, extracting details such as branch, repository, and project info. +// Handles RepositoryReferenceChangedEvent to set branch-related fields. func (d *Data) parseBitbucketServer(c *gin.Context) error { bh, err := bitbucketserver.New() if err != nil { @@ -35,10 +37,12 @@ func (d *Data) parseBitbucketServer(c *gin.Context) error { return nil } +// bitbucketServerDeleted checks if the branch was deleted in the reference change event. func (d *Data) bitbucketServerDeleted(c bitbucketserver.RepositoryReferenceChangedPayload) bool { return c.Changes[0].Type == "DELETE" } +// bsParseBranch extracts the branch name from the reference change event, removing the ref prefix. func (d *Data) bsParseBranch(e bitbucketserver.RepositoryReferenceChangedPayload) string { return strings.TrimPrefix(e.Changes[0].ReferenceID, prefix) } diff --git a/lib/parsers/bitbucket.go b/lib/parsers/bitbucket.go index 1df0817..b3dd422 100644 --- a/lib/parsers/bitbucket.go +++ b/lib/parsers/bitbucket.go @@ -7,6 +7,8 @@ import ( "github.com/go-playground/webhooks/v6/bitbucket" ) +// parseBitbucket processes a Bitbucket webhook, extracting branch, repository, and user information. +// Handles RepoPushEvent to set relevant fields based on the payload. func (d *Data) parseBitbucket(c *gin.Context) error { bh, err := bitbucket.New() if err != nil { @@ -41,6 +43,7 @@ func (d *Data) parseBitbucket(c *gin.Context) error { return nil } +// bitbucketDeleted checks if the repository changes indicate a deletion. func (d *Data) bitbucketDeleted(b bitbucket.RepoPushPayload) bool { return b.Push.Changes[0].Closed } diff --git a/lib/parsers/gitea.go b/lib/parsers/gitea.go index 6617785..afbaeb5 100644 --- a/lib/parsers/gitea.go +++ b/lib/parsers/gitea.go @@ -9,35 +9,37 @@ import ( "github.com/gin-gonic/gin" ) +// giteaWebhookType retrieves the event type from the Gitea webhook request. func giteaWebhookType(r *http.Request) string { return r.Header.Get("X-Gitea-Event") } +// parseGitea processes a Gitea webhook, extracting branch, repository, and user information. +// Handles "push" events to set relevant fields based on the payload. func (d *Data) parseGitea(c *gin.Context) error { payload, err := ioutil.ReadAll(c.Request.Body) if err != nil { return err } defer c.Request.Body.Close() + eventType := giteaWebhookType(c.Request) switch eventType { - // switch e := event.(type) { case "push": e, err := api.ParsePushHook(payload) if err != nil { return api.ErrInvalidReceiveHook } d.Branch = e.Branch() - // Deletion in Gitea is a different event - d.Deleted = false + d.Deleted = false // Deletion in Gitea is a different event d.ModuleName = e.Repo.Name d.RepoName = e.Repo.FullName d.RepoUser = e.Repo.Owner.UserName d.Completed = true d.Succeed = true default: - return fmt.Errorf("unknown event type %s", giteaWebhookType(c.Request)) + return fmt.Errorf("unknown event type %s", eventType) } return nil } diff --git a/lib/parsers/github.go b/lib/parsers/github.go index 0f33ac5..7d1ea81 100644 --- a/lib/parsers/github.go +++ b/lib/parsers/github.go @@ -9,6 +9,8 @@ import ( "github.com/google/go-github/v39/github" ) +// parseGithub processes a GitHub webhook, extracting branch, repository, and user information. +// Handles both "push" and "workflow_run" events to set relevant fields based on the payload. func (d *Data) parseGithub(c *gin.Context) error { payload, err := ioutil.ReadAll(c.Request.Body) if err != nil { @@ -44,14 +46,15 @@ func (d *Data) parseGithub(c *gin.Context) error { return nil } +// isSucceed checks if the conclusion of a workflow run is "success". func (d Data) isSucceed(conclusion *string) bool { if conclusion == nil { return false } - return *conclusion == "success" } +// githubDeleted checks if the specified SHA represents a deleted commit. func (d *Data) githubDeleted(after string) bool { return after == "0000000000000000000000000000000000000000" } diff --git a/lib/queue/queue.go b/lib/queue/queue.go index 84b7f7f..1263c6a 100644 --- a/lib/queue/queue.go +++ b/lib/queue/queue.go @@ -13,30 +13,37 @@ import ( "github.com/voxpupuli/webhook-go/lib/helpers" ) +// Queue holds the items to be processed and manages job concurrency. + type Queue struct { - Items []*QueueItem - wg sync.WaitGroup - jobChan chan *QueueItem + Items []*QueueItem // List of items in the queue + wg sync.WaitGroup // WaitGroup to synchronize goroutines + jobChan chan *QueueItem // Channel for jobs to be processed } +// QueueItem represents a task to be executed, with metadata like ID, command, and state. + type QueueItem struct { - Id uuid.UUID - Name string - CommandType string - AddedAt time.Time - StartedAt time.Time - FinishedAt time.Time - Command []string - Response interface{} - State string + Id uuid.UUID // Unique identifier for the job + Name string // Descriptive name of the job + CommandType string // Type of command being executed + AddedAt time.Time // Timestamp when the job was added to the queue + StartedAt time.Time // Timestamp when the job execution started + FinishedAt time.Time // Timestamp when the job execution finished + Command []string // Command to be executed + Response interface{} // Response from the executed command + State string // Current state of the job (e.g., added, success, failed) } var queue = &Queue{} +// GetQueueItems returns all items currently in the queue. func GetQueueItems() []*QueueItem { return queue.Items } +// AddToQueue adds a new command to the queue and triggers job processing. +// Returns the newly created QueueItem or an error if the job cannot be queued. func AddToQueue(commandType string, name string, command []string) (*QueueItem, error) { id, err := uuid.NewUUID() if err != nil { @@ -64,6 +71,7 @@ func AddToQueue(commandType string, name string, command []string) (*QueueItem, return &queueItem, nil } +// trimItems ensures the queue doesn't exceed the configured maximum history size. func trimItems() { conf := config.GetConfig() @@ -76,6 +84,7 @@ func trimItems() { } } +// Work initializes the job queue and starts the worker process. func Work() error { conf := config.GetConfig() log.Printf("start queue with %d jobs", conf.Server.Queue.MaxConcurrentJobs) @@ -88,10 +97,13 @@ func Work() error { return nil } +// Dispose shuts down the job channel and signals the worker to stop. func Dispose() { close(queue.jobChan) } +// queueJob attempts to add a job to the queue. +// Returns true if the job was successfully queued, false otherwise. func queueJob(command *QueueItem) bool { select { case queue.jobChan <- command: @@ -101,6 +113,7 @@ func queueJob(command *QueueItem) bool { } } +// worker processes jobs from the queue and executes the associated commands. func worker() { defer queue.wg.Done() diff --git a/main.go b/main.go index b3c60fe..0058e0f 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( // server.Init() // } +// Main function that starts the application func main() { cmd.Execute() } diff --git a/server/router.go b/server/router.go index a437951..f442a87 100644 --- a/server/router.go +++ b/server/router.go @@ -7,43 +7,44 @@ import ( "github.com/voxpupuli/webhook-go/lib/users" ) -// The NewRouter function sets up the main web routes -// for the Gin API server. +// NewRouter sets up the main routes for the Gin API server. +// It includes logging, recovery middleware, health checks, and API routes for version 1. +// If server protection is enabled, it adds BasicAuth middleware for the API. func NewRouter() *gin.Engine { - router := gin.New() - router.Use(gin.Logger()) - router.Use(gin.Recovery()) + router := gin.New() // Create a new Gin router. + router.Use(gin.Logger()) // Add logging middleware. + router.Use(gin.Recovery()) // Add recovery middleware to handle panics. var apiHandlerFuncs []gin.HandlerFunc if config.GetConfig().Server.Protected { + // If the server is protected, set up BasicAuth using the configured username and password. user := users.Users{ User: config.GetConfig().Server.User, Password: config.GetConfig().Server.Password, } - apiHandlerFuncs = append(apiHandlerFuncs, gin.BasicAuth(gin.Accounts{user.User: user.Password})) } health := new(wapi.HealthController) + router.GET("/health", health.Status) // Route for health status check. - router.GET("/health", health.Status) - + // Group API routes under /api with optional BasicAuth. api := router.Group("api", apiHandlerFuncs...) { v1 := api.Group("v1") { - r10k := v1.Group("r10k") + r10k := v1.Group("r10k") // Group r10k-related routes. { module := new(wapi.ModuleController) - r10k.POST("/module", module.DeployModule) + r10k.POST("/module", module.DeployModule) // Route for deploying a module. environment := new(wapi.EnvironmentController) - r10k.POST("/environment", environment.DeployEnvironment) + r10k.POST("/environment", environment.DeployEnvironment) // Route for deploying an environment. } - queue := v1.Group("queue") + queue := v1.Group("queue") // Group queue-related routes. { q := new(wapi.QueueController) - queue.GET("", q.QueueStatus) + queue.GET("", q.QueueStatus) // Route to check the queue status. } } } diff --git a/server/server.go b/server/server.go index b536178..ad7d31d 100644 --- a/server/server.go +++ b/server/server.go @@ -7,18 +7,22 @@ import ( "github.com/voxpupuli/webhook-go/lib/queue" ) -// The Init function starts the Server on a specific port +// Init initializes and starts the server on the configured port. +// If queue functionality is enabled in the config, it starts the job queue processing. +// The server can run with or without TLS, depending on the configuration. func Init() { config := config.GetConfig().Server if config.Queue.Enabled { - queue.Work() + queue.Work() // Start the job queue if enabled. } - r := NewRouter() + r := NewRouter() // Initialize the router. if config.TLS.Enabled { + // Start the server with TLS (HTTPS) using the provided certificate and key. r.RunTLS(":"+fmt.Sprint(config.Port), config.TLS.Certificate, config.TLS.Key) } else { + // Start the server without TLS (HTTP). r.Run(":" + fmt.Sprint(config.Port)) } } diff --git a/server/server_test.go b/server/server_test.go index cc630ec..e768bd5 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -12,43 +12,49 @@ import ( "github.com/voxpupuli/webhook-go/lib/queue" ) +// TestPingRoute ensures the /health endpoint returns HTTP 200 with "running" message. func TestPingRoute(t *testing.T) { router := NewRouter() w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/health", nil) + router.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) - assert.Equal(t, "{\"message\":\"running\"}", w.Body.String()) + assert.Equal(t, 200, w.Code) // Check response status code + assert.Equal(t, "{\"message\":\"running\"}", w.Body.String()) // Check response body } +// TestQueue verifies if a payload is correctly processed and added to the queue. func TestQueue(t *testing.T) { + // Initialize the config with the webhook queue config file mCfg := "../lib/helpers/yaml/webhook.queue.yaml" config.Init(&mCfg) + // Start the queue worker queue.Work() router := NewRouter() + // Open the test payload file payloadFile, err := os.Open("../lib/parsers/json/github/push.json") if err != nil { - t.Fatal(err) + t.Fatal(err) // Fail if unable to open the file } w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/api/v1/r10k/environment", payloadFile) - req.Header.Add("X-GitHub-Event", "push") + req.Header.Add("X-GitHub-Event", "push") // Set GitHub event header router.ServeHTTP(w, req) var queueItem queue.QueueItem err = json.Unmarshal(w.Body.Bytes(), &queueItem) if err != nil { - t.Fatal(err) + t.Fatal(err) // Fail if JSON unmarshaling fails } - assert.Equal(t, 202, w.Code) - assert.Equal(t, "simple-tag", queueItem.Name) - assert.Equal(t, "added", queueItem.State) + assert.Equal(t, 202, w.Code) // Ensure 202 Accepted + assert.Equal(t, "simple-tag", queueItem.Name) // Ensure correct queue item name + assert.Equal(t, "added", queueItem.State) // Ensure correct queue item state }