github.com/tilt-dev/tilt@v0.36.0/internal/kustomize/dependencies.go (about)

     1  /**
     2  Code for parsing Kustomize YAML and analyzing dependencies.
     3  
     4  Adapted from
     5  https://github.com/GoogleContainerTools/skaffold/blob/511c77f1736b657415500eb9b820ae7e4f753347/pkg/skaffold/deploy/kustomize.go
     6  
     7  Copyright 2018 The Skaffold Authors
     8  
     9  Licensed under the Apache License, Version 2.0 (the "License");
    10  you may not use this file except in compliance with the License.
    11  You may obtain a copy of the License at
    12  
    13      http://www.apache.org/licenses/LICENSE-2.0
    14  
    15  Unless required by applicable law or agreed to in writing, software
    16  distributed under the License is distributed on an "AS IS" BASIS,
    17  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    18  See the License for the specific language governing permissions and
    19  limitations under the License.
    20  */
    21  
    22  package kustomize
    23  
    24  import (
    25  	"fmt"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  
    30  	"github.com/tilt-dev/tilt/internal/ospath"
    31  
    32  	yaml "gopkg.in/yaml.v2"
    33  	"sigs.k8s.io/kustomize/api/konfig"
    34  	"sigs.k8s.io/kustomize/api/types"
    35  )
    36  
    37  // Mostly taken from the [kustomize source code](https://github.com/kubernetes-sigs/kustomize/blob/ee68a9c450bc884b0d657fb7e3d62eb1ac59d14f/pkg/target/kusttarget.go#L97) itself.
    38  func loadKustFile(dir string) ([]byte, string, error) {
    39  	var content []byte
    40  	var path string
    41  	match := 0
    42  	for _, kf := range konfig.RecognizedKustomizationFileNames() {
    43  		p := filepath.Join(dir, kf)
    44  		c, err := os.ReadFile(p)
    45  		if err == nil {
    46  			path = p
    47  			match += 1
    48  			content = c
    49  		}
    50  	}
    51  
    52  	switch match {
    53  	case 0:
    54  		return nil, "", fmt.Errorf(
    55  			"unable to find one of %v in directory '%s'",
    56  			konfig.RecognizedKustomizationFileNames(), dir)
    57  	case 1:
    58  		return content, path, nil
    59  	default:
    60  		return nil, "", fmt.Errorf(
    61  			"Found multiple kustomization files under: %s\n", dir)
    62  	}
    63  }
    64  
    65  // Code for parsing Kustomize adapted from Kustomize
    66  // https://github.com/kubernetes-sigs/kustomize/blob/ee68a9c450bc884b0d657fb7e3d62eb1ac59d14f/pkg/target/kusttarget.go#L97
    67  //
    68  // Code for parsing out dependencies copied from Skaffold
    69  // https://github.com/GoogleContainerTools/skaffold/blob/511c77f1736b657415500eb9b820ae7e4f753347/pkg/skaffold/deploy/kustomize.go
    70  func dependenciesForKustomization(dir string) ([]string, error) {
    71  	var deps []string
    72  
    73  	buf, path, err := loadKustFile(dir)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	content := types.Kustomization{}
    79  	if err := yaml.Unmarshal(buf, &content); err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	errs := content.EnforceFields()
    84  	if len(errs) > 0 {
    85  		return nil, fmt.Errorf("Failed to read kustomization file under %s:\n"+strings.Join(errs, "\n"), dir)
    86  	}
    87  
    88  	paths := append([]string{}, content.Bases...)
    89  	paths = append(paths, content.Components...)
    90  	paths = append(paths, content.Resources...)
    91  
    92  	for _, p := range paths {
    93  		abs := filepath.Join(dir, p)
    94  		if ospath.IsDir(abs) {
    95  			curDeps, err := dependenciesForKustomization(filepath.Join(dir, p))
    96  			if err != nil {
    97  				return nil, err
    98  			}
    99  			deps = append(deps, curDeps...)
   100  		} else {
   101  			deps = append(deps, abs)
   102  		}
   103  	}
   104  
   105  	deps = append(deps, path)
   106  	for _, patch := range content.Patches {
   107  		if patch.Path != "" {
   108  			deps = append(deps, filepath.Join(dir, patch.Path))
   109  		}
   110  	}
   111  	for _, patch := range content.PatchesStrategicMerge {
   112  		deps = append(deps, filepath.Join(dir, string(patch)))
   113  	}
   114  	deps = append(deps, joinPaths(dir, content.Crds)...)
   115  	for _, patch := range content.PatchesJson6902 {
   116  		deps = append(deps, filepath.Join(dir, patch.Path))
   117  	}
   118  	for _, generator := range content.ConfigMapGenerator {
   119  		deps = append(deps, joinPaths(dir, generator.FileSources)...)
   120  	}
   121  
   122  	return deps, nil
   123  }
   124  
   125  func Deps(baseDir string) ([]string, error) {
   126  	deps, err := dependenciesForKustomization(baseDir)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	return uniqDependencies(deps), nil
   132  }
   133  
   134  func joinPaths(root string, paths []string) []string {
   135  	var list []string
   136  
   137  	for _, path := range paths {
   138  		list = append(list, filepath.Join(root, path))
   139  	}
   140  
   141  	return list
   142  }
   143  
   144  func uniqDependencies(deps []string) []string {
   145  	seen := make(map[string]struct{}, len(deps))
   146  	j := 0
   147  	for _, v := range deps {
   148  		if _, ok := seen[v]; ok {
   149  			continue
   150  		}
   151  		seen[v] = struct{}{}
   152  		deps[j] = v
   153  		j++
   154  	}
   155  
   156  	return deps[:j]
   157  }