Skip to content

Commit 41073df

Browse files
committed
WIP Implementation of mg dev watch - a skaffold like experience for
metagraf.
1 parent 54abd41 commit 41073df

File tree

3 files changed

+178
-33
lines changed

3 files changed

+178
-33
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ require (
99
github.com/coreos/prometheus-operator v0.41.1
1010
github.com/crossplane/crossplane-runtime v0.9.0 // indirect
1111
github.com/crossplane/oam-kubernetes-runtime v0.0.9
12+
github.com/fsnotify/fsnotify v1.4.9
1213
github.com/ghodss/yaml v1.0.0
14+
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae // indirect
1315
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
1416
github.com/google/go-containerregistry v0.4.0
1517
github.com/google/gofuzz v1.2.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod
339339
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
340340
github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
341341
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
342+
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae h1:PeVNzgTRtWGm6fVic5i21t+n5ptPGCZuMcSPVMyTWjs=
343+
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae/go.mod h1:BbhqyaehKPCLD83cqfRYdm177Ylm1cdGHu3txjbQSQI=
342344
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
343345
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
344346
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=

mg/cmd/dev.go

Lines changed: 174 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import (
2121
"fmt"
2222
"os"
2323
"os/exec"
24+
"path/filepath"
25+
"strings"
26+
"sync"
2427

28+
"github.com/fsnotify/fsnotify"
2529
"github.com/laetho/metagraf/internal/pkg/params"
2630
"github.com/laetho/metagraf/pkg/metagraf"
2731
"github.com/laetho/metagraf/pkg/modules"
@@ -31,6 +35,17 @@ import (
3135
log "k8s.io/klog"
3236
)
3337

38+
var wg sync.WaitGroup
39+
40+
// Type for events emitted by filteredFileWatcher.
41+
type WatchEvent int
42+
43+
// Enum values for WatchEvent
44+
const (
45+
BuildAndDeploy WatchEvent = iota
46+
Deploy
47+
)
48+
3449
func init() {
3550
RootCmd.AddCommand(devCmd)
3651
devCmd.PersistentFlags().BoolVar(&Output, "output", false, "also output objects")
@@ -51,7 +66,6 @@ func init() {
5166
devCmdUp.Flags().Int32Var(&params.ServiceMonitorPort, "service-monitor-port", params.ServiceMonitorPort, "Set Service port to scrape in ServiceMonitor.")
5267
devCmdUp.Flags().StringVar(&params.ServiceMonitorOperatorName, "service-monitor-operator-name", params.ServiceMonitorOperatorName, "Name of prometheus-operator instance to create ServiceMonitor for.")
5368

54-
5569
devCmd.AddCommand(devCmdDown)
5670
devCmdDown.Flags().StringVarP(&params.NameSpace, "namespace", "n", "", "namespace to work on, if not supplied it will use current active namespace.")
5771
devCmdDown.Flags().BoolVar(&params.Everything, "everything", false, "Delete all resources and artifacts generated from mg dev up.")
@@ -61,6 +75,11 @@ func init() {
6175
devCmdBuild.Flags().StringVar(&params.SourceRef, "ref", "master", "Specify the git ref or branch ref to build.")
6276
devCmdBuild.Flags().StringVarP(&params.NameSpace, "namespace", "n", "", "namespace to work on, if not supplied it will use current active namespace.")
6377
devCmdBuild.Flags().BoolVar(&params.LocalBuild, "local", false, "Builds application from src in current (.) direcotry.")
78+
79+
devCmd.AddCommand(devCmdWatch)
80+
devCmdWatch.Flags().StringVarP(&params.NameSpace, "namespace", "n", "", "namespace to work on, if not supplied it will use current active namespace.")
81+
devCmdWatch.Flags().StringSliceVar(&CVars, "cvars", []string{}, "Slice of key=value pairs, seperated by ,")
82+
devCmdWatch.Flags().StringVar(&params.PropertiesFile, "cvfile", "", "Property file with component configuration values. Can be generated with \"mg generate properties\" command.)")
6483
}
6584

6685
var devCmd = &cobra.Command{
@@ -69,6 +88,51 @@ var devCmd = &cobra.Command{
6988
Long: `dev subcommands`,
7089
}
7190

91+
var devCmdWatch = &cobra.Command{
92+
Use: "watch <metagraf.json>",
93+
Short: "watches for local filechanges and rebuilds or redeploys the component.",
94+
Long: `Inspired by skaffold.dev. Watches for local file changes and rebuilds and redeploys.`,
95+
Run: func(cmd *cobra.Command, args []string) {
96+
requireMetagraf(args)
97+
requireNamespace()
98+
99+
mg := metagraf.Parse(args[0])
100+
bc := mg.Name(OName, Version)
101+
102+
FlagPassingHack()
103+
modules.NameSpace = params.NameSpace
104+
105+
// Crate a buffered channel with room for one event.
106+
chEvents := make(chan WatchEvent, 1)
107+
// Channel to indicate if we are in processing state.
108+
chProcessing := make(chan bool, 1)
109+
110+
wg.Add(2)
111+
112+
go filteredFileWatcher(chEvents, chProcessing)
113+
114+
for {
115+
select {
116+
case command := <-chEvents:
117+
chProcessing<-true
118+
switch command {
119+
case BuildAndDeploy:
120+
buildGenerate(&mg, params.NameSpace, true)
121+
err := s2ibuild(bc, params.NameSpace, true)
122+
if err != nil {
123+
log.Fatalf("Unable to build: %v ", err)
124+
}
125+
devUp(args[0])
126+
case Deploy:
127+
devUp(args[0])
128+
}
129+
chProcessing<-false
130+
}
131+
}
132+
wg.Wait()
133+
},
134+
}
135+
72136
var devCmdUp = &cobra.Command{
73137
Use: "up <metagraf.json>",
74138
Short: "creates the required component resources.",
@@ -113,36 +177,12 @@ var devCmdBuild = &cobra.Command{
113177
FlagPassingHack()
114178
modules.NameSpace = params.NameSpace
115179

116-
// Remove RepSecRef from generated BuildConfig if --local argument is provided.
117-
if params.LocalBuild && len(mg.Spec.RepSecRef) > 0 {
118-
mg.Spec.RepSecRef = ""
119-
}
120-
121-
modules.GenImageStream(&mg, params.NameSpace)
122-
modules.GenBuildConfig(&mg)
123-
124-
path, err := exec.LookPath("oc")
125-
if err != nil {
126-
log.Fatal(err)
127-
}
128-
arg := []string{"start-build", bc,"-n", params.NameSpace, "--follow"}
129-
if params.LocalBuild {
130-
arg = append(arg, "--from-file=.")
131-
}
132-
c := exec.Command(path, arg...)
133-
stdout, err := c.StdoutPipe()
180+
buildGenerate(&mg, params.NameSpace, params.LocalBuild)
181+
err := s2ibuild(bc, params.NameSpace, params.LocalBuild)
134182
if err != nil {
135-
log.Fatal(err)
136-
}
137-
c.Env = append(os.Environ())
138-
c.Start()
139-
140-
buf := bufio.NewReader(stdout)
141-
line := []byte{}
142-
for err == nil {
143-
line, _,err = buf.ReadLine()
144-
fmt.Println(string(line))
183+
log.Fatalf("Build failed: %v", err)
145184
}
185+
146186
},
147187
}
148188

@@ -177,10 +217,111 @@ func devDown(mgf string) {
177217
}
178218
}
179219

180-
func checkForOC() bool {
181-
_, err := exec.LookPath("oc")
220+
func buildGenerate(mg *metagraf.MetaGraf, ns string, local bool) {
221+
// Remove RepSecRef from generated BuildConfig if --local argument is provided.
222+
if local && len(mg.Spec.RepSecRef) > 0 {
223+
mg.Spec.RepSecRef = ""
224+
}
225+
226+
modules.GenImageStream(mg, ns)
227+
modules.GenBuildConfig(mg)
228+
}
229+
230+
func s2ibuild(bc string, ns string, local bool) error {
231+
path, err := exec.LookPath("oc")
182232
if err != nil {
183-
return false
233+
return err
184234
}
185-
return true
235+
236+
arg := []string{"start-build", bc, "-n", ns, "--follow"}
237+
if local {
238+
arg = append(arg, "--from-file=.")
239+
}
240+
c := exec.Command(path, arg...)
241+
stdout, pipeerr := c.StdoutPipe()
242+
if pipeerr != nil {
243+
return pipeerr
244+
}
245+
c.Env = append(os.Environ())
246+
cmderr := c.Start()
247+
if cmderr != nil {
248+
return cmderr
249+
}
250+
251+
buf := bufio.NewReader(stdout)
252+
line := []byte{}
253+
for err == nil {
254+
line, _, err = buf.ReadLine()
255+
fmt.Println(string(line))
256+
}
257+
return nil
258+
}
259+
260+
// Watches for events in ./ using fsnotify and writes WatchEvent's to typed channel.
261+
// Reads processing state from a bool channel. If true, skip writing commands.
262+
func filteredFileWatcher(chEvents chan<- WatchEvent, chProcessing <-chan bool) {
263+
264+
done := make(chan bool)
265+
266+
watcher, err := fsnotify.NewWatcher()
267+
if err != nil {
268+
log.Fatal(err)
269+
}
270+
271+
watchDir := func(path string, info os.FileInfo, err error) error {
272+
if info.Mode().IsDir() {
273+
return watcher.Add(path)
274+
}
275+
return nil
276+
}
277+
278+
if err := filepath.Walk("./", watchDir); err != nil {
279+
log.Fatal(err)
280+
}
281+
282+
go func() {
283+
var skipevents bool = false
284+
for {
285+
select {
286+
case event := <-watcher.Events:
287+
// Check to see if we got a new chProcessing state to handle
288+
if len(chProcessing) >= 1 {
289+
skipevents = <-chProcessing
290+
}
291+
if skipevents {
292+
// Pop of the Event and continue.
293+
<-watcher.Events
294+
continue
295+
}
296+
297+
// Discard temporary files from editors
298+
if strings.Contains(event.Name, "~") {
299+
continue
300+
}
301+
if len(event.Name) == 0 {
302+
continue
303+
}
304+
305+
switch event.Op.String() {
306+
case "WRITE":
307+
log.V(2).Infof("Got event on: %v, Type: %v", event.Name, event.Op)
308+
// Redeploy if configuration input changed.
309+
if strings.Contains(event.Name, params.PropertiesFile) {
310+
log.Info("Properties file changed, redeploying.")
311+
chEvents <- Deploy
312+
} else if strings.Contains(event.Name, "metagraf.json") {
313+
log.Info("metaGraf Specification Changed")
314+
chEvents <- BuildAndDeploy
315+
}else {
316+
// Build and Redeploy
317+
chEvents <- BuildAndDeploy
318+
}
319+
}
320+
case err := <-watcher.Errors:
321+
fmt.Println("ERROR:", err)
322+
default:
323+
}
324+
}
325+
}()
326+
<-done
186327
}

0 commit comments

Comments
 (0)