github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chartutil/dependencies.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package chartutil
    17  
    18  import (
    19  	"log"
    20  	"strings"
    21  
    22  	"github.com/stefanmcshane/helm/pkg/chart"
    23  )
    24  
    25  // ProcessDependencies checks through this chart's dependencies, processing accordingly.
    26  func ProcessDependencies(c *chart.Chart, v Values) error {
    27  	if err := processDependencyEnabled(c, v, ""); err != nil {
    28  		return err
    29  	}
    30  	return processDependencyImportValues(c)
    31  }
    32  
    33  // processDependencyConditions disables charts based on condition path value in values
    34  func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) {
    35  	if reqs == nil {
    36  		return
    37  	}
    38  	for _, r := range reqs {
    39  		for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") {
    40  			if len(c) > 0 {
    41  				// retrieve value
    42  				vv, err := cvals.PathValue(cpath + c)
    43  				if err == nil {
    44  					// if not bool, warn
    45  					if bv, ok := vv.(bool); ok {
    46  						r.Enabled = bv
    47  						break
    48  					} else {
    49  						log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name)
    50  					}
    51  				} else if _, ok := err.(ErrNoValue); !ok {
    52  					// this is a real error
    53  					log.Printf("Warning: PathValue returned error %v", err)
    54  				}
    55  			}
    56  		}
    57  	}
    58  }
    59  
    60  // processDependencyTags disables charts based on tags in values
    61  func processDependencyTags(reqs []*chart.Dependency, cvals Values) {
    62  	if reqs == nil {
    63  		return
    64  	}
    65  	vt, err := cvals.Table("tags")
    66  	if err != nil {
    67  		return
    68  	}
    69  	for _, r := range reqs {
    70  		var hasTrue, hasFalse bool
    71  		for _, k := range r.Tags {
    72  			if b, ok := vt[k]; ok {
    73  				// if not bool, warn
    74  				if bv, ok := b.(bool); ok {
    75  					if bv {
    76  						hasTrue = true
    77  					} else {
    78  						hasFalse = true
    79  					}
    80  				} else {
    81  					log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name)
    82  				}
    83  			}
    84  		}
    85  		if !hasTrue && hasFalse {
    86  			r.Enabled = false
    87  		} else if hasTrue || !hasTrue && !hasFalse {
    88  			r.Enabled = true
    89  		}
    90  	}
    91  }
    92  
    93  func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart {
    94  	for _, c := range charts {
    95  		if c == nil {
    96  			continue
    97  		}
    98  		if c.Name() != dep.Name {
    99  			continue
   100  		}
   101  		if !IsCompatibleRange(dep.Version, c.Metadata.Version) {
   102  			continue
   103  		}
   104  
   105  		out := *c
   106  		md := *c.Metadata
   107  		out.Metadata = &md
   108  
   109  		if dep.Alias != "" {
   110  			md.Name = dep.Alias
   111  		}
   112  		return &out
   113  	}
   114  	return nil
   115  }
   116  
   117  // processDependencyEnabled removes disabled charts from dependencies
   118  func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error {
   119  	if c.Metadata.Dependencies == nil {
   120  		return nil
   121  	}
   122  
   123  	var chartDependencies []*chart.Chart
   124  	// If any dependency is not a part of Chart.yaml
   125  	// then this should be added to chartDependencies.
   126  	// However, if the dependency is already specified in Chart.yaml
   127  	// we should not add it, as it would be anyways processed from Chart.yaml
   128  
   129  Loop:
   130  	for _, existing := range c.Dependencies() {
   131  		for _, req := range c.Metadata.Dependencies {
   132  			if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) {
   133  				continue Loop
   134  			}
   135  		}
   136  		chartDependencies = append(chartDependencies, existing)
   137  	}
   138  
   139  	for _, req := range c.Metadata.Dependencies {
   140  		if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil {
   141  			chartDependencies = append(chartDependencies, chartDependency)
   142  		}
   143  		if req.Alias != "" {
   144  			req.Name = req.Alias
   145  		}
   146  	}
   147  	c.SetDependencies(chartDependencies...)
   148  
   149  	// set all to true
   150  	for _, lr := range c.Metadata.Dependencies {
   151  		lr.Enabled = true
   152  	}
   153  	cvals, err := CoalesceValues(c, v)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	// flag dependencies as enabled/disabled
   158  	processDependencyTags(c.Metadata.Dependencies, cvals)
   159  	processDependencyConditions(c.Metadata.Dependencies, cvals, path)
   160  	// make a map of charts to remove
   161  	rm := map[string]struct{}{}
   162  	for _, r := range c.Metadata.Dependencies {
   163  		if !r.Enabled {
   164  			// remove disabled chart
   165  			rm[r.Name] = struct{}{}
   166  		}
   167  	}
   168  	// don't keep disabled charts in new slice
   169  	cd := []*chart.Chart{}
   170  	copy(cd, c.Dependencies()[:0])
   171  	for _, n := range c.Dependencies() {
   172  		if _, ok := rm[n.Metadata.Name]; !ok {
   173  			cd = append(cd, n)
   174  		}
   175  	}
   176  	// don't keep disabled charts in metadata
   177  	cdMetadata := []*chart.Dependency{}
   178  	copy(cdMetadata, c.Metadata.Dependencies[:0])
   179  	for _, n := range c.Metadata.Dependencies {
   180  		if _, ok := rm[n.Name]; !ok {
   181  			cdMetadata = append(cdMetadata, n)
   182  		}
   183  	}
   184  
   185  	// recursively call self to process sub dependencies
   186  	for _, t := range cd {
   187  		subpath := path + t.Metadata.Name + "."
   188  		if err := processDependencyEnabled(t, cvals, subpath); err != nil {
   189  			return err
   190  		}
   191  	}
   192  	// set the correct dependencies in metadata
   193  	c.Metadata.Dependencies = nil
   194  	c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...)
   195  	c.SetDependencies(cd...)
   196  
   197  	return nil
   198  }
   199  
   200  // pathToMap creates a nested map given a YAML path in dot notation.
   201  func pathToMap(path string, data map[string]interface{}) map[string]interface{} {
   202  	if path == "." {
   203  		return data
   204  	}
   205  	return set(parsePath(path), data)
   206  }
   207  
   208  func set(path []string, data map[string]interface{}) map[string]interface{} {
   209  	if len(path) == 0 {
   210  		return nil
   211  	}
   212  	cur := data
   213  	for i := len(path) - 1; i >= 0; i-- {
   214  		cur = map[string]interface{}{path[i]: cur}
   215  	}
   216  	return cur
   217  }
   218  
   219  // processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
   220  func processImportValues(c *chart.Chart) error {
   221  	if c.Metadata.Dependencies == nil {
   222  		return nil
   223  	}
   224  	// combine chart values and empty config to get Values
   225  	cvals, err := CoalesceValues(c, nil)
   226  	if err != nil {
   227  		return err
   228  	}
   229  	b := make(map[string]interface{})
   230  	// import values from each dependency if specified in import-values
   231  	for _, r := range c.Metadata.Dependencies {
   232  		var outiv []interface{}
   233  		for _, riv := range r.ImportValues {
   234  			switch iv := riv.(type) {
   235  			case map[string]interface{}:
   236  				child := iv["child"].(string)
   237  				parent := iv["parent"].(string)
   238  
   239  				outiv = append(outiv, map[string]string{
   240  					"child":  child,
   241  					"parent": parent,
   242  				})
   243  
   244  				// get child table
   245  				vv, err := cvals.Table(r.Name + "." + child)
   246  				if err != nil {
   247  					log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err)
   248  					continue
   249  				}
   250  				// create value map from child to be merged into parent
   251  				b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap()))
   252  			case string:
   253  				child := "exports." + iv
   254  				outiv = append(outiv, map[string]string{
   255  					"child":  child,
   256  					"parent": ".",
   257  				})
   258  				vm, err := cvals.Table(r.Name + "." + child)
   259  				if err != nil {
   260  					log.Printf("Warning: ImportValues missing table: %v", err)
   261  					continue
   262  				}
   263  				b = CoalesceTables(b, vm.AsMap())
   264  			}
   265  		}
   266  		// set our formatted import values
   267  		r.ImportValues = outiv
   268  	}
   269  
   270  	// set the new values
   271  	c.Values = CoalesceTables(cvals, b)
   272  
   273  	return nil
   274  }
   275  
   276  // processDependencyImportValues imports specified chart values from child to parent.
   277  func processDependencyImportValues(c *chart.Chart) error {
   278  	for _, d := range c.Dependencies() {
   279  		// recurse
   280  		if err := processDependencyImportValues(d); err != nil {
   281  			return err
   282  		}
   283  	}
   284  	return processImportValues(c)
   285  }