github.com/databricks/cli@v0.203.0/bundle/config/mutator/process_root_includes.go (about)

     1  package mutator
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/databricks/cli/bundle"
    11  	"github.com/databricks/cli/bundle/config"
    12  	"golang.org/x/exp/slices"
    13  )
    14  
    15  // Get extra include paths from environment variable
    16  func GetExtraIncludePaths() []string {
    17  	value, exists := os.LookupEnv(bundle.ExtraIncludePathsKey)
    18  	if !exists {
    19  		return nil
    20  	}
    21  	return strings.Split(value, string(os.PathListSeparator))
    22  }
    23  
    24  type processRootIncludes struct{}
    25  
    26  // ProcessRootIncludes expands the patterns in the configuration's include list
    27  // into a list of mutators for each matching file.
    28  func ProcessRootIncludes() bundle.Mutator {
    29  	return &processRootIncludes{}
    30  }
    31  
    32  func (m *processRootIncludes) Name() string {
    33  	return "ProcessRootIncludes"
    34  }
    35  
    36  func (m *processRootIncludes) Apply(ctx context.Context, b *bundle.Bundle) error {
    37  	var out []bundle.Mutator
    38  
    39  	// Map with files we've already seen to avoid loading them twice.
    40  	var seen = map[string]bool{}
    41  
    42  	for _, file := range config.FileNames {
    43  		seen[file] = true
    44  	}
    45  
    46  	// Maintain list of files in order of files being loaded.
    47  	// This is stored in the bundle configuration for observability.
    48  	var files []string
    49  
    50  	// Converts extra include paths from environment variable to relative paths
    51  	for _, extraIncludePath := range GetExtraIncludePaths() {
    52  		if filepath.IsAbs(extraIncludePath) {
    53  			rel, err := filepath.Rel(b.Config.Path, extraIncludePath)
    54  			if err != nil {
    55  				return fmt.Errorf("unable to include file '%s': %w", extraIncludePath, err)
    56  			}
    57  			extraIncludePath = rel
    58  		}
    59  		b.Config.Include = append(b.Config.Include, extraIncludePath)
    60  	}
    61  
    62  	// For each glob, find all files to load.
    63  	// Ordering of the list of globs is maintained in the output.
    64  	// For matches that appear in multiple globs, only the first is kept.
    65  	for _, entry := range b.Config.Include {
    66  		// Include paths must be relative.
    67  		if filepath.IsAbs(entry) {
    68  			return fmt.Errorf("%s: includes must be relative paths", entry)
    69  		}
    70  
    71  		// Anchor includes to the bundle root path.
    72  		matches, err := filepath.Glob(filepath.Join(b.Config.Path, entry))
    73  		if err != nil {
    74  			return err
    75  		}
    76  
    77  		// If the entry is not a glob pattern and no matches found,
    78  		// return an error because the file defined is not found
    79  		if len(matches) == 0 && !strings.ContainsAny(entry, "*?[") {
    80  			return fmt.Errorf("%s defined in 'include' section does not match any files", entry)
    81  		}
    82  
    83  		// Filter matches to ones we haven't seen yet.
    84  		var includes []string
    85  		for _, match := range matches {
    86  			rel, err := filepath.Rel(b.Config.Path, match)
    87  			if err != nil {
    88  				return err
    89  			}
    90  			if _, ok := seen[rel]; ok {
    91  				continue
    92  			}
    93  			seen[rel] = true
    94  			includes = append(includes, rel)
    95  		}
    96  
    97  		// Add matches to list of mutators to return.
    98  		slices.Sort(includes)
    99  		files = append(files, includes...)
   100  		for _, include := range includes {
   101  			out = append(out, ProcessInclude(filepath.Join(b.Config.Path, include), include))
   102  		}
   103  	}
   104  
   105  	// Swap out the original includes list with the expanded globs.
   106  	b.Config.Include = files
   107  
   108  	return bundle.Apply(ctx, b, bundle.Seq(out...))
   109  }