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 }