Compare commits

..

30 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
c0daf06ee6 feature: update readme 2023-02-26 19:00:02 +01:00
41517ff292 feature: add license 2023-02-26 18:41:05 +01:00
16b0a31dd4 feat: complete tests 2023-02-26 16:59:20 +01:00
49a72dc34b feat: log event 2023-02-26 16:26:13 +01:00
a67ef6f2d8 feat: block routine with for loop 2023-02-26 16:19:12 +01:00
19f17308b1 feat: test pkg watcher 2023-02-26 16:07:14 +01:00
e25b192aff feat: update makefile 2023-02-26 11:07:30 +01:00
ca4541ca1a feat: update gitignore 2023-02-26 11:07:25 +01:00
e02c244425 feat: update README 2023-02-26 11:07:21 +01:00
a4ce793707 feat: update gitignore 2023-02-26 11:00:04 +01:00
8d533a5ca6 feat: add Make file 2023-02-26 10:59:41 +01:00
28c7a03d9c feat: main 2023-02-26 10:59:33 +01:00
103016bc39 feat: add config 2023-02-26 10:59:22 +01:00
d50a9afde8 feat: add .env.example 2023-02-26 10:59:08 +01:00
49126da9d9 feat: update gitignore 2023-02-26 10:59:00 +01:00
7513abffbb feat: test watcher 2023-02-26 10:58:55 +01:00
70a777db3a feat: add readme 2023-02-26 10:58:32 +01:00
18 changed files with 467 additions and 151 deletions

4
.gitignore vendored
View File

@@ -1 +1,5 @@
.env .env
coverage/*
test_monitor.txt
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"]

8
LICENSE Normal file
View File

@@ -0,0 +1,8 @@
---- Definitions ----
license means right to use
Everybody is invited to contribute to improve this project and the main idea.
This idea which is to help the community to develop more secure code.
By the grace of YAHWEH

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
BINARY_DIR=bin
BINARY_NAME=webhook-listener
COVERAGE_DIR=coverage
lint:
golangci-lint run ./...
goreportcard:
goreportcard-cli -v
test:
go test ./...
test-coverage:
rm -rf ${COVERAGE_DIR}
mkdir ${COVERAGE_DIR}
go test -v -coverprofile ${COVERAGE_DIR}/cover.out ./...
go tool cover -html ${COVERAGE_DIR}/cover.out -o ${COVERAGE_DIR}/cover.html
build:
rm -rf ${BINARY_DIR}
mkdir ${BINARY_DIR}
env GOOS=linux CGO_ENABLED=0 GOARCH=amd64 go build -o ./${BINARY_DIR}/${BINARY_NAME} main.go

78
README.md Normal file
View File

@@ -0,0 +1,78 @@
# git-webhook-ci
Tool to automatize your deploy based on file write changes
## Description
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
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
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
- move build to desired dir
- restart services like nginx or whatever
## Installation
### Requirements
- [Go version > 1.22](https://go.dev/dl/)
- [GNU Make 4.3](https://www.gnu.org/software/make/)
- [goreportcard-cli](https://github.com/gojp/goreportcard)
- [golangci-lint](https://golangci-lint.run/)
### Environment Vars
As shown in app.example.yml you have to configure this variables in order to make your binary works right:
```.env
script_binary_path: "/bin/bash"
webhook_script_path: "./test-script.sh"
file_to_watch_path: "./test_monitor.txt"
```
## How to use
### Build
To build you can only just `make build` command
```bash
make build
```
## Tests
```bash
make test-coverage
```
output:
```terminal
coverage: 80.0% of statements
ok gitea.urkob.com/urko/git-webhook-ci/internal/watcher 2.024s coverage: 80.0% of statements
coverage: 100.0% of statements
ok gitea.urkob.com/urko/git-webhook-ci/pkg/watcher 0.017s coverage: 100.0% of statements
```
## goreportcard
```bash
➜ git-webhook-ci git:(main) ✗ make goreportcard
oreportcard-cli -v
Grade .......... A+ 100.0%
Files .................. 6
Issues ................. 0
gofmt ............... 100%
go_vet .............. 100%
gocyclo ............. 100%
ineffassign ......... 100%
license ............. 100%
misspell ............ 100%
```

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,44 +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"`
}
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
}

15
go.mod
View File

@@ -1,11 +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/stretchr/testify v1.8.2
github.com/kelseyhightower/envconfig v1.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

31
go.sum
View File

@@ -1,8 +1,23 @@
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
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/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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

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

View File

@@ -4,17 +4,32 @@ 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
} }
func NewWatcher(deploy pkgwatcher.DeployFunc) *watcher { type notifier struct{}
wt, err := fsnotify.NewWatcher()
func (n *notifier) NewWatcher() (*fsnotify.Watcher, error) {
return fsnotify.NewWatcher()
}
func NewNotifier() *notifier {
return &notifier{}
}
var (
errEventsClosedChan = errors.New("events is closed")
errErrorsClosedChan = errors.New("errors is closed")
)
func NewWatcher(notifier pkg.NotifyIface, deploy pkg.DeployFunc) *watcher {
wt, err := notifier.NewWatcher()
if err != nil { if err != nil {
log.Printf("fsnotify.NewWatcher: %s\n", err) log.Printf("fsnotify.NewWatcher: %s\n", err)
return nil return nil
@@ -36,25 +51,30 @@ func (w *watcher) Listen(binaryPath, scriptPath string, outputErr chan<- error)
select { select {
case event, ok := <-events: case event, ok := <-events:
if !ok { if !ok {
log.Printf("!ok <-events \n") log.Println(errEventsClosedChan)
outputErr <- errors.New("!ok <-events") outputErr <- errEventsClosedChan
return return
} }
if !event.Has(fsnotify.Write) { if !event.Has(fsnotify.Write) {
log.Printf("is not Write: %s\n", event.Name) log.Printf("is not Write: %s\n", event.Name)
continue continue
} }
log.Printf("event: %s | op: %s \n", event.Name, event.Op)
if err := w.deploy(binaryPath, scriptPath); err != nil { if err := w.deploy(binaryPath, scriptPath); err != nil {
log.Printf("deploy: %s\n", err) log.Printf("deploy: %s\n", err)
outputErr <- err
continue continue
} }
case err, ok := <-errChan: case err, ok := <-errChan:
if !ok { if !ok {
log.Printf("!ok <-errors\n") log.Println(errErrorsClosedChan)
outputErr <- errErrorsClosedChan
return return
} }
log.Printf("<-errors: %s\n", err) log.Printf("<-errors: %s\n", err)
outputErr <- err
} }
} }
}(w.fswatcher.Events, w.fswatcher.Errors, outputErr) }(w.fswatcher.Events, w.fswatcher.Errors, outputErr)

View File

@@ -0,0 +1,172 @@
package watcher
import (
"context"
"errors"
"fmt"
"testing"
"time"
"gitea.urkob.com/urko/git-webhook-ci/kit/config"
"gitea.urkob.com/urko/git-webhook-ci/pkg"
"github.com/fsnotify/fsnotify"
"github.com/stretchr/testify/require"
)
type testErrorNotifier struct {
*fsnotify.Watcher
}
func (n *testErrorNotifier) NewWatcher() (*fsnotify.Watcher, error) {
return nil, errIntentional
}
var (
errNotifier = &testErrorNotifier{}
okNotifier = &notifier{}
)
var (
mockDeploy pkg.DeployFunc
mockErrorDeploy pkg.DeployFunc
errIntentional = errors.New("intentional error")
binaryPath = ""
scriptPath = ""
executionMaxTimeout = time.Second * 2
cfg *config.Config
events = []fsnotify.Event{
{
Name: "test event",
Op: fsnotify.Create,
},
{
Name: "Write",
Op: fsnotify.Write,
},
}
)
func TestMain(t *testing.M) {
mockDeploy = func(binaryPath, scriptPath string) error {
return nil
}
mockErrorDeploy = func(binaryPath, scriptPath string) error {
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
}
func sendTestEvents(w *watcher) {
for _, event := range events {
w.fswatcher.Events <- event
}
}
func newWatcher() *watcher {
return NewWatcher(okNotifier, mockDeploy)
}
func newWatcherWithDeployError() *watcher {
return NewWatcher(okNotifier, mockErrorDeploy)
}
func newWatcherWithCtorError() *watcher {
return NewWatcher(errNotifier, mockDeploy)
}
func Test_NewNotifier(t *testing.T) {
LoadConfig(t)
require.NotNil(t, NewNotifier())
}
func Test_NewWatcher(t *testing.T) {
w := newWatcher()
require.NotNil(t, w)
}
func Test_ErrorNewWatcher(t *testing.T) {
w := newWatcherWithCtorError()
require.Nil(t, w)
}
func Test_Close(t *testing.T) {
w := newWatcher()
err := w.Close()
require.NoError(t, err)
}
func Test_Monitor(t *testing.T) {
w := newWatcher()
err := w.Monitor(cfg.FileToWatchPath)
require.NoError(t, err)
}
func Test_ListenSuccess(t *testing.T) {
w := newWatcher()
ctx, errors := listenWithSendEvents(w)
for {
select {
case <-ctx.Done():
return
case err := <-errors:
require.NoError(t, err)
return
}
}
}
func Test_ListenError(t *testing.T) {
w := newWatcherWithDeployError()
ctx, errors := listenWithSendEvents(w)
for {
select {
case <-ctx.Done():
return
case err := <-errors:
require.Error(t, err)
require.EqualError(t, err, errIntentional.Error())
return
}
}
}
func Test_ListenErrorChanClose(t *testing.T) {
w := newWatcher()
ctx, errors := listenWithSendEvents(w)
close(w.fswatcher.Events)
for {
select {
case <-ctx.Done():
return
case err := <-errors:
require.Error(t, err)
require.EqualError(t, err, errEventsClosedChan.Error())
return
}
}
}
func listenWithSendEvents(w *watcher) (context.Context, chan error) {
ctx, cancel := context.WithTimeout(context.Background(), executionMaxTimeout)
errors := make(chan error)
w.Listen(binaryPath, scriptPath, errors)
go func(cancel context.CancelFunc) {
time.Sleep(executionMaxTimeout)
cancel()
}(cancel)
sendTestEvents(w)
return ctx, errors
}

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
}

98
main.go
View File

@@ -6,18 +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 watcherIface pkgwatcher.WatcherIface var (
watcherIface pkg.WatcherIface
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)
}
watcherIface = watcher.NewWatcher(pkgwatcher.Deploy) notifierIface = watcher.NewNotifier()
watcherIface = watcher.NewWatcher(notifierIface, pkg.Deploy)
defer func() { defer func() {
if err := watcherIface.Close(); err != nil { if err := watcherIface.Close(); err != nil {
@@ -33,87 +39,17 @@ func main() {
watcherIface.Listen(config.ScriptBinaryPath, config.WebhookScriptPath, errors) watcherIface.Listen(config.ScriptBinaryPath, config.WebhookScriptPath, errors)
// Handle termination on ctrl+signalChan // Handle termination on ctrl+signalChan
signalChan := make(chan os.Signal) signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-signalChan
os.Exit(1)
}()
go func(errors chan error) {
for { for {
select { select {
case <-signalChan:
os.Exit(1)
case err := <-errors: case err := <-errors:
if err != nil { if err != nil {
log.Fatalf("watcherIface.Monitor: %s\n", err) log.Printf("watcherIface.Monitor: %s\n", err)
return continue
} }
} }
} }
}(errors)
// Block main goroutine forever.
<-make(chan struct{})
// watcher, err := fsnotify.NewWatcher()
// if err != nil {
// log.Fatal(err)
// }
// defer watcher.Close()
// // Start listening for events.
// go func() {
// for {
// select {
// case event, ok := <-watcher.Events:
// if !ok {
// log.Printf("<-watcher.Events: %s\n", err)
// return
// }
// if !event.Has(fsnotify.Write) {
// log.Printf("is not Write: %s\n", event.Name)
// continue
// }
// if err := deploy(config.ScriptBinaryPath, config.WebhookScriptPath); err != nil {
// log.Printf("deploy: %s\n", err)
// continue
// }
// case err, ok := <-watcher.Errors:
// if !ok {
// return
// }
// log.Printf("<-watcher.Errors: %s\n", err)
// }
// }
// }()
// if err = watcher.Add(config.FileToWatchPath); err != nil {
// log.Fatal(err)
// }
// // Block main goroutine forever.
// <-make(chan struct{})
} }
// func deploy(binaryPath, scriptPath string) error {
// err := execute(binaryPath, scriptPath)
// if err != nil {
// return fmt.Errorf("execute: %s", err)
// }
// log.Println("deploy done")
// return nil
// }
// func execute(binaryPath, scriptPath string) error {
// cmd := exec.Command(binaryPath, scriptPath)
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
// if err := cmd.Run(); err != nil {
// return fmt.Errorf("cmd.Run %s", err)
// }
// return 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,12 +1,18 @@
package watcher package pkg
import ( import (
"fmt" "fmt"
"log" "log"
"os" "os"
"os/exec" "os/exec"
"github.com/fsnotify/fsnotify"
) )
type NotifyIface interface {
NewWatcher() (*fsnotify.Watcher, error)
}
type WatcherIface interface { type WatcherIface interface {
Monitor(path string) error Monitor(path string) error
Listen(binaryPath, scriptPath string, outputErr chan<- error) Listen(binaryPath, scriptPath string, outputErr chan<- error)

32
pkg/watcher_test.go Normal file
View File

@@ -0,0 +1,32 @@
package pkg
import (
"testing"
"github.com/stretchr/testify/require"
)
const (
binaryPath = "/bin/bash"
scriptPath = "testdata/test-script.sh"
)
func TestDeploy(t *testing.T) {
err := Deploy(binaryPath, scriptPath)
require.NoError(t, err)
}
func TestDeployError(t *testing.T) {
err := Deploy("", "")
require.Error(t, err)
}
func TestExecute(t *testing.T) {
err := execute(binaryPath, scriptPath)
require.NoError(t, err)
}
func TestExecuteError(t *testing.T) {
err := execute("", "")
require.Error(t, err)
}