Skip to content

Commit

Permalink
Merge pull request #7 from kontrolplane/patch
Browse files Browse the repository at this point in the history
chore: change environment variable parsing and logging
  • Loading branch information
levivannoort authored Nov 29, 2024
2 parents deb253c + 6a47912 commit 44ba7f3
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 57 deletions.
10 changes: 1 addition & 9 deletions .github/workflows/continuous-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,7 @@ jobs:
- name: install go
uses: actions/setup-go@v4
with:
go-version: 1.21.3
go-version: 1.23.1

- name: run linters
uses: golangci/golangci-lint-action@v3
with:
version: v1.53

- name: checkout code
uses: actions/checkout@v4

- name: pull request title validator
uses: ./ # kontrolplane/pull-request-title-validator@latest
35 changes: 35 additions & 0 deletions .github/workflows/update-contributors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: update-contributors

on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0'

jobs:
update-contributors:
name: validate-pull-request-title
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4

- name: update-contributors
uses: kontrolplane/[email protected]
with:
owner: kontrolplane
repository: pull-request-title-validator

- name: open-pull-request
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name github-actions
git config user.email [email protected]
git add README.md
git commit -m "chore: update contributors section"
git push -u origin update-contributors
gh pr create \
--title "chore: update contributors" \
--body "Automatically update contributors section." \
--base main \
--head update-contributors
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.21.3 AS build
FROM golang:1.23.1 AS build
WORKDIR /action
COPY . .
RUN CGO_ENABLED=0 go build -o pull-request-title-validator
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,12 @@ jobs:
with:
scopes: "api,lang,parser,package/.+"
```
## Contributors
[//]: kontrolplane/generate-contributors-list
<a href="https://github.com/levivannoort"><img src="https://avatars.githubusercontent.com/u/73097785?v=4" title="levivannoort" width="50" height="50"></a>
<a href="https://github.com/paopa"><img src="https://avatars.githubusercontent.com/u/52045032?v=4" title="paopa" width="50" height="50"></a>
[//]: kontrolplane/generate-contributors-list
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module github.com/kontrolplane/pull-request-title-validator

go 1.21.1
go 1.23.1

require github.com/caarlos0/env v3.5.0+incompatible

require github.com/stretchr/testify v1.10.0 // indirect
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
115 changes: 69 additions & 46 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@ import (
"os"
"regexp"
"strings"

"log/slog"

"github.com/caarlos0/env"
)

var desiredFormat string = "<type>(optional: <scope>): <message>"
var defaultConventionTypes []string = []string{"fix", "feat", "chore", "docs", "build", "ci", "refactor", "perf", "test"}

type config struct {
GithubEventName string `env:"GITHUB_EVENT_NAME"`
GithubEventPath string `env:"GITHUB_EVENT_PATH"`
Types string `env:"INPUT_TYPES"`
Scope string `env:"INPUT_SCOPE"`
}

type PullRequest struct {
Title string `json:"title"`
}
Expand All @@ -22,132 +33,144 @@ type Event struct {
// The pull-request-title-validator function mankes sure that for each pull request created the
// title of the pull request adheres to a desired structure, in this case convention commit style.
func main() {
githubEventName := os.Getenv("GITHUB_EVENT_NAME")
githubEventPath := os.Getenv("GITHUB_EVENT_PATH")
conventionTypes := parseTypes(os.Getenv("INPUT_TYPES"), defaultConventionTypes)
scopes := parseScopes(os.Getenv("INPUT_SCOPES"))

if githubEventName != "pull_request" && githubEventName != "pull_request_target" {
fmt.Printf("Error: the 'pull_request' trigger type should be used, received '%s'\n", githubEventName)
var cfg config
if err := env.Parse(&cfg); err != nil {
fmt.Printf("unable to parse the environment variables: %v", err)
os.Exit(1)
}

logHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: false,
Level: slog.LevelInfo,
})
logger := slog.New(logHandler)

logger.Info("starting pull-request-title-validator", slog.String("event", cfg.GithubEventName))

if cfg.GithubEventName != "pull_request" && cfg.GithubEventName != "pull_request_target" {
logger.Error("invalid event type", slog.String("event", cfg.GithubEventName))
os.Exit(1)
}

title := fetchTitle(githubEventPath)
titleType, titleScope, titleMessage := splitTitle(title)
title := fetchTitle(logger, cfg.GithubEventPath)
titleType, titleScope, titleMessage := splitTitle(logger, title)

if err := checkAgainstConventionTypes(titleType, conventionTypes); err != nil {
fmt.Printf("The type passed '%s' is not present in the types allowed by the convention: %s\n", titleType, conventionTypes)
parsedTypes := parseTypes(logger, cfg.Types, defaultConventionTypes)
parsedScope := parseScopes(logger, cfg.Scope)

if err := checkAgainstConventionTypes(logger, titleType, parsedTypes); err != nil {
logger.Error("error while checking the type against the allowed types",
slog.String("event name", cfg.GithubEventName),
slog.String("event path", cfg.GithubEventPath),
slog.Any("convention types", parsedTypes),
)
os.Exit(1)
}

if err := checkAgainstScopes(titleScope, scopes); err != nil && len(scopes) >= 1 {
fmt.Println(err)
if err := checkAgainstScopes(logger, titleScope, parsedScope); err != nil && len(parsedScope) >= 1 {
logger.Error("error while checking the scope against the allowed scopes", slog.Any("error", err))
os.Exit(1)
}

fmt.Printf("commit title type used: %s\n", titleType)
fmt.Printf("commit title scope used: %s\n", titleScope)
fmt.Printf("commit title message used: %s\n\n", titleMessage)
fmt.Printf("the commit message adheres to the configured standard")
logger.Info("commit title validated successfully",
slog.String("type", titleType),
slog.String("scope", titleScope),
slog.String("message", titleMessage),
)
logger.Info("the commit message adheres to the configured standard")
}

func fetchTitle(githubEventPath string) string {

func fetchTitle(logger *slog.Logger, githubEventPath string) string {
var event Event
var eventData []byte
var err error

if eventData, err = os.ReadFile(githubEventPath); err != nil {
fmt.Printf("Problem reading the event json file: %v\n", err)
os.Exit(1)
logger.Error("Problem reading the event JSON file", slog.String("path", githubEventPath), slog.Any("error", err))
os.Exit(1) // You might want to return an empty string or handle this error upstream instead.
}

if err = json.Unmarshal(eventData, &event); err != nil {
fmt.Printf("Failed to unmarshal JSON: %v", err)
logger.Error("Failed to unmarshal JSON", slog.Any("error", err))
os.Exit(1)
}

return event.PullRequest.Title
}

func splitTitle(title string) (titleType string, titleScope string, titleMessage string) {

// this part of the function extracts the type
func splitTitle(logger *slog.Logger, title string) (titleType string, titleScope string, titleMessage string) {
if index := strings.Index(title, "("); strings.Contains(title, "(") {
titleType = title[:index]
} else if index := strings.Index(title, ":"); strings.Contains(title, ":") {
titleType = title[:index]
} else {
fmt.Println("No type was included in the pull request title.")
fmt.Println(desiredFormat)
logger.Error("No type was included in the pull request title.", slog.String("desired format", desiredFormat))
os.Exit(1)
}

// this part of the function extracts the optional scope
if strings.Contains(title, "(") && strings.Contains(title, ")") {
scope := regexp.MustCompile(`\(([^)]+)\)`)
titleScope = scope.FindStringSubmatch(title)[1]
if matches := scope.FindStringSubmatch(title); len(matches) > 1 {
titleScope = matches[1]
}
}

// this part of the function extracts the message
if strings.Contains(title, ":") {
titleMessage = strings.SplitAfter(title, ":")[1]
titleMessage = strings.TrimSpace(titleMessage)
} else {
fmt.Println("no message was included in the pull request title.")
fmt.Println(desiredFormat)
logger.Error("No message was included in the pull request title.", slog.String("desired format", desiredFormat))
os.Exit(1)
}

return
}

func checkAgainstConventionTypes(titleType string, conventionTypes []string) error {
func checkAgainstConventionTypes(logger *slog.Logger, titleType string, conventionTypes []string) error {
for _, conventionType := range conventionTypes {
if titleType == conventionType {
return nil
}
}

return fmt.Errorf("the type passed '%s' is not present in the types allowed by the convention: %s", titleType, conventionTypes)
logger.Error("Type not allowed by the convention", slog.String("type", titleType), slog.Any("allowedTypes", conventionTypes))
return fmt.Errorf("type '%s' is not allowed", titleType)
}

func checkAgainstScopes(titleScope string, scopes []string) error {
func checkAgainstScopes(logger *slog.Logger, titleScope string, scopes []string) error {
for _, scope := range scopes {
if regexp.MustCompile("(?i)" + scope + "$").MatchString(titleScope) {
return nil
}
}

return fmt.Errorf("the scope '%s' is not allowed. Please choose from the following patterns of scopes: %s", titleScope, scopes)
return fmt.Errorf("scope '%s' is not allowed", titleScope)
}

func parseTypes(input string, fallback []string) []string {
func parseTypes(logger *slog.Logger, input string, fallback []string) []string {
if input == "" {
fmt.Println("no custom list of commit types was passed using fallback.")
logger.Warn("No custom list of commit types passed, using fallback.")
return fallback
}

types := strings.Split(input, ",")
for i := range types {
types[i] = strings.TrimSpace(types[i])
}
if len(types) == 0 {
return fallback
}

return types
}

func parseScopes(input string) []string {
func parseScopes(logger *slog.Logger, input string) []string {
if input == "" {
fmt.Println("no custom list of commit scopes was passed using fallback.")
logger.Warn("No custom list of commit scopes passed, using fallback.")
return []string{}
}

scopes := strings.Split(input, ",")
for i := range scopes {
scopes[i] = strings.TrimSpace(scopes[i])
}
if len(scopes) == 0 {
return []string{}
}

return scopes
}

0 comments on commit 44ba7f3

Please sign in to comment.