Merge branch 'master' into datasource_documentation
This commit is contained in:
commit
a4a70e0bb8
|
@ -70,9 +70,6 @@ type Driver interface {
|
||||||
// DeleteOSLoginSSHKey deletes the SSH public key for OSLogin with the given key.
|
// DeleteOSLoginSSHKey deletes the SSH public key for OSLogin with the given key.
|
||||||
DeleteOSLoginSSHKey(user, fingerprint string) error
|
DeleteOSLoginSSHKey(user, fingerprint string) error
|
||||||
|
|
||||||
// If packer is running on a GCE, derives the user from it for use with OSLogin.
|
|
||||||
GetOSLoginUserFromGCE() string
|
|
||||||
|
|
||||||
// Add to the instance metadata for the existing instance
|
// Add to the instance metadata for the existing instance
|
||||||
AddToInstanceMetadata(zone string, name string, metadata map[string]string) error
|
AddToInstanceMetadata(zone string, name string, metadata map[string]string) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
metadata "cloud.google.com/go/compute/metadata"
|
|
||||||
compute "google.golang.org/api/compute/v1"
|
compute "google.golang.org/api/compute/v1"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
oslogin "google.golang.org/api/oslogin/v1"
|
oslogin "google.golang.org/api/oslogin/v1"
|
||||||
|
@ -34,7 +32,6 @@ import (
|
||||||
type driverGCE struct {
|
type driverGCE struct {
|
||||||
projectId string
|
projectId string
|
||||||
service *compute.Service
|
service *compute.Service
|
||||||
thisGCEUser string
|
|
||||||
osLoginService *oslogin.Service
|
osLoginService *oslogin.Service
|
||||||
ui packersdk.Ui
|
ui packersdk.Ui
|
||||||
}
|
}
|
||||||
|
@ -136,8 +133,6 @@ func NewClientOptionGoogle(account *ServiceAccount, vaultOauth string, impersona
|
||||||
|
|
||||||
func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
|
func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
|
||||||
|
|
||||||
var thisGCEUser string
|
|
||||||
|
|
||||||
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
|
opts, err := NewClientOptionGoogle(config.Account, config.VaultOauthEngineName, config.ImpersonateServiceAccountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -149,15 +144,6 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if metadata.OnGCE() {
|
|
||||||
log.Printf("[INFO] On GCE, capture service account for OSLogin...")
|
|
||||||
thisGCEUser, err = metadata.NewClient(&http.Client{}).Email("")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("[INFO] Instantiating OS Login client...")
|
log.Printf("[INFO] Instantiating OS Login client...")
|
||||||
osLoginService, err := oslogin.NewService(context.TODO(), opts)
|
osLoginService, err := oslogin.NewService(context.TODO(), opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -170,7 +156,6 @@ func NewDriverGCE(config GCEDriverConfig) (Driver, error) {
|
||||||
return &driverGCE{
|
return &driverGCE{
|
||||||
projectId: config.ProjectId,
|
projectId: config.ProjectId,
|
||||||
service: service,
|
service: service,
|
||||||
thisGCEUser: thisGCEUser,
|
|
||||||
osLoginService: osLoginService,
|
osLoginService: osLoginService,
|
||||||
ui: config.Ui,
|
ui: config.Ui,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -644,10 +629,6 @@ func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswo
|
||||||
return passwordResponses, nil
|
return passwordResponses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *driverGCE) GetOSLoginUserFromGCE() string {
|
|
||||||
return d.thisGCEUser
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *driverGCE) ImportOSLoginSSHKey(user, sshPublicKey string) (*oslogin.LoginProfile, error) {
|
func (d *driverGCE) ImportOSLoginSSHKey(user, sshPublicKey string) (*oslogin.LoginProfile, error) {
|
||||||
parent := fmt.Sprintf("users/%s", user)
|
parent := fmt.Sprintf("users/%s", user)
|
||||||
|
|
||||||
|
|
|
@ -94,8 +94,6 @@ type DriverMock struct {
|
||||||
AddToInstanceMetadataKVPairs map[string]string
|
AddToInstanceMetadataKVPairs map[string]string
|
||||||
AddToInstanceMetadataErrCh <-chan error
|
AddToInstanceMetadataErrCh <-chan error
|
||||||
AddToInstanceMetadataErr error
|
AddToInstanceMetadataErr error
|
||||||
|
|
||||||
OSLoginUserFromGCE string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string, image_encryption_key *compute.CustomerEncryptionKey, imageStorageLocations []string) (<-chan *Image, <-chan error) {
|
func (d *DriverMock) CreateImage(name, description, family, zone, disk string, image_labels map[string]string, image_licenses []string, image_encryption_key *compute.CustomerEncryptionKey, imageStorageLocations []string) (<-chan *Image, <-chan error) {
|
||||||
|
@ -310,7 +308,3 @@ func (d *DriverMock) AddToInstanceMetadata(zone string, name string, metadata ma
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DriverMock) GetOSLoginUserFromGCE() string {
|
|
||||||
return d.OSLoginUserFromGCE
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,11 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
metadata "cloud.google.com/go/compute/metadata"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
"github.com/hashicorp/packer-plugin-sdk/multistep"
|
||||||
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
||||||
"google.golang.org/api/oauth2/v2"
|
"google.golang.org/api/oauth2/v2"
|
||||||
|
@ -37,7 +41,7 @@ func (s *StepImportOSLoginSSHKey) Run(ctx context.Context, state multistep.State
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we running packer on a GCE ?
|
// Are we running packer on a GCE ?
|
||||||
s.accountEmail = driver.GetOSLoginUserFromGCE()
|
s.accountEmail = getGCEUser()
|
||||||
|
|
||||||
if s.TokeninfoFunc == nil && s.accountEmail == "" {
|
if s.TokeninfoFunc == nil && s.accountEmail == "" {
|
||||||
s.TokeninfoFunc = tokeninfo
|
s.TokeninfoFunc = tokeninfo
|
||||||
|
@ -140,3 +144,28 @@ func tokeninfo(ctx context.Context) (*oauth2.Tokeninfo, error) {
|
||||||
|
|
||||||
return svc.Tokeninfo().Context(ctx).Do()
|
return svc.Tokeninfo().Context(ctx).Do()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getGCEUser determines if we're running packer on a GCE, and if we are, gets the associated service account email for subsequent use with OSLogin.
|
||||||
|
// There are cases where we are running on a GCE, but the GCP metadata server isn't accessible. GitLab docker-engine runners are an edge case example of this.
|
||||||
|
// It makes little sense to run packer on GCP in this way, however, we defensively timeout in those cases, rather than abort.
|
||||||
|
func getGCEUser() string {
|
||||||
|
|
||||||
|
metadataCheckTimeout := 5 * time.Second
|
||||||
|
metadataCheckChl := make(chan string, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if metadata.OnGCE() {
|
||||||
|
GCEUser, _ := metadata.NewClient(&http.Client{}).Email("")
|
||||||
|
metadataCheckChl <- GCEUser
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case thisGCEUser := <-metadataCheckChl:
|
||||||
|
log.Printf("[INFO] OSLogin: GCE service account %s will be used for identity", thisGCEUser)
|
||||||
|
return thisGCEUser
|
||||||
|
case <-time.After(metadataCheckTimeout):
|
||||||
|
log.Printf("[INFO] OSLogin: Could not derive a GCE service account from google metadata server after %s", metadataCheckTimeout)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -150,37 +150,3 @@ func TestStepImportOSLoginSSHKey_withPrivateSSHKey(t *testing.T) {
|
||||||
t.Errorf("expected to not see a public key when using a dedicated private key, but got %q", pubKey)
|
t.Errorf("expected to not see a public key when using a dedicated private key, but got %q", pubKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStepImportOSLoginSSHKey_onGCE(t *testing.T) {
|
|
||||||
|
|
||||||
state := testState(t)
|
|
||||||
d := state.Get("driver").(*DriverMock)
|
|
||||||
step := new(StepImportOSLoginSSHKey)
|
|
||||||
defer step.Cleanup(state)
|
|
||||||
|
|
||||||
fakeAccountEmail := "testing@packer.io"
|
|
||||||
|
|
||||||
config := state.Get("config").(*Config)
|
|
||||||
config.UseOSLogin = true
|
|
||||||
config.Comm.SSHPublicKey = []byte{'k', 'e', 'y'}
|
|
||||||
|
|
||||||
d.OSLoginUserFromGCE = fakeAccountEmail
|
|
||||||
|
|
||||||
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
|
|
||||||
t.Fatalf("bad action: %#v", action)
|
|
||||||
}
|
|
||||||
|
|
||||||
if step.accountEmail != fakeAccountEmail {
|
|
||||||
t.Fatalf("expected accountEmail to be %q but got %q", fakeAccountEmail, step.accountEmail)
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, ok := state.GetOk("ssh_key_public_sha256")
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("expected to see a public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
sha256sum := sha256.Sum256(config.Comm.SSHPublicKey)
|
|
||||||
if pubKey != hex.EncodeToString(sha256sum[:]) {
|
|
||||||
t.Errorf("expected to see a matching public key, but got %q", pubKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ type FlatConfig struct {
|
||||||
ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format" hcl:"image_disk_format"`
|
ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format" hcl:"image_disk_format"`
|
||||||
ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags" hcl:"image_tags"`
|
ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags" hcl:"image_tags"`
|
||||||
ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk" hcl:"image_min_disk"`
|
ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk" hcl:"image_min_disk"`
|
||||||
|
SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"`
|
||||||
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
|
||||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
|
||||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
|
||||||
|
@ -177,6 +178,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||||
"image_disk_format": &hcldec.AttrSpec{Name: "image_disk_format", Type: cty.String, Required: false},
|
"image_disk_format": &hcldec.AttrSpec{Name: "image_disk_format", Type: cty.String, Required: false},
|
||||||
"image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.List(cty.String), Required: false},
|
"image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.List(cty.String), Required: false},
|
||||||
"image_min_disk": &hcldec.AttrSpec{Name: "image_min_disk", Type: cty.Number, Required: false},
|
"image_min_disk": &hcldec.AttrSpec{Name: "image_min_disk", Type: cty.Number, Required: false},
|
||||||
|
"skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false},
|
||||||
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
|
||||||
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
|
||||||
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
|
||||||
|
|
|
@ -33,6 +33,8 @@ type ImageConfig struct {
|
||||||
ImageTags []string `mapstructure:"image_tags" required:"false"`
|
ImageTags []string `mapstructure:"image_tags" required:"false"`
|
||||||
// Minimum disk size needed to boot image, in gigabytes.
|
// Minimum disk size needed to boot image, in gigabytes.
|
||||||
ImageMinDisk int `mapstructure:"image_min_disk" required:"false"`
|
ImageMinDisk int `mapstructure:"image_min_disk" required:"false"`
|
||||||
|
// Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
|
||||||
|
SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
|
func (c *ImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||||
|
|
|
@ -12,10 +12,16 @@ import (
|
||||||
type stepAddImageMembers struct{}
|
type stepAddImageMembers struct{}
|
||||||
|
|
||||||
func (s *stepAddImageMembers) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepAddImageMembers) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
imageId := state.Get("image").(string)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
|
|
||||||
|
if config.SkipCreateImage {
|
||||||
|
ui.Say("Skipping image add members...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
imageId := state.Get("image").(string)
|
||||||
|
|
||||||
if len(config.ImageMembers) == 0 {
|
if len(config.ImageMembers) == 0 {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,11 @@ func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
|
||||||
server := state.Get("server").(*servers.Server)
|
server := state.Get("server").(*servers.Server)
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
|
|
||||||
|
if config.SkipCreateImage {
|
||||||
|
ui.Say("Skipping image creation...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
// We need the v2 compute client
|
// We need the v2 compute client
|
||||||
computeClient, err := config.computeV2Client()
|
computeClient, err := config.computeV2Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,10 +12,16 @@ import (
|
||||||
type stepUpdateImageMinDisk struct{}
|
type stepUpdateImageMinDisk struct{}
|
||||||
|
|
||||||
func (s *stepUpdateImageMinDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepUpdateImageMinDisk) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
imageId := state.Get("image").(string)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
|
|
||||||
|
if config.SkipCreateImage {
|
||||||
|
ui.Say("Skipping image update mindisk...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
imageId := state.Get("image").(string)
|
||||||
|
|
||||||
if config.ImageMinDisk == 0 {
|
if config.ImageMinDisk == 0 {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,16 @@ import (
|
||||||
type stepUpdateImageTags struct{}
|
type stepUpdateImageTags struct{}
|
||||||
|
|
||||||
func (s *stepUpdateImageTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepUpdateImageTags) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
imageId := state.Get("image").(string)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
|
|
||||||
|
if config.SkipCreateImage {
|
||||||
|
ui.Say("Skipping image update tags...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
imageId := state.Get("image").(string)
|
||||||
|
|
||||||
if len(config.ImageTags) == 0 {
|
if len(config.ImageTags) == 0 {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,16 @@ import (
|
||||||
type stepUpdateImageVisibility struct{}
|
type stepUpdateImageVisibility struct{}
|
||||||
|
|
||||||
func (s *stepUpdateImageVisibility) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *stepUpdateImageVisibility) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
imageId := state.Get("image").(string)
|
|
||||||
ui := state.Get("ui").(packersdk.Ui)
|
ui := state.Get("ui").(packersdk.Ui)
|
||||||
config := state.Get("config").(*Config)
|
config := state.Get("config").(*Config)
|
||||||
|
|
||||||
|
if config.SkipCreateImage {
|
||||||
|
ui.Say("Skipping image update visibility...")
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
imageId := state.Get("image").(string)
|
||||||
|
|
||||||
if config.ImageVisibility == "" {
|
if config.ImageVisibility == "" {
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,7 +114,7 @@ func (s *StepRegisterOMI) Cleanup(state multistep.StateBag) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []osc.BlockDeviceMappingImage {
|
func (s *StepRegisterOMI) combineDevices(snapshotIDs map[string]string) []osc.BlockDeviceMappingImage {
|
||||||
devices := map[string]osc.BlockDeviceMappingImage{}
|
devices := map[string]osc.BlockDeviceMappingImage{}
|
||||||
|
|
||||||
for _, device := range s.OMIDevices {
|
for _, device := range s.OMIDevices {
|
||||||
|
@ -125,12 +125,20 @@ func (s *StepRegisterOMI) combineDevices(snapshotIds map[string]string) []osc.Bl
|
||||||
// the same name in ami_block_device_mappings, except for the
|
// the same name in ami_block_device_mappings, except for the
|
||||||
// one designated as the root device in ami_root_device
|
// one designated as the root device in ami_root_device
|
||||||
for _, device := range s.LaunchDevices {
|
for _, device := range s.LaunchDevices {
|
||||||
snapshotId, ok := snapshotIds[device.DeviceName]
|
snapshotID, ok := snapshotIDs[device.DeviceName]
|
||||||
if ok {
|
if ok {
|
||||||
device.Bsu.SnapshotId = snapshotId
|
device.Bsu.SnapshotId = snapshotID
|
||||||
}
|
}
|
||||||
if device.DeviceName == s.RootDevice.SourceDeviceName {
|
if device.DeviceName == s.RootDevice.SourceDeviceName {
|
||||||
device.DeviceName = s.RootDevice.DeviceName
|
device.DeviceName = s.RootDevice.DeviceName
|
||||||
|
|
||||||
|
if device.Bsu.VolumeType != "" {
|
||||||
|
device.Bsu.VolumeType = s.RootDevice.VolumeType
|
||||||
|
if device.Bsu.VolumeType != "io1" {
|
||||||
|
device.Bsu.Iops = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
devices[device.DeviceName] = copyToDeviceMappingImage(device)
|
devices[device.DeviceName] = copyToDeviceMappingImage(device)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,11 @@ func (s *stepFinalizeTemplateConfig) Run(ctx context.Context, state multistep.St
|
||||||
ui.Error(err.Error())
|
ui.Error(err.Error())
|
||||||
return multistep.ActionHalt
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
err := fmt.Errorf("cloud_init is set to true, but cloud_init_storage_pool is empty and could not be set automatically. set cloud_init_storage_pool in your configuration")
|
||||||
|
state.Put("error", err)
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
texttemplate "text/template"
|
texttemplate "text/template"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||||
"github.com/hashicorp/packer-plugin-sdk/template"
|
"github.com/hashicorp/packer-plugin-sdk/template"
|
||||||
hcl2shim "github.com/hashicorp/packer/hcl2template/shim"
|
hcl2shim "github.com/hashicorp/packer/hcl2template/shim"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -55,7 +57,7 @@ func (c *HCL2UpgradeCommand) ParseArgs(args []string) (*HCL2UpgradeArgs, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hcl2UpgradeFileHeader = `# This file was autogenerated by the BETA 'packer hcl2_upgrade' command. We
|
hcl2UpgradeFileHeader = `# This file was autogenerated by the 'packer hcl2_upgrade' command. We
|
||||||
# recommend double checking that everything is correct before going forward. We
|
# recommend double checking that everything is correct before going forward. We
|
||||||
# also recommend treating this file as disposable. The HCL2 blocks in this
|
# also recommend treating this file as disposable. The HCL2 blocks in this
|
||||||
# file can be moved to other files. For example, the variable blocks could be
|
# file can be moved to other files. For example, the variable blocks could be
|
||||||
|
@ -91,10 +93,14 @@ const (
|
||||||
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
|
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
|
||||||
build {
|
build {
|
||||||
`
|
`
|
||||||
|
amazonAmiDataHeader = `
|
||||||
|
# The amazon-ami data block is generated from your amazon builder source_ami_filter; a data
|
||||||
|
# from this block can be referenced in source and locals blocks.
|
||||||
|
# Read the documentation for data blocks here:
|
||||||
|
# https://www.packer.io/docs/templates/hcl_templates/blocks/data`
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2UpgradeArgs) int {
|
func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2UpgradeArgs) int {
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
var output io.Writer
|
var output io.Writer
|
||||||
if err := os.MkdirAll(filepath.Dir(cla.OutputFile), 0); err != nil {
|
if err := os.MkdirAll(filepath.Dir(cla.OutputFile), 0); err != nil {
|
||||||
|
@ -179,10 +185,15 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
|
||||||
for _, builder := range tpl.Builders {
|
for _, builder := range tpl.Builders {
|
||||||
builders = append(builders, builder)
|
builders = append(builders, builder)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.writeAmazonAmiDatasource(builders, out); err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
sort.Slice(builders, func(i, j int) bool {
|
sort.Slice(builders, func(i, j int) bool {
|
||||||
return builders[i].Type+builders[i].Name < builders[j].Type+builders[j].Name
|
return builders[i].Type+builders[i].Name < builders[j].Type+builders[j].Name
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
out.Write([]byte(sourcesHeader))
|
out.Write([]byte(sourcesHeader))
|
||||||
|
|
||||||
|
@ -287,6 +298,61 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *HCL2UpgradeCommand) writeAmazonAmiDatasource(builders []*template.Builder, out *bytes.Buffer) error {
|
||||||
|
amazonAmiFilters := []map[string]interface{}{}
|
||||||
|
first := true
|
||||||
|
i := 1
|
||||||
|
for _, builder := range builders {
|
||||||
|
if strings.HasPrefix(builder.Type, "amazon-") {
|
||||||
|
if sourceAmiFilter, ok := builder.Config["source_ami_filter"]; ok {
|
||||||
|
sourceAmiFilterCfg := map[string]interface{}{}
|
||||||
|
if err := mapstructure.Decode(sourceAmiFilter, &sourceAmiFilterCfg); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to write amazon-ami data source: %v", err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicate := false
|
||||||
|
dataSourceName := fmt.Sprintf("autogenerated_%d", i)
|
||||||
|
for j, filter := range amazonAmiFilters {
|
||||||
|
if reflect.DeepEqual(filter, sourceAmiFilter) {
|
||||||
|
duplicate = true
|
||||||
|
dataSourceName = fmt.Sprintf("autogenerated_%d", j+1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a hack...
|
||||||
|
// Use templating so that it could be correctly transformed later into a data resource
|
||||||
|
sourceAmiDataRef := fmt.Sprintf("{{ data `amazon-ami.%s.id` }}", dataSourceName)
|
||||||
|
|
||||||
|
if duplicate {
|
||||||
|
delete(builder.Config, "source_ami_filter")
|
||||||
|
builder.Config["source_ami"] = sourceAmiDataRef
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
amazonAmiFilters = append(amazonAmiFilters, sourceAmiFilterCfg)
|
||||||
|
delete(builder.Config, "source_ami_filter")
|
||||||
|
builder.Config["source_ami"] = sourceAmiDataRef
|
||||||
|
i++
|
||||||
|
|
||||||
|
if first {
|
||||||
|
out.Write([]byte(amazonAmiDataHeader))
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
datasourceContent := hclwrite.NewEmptyFile()
|
||||||
|
body := datasourceContent.Body()
|
||||||
|
body.AppendNewline()
|
||||||
|
sourceBody := body.AppendNewBlock("data", []string{"amazon-ami", dataSourceName}).Body()
|
||||||
|
jsonBodyToHCL2Body(sourceBody, sourceAmiFilterCfg)
|
||||||
|
_, _ = out.Write(transposeTemplatingCalls(datasourceContent.Bytes()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type UnhandleableArgumentError struct {
|
type UnhandleableArgumentError struct {
|
||||||
Call string
|
Call string
|
||||||
Correspondance string
|
Correspondance string
|
||||||
|
@ -327,6 +393,9 @@ func transposeTemplatingCalls(s []byte) []byte {
|
||||||
"build": func(a string) string {
|
"build": func(a string) string {
|
||||||
return fmt.Sprintf("${build.%s}", a)
|
return fmt.Sprintf("${build.%s}", a)
|
||||||
},
|
},
|
||||||
|
"data": func(a string) string {
|
||||||
|
return fmt.Sprintf("${data.%s}", a)
|
||||||
|
},
|
||||||
"template_dir": func() string {
|
"template_dir": func() string {
|
||||||
return fmt.Sprintf("${path.root}")
|
return fmt.Sprintf("${path.root}")
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# This file was autogenerated by the BETA 'packer hcl2_upgrade' command. We
|
# This file was autogenerated by the 'packer hcl2_upgrade' command. We
|
||||||
# recommend double checking that everything is correct before going forward. We
|
# recommend double checking that everything is correct before going forward. We
|
||||||
# also recommend treating this file as disposable. The HCL2 blocks in this
|
# also recommend treating this file as disposable. The HCL2 blocks in this
|
||||||
# file can be moved to other files. For example, the variable blocks could be
|
# file can be moved to other files. For example, the variable blocks could be
|
||||||
|
@ -50,6 +50,20 @@ variable "secret_account" {
|
||||||
# "timestamp" template function replacement
|
# "timestamp" template function replacement
|
||||||
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
|
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
|
||||||
|
|
||||||
|
# The amazon-ami data block is generated from your amazon builder source_ami_filter; a data
|
||||||
|
# from this block can be referenced in source and locals blocks.
|
||||||
|
# Read the documentation for data blocks here:
|
||||||
|
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
|
||||||
|
data "amazon-ami" "autogenerated_1" {
|
||||||
|
filters = {
|
||||||
|
name = "ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*"
|
||||||
|
root-device-type = "ebs"
|
||||||
|
virtualization-type = "hvm"
|
||||||
|
}
|
||||||
|
most_recent = true
|
||||||
|
owners = ["099720109477"]
|
||||||
|
}
|
||||||
|
|
||||||
# source blocks are generated from your builders; a source can be referenced in
|
# source blocks are generated from your builders; a source can be referenced in
|
||||||
# build blocks. A build block runs provisioner and post-processors on a
|
# build blocks. A build block runs provisioner and post-processors on a
|
||||||
# source. Read the documentation for source blocks here:
|
# source. Read the documentation for source blocks here:
|
||||||
|
@ -67,15 +81,35 @@ source "amazon-ebs" "autogenerated_1" {
|
||||||
}
|
}
|
||||||
region = "${var.aws_region}"
|
region = "${var.aws_region}"
|
||||||
secret_key = "${var.aws_secret_key}"
|
secret_key = "${var.aws_secret_key}"
|
||||||
source_ami_filter {
|
source_ami = "${data.amazon-ami.autogenerated_1.id}"
|
||||||
filters = {
|
spot_instance_types = ["t2.small", "t2.medium", "t2.large"]
|
||||||
name = "ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*"
|
spot_price = "0.0075"
|
||||||
root-device-type = "ebs"
|
ssh_interface = "session_manager"
|
||||||
virtualization-type = "hvm"
|
ssh_username = "ubuntu"
|
||||||
|
temporary_iam_instance_profile_policy_document {
|
||||||
|
Statement {
|
||||||
|
Action = ["*"]
|
||||||
|
Effect = "Allow"
|
||||||
|
Resource = ["*"]
|
||||||
}
|
}
|
||||||
most_recent = true
|
Version = "2012-10-17"
|
||||||
owners = ["099720109477"]
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source "amazon-ebs" "named_builder" {
|
||||||
|
access_key = "${var.aws_access_key}"
|
||||||
|
ami_description = "Ubuntu 16.04 LTS - expand root partition"
|
||||||
|
ami_name = "ubuntu-16-04-test-${local.timestamp}"
|
||||||
|
encrypt_boot = true
|
||||||
|
launch_block_device_mappings {
|
||||||
|
delete_on_termination = true
|
||||||
|
device_name = "/dev/sda1"
|
||||||
|
volume_size = 48
|
||||||
|
volume_type = "gp2"
|
||||||
|
}
|
||||||
|
region = "${var.aws_region}"
|
||||||
|
secret_key = "${var.aws_secret_key}"
|
||||||
|
source_ami = "${data.amazon-ami.autogenerated_1.id}"
|
||||||
spot_instance_types = ["t2.small", "t2.medium", "t2.large"]
|
spot_instance_types = ["t2.small", "t2.medium", "t2.large"]
|
||||||
spot_price = "0.0075"
|
spot_price = "0.0075"
|
||||||
ssh_interface = "session_manager"
|
ssh_interface = "session_manager"
|
||||||
|
@ -94,7 +128,7 @@ source "amazon-ebs" "autogenerated_1" {
|
||||||
# documentation for build blocks can be found here:
|
# documentation for build blocks can be found here:
|
||||||
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
|
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
|
||||||
build {
|
build {
|
||||||
sources = ["source.amazon-ebs.autogenerated_1"]
|
sources = ["source.amazon-ebs.autogenerated_1", "source.amazon-ebs.named_builder"]
|
||||||
|
|
||||||
provisioner "shell" {
|
provisioner "shell" {
|
||||||
except = ["amazon-ebs"]
|
except = ["amazon-ebs"]
|
||||||
|
|
|
@ -61,6 +61,55 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ssh_interface": "session_manager"
|
"ssh_interface": "session_manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "amazon-ebs",
|
||||||
|
"name": "named_builder",
|
||||||
|
"region": "{{ user `aws_region` }}",
|
||||||
|
"secret_key": "{{ user `aws_secret_key` }}",
|
||||||
|
"access_key": "{{ user `aws_access_key` }}",
|
||||||
|
"ami_name": "ubuntu-16-04-test-{{ timestamp }}",
|
||||||
|
"ami_description": "Ubuntu 16.04 LTS - expand root partition",
|
||||||
|
"source_ami_filter": {
|
||||||
|
"filters": {
|
||||||
|
"virtualization-type": "hvm",
|
||||||
|
"name": "ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*",
|
||||||
|
"root-device-type": "ebs"
|
||||||
|
},
|
||||||
|
"owners": [
|
||||||
|
"099720109477"
|
||||||
|
],
|
||||||
|
"most_recent": true
|
||||||
|
},
|
||||||
|
"launch_block_device_mappings": [
|
||||||
|
{
|
||||||
|
"delete_on_termination": true,
|
||||||
|
"device_name": "/dev/sda1",
|
||||||
|
"volume_type": "gp2",
|
||||||
|
"volume_size": 48
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"spot_price": "0.0075",
|
||||||
|
"spot_instance_types": [
|
||||||
|
"t2.small",
|
||||||
|
"t2.medium",
|
||||||
|
"t2.large"
|
||||||
|
],
|
||||||
|
"encrypt_boot": true,
|
||||||
|
"ssh_username": "ubuntu",
|
||||||
|
"temporary_iam_instance_profile_policy_document": {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"Resource": ["*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ssh_interface": "session_manager"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"provisioners": [
|
"provisioners": [
|
||||||
|
|
|
@ -26,6 +26,12 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "^/docs/extending/custom-datasources"
|
"pattern": "^/docs/extending/custom-datasources"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^/docs/templates/legacy_json_templates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^https://packer.io/docs/templates/legacy_json_templates"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"replacementPatterns": [
|
"replacementPatterns": [
|
||||||
|
|
|
@ -8,6 +8,8 @@ page_title: Builders - Templates
|
||||||
sidebar_title: Builders
|
sidebar_title: Builders
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# Template Builders
|
# Template Builders
|
||||||
|
|
||||||
Within the template, the builders section contains an array of all the builders
|
Within the template, the builders section contains an array of all the builders
|
||||||
|
|
|
@ -6,6 +6,8 @@ page_title: Communicators - Templates
|
||||||
sidebar_title: Communicators
|
sidebar_title: Communicators
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# Template Communicators
|
# Template Communicators
|
||||||
|
|
||||||
Communicators are the mechanism Packer uses to upload files, execute scripts,
|
Communicators are the mechanism Packer uses to upload files, execute scripts,
|
||||||
|
|
|
@ -7,6 +7,8 @@ page_title: Template Engine - Templates
|
||||||
sidebar_title: Engine
|
sidebar_title: Engine
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# Template Engine
|
# Template Engine
|
||||||
|
|
||||||
All strings within templates are processed by a common Packer templating
|
All strings within templates are processed by a common Packer templating
|
||||||
|
|
|
@ -10,6 +10,8 @@ page_title: JSON Templates
|
||||||
sidebar_title: JSON Templates
|
sidebar_title: JSON Templates
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# JSON Templates
|
# JSON Templates
|
||||||
|
|
||||||
These Packer templates are JSON files that configure the various components
|
These Packer templates are JSON files that configure the various components
|
||||||
|
|
|
@ -7,6 +7,8 @@ page_title: Post-Processors - Templates
|
||||||
sidebar_title: Post-Processors
|
sidebar_title: Post-Processors
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# Template Post-Processors
|
# Template Post-Processors
|
||||||
|
|
||||||
The post-processor section within a template configures any post-processing
|
The post-processor section within a template configures any post-processing
|
||||||
|
|
|
@ -7,6 +7,8 @@ page_title: Provisioners - Templates
|
||||||
sidebar_title: Provisioners
|
sidebar_title: Provisioners
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# Template Provisioners
|
# Template Provisioners
|
||||||
|
|
||||||
Within the template, the provisioners section contains an array of all the
|
Within the template, the provisioners section contains an array of all the
|
||||||
|
|
|
@ -9,6 +9,8 @@ page_title: User Variables - Templates
|
||||||
sidebar_title: User Variables
|
sidebar_title: User Variables
|
||||||
---
|
---
|
||||||
|
|
||||||
|
`@include 'from-1.5/legacy-json-warning.mdx'`
|
||||||
|
|
||||||
# Template User Variables
|
# Template User Variables
|
||||||
|
|
||||||
User variables allow your templates to be further configured with variables
|
User variables allow your templates to be further configured with variables
|
||||||
|
|
|
@ -18,3 +18,5 @@
|
||||||
- `image_tags` ([]string) - List of tags to add to the image after creation.
|
- `image_tags` ([]string) - List of tags to add to the image after creation.
|
||||||
|
|
||||||
- `image_min_disk` (int) - Minimum disk size needed to boot image, in gigabytes.
|
- `image_min_disk` (int) - Minimum disk size needed to boot image, in gigabytes.
|
||||||
|
|
||||||
|
- `skip_create_image` (bool) - Skip creating the image. Useful for setting to `true` during a build test stage. Defaults to `false`.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
-> **Note:** This page is about HCL2 in Packer 1.5 and later. HCL2 support for
|
-> **Note:** This page is about HCL2 Packer templates. HCL2 templates
|
||||||
Packer is still in Beta. Please see the [Packer Issue
|
were first introduced as a beta feature into Packer version 1.5. As of v1.7,
|
||||||
Tracker](https://github.com/hashicorp/packer/issues/9176) for a list of
|
HCL2 support is no longer in beta, and is the preferred way to write Packer
|
||||||
supported features. For the old-style stable configuration language see
|
configuration. For the old-style stable configuration language see
|
||||||
[template docs](/docs/templates). You can now transform your JSON file into an
|
[template docs](/docs/templates/legacy_json_templates). As of v1.6.2, you can
|
||||||
HCL2 config file using the [hcl2_upgrade command](/docs/commands/hcl2_upgrade).
|
convert your legacy JSON template into an HCL2 config file using the
|
||||||
|
[hcl2_upgrade command](/docs/commands/hcl2_upgrade).
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
-> **Note:** This page is about older-style JSON Packer templates. JSON
|
||||||
|
templates are still supported by the Packer core, but new features added to the
|
||||||
|
Packer core may not be implemented for JSON templates. We recommend you
|
||||||
|
transition to HCL templates as soon as is convenient for you, in order to have
|
||||||
|
the best possible experience with Packer. To help you upgrade your templates,
|
||||||
|
we have written an [hcl2_upgrade command](/docs/commands/hcl2_upgrade) command.
|
Loading…
Reference in New Issue