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