github.com/x-helm/helm@v3.0.0-beta.3+incompatible/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  	"helm.sh/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) {
    35  	if reqs == nil {
    36  		return
    37  	}
    38  	for _, r := range reqs {
    39  		var hasTrue, hasFalse bool
    40  		for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") {
    41  			if len(c) > 0 {
    42  				// retrieve value
    43  				vv, err := cvals.PathValue(c)
    44  				if err == nil {
    45  					// if not bool, warn
    46  					if bv, ok := vv.(bool); ok {
    47  						if bv {
    48  							hasTrue = true
    49  						} else {
    50  							hasFalse = true
    51  						}
    52  					} else {
    53  						log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name)
    54  					}
    55  				} else if _, ok := err.(ErrNoValue); !ok {
    56  					// this is a real error
    57  					log.Printf("Warning: PathValue returned error %v", err)
    58  				}
    59  				if vv != nil {
    60  					// got first value, break loop
    61  					break
    62  				}
    63  			}
    64  		}
    65  		if !hasTrue && hasFalse {
    66  			r.Enabled = false
    67  		} else if hasTrue {
    68  			r.Enabled = true
    69  
    70  		}
    71  	}
    72  }
    73  
    74  // processDependencyTags disables charts based on tags in values
    75  func processDependencyTags(reqs []*chart.Dependency, cvals Values) {
    76  	if reqs == nil {
    77  		return
    78  	}
    79  	vt, err := cvals.Table("tags")
    80  	if err != nil {
    81  		return
    82  	}
    83  	for _, r := range reqs {
    84  		var hasTrue, hasFalse bool
    85  		for _, k := range r.Tags {
    86  			if b, ok := vt[k]; ok {
    87  				// if not bool, warn
    88  				if bv, ok := b.(bool); ok {
    89  					if bv {
    90  						hasTrue = true
    91  					} else {
    92  						hasFalse = true
    93  					}
    94  				} else {
    95  					log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name)
    96  				}
    97  			}
    98  		}
    99  		if !hasTrue && hasFalse {
   100  			r.Enabled = false
   101  		} else if hasTrue || !hasTrue && !hasFalse {
   102  			r.Enabled = true
   103  		}
   104  	}
   105  }
   106  
   107  func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart {
   108  	for _, c := range charts {
   109  		if c == nil {
   110  			continue
   111  		}
   112  		if c.Name() != dep.Name {
   113  			continue
   114  		}
   115  		if !IsCompatibleRange(dep.Version, c.Metadata.Version) {
   116  			continue
   117  		}
   118  
   119  		out := *c
   120  		md := *c.Metadata
   121  		out.Metadata = &md
   122  
   123  		if dep.Alias != "" {
   124  			md.Name = dep.Alias
   125  		}
   126  		return &out
   127  	}
   128  	return nil
   129  }
   130  
   131  // processDependencyEnabled removes disabled charts from dependencies
   132  func processDependencyEnabled(c *chart.Chart, v map[string]interface{}) error {
   133  	if c.Metadata.Dependencies == nil {
   134  		return nil
   135  	}
   136  
   137  	var chartDependencies []*chart.Chart
   138  	// If any dependency is not a part of Chart.yaml
   139  	// then this should be added to chartDependencies.
   140  	// However, if the dependency is already specified in Chart.yaml
   141  	// we should not add it, as it would be anyways processed from Chart.yaml
   142  
   143  Loop:
   144  	for _, existing := range c.Dependencies() {
   145  		for _, req := range c.Metadata.Dependencies {
   146  			if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) {
   147  				continue Loop
   148  			}
   149  		}
   150  		chartDependencies = append(chartDependencies, existing)
   151  	}
   152  
   153  	for _, req := range c.Metadata.Dependencies {
   154  		if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil {
   155  			chartDependencies = append(chartDependencies, chartDependency)
   156  		}
   157  		if req.Alias != "" {
   158  			req.Name = req.Alias
   159  		}
   160  	}
   161  	c.SetDependencies(chartDependencies...)
   162  
   163  	// set all to true
   164  	for _, lr := range c.Metadata.Dependencies {
   165  		lr.Enabled = true
   166  	}
   167  	cvals, err := CoalesceValues(c, v)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	// flag dependencies as enabled/disabled
   172  	processDependencyTags(c.Metadata.Dependencies, cvals)
   173  	processDependencyConditions(c.Metadata.Dependencies, cvals)
   174  	// make a map of charts to remove
   175  	rm := map[string]struct{}{}
   176  	for _, r := range c.Metadata.Dependencies {
   177  		if !r.Enabled {
   178  			// remove disabled chart
   179  			rm[r.Name] = struct{}{}
   180  		}
   181  	}
   182  	// don't keep disabled charts in new slice
   183  	cd := []*chart.Chart{}
   184  	copy(cd, c.Dependencies()[:0])
   185  	for _, n := range c.Dependencies() {
   186  		if _, ok := rm[n.Metadata.Name]; !ok {
   187  			cd = append(cd, n)
   188  		}
   189  	}
   190  
   191  	// recursively call self to process sub dependencies
   192  	for _, t := range cd {
   193  		if err := processDependencyEnabled(t, cvals); err != nil {
   194  			return err
   195  		}
   196  	}
   197  	c.SetDependencies(cd...)
   198  
   199  	return nil
   200  }
   201  
   202  // pathToMap creates a nested map given a YAML path in dot notation.
   203  func pathToMap(path string, data map[string]interface{}) map[string]interface{} {
   204  	if path == "." {
   205  		return data
   206  	}
   207  	return set(parsePath(path), data)
   208  }
   209  
   210  func set(path []string, data map[string]interface{}) map[string]interface{} {
   211  	if len(path) == 0 {
   212  		return nil
   213  	}
   214  	cur := data
   215  	for i := len(path) - 1; i >= 0; i-- {
   216  		cur = map[string]interface{}{path[i]: cur}
   217  	}
   218  	return cur
   219  }
   220  
   221  // processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field.
   222  func processImportValues(c *chart.Chart) error {
   223  	if c.Metadata.Dependencies == nil {
   224  		return nil
   225  	}
   226  	// combine chart values and empty config to get Values
   227  	cvals, err := CoalesceValues(c, nil)
   228  	if err != nil {
   229  		return err
   230  	}
   231  	b := make(map[string]interface{})
   232  	// import values from each dependency if specified in import-values
   233  	for _, r := range c.Metadata.Dependencies {
   234  		var outiv []interface{}
   235  		for _, riv := range r.ImportValues {
   236  			switch iv := riv.(type) {
   237  			case map[string]interface{}:
   238  				child := iv["child"].(string)
   239  				parent := iv["parent"].(string)
   240  
   241  				outiv = append(outiv, map[string]string{
   242  					"child":  child,
   243  					"parent": parent,
   244  				})
   245  
   246  				// get child table
   247  				vv, err := cvals.Table(r.Name + "." + child)
   248  				if err != nil {
   249  					log.Printf("Warning: ImportValues missing table: %v", err)
   250  					continue
   251  				}
   252  				// create value map from child to be merged into parent
   253  				b = CoalesceTables(cvals, pathToMap(parent, vv.AsMap()))
   254  			case string:
   255  				child := "exports." + iv
   256  				outiv = append(outiv, map[string]string{
   257  					"child":  child,
   258  					"parent": ".",
   259  				})
   260  				vm, err := cvals.Table(r.Name + "." + child)
   261  				if err != nil {
   262  					log.Printf("Warning: ImportValues missing table: %v", err)
   263  					continue
   264  				}
   265  				b = CoalesceTables(b, vm.AsMap())
   266  			}
   267  		}
   268  		// set our formatted import values
   269  		r.ImportValues = outiv
   270  	}
   271  
   272  	// set the new values
   273  	c.Values = CoalesceTables(b, cvals)
   274  
   275  	return nil
   276  }
   277  
   278  // processDependencyImportValues imports specified chart values from child to parent.
   279  func processDependencyImportValues(c *chart.Chart) error {
   280  	for _, d := range c.Dependencies() {
   281  		// recurse
   282  		if err := processDependencyImportValues(d); err != nil {
   283  			return err
   284  		}
   285  	}
   286  	return processImportValues(c)
   287  }