Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GoDoc Comments added for better code tour #177

Merged
merged 1 commit into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 13 additions & 18 deletions api/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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 := ""
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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)
Expand Down
15 changes: 11 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -21,23 +25,26 @@ 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)
os.Exit(1)
}
}

// 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
}
}
7 changes: 3 additions & 4 deletions cmd/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/*
Copyright © 2022 NAME HERE <EMAIL ADDRESS>

*/
package cmd

Expand All @@ -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
Expand All @@ -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()
}
15 changes: 11 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -94,13 +98,16 @@ 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] != '/' {
*path = filepath.Join(basedir, p)
}
}

// This function simply returns the currently loaded configuration
func GetConfig() Config {
return config
}
24 changes: 16 additions & 8 deletions lib/chatops/chatops.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
11 changes: 10 additions & 1 deletion lib/chatops/rocketchat.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,42 @@ 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,
Color: msg.Color,
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
Expand Down
2 changes: 2 additions & 0 deletions lib/chatops/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions lib/helpers/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions lib/helpers/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions lib/helpers/r10k-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions lib/parsers/azure-devops.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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{}

Expand All @@ -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)
}
Loading
Loading