github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/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.Resources...)
    90  
    91  	for _, p := range paths {
    92  		abs := filepath.Join(dir, p)
    93  		if ospath.IsDir(abs) {
    94  			curDeps, err := dependenciesForKustomization(filepath.Join(dir, p))
    95  			if err != nil {
    96  				return nil, err
    97  			}
    98  			deps = append(deps, curDeps...)
    99  		} else {
   100  			deps = append(deps, abs)
   101  		}
   102  	}
   103  
   104  	deps = append(deps, path)
   105  	for _, patch := range content.Patches {
   106  		if patch.Path != "" {
   107  			deps = append(deps, filepath.Join(dir, patch.Path))
   108  		}
   109  	}
   110  	for _, patch := range content.PatchesStrategicMerge {
   111  		deps = append(deps, filepath.Join(dir, string(patch)))
   112  	}
   113  	deps = append(deps, joinPaths(dir, content.Crds)...)
   114  	for _, patch := range content.PatchesJson6902 {
   115  		deps = append(deps, filepath.Join(dir, patch.Path))
   116  	}
   117  	for _, generator := range content.ConfigMapGenerator {
   118  		deps = append(deps, joinPaths(dir, generator.FileSources)...)
   119  	}
   120  
   121  	return deps, nil
   122  }
   123  
   124  func Deps(baseDir string) ([]string, error) {
   125  	deps, err := dependenciesForKustomization(baseDir)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	return uniqDependencies(deps), nil
   131  }
   132  
   133  func joinPaths(root string, paths []string) []string {
   134  	var list []string
   135  
   136  	for _, path := range paths {
   137  		list = append(list, filepath.Join(root, path))
   138  	}
   139  
   140  	return list
   141  }
   142  
   143  func uniqDependencies(deps []string) []string {
   144  	seen := make(map[string]struct{}, len(deps))
   145  	j := 0
   146  	for _, v := range deps {
   147  		if _, ok := seen[v]; ok {
   148  			continue
   149  		}
   150  		seen[v] = struct{}{}
   151  		deps[j] = v
   152  		j++
   153  	}
   154  
   155  	return deps[:j]
   156  }