github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/render/generate/generate.go (about)

     1  /*
     2  Copyright 2021 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 generate
    18  
    19  import (
    20  	"context"
    21  	"io/ioutil"
    22  	"os"
    23  	"os/exec"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kustomize"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    31  	latestV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest/v2"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/stringslice"
    34  )
    35  
    36  // NewGenerator instantiates a Generator object.
    37  func NewGenerator(workingDir string, config latestV2.Generate) *Generator {
    38  	return &Generator{
    39  		workingDir: workingDir,
    40  		config:     config,
    41  	}
    42  }
    43  
    44  // Generator provides the functions for the manifest sources (raw manifests, helm charts, kustomize configs and remote packages).
    45  type Generator struct {
    46  	workingDir string
    47  	config     latestV2.Generate
    48  }
    49  
    50  // Generate parses the config resources from the paths in .Generate.Manifests. This path can be the path to raw manifest,
    51  // kustomize manifests, helm charts or kpt function configs. All should be file-watched.
    52  func (g *Generator) Generate(ctx context.Context) (manifest.ManifestList, error) {
    53  	// exclude remote url.
    54  	var paths []string
    55  	// TODO(yuwenma): Apply new UX, kustomize kpt and helm
    56  	for _, path := range g.config.RawK8s {
    57  		switch {
    58  		case util.IsURL(path):
    59  			// TODO(yuwenma): remote URL should be changed to use kpt package management approach, via API Schema
    60  			//  `render.generate.remotePackages`
    61  		case strings.HasPrefix(path, "gs://"):
    62  			// TODO(yuwenma): handle GS packages.
    63  		default:
    64  			paths = append(paths, path)
    65  		}
    66  	}
    67  	// expend the glob paths.
    68  	expanded, err := util.ExpandPathsGlob(g.workingDir, paths)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	// Parse kustomize manifests and non-kustomize manifests.  We may also want to parse (and exclude) kpt function manifests later.
    74  	// TODO: Update `kustomize build` to kustomize kpt-fn once https://github.com/GoogleContainerTools/kpt/issues/1447 is fixed.
    75  	kustomizePathMap := make(map[string]bool)
    76  	var nonKustomizePaths []string
    77  	for _, path := range expanded {
    78  		if dir, ok := isKustomizeDir(path); ok {
    79  			kustomizePathMap[dir] = true
    80  		}
    81  	}
    82  	for _, path := range expanded {
    83  		kustomizeDirDup := false
    84  		for kPath := range kustomizePathMap {
    85  			// Before kustomize kpt-fn can provide a way to parse the kustomize content, we assume the users do not place non-kustomize manifests under the kustomization.yaml directory.
    86  			if strings.HasPrefix(path, kPath) {
    87  				kustomizeDirDup = true
    88  				break
    89  			}
    90  		}
    91  		if !kustomizeDirDup {
    92  			nonKustomizePaths = append(nonKustomizePaths, path)
    93  		}
    94  	}
    95  
    96  	var manifests manifest.ManifestList
    97  	for kPath := range kustomizePathMap {
    98  		// TODO:  support kustomize buildArgs (shall we support it in kpt-fn)?
    99  		cmd := exec.CommandContext(ctx, "kustomize", "build", kPath)
   100  		out, err := util.RunCmdOut(ctx, cmd)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		if len(out) == 0 {
   105  			continue
   106  		}
   107  		manifests.Append(out)
   108  	}
   109  	for _, nkPath := range nonKustomizePaths {
   110  		if !kubernetes.HasKubernetesFileExtension(nkPath) {
   111  			if !stringslice.Contains(g.config.RawK8s, nkPath) {
   112  				log.Entry(ctx).Infof("refusing to deploy/delete non {json, yaml} file %s", nkPath)
   113  				log.Entry(ctx).Info("If you still wish to deploy this file, please specify it directly, outside a glob pattern.")
   114  				continue
   115  			}
   116  		}
   117  		manifestFileContent, err := ioutil.ReadFile(nkPath)
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  		manifests.Append(manifestFileContent)
   122  	}
   123  	// TODO(yuwenma): helm resources. `render.generate.helmCharts`
   124  	return manifests, nil
   125  }
   126  
   127  // isKustomizeDir checks if the path is managed by kustomize. A more reliable approach is parsing the kustomize content
   128  // resources, bases, overlays. However, this switches the manifests parsing from kustomize/kpt to skaffold. To avoid
   129  // skaffold render.generate mis-use, we expect the users do not place non-kustomize manifests under the kustomization.yaml directory, so as the kpt manifests.
   130  func isKustomizeDir(path string) (string, bool) {
   131  	fileInfo, err := os.Stat(path)
   132  	if err != nil {
   133  		return "", false
   134  	}
   135  	var dir string
   136  	switch mode := fileInfo.Mode(); {
   137  	// TODO: Check if regular file contains kpt functions. if so, we may want to abstract that info as well.
   138  	case mode.IsDir():
   139  		dir = path
   140  	case mode.IsRegular():
   141  		dir = filepath.Dir(path)
   142  	}
   143  
   144  	for _, base := range kustomize.KustomizeFilePaths {
   145  		if _, err := os.Stat(filepath.Join(dir, base)); os.IsNotExist(err) {
   146  			continue
   147  		}
   148  		return dir, true
   149  	}
   150  	return "", false
   151  }