github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/initializer/analyze/analyze.go (about) 1 /* 2 Copyright 2020 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package analyze 18 19 import ( 20 "context" 21 "os" 22 "path/filepath" 23 "sort" 24 "strings" 25 26 "github.com/karrick/godirwalk" 27 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/build" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/config" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 32 ) 33 34 // analyzer is following the visitor pattern. It is called on every file 35 // as the analysis.analyze function walks the directory structure recursively. 36 // It can manage state and react to walking events assuming a breadth first search. 37 type analyzer interface { 38 enterDir(dir string) 39 analyzeFile(ctx context.Context, file string) error 40 exitDir(dir string) 41 } 42 43 type ProjectAnalysis struct { 44 configAnalyzer *skaffoldConfigAnalyzer 45 kubeAnalyzer *kubeAnalyzer 46 kustomizeAnalyzer *kustomizeAnalyzer 47 helmAnalyzer *helmAnalyzer 48 builderAnalyzer *builderAnalyzer 49 maxFileSize int64 50 } 51 52 func (a *ProjectAnalysis) Builders() []build.InitBuilder { 53 return a.builderAnalyzer.foundBuilders 54 } 55 56 func (a *ProjectAnalysis) Manifests() []string { 57 return a.kubeAnalyzer.kubernetesManifests 58 } 59 60 func (a *ProjectAnalysis) KustomizePaths() []string { 61 return a.kustomizeAnalyzer.kustomizePaths 62 } 63 64 func (a *ProjectAnalysis) KustomizeBases() []string { 65 return a.kustomizeAnalyzer.bases 66 } 67 68 func (a *ProjectAnalysis) ChartPaths() []string { 69 return a.helmAnalyzer.chartPaths 70 } 71 72 func (a *ProjectAnalysis) analyzers() []analyzer { 73 return []analyzer{ 74 a.kubeAnalyzer, 75 a.kustomizeAnalyzer, 76 a.helmAnalyzer, 77 a.configAnalyzer, 78 a.builderAnalyzer, 79 } 80 } 81 82 // NewAnalyzer sets up the analysis of the directory based on the initializer configuration 83 func NewAnalyzer(c config.Config) *ProjectAnalysis { 84 return &ProjectAnalysis{ 85 kubeAnalyzer: &kubeAnalyzer{}, 86 kustomizeAnalyzer: &kustomizeAnalyzer{}, 87 helmAnalyzer: &helmAnalyzer{}, 88 builderAnalyzer: &builderAnalyzer{ 89 findBuilders: !c.SkipBuild, 90 enableJibInit: c.EnableJibInit, 91 enableJibGradleInit: c.EnableJibGradleInit, 92 enableBuildpacksInit: c.EnableBuildpacksInit, 93 buildpacksBuilder: c.BuildpacksBuilder, 94 }, 95 configAnalyzer: &skaffoldConfigAnalyzer{ 96 force: c.Force, 97 analyzeMode: c.Analyze, 98 targetConfig: c.Opts.ConfigurationFile, 99 }, 100 maxFileSize: c.MaxFileSize, 101 } 102 } 103 104 // Analyze recursively walks a directory and notifies the analyzers of files and enterDir and exitDir events 105 // at the end of the analyze function the analysis struct's analyzers should contain the state that we can 106 // use to do further computation. 107 func (a *ProjectAnalysis) Analyze(dir string) error { 108 for _, analyzer := range a.analyzers() { 109 analyzer.enterDir(dir) 110 } 111 112 dirents, err := godirwalk.ReadDirents(dir, nil) 113 if err != nil { 114 return err 115 } 116 117 // This is for deterministic results - given the same directory structure 118 // init should have the same results. 119 sort.Sort(dirents) 120 121 var subdirectories []string 122 123 // Traverse files 124 for _, file := range dirents { 125 name := file.Name() 126 127 if file.IsDir() { 128 if util.IsHiddenDir(name) || skipFolder(name) { 129 continue 130 } 131 } else if util.IsHiddenFile(name) { 132 continue 133 } 134 135 filePath := filepath.Join(dir, name) 136 137 // If we found a directory, keep track of it until we've gone through all the files first 138 if file.IsDir() { 139 subdirectories = append(subdirectories, filePath) 140 continue 141 } 142 143 if a.maxFileSize > 0 { 144 stat, err := os.Stat(filePath) 145 if err != nil { 146 // this is highly unexpected but in case there could be a racey situation where 147 // the file gets removed right between ReadDirents and Stat 148 continue 149 } 150 if stat.Size() > a.maxFileSize { 151 log.Entry(context.TODO()).Debugf("skipping %s as it is larger (%d) than max allowed size %d", filePath, stat.Size(), a.maxFileSize) 152 continue 153 } 154 } 155 156 // to make skaffold.yaml more portable across OS-es we should always generate /-delimited filePaths 157 filePath = strings.ReplaceAll(filePath, string(os.PathSeparator), "/") 158 for _, analyzer := range a.analyzers() { 159 if err := analyzer.analyzeFile(context.Background(), filePath); err != nil { 160 return err 161 } 162 } 163 } 164 165 // Recurse into subdirectories 166 for _, subdir := range subdirectories { 167 if err := a.Analyze(subdir); err != nil { 168 return err 169 } 170 } 171 172 for _, analyzer := range a.analyzers() { 173 analyzer.exitDir(dir) 174 } 175 176 return nil 177 } 178 179 func skipFolder(name string) bool { 180 return name == "vendor" || name == "node_modules" 181 }