Skip to content

Commit 01c4c57

Browse files
add watcher to monitor server paths for changes and report automatically (#420)
* add watcher to server paths reporting * fix linting error
1 parent f3b0a34 commit 01c4c57

File tree

5 files changed

+101
-22
lines changed

5 files changed

+101
-22
lines changed

cmd/kosli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file,
246246
attestationTypeSchemaFlag = "[optional] Path to the attestation type schema in JSON Schema format."
247247
attestationTypeJqFlag = "[optional] The attestation type evaluation JQ rules."
248248
envNameFlag = "The Kosli environment name to assert the artifact against."
249+
pathsWatchFlag = "[optional] Watch the filesystem for changes and report snapshots of artifacts running in specific filesystem paths to Kosli."
249250
)
250251

251252
var global *GlobalOpts

cmd/kosli/snapshotPath.go

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import (
44
"fmt"
55
"io"
66
"net/http"
7+
"os"
8+
"os/signal"
9+
"path/filepath"
10+
11+
"github.com/rjeczalik/notify"
712

813
"github.com/kosli-dev/cli/internal/requests"
914
"github.com/kosli-dev/cli/internal/server"
@@ -41,6 +46,7 @@ type snapshotPathOptions struct {
4146
path string
4247
artifactName string
4348
exclude []string
49+
watch bool
4450
}
4551

4652
func newSnapshotPathCmd(out io.Writer) *cobra.Command {
@@ -67,6 +73,7 @@ func newSnapshotPathCmd(out io.Writer) *cobra.Command {
6773
cmd.Flags().StringVar(&o.path, "path", "", snapshotPathPathFlag)
6874
cmd.Flags().StringVar(&o.artifactName, "name", "", snapshotPathArtifactNameFlag)
6975
cmd.Flags().StringSliceVarP(&o.exclude, "exclude", "x", []string{}, snapshotPathExcludeFlag)
76+
cmd.Flags().BoolVar(&o.watch, "watch", false, pathsWatchFlag)
7077
addDryRunFlag(cmd)
7178

7279
if err := RequireFlags(cmd, []string{"path", "name"}); err != nil {
@@ -78,9 +85,6 @@ func newSnapshotPathCmd(out io.Writer) *cobra.Command {
7885

7986
func (o *snapshotPathOptions) run(args []string) error {
8087
envName := args[0]
81-
82-
url := fmt.Sprintf("%s/api/v2/environments/%s/%s/report/server", global.Host, global.Org, envName)
83-
8488
// load path spec from flags
8589
ps := &server.PathsSpec{
8690
Version: 1,
@@ -92,6 +96,23 @@ func (o *snapshotPathOptions) run(args []string) error {
9296
},
9397
}
9498

99+
err := reportArtifacts(ps, envName)
100+
if err != nil {
101+
return err
102+
}
103+
104+
if o.watch {
105+
err := watchPath(ps, o.path, envName)
106+
if err != nil {
107+
return err
108+
}
109+
}
110+
111+
return nil
112+
}
113+
114+
func reportArtifacts(ps *server.PathsSpec, envName string) error {
115+
url := fmt.Sprintf("%s/api/v2/environments/%s/%s/report/server", global.Host, global.Org, envName)
95116
artifacts, err := server.CreatePathsArtifactsData(ps, logger)
96117
if err != nil {
97118
return err
@@ -113,3 +134,63 @@ func (o *snapshotPathOptions) run(args []string) error {
113134
}
114135
return err
115136
}
137+
138+
func watchPath(ps *server.PathsSpec, path, envName string) error {
139+
events := make(chan notify.EventInfo, 1)
140+
if err := watchRecursive(path, events); err != nil {
141+
return fmt.Errorf("error setting up watcher: %v", err)
142+
}
143+
defer notify.Stop(events)
144+
145+
logger.Info("watching for file changes in %s. Press Ctrl+C to exit...", path)
146+
147+
// Handle system interrupts (Ctrl+C)
148+
stop := make(chan os.Signal, 1)
149+
signal.Notify(stop, os.Interrupt)
150+
151+
for {
152+
select {
153+
case event := <-events:
154+
logger.Debug("event: %s on %s", event.Event(), event.Path())
155+
156+
err := reportArtifacts(ps, envName)
157+
if err != nil {
158+
return err
159+
}
160+
161+
// If a new directory is created, start watching it
162+
// github.com/rjeczalik/notify does not automatically watch new subdirs
163+
if event.Event() == notify.Create {
164+
info, err := os.Stat(event.Path())
165+
if err == nil && info.IsDir() {
166+
logger.Debug("new directory detected: %s. Adding to watcher.", event.Path())
167+
err = notify.Watch(event.Path()+"/...", events, notify.All)
168+
if err != nil {
169+
return err
170+
}
171+
}
172+
}
173+
case <-stop:
174+
logger.Info("\tstopping file watcher...")
175+
return nil
176+
}
177+
}
178+
}
179+
180+
// Watches a directory recursively and detects new subdirectories
181+
func watchRecursive(root string, events chan notify.EventInfo) error {
182+
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
183+
if err != nil {
184+
return err
185+
}
186+
// Watch all directories, including existing and newly created ones
187+
if info.IsDir() {
188+
if err := notify.Watch(path+"/...", events, notify.All); err != nil {
189+
return fmt.Errorf("failed to watch %s: %v", path, err)
190+
} else {
191+
logger.Debug("watching: %s", path)
192+
}
193+
}
194+
return nil
195+
})
196+
}

cmd/kosli/snapshotPaths.go

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ package main
33
import (
44
"fmt"
55
"io"
6-
"net/http"
76
"path/filepath"
87
"strings"
98

109
validator "github.com/go-playground/validator/v10"
11-
"github.com/kosli-dev/cli/internal/requests"
1210
"github.com/kosli-dev/cli/internal/server"
1311
"github.com/spf13/cobra"
1412
"github.com/spf13/viper"
@@ -53,6 +51,7 @@ kosli snapshot paths yourEnvironmentName \
5351

5452
type snapshotPathsOptions struct {
5553
pathSpecFile string
54+
watch bool
5655
}
5756

5857
func newSnapshotPathsCmd(out io.Writer) *cobra.Command {
@@ -77,6 +76,7 @@ func newSnapshotPathsCmd(out io.Writer) *cobra.Command {
7776
}
7877

7978
cmd.Flags().StringVar(&o.pathSpecFile, "paths-file", "", pathsSpecFileFlag)
79+
cmd.Flags().BoolVar(&o.watch, "watch", false, pathsWatchFlag)
8080
addDryRunFlag(cmd)
8181

8282
if err := RequireFlags(cmd, []string{"paths-file"}); err != nil {
@@ -89,34 +89,27 @@ func newSnapshotPathsCmd(out io.Writer) *cobra.Command {
8989
func (o *snapshotPathsOptions) run(args []string) error {
9090
envName := args[0]
9191

92-
url := fmt.Sprintf("%s/api/v2/environments/%s/%s/report/server", global.Host, global.Org, envName)
93-
9492
// load path spec from file
9593
ps, err := processPathSpecFile(o.pathSpecFile)
9694
if err != nil {
9795
return err
9896
}
9997

100-
artifacts, err := server.CreatePathsArtifactsData(ps, logger)
98+
err = reportArtifacts(ps, envName)
10199
if err != nil {
102100
return err
103101
}
104-
payload := &server.ServerEnvRequest{
105-
Artifacts: artifacts,
106-
}
107102

108-
reqParams := &requests.RequestParams{
109-
Method: http.MethodPut,
110-
URL: url,
111-
Payload: payload,
112-
DryRun: global.DryRun,
113-
Token: global.ApiToken,
114-
}
115-
_, err = kosliClient.Do(reqParams)
116-
if err == nil && !global.DryRun {
117-
logger.Info("[%d] artifacts were reported to environment %s", len(payload.Artifacts), envName)
103+
if o.watch {
104+
for _, artifactSpec := range ps.Artifacts {
105+
err := watchPath(ps, artifactSpec.Path, envName)
106+
if err != nil {
107+
return err
108+
}
109+
}
118110
}
119-
return err
111+
112+
return nil
120113
}
121114

122115
func processPathSpecFile(pathsSpecFile string) (*server.PathsSpec, error) {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131
github.com/otiai10/copy v1.9.0
3232
github.com/owenrumney/go-sarif/v2 v2.3.0
3333
github.com/pkg/errors v0.9.1
34+
github.com/rjeczalik/notify v0.9.3
3435
github.com/spf13/cobra v1.8.1
3536
github.com/spf13/pflag v1.0.5
3637
github.com/spf13/viper v1.15.0

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
662662
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
663663
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
664664
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
665+
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
666+
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
665667
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
666668
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
667669
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -985,6 +987,7 @@ golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5h
985987
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
986988
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
987989
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
990+
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
988991
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
989992
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
990993
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

0 commit comments

Comments
 (0)