@ -6,18 +6,24 @@ package command
import (
"context"
"crypto/sha256"
"encoding/json"
"flag"
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer-plugin-sdk/plugin"
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
"github.com/hashicorp/packer/hcl2template/addrs"
"github.com/hashicorp/packer/packer"
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
"github.com/hashicorp/packer/packer/plugin-getter/github"
pkrversion "github.com/hashicorp/packer/version"
"github.com/mitchellh/cli"
)
type PluginsInstallCommand struct {
@ -30,7 +36,7 @@ func (c *PluginsInstallCommand) Synopsis() string {
func ( c * PluginsInstallCommand ) Help ( ) string {
helpText := `
Usage : packer plugins install <plugin > [ < version constraint > ]
Usage : packer plugins install [OPTIONS ... ] <plugin > [ < version constraint > ]
This command will install the most recent compatible Packer plugin matching
version constraint .
@ -38,6 +44,12 @@ Usage: packer plugins install <plugin> [<version constraint>]
installed .
Ex : packer plugins install github . com / hashicorp / happycloud v1 .2 .3
Options :
- path < path > : install the plugin from a locally - sourced plugin binary . This
installs the plugin where a normal invocation would , but will
not try to download it from a web server , but instead directly
install the binary for Packer to be able to load it later on .
`
return strings . TrimSpace ( helpText )
@ -47,14 +59,55 @@ func (c *PluginsInstallCommand) Run(args []string) int {
ctx , cleanup := handleTermInterrupt ( c . Ui )
defer cleanup ( )
return c . RunContext ( ctx , args )
cmdArgs , ret := c . ParseArgs ( args )
if ret != 0 {
return ret
}
return c . RunContext ( ctx , cmdArgs )
}
type PluginsInstallArgs struct {
MetaArgs
PluginName string
PluginPath string
Version string
}
func ( pa * PluginsInstallArgs ) AddFlagSets ( flags * flag . FlagSet ) {
flags . StringVar ( & pa . PluginPath , "path" , "" , "install the plugin from a specific path" )
pa . MetaArgs . AddFlagSets ( flags )
}
func ( c * PluginsInstallCommand ) RunContext ( buildCtx context . Context , args [ ] string ) int {
func ( c * PluginsInstallCommand ) ParseArgs ( args [ ] string ) ( * PluginsInstallArgs , int ) {
pa := & PluginsInstallArgs { }
flags := c . Meta . FlagSet ( "plugins install" )
flags . Usage = func ( ) { c . Ui . Say ( c . Help ( ) ) }
pa . AddFlagSets ( flags )
err := flags . Parse ( args )
if err != nil {
c . Ui . Error ( fmt . Sprintf ( "Failed to parse options: %s" , err ) )
return pa , 1
}
args = flags . Args ( )
if len ( args ) < 1 || len ( args ) > 2 {
return cli . RunResultHelp
c . Ui . Error ( fmt . Sprintf ( "Invalid arguments, expected either 1 or 2 positional arguments, got %d" , len ( args ) ) )
flags . Usage ( )
return pa , 1
}
if len ( args ) == 2 {
pa . Version = args [ 1 ]
}
pa . PluginName = args [ 0 ]
return pa , 0
}
func ( c * PluginsInstallCommand ) RunContext ( buildCtx context . Context , args * PluginsInstallArgs ) int {
opts := plugingetter . ListInstallationsOptions {
FromFolders : c . Meta . CoreConfig . Components . PluginConfig . KnownPluginFolders ,
BinaryInstallationOptions : plugingetter . BinaryInstallationOptions {
@ -68,19 +121,25 @@ func (c *PluginsInstallCommand) RunContext(buildCtx context.Context, args []stri
} ,
}
plugin , diags := addrs . ParsePluginSourceString ( args [0 ] )
plugin , diags := addrs . ParsePluginSourceString ( args .PluginName )
if diags . HasErrors ( ) {
c . Ui . Error ( diags . Error ( ) )
return 1
}
// If we did specify a binary to install the plugin from, we ignore
// the Github-based getter in favour of installing it directly.
if args . PluginPath != "" {
return c . InstallFromBinary ( args )
}
// a plugin requirement that matches them all
pluginRequirement := plugingetter . Requirement {
Identifier : plugin ,
}
if len ( args ) > 1 {
constraints , err := version . NewConstraint ( args [1 ] )
if args . Version != "" {
constraints , err := version . NewConstraint ( args .Version )
if err != nil {
c . Ui . Error ( err . Error ( ) )
return 1
@ -128,3 +187,165 @@ func (c *PluginsInstallCommand) RunContext(buildCtx context.Context, args []stri
return 0
}
func ( c * PluginsInstallCommand ) InstallFromBinary ( args * PluginsInstallArgs ) int {
pluginDirs := c . Meta . CoreConfig . Components . PluginConfig . KnownPluginFolders
if len ( pluginDirs ) == 0 {
c . Ui . Say ( ` Error : cannot find a place to install the plugin to
In order to install the plugin for later use , Packer needs to know where to
install them .
This can be specified through the PACKER_CONFIG_DIR environment variable ,
but should be automatically inferred by Packer .
If you see this message , this is likely a Packer bug , please consider opening
an issue on our Github repo to signal it . ` )
}
pluginSlugParts := strings . Split ( args . PluginName , "/" )
if len ( pluginSlugParts ) != 3 {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid plugin name specifier" ,
Detail : fmt . Sprintf ( "The plugin name specified provided (%q) does not conform to the mandated format of <host>/<org>/<plugin-name>." , args . PluginName ) ,
} } )
}
// As with the other commands, we get the last plugin directory as it
// has precedence over the others, and is where we'll install the
// plugins to.
pluginDir := pluginDirs [ len ( pluginDirs ) - 1 ]
s , err := os . Stat ( args . PluginPath )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Unable to find plugin to promote" ,
Detail : fmt . Sprintf ( "The plugin %q failed to be opened because of an error: %s" , args . PluginName , err ) ,
} } )
}
if s . IsDir ( ) {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Plugin to promote cannot be a directory" ,
Detail : "The packer plugin promote command can only install binaries, not directories" ,
} } )
}
describeCmd , err := exec . Command ( args . PluginPath , "describe" ) . Output ( )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to describe the plugin" ,
Detail : fmt . Sprintf ( "Packer failed to run %s describe: %s" , args . PluginPath , err ) ,
} } )
}
var desc plugin . SetDescription
if err := json . Unmarshal ( describeCmd , & desc ) ; err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to decode plugin describe info" ,
Detail : fmt . Sprintf ( "'%s describe' produced information that Packer couldn't decode: %s" , args . PluginPath , err ) ,
} } )
}
// Let's override the plugin's version if we specify it in the options
// of the command
if args . Version != "" {
desc . Version = args . Version
}
pluginBinary , err := os . Open ( args . PluginPath )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to open plugin binary" ,
Detail : fmt . Sprintf ( "Failed to open plugin binary from %q: %s" , args . PluginPath , err ) ,
} } )
}
defer pluginBinary . Close ( )
// We'll install the SHA256SUM file alongside the plugin, based on the
// contents of the plugin being passed.
//
// This will make our loaders happy as they require a valid checksum
// for loading plugins installed this way.
shasum := sha256 . New ( )
_ , err = io . Copy ( shasum , pluginBinary )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to read plugin binary's contents" ,
Detail : fmt . Sprintf ( "Failed to read plugin binary from %q: %s" , args . PluginPath , err ) ,
} } )
}
// At this point, we know the provided binary behaves correctly with
// describe, so it's very likely to be a plugin, let's install it.
installDir := fmt . Sprintf ( "%s/%s" , pluginDir , args . PluginName )
err = os . MkdirAll ( installDir , 0755 )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to create output directory" ,
Detail : fmt . Sprintf ( "The installation directory %q failed to be created because of an error: %s" , installDir , err ) ,
} } )
}
binaryPath := fmt . Sprintf (
"%s/packer-plugin-%s_v%s_%s_%s_%s" ,
installDir ,
pluginSlugParts [ 2 ] ,
desc . Version ,
desc . APIVersion ,
runtime . GOOS ,
runtime . GOARCH ,
)
outputPlugin , err := os . OpenFile ( binaryPath , os . O_CREATE | os . O_TRUNC | os . O_RDWR , 0755 )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to create plugin binary" ,
Detail : fmt . Sprintf ( "Failed to create plugin binary at %q: %s" , binaryPath , err ) ,
} } )
}
defer outputPlugin . Close ( )
_ , err = pluginBinary . Seek ( 0 , 0 )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to reset plugin's reader" ,
Detail : fmt . Sprintf ( "Failed to seek offset 0 while attempting to reset the buffer for the plugin to install: %s" , err ) ,
} } )
}
_ , err = io . Copy ( outputPlugin , pluginBinary )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to copy plugin binary's contents" ,
Detail : fmt . Sprintf ( "Failed to copy plugin binary from %q to %q: %s" , args . PluginPath , binaryPath , err ) ,
} } )
}
shasumPath := fmt . Sprintf ( "%s_SHA256SUM" , binaryPath )
shaFile , err := os . OpenFile ( shasumPath , os . O_CREATE | os . O_TRUNC | os . O_RDWR , 0644 )
if err != nil {
return writeDiags ( c . Ui , nil , hcl . Diagnostics { & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Failed to create plugin SHA256SUM file" ,
Detail : fmt . Sprintf ( "Failed to create SHA256SUM file at %q: %s" , shasumPath , err ) ,
} } )
}
defer shaFile . Close ( )
fmt . Fprintf ( shaFile , "%x" , shasum . Sum ( [ ] byte { } ) )
c . Ui . Say ( fmt . Sprintf ( "Successfully installed plugin %s from %s to %s" , args . PluginName , args . PluginPath , binaryPath ) )
return 0
}