mirror of https://github.com/hashicorp/packer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
9.4 KiB
272 lines
9.4 KiB
// Copyright IBM Corp. 2013, 2025
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package hcl2template
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/packer-plugin-sdk/didyoumean"
|
|
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
|
|
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
|
|
)
|
|
|
|
// PluginRequirements returns a sorted list of plugin requirements.
|
|
func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Diagnostics) {
|
|
|
|
var diags hcl.Diagnostics
|
|
var reqs plugingetter.Requirements
|
|
reqPluginsBlocks := cfg.Packer.RequiredPlugins
|
|
|
|
// Take all required plugins, make sure there are no conflicting blocks
|
|
// and append them to the list.
|
|
uniq := map[string]*RequiredPlugin{}
|
|
for _, requiredPluginsBlock := range reqPluginsBlocks {
|
|
for name, block := range requiredPluginsBlock.RequiredPlugins {
|
|
|
|
if previouslySeenBlock, found := uniq[name]; found {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: fmt.Sprintf("Duplicate required_plugin.%q block", name),
|
|
Detail: fmt.Sprintf("Block previously seen at %s is already named %q.\n", previouslySeenBlock.DeclRange, name) +
|
|
"Names at the left hand side of required_plugins are made available to use in your HCL2 configurations.\n" +
|
|
"To allow to calling to their features correctly two plugins have to have different accessors.",
|
|
Context: &block.DeclRange,
|
|
})
|
|
continue
|
|
}
|
|
|
|
reqs = append(reqs, &plugingetter.Requirement{
|
|
Accessor: name,
|
|
Identifier: block.Type,
|
|
VersionConstraints: block.Requirement.Required,
|
|
})
|
|
uniq[name] = block
|
|
}
|
|
|
|
}
|
|
|
|
return reqs, diags
|
|
}
|
|
|
|
func (cfg *PackerConfig) DetectPluginBinaries() hcl.Diagnostics {
|
|
// Then we can apply any constraint from the template, if any
|
|
opts := plugingetter.ListInstallationsOptions{
|
|
PluginDirectory: cfg.parser.PluginConfig.PluginDirectory,
|
|
BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
|
|
OS: runtime.GOOS,
|
|
ARCH: runtime.GOARCH,
|
|
APIVersionMajor: pluginsdk.APIVersionMajor,
|
|
APIVersionMinor: pluginsdk.APIVersionMinor,
|
|
Checksummers: []plugingetter.Checksummer{
|
|
{Type: "sha256", Hash: sha256.New()},
|
|
},
|
|
ReleasesOnly: cfg.parser.PluginConfig.ReleasesOnly,
|
|
},
|
|
}
|
|
|
|
if runtime.GOOS == "windows" && opts.Ext == "" {
|
|
opts.BinaryInstallationOptions.Ext = ".exe"
|
|
}
|
|
|
|
pluginReqs, diags := cfg.PluginRequirements()
|
|
if diags.HasErrors() {
|
|
return diags
|
|
}
|
|
|
|
uninstalledPlugins := map[string]string{}
|
|
|
|
for _, pluginRequirement := range pluginReqs {
|
|
sortedInstalls, err := pluginRequirement.ListInstallations(opts)
|
|
if err != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: fmt.Sprintf("Failed to list installation for %s", pluginRequirement.Identifier),
|
|
Detail: err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
if len(sortedInstalls) == 0 {
|
|
uninstalledPlugins[pluginRequirement.Identifier.String()] = pluginRequirement.VersionConstraints.String()
|
|
continue
|
|
}
|
|
log.Printf("[TRACE] Found the following %q installations: %v", pluginRequirement.Identifier, sortedInstalls)
|
|
install := sortedInstalls[len(sortedInstalls)-1]
|
|
err = cfg.parser.PluginConfig.DiscoverMultiPlugin(pluginRequirement.Accessor, install.BinaryPath)
|
|
if err != nil {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: fmt.Sprintf("Error discovering plugin %s", pluginRequirement.Identifier),
|
|
Detail: err.Error(),
|
|
})
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(uninstalledPlugins) > 0 {
|
|
detailMessage := &strings.Builder{}
|
|
detailMessage.WriteString("The following plugins are required, but not installed:\n\n")
|
|
for pluginName, pluginVersion := range uninstalledPlugins {
|
|
fmt.Fprintf(detailMessage, "* %s %s\n", pluginName, pluginVersion)
|
|
}
|
|
detailMessage.WriteString("\nDid you run packer init for this project ?")
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Missing plugins",
|
|
Detail: detailMessage.String(),
|
|
})
|
|
}
|
|
|
|
// Do a second pass to discover the remaining installed plugins
|
|
err := cfg.parser.PluginConfig.Discover()
|
|
if err != nil {
|
|
return (hcl.Diagnostics{}).Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Failed to discover installed plugins",
|
|
Detail: err.Error(),
|
|
})
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
|
|
// verify that all used plugins do exist
|
|
var diags hcl.Diagnostics
|
|
|
|
for _, build := range cfg.Builds {
|
|
for i := range build.Sources {
|
|
// here we grab a pointer to the source usage because we will set
|
|
// its body.
|
|
srcUsage := &(build.Sources[i])
|
|
if !cfg.parser.PluginConfig.Builders.Has(srcUsage.Type) {
|
|
detail := fmt.Sprintf(
|
|
"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
|
|
"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
|
|
"https://developer.hashicorp.com/packer/integrations?filter=%s",
|
|
buildSourceLabel,
|
|
srcUsage.Type,
|
|
strings.Split(srcUsage.Type, "-")[0],
|
|
)
|
|
|
|
if sugg := didyoumean.NameSuggestion(srcUsage.Type, cfg.parser.PluginConfig.Builders.List()); sugg != "" {
|
|
detail = fmt.Sprintf("Did you mean to use %q?", sugg)
|
|
}
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: "Unknown " + buildSourceLabel + " type " + srcUsage.Type,
|
|
Subject: &build.HCL2Ref.DefRange,
|
|
Detail: detail,
|
|
Severity: hcl.DiagError,
|
|
})
|
|
continue
|
|
}
|
|
|
|
sourceDefinition, found := cfg.Sources[srcUsage.SourceRef]
|
|
if !found {
|
|
availableSrcs := listAvailableSourceNames(cfg.Sources)
|
|
detail := fmt.Sprintf("Known: %v", availableSrcs)
|
|
if sugg := didyoumean.NameSuggestion(srcUsage.SourceRef.String(), availableSrcs); sugg != "" {
|
|
detail = fmt.Sprintf("Did you mean to use %q?", sugg)
|
|
}
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: "Unknown " + sourceLabel + " " + srcUsage.SourceRef.String(),
|
|
Subject: build.HCL2Ref.DefRange.Ptr(),
|
|
Severity: hcl.DiagError,
|
|
Detail: detail,
|
|
})
|
|
continue
|
|
}
|
|
|
|
body := sourceDefinition.block.Body
|
|
if srcUsage.Body != nil {
|
|
// merge additions into source definition to get a new body.
|
|
body = hcl.MergeBodies([]hcl.Body{body, srcUsage.Body})
|
|
}
|
|
|
|
srcUsage.Body = body
|
|
}
|
|
|
|
for _, provBlock := range build.ProvisionerBlocks {
|
|
if !cfg.parser.PluginConfig.Provisioners.Has(provBlock.PType) {
|
|
detail := fmt.Sprintf(
|
|
"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
|
|
"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
|
|
"https://developer.hashicorp.com/packer/integrations?filter=%s",
|
|
buildProvisionerLabel,
|
|
provBlock.PType,
|
|
strings.Split(provBlock.PType, "-")[0],
|
|
)
|
|
|
|
if sugg := didyoumean.NameSuggestion(provBlock.PType, cfg.parser.PluginConfig.Provisioners.List()); sugg != "" {
|
|
detail = fmt.Sprintf("Did you mean to use %q?", sugg)
|
|
}
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: fmt.Sprintf("Unknown "+buildProvisionerLabel+" type %q", provBlock.PType),
|
|
Subject: provBlock.HCL2Ref.TypeRange.Ptr(),
|
|
Detail: detail,
|
|
Severity: hcl.DiagError,
|
|
})
|
|
}
|
|
}
|
|
|
|
if build.ErrorCleanupProvisionerBlock != nil {
|
|
if !cfg.parser.PluginConfig.Provisioners.Has(build.ErrorCleanupProvisionerBlock.PType) {
|
|
detail := fmt.Sprintf(
|
|
"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
|
|
"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
|
|
"https://developer.hashicorp.com/packer/integrations?filter=%s",
|
|
buildErrorCleanupProvisionerLabel,
|
|
build.ErrorCleanupProvisionerBlock.PType,
|
|
strings.Split(build.ErrorCleanupProvisionerBlock.PType, "-")[0],
|
|
)
|
|
|
|
if sugg := didyoumean.NameSuggestion(build.ErrorCleanupProvisionerBlock.PType, cfg.parser.PluginConfig.Provisioners.List()); sugg != "" {
|
|
detail = fmt.Sprintf("Did you mean to use %q?", sugg)
|
|
}
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: fmt.Sprintf("Unknown "+buildErrorCleanupProvisionerLabel+" type %q", build.ErrorCleanupProvisionerBlock.PType),
|
|
Subject: build.ErrorCleanupProvisionerBlock.HCL2Ref.TypeRange.Ptr(),
|
|
Detail: detail,
|
|
Severity: hcl.DiagError,
|
|
})
|
|
}
|
|
}
|
|
|
|
for _, ppList := range build.PostProcessorsLists {
|
|
for _, ppBlock := range ppList {
|
|
if !cfg.parser.PluginConfig.PostProcessors.Has(ppBlock.PType) {
|
|
detail := fmt.Sprintf(
|
|
"The %s %s is unknown by Packer, and is likely part of a plugin that is not installed.\n"+
|
|
"You may find the needed plugin along with installation instructions documented on the Packer integrations page.\n\n"+
|
|
"https://developer.hashicorp.com/packer/integrations?filter=%s",
|
|
buildPostProcessorLabel,
|
|
ppBlock.PType,
|
|
strings.Split(ppBlock.PType, "-")[0],
|
|
)
|
|
|
|
if sugg := didyoumean.NameSuggestion(ppBlock.PType, cfg.parser.PluginConfig.PostProcessors.List()); sugg != "" {
|
|
detail = fmt.Sprintf("Did you mean to use %q?", sugg)
|
|
}
|
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Summary: fmt.Sprintf("Unknown "+buildPostProcessorLabel+" type %q", ppBlock.PType),
|
|
Subject: ppBlock.HCL2Ref.TypeRange.Ptr(),
|
|
Detail: detail,
|
|
Severity: hcl.DiagError,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return diags
|
|
}
|