github.com/opentofu/opentofu@v1.7.1/internal/moduledeps/module.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package moduledeps
     7  
     8  import (
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/opentofu/opentofu/internal/plugin/discovery"
    13  )
    14  
    15  // Module represents the dependencies of a single module, as well being
    16  // a node in a tree of such structures representing the dependencies of
    17  // an entire configuration.
    18  type Module struct {
    19  	Name      string
    20  	Providers Providers
    21  	Children  []*Module
    22  }
    23  
    24  // WalkFunc is a callback type for use with Module.WalkTree
    25  type WalkFunc func(path []string, parent *Module, current *Module) error
    26  
    27  // WalkTree calls the given callback once for the receiver and then
    28  // once for each descendent, in an order such that parents are called
    29  // before their children and siblings are called in the order they
    30  // appear in the Children slice.
    31  //
    32  // When calling the callback, parent will be nil for the first call
    33  // for the receiving module, and then set to the direct parent of
    34  // each module for the subsequent calls.
    35  //
    36  // The path given to the callback is valid only until the callback
    37  // returns, after which it will be mutated and reused. Callbacks must
    38  // therefore copy the path slice if they wish to retain it.
    39  //
    40  // If the given callback returns an error, the walk will be aborted at
    41  // that point and that error returned to the caller.
    42  //
    43  // This function is not thread-safe for concurrent modifications of the
    44  // data structure, so it's the caller's responsibility to arrange for that
    45  // should it be needed.
    46  //
    47  // It is safe for a callback to modify the descendents of the "current"
    48  // module, including the ordering of the Children slice itself, but the
    49  // callback MUST NOT modify the parent module.
    50  func (m *Module) WalkTree(cb WalkFunc) error {
    51  	return walkModuleTree(make([]string, 0, 1), nil, m, cb)
    52  }
    53  
    54  func walkModuleTree(path []string, parent *Module, current *Module, cb WalkFunc) error {
    55  	path = append(path, current.Name)
    56  	err := cb(path, parent, current)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	for _, child := range current.Children {
    62  		err := walkModuleTree(path, current, child, cb)
    63  		if err != nil {
    64  			return err
    65  		}
    66  	}
    67  	return nil
    68  }
    69  
    70  // SortChildren sorts the Children slice into lexicographic order by
    71  // name, in-place.
    72  //
    73  // This is primarily useful prior to calling WalkTree so that the walk
    74  // will proceed in a consistent order.
    75  func (m *Module) SortChildren() {
    76  	sort.Sort(sortModules{m.Children})
    77  }
    78  
    79  // SortDescendents is a convenience wrapper for calling SortChildren on
    80  // the receiver and all of its descendent modules.
    81  func (m *Module) SortDescendents() {
    82  	m.WalkTree(func(path []string, parent *Module, current *Module) error {
    83  		current.SortChildren()
    84  		return nil
    85  	})
    86  }
    87  
    88  type sortModules struct {
    89  	modules []*Module
    90  }
    91  
    92  func (s sortModules) Len() int {
    93  	return len(s.modules)
    94  }
    95  
    96  func (s sortModules) Less(i, j int) bool {
    97  	cmp := strings.Compare(s.modules[i].Name, s.modules[j].Name)
    98  	return cmp < 0
    99  }
   100  
   101  func (s sortModules) Swap(i, j int) {
   102  	s.modules[i], s.modules[j] = s.modules[j], s.modules[i]
   103  }
   104  
   105  // ProviderRequirements produces a PluginRequirements structure that can
   106  // be used with discovery.PluginMetaSet.ConstrainVersions to identify
   107  // suitable plugins to satisfy the module's provider dependencies.
   108  //
   109  // This method only considers the direct requirements of the receiver.
   110  // Use AllPluginRequirements to flatten the dependencies for the
   111  // entire tree of modules.
   112  //
   113  // Requirements returned by this method include only version constraints,
   114  // and apply no particular SHA256 hash constraint.
   115  func (m *Module) ProviderRequirements() discovery.PluginRequirements {
   116  	ret := make(discovery.PluginRequirements)
   117  	for pFqn, dep := range m.Providers {
   118  		providerType := pFqn.Type
   119  		if existing, exists := ret[providerType]; exists {
   120  			ret[providerType].Versions = existing.Versions.Append(dep.Constraints)
   121  		} else {
   122  			ret[providerType] = &discovery.PluginConstraints{
   123  				Versions: dep.Constraints,
   124  			}
   125  		}
   126  	}
   127  	return ret
   128  }
   129  
   130  // AllProviderRequirements calls ProviderRequirements for the receiver and all
   131  // of its descendents, and merges the result into a single PluginRequirements
   132  // structure that would satisfy all of the modules together.
   133  //
   134  // Requirements returned by this method include only version constraints,
   135  // and apply no particular SHA256 hash constraint.
   136  func (m *Module) AllProviderRequirements() discovery.PluginRequirements {
   137  	var ret discovery.PluginRequirements
   138  	m.WalkTree(func(path []string, parent *Module, current *Module) error {
   139  		ret = ret.Merge(current.ProviderRequirements())
   140  		return nil
   141  	})
   142  	return ret
   143  }
   144  
   145  // Equal returns true if the receiver is the root of an identical tree
   146  // to the other given Module. This is a deep comparison that considers
   147  // the equality of all downstream modules too.
   148  //
   149  // The children are considered to be ordered, so callers may wish to use
   150  // SortDescendents first to normalize the order of the slices of child nodes.
   151  //
   152  // The implementation of this function is not optimized since it is provided
   153  // primarily for use in tests.
   154  func (m *Module) Equal(other *Module) bool {
   155  	// take care of nils first
   156  	if m == nil && other == nil {
   157  		return true
   158  	} else if (m == nil && other != nil) || (m != nil && other == nil) {
   159  		return false
   160  	}
   161  
   162  	if m.Name != other.Name {
   163  		return false
   164  	}
   165  
   166  	if len(m.Providers) != len(other.Providers) {
   167  		return false
   168  	}
   169  	if len(m.Children) != len(other.Children) {
   170  		return false
   171  	}
   172  
   173  	// Can't use reflect.DeepEqual on this provider structure because
   174  	// the nested Constraints objects contain function pointers that
   175  	// never compare as equal. So we'll need to walk it the long way.
   176  	for inst, dep := range m.Providers {
   177  		if _, exists := other.Providers[inst]; !exists {
   178  			return false
   179  		}
   180  
   181  		if dep.Reason != other.Providers[inst].Reason {
   182  			return false
   183  		}
   184  
   185  		// Constraints are not too easy to compare robustly, so
   186  		// we'll just use their string representations as a proxy
   187  		// for now.
   188  		if dep.Constraints.String() != other.Providers[inst].Constraints.String() {
   189  			return false
   190  		}
   191  	}
   192  
   193  	// Above we already checked that we have the same number of children
   194  	// in each module, so now we just need to check that they are
   195  	// recursively equal.
   196  	for i := range m.Children {
   197  		if !m.Children[i].Equal(other.Children[i]) {
   198  			return false
   199  		}
   200  	}
   201  
   202  	// If we fall out here then they are equal
   203  	return true
   204  }