github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/cliconfig/provider_installation.go (about)

     1  package cliconfig
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  
     7  	"github.com/hashicorp/hcl"
     8  	hclast "github.com/hashicorp/hcl/hcl/ast"
     9  	"github.com/hashicorp/terraform/internal/addrs"
    10  	"github.com/hashicorp/terraform/internal/getproviders"
    11  	"github.com/hashicorp/terraform/internal/tfdiags"
    12  )
    13  
    14  // ProviderInstallation is the structure of the "provider_installation"
    15  // nested block within the CLI configuration.
    16  type ProviderInstallation struct {
    17  	Methods []*ProviderInstallationMethod
    18  
    19  	// DevOverrides allows overriding the normal selection process for
    20  	// a particular subset of providers to force using a particular
    21  	// local directory and disregard version numbering altogether.
    22  	// This is here to allow provider developers to conveniently test
    23  	// local builds of their plugins in a development environment, without
    24  	// having to fuss with version constraints, dependency lock files, and
    25  	// so forth.
    26  	//
    27  	// This is _not_ intended for "production" use because it bypasses the
    28  	// usual version selection and checksum verification mechanisms for
    29  	// the providers in question. To make that intent/effect clearer, some
    30  	// Terraform commands emit warnings when overrides are present. Local
    31  	// mirror directories are a better way to distribute "released"
    32  	// providers, because they are still subject to version constraints and
    33  	// checksum verification.
    34  	DevOverrides map[addrs.Provider]getproviders.PackageLocalDir
    35  }
    36  
    37  // decodeProviderInstallationFromConfig uses the HCL AST API directly to
    38  // decode "provider_installation" blocks from the given file.
    39  //
    40  // This uses the HCL AST directly, rather than HCL's decoder, because the
    41  // intended configuration structure can't be represented using the HCL
    42  // decoder's struct tags. This structure is intended as something that would
    43  // be relatively easier to deal with in HCL 2 once we eventually migrate
    44  // CLI config over to that, and so this function is stricter than HCL 1's
    45  // decoder would be in terms of exactly what configuration shape it is
    46  // expecting.
    47  //
    48  // Note that this function wants the top-level file object which might or
    49  // might not contain provider_installation blocks, not a provider_installation
    50  // block directly itself.
    51  func decodeProviderInstallationFromConfig(hclFile *hclast.File) ([]*ProviderInstallation, tfdiags.Diagnostics) {
    52  	var ret []*ProviderInstallation
    53  	var diags tfdiags.Diagnostics
    54  
    55  	root := hclFile.Node.(*hclast.ObjectList)
    56  
    57  	// This is a rather odd hybrid: it's a HCL 2-like decode implemented using
    58  	// the HCL 1 AST API. That makes it a bit awkward in places, but it allows
    59  	// us to mimick the strictness of HCL 2 (making a later migration easier)
    60  	// and to support a block structure that the HCL 1 decoder can't represent.
    61  	for _, block := range root.Items {
    62  		if block.Keys[0].Token.Value() != "provider_installation" {
    63  			continue
    64  		}
    65  		// HCL only tracks whether the input was JSON or native syntax inside
    66  		// individual tokens, so we'll use our block type token to decide
    67  		// and assume that the rest of the block must be written in the same
    68  		// syntax, because syntax is a whole-file idea.
    69  		isJSON := block.Keys[0].Token.JSON
    70  		if block.Assign.Line != 0 && !isJSON {
    71  			// Seems to be an attribute rather than a block
    72  			diags = diags.Append(tfdiags.Sourceless(
    73  				tfdiags.Error,
    74  				"Invalid provider_installation block",
    75  				fmt.Sprintf("The provider_installation block at %s must not be introduced with an equals sign.", block.Pos()),
    76  			))
    77  			continue
    78  		}
    79  		if len(block.Keys) > 1 && !isJSON {
    80  			diags = diags.Append(tfdiags.Sourceless(
    81  				tfdiags.Error,
    82  				"Invalid provider_installation block",
    83  				fmt.Sprintf("The provider_installation block at %s must not have any labels.", block.Pos()),
    84  			))
    85  		}
    86  
    87  		pi := &ProviderInstallation{}
    88  		devOverrides := make(map[addrs.Provider]getproviders.PackageLocalDir)
    89  
    90  		body, ok := block.Val.(*hclast.ObjectType)
    91  		if !ok {
    92  			// We can't get in here with native HCL syntax because we
    93  			// already checked above that we're using block syntax, but
    94  			// if we're reading JSON then our value could potentially be
    95  			// anything.
    96  			diags = diags.Append(tfdiags.Sourceless(
    97  				tfdiags.Error,
    98  				"Invalid provider_installation block",
    99  				fmt.Sprintf("The provider_installation block at %s must not be introduced with an equals sign.", block.Pos()),
   100  			))
   101  			continue
   102  		}
   103  
   104  		for _, methodBlock := range body.List.Items {
   105  			if methodBlock.Assign.Line != 0 && !isJSON {
   106  				// Seems to be an attribute rather than a block
   107  				diags = diags.Append(tfdiags.Sourceless(
   108  					tfdiags.Error,
   109  					"Invalid provider_installation method block",
   110  					fmt.Sprintf("The items inside the provider_installation block at %s must all be blocks.", block.Pos()),
   111  				))
   112  				continue
   113  			}
   114  			if len(methodBlock.Keys) > 1 && !isJSON {
   115  				diags = diags.Append(tfdiags.Sourceless(
   116  					tfdiags.Error,
   117  					"Invalid provider_installation method block",
   118  					fmt.Sprintf("The blocks inside the provider_installation block at %s may not have any labels.", block.Pos()),
   119  				))
   120  			}
   121  
   122  			methodBody, ok := methodBlock.Val.(*hclast.ObjectType)
   123  			if !ok {
   124  				// We can't get in here with native HCL syntax because we
   125  				// already checked above that we're using block syntax, but
   126  				// if we're reading JSON then our value could potentially be
   127  				// anything.
   128  				diags = diags.Append(tfdiags.Sourceless(
   129  					tfdiags.Error,
   130  					"Invalid provider_installation method block",
   131  					fmt.Sprintf("The items inside the provider_installation block at %s must all be blocks.", block.Pos()),
   132  				))
   133  				continue
   134  			}
   135  
   136  			methodTypeStr := methodBlock.Keys[0].Token.Value().(string)
   137  			var location ProviderInstallationLocation
   138  			var include, exclude []string
   139  			switch methodTypeStr {
   140  			case "direct":
   141  				type BodyContent struct {
   142  					Include []string `hcl:"include"`
   143  					Exclude []string `hcl:"exclude"`
   144  				}
   145  				var bodyContent BodyContent
   146  				err := hcl.DecodeObject(&bodyContent, methodBody)
   147  				if err != nil {
   148  					diags = diags.Append(tfdiags.Sourceless(
   149  						tfdiags.Error,
   150  						"Invalid provider_installation method block",
   151  						fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
   152  					))
   153  					continue
   154  				}
   155  				location = ProviderInstallationDirect
   156  				include = bodyContent.Include
   157  				exclude = bodyContent.Exclude
   158  			case "filesystem_mirror":
   159  				type BodyContent struct {
   160  					Path    string   `hcl:"path"`
   161  					Include []string `hcl:"include"`
   162  					Exclude []string `hcl:"exclude"`
   163  				}
   164  				var bodyContent BodyContent
   165  				err := hcl.DecodeObject(&bodyContent, methodBody)
   166  				if err != nil {
   167  					diags = diags.Append(tfdiags.Sourceless(
   168  						tfdiags.Error,
   169  						"Invalid provider_installation method block",
   170  						fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
   171  					))
   172  					continue
   173  				}
   174  				if bodyContent.Path == "" {
   175  					diags = diags.Append(tfdiags.Sourceless(
   176  						tfdiags.Error,
   177  						"Invalid provider_installation method block",
   178  						fmt.Sprintf("Invalid %s block at %s: \"path\" argument is required.", methodTypeStr, block.Pos()),
   179  					))
   180  					continue
   181  				}
   182  				location = ProviderInstallationFilesystemMirror(bodyContent.Path)
   183  				include = bodyContent.Include
   184  				exclude = bodyContent.Exclude
   185  			case "network_mirror":
   186  				type BodyContent struct {
   187  					URL     string   `hcl:"url"`
   188  					Include []string `hcl:"include"`
   189  					Exclude []string `hcl:"exclude"`
   190  				}
   191  				var bodyContent BodyContent
   192  				err := hcl.DecodeObject(&bodyContent, methodBody)
   193  				if err != nil {
   194  					diags = diags.Append(tfdiags.Sourceless(
   195  						tfdiags.Error,
   196  						"Invalid provider_installation method block",
   197  						fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
   198  					))
   199  					continue
   200  				}
   201  				if bodyContent.URL == "" {
   202  					diags = diags.Append(tfdiags.Sourceless(
   203  						tfdiags.Error,
   204  						"Invalid provider_installation method block",
   205  						fmt.Sprintf("Invalid %s block at %s: \"url\" argument is required.", methodTypeStr, block.Pos()),
   206  					))
   207  					continue
   208  				}
   209  				location = ProviderInstallationNetworkMirror(bodyContent.URL)
   210  				include = bodyContent.Include
   211  				exclude = bodyContent.Exclude
   212  			case "dev_overrides":
   213  				if len(pi.Methods) > 0 {
   214  					// We require dev_overrides to appear first if it's present,
   215  					// because dev_overrides effectively bypass the normal
   216  					// selection process for a particular provider altogether,
   217  					// and so they don't participate in the usual
   218  					// include/exclude arguments and priority ordering.
   219  					diags = diags.Append(tfdiags.Sourceless(
   220  						tfdiags.Error,
   221  						"Invalid provider_installation method block",
   222  						fmt.Sprintf("The dev_overrides block at at %s must appear before all other installation methods, because development overrides always have the highest priority.", methodBlock.Pos()),
   223  					))
   224  					continue
   225  				}
   226  
   227  				// The content of a dev_overrides block is a mapping from
   228  				// provider source addresses to local filesystem paths. To get
   229  				// our decoding started, we'll use the normal HCL decoder to
   230  				// populate a map of strings and then decode further from
   231  				// that.
   232  				var rawItems map[string]string
   233  				err := hcl.DecodeObject(&rawItems, methodBody)
   234  				if err != nil {
   235  					diags = diags.Append(tfdiags.Sourceless(
   236  						tfdiags.Error,
   237  						"Invalid provider_installation method block",
   238  						fmt.Sprintf("Invalid %s block at %s: %s.", methodTypeStr, block.Pos(), err),
   239  					))
   240  					continue
   241  				}
   242  
   243  				for rawAddr, rawPath := range rawItems {
   244  					addr, moreDiags := addrs.ParseProviderSourceString(rawAddr)
   245  					if moreDiags.HasErrors() {
   246  						diags = diags.Append(tfdiags.Sourceless(
   247  							tfdiags.Error,
   248  							"Invalid provider installation dev overrides",
   249  							fmt.Sprintf("The entry %q in %s is not a valid provider source string.\n\n%s", rawAddr, block.Pos(), moreDiags.Err().Error()),
   250  						))
   251  						continue
   252  					}
   253  					dirPath := filepath.Clean(rawPath)
   254  					devOverrides[addr] = getproviders.PackageLocalDir(dirPath)
   255  				}
   256  
   257  				continue // We won't add anything to pi.Methods for this one
   258  
   259  			default:
   260  				diags = diags.Append(tfdiags.Sourceless(
   261  					tfdiags.Error,
   262  					"Invalid provider_installation method block",
   263  					fmt.Sprintf("Unknown provider installation method %q at %s.", methodTypeStr, methodBlock.Pos()),
   264  				))
   265  				continue
   266  			}
   267  
   268  			pi.Methods = append(pi.Methods, &ProviderInstallationMethod{
   269  				Location: location,
   270  				Include:  include,
   271  				Exclude:  exclude,
   272  			})
   273  		}
   274  
   275  		if len(devOverrides) > 0 {
   276  			pi.DevOverrides = devOverrides
   277  		}
   278  
   279  		ret = append(ret, pi)
   280  	}
   281  
   282  	return ret, diags
   283  }
   284  
   285  // ProviderInstallationMethod represents an installation method block inside
   286  // a provider_installation block.
   287  type ProviderInstallationMethod struct {
   288  	Location ProviderInstallationLocation
   289  	Include  []string `hcl:"include"`
   290  	Exclude  []string `hcl:"exclude"`
   291  }
   292  
   293  // ProviderInstallationLocation is an interface type representing the
   294  // different installation location types. The concrete implementations of
   295  // this interface are:
   296  //
   297  //   - [ProviderInstallationDirect]:                 install from the provider's origin registry
   298  //   - [ProviderInstallationFilesystemMirror] (dir): install from a local filesystem mirror
   299  //   - [ProviderInstallationNetworkMirror] (host):   install from a network mirror
   300  type ProviderInstallationLocation interface {
   301  	providerInstallationLocation()
   302  }
   303  
   304  type providerInstallationDirect [0]byte
   305  
   306  func (i providerInstallationDirect) providerInstallationLocation() {}
   307  
   308  // ProviderInstallationDirect is a ProviderInstallationSourceLocation
   309  // representing installation from a provider's origin registry.
   310  var ProviderInstallationDirect ProviderInstallationLocation = providerInstallationDirect{}
   311  
   312  func (i providerInstallationDirect) GoString() string {
   313  	return "cliconfig.ProviderInstallationDirect"
   314  }
   315  
   316  // ProviderInstallationFilesystemMirror is a ProviderInstallationSourceLocation
   317  // representing installation from a particular local filesystem mirror. The
   318  // string value is the filesystem path to the mirror directory.
   319  type ProviderInstallationFilesystemMirror string
   320  
   321  func (i ProviderInstallationFilesystemMirror) providerInstallationLocation() {}
   322  
   323  func (i ProviderInstallationFilesystemMirror) GoString() string {
   324  	return fmt.Sprintf("cliconfig.ProviderInstallationFilesystemMirror(%q)", i)
   325  }
   326  
   327  // ProviderInstallationNetworkMirror is a ProviderInstallationSourceLocation
   328  // representing installation from a particular local network mirror. The
   329  // string value is the HTTP base URL exactly as written in the configuration,
   330  // without any normalization.
   331  type ProviderInstallationNetworkMirror string
   332  
   333  func (i ProviderInstallationNetworkMirror) providerInstallationLocation() {}
   334  
   335  func (i ProviderInstallationNetworkMirror) GoString() string {
   336  	return fmt.Sprintf("cliconfig.ProviderInstallationNetworkMirror(%q)", i)
   337  }