Repository: appvia/kev
Pull request #576 (2021-06-18 07:35:14)

Dev command environments handling fix
Resolves: #575 

This PR fixes the way `dev` command handles project environments in such a way that:

1) in standard development loop (without Skaffold) it takes all tracked environments into account by default. It renders all envs K8s manifest upon change to any of the environment specific docker compose override files.

2) in development loop (with Skaffold activated) it only renders the K8s manifests for a specified environment (selected by `--kev-env` flag), or `dev` environment if none was specified. Specified environment's manifests are auto-deployed to a target K8s cluster.

It also fixes `manifest#UpdateBuildArtifacts` so that slices comparison works as expected. NOTE: `reflect.DeepEqual` treats nil and empty slice as non equal!


Patch:
diff --git a/cmd/kev/cmd/dev.go b/cmd/kev/cmd/dev.go
index daaac36..897758a 100644
--- a/cmd/kev/cmd/dev.go
+++ b/cmd/kev/cmd/dev.go
@@ -139,7 +139,8 @@ func runDevCmd(cmd *cobra.Command, _ []string) error {
 	eventHandler := func(e kev.RunnerEvent, r kev.Runner) error { return nil }
 
 	var envs []string
-	if len(kevenv) > 0 {
+	if len(kevenv) > 0 && skaffold {
+		// when in --skaffold mode - only watch, render and deploy a specified environment
 		envs = append(envs, kevenv)
 	}
 
diff --git a/pkg/kev/dev.go b/pkg/kev/dev.go
index c60c832..7e67e67 100644
--- a/pkg/kev/dev.go
+++ b/pkg/kev/dev.go
@@ -22,6 +22,7 @@ import (
 	"io"
 	"os"
 	"os/signal"
+	"regexp"
 	"syscall"
 
 	"github.com/appvia/kev/pkg/kev/log"
@@ -65,16 +66,23 @@ func (r *DevRunner) Run() error {
 	r.UI.Output("[development mode] ... watching for changes - press Ctrl+C to stop", kmd.WithStyle(kmd.LogStyle))
 	r.DisplaySkaffoldOptionsIfAvailable()
 
-	runPreCommands := func() error {
+	runPreCommands := func(envs []string) error {
 		sg := r.UI.StepGroup()
 		defer sg.Done()
 
-		step := sg.Add(fmt.Sprintf("Running render for environment: %s", r.config.Envs[0]))
+		var msg string
+		if len(envs) == 0 {
+			msg = "Running render for all environments"
+		} else {
+			msg = fmt.Sprintf("Running render for environment: %s", envs[0])
+		}
+
+		step := sg.Add(msg)
 
 		renderRunner = NewRenderRunner(
 			r.WorkingDir,
 			WithEventHandler(r.eventHandler),
-			WithEnvs(r.config.Envs),
+			WithEnvs(envs),
 			WithUI(kmd.NoOpUI()),
 		)
 		if _, err := renderRunner.Run(); err != nil {
@@ -90,7 +98,7 @@ func (r *DevRunner) Run() error {
 	defer close(change)
 
 	// initial manifests generation for specified environments only
-	if err := runPreCommands(); err != nil {
+	if err := runPreCommands(r.config.Envs); err != nil {
 		return err
 	}
 
@@ -133,6 +141,8 @@ func (r *DevRunner) Run() error {
 
 	go r.Watch(change)
 
+	envRe := regexp.MustCompile(`^.*\.(.*)\.ya?ml$`)
+
 	for {
 		ch := <-change
 		if len(ch) > 0 {
@@ -143,11 +153,14 @@ func (r *DevRunner) Run() error {
 				kmd.WithStyle(kmd.LogStyle),
 			)
 
+			match := envRe.FindStringSubmatch(ch)
+			env := match[1]
+
 			if err := r.eventHandler(DevLoopIterated, r); err != nil {
 				return newEventError(err, DevLoopIterated)
 			}
 
-			_ = runPreCommands()
+			_ = runPreCommands([]string{env})
 
 			// empty the buffer as we only ever do one re-render cycle per a batch of changes
 			if len(change) > 0 {
diff --git a/pkg/kev/skaffold.go b/pkg/kev/skaffold.go
index f990e78..cd51b17 100644
--- a/pkg/kev/skaffold.go
+++ b/pkg/kev/skaffold.go
@@ -154,7 +154,9 @@ func UpdateSkaffoldBuildArtifacts(path string, project *ComposeProject) error {
 // true - when list of artefacts was updated, false - otherwise
 func (s *SkaffoldManifest) UpdateBuildArtifacts(analysis *Analysis, project *ComposeProject) bool {
 	prevArts := s.Build.Artifacts
-
+	if prevArts == nil {
+		prevArts = []*latest.Artifact{}
+	}
 	sort.SliceStable(prevArts, func(i, j int) bool {
 		return prevArts[i].ImageName < prevArts[j].ImageName
 	})
@@ -162,6 +164,9 @@ func (s *SkaffoldManifest) UpdateBuildArtifacts(analysis *Analysis, project *Com
 	s.SetBuildArtifacts(analysis, project)
 
 	currArts := s.Build.Artifacts
+	if currArts == nil {
+		currArts = []*latest.Artifact{}
+	}
 	sort.SliceStable(currArts, func(i, j int) bool {
 		return currArts[i].ImageName < currArts[j].ImageName
 	})


Test patch:
diff --git a/pkg/kev/skaffold_test.go b/pkg/kev/skaffold_test.go
index 6c3ba53..a8448bf 100644
--- a/pkg/kev/skaffold_test.go
+++ b/pkg/kev/skaffold_test.go
@@ -349,6 +349,23 @@ var _ = Describe("Skaffold", func() {
 				Expect(images).ToNot(ContainElement("quay.io/myorg/svc2"))
 			})
 		})
+
+		When("previous build artefacts were not defined (nil)", func() {
+			BeforeEach(func() {
+				skaffoldManifest.Build.Artifacts = nil
+			})
+
+			When("and current build artefacts were not detected (empty slice)", func() {
+				BeforeEach(func() {
+					analysis = nil
+					project.Services[0].Build = nil // reminder: images with no build context are excluded
+				})
+
+				It("returns false", func() {
+					Expect(changed).To(BeFalse())
+				})
+			})
+		})
 	})
 
 	Describe("InjectProfiles", func() {