Compare commits

..

13 Commits

Author SHA1 Message Date
26ce3260b9 fix: load yaml config file 2024-05-02 08:46:36 +02:00
8dc7bbc6d4 fix: remove app yml 2024-04-29 22:18:22 +02:00
091ad1fec0 feat: update README 2024-04-29 21:52:15 +02:00
ad77710ad1 fix: Dockerfile 2024-04-29 21:50:35 +02:00
5860f81aa2 refactor: change configruation to yml 2024-04-29 21:50:16 +02:00
727b083e52 refactor: project structure 2024-04-29 21:29:05 +02:00
2abed91918 feat: update go version 2024-04-29 21:20:06 +02:00
fe002edab1 fix: readme lint 2024-04-29 21:19:03 +02:00
422fedf9cd fix: test 2023-03-09 13:28:27 +01:00
1b83793f38 feat: get envfile from path when is not prod 2023-03-03 22:47:44 +01:00
f56e85cc64 feat: use env path as parameter 2023-03-03 22:46:49 +01:00
4877f72f04 feat: config small fix 2023-02-26 22:10:07 +01:00
60b40c84f3 feat: upgrade makefile 2023-02-26 22:09:51 +01:00
18 changed files with 139 additions and 103 deletions

View File

@@ -1,4 +0,0 @@
SCRIPT_BINARY_PATH=
WEBHOOK_SCRIPT_PATH=
FILE_TO_WATCH_PATH=
TEST_FILE_TO_WATCH_PATH=

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
coverage/* coverage/*
test_monitor.txt test_monitor.txt
bin bin
configs/*

28
Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM golang:1.22 as builder
WORKDIR /app
# Copy the local package files to the container's workspace.
COPY go.mod go.sum ./
RUN go mod download
# Copy the project code into the container
COPY . .
# Build the Go app
RUN CGO_ENABLED=0 GOOS=linux go build -v -o git-webhook-ci
# Use a small Alpine Linux image
FROM alpine:latest
# Set the working directory
WORKDIR /root/
# Copy the binary from the builder stage
COPY --from=builder /app/git-webhook-ci .
# Document that the service listens on port 8080.
EXPOSE 8080
# Run the binary.
CMD ["./git-webhook-ci"]

View File

@@ -13,8 +13,7 @@ test-coverage:
mkdir ${COVERAGE_DIR} mkdir ${COVERAGE_DIR}
go test -v -coverprofile ${COVERAGE_DIR}/cover.out ./... go test -v -coverprofile ${COVERAGE_DIR}/cover.out ./...
go tool cover -html ${COVERAGE_DIR}/cover.out -o ${COVERAGE_DIR}/cover.html go tool cover -html ${COVERAGE_DIR}/cover.out -o ${COVERAGE_DIR}/cover.html
cd ${COVERAGE_DIR}
open cover.html
build: build:
rm -rf ${BINARY_DIR}
mkdir ${BINARY_DIR} mkdir ${BINARY_DIR}
go build -o ${BINARY_DIR}/${BINARY_NAME} env GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -o ./${BINARY_DIR}/${BINARY_NAME} main.go

View File

@@ -1,46 +1,59 @@
# git-webhook-ci # git-webhook-ci
Tool to automatize your deploy based on file write changes Tool to automatize your deploy based on file write changes
## Description ## Description
After some change is made in our listener file the script placed on path set on `env` variable called **WEBHOOK_SCRIPT_PATH** will be executed.
After some change is made in our listener file the script placed on path set on `.yaml` configuration falled placed on `./configs/app.yaml` variable called **webhook_script_path** will be executed.
## Context ## Context
As a security risk that could be allow a webhook listener on a VPS for your git repository, I've decided As a security risk that could be allow a webhook listener on a VPS for your git repository, I've decided
to create this package which is a listener to file changes on write. This will trigger a bash script which to create this package which is a listener to file changes on write. This will trigger a bash script which
you shold place on your server to run desired tasks. In my case I've done this to run a bash deploy command like you shold place on your server to run desired tasks. In my case I've done this to run a bash deploy command like
- git pull
- build - git pull
- move build to desired dir - build
- restart services like nginx or whatever - move build to desired dir
- restart services like nginx or whatever
## Installation ## Installation
### Requirements ### Requirements
- [Go version > 1.19](https://go.dev/dl/)
- [Go version > 1.22](https://go.dev/dl/)
- [GNU Make 4.3](https://www.gnu.org/software/make/) - [GNU Make 4.3](https://www.gnu.org/software/make/)
- [goreportcard-cli](https://github.com/gojp/goreportcard) - [goreportcard-cli](https://github.com/gojp/goreportcard)
- [golangci-lint](https://golangci-lint.run/) - [golangci-lint](https://golangci-lint.run/)
### Environment Vars ### Environment Vars
As shown in .env.example you have to configure this variables in order to make your binary works right:
As shown in app.example.yml you have to configure this variables in order to make your binary works right:
```.env ```.env
SCRIPT_BINARY_PATH=/bin/bash script_binary_path: "/bin/bash"
WEBHOOK_SCRIPT_PATH=/path/to/mybashscript.sh webhook_script_path: "./test-script.sh"
FILE_TO_WATCH_PATH=/path/to/myfile.txt file_to_watch_path: "./test_monitor.txt"
TEST_FILE_TO_WATCH_PATH=./test_monitor.txt
``` ```
**TEST_FILE_TO_WATCH_PATH** is needed just to run tests
## How to use ## How to use
### Build ### Build
To build you can only just `make build` command To build you can only just `make build` command
```bash ```bash
make build make build
``` ```
## Tests ## Tests
```bash ```bash
make test-coverage make test-coverage
``` ```
output: output:
```terminal ```terminal
coverage: 80.0% of statements coverage: 80.0% of statements
ok gitea.urkob.com/urko/git-webhook-ci/internal/watcher 2.024s coverage: 80.0% of statements ok gitea.urkob.com/urko/git-webhook-ci/internal/watcher 2.024s coverage: 80.0% of statements
@@ -49,6 +62,7 @@ ok gitea.urkob.com/urko/git-webhook-ci/pkg/watcher 0.017s coverage: 100.0%
``` ```
## goreportcard ## goreportcard
```bash ```bash
➜ git-webhook-ci git:(main) ✗ make goreportcard ➜ git-webhook-ci git:(main) ✗ make goreportcard
oreportcard-cli -v oreportcard-cli -v

3
app.example.yml Normal file
View File

@@ -0,0 +1,3 @@
script_binary_path: "/bin/bash"
webhook_script_path: "./test-script.sh"
file_to_watch_path: "./test_monitor.txt"

View File

@@ -1,45 +0,0 @@
package cfg
import (
"log"
"os/exec"
"strings"
"github.com/joho/godotenv"
"github.com/kelseyhightower/envconfig"
)
type Config struct {
ScriptBinaryPath string `required:"true" split_words:"true"`
WebhookScriptPath string `required:"true" split_words:"true"`
FileToWatchPath string `required:"true" split_words:"true"`
TestFileToWatchPath string `required:"true" split_words:"true"`
}
func rootDir() string {
cmdOut, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
if err != nil {
log.Fatalf("exec.Command: %s", err)
return ""
}
rootDir := strings.TrimSpace(string(cmdOut))
return rootDir
}
func NewConfig(isProd bool) *Config {
if !isProd {
err := godotenv.Load(rootDir() + "/.env")
if err != nil {
log.Fatalf("environment variable ENV is empty and an error occurred while loading the .env file\n")
}
}
cfg := &Config{}
err := envconfig.Process("", cfg)
if err != nil {
log.Fatalf("envconfig.Process: %s\n", err)
}
return cfg
}

9
go.mod
View File

@@ -1,17 +1,16 @@
module gitea.urkob.com/urko/git-webhook-ci module gitea.urkob.com/urko/git-webhook-ci
go 1.19 go 1.22
require ( require (
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.7.0
github.com/joho/godotenv v1.5.1
github.com/kelseyhightower/envconfig v1.4.0
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.2
gopkg.in/yaml.v2 v2.4.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect golang.org/x/sys v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

14
go.sum
View File

@@ -1,12 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -16,10 +12,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

3
internal/watcher/testdata/config.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
script_binary_path: "/bin/bash"
webhook_script_path: "testdata/test-script.sh"
file_to_watch_path: "testdata/test_monitor.txt"

View File

@@ -4,13 +4,13 @@ import (
"errors" "errors"
"log" "log"
pkgwatcher "gitea.urkob.com/urko/git-webhook-ci/pkg/watcher" "gitea.urkob.com/urko/git-webhook-ci/pkg"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
) )
type watcher struct { type watcher struct {
fswatcher *fsnotify.Watcher fswatcher *fsnotify.Watcher
deploy pkgwatcher.DeployFunc deploy pkg.DeployFunc
} }
type notifier struct{} type notifier struct{}
@@ -28,7 +28,7 @@ var (
errErrorsClosedChan = errors.New("errors is closed") errErrorsClosedChan = errors.New("errors is closed")
) )
func NewWatcher(notifier pkgwatcher.NotifyIface, deploy pkgwatcher.DeployFunc) *watcher { func NewWatcher(notifier pkg.NotifyIface, deploy pkg.DeployFunc) *watcher {
wt, err := notifier.NewWatcher() wt, err := notifier.NewWatcher()
if err != nil { if err != nil {
log.Printf("fsnotify.NewWatcher: %s\n", err) log.Printf("fsnotify.NewWatcher: %s\n", err)

View File

@@ -3,11 +3,12 @@ package watcher
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"testing" "testing"
"time" "time"
"gitea.urkob.com/urko/git-webhook-ci/cfg" "gitea.urkob.com/urko/git-webhook-ci/kit/config"
pkgwatcher "gitea.urkob.com/urko/git-webhook-ci/pkg/watcher" "gitea.urkob.com/urko/git-webhook-ci/pkg"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -26,13 +27,13 @@ var (
) )
var ( var (
mockDeploy pkgwatcher.DeployFunc mockDeploy pkg.DeployFunc
mockErrorDeploy pkgwatcher.DeployFunc mockErrorDeploy pkg.DeployFunc
errIntentional = errors.New("intentional error") errIntentional = errors.New("intentional error")
binaryPath = "" binaryPath = ""
scriptPath = "" scriptPath = ""
executionMaxTimeout = time.Second * 2 executionMaxTimeout = time.Second * 2
config *cfg.Config cfg *config.Config
events = []fsnotify.Event{ events = []fsnotify.Event{
{ {
Name: "test event", Name: "test event",
@@ -45,7 +46,7 @@ var (
} }
) )
func init() { func TestMain(t *testing.M) {
mockDeploy = func(binaryPath, scriptPath string) error { mockDeploy = func(binaryPath, scriptPath string) error {
return nil return nil
} }
@@ -53,8 +54,16 @@ func init() {
mockErrorDeploy = func(binaryPath, scriptPath string) error { mockErrorDeploy = func(binaryPath, scriptPath string) error {
return errIntentional return errIntentional
} }
}
func LoadConfig(t *testing.T) {
t.Helper()
cf, err := config.LoadConfig("testdata/config.yaml")
if err != nil {
panic(fmt.Errorf("Error loading config: %w", err))
}
cfg = cf
config = cfg.NewConfig(false)
} }
func sendTestEvents(w *watcher) { func sendTestEvents(w *watcher) {
@@ -76,6 +85,7 @@ func newWatcherWithCtorError() *watcher {
} }
func Test_NewNotifier(t *testing.T) { func Test_NewNotifier(t *testing.T) {
LoadConfig(t)
require.NotNil(t, NewNotifier()) require.NotNil(t, NewNotifier())
} }
@@ -97,7 +107,7 @@ func Test_Close(t *testing.T) {
func Test_Monitor(t *testing.T) { func Test_Monitor(t *testing.T) {
w := newWatcher() w := newWatcher()
err := w.Monitor(config.TestFileToWatchPath) err := w.Monitor(cfg.FileToWatchPath)
require.NoError(t, err) require.NoError(t, err)
} }

27
kit/config/config.go Normal file
View File

@@ -0,0 +1,27 @@
package config
import (
"os"
"gopkg.in/yaml.v2"
)
type Config struct {
ScriptBinaryPath string `yaml:"script_binary_path"`
WebhookScriptPath string `yaml:"webhook_script_path"`
FileToWatchPath string `yaml:"file_to_watch_path"`
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}

16
main.go
View File

@@ -6,22 +6,24 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"gitea.urkob.com/urko/git-webhook-ci/cfg"
"gitea.urkob.com/urko/git-webhook-ci/internal/watcher" "gitea.urkob.com/urko/git-webhook-ci/internal/watcher"
pkgwatcher "gitea.urkob.com/urko/git-webhook-ci/pkg/watcher" "gitea.urkob.com/urko/git-webhook-ci/kit/config"
"gitea.urkob.com/urko/git-webhook-ci/pkg"
) )
var ( var (
watcherIface pkgwatcher.WatcherIface watcherIface pkg.WatcherIface
notifierIface pkgwatcher.NotifyIface notifierIface pkg.NotifyIface
) )
func main() { func main() {
isProd := os.Getenv("ENV") == "prod" config, err := config.LoadConfig("./configs/app.yaml")
config := cfg.NewConfig(isProd) if err != nil {
panic(err)
}
notifierIface = watcher.NewNotifier() notifierIface = watcher.NewNotifier()
watcherIface = watcher.NewWatcher(notifierIface, pkgwatcher.Deploy) watcherIface = watcher.NewWatcher(notifierIface, pkg.Deploy)
defer func() { defer func() {
if err := watcherIface.Close(); err != nil { if err := watcherIface.Close(); err != nil {

2
pkg/testdata/test-script.sh vendored Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
echo "deploy script has been called"

View File

@@ -1,4 +1,4 @@
package watcher package pkg
import ( import (
"fmt" "fmt"

View File

@@ -1,15 +1,14 @@
package watcher package pkg
import ( import (
"testing" "testing"
"gitea.urkob.com/urko/git-webhook-ci/cfg"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
var ( const (
binaryPath = "/bin/bash" binaryPath = "/bin/bash"
scriptPath = cfg.RootDir() + "/test-script.sh" scriptPath = "testdata/test-script.sh"
) )
func TestDeploy(t *testing.T) { func TestDeploy(t *testing.T) {