mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-25 01:04:00 -04:00
This follows #8232 which added the code to generate the code required to parse HCL files for each packer component. All old config files of packer will keep on working the same. Packer takes one argument. When a directory is passed, all files in the folder with a name ending with “.pkr.hcl” or “.pkr.json” will be parsed using the HCL2 format. When a file ending with “.pkr.hcl” or “.pkr.json” is passed it will be parsed using the HCL2 format. For every other case; the old packer style will be used. ## 1. the hcl2template pkg can create a packer.Build from a set of HCL (v2) files I had to make the packer.coreBuild (which is our one and only packer.Build ) a public struct with public fields ## 2. Components interfaces get a new ConfigSpec Method to read a file from an HCL file. This is a breaking change for packer plugins. a packer component can be a: builder/provisioner/post-processor each component interface now gets a `ConfigSpec() hcldec.ObjectSpec` which allows packer to tell what is the layout of the hcl2 config meant to configure that specific component. This ObjectSpec is sent through the wire (RPC) and a cty.Value is now sent through the already existing configuration entrypoints: Provisioner.Prepare(raws ...interface{}) error Builder.Prepare(raws ...interface{}) ([]string, error) PostProcessor.Configure(raws ...interface{}) error close #1768 Example hcl files: ```hcl // file amazon-ebs-kms-key/run.pkr.hcl build { sources = [ "source.amazon-ebs.first", ] provisioner "shell" { inline = [ "sleep 5" ] } post-processor "shell-local" { inline = [ "sleep 5" ] } } // amazon-ebs-kms-key/source.pkr.hcl source "amazon-ebs" "first" { ami_name = "hcl2-test" region = "us-east-1" instance_type = "t2.micro" kms_key_id = "c729958f-c6ba-44cd-ab39-35ab68ce0a6c" encrypt_boot = true source_ami_filter { filters { virtualization-type = "hvm" name = "amzn-ami-hvm-????.??.?.????????-x86_64-gp2" root-device-type = "ebs" } most_recent = true owners = ["amazon"] } launch_block_device_mappings { device_name = "/dev/xvda" volume_size = 20 volume_type = "gp2" delete_on_termination = "true" } launch_block_device_mappings { device_name = "/dev/xvdf" volume_size = 500 volume_type = "gp2" delete_on_termination = true encrypted = true } ami_regions = ["eu-central-1"] run_tags { Name = "packer-solr-something" stack-name = "DevOps Tools" } communicator = "ssh" ssh_pty = true ssh_username = "ec2-user" associate_public_ip_address = true } ```
149 lines
3.7 KiB
Go
149 lines
3.7 KiB
Go
// The plugin package provides the functionality to both expose a Packer
|
|
// plugin binary and to connect to an existing Packer plugin binary.
|
|
//
|
|
// Packer supports plugins in the form of self-contained external static
|
|
// Go binaries. These binaries behave in a certain way (enforced by this
|
|
// package) and are connected to in a certain way (also enforced by this
|
|
// package).
|
|
package plugin
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"os/signal"
|
|
"runtime"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
packrpc "github.com/hashicorp/packer/packer/rpc"
|
|
"github.com/hashicorp/packer/packer/tmp"
|
|
)
|
|
|
|
// This is a count of the number of interrupts the process has received.
|
|
// This is updated with sync/atomic whenever a SIGINT is received and can
|
|
// be checked by the plugin safely to take action.
|
|
var Interrupts int32 = 0
|
|
|
|
const MagicCookieKey = "PACKER_PLUGIN_MAGIC_COOKIE"
|
|
const MagicCookieValue = "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2"
|
|
|
|
// The APIVersion is outputted along with the RPC address. The plugin
|
|
// client validates this API version and will show an error if it doesn't
|
|
// know how to speak it.
|
|
const APIVersion = "4"
|
|
|
|
// Server waits for a connection to this plugin and returns a Packer
|
|
// RPC server that you can use to register components and serve them.
|
|
func Server() (*packrpc.Server, error) {
|
|
if os.Getenv(MagicCookieKey) != MagicCookieValue {
|
|
return nil, errors.New(
|
|
"Please do not execute plugins directly. Packer will execute these for you.")
|
|
}
|
|
|
|
// If there is no explicit number of Go threads to use, then set it
|
|
if os.Getenv("GOMAXPROCS") == "" {
|
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
}
|
|
|
|
listener, err := serverListener()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer listener.Close()
|
|
|
|
// Output the address to stdout
|
|
log.Printf("Plugin address: %s %s\n",
|
|
listener.Addr().Network(), listener.Addr().String())
|
|
fmt.Printf("%s|%s|%s\n",
|
|
APIVersion,
|
|
listener.Addr().Network(),
|
|
listener.Addr().String())
|
|
os.Stdout.Sync()
|
|
|
|
// Accept a connection
|
|
log.Println("Waiting for connection...")
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
log.Printf("Error accepting connection: %s\n", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
// Eat the interrupts
|
|
ch := make(chan os.Signal, 1)
|
|
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
|
go func() {
|
|
var count int32 = 0
|
|
for {
|
|
<-ch
|
|
newCount := atomic.AddInt32(&count, 1)
|
|
log.Printf("Received interrupt signal (count: %d). Ignoring.", newCount)
|
|
}
|
|
}()
|
|
|
|
// Serve a single connection
|
|
log.Println("Serving a plugin connection...")
|
|
return packrpc.NewServer(conn)
|
|
}
|
|
|
|
func serverListener() (net.Listener, error) {
|
|
if runtime.GOOS == "windows" {
|
|
return serverListener_tcp()
|
|
}
|
|
|
|
return serverListener_unix()
|
|
}
|
|
|
|
func serverListener_tcp() (net.Listener, error) {
|
|
minPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MIN_PORT"), 10, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
maxPort, err := strconv.ParseInt(os.Getenv("PACKER_PLUGIN_MAX_PORT"), 10, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Printf("Plugin port range: [%d,%d]", minPort, maxPort)
|
|
|
|
for port := minPort; port <= maxPort; port++ {
|
|
address := fmt.Sprintf("127.0.0.1:%d", port)
|
|
listener, err := net.Listen("tcp", address)
|
|
if err == nil {
|
|
return listener, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("Couldn't bind plugin TCP listener")
|
|
}
|
|
|
|
func serverListener_unix() (net.Listener, error) {
|
|
tf, err := tmp.File("packer-plugin")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
path := tf.Name()
|
|
|
|
// Close the file and remove it because it has to not exist for
|
|
// the domain socket.
|
|
if err := tf.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := os.Remove(path); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return net.Listen("unix", path)
|
|
}
|
|
|
|
func init() {
|
|
// Seed the random number generator
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
}
|