github.com/diggerhq/digger/libs@v0.0.0-20240604170430-9d61cdf01cc5/digger_config/digger_config.go (about)

     1  package digger_config
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"github.com/samber/lo"
     7  	"log"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/diggerhq/digger/libs/digger_config/terragrunt/atlantis"
    14  
    15  	"github.com/dominikbraun/graph"
    16  	"gopkg.in/yaml.v3"
    17  )
    18  
    19  type DirWalker interface {
    20  	GetDirs(workingDir string, config DiggerConfigYaml) ([]string, error)
    21  }
    22  
    23  type FileSystemTopLevelTerraformDirWalker struct {
    24  }
    25  
    26  type FileSystemTerragruntDirWalker struct {
    27  }
    28  
    29  type FileSystemModuleDirWalker struct {
    30  }
    31  
    32  func GetFilesWithExtension(workingDir string, ext string) ([]string, error) {
    33  	var files []string
    34  	listOfFiles, err := os.ReadDir(workingDir)
    35  	if err != nil {
    36  		return nil, errors.New(fmt.Sprintf("error reading directory %s: %v", workingDir, err))
    37  	}
    38  	for _, f := range listOfFiles {
    39  		if !f.IsDir() {
    40  			r, err := filepath.Match("*"+ext, f.Name())
    41  			if err == nil && r {
    42  				files = append(files, f.Name())
    43  			}
    44  		}
    45  	}
    46  
    47  	return files, nil
    48  }
    49  
    50  func (walker *FileSystemTopLevelTerraformDirWalker) GetDirs(workingDir string, configYaml *DiggerConfigYaml) ([]string, error) {
    51  	var dirs []string
    52  	err := filepath.Walk(workingDir,
    53  		func(path string, info os.FileInfo, err error) error {
    54  
    55  			if err != nil {
    56  				return err
    57  			}
    58  			if info.IsDir() {
    59  				if info.Name() == "modules" {
    60  					return filepath.SkipDir
    61  				}
    62  				terraformFiles, _ := GetFilesWithExtension(path, ".tf")
    63  				if len(terraformFiles) > 0 {
    64  					dirs = append(dirs, strings.ReplaceAll(path, workingDir+string(os.PathSeparator), ""))
    65  					if configYaml.TraverseToNestedProjects != nil && !*configYaml.TraverseToNestedProjects {
    66  						return filepath.SkipDir
    67  					}
    68  				}
    69  			}
    70  			return nil
    71  		})
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	return dirs, nil
    76  }
    77  
    78  func (walker *FileSystemModuleDirWalker) GetDirs(workingDir string, configYaml *DiggerConfigYaml) ([]string, error) {
    79  	var dirs []string
    80  	err := filepath.Walk(workingDir,
    81  		func(path string, info os.FileInfo, err error) error {
    82  
    83  			if err != nil {
    84  				return err
    85  			}
    86  			if info.IsDir() && info.Name() == "modules" {
    87  				dirs = append(dirs, strings.ReplaceAll(path, workingDir+string(os.PathSeparator), ""))
    88  				return filepath.SkipDir
    89  			}
    90  			return nil
    91  		})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return dirs, nil
    96  }
    97  
    98  func (walker *FileSystemTerragruntDirWalker) GetDirs(workingDir string, configYaml *DiggerConfigYaml) ([]string, error) {
    99  	var dirs []string
   100  	err := filepath.Walk(workingDir,
   101  		func(path string, info os.FileInfo, err error) error {
   102  
   103  			if err != nil {
   104  				return err
   105  			}
   106  			if info.IsDir() {
   107  				if info.Name() == "modules" {
   108  					return filepath.SkipDir
   109  				}
   110  				terragruntFiles, _ := GetFilesWithExtension(path, "terragrunt.hcl")
   111  				if len(terragruntFiles) > 0 {
   112  					for _, f := range terragruntFiles {
   113  						terragruntFile := path + string(os.PathSeparator) + f
   114  						fileContent, err := os.ReadFile(terragruntFile)
   115  						if err != nil {
   116  							return err
   117  						}
   118  						if strings.Contains(string(fileContent), "include \"root\"") {
   119  							dirs = append(dirs, strings.ReplaceAll(path, workingDir+string(os.PathSeparator), ""))
   120  							return filepath.SkipDir
   121  						}
   122  					}
   123  				}
   124  			}
   125  			return nil
   126  		})
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return dirs, nil
   131  }
   132  
   133  var ErrDiggerConfigConflict = errors.New("more than one digger digger_config file detected, please keep either 'digger.yml' or 'digger.yaml'")
   134  
   135  func LoadDiggerConfig(workingDir string, generateProjects bool) (*DiggerConfig, *DiggerConfigYaml, graph.Graph[string, Project], error) {
   136  	config := &DiggerConfig{}
   137  	configYaml, err := LoadDiggerConfigYaml(workingDir, generateProjects)
   138  	if err != nil {
   139  		return nil, nil, nil, err
   140  	}
   141  
   142  	config, projectDependencyGraph, err := ConvertDiggerYamlToConfig(configYaml)
   143  	if err != nil {
   144  		return nil, nil, nil, err
   145  	}
   146  
   147  	err = ValidateDiggerConfig(config)
   148  	if err != nil {
   149  		return config, configYaml, projectDependencyGraph, err
   150  	}
   151  	return config, configYaml, projectDependencyGraph, nil
   152  }
   153  
   154  func LoadDiggerConfigFromString(yamlString string, terraformDir string) (*DiggerConfig, *DiggerConfigYaml, graph.Graph[string, Project], error) {
   155  	config := &DiggerConfig{}
   156  	configYaml, err := LoadDiggerConfigYamlFromString(yamlString)
   157  	if err != nil {
   158  		return nil, nil, nil, err
   159  	}
   160  
   161  	err = ValidateDiggerConfigYaml(configYaml, "loaded_yaml_string")
   162  	if err != nil {
   163  		return nil, nil, nil, err
   164  	}
   165  
   166  	err = HandleYamlProjectGeneration(configYaml, terraformDir)
   167  	if err != nil {
   168  		return nil, nil, nil, err
   169  	}
   170  
   171  	config, projectDependencyGraph, err := ConvertDiggerYamlToConfig(configYaml)
   172  	if err != nil {
   173  		return nil, nil, nil, err
   174  	}
   175  
   176  	err = ValidateDiggerConfig(config)
   177  	if err != nil {
   178  		return config, configYaml, projectDependencyGraph, err
   179  	}
   180  	return config, configYaml, projectDependencyGraph, nil
   181  }
   182  
   183  func LoadDiggerConfigYamlFromString(yamlString string) (*DiggerConfigYaml, error) {
   184  	configYaml := &DiggerConfigYaml{}
   185  	if err := yaml.Unmarshal([]byte(yamlString), configYaml); err != nil {
   186  		return nil, fmt.Errorf("error parsing yaml: %v", err)
   187  	}
   188  
   189  	return configYaml, nil
   190  }
   191  
   192  func HandleYamlProjectGeneration(config *DiggerConfigYaml, terraformDir string) error {
   193  	if config.GenerateProjectsConfig != nil && config.GenerateProjectsConfig.TerragruntParsingConfig != nil {
   194  		err := hydrateDiggerConfigYamlWithTerragrunt(config, *config.GenerateProjectsConfig.TerragruntParsingConfig, terraformDir)
   195  		if err != nil {
   196  			return err
   197  		}
   198  	} else if config.GenerateProjectsConfig != nil && config.GenerateProjectsConfig.Terragrunt {
   199  		err := hydrateDiggerConfigYamlWithTerragrunt(config, TerragruntParsingConfig{}, terraformDir)
   200  		if err != nil {
   201  			return err
   202  		}
   203  	} else if config.GenerateProjectsConfig != nil {
   204  		var dirWalker = &FileSystemTopLevelTerraformDirWalker{}
   205  		dirs, err := dirWalker.GetDirs(terraformDir, config)
   206  
   207  		if err != nil {
   208  			fmt.Printf("Error while walking through directories: %v", err)
   209  		}
   210  
   211  		var includePatterns []string
   212  		var excludePatterns []string
   213  		if config.GenerateProjectsConfig.Include != "" || config.GenerateProjectsConfig.Exclude != "" {
   214  			includePatterns = []string{config.GenerateProjectsConfig.Include}
   215  			excludePatterns = []string{config.GenerateProjectsConfig.Exclude}
   216  			for _, dir := range dirs {
   217  				if MatchIncludeExcludePatternsToFile(dir, includePatterns, excludePatterns) {
   218  					projectName := strings.ReplaceAll(dir, "/", "_")
   219  					project := ProjectYaml{Name: projectName, Dir: dir, Workflow: defaultWorkflowName, Workspace: "default", AwsRoleToAssume: config.GenerateProjectsConfig.AwsRoleToAssume}
   220  					config.Projects = append(config.Projects, &project)
   221  				}
   222  			}
   223  		}
   224  		if config.GenerateProjectsConfig.Blocks != nil && len(config.GenerateProjectsConfig.Blocks) > 0 {
   225  			// if blocks of include/exclude patterns defined
   226  			for _, b := range config.GenerateProjectsConfig.Blocks {
   227  				includePatterns = []string{b.Include}
   228  				excludePatterns = []string{b.Exclude}
   229  				workflow := "default"
   230  				if b.Workflow != "" {
   231  					workflow = b.Workflow
   232  				}
   233  
   234  				for _, dir := range dirs {
   235  					if MatchIncludeExcludePatternsToFile(dir, includePatterns, excludePatterns) {
   236  						projectName := strings.ReplaceAll(dir, "/", "_")
   237  						project := ProjectYaml{Name: projectName, Dir: dir, Workflow: workflow, Workspace: "default", AwsRoleToAssume: b.AwsRoleToAssume}
   238  						config.Projects = append(config.Projects, &project)
   239  					}
   240  				}
   241  			}
   242  		}
   243  	}
   244  	return nil
   245  }
   246  
   247  func LoadDiggerConfigYaml(workingDir string, generateProjects bool) (*DiggerConfigYaml, error) {
   248  	configYaml := &DiggerConfigYaml{}
   249  	fileName, err := retrieveConfigFile(workingDir)
   250  	if err != nil {
   251  		if errors.Is(err, ErrDiggerConfigConflict) {
   252  			return nil, fmt.Errorf("error while retrieving digger_config file: %v", err)
   253  		}
   254  	}
   255  
   256  	if fileName == "" {
   257  		configYaml, err = AutoDetectDiggerConfig(workingDir)
   258  		if err != nil {
   259  			return nil, fmt.Errorf("failed to auto detect digger digger_config: %v", err)
   260  		}
   261  		marshalledConfig, err := yaml.Marshal(configYaml)
   262  		if err != nil {
   263  			log.Printf("failed to marshal auto detected digger digger_config: %v", err)
   264  		} else {
   265  			log.Printf("Auto detected digger digger_config: \n%v", string(marshalledConfig))
   266  		}
   267  	} else {
   268  		data, err := os.ReadFile(fileName)
   269  		if err != nil {
   270  			return nil, fmt.Errorf("failed to read digger_config file %s: %v", fileName, err)
   271  		}
   272  
   273  		if err := yaml.Unmarshal(data, configYaml); err != nil {
   274  			return nil, fmt.Errorf("error parsing '%s': %v", fileName, err)
   275  		}
   276  	}
   277  
   278  	err = ValidateDiggerConfigYaml(configYaml, fileName)
   279  	if err != nil {
   280  		return configYaml, err
   281  	}
   282  
   283  	if generateProjects == true {
   284  		err = HandleYamlProjectGeneration(configYaml, workingDir)
   285  		if err != nil {
   286  			return configYaml, err
   287  		}
   288  	}
   289  
   290  	return configYaml, nil
   291  }
   292  
   293  func ValidateDiggerConfigYaml(configYaml *DiggerConfigYaml, fileName string) error {
   294  	if (configYaml.Projects == nil || len(configYaml.Projects) == 0) && configYaml.GenerateProjectsConfig == nil {
   295  		return fmt.Errorf("no projects digger_config found in '%s'", fileName)
   296  	}
   297  	if configYaml.DependencyConfiguration != nil {
   298  		if configYaml.DependencyConfiguration.Mode != DependencyConfigurationHard && configYaml.DependencyConfiguration.Mode != DependencyConfigurationSoft {
   299  			return fmt.Errorf("dependency digger_config mode can only be '%s' or '%s'", DependencyConfigurationHard, DependencyConfigurationSoft)
   300  		}
   301  	}
   302  
   303  	if configYaml.GenerateProjectsConfig != nil {
   304  		if configYaml.GenerateProjectsConfig.Include != "" &&
   305  			configYaml.GenerateProjectsConfig.Exclude != "" &&
   306  			len(configYaml.GenerateProjectsConfig.Blocks) != 0 {
   307  			return fmt.Errorf("if include/exclude patterns are used for project generation, blocks of include/exclude can't be used")
   308  		}
   309  	}
   310  
   311  	return nil
   312  }
   313  
   314  func ValidateDiggerConfig(config *DiggerConfig) error {
   315  
   316  	if config.CommentRenderMode != CommentRenderModeBasic && config.CommentRenderMode != CommentRenderModeGroupByModule {
   317  		return fmt.Errorf("invalid value for comment_render_mode, %v expecting %v, %v", config.CommentRenderMode, CommentRenderModeBasic, CommentRenderModeGroupByModule)
   318  	}
   319  
   320  	for _, p := range config.Projects {
   321  		_, ok := config.Workflows[p.Workflow]
   322  		if !ok {
   323  			return fmt.Errorf("failed to find workflow digger_config '%s' for project '%s'", p.Workflow, p.Name)
   324  		}
   325  	}
   326  
   327  	for _, w := range config.Workflows {
   328  		for _, s := range w.Plan.Steps {
   329  			if s.Action == "" {
   330  				return fmt.Errorf("plan step's action can't be empty")
   331  			}
   332  		}
   333  	}
   334  
   335  	for _, w := range config.Workflows {
   336  		for _, s := range w.Apply.Steps {
   337  			if s.Action == "" {
   338  				return fmt.Errorf("apply step's action can't be empty")
   339  			}
   340  		}
   341  	}
   342  	return nil
   343  }
   344  
   345  func hydrateDiggerConfigYamlWithTerragrunt(configYaml *DiggerConfigYaml, parsingConfig TerragruntParsingConfig, workingDir string) error {
   346  	root := workingDir
   347  	if parsingConfig.GitRoot != nil {
   348  		root = path.Join(workingDir, *parsingConfig.GitRoot)
   349  	}
   350  	projectExternalChilds := true
   351  
   352  	if parsingConfig.CreateHclProjectExternalChilds != nil {
   353  		projectExternalChilds = *parsingConfig.CreateHclProjectExternalChilds
   354  	}
   355  
   356  	parallel := true
   357  	if parsingConfig.Parallel != nil {
   358  		parallel = *parsingConfig.Parallel
   359  	}
   360  
   361  	ignoreParentTerragrunt := true
   362  	if parsingConfig.IgnoreParentTerragrunt != nil {
   363  		ignoreParentTerragrunt = *parsingConfig.IgnoreParentTerragrunt
   364  	}
   365  
   366  	cascadeDependencies := true
   367  	if parsingConfig.CascadeDependencies != nil {
   368  		cascadeDependencies = *parsingConfig.CascadeDependencies
   369  	}
   370  
   371  	executionOrderGroups := false
   372  	if parsingConfig.ExecutionOrderGroups != nil {
   373  		executionOrderGroups = *parsingConfig.ExecutionOrderGroups
   374  	}
   375  
   376  	atlantisConfig, _, err := atlantis.Parse(
   377  		root,
   378  		parsingConfig.ProjectHclFiles,
   379  		projectExternalChilds,
   380  		parsingConfig.AutoMerge,
   381  		parallel,
   382  		parsingConfig.FilterPath,
   383  		parsingConfig.CreateHclProjectChilds,
   384  		ignoreParentTerragrunt,
   385  		parsingConfig.IgnoreDependencyBlocks,
   386  		cascadeDependencies,
   387  		parsingConfig.DefaultWorkflow,
   388  		parsingConfig.DefaultApplyRequirements,
   389  		parsingConfig.AutoPlan,
   390  		parsingConfig.DefaultTerraformVersion,
   391  		parsingConfig.CreateProjectName,
   392  		parsingConfig.CreateWorkspace,
   393  		parsingConfig.PreserveProjects,
   394  		parsingConfig.UseProjectMarkers,
   395  		executionOrderGroups,
   396  	)
   397  	if err != nil {
   398  		return fmt.Errorf("failed to autogenerate digger_config, error during parse: %v", err)
   399  	}
   400  
   401  	if err != nil {
   402  		log.Printf("failed to autogenerate digger_config: %v", err)
   403  	}
   404  
   405  	if atlantisConfig.Projects == nil {
   406  		return fmt.Errorf("atlantisConfig.Projects is nil")
   407  	}
   408  
   409  	configYaml.AutoMerge = &atlantisConfig.AutoMerge
   410  
   411  	pathPrefix := ""
   412  	if parsingConfig.GitRoot != nil {
   413  		pathPrefix = *parsingConfig.GitRoot
   414  	}
   415  
   416  	for _, atlantisProject := range atlantisConfig.Projects {
   417  
   418  		// normalize paths
   419  		projectDir := path.Join(pathPrefix, atlantisProject.Dir)
   420  		atlantisProject.Autoplan.WhenModified, err = GetPatternsRelativeToRepo(projectDir, atlantisProject.Autoplan.WhenModified)
   421  		if err != nil {
   422  			return fmt.Errorf("could not normalize patterns: %v", err)
   423  		}
   424  
   425  		configYaml.Projects = append(configYaml.Projects, &ProjectYaml{
   426  			Name:            atlantisProject.Name,
   427  			Dir:             projectDir,
   428  			Workspace:       atlantisProject.Workspace,
   429  			Terragrunt:      true,
   430  			Workflow:        atlantisProject.Workflow,
   431  			IncludePatterns: atlantisProject.Autoplan.WhenModified,
   432  		})
   433  	}
   434  	return nil
   435  }
   436  
   437  func AutoDetectDiggerConfig(workingDir string) (*DiggerConfigYaml, error) {
   438  	configYaml := &DiggerConfigYaml{}
   439  	telemetry := true
   440  	configYaml.Telemetry = &telemetry
   441  
   442  	TraverseToNestedProjects := false
   443  	configYaml.TraverseToNestedProjects = &TraverseToNestedProjects
   444  
   445  	AllowDraftPRs := false
   446  	configYaml.AllowDraftPRs = &AllowDraftPRs
   447  
   448  	terragruntDirWalker := &FileSystemTerragruntDirWalker{}
   449  	terraformDirWalker := &FileSystemTopLevelTerraformDirWalker{}
   450  	moduleDirWalker := &FileSystemModuleDirWalker{}
   451  
   452  	terragruntDirs, err := terragruntDirWalker.GetDirs(workingDir, configYaml)
   453  
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  
   458  	terraformDirs, err := terraformDirWalker.GetDirs(workingDir, configYaml)
   459  	if err != nil {
   460  		return nil, err
   461  	}
   462  
   463  	moduleDirs, err := moduleDirWalker.GetDirs(workingDir, configYaml)
   464  
   465  	var modulePatterns []string
   466  	for _, dir := range moduleDirs {
   467  		modulePatterns = append(modulePatterns, dir+"/**")
   468  	}
   469  
   470  	if err != nil {
   471  		return nil, err
   472  	}
   473  	if len(terragruntDirs) > 0 {
   474  		configYaml.GenerateProjectsConfig = &GenerateProjectsConfigYaml{
   475  			Terragrunt: true,
   476  		}
   477  		return configYaml, nil
   478  	} else if len(terraformDirs) > 0 {
   479  		for _, dir := range terraformDirs {
   480  			var projectName string
   481  			if dir == "./" {
   482  				projectName = "default"
   483  			} else {
   484  				projectName = strings.ReplaceAll(dir, "/", "_")
   485  			}
   486  			project := ProjectYaml{Name: projectName, Dir: dir, Workflow: defaultWorkflowName, Workspace: "default", Terragrunt: false, IncludePatterns: modulePatterns}
   487  			configYaml.Projects = append(configYaml.Projects, &project)
   488  		}
   489  		return configYaml, nil
   490  	} else {
   491  		return nil, fmt.Errorf("no terragrunt or terraform project detected in the repository")
   492  	}
   493  }
   494  
   495  func (c *DiggerConfig) GetProject(projectName string) *Project {
   496  	for _, project := range c.Projects {
   497  		if projectName == project.Name {
   498  			return &project
   499  		}
   500  	}
   501  	return nil
   502  }
   503  
   504  func (c *DiggerConfig) GetProjects(projectName string) []Project {
   505  	if projectName == "" {
   506  		return c.Projects
   507  	}
   508  	project := c.GetProject(projectName)
   509  	if project == nil {
   510  		return nil
   511  	}
   512  	return []Project{*project}
   513  }
   514  
   515  type ProjectToSourceMapping struct {
   516  	ImpactingLocations []string          `json:"impacting_locations"`
   517  	CommentIds         map[string]string `json:"comment_ids"` // impactingLocation => PR commentId
   518  }
   519  
   520  func (c *DiggerConfig) GetModifiedProjects(changedFiles []string) ([]Project, map[string]ProjectToSourceMapping) {
   521  	var result []Project
   522  	mapping := make(map[string]ProjectToSourceMapping)
   523  	for _, project := range c.Projects {
   524  		sourceChangesForProject := make([]string, 0)
   525  		isProjectAdded := false
   526  		for _, changedFile := range changedFiles {
   527  			includePatterns := project.IncludePatterns
   528  			excludePatterns := project.ExcludePatterns
   529  			if !project.Terragrunt {
   530  				includePatterns = append(includePatterns, filepath.Join(project.Dir, "**", "*"))
   531  			} else {
   532  				includePatterns = append(includePatterns, filepath.Join(project.Dir, "*"))
   533  			}
   534  			// all our patterns are the globale dir pattern + the include patterns specified by user
   535  			if MatchIncludeExcludePatternsToFile(changedFile, includePatterns, excludePatterns) {
   536  				if !isProjectAdded {
   537  					result = append(result, project)
   538  					isProjectAdded = true
   539  				}
   540  				changedDir := filepath.Dir(changedFile)
   541  				if !lo.Contains(sourceChangesForProject, changedDir) {
   542  					sourceChangesForProject = append(sourceChangesForProject, changedDir)
   543  				}
   544  			}
   545  		}
   546  		mapping[project.Name] = ProjectToSourceMapping{
   547  			ImpactingLocations: sourceChangesForProject,
   548  		}
   549  	}
   550  	return result, mapping
   551  }
   552  
   553  func (c *DiggerConfig) GetDirectory(projectName string) string {
   554  	project := c.GetProject(projectName)
   555  	if project == nil {
   556  		return ""
   557  	}
   558  	return project.Dir
   559  }
   560  
   561  func (c *DiggerConfig) GetWorkflow(workflowName string) *Workflow {
   562  	workflows := c.Workflows
   563  
   564  	workflow, ok := workflows[workflowName]
   565  	if !ok {
   566  		return nil
   567  	}
   568  	return &workflow
   569  
   570  }
   571  
   572  type File struct {
   573  	Filename string
   574  }
   575  
   576  func isFileExists(path string) bool {
   577  	fi, err := os.Stat(path)
   578  	if os.IsNotExist(err) {
   579  		return false
   580  	}
   581  	// file exists make sure it's not a directory
   582  	return !fi.IsDir()
   583  }
   584  
   585  func retrieveConfigFile(workingDir string) (string, error) {
   586  	var fileName string = "digger"
   587  	customConfigFile := os.Getenv("DIGGER_FILENAME") != ""
   588  
   589  	if customConfigFile {
   590  		fileName = os.Getenv("DIGGER_FILENAME")
   591  	}
   592  
   593  	if workingDir != "" {
   594  		fileName = path.Join(workingDir, fileName)
   595  	}
   596  
   597  	if !customConfigFile {
   598  		// Make sure we don't have more than one digger digger_config file
   599  		ymlCfg := fileName + ".yml"
   600  		yamlCfg := fileName + ".yaml"
   601  		ymlCfgExists := isFileExists(ymlCfg)
   602  		yamlCfgExists := isFileExists(yamlCfg)
   603  
   604  		if ymlCfgExists && yamlCfgExists {
   605  			return "", ErrDiggerConfigConflict
   606  		} else if ymlCfgExists {
   607  			return ymlCfg, nil
   608  		} else if yamlCfgExists {
   609  			return yamlCfg, nil
   610  		}
   611  	} else {
   612  		return fileName, nil
   613  	}
   614  
   615  	// Passing this point means digger digger_config file is
   616  	// missing which is a non-error
   617  	return "", nil
   618  }
   619  
   620  func CollectTerraformEnvConfig(envs *TerraformEnvConfig) (map[string]string, map[string]string) {
   621  	stateEnvVars := map[string]string{}
   622  	commandEnvVars := map[string]string{}
   623  
   624  	if envs != nil {
   625  		for _, envvar := range envs.State {
   626  			if envvar.Value != "" {
   627  				stateEnvVars[envvar.Name] = envvar.Value
   628  			} else if envvar.ValueFrom != "" {
   629  				stateEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
   630  			}
   631  		}
   632  
   633  		for _, envvar := range envs.Commands {
   634  			if envvar.Value != "" {
   635  				commandEnvVars[envvar.Name] = envvar.Value
   636  			} else if envvar.ValueFrom != "" {
   637  				commandEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
   638  			}
   639  		}
   640  	}
   641  
   642  	return stateEnvVars, commandEnvVars
   643  }