github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/tanka/find.go (about) 1 package tanka 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "runtime" 7 "time" 8 9 "github.com/grafana/tanka/pkg/jsonnet" 10 "github.com/grafana/tanka/pkg/jsonnet/jpath" 11 "github.com/grafana/tanka/pkg/spec/v1alpha1" 12 "github.com/pkg/errors" 13 "github.com/rs/zerolog/log" 14 "k8s.io/apimachinery/pkg/labels" 15 ) 16 17 // FindOpts are optional arguments for FindEnvs 18 type FindOpts struct { 19 JsonnetOpts 20 Selector labels.Selector 21 Parallelism int 22 } 23 24 // FindEnvs returns metadata of all environments recursively found in 'path'. 25 // Each directory is tested and included if it is a valid environment, either 26 // static or inline. If a directory is a valid environment, its subdirectories 27 // are not checked. 28 func FindEnvs(path string, opts FindOpts) ([]*v1alpha1.Environment, error) { 29 return findEnvsFromPaths([]string{path}, opts) 30 } 31 32 // FindEnvsFromPaths does the same as FindEnvs but takes a list of paths instead 33 func FindEnvsFromPaths(paths []string, opts FindOpts) ([]*v1alpha1.Environment, error) { 34 return findEnvsFromPaths(paths, opts) 35 } 36 37 func findEnvsFromPaths(paths []string, opts FindOpts) ([]*v1alpha1.Environment, error) { 38 if opts.Parallelism <= 0 { 39 opts.Parallelism = runtime.NumCPU() 40 } 41 42 log.Debug().Int("parallelism", opts.Parallelism).Int("paths", len(paths)).Msg("Finding Tanka environments") 43 startTime := time.Now() 44 45 jsonnetFiles, err := findJsonnetFilesFromPaths(paths, opts) 46 if err != nil { 47 return nil, fmt.Errorf("finding jsonnet files: %w", err) 48 } 49 50 findJsonnetFilesEndTime := time.Now() 51 52 envs, err := findEnvsFromJsonnetFiles(jsonnetFiles, opts) 53 if err != nil { 54 return nil, fmt.Errorf("finding environments: %w", err) 55 } 56 57 findEnvsEndTime := time.Now() 58 59 log.Info(). 60 Int("environments", len(envs)). 61 Dur("ms_to_find_jsonnet_files", findJsonnetFilesEndTime.Sub(startTime)). 62 Dur("ms_to_find_environments", findEnvsEndTime.Sub(findJsonnetFilesEndTime)). 63 Msg("Found Tanka environments") 64 65 return envs, nil 66 } 67 68 // find all jsonnet files within given paths 69 func findJsonnetFilesFromPaths(paths []string, opts FindOpts) ([]string, error) { 70 type findJsonnetFilesOut struct { 71 jsonnetFiles []string 72 err error 73 } 74 75 pathChan := make(chan string, len(paths)) 76 findJsonnetFilesChan := make(chan findJsonnetFilesOut) 77 for i := 0; i < opts.Parallelism; i++ { 78 go func() { 79 for path := range pathChan { 80 jsonnetFiles, err := jsonnet.FindFiles(path, nil) 81 var mainFiles []string 82 for _, file := range jsonnetFiles { 83 if filepath.Base(file) == jpath.DefaultEntrypoint { 84 mainFiles = append(mainFiles, file) 85 } 86 } 87 findJsonnetFilesChan <- findJsonnetFilesOut{jsonnetFiles: mainFiles, err: err} 88 } 89 }() 90 } 91 92 // push paths to channel 93 for _, path := range paths { 94 pathChan <- path 95 } 96 close(pathChan) 97 98 // collect jsonnet files 99 var jsonnetFiles []string 100 var errs []error 101 for range paths { 102 res := <-findJsonnetFilesChan 103 if res.err != nil { 104 errs = append(errs, res.err) 105 continue 106 } 107 jsonnetFiles = append(jsonnetFiles, res.jsonnetFiles...) 108 } 109 close(findJsonnetFilesChan) 110 111 if len(errs) != 0 { 112 return jsonnetFiles, ErrParallel{errors: errs} 113 } 114 115 return jsonnetFiles, nil 116 } 117 118 // find all environments within jsonnet files 119 func findEnvsFromJsonnetFiles(jsonnetFiles []string, opts FindOpts) ([]*v1alpha1.Environment, error) { 120 type findEnvsOut struct { 121 envs []*v1alpha1.Environment 122 err error 123 } 124 125 jsonnetFilesChan := make(chan string, len(jsonnetFiles)) 126 findEnvsChan := make(chan findEnvsOut) 127 128 for i := 0; i < opts.Parallelism; i++ { 129 go func() { 130 for jsonnetFile := range jsonnetFilesChan { 131 // try if this has envs 132 list, err := List(jsonnetFile, Opts{JsonnetOpts: opts.JsonnetOpts}) 133 if err != nil && 134 // expected when looking for environments 135 !errors.As(err, &jpath.ErrorNoBase{}) && 136 !errors.As(err, &jpath.ErrorFileNotFound{}) { 137 findEnvsChan <- findEnvsOut{err: fmt.Errorf("%s:\n %w", jsonnetFile, err)} 138 continue 139 } 140 filtered := []*v1alpha1.Environment{} 141 // optionally filter 142 if opts.Selector != nil && !opts.Selector.Empty() { 143 for _, e := range list { 144 if !opts.Selector.Matches(e.Metadata) { 145 continue 146 } 147 filtered = append(filtered, e) 148 } 149 } else { 150 filtered = append(filtered, list...) 151 } 152 findEnvsChan <- findEnvsOut{envs: filtered, err: nil} 153 } 154 }() 155 } 156 157 // push jsonnet files to channel 158 for _, jsonnetFile := range jsonnetFiles { 159 jsonnetFilesChan <- jsonnetFile 160 } 161 close(jsonnetFilesChan) 162 163 // collect environments 164 var envs []*v1alpha1.Environment 165 var errs []error 166 for i := 0; i < len(jsonnetFiles); i++ { 167 res := <-findEnvsChan 168 if res.err != nil { 169 errs = append(errs, res.err) 170 continue 171 } 172 envs = append(envs, res.envs...) 173 } 174 close(findEnvsChan) 175 176 if len(errs) != 0 { 177 return envs, ErrParallel{errors: errs} 178 } 179 180 return envs, nil 181 }