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  }