github.com/hashicorp/packer@v1.14.3/hcl2template/plugin.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package hcl2template
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"fmt"
     9  	"log"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/hcl/v2"
    14  	"github.com/hashicorp/packer-plugin-sdk/didyoumean"
    15  	pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
    16  	plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
    17  )
    18  
    19  // PluginRequirements returns a sorted list of plugin requirements.
    20  func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Diagnostics) {
    21  
    22  	var diags hcl.Diagnostics
    23  	var reqs plugingetter.Requirements
    24  	reqPluginsBlocks := cfg.Packer.RequiredPlugins
    25  
    26  	// Take all required plugins, make sure there are no conflicting blocks
    27  	// and append them to the list.
    28  	uniq := map[string]*RequiredPlugin{}
    29  	for _, requiredPluginsBlock := range reqPluginsBlocks {
    30  		for name, block := range requiredPluginsBlock.RequiredPlugins {
    31  
    32  			if previouslySeenBlock, found := uniq[name]; found {
    33  				diags = append(diags, &hcl.Diagnostic{
    34  					Severity: hcl.DiagError,
    35  					Summary:  fmt.Sprintf("Duplicate required_plugin.%q block", name),
    36  					Detail: fmt.Sprintf("Block previously seen at %s is already named %q.\n", previouslySeenBlock.DeclRange, name) +
    37  						"Names at the left hand side of required_plugins are made available to use in your HCL2 configurations.\n" +
    38  						"To allow to calling to their features correctly two plugins have to have different accessors.",
    39  					Context: &block.DeclRange,
    40  				})
    41  				continue
    42  			}
    43  
    44  			reqs = append(reqs, &plugingetter.Requirement{
    45  				Accessor:           name,
    46  				Identifier:         block.Type,
    47  				VersionConstraints: block.Requirement.Required,
    48  			})
    49  			uniq[name] = block
    50  		}
    51  
    52  	}
    53  
    54  	return reqs, diags
    55  }
    56  
    57  func (cfg *PackerConfig) DetectPluginBinaries() hcl.Diagnostics {
    58  	// Then we can apply any constraint from the template, if any
    59  	opts := plugingetter.ListInstallationsOptions{
    60  		PluginDirectory: cfg.parser.PluginConfig.PluginDirectory,
    61  		BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
    62  			OS:              runtime.GOOS,
    63  			ARCH:            runtime.GOARCH,
    64  			APIVersionMajor: pluginsdk.APIVersionMajor,
    65  			APIVersionMinor: pluginsdk.APIVersionMinor,
    66  			Checksummers: []plugingetter.Checksummer{
    67  				{Type: "sha256", Hash: sha256.New()},
    68  			},
    69  			ReleasesOnly: cfg.parser.PluginConfig.ReleasesOnly,
    70  		},
    71  	}
    72  
    73  	if runtime.GOOS == "windows" && opts.Ext == "" {
    74  		opts.BinaryInstallationOptions.Ext = ".exe"
    75  	}
    76  
    77  	pluginReqs, diags := cfg.PluginRequirements()
    78  	if diags.HasErrors() {
    79  		return diags
    80  	}
    81  
    82  	uninstalledPlugins := map[string]string{}
    83  
    84  	for _, pluginRequirement := range pluginReqs {
    85  		sortedInstalls, err := pluginRequirement.ListInstallations(opts)
    86  		if err != nil {
    87  			diags = append(diags, &hcl.Diagnostic{
    88  				Severity: hcl.DiagError,
    89  				Summary:  fmt.Sprintf("Failed to list installation for %s", pluginRequirement.Identifier),
    90  				Detail:   err.Error(),
    91  			})
    92  			continue
    93  		}
    94  		if len(sortedInstalls) == 0 {
    95  			uninstalledPlugins[pluginRequirement.Identifier.String()] = pluginRequirement.VersionConstraints.String()
    96  			continue
    97  		}
    98  		log.Printf("[TRACE] Found the following %q installations: %v", pluginRequirement.Identifier, sortedInstalls)
    99  		install := sortedInstalls[len(sortedInstalls)-1]
   100  		err = cfg.parser.PluginConfig.DiscoverMultiPlugin(pluginRequirement.Accessor, install.BinaryPath)
   101  		if err != nil {
   102  			diags = append(diags, &hcl.Diagnostic{
   103  				Severity: hcl.DiagError,
   104  				Summary:  fmt.Sprintf("Error discovering plugin %s", pluginRequirement.Identifier),
   105  				Detail:   err.Error(),
   106  			})
   107  			continue
   108  		}
   109  	}
   110  
   111  	if len(uninstalledPlugins) > 0 {
   112  		detailMessage := &strings.Builder{}
   113  		detailMessage.WriteString("The following plugins are required, but not installed:\n\n")
   114  		for pluginName, pluginVersion := range uninstalledPlugins {
   115  			fmt.Fprintf(detailMessage, "* %s %s\n", pluginName, pluginVersion)
   116  		}
   117  		detailMessage.WriteString("\nDid you run packer init for this project ?")
   118  		diags = append(diags, &hcl.Diagnostic{
   119  			Severity: hcl.DiagError,
   120  			Summary:  "Missing plugins",
   121  			Detail:   detailMessage.String(),
   122  		})
   123  	}
   124  
   125  	// Do a second pass to discover the remaining installed plugins
   126  	err := cfg.parser.PluginConfig.Discover()
   127  	if err != nil {
   128  		return (hcl.Diagnostics{}).Append(&hcl.Diagnostic{
   129  			Severity: hcl.DiagError,
   130  			Summary:  "Failed to discover installed plugins",
   131  			Detail:   err.Error(),
   132  		})
   133  	}
   134  
   135  	return diags
   136  }
   137  
   138  func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
   139  	// verify that all used plugins do exist
   140  	var diags hcl.Diagnostics
   141  
   142  	for _, build := range cfg.Builds {
   143  		for i := range build.Sources {
   144  			// here we grab a pointer to the source usage because we will set
   145  			// its body.
   146  			srcUsage := &(build.Sources[i])
   147  			if !cfg.parser.PluginConfig.Builders.Has(srcUsage.Type) {
   148  				detail := fmt.Sprintf(
   149  					"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
   150  						"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
   151  						"https://developer.hashicorp.com/packer/integrations?filter=%s",
   152  					buildSourceLabel,
   153  					srcUsage.Type,
   154  					strings.Split(srcUsage.Type, "-")[0],
   155  				)
   156  
   157  				if sugg := didyoumean.NameSuggestion(srcUsage.Type, cfg.parser.PluginConfig.Builders.List()); sugg != "" {
   158  					detail = fmt.Sprintf("Did you mean to use %q?", sugg)
   159  				}
   160  				diags = append(diags, &hcl.Diagnostic{
   161  					Summary:  "Unknown " + buildSourceLabel + " type " + srcUsage.Type,
   162  					Subject:  &build.HCL2Ref.DefRange,
   163  					Detail:   detail,
   164  					Severity: hcl.DiagError,
   165  				})
   166  				continue
   167  			}
   168  
   169  			sourceDefinition, found := cfg.Sources[srcUsage.SourceRef]
   170  			if !found {
   171  				availableSrcs := listAvailableSourceNames(cfg.Sources)
   172  				detail := fmt.Sprintf("Known: %v", availableSrcs)
   173  				if sugg := didyoumean.NameSuggestion(srcUsage.SourceRef.String(), availableSrcs); sugg != "" {
   174  					detail = fmt.Sprintf("Did you mean to use %q?", sugg)
   175  				}
   176  				diags = append(diags, &hcl.Diagnostic{
   177  					Summary:  "Unknown " + sourceLabel + " " + srcUsage.SourceRef.String(),
   178  					Subject:  build.HCL2Ref.DefRange.Ptr(),
   179  					Severity: hcl.DiagError,
   180  					Detail:   detail,
   181  				})
   182  				continue
   183  			}
   184  
   185  			body := sourceDefinition.block.Body
   186  			if srcUsage.Body != nil {
   187  				// merge additions into source definition to get a new body.
   188  				body = hcl.MergeBodies([]hcl.Body{body, srcUsage.Body})
   189  			}
   190  
   191  			srcUsage.Body = body
   192  		}
   193  
   194  		for _, provBlock := range build.ProvisionerBlocks {
   195  			if !cfg.parser.PluginConfig.Provisioners.Has(provBlock.PType) {
   196  				detail := fmt.Sprintf(
   197  					"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
   198  						"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
   199  						"https://developer.hashicorp.com/packer/integrations?filter=%s",
   200  					buildProvisionerLabel,
   201  					provBlock.PType,
   202  					strings.Split(provBlock.PType, "-")[0],
   203  				)
   204  
   205  				if sugg := didyoumean.NameSuggestion(provBlock.PType, cfg.parser.PluginConfig.Provisioners.List()); sugg != "" {
   206  					detail = fmt.Sprintf("Did you mean to use %q?", sugg)
   207  				}
   208  
   209  				diags = append(diags, &hcl.Diagnostic{
   210  					Summary:  fmt.Sprintf("Unknown "+buildProvisionerLabel+" type %q", provBlock.PType),
   211  					Subject:  provBlock.HCL2Ref.TypeRange.Ptr(),
   212  					Detail:   detail,
   213  					Severity: hcl.DiagError,
   214  				})
   215  			}
   216  		}
   217  
   218  		if build.ErrorCleanupProvisionerBlock != nil {
   219  			if !cfg.parser.PluginConfig.Provisioners.Has(build.ErrorCleanupProvisionerBlock.PType) {
   220  				detail := fmt.Sprintf(
   221  					"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
   222  						"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
   223  						"https://developer.hashicorp.com/packer/integrations?filter=%s",
   224  					buildErrorCleanupProvisionerLabel,
   225  					build.ErrorCleanupProvisionerBlock.PType,
   226  					strings.Split(build.ErrorCleanupProvisionerBlock.PType, "-")[0],
   227  				)
   228  
   229  				if sugg := didyoumean.NameSuggestion(build.ErrorCleanupProvisionerBlock.PType, cfg.parser.PluginConfig.Provisioners.List()); sugg != "" {
   230  					detail = fmt.Sprintf("Did you mean to use %q?", sugg)
   231  				}
   232  
   233  				diags = append(diags, &hcl.Diagnostic{
   234  					Summary:  fmt.Sprintf("Unknown "+buildErrorCleanupProvisionerLabel+" type %q", build.ErrorCleanupProvisionerBlock.PType),
   235  					Subject:  build.ErrorCleanupProvisionerBlock.HCL2Ref.TypeRange.Ptr(),
   236  					Detail:   detail,
   237  					Severity: hcl.DiagError,
   238  				})
   239  			}
   240  		}
   241  
   242  		for _, ppList := range build.PostProcessorsLists {
   243  			for _, ppBlock := range ppList {
   244  				if !cfg.parser.PluginConfig.PostProcessors.Has(ppBlock.PType) {
   245  					detail := fmt.Sprintf(
   246  						"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
   247  							"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
   248  							"https://developer.hashicorp.com/packer/integrations?filter=%s",
   249  						buildPostProcessorLabel,
   250  						ppBlock.PType,
   251  						strings.Split(ppBlock.PType, "-")[0],
   252  					)
   253  
   254  					if sugg := didyoumean.NameSuggestion(ppBlock.PType, cfg.parser.PluginConfig.PostProcessors.List()); sugg != "" {
   255  						detail = fmt.Sprintf("Did you mean to use %q?", sugg)
   256  					}
   257  
   258  					diags = append(diags, &hcl.Diagnostic{
   259  						Summary:  fmt.Sprintf("Unknown "+buildPostProcessorLabel+" type %q", ppBlock.PType),
   260  						Subject:  ppBlock.HCL2Ref.TypeRange.Ptr(),
   261  						Detail:   detail,
   262  						Severity: hcl.DiagError,
   263  					})
   264  				}
   265  			}
   266  		}
   267  
   268  	}
   269  
   270  	return diags
   271  }