Repository: diggerhq/digger (go)

Issue:
Support digger.yaml along with digger.yml 
We currently read a configuration file for specifying config options. This configuration file is specified directly in the repository. We only support digger.yml at the moment, we want to also support digger.yaml along with it if it exists.

If both files exists then its good to throw an error saying that you are not sure which one to chose, please delete one of the files.

Pull request:
Support both digger.[yml | yaml]
### Changelog
Fixes #60 


#### Fix details
* Added support for both digger.yaml & digger.yml config files
* Added digger config file conflict detection
* Added a specific error for  when multiple DiggerConfig are detected
* Added tests to cover multiple digger config files case
* Added tests to validate `digger.yaml` is now recognized as a legit config file
* Fixed a test message error

#### Refactor
* Removed nested conditions from `NewDiggerConfig` function
* Removed else conditions from `NewDiggerConfig` function
* Created SetUp & TearDown test functions
* Removed path concatenation, and used `path.Join` instead

Patch:
diff --git a/pkg/digger/digger_config.go b/pkg/digger/digger_config.go
index 96cfbec5..452bdb25 100644
--- a/pkg/digger/digger_config.go
+++ b/pkg/digger/digger_config.go
@@ -1,12 +1,14 @@
 package digger
 
 import (
+	"errors"
 	"fmt"
-	"gopkg.in/yaml.v2"
 	"os"
 	"path"
 	"path/filepath"
 	"strings"
+
+	"gopkg.in/yaml.v2"
 )
 
 type WorkflowConfiguration struct {
@@ -25,6 +27,8 @@ type Project struct {
 	WorkflowConfiguration WorkflowConfiguration `yaml:"workflow_configuration"`
 }
 
+var ErrDiggerConfigConflict error = errors.New("more than one digger config file detected, please keep either 'digger.yml' or 'digger.yaml'")
+
 func (p *Project) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	type rawProject Project
 	raw := rawProject{
@@ -44,17 +48,15 @@ func (p *Project) UnmarshalYAML(unmarshal func(interface{}) error) error {
 
 func NewDiggerConfig(workingDir string) (*DiggerConfig, error) {
 	config := &DiggerConfig{}
-	var fileName string
-	if workingDir == "" {
-		fileName = "digger.yml"
-	} else {
-		fileName = workingDir + "/digger.yml"
-	}
-	if data, err := os.ReadFile(fileName); err == nil {
-		if err := yaml.Unmarshal(data, config); err != nil {
-			return nil, fmt.Errorf("error parsing digger.yml: %v", err)
+	fileName, err := retrieveConfigFile(workingDir)
+	if err != nil {
+		if errors.Is(err, ErrDiggerConfigConflict) {
+			return nil, fmt.Errorf("error while retrieving config file: %v", err)
 		}
-	} else {
+	}
+
+	data, err := os.ReadFile(fileName)
+	if err != nil {
 		config.Projects = make([]Project, 1)
 		config.Projects[0] = Project{Name: "default", Dir: ".", WorkflowConfiguration: WorkflowConfiguration{
 			OnPullRequestPushed: []string{"digger plan"},
@@ -63,6 +65,11 @@ func NewDiggerConfig(workingDir string) (*DiggerConfig, error) {
 		}}
 		return config, nil
 	}
+
+	if err := yaml.Unmarshal(data, config); err != nil {
+		return nil, fmt.Errorf("error parsing '%s': %v", fileName, err)
+	}
+
 	return config, nil
 }
 
@@ -120,3 +127,39 @@ func (c *DiggerConfig) GetWorkflowConfiguration(projectName string) WorkflowConf
 type File struct {
 	Filename string
 }
+
+func isFileExists(path string) bool {
+	fi, err := os.Stat(path)
+	if os.IsNotExist(err) {
+		return false
+	}
+	// file exists make sure it's not a directory
+	return !fi.IsDir()
+}
+
+func retrieveConfigFile(workingDir string) (string, error) {
+	fileName := "digger"
+	if workingDir != "" {
+		fileName = path.Join(workingDir, fileName)
+	}
+
+	// Make sure we don't have more than one digger config file
+	ymlCfg := isFileExists(fileName + ".yml")
+	yamlCfg := isFileExists(fileName + ".yaml")
+	if ymlCfg && yamlCfg {
+		return "", ErrDiggerConfigConflict
+	}
+
+	// At this point we know there are no duplicates
+	// Return the first one that exists
+	if ymlCfg {
+		return "digger.yml", nil
+	}
+	if yamlCfg {
+		return "digger.yaml", nil
+	}
+
+	// Passing this point means digger config file is
+	// missing which is a non-error
+	return "", nil
+}


Test patch:
diff --git a/pkg/digger/digger_config_test.go b/pkg/digger/digger_config_test.go
index f2363b12..44581b8d 100644
--- a/pkg/digger/digger_config_test.go
+++ b/pkg/digger/digger_config_test.go
@@ -3,9 +3,18 @@ package digger
 import (
 	"log"
 	"os"
+	"path"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
-import "github.com/stretchr/testify/assert"
+
+func setUp() (string, func()) {
+	tempDir := createTempDir()
+	return tempDir, func() {
+		deleteTempDir(tempDir)
+	}
+}
 
 func TestDiggerConfigFileDoesNotExist(t *testing.T) {
 	dg, err := NewDiggerConfig("")
@@ -14,16 +23,59 @@ func TestDiggerConfigFileDoesNotExist(t *testing.T) {
 	assert.Equal(t, dg.Projects[0].Dir, ".", "expected default project dir to be '.'")
 }
 
+func TestDiggerConfigWhenMultipleConfigExist(t *testing.T) {
+	tempDir, teardown := setUp()
+	defer teardown()
+
+	_, err := os.Create(path.Join(tempDir, "digger.yaml"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	_, err = os.Create(path.Join(tempDir, "digger.yml"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dg, err := NewDiggerConfig(tempDir)
+	assert.Error(t, err, "expected error to be returned")
+	assert.ErrorContains(t, err, ErrDiggerConfigConflict.Error(), "expected error to match target error")
+	assert.Nil(t, dg, "expected diggerConfig to be nil")
+}
+
+func TestDiggerConfigWhenOnlyYamlExists(t *testing.T) {
+	tempDir, teardown := setUp()
+	defer teardown()
+
+	_, err := os.Create(path.Join(tempDir, "digger.yaml"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dg, err := NewDiggerConfig(tempDir)
+	assert.NoError(t, err, "expected error to be nil")
+	assert.NotNil(t, dg, "expected digger config to be not nil")
+}
+
+func TestDiggerConfigWhenOnlyYmlExists(t *testing.T) {
+	tempDir, teardown := setUp()
+	defer teardown()
+
+	_, err := os.Create(path.Join(tempDir, "digger.yml"))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	dg, err := NewDiggerConfig(tempDir)
+	assert.NoError(t, err, "expected error to be nil")
+	assert.NotNil(t, dg, "expected digger config to be not nil")
+}
+
 func TestDefaultValuesForWorkflowConfiguration(t *testing.T) {
-	tempDir := CreateTempDir()
-	defer func(name string) {
-		err := os.RemoveAll(name)
-		if err != nil {
-			log.Fatal(err)
-		}
-	}(tempDir)
+	tempDir, teardown := setUp()
+	defer teardown()
 
-	f, err := os.Create(tempDir + "/digger.yml")
+	f, err := os.Create(path.Join(tempDir, "digger.yaml"))
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -48,16 +100,23 @@ projects:
 	}
 
 	dg, err := NewDiggerConfig(tempDir)
-	assert.NoError(t, err, "expected error to be not nil")
+	assert.NoError(t, err, "expected error to be nil")
 	assert.Equal(t, dg.Projects[0].WorkflowConfiguration.OnPullRequestPushed[0], "digger plan")
 	assert.Equal(t, dg.Projects[0].WorkflowConfiguration.OnPullRequestClosed[0], "digger unlock")
 	assert.Equal(t, dg.Projects[0].WorkflowConfiguration.OnCommitToDefault[0], "digger apply")
 }
 
-func CreateTempDir() string {
+func createTempDir() string {
 	dir, err := os.MkdirTemp("", "tmp")
 	if err != nil {
 		log.Fatal(err)
 	}
 	return dir
 }
+
+func deleteTempDir(name string) {
+	err := os.RemoveAll(name)
+	if err != nil {
+		log.Fatal(err)
+	}
+}