Merge remote-tracking branch 'upstream/master' into feature/datadisk-change_name
This commit is contained in:
commit
5340af4d49
|
@ -4,6 +4,7 @@ comment:
|
|||
require_changes: true # only comment on changes in coverage
|
||||
require_base: yes # [yes :: must have a base report to post]
|
||||
require_head: yes # [yes :: must have a head report to post]
|
||||
after_n_builds: 3 # wait for all OS test coverage builds to post comment
|
||||
branches: # branch names that can post comment
|
||||
- "master"
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ Run golangci-lint on a single pkg or directory; PKG_NAME expands to /builder/ama
|
|||
make lint PKG_NAME=builder/amazon
|
||||
```
|
||||
|
||||
Note: linting on Travis uses the `--new-from-rev=origin/master` flag to only lint new files added within a branch or pull-request. To run this check locally you can use the `ci-lint` make target. See [golangci-lint in CI](https://github.com/golangci/golangci-lint#faq) for more information.
|
||||
Note: linting on Travis uses the `--new-from-rev` flag to only lint new files added within a branch or pull-request. To run this check locally you can use the `ci-lint` make target. See [golangci-lint in CI](https://github.com/golangci/golangci-lint#faq) for more information.
|
||||
|
||||
```
|
||||
make ci-lint
|
||||
|
|
27
CHANGELOG.md
27
CHANGELOG.md
|
@ -1,21 +1,34 @@
|
|||
## 1.5.5 (Upcoming)
|
||||
## 1.5.6 (Upcoming)
|
||||
|
||||
### Bug Fixes:
|
||||
* core: Make sure CLI variables supersede variables from var files [GH-8964]
|
||||
* builder/qemu: Remove `net_device` pre-validation [GH-8979]
|
||||
|
||||
## 1.5.5 (March 25,2020)
|
||||
|
||||
### IMPROVEMENTS:
|
||||
* builder/azure: Add support for configurable KeyVault SKU [GH-8879]
|
||||
* builder/hyperv: Add `first_boot_device` setting to allow the selection of the
|
||||
initial device or device class used for booting the VM. [GH-8714]
|
||||
* builder/hyperv: Fix Hyper-V compacted disk size comparison [GH-8811]
|
||||
* builder/openstack: Add new `image_auto_accept_members` option [GH-8931]
|
||||
* builder/proxmox: Add ability to specify vga adapter [GH-8892]
|
||||
* builder/proxmox: Add onboot directive support [GH-8935]
|
||||
* builder/tencentcloud: Show tencentcloud image id after copy to desination
|
||||
region. [GH-8763]
|
||||
* builder/vmware-iso: Add `cleanup_remote_cache` config option to [GH-8917]
|
||||
* builder/vmware-iso: Do not perform dial test of NIC when ssh bastion is
|
||||
required [GH-8877]
|
||||
* builder/vsphere-clone: Add ability to export VM to OVF file [GH-8764]
|
||||
* builder/vsphere-iso: Add ability to define multiple disks. [GH-8787]
|
||||
* builder/vsphere-iso: Add ability to export VM to OVF file [GH-8764]
|
||||
* builder/vsphere-iso: Add support for eagerly zeroed / scrubbed disks.
|
||||
[GH-8756]
|
||||
* builder/vsphere-iso: Add the remote iso first so that it is first in boot
|
||||
order, and clarify boot behavior. [GH-8732]
|
||||
* communicator/ssh: Add flag to enable support for keyboard-interactive auth to
|
||||
connect bastion [GH-8847]
|
||||
* core/hcl2: Add support for singular blocks [GH-8889]
|
||||
* core/hcl2: Add support in HCL2 configs for dynamic blocks, document for loops
|
||||
and splat expressions [GH-8720]
|
||||
* core/hcl2: Fix HCL2 local variables decoding to allow local usage within
|
||||
|
@ -29,17 +42,28 @@
|
|||
### Bug Fixes:
|
||||
* bilder/proxmox: Bump proxmox-api-go to fix upstream bug where users hit open
|
||||
file limit. [GH-8800]
|
||||
* builder/azure: Fix `winrm_password` attribution and allow users to set
|
||||
`winrm_username` [GH-8928]
|
||||
* builder/azure: Fix azure key vault cleanup failure [GH-8905]
|
||||
* builder/azure: Fix HCL2 bug that prevented Azure and other builders from
|
||||
loading properly. [GH-8785]
|
||||
* builder/googlecompute: Fix WinRMPassword template engine. [GH-8890]
|
||||
* builder/googlecompute: Replace deprecated "sshKeys" metadata with "ssh-keys"
|
||||
to fix SSH authentication issue [GH-8942]
|
||||
* builder/proxmox: Add new validation to catch that template_name cannot
|
||||
contain spaces. [GH-8799]
|
||||
* builder/vagrant: Fix path validation in ssh config step. [GH-8826]
|
||||
* builder/virtualbox-vm: Fix crash when VM has no snapshots. [GH-8906]
|
||||
* builder/virtualbox: Remove all floppy controllers before adding a new one.
|
||||
[GH-8828]
|
||||
* builder/vsphere-clone: Fix issue preventing the cloning of VMs with the same
|
||||
name in different folders [GH-8938]
|
||||
* builder/vsphere-iso: Fix issue preventing the creation of VMs with the same
|
||||
name in different folders [GH-8938]
|
||||
* builder/vsphere: Fix network object interface panic. [GH-8753]
|
||||
* core/hcl2: Fix crash when an unset variable is used [GH-8837]
|
||||
* core/hcl2: Fix logic for parsing literal value variables [GH-8834]
|
||||
* core/hcl2: Make sure locals are evaluated only after variables are. [GH-8918]
|
||||
* core: Fix "build" template engine interpolation for certain fields in certain
|
||||
provisioners. [GH-8771]
|
||||
* core: Fix bug where user var recursion could fail intermittently when used
|
||||
|
@ -48,7 +72,6 @@
|
|||
can disable plugins by renaming the extension [GH-8735]
|
||||
* provisioner/shell: "inline" config option is now a template engine. [GH-8883]
|
||||
|
||||
|
||||
## 1.5.4 (February 14, 2020)
|
||||
no-change release to fix code-signing on OSX binaries. Since checksums for these
|
||||
binaries has changed, we are releasing a second time to prevent confusion.
|
||||
|
|
2
Makefile
2
Makefile
|
@ -82,7 +82,7 @@ lint: install-lint-deps ## Lint Go code
|
|||
|
||||
ci-lint: install-lint-deps ## On ci only lint newly added Go source files
|
||||
@echo "==> Running linter on newly added Go source files..."
|
||||
GO111MODULE=on golangci-lint run --new-from-rev=origin/master ./...
|
||||
GO111MODULE=on golangci-lint run --new-from-rev=`git merge-base master HEAD` ./...
|
||||
|
||||
|
||||
fmt: ## Format Go code
|
||||
|
|
|
@ -49,24 +49,6 @@ type StepRunSpotInstance struct {
|
|||
func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||
state multistep.StateBag, marketOptions *ec2.LaunchTemplateInstanceMarketOptionsRequest) *ec2.RequestLaunchTemplateData {
|
||||
blockDeviceMappings := s.LaunchMappings.BuildEC2BlockDeviceMappings()
|
||||
if s.NoEphemeral {
|
||||
// This is only relevant for windows guests. Ephemeral drives by
|
||||
// default are assigned to drive names xvdca-xvdcz.
|
||||
// When vms are launched from the AWS console, they're automatically
|
||||
// removed from the block devices if the user hasn't said to use them,
|
||||
// but the SDK does not perform this cleanup. The following code just
|
||||
// manually removes the ephemeral drives from the mapping so that they
|
||||
// don't clutter up console views and cause confusion.
|
||||
log.Printf("no_ephemeral was set, so creating drives xvdca-xvdcz as empty mappings")
|
||||
DefaultEphemeralDeviceLetters := "abcdefghijklmnopqrstuvwxyz"
|
||||
for _, letter := range DefaultEphemeralDeviceLetters {
|
||||
bd := &ec2.BlockDeviceMapping{
|
||||
DeviceName: aws.String("xvdc" + string(letter)),
|
||||
NoDevice: aws.String(""),
|
||||
}
|
||||
blockDeviceMappings = append(blockDeviceMappings, bd)
|
||||
}
|
||||
}
|
||||
// Convert the BlockDeviceMapping into a
|
||||
// LaunchTemplateBlockDeviceMappingRequest. These structs are identical,
|
||||
// except for the EBS field -- on one, that field contains a
|
||||
|
@ -80,11 +62,29 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
|||
launchRequest := &ec2.LaunchTemplateBlockDeviceMappingRequest{
|
||||
DeviceName: mapping.DeviceName,
|
||||
Ebs: (*ec2.LaunchTemplateEbsBlockDeviceRequest)(mapping.Ebs),
|
||||
NoDevice: mapping.NoDevice,
|
||||
VirtualName: mapping.VirtualName,
|
||||
}
|
||||
launchMappingRequests = append(launchMappingRequests, launchRequest)
|
||||
}
|
||||
if s.NoEphemeral {
|
||||
// This is only relevant for windows guests. Ephemeral drives by
|
||||
// default are assigned to drive names xvdca-xvdcz.
|
||||
// When vms are launched from the AWS console, they're automatically
|
||||
// removed from the block devices if the user hasn't said to use them,
|
||||
// but the SDK does not perform this cleanup. The following code just
|
||||
// manually removes the ephemeral drives from the mapping so that they
|
||||
// don't clutter up console views and cause confusion.
|
||||
log.Printf("no_ephemeral was set, so creating drives xvdca-xvdcz as empty mappings")
|
||||
DefaultEphemeralDeviceLetters := "abcdefghijklmnopqrstuvwxyz"
|
||||
for _, letter := range DefaultEphemeralDeviceLetters {
|
||||
launchRequest := &ec2.LaunchTemplateBlockDeviceMappingRequest{
|
||||
DeviceName: aws.String("xvdc" + string(letter)),
|
||||
NoDevice: aws.String(""),
|
||||
}
|
||||
launchMappingRequests = append(launchMappingRequests, launchRequest)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))
|
||||
|
||||
|
|
|
@ -94,3 +94,42 @@ func TestCreateTemplateData(t *testing.T) {
|
|||
t.Fatalf("Template shouldn't contain instance profile if iamInstanceProfile is unset.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTemplateData_NoEphemeral(t *testing.T) {
|
||||
state := tStateSpot()
|
||||
stepRunSpotInstance := getBasicStep()
|
||||
stepRunSpotInstance.NoEphemeral = true
|
||||
template := stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||
&ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||
if len(template.BlockDeviceMappings) != 26 {
|
||||
t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
|
||||
}
|
||||
|
||||
// Now check that noEphemeral doesn't mess with the mappings in real life.
|
||||
// state = tStateSpot()
|
||||
// stepRunSpotInstance = getBasicStep()
|
||||
// stepRunSpotInstance.NoEphemeral = true
|
||||
// mappings := []*ec2.InstanceBlockDeviceMapping{
|
||||
// &ec2.InstanceBlockDeviceMapping{
|
||||
// DeviceName: "xvda",
|
||||
// Ebs: {
|
||||
// DeleteOnTermination: true,
|
||||
// Status: "attaching",
|
||||
// VolumeId: "vol-044cd49c330f21c05",
|
||||
// },
|
||||
// },
|
||||
// &ec2.InstanceBlockDeviceMapping{
|
||||
// DeviceName: "/dev/xvdf",
|
||||
// Ebs: {
|
||||
// DeleteOnTermination: false,
|
||||
// Status: "attaching",
|
||||
// VolumeId: "vol-0eefaf2d6ae35827e",
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// template = stepRunSpotInstance.CreateTemplateData(aws.String("userdata"), "az", state,
|
||||
// &ec2.LaunchTemplateInstanceMarketOptionsRequest{})
|
||||
// if len(*template.BlockDeviceMappings) != 26 {
|
||||
// t.Fatalf("Should have created 26 mappings to keep ephemeral drives from appearing.")
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) {
|
||||
return &communicator.WinRMConfig{
|
||||
Username: b.config.UserName,
|
||||
Password: b.config.Comm.WinRMPassword,
|
||||
Password: b.config.Password,
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
|
|
|
@ -18,6 +18,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/common/random"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/masterzen/winrm"
|
||||
|
@ -529,7 +531,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
|
||||
provideDefaultValues(c)
|
||||
setRuntimeValues(c)
|
||||
setUserNamePassword(c)
|
||||
err = setUserNamePassword(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// copy singular blocks
|
||||
for _, kv := range c.AzureTag {
|
||||
|
@ -653,23 +658,34 @@ func setRuntimeValues(c *Config) {
|
|||
c.tmpKeyVaultName = tempName.KeyVaultName
|
||||
}
|
||||
|
||||
func setUserNamePassword(c *Config) {
|
||||
func setUserNamePassword(c *Config) error {
|
||||
// SSH comm
|
||||
if c.Comm.SSHUsername == "" {
|
||||
c.Comm.SSHUsername = DefaultUserName
|
||||
}
|
||||
|
||||
c.UserName = c.Comm.SSHUsername
|
||||
|
||||
if c.Comm.SSHPassword != "" {
|
||||
c.Password = c.Comm.SSHPassword
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure password settings using Azure generated credentials
|
||||
c.Password = c.tmpAdminPassword
|
||||
if c.Comm.WinRMPassword == "" {
|
||||
c.Comm.WinRMPassword = c.Password
|
||||
// WinRM comm
|
||||
if c.Comm.WinRMUser == "" {
|
||||
c.Comm.WinRMUser = DefaultUserName
|
||||
}
|
||||
c.UserName = c.Comm.WinRMUser
|
||||
|
||||
if c.Comm.WinRMPassword == "" {
|
||||
// Configure password settings using Azure generated credentials
|
||||
c.Comm.WinRMPassword = c.tmpAdminPassword
|
||||
}
|
||||
if !isValidPassword(c.Comm.WinRMPassword) {
|
||||
return fmt.Errorf("The supplied \"winrm_password\" must be between 8-123 characters long and must satisfy at least 3 from the following: \n1) Contains an uppercase character \n2) Contains a lowercase character\n3) Contains a numeric digit\n4) Contains a special character\n5) Control characters are not allowed")
|
||||
}
|
||||
c.Password = c.Comm.WinRMPassword
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setCustomData(c *Config) error {
|
||||
|
@ -1039,6 +1055,34 @@ func isValidAzureName(re *regexp.Regexp, rgn string) bool {
|
|||
!strings.HasSuffix(rgn, "-")
|
||||
}
|
||||
|
||||
// The supplied password must be between 8-123 characters long and must satisfy at least 3 of password complexity requirements from the following:
|
||||
// 1) Contains an uppercase character
|
||||
// 2) Contains a lowercase character
|
||||
// 3) Contains a numeric digit
|
||||
// 4) Contains a special character
|
||||
// 5) Control characters are not allowed (a very specific case - not included in this validation)
|
||||
func isValidPassword(password string) bool {
|
||||
if !(len(password) >= 8 && len(password) <= 123) {
|
||||
return false
|
||||
}
|
||||
|
||||
requirements := 0
|
||||
if strings.ContainsAny(password, random.PossibleNumbers) {
|
||||
requirements++
|
||||
}
|
||||
if strings.ContainsAny(password, random.PossibleLowerCase) {
|
||||
requirements++
|
||||
}
|
||||
if strings.ContainsAny(password, random.PossibleUpperCase) {
|
||||
requirements++
|
||||
}
|
||||
if strings.ContainsAny(password, random.PossibleSpecialCharacter) {
|
||||
requirements++
|
||||
}
|
||||
|
||||
return requirements >= 3
|
||||
}
|
||||
|
||||
func (c *Config) validateLocationZoneResiliency(say func(s string)) {
|
||||
// Docs on regions that support Availibility Zones:
|
||||
// https://docs.microsoft.com/en-us/azure/availability-zones/az-overview#regions-that-support-availability-zones
|
||||
|
|
|
@ -71,22 +71,6 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
|
|||
t.Fatalf("newConfig failed: %s", err)
|
||||
}
|
||||
|
||||
if c.Password != "override_password" {
|
||||
t.Errorf("Expected 'Password' to be set to 'override_password', but found %q!", c.Password)
|
||||
}
|
||||
|
||||
if c.Comm.SSHPassword != "override_password" {
|
||||
t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found %q!", c.Comm.SSHPassword)
|
||||
}
|
||||
|
||||
if c.UserName != "override_username" {
|
||||
t.Errorf("Expected 'UserName' to be set to 'override_username', but found %q!", c.UserName)
|
||||
}
|
||||
|
||||
if c.Comm.SSHUsername != "override_username" {
|
||||
t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found %q!", c.Comm.SSHUsername)
|
||||
}
|
||||
|
||||
if c.VMSize != "override_vm_size" {
|
||||
t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found %q!", c.VMSize)
|
||||
}
|
||||
|
@ -98,6 +82,43 @@ func TestConfigShouldBeAbleToOverrideDefaultedValues(t *testing.T) {
|
|||
if c.diskCachingType != compute.CachingTypesNone {
|
||||
t.Errorf("Expected 'disk_caching_type' to be set to 'None', but found %q!", c.diskCachingType)
|
||||
}
|
||||
|
||||
// SSH comm
|
||||
if c.Password != "override_password" {
|
||||
t.Errorf("Expected 'Password' to be set to 'override_password', but found %q!", c.Password)
|
||||
}
|
||||
if c.Comm.SSHPassword != "override_password" {
|
||||
t.Errorf("Expected 'c.Comm.SSHPassword' to be set to 'override_password', but found %q!", c.Comm.SSHPassword)
|
||||
}
|
||||
if c.UserName != "override_username" {
|
||||
t.Errorf("Expected 'UserName' to be set to 'override_username', but found %q!", c.UserName)
|
||||
}
|
||||
if c.Comm.SSHUsername != "override_username" {
|
||||
t.Errorf("Expected 'c.Comm.SSHUsername' to be set to 'override_username', but found %q!", c.Comm.SSHUsername)
|
||||
}
|
||||
|
||||
// Winrm comm
|
||||
c = Config{}
|
||||
builderValues = getArmBuilderConfiguration()
|
||||
builderValues["communicator"] = "winrm"
|
||||
builderValues["winrm_password"] = "Override_winrm_password1"
|
||||
builderValues["winrm_username"] = "override_winrm_username"
|
||||
_, err = c.Prepare(builderValues, getPackerConfiguration())
|
||||
if err != nil {
|
||||
t.Fatalf("newConfig failed: %s", err)
|
||||
}
|
||||
if c.Password != "Override_winrm_password1" {
|
||||
t.Errorf("Expected 'Password' to be set to 'Override_winrm_password1', but found %q!", c.Password)
|
||||
}
|
||||
if c.Comm.WinRMPassword != "Override_winrm_password1" {
|
||||
t.Errorf("Expected 'c.Comm.WinRMPassword' to be set to 'Override_winrm_password1', but found %q!", c.Comm.SSHPassword)
|
||||
}
|
||||
if c.UserName != "override_winrm_username" {
|
||||
t.Errorf("Expected 'UserName' to be set to 'override_winrm_username', but found %q!", c.UserName)
|
||||
}
|
||||
if c.Comm.WinRMUser != "override_winrm_username" {
|
||||
t.Errorf("Expected 'c.Comm.WinRMUser' to be set to 'override_winrm_username', but found %q!", c.Comm.SSHUsername)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigShouldDefaultVMSizeToStandardA1(t *testing.T) {
|
||||
|
@ -574,7 +595,7 @@ func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) {
|
|||
config := getArmBuilderConfiguration()
|
||||
config["communicator"] = "winrm"
|
||||
config["winrm_username"] = "username"
|
||||
config["winrm_password"] = "password"
|
||||
config["winrm_password"] = "Password123"
|
||||
|
||||
var c Config
|
||||
_, err := c.Prepare(config, getPackerConfiguration())
|
||||
|
@ -1920,6 +1941,60 @@ func Test_GivenZoneSupportingResiliency_ConfigValidate_ShouldNotWarn(t *testing.
|
|||
}
|
||||
}
|
||||
|
||||
func TestConfig_PrepareProvidedWinRMPassword(t *testing.T) {
|
||||
config := getArmBuilderConfiguration()
|
||||
config["communicator"] = "winrm"
|
||||
|
||||
var c Config
|
||||
tc := []struct {
|
||||
name string
|
||||
password string
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
name: "password should be longer than 8 characters",
|
||||
password: "packer",
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "password should be shorter than 123 characters",
|
||||
password: "1Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "password should have valid size but only lower and upper case letters",
|
||||
password: "AAAbbbCCC",
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "password should have valid size but only digits and upper case letters",
|
||||
password: "AAA12345",
|
||||
shouldFail: true,
|
||||
},
|
||||
{
|
||||
name: "password should have valid size, digits, upper and lower case letters",
|
||||
password: "AAA12345bbb",
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
name: "password should have valid size, digits, special characters and lower case letters",
|
||||
password: "//12345bbb",
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config["winrm_password"] = tt.password
|
||||
_, err := c.Prepare(config)
|
||||
fail := err != nil
|
||||
if tt.shouldFail != fail {
|
||||
t.Fatalf("bad: %s. Expected fail is: %t but it was %t", tt.name, tt.shouldFail, fail)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func getArmBuilderConfiguration() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, v := range requiredConfigValues {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
|
@ -30,8 +31,9 @@ func (c *Config) createInstanceMetadata(sourceImage *Image, sshPublicKey string)
|
|||
// supplied public key. This is possible if a private_key_file was
|
||||
// specified.
|
||||
if sshPublicKey != "" {
|
||||
sshMetaKey := "sshKeys"
|
||||
sshKeys := fmt.Sprintf("%s:%s", c.Comm.SSHUsername, sshPublicKey)
|
||||
sshMetaKey := "ssh-keys"
|
||||
sshPublicKey = strings.TrimSuffix(sshPublicKey, "\n")
|
||||
sshKeys := fmt.Sprintf("%s:%s %s", c.Comm.SSHUsername, sshPublicKey, c.Comm.SSHUsername)
|
||||
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
|
||||
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
|
||||
}
|
||||
|
|
|
@ -308,14 +308,14 @@ func TestCreateInstanceMetadata(t *testing.T) {
|
|||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
|
||||
// ensure our key is listed
|
||||
assert.True(t, strings.Contains(metadata["sshKeys"], key), "Instance metadata should contain provided key")
|
||||
assert.True(t, strings.Contains(metadata["ssh-keys"], key), "Instance metadata should contain provided key")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata_noPublicKey(t *testing.T) {
|
||||
state := testState(t)
|
||||
c := state.Get("config").(*Config)
|
||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||
sshKeys := c.Metadata["sshKeys"]
|
||||
sshKeys := c.Metadata["ssh-keys"]
|
||||
|
||||
// create our metadata
|
||||
metadata, err := c.createInstanceMetadata(image, "")
|
||||
|
@ -323,7 +323,7 @@ func TestCreateInstanceMetadata_noPublicKey(t *testing.T) {
|
|||
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||
|
||||
// ensure the ssh metadata hasn't changed
|
||||
assert.Equal(t, metadata["sshKeys"], sshKeys, "Instance metadata should not have been modified")
|
||||
assert.Equal(t, metadata["ssh-keys"], sshKeys, "Instance metadata should not have been modified")
|
||||
}
|
||||
|
||||
func TestCreateInstanceMetadata_metadataFile(t *testing.T) {
|
||||
|
|
|
@ -40,6 +40,7 @@ type FlatConfig struct {
|
|||
ImageMetadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata"`
|
||||
ImageVisibility *images.ImageVisibility `mapstructure:"image_visibility" required:"false" cty:"image_visibility"`
|
||||
ImageMembers []string `mapstructure:"image_members" required:"false" cty:"image_members"`
|
||||
ImageAutoAcceptMembers *bool `mapstructure:"image_auto_accept_members" required:"false" cty:"image_auto_accept_members"`
|
||||
ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format"`
|
||||
ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags"`
|
||||
ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk"`
|
||||
|
@ -158,6 +159,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"metadata": &hcldec.BlockAttrsSpec{TypeName: "metadata", ElementType: cty.String, Required: false},
|
||||
"image_visibility": &hcldec.AttrSpec{Name: "image_visibility", Type: cty.String, Required: false},
|
||||
"image_members": &hcldec.AttrSpec{Name: "image_members", Type: cty.List(cty.String), Required: false},
|
||||
"image_auto_accept_members": &hcldec.AttrSpec{Name: "image_auto_accept_members", Type: cty.Bool, 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_min_disk": &hcldec.AttrSpec{Name: "image_min_disk", Type: cty.Number, Required: false},
|
||||
|
|
|
@ -22,6 +22,10 @@ type ImageConfig struct {
|
|||
// usually a project (also called the "tenant") with whom the image is
|
||||
// shared.
|
||||
ImageMembers []string `mapstructure:"image_members" required:"false"`
|
||||
// When true, perform the image accept so the members can see the image in their
|
||||
// project. This requires a user with priveleges both in the build project and
|
||||
// in the members provided. Defaults to false.
|
||||
ImageAutoAcceptMembers bool `mapstructure:"image_auto_accept_members" required:"false"`
|
||||
// Disk format of the resulting image. This option works if
|
||||
// use_blockstorage_volume is true.
|
||||
ImageDiskFormat string `mapstructure:"image_disk_format" required:"false"`
|
||||
|
|
|
@ -37,6 +37,18 @@ func (s *stepAddImageMembers) Run(ctx context.Context, state multistep.StateBag)
|
|||
}
|
||||
}
|
||||
|
||||
if config.ImageAutoAcceptMembers {
|
||||
for _, member := range config.ImageMembers {
|
||||
ui.Say(fmt.Sprintf("Accepting image %s for member '%s'", imageId, member))
|
||||
r := members.Update(imageClient, imageId, member, members.UpdateOpts{Status: "accepted"})
|
||||
if _, err = r.Extract(); err != nil {
|
||||
err = fmt.Errorf("Error accepting image for member: %s", err)
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
ocicommon "github.com/oracle/oci-go-sdk/common"
|
||||
ociauth "github.com/oracle/oci-go-sdk/common/auth"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -27,6 +28,18 @@ type Config struct {
|
|||
|
||||
configProvider ocicommon.ConfigurationProvider
|
||||
|
||||
// Instance Principals (OPTIONAL)
|
||||
// If set to true the following can't have non empty values
|
||||
// - AccessCfgFile
|
||||
// - AccessCfgFileAccount
|
||||
// - UserID
|
||||
// - TenancyID
|
||||
// - Region
|
||||
// - Fingerprint
|
||||
// - KeyFile
|
||||
// - PassPhrase
|
||||
InstancePrincipals bool `mapstructure:"use_instance_principals"`
|
||||
|
||||
AccessCfgFile string `mapstructure:"access_cfg_file"`
|
||||
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
|
||||
|
||||
|
@ -86,84 +99,134 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
|||
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
||||
}
|
||||
|
||||
// Determine where the SDK config is located
|
||||
if c.AccessCfgFile == "" {
|
||||
c.AccessCfgFile, err = getDefaultOCISettingsPath()
|
||||
if err != nil {
|
||||
log.Println("Default OCI settings file not found")
|
||||
}
|
||||
}
|
||||
|
||||
if c.AccessCfgFileAccount == "" {
|
||||
c.AccessCfgFileAccount = "DEFAULT"
|
||||
}
|
||||
|
||||
var keyContent []byte
|
||||
if c.KeyFile != "" {
|
||||
path, err := packer.ExpandUser(c.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read API signing key
|
||||
keyContent, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
|
||||
if c.Region == "" {
|
||||
var region string
|
||||
if fileProvider != nil {
|
||||
region, _ = fileProvider.Region()
|
||||
}
|
||||
if region == "" {
|
||||
c.Region = "us-phoenix-1"
|
||||
}
|
||||
}
|
||||
|
||||
providers := []ocicommon.ConfigurationProvider{
|
||||
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
|
||||
}
|
||||
|
||||
if fileProvider != nil {
|
||||
providers = append(providers, fileProvider)
|
||||
}
|
||||
|
||||
// Load API access configuration from SDK
|
||||
configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||
errs = packer.MultiErrorAppend(errs, es...)
|
||||
}
|
||||
|
||||
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'user_ocid' must be specified"))
|
||||
}
|
||||
var tenancyOCID string
|
||||
|
||||
tenancyOCID, _ := configProvider.TenancyOCID()
|
||||
if tenancyOCID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'tenancy_ocid' must be specified"))
|
||||
}
|
||||
if c.InstancePrincipals {
|
||||
// We could go through all keys in one go and report that the below set
|
||||
// of keys cannot coexist with use_instance_principals but decided to
|
||||
// split them and report them seperately so that the user sees the specific
|
||||
// key involved.
|
||||
var message string = " cannot be present when use_instance_principals is set to true."
|
||||
if c.AccessCfgFile != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file"+message))
|
||||
}
|
||||
if c.AccessCfgFileAccount != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("access_cfg_file_account"+message))
|
||||
}
|
||||
if c.UserID != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user_ocid"+message))
|
||||
}
|
||||
if c.TenancyID != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("tenancy_ocid"+message))
|
||||
}
|
||||
if c.Region != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("region"+message))
|
||||
}
|
||||
if c.Fingerprint != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("fingerprint"+message))
|
||||
}
|
||||
if c.KeyFile != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("key_file"+message))
|
||||
}
|
||||
if c.PassPhrase != "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("pass_phrase"+message))
|
||||
}
|
||||
// This check is used to facilitate testing. During testing a Mock struct
|
||||
// is assigned to c.configProvider otherwise testing fails because Instance
|
||||
// Principals cannot be obtained.
|
||||
if c.configProvider == nil {
|
||||
// Even though the previous configuraion checks might fail we don't want
|
||||
// to skip this step. It seems that the logic behind the checks in this
|
||||
// file is to check everything even getting the configProvider.
|
||||
c.configProvider, err = ociauth.InstancePrincipalConfigurationProvider()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
tenancyOCID, err = c.configProvider.TenancyOCID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Determine where the SDK config is located
|
||||
if c.AccessCfgFile == "" {
|
||||
c.AccessCfgFile, err = getDefaultOCISettingsPath()
|
||||
if err != nil {
|
||||
log.Println("Default OCI settings file not found")
|
||||
}
|
||||
}
|
||||
|
||||
if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'fingerprint' must be specified"))
|
||||
}
|
||||
if c.AccessCfgFileAccount == "" {
|
||||
c.AccessCfgFileAccount = "DEFAULT"
|
||||
}
|
||||
|
||||
if _, err := configProvider.PrivateRSAKey(); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'key_file' must be specified"))
|
||||
}
|
||||
var keyContent []byte
|
||||
if c.KeyFile != "" {
|
||||
path, err := packer.ExpandUser(c.KeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.configProvider = configProvider
|
||||
// Read API signing key
|
||||
keyContent, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fileProvider, _ := ocicommon.ConfigurationProviderFromFileWithProfile(c.AccessCfgFile, c.AccessCfgFileAccount, c.PassPhrase)
|
||||
if c.Region == "" {
|
||||
var region string
|
||||
if fileProvider != nil {
|
||||
region, _ = fileProvider.Region()
|
||||
}
|
||||
if region == "" {
|
||||
c.Region = "us-phoenix-1"
|
||||
}
|
||||
}
|
||||
|
||||
providers := []ocicommon.ConfigurationProvider{
|
||||
NewRawConfigurationProvider(c.TenancyID, c.UserID, c.Region, c.Fingerprint, string(keyContent), &c.PassPhrase),
|
||||
}
|
||||
|
||||
if fileProvider != nil {
|
||||
providers = append(providers, fileProvider)
|
||||
}
|
||||
|
||||
// Load API access configuration from SDK
|
||||
configProvider, err := ocicommon.ComposingConfigurationProvider(providers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'user_ocid' must be specified"))
|
||||
}
|
||||
|
||||
tenancyOCID, _ = configProvider.TenancyOCID()
|
||||
if tenancyOCID == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'tenancy_ocid' must be specified"))
|
||||
}
|
||||
|
||||
if fingerprint, _ := configProvider.KeyFingerprint(); fingerprint == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'fingerprint' must be specified"))
|
||||
}
|
||||
|
||||
if _, err := configProvider.PrivateRSAKey(); err != nil {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("'key_file' must be specified"))
|
||||
}
|
||||
|
||||
c.configProvider = configProvider
|
||||
}
|
||||
|
||||
if c.AvailabilityDomain == "" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
|
|
|
@ -57,6 +57,7 @@ type FlatConfig struct {
|
|||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
InstancePrincipals *bool `mapstructure:"use_instance_principals" cty:"use_instance_principals"`
|
||||
AccessCfgFile *string `mapstructure:"access_cfg_file" cty:"access_cfg_file"`
|
||||
AccessCfgFileAccount *string `mapstructure:"access_cfg_file_account" cty:"access_cfg_file_account"`
|
||||
UserID *string `mapstructure:"user_ocid" cty:"user_ocid"`
|
||||
|
@ -140,6 +141,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"winrm_use_ssl": &hcldec.AttrSpec{Name: "winrm_use_ssl", Type: cty.Bool, Required: false},
|
||||
"winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false},
|
||||
"winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false},
|
||||
"use_instance_principals": &hcldec.AttrSpec{Name: "use_instance_principals", Type: cty.Bool, Required: false},
|
||||
"access_cfg_file": &hcldec.AttrSpec{Name: "access_cfg_file", Type: cty.String, Required: false},
|
||||
"access_cfg_file_account": &hcldec.AttrSpec{Name: "access_cfg_file_account", Type: cty.String, Required: false},
|
||||
"user_ocid": &hcldec.AttrSpec{Name: "user_ocid", Type: cty.String, Required: false},
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
func testConfig(accessConfFile *os.File) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
|
||||
"availability_domain": "aaaa:PHX-AD-3",
|
||||
"access_cfg_file": accessConfFile.Name(),
|
||||
|
||||
|
@ -252,6 +253,36 @@ func TestConfig(t *testing.T) {
|
|||
t.Errorf("Expected ConfigProvider.KeyFingerprint: %s, got %s", expected, fingerprint)
|
||||
}
|
||||
})
|
||||
|
||||
// Test the correct errors are produced when certain template keys
|
||||
// are present alongside use_instance_principals key.
|
||||
invalidKeys := []string{
|
||||
"access_cfg_file",
|
||||
"access_cfg_file_account",
|
||||
"user_ocid",
|
||||
"tenancy_ocid",
|
||||
"region",
|
||||
"fingerprint",
|
||||
"key_file",
|
||||
"pass_phrase",
|
||||
}
|
||||
for _, k := range invalidKeys {
|
||||
t.Run(k+"_mixed_with_use_instance_principals", func(t *testing.T) {
|
||||
raw := testConfig(cfgFile)
|
||||
raw["use_instance_principals"] = "true"
|
||||
raw[k] = "some_random_value"
|
||||
|
||||
var c Config
|
||||
|
||||
c.configProvider = instancePrincipalConfigurationProviderMock{}
|
||||
|
||||
errs := c.Prepare(raw)
|
||||
|
||||
if !strings.Contains(errs.Error(), k) {
|
||||
t.Errorf("Expected '%s' to contain '%s'", errs.Error(), k)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BaseTestConfig creates the base (DEFAULT) config including a temporary key
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package oci
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// Mock struct to be used during testing to obtain Instance Principals.
|
||||
type instancePrincipalConfigurationProviderMock struct {
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
||||
return rsa.GenerateKey(rand.Reader, 1024)
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) KeyID() (string, error) {
|
||||
return "some_random_key_id", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) TenancyOCID() (string, error) {
|
||||
return "some_random_tenancy", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) UserOCID() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) KeyFingerprint() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProviderMock) Region() (string, error) {
|
||||
return "some_random_region", nil
|
||||
}
|
|
@ -52,6 +52,7 @@ type Config struct {
|
|||
ISOStoragePool string `mapstructure:"iso_storage_pool"`
|
||||
Agent bool `mapstructure:"qemu_agent"`
|
||||
SCSIController string `mapstructure:"scsi_controller"`
|
||||
Onboot bool `mapstructure:"onboot"`
|
||||
|
||||
TemplateName string `mapstructure:"template_name"`
|
||||
TemplateDescription string `mapstructure:"template_description"`
|
||||
|
|
|
@ -91,6 +91,7 @@ type FlatConfig struct {
|
|||
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool"`
|
||||
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent"`
|
||||
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller"`
|
||||
Onboot *bool `mapstructure:"onboot" cty:"onboot"`
|
||||
TemplateName *string `mapstructure:"template_name" cty:"template_name"`
|
||||
TemplateDescription *string `mapstructure:"template_description" cty:"template_description"`
|
||||
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso"`
|
||||
|
@ -190,6 +191,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"iso_storage_pool": &hcldec.AttrSpec{Name: "iso_storage_pool", Type: cty.String, Required: false},
|
||||
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
|
||||
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, Required: false},
|
||||
"onboot": &hcldec.AttrSpec{Name: "onboot", Type: cty.Bool, Required: false},
|
||||
"template_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
||||
"template_description": &hcldec.AttrSpec{Name: "template_description", Type: cty.String, Required: false},
|
||||
"unmount_iso": &hcldec.AttrSpec{Name: "unmount_iso", Type: cty.Bool, Required: false},
|
||||
|
|
|
@ -44,6 +44,7 @@ func (s *stepStartVM) Run(ctx context.Context, state multistep.StateBag) multist
|
|||
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
|
||||
QemuDisks: generateProxmoxDisks(c.Disks),
|
||||
Scsihw: c.SCSIController,
|
||||
Onboot: c.Onboot,
|
||||
}
|
||||
|
||||
if c.VMID == 0 {
|
||||
|
|
|
@ -39,31 +39,6 @@ var accels = map[string]struct{}{
|
|||
"whpx": {},
|
||||
}
|
||||
|
||||
var netDevice = map[string]bool{
|
||||
"ne2k_pci": true,
|
||||
"i82551": true,
|
||||
"i82557b": true,
|
||||
"i82559er": true,
|
||||
"rtl8139": true,
|
||||
"e1000": true,
|
||||
"pcnet": true,
|
||||
"virtio": true,
|
||||
"virtio-net": true,
|
||||
"virtio-net-pci": true,
|
||||
"usb-net": true,
|
||||
"i82559a": true,
|
||||
"i82559b": true,
|
||||
"i82559c": true,
|
||||
"i82550": true,
|
||||
"i82562": true,
|
||||
"i82557a": true,
|
||||
"i82557c": true,
|
||||
"i82801": true,
|
||||
"vmxnet3": true,
|
||||
"i82558a": true,
|
||||
"i82558b": true,
|
||||
}
|
||||
|
||||
var diskInterface = map[string]bool{
|
||||
"ide": true,
|
||||
"scsi": true,
|
||||
|
@ -526,11 +501,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||
errs, errors.New("invalid accelerator, only 'kvm', 'tcg', 'xen', 'hax', 'hvf', 'whpx', or 'none' are allowed"))
|
||||
}
|
||||
|
||||
if _, ok := netDevice[b.config.NetDevice]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized network device type"))
|
||||
}
|
||||
|
||||
if _, ok := diskInterface[b.config.DiskInterface]; !ok {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, errors.New("unrecognized disk interface type"))
|
||||
|
|
|
@ -16,6 +16,9 @@ import (
|
|||
)
|
||||
|
||||
type DriverConfig struct {
|
||||
// When set to true, Packer will cleanup the cache folder where the ISO file is stored during the build on the remote machine.
|
||||
// By default, this is set to false.
|
||||
CleanUpRemoteCache bool `mapstructure:"cleanup_remote_cache" required:"false"`
|
||||
// Path to "VMware Fusion.app". By default this is
|
||||
// /Applications/VMware Fusion.app but this setting allows you to
|
||||
// customize this.
|
||||
|
|
|
@ -26,6 +26,9 @@ type RemoteDriverMock struct {
|
|||
UploadErr error
|
||||
DownloadErr error
|
||||
|
||||
RemovedCachePath string
|
||||
CacheRemoved bool
|
||||
|
||||
ReloadVMErr error
|
||||
}
|
||||
|
||||
|
@ -66,6 +69,8 @@ func (d *RemoteDriverMock) Download(src, dst string) error {
|
|||
}
|
||||
|
||||
func (d *RemoteDriverMock) RemoveCache(localPath string) error {
|
||||
d.RemovedCachePath = localPath
|
||||
d.CacheRemoved = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
)
|
||||
|
||||
func TestStepRemoteUpload_Cleanup(t *testing.T) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
driver := new(RemoteDriverMock)
|
||||
state.Put("driver", driver)
|
||||
state.Put("path_key", "packer_cache")
|
||||
|
||||
// Should clean up cache
|
||||
s := &StepRemoteUpload{
|
||||
Key: "path_key",
|
||||
DoCleanup: true,
|
||||
}
|
||||
s.Cleanup(state)
|
||||
|
||||
if !driver.CacheRemoved {
|
||||
t.Fatalf("bad: remote cache was not removed")
|
||||
}
|
||||
if driver.RemovedCachePath != "packer_cache" {
|
||||
t.Fatalf("bad: removed cache path was expected to be packer_cache but was %s", driver.RemovedCachePath)
|
||||
}
|
||||
|
||||
// Should NOT clean up cache
|
||||
s = &StepRemoteUpload{
|
||||
Key: "path_key",
|
||||
}
|
||||
driver = new(RemoteDriverMock)
|
||||
state.Put("driver", driver)
|
||||
s.Cleanup(state)
|
||||
|
||||
if driver.CacheRemoved {
|
||||
t.Fatalf("bad: remote cache was removed but was expected to not")
|
||||
}
|
||||
}
|
|
@ -98,6 +98,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
&vmwcommon.StepRemoteUpload{
|
||||
Key: "iso_path",
|
||||
Message: "Uploading ISO to remote machine...",
|
||||
DoCleanup: b.config.DriverConfig.CleanUpRemoteCache,
|
||||
Checksum: b.config.ISOChecksum,
|
||||
ChecksumType: b.config.ISOChecksumType,
|
||||
},
|
||||
|
|
|
@ -34,6 +34,7 @@ type FlatConfig struct {
|
|||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
||||
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc"`
|
||||
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval"`
|
||||
CleanUpRemoteCache *bool `mapstructure:"cleanup_remote_cache" required:"false" cty:"cleanup_remote_cache"`
|
||||
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path"`
|
||||
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type"`
|
||||
RemoteDatastore *string `mapstructure:"remote_datastore" required:"false" cty:"remote_datastore"`
|
||||
|
@ -165,6 +166,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
|
||||
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
|
||||
"cleanup_remote_cache": &hcldec.AttrSpec{Name: "cleanup_remote_cache", Type: cty.Bool, Required: false},
|
||||
"fusion_app_path": &hcldec.AttrSpec{Name: "fusion_app_path", Type: cty.String, Required: false},
|
||||
"remote_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
|
||||
"remote_datastore": &hcldec.AttrSpec{Name: "remote_datastore", Type: cty.String, Required: false},
|
||||
|
|
|
@ -27,6 +27,7 @@ type FlatConfig struct {
|
|||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
||||
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc"`
|
||||
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval"`
|
||||
CleanUpRemoteCache *bool `mapstructure:"cleanup_remote_cache" required:"false" cty:"cleanup_remote_cache"`
|
||||
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path"`
|
||||
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type"`
|
||||
RemoteDatastore *string `mapstructure:"remote_datastore" required:"false" cty:"remote_datastore"`
|
||||
|
@ -134,6 +135,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},
|
||||
"disable_vnc": &hcldec.AttrSpec{Name: "disable_vnc", Type: cty.Bool, Required: false},
|
||||
"boot_key_interval": &hcldec.AttrSpec{Name: "boot_key_interval", Type: cty.String, Required: false},
|
||||
"cleanup_remote_cache": &hcldec.AttrSpec{Name: "cleanup_remote_cache", Type: cty.Bool, Required: false},
|
||||
"fusion_app_path": &hcldec.AttrSpec{Name: "fusion_app_path", Type: cty.String, Required: false},
|
||||
"remote_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
|
||||
"remote_datastore": &hcldec.AttrSpec{Name: "remote_datastore", Type: cty.String, Required: false},
|
||||
|
|
|
@ -82,6 +82,17 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
},
|
||||
)
|
||||
|
||||
if b.config.Export != nil {
|
||||
steps = append(steps, &common.StepExport{
|
||||
Name: b.config.Export.Name,
|
||||
Force: b.config.Export.Force,
|
||||
Images: b.config.Export.Images,
|
||||
Manifest: b.config.Export.Manifest,
|
||||
OutputDir: b.config.Export.OutputDir.OutputDir,
|
||||
Options: b.config.Export.Options,
|
||||
})
|
||||
}
|
||||
|
||||
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(ctx, state)
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ type Config struct {
|
|||
// Convert VM to a template. Defaults to `false`.
|
||||
ConvertToTemplate bool `mapstructure:"convert_to_template"`
|
||||
|
||||
Export *common.ExportConfig `mapstructure:"export"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
|
@ -53,6 +55,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
||||
if c.Export != nil {
|
||||
errs = packer.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
|
|
|
@ -3,95 +3,97 @@ package clone
|
|||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server"`
|
||||
Username *string `mapstructure:"username" cty:"username"`
|
||||
Password *string `mapstructure:"password" cty:"password"`
|
||||
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
|
||||
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
|
||||
Template *string `mapstructure:"template" cty:"template"`
|
||||
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
|
||||
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone"`
|
||||
Network *string `mapstructure:"network" cty:"network"`
|
||||
Notes *string `mapstructure:"notes" cty:"notes"`
|
||||
VMName *string `mapstructure:"vm_name" cty:"vm_name"`
|
||||
Folder *string `mapstructure:"folder" cty:"folder"`
|
||||
Cluster *string `mapstructure:"cluster" cty:"cluster"`
|
||||
Host *string `mapstructure:"host" cty:"host"`
|
||||
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
|
||||
Datastore *string `mapstructure:"datastore" cty:"datastore"`
|
||||
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs"`
|
||||
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
|
||||
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
|
||||
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
|
||||
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
|
||||
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
|
||||
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
|
||||
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
|
||||
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
|
||||
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
|
||||
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
|
||||
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters"`
|
||||
BootOrder *string `mapstructure:"boot_order" cty:"boot_order"`
|
||||
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout"`
|
||||
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"`
|
||||
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
||||
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
||||
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server"`
|
||||
Username *string `mapstructure:"username" cty:"username"`
|
||||
Password *string `mapstructure:"password" cty:"password"`
|
||||
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
|
||||
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
|
||||
Template *string `mapstructure:"template" cty:"template"`
|
||||
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
|
||||
LinkedClone *bool `mapstructure:"linked_clone" cty:"linked_clone"`
|
||||
Network *string `mapstructure:"network" cty:"network"`
|
||||
Notes *string `mapstructure:"notes" cty:"notes"`
|
||||
VMName *string `mapstructure:"vm_name" cty:"vm_name"`
|
||||
Folder *string `mapstructure:"folder" cty:"folder"`
|
||||
Cluster *string `mapstructure:"cluster" cty:"cluster"`
|
||||
Host *string `mapstructure:"host" cty:"host"`
|
||||
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
|
||||
Datastore *string `mapstructure:"datastore" cty:"datastore"`
|
||||
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs"`
|
||||
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
|
||||
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
|
||||
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
|
||||
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
|
||||
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
|
||||
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
|
||||
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
|
||||
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
|
||||
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
|
||||
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
|
||||
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters"`
|
||||
BootOrder *string `mapstructure:"boot_order" cty:"boot_order"`
|
||||
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout"`
|
||||
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"`
|
||||
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
||||
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
||||
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
||||
Export *common.FlatExportConfig `mapstructure:"export" cty:"export"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
|
@ -189,6 +191,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
|
||||
"create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, Required: false},
|
||||
"convert_to_template": &hcldec.AttrSpec{Name: "convert_to_template", Type: cty.Bool, Required: false},
|
||||
"export": &hcldec.BlockSpec{TypeName: "export", Nested: hcldec.ObjectSpec((*common.FlatExportConfig)(nil).HCL2Spec())},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -49,17 +49,18 @@ type StepCloneVM struct {
|
|||
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
d := state.Get("driver").(*driver.Driver)
|
||||
vmPath := fmt.Sprintf("%s/%s", s.Location.Folder, s.Location.VMName)
|
||||
|
||||
vm, err := d.FindVM(s.Location.VMName)
|
||||
vm, err := d.FindVM(vmPath)
|
||||
|
||||
if s.Force == false && err == nil {
|
||||
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it", s.Location.VMName))
|
||||
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it", vmPath))
|
||||
return multistep.ActionHalt
|
||||
} else if s.Force == true && err == nil {
|
||||
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", s.Location.VMName))
|
||||
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", vmPath))
|
||||
err := vm.Destroy()
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error destroying %s: %v", s.Location.VMName, err))
|
||||
state.Put("error", fmt.Errorf("error destroying %s: %v", vmPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type OutputConfig
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type OutputConfig struct {
|
||||
// This setting specifies the directory that
|
||||
// artifacts from the build, such as the virtual machine files and disks,
|
||||
// will be output to. The path to the directory may be relative or
|
||||
// absolute. If relative, the path is relative to the working directory
|
||||
// packer is executed from. This directory must not exist or, if
|
||||
// created, must be empty prior to running the builder. By default this is
|
||||
// "output-BUILDNAME" where "BUILDNAME" is the name of the build.
|
||||
OutputDir string `mapstructure:"output_directory" required:"false"`
|
||||
}
|
||||
|
||||
func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error {
|
||||
if c.OutputDir == "" {
|
||||
c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Code generated by "mapstructure-to-hcl2 -type OutputConfig"; DO NOT EDIT.
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatOutputConfig is an auto-generated flat version of OutputConfig.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatOutputConfig struct {
|
||||
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatOutputConfig.
|
||||
// FlatOutputConfig is an auto-generated flat version of OutputConfig.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*OutputConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatOutputConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a OutputConfig.
|
||||
// This spec is used by HCL to read the fields of OutputConfig.
|
||||
// The decoded values from this spec will then be applied to a FlatOutputConfig.
|
||||
func (*FlatOutputConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
//go:generate struct-markdown
|
||||
//go:generate mapstructure-to-hcl2 -type ExportConfig
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/builder/vsphere/driver"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vmware/govmomi/nfc"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// You may optionally export an ovf from VSphere to the instance running Packer.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// ```json
|
||||
// ...
|
||||
// "vm_name": "example-ubuntu",
|
||||
// ...
|
||||
// "export": {
|
||||
// "force": true,
|
||||
// "output_directory": "./output_vsphere"
|
||||
// },
|
||||
// ```
|
||||
// The above configuration would create the following files:
|
||||
//
|
||||
// ```
|
||||
// ./output_vsphere/example-ubuntu-disk-0.vmdk
|
||||
// ./output_vsphere/example-ubuntu.mf
|
||||
// ./output_vsphere/example-ubuntu.ovf
|
||||
// ```
|
||||
type ExportConfig struct {
|
||||
// name of the ovf. defaults to the name of the VM
|
||||
Name string `mapstructure:"name"`
|
||||
// overwrite ovf if it exists
|
||||
Force bool `mapstructure:"force"`
|
||||
// include iso and img image files that are attached to the VM
|
||||
Images bool `mapstructure:"images"`
|
||||
// generate manifest using sha1, sha256, sha512. Defaults to 'sha256'. Use 'none' for no manifest.
|
||||
Manifest string `mapstructure:"manifest"`
|
||||
OutputDir OutputConfig `mapstructure:",squash"`
|
||||
// Advanced ovf export options. Options can include:
|
||||
// * mac - MAC address is exported for all ethernet devices
|
||||
// * uuid - UUID is exported for all virtual machines
|
||||
// * extraconfig - all extra configuration options are exported for a virtual machine
|
||||
// * nodevicesubtypes - resource subtypes for CD/DVD drives, floppy drives, and serial and parallel ports are not exported
|
||||
//
|
||||
// For example, adding the following export config option would output the mac addresses for all Ethernet devices in the ovf file:
|
||||
//
|
||||
// ```json
|
||||
// ...
|
||||
// "export": {
|
||||
// "options": ["mac"]
|
||||
// },
|
||||
// ```
|
||||
Options []string `mapstructure:"options"`
|
||||
}
|
||||
|
||||
var sha = map[string]func() hash.Hash{
|
||||
"none": nil,
|
||||
"sha1": sha1.New,
|
||||
"sha256": sha256.New,
|
||||
"sha512": sha512.New,
|
||||
}
|
||||
|
||||
func (c *ExportConfig) Prepare(ctx *interpolate.Context, lc *LocationConfig, pc *common.PackerConfig) []error {
|
||||
var errs *packer.MultiError
|
||||
|
||||
errs = packer.MultiErrorAppend(errs, c.OutputDir.Prepare(ctx, pc)...)
|
||||
|
||||
// manifest should default to sha256
|
||||
if c.Manifest == "" {
|
||||
c.Manifest = "sha256"
|
||||
}
|
||||
if _, ok := sha[c.Manifest]; !ok {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("unknown hash: %s. available options include available options being 'none', 'sha1', 'sha256', 'sha512'", c.Manifest))
|
||||
}
|
||||
|
||||
if c.Name == "" {
|
||||
c.Name = lc.VMName
|
||||
}
|
||||
target := getTarget(c.OutputDir.OutputDir, c.Name)
|
||||
if !c.Force {
|
||||
if _, err := os.Stat(target); err == nil {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("file already exists: %s", target))
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(c.OutputDir.OutputDir, 0750); err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, errors.Wrap(err, "unable to make directory for export"))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs.Errors
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTarget(dir string, name string) string {
|
||||
return filepath.Join(dir, name+".ovf")
|
||||
}
|
||||
|
||||
type StepExport struct {
|
||||
Name string
|
||||
Force bool
|
||||
Images bool
|
||||
Manifest string
|
||||
OutputDir string
|
||||
Options []string
|
||||
mf bytes.Buffer
|
||||
}
|
||||
|
||||
func (s *StepExport) Cleanup(multistep.StateBag) {
|
||||
}
|
||||
|
||||
func (s *StepExport) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vm := state.Get("vm").(*driver.VirtualMachine)
|
||||
|
||||
ui.Message("Starting export...")
|
||||
lease, err := vm.Export()
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "error exporting vm"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
info, err := lease.Wait(ctx, nil)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
u := lease.StartUpdater(ctx, info)
|
||||
defer u.Done()
|
||||
|
||||
cdp := types.OvfCreateDescriptorParams{
|
||||
Name: s.Name,
|
||||
}
|
||||
|
||||
m := vm.NewOvfManager()
|
||||
if len(s.Options) > 0 {
|
||||
exportOptions, err := vm.GetOvfExportOptions(m)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
var unknown []string
|
||||
for _, option := range s.Options {
|
||||
found := false
|
||||
for _, exportOpt := range exportOptions {
|
||||
if exportOpt.Option == option {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
unknown = append(unknown, option)
|
||||
}
|
||||
cdp.ExportOption = append(cdp.ExportOption, option)
|
||||
}
|
||||
|
||||
// only printing error message because the unknown options are just ignored by vcenter
|
||||
if len(unknown) > 0 {
|
||||
ui.Error(fmt.Sprintf("unknown export options %s", strings.Join(unknown, ",")))
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range info.Items {
|
||||
if !s.include(&i) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(i.Path, s.Name) {
|
||||
i.Path = s.Name + "-" + i.Path
|
||||
}
|
||||
|
||||
ui.Message("Downloading: " + i.File().Path)
|
||||
err = s.Download(ctx, lease, i)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message("Exporting file: " + i.File().Path)
|
||||
cdp.OvfFiles = append(cdp.OvfFiles, i.File())
|
||||
}
|
||||
|
||||
if err = lease.Complete(ctx); err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to complete lease"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
desc, err := vm.CreateDescriptor(m, cdp)
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to create descriptor"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
target := getTarget(s.OutputDir, s.Name)
|
||||
file, err := os.Create(target)
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to create file: "+target))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
var w io.Writer = file
|
||||
h, ok := s.newHash()
|
||||
if ok {
|
||||
w = io.MultiWriter(file, h)
|
||||
}
|
||||
|
||||
ui.Message("Writing ovf...")
|
||||
_, err = io.WriteString(w, desc.OvfDescriptor)
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to write descriptor"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to close descriptor"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if s.Manifest == "none" {
|
||||
// manifest does not need to be created, return
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
ui.Message("Creating manifest...")
|
||||
s.addHash(filepath.Base(target), h)
|
||||
|
||||
file, err = os.Create(filepath.Join(s.OutputDir, s.Name+".mf"))
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to create manifest"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
_, err = io.Copy(file, &s.mf)
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to write manifest"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
state.Put("error", errors.Wrap(err, "unable to close file"))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Message("Finished exporting...")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *StepExport) include(item *nfc.FileItem) bool {
|
||||
if s.Images {
|
||||
return true
|
||||
}
|
||||
|
||||
return filepath.Ext(item.Path) == ".vmdk"
|
||||
}
|
||||
|
||||
func (s *StepExport) newHash() (hash.Hash, bool) {
|
||||
// check if function is nil to handle the 'none' case
|
||||
if h, ok := sha[s.Manifest]; ok && h != nil {
|
||||
return h(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (s *StepExport) addHash(p string, h hash.Hash) {
|
||||
_, _ = fmt.Fprintf(&s.mf, "%s(%s)= %x\n", strings.ToUpper(s.Manifest), p, h.Sum(nil))
|
||||
}
|
||||
|
||||
func (s *StepExport) Download(ctx context.Context, lease *nfc.Lease, item nfc.FileItem) error {
|
||||
path := filepath.Join(s.OutputDir, item.Path)
|
||||
opts := soap.Download{}
|
||||
|
||||
if h, ok := s.newHash(); ok {
|
||||
opts.Writer = h
|
||||
defer s.addHash(item.Path, h)
|
||||
}
|
||||
|
||||
return lease.DownloadFile(ctx, path, item, opts)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Code generated by "mapstructure-to-hcl2 -type ExportConfig"; DO NOT EDIT.
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatExportConfig is an auto-generated flat version of ExportConfig.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatExportConfig struct {
|
||||
Name *string `mapstructure:"name" cty:"name"`
|
||||
Force *bool `mapstructure:"force" cty:"force"`
|
||||
Images *bool `mapstructure:"images" cty:"images"`
|
||||
Manifest *string `mapstructure:"manifest" cty:"manifest"`
|
||||
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory"`
|
||||
Options []string `mapstructure:"options" cty:"options"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatExportConfig.
|
||||
// FlatExportConfig is an auto-generated flat version of ExportConfig.
|
||||
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
|
||||
func (*ExportConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
|
||||
return new(FlatExportConfig)
|
||||
}
|
||||
|
||||
// HCL2Spec returns the hcl spec of a ExportConfig.
|
||||
// This spec is used by HCL to read the fields of ExportConfig.
|
||||
// The decoded values from this spec will then be applied to a FlatExportConfig.
|
||||
func (*FlatExportConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false},
|
||||
"force": &hcldec.AttrSpec{Name: "force", Type: cty.Bool, Required: false},
|
||||
"images": &hcldec.AttrSpec{Name: "images", Type: cty.Bool, Required: false},
|
||||
"manifest": &hcldec.AttrSpec{Name: "manifest", Type: cty.String, Required: false},
|
||||
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
||||
"options": &hcldec.AttrSpec{Name: "options", Type: cty.List(cty.String), Required: false},
|
||||
}
|
||||
return s
|
||||
}
|
|
@ -8,6 +8,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
|
||||
"github.com/vmware/govmomi/nfc"
|
||||
"github.com/vmware/govmomi/ovf"
|
||||
|
||||
"github.com/vmware/govmomi/object"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
|
@ -688,6 +693,27 @@ func (vm *VirtualMachine) AddConfigParams(params map[string]string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) Export() (*nfc.Lease, error) {
|
||||
return vm.vm.Export(vm.driver.ctx)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) CreateDescriptor(m *ovf.Manager, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
|
||||
return m.CreateDescriptor(vm.driver.ctx, vm.vm, cdp)
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) NewOvfManager() *ovf.Manager {
|
||||
return ovf.NewManager(vm.vm.Client())
|
||||
}
|
||||
|
||||
func (vm *VirtualMachine) GetOvfExportOptions(m *ovf.Manager) ([]types.OvfOptionInfo, error) {
|
||||
var mgr mo.OvfManager
|
||||
err := property.DefaultCollector(vm.vm.Client()).RetrieveOne(vm.driver.ctx, m.Reference(), nil, &mgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mgr.OvfExportOption, nil
|
||||
}
|
||||
|
||||
func findNetworkAdapter(l object.VirtualDeviceList) (types.BaseVirtualEthernetCard, error) {
|
||||
c := l.SelectByType((*types.VirtualEthernetCard)(nil))
|
||||
if len(c) == 0 {
|
||||
|
|
|
@ -132,6 +132,17 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
},
|
||||
)
|
||||
|
||||
if b.config.Export != nil {
|
||||
steps = append(steps, &common.StepExport{
|
||||
Name: b.config.Export.Name,
|
||||
Force: b.config.Export.Force,
|
||||
Images: b.config.Export.Images,
|
||||
Manifest: b.config.Export.Manifest,
|
||||
OutputDir: b.config.Export.OutputDir.OutputDir,
|
||||
Options: b.config.Export.Options,
|
||||
})
|
||||
}
|
||||
|
||||
b.runner = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(ctx, state)
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ type Config struct {
|
|||
CreateSnapshot bool `mapstructure:"create_snapshot"`
|
||||
// Convert VM to a template. Defaults to `false`.
|
||||
ConvertToTemplate bool `mapstructure:"convert_to_template"`
|
||||
// Configuration for exporting VM to an ovf file.
|
||||
// The VM will not be exported if no [Export Configuration](#export-configuration) is specified.
|
||||
Export *common.ExportConfig `mapstructure:"export"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
@ -77,6 +80,9 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
|||
errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...)
|
||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
||||
if c.Export != nil {
|
||||
errs = packer.MultiErrorAppend(errs, c.Export.Prepare(&c.ctx, &c.LocationConfig, &c.PackerConfig)...)
|
||||
}
|
||||
|
||||
if len(errs.Errors) > 0 {
|
||||
return warnings, errs
|
||||
|
|
|
@ -3,122 +3,124 @@ package iso
|
|||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// FlatConfig is an auto-generated flat version of Config.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatConfig struct {
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory"`
|
||||
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min"`
|
||||
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max"`
|
||||
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server"`
|
||||
Username *string `mapstructure:"username" cty:"username"`
|
||||
Password *string `mapstructure:"password" cty:"password"`
|
||||
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
|
||||
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
|
||||
Version *uint `mapstructure:"vm_version" cty:"vm_version"`
|
||||
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type"`
|
||||
Firmware *string `mapstructure:"firmware" cty:"firmware"`
|
||||
DiskControllerType *string `mapstructure:"disk_controller_type" cty:"disk_controller_type"`
|
||||
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
|
||||
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned"`
|
||||
DiskEagerlyScrub *bool `mapstructure:"disk_eagerly_scrub" cty:"disk_eagerly_scrub"`
|
||||
Storage []FlatDiskConfig `mapstructure:"storage" cty:"storage"`
|
||||
Network *string `mapstructure:"network" cty:"network"`
|
||||
NetworkCard *string `mapstructure:"network_card" cty:"network_card"`
|
||||
NICs []FlatNIC `mapstructure:"network_adapters" cty:"network_adapters"`
|
||||
USBController *bool `mapstructure:"usb_controller" cty:"usb_controller"`
|
||||
Notes *string `mapstructure:"notes" cty:"notes"`
|
||||
VMName *string `mapstructure:"vm_name" cty:"vm_name"`
|
||||
Folder *string `mapstructure:"folder" cty:"folder"`
|
||||
Cluster *string `mapstructure:"cluster" cty:"cluster"`
|
||||
Host *string `mapstructure:"host" cty:"host"`
|
||||
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
|
||||
Datastore *string `mapstructure:"datastore" cty:"datastore"`
|
||||
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs"`
|
||||
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
|
||||
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
|
||||
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
|
||||
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
|
||||
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
|
||||
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
|
||||
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
|
||||
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
|
||||
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
|
||||
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
|
||||
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters"`
|
||||
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum"`
|
||||
ISOChecksumURL *string `mapstructure:"iso_checksum_url" cty:"iso_checksum_url"`
|
||||
ISOChecksumType *string `mapstructure:"iso_checksum_type" cty:"iso_checksum_type"`
|
||||
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url"`
|
||||
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls"`
|
||||
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path"`
|
||||
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension"`
|
||||
CdromType *string `mapstructure:"cdrom_type" cty:"cdrom_type"`
|
||||
ISOPaths []string `mapstructure:"iso_paths" cty:"iso_paths"`
|
||||
RemoveCdrom *bool `mapstructure:"remove_cdrom" cty:"remove_cdrom"`
|
||||
FloppyIMGPath *string `mapstructure:"floppy_img_path" cty:"floppy_img_path"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files"`
|
||||
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs"`
|
||||
BootOrder *string `mapstructure:"boot_order" cty:"boot_order"`
|
||||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
||||
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait"`
|
||||
HTTPIP *string `mapstructure:"http_ip" cty:"http_ip"`
|
||||
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout"`
|
||||
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"`
|
||||
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
||||
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
||||
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
||||
PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name"`
|
||||
PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type"`
|
||||
PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug"`
|
||||
PackerForce *bool `mapstructure:"packer_force" cty:"packer_force"`
|
||||
PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error"`
|
||||
PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables"`
|
||||
PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables"`
|
||||
HTTPDir *string `mapstructure:"http_directory" cty:"http_directory"`
|
||||
HTTPPortMin *int `mapstructure:"http_port_min" cty:"http_port_min"`
|
||||
HTTPPortMax *int `mapstructure:"http_port_max" cty:"http_port_max"`
|
||||
VCenterServer *string `mapstructure:"vcenter_server" cty:"vcenter_server"`
|
||||
Username *string `mapstructure:"username" cty:"username"`
|
||||
Password *string `mapstructure:"password" cty:"password"`
|
||||
InsecureConnection *bool `mapstructure:"insecure_connection" cty:"insecure_connection"`
|
||||
Datacenter *string `mapstructure:"datacenter" cty:"datacenter"`
|
||||
Version *uint `mapstructure:"vm_version" cty:"vm_version"`
|
||||
GuestOSType *string `mapstructure:"guest_os_type" cty:"guest_os_type"`
|
||||
Firmware *string `mapstructure:"firmware" cty:"firmware"`
|
||||
DiskControllerType *string `mapstructure:"disk_controller_type" cty:"disk_controller_type"`
|
||||
DiskSize *int64 `mapstructure:"disk_size" cty:"disk_size"`
|
||||
DiskThinProvisioned *bool `mapstructure:"disk_thin_provisioned" cty:"disk_thin_provisioned"`
|
||||
DiskEagerlyScrub *bool `mapstructure:"disk_eagerly_scrub" cty:"disk_eagerly_scrub"`
|
||||
Storage []FlatDiskConfig `mapstructure:"storage" cty:"storage"`
|
||||
Network *string `mapstructure:"network" cty:"network"`
|
||||
NetworkCard *string `mapstructure:"network_card" cty:"network_card"`
|
||||
NICs []FlatNIC `mapstructure:"network_adapters" cty:"network_adapters"`
|
||||
USBController *bool `mapstructure:"usb_controller" cty:"usb_controller"`
|
||||
Notes *string `mapstructure:"notes" cty:"notes"`
|
||||
VMName *string `mapstructure:"vm_name" cty:"vm_name"`
|
||||
Folder *string `mapstructure:"folder" cty:"folder"`
|
||||
Cluster *string `mapstructure:"cluster" cty:"cluster"`
|
||||
Host *string `mapstructure:"host" cty:"host"`
|
||||
ResourcePool *string `mapstructure:"resource_pool" cty:"resource_pool"`
|
||||
Datastore *string `mapstructure:"datastore" cty:"datastore"`
|
||||
CPUs *int32 `mapstructure:"CPUs" cty:"CPUs"`
|
||||
CpuCores *int32 `mapstructure:"cpu_cores" cty:"cpu_cores"`
|
||||
CPUReservation *int64 `mapstructure:"CPU_reservation" cty:"CPU_reservation"`
|
||||
CPULimit *int64 `mapstructure:"CPU_limit" cty:"CPU_limit"`
|
||||
CpuHotAddEnabled *bool `mapstructure:"CPU_hot_plug" cty:"CPU_hot_plug"`
|
||||
RAM *int64 `mapstructure:"RAM" cty:"RAM"`
|
||||
RAMReservation *int64 `mapstructure:"RAM_reservation" cty:"RAM_reservation"`
|
||||
RAMReserveAll *bool `mapstructure:"RAM_reserve_all" cty:"RAM_reserve_all"`
|
||||
MemoryHotAddEnabled *bool `mapstructure:"RAM_hot_plug" cty:"RAM_hot_plug"`
|
||||
VideoRAM *int64 `mapstructure:"video_ram" cty:"video_ram"`
|
||||
NestedHV *bool `mapstructure:"NestedHV" cty:"NestedHV"`
|
||||
ConfigParams map[string]string `mapstructure:"configuration_parameters" cty:"configuration_parameters"`
|
||||
ISOChecksum *string `mapstructure:"iso_checksum" required:"true" cty:"iso_checksum"`
|
||||
ISOChecksumURL *string `mapstructure:"iso_checksum_url" cty:"iso_checksum_url"`
|
||||
ISOChecksumType *string `mapstructure:"iso_checksum_type" cty:"iso_checksum_type"`
|
||||
RawSingleISOUrl *string `mapstructure:"iso_url" required:"true" cty:"iso_url"`
|
||||
ISOUrls []string `mapstructure:"iso_urls" cty:"iso_urls"`
|
||||
TargetPath *string `mapstructure:"iso_target_path" cty:"iso_target_path"`
|
||||
TargetExtension *string `mapstructure:"iso_target_extension" cty:"iso_target_extension"`
|
||||
CdromType *string `mapstructure:"cdrom_type" cty:"cdrom_type"`
|
||||
ISOPaths []string `mapstructure:"iso_paths" cty:"iso_paths"`
|
||||
RemoveCdrom *bool `mapstructure:"remove_cdrom" cty:"remove_cdrom"`
|
||||
FloppyIMGPath *string `mapstructure:"floppy_img_path" cty:"floppy_img_path"`
|
||||
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files"`
|
||||
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs"`
|
||||
BootOrder *string `mapstructure:"boot_order" cty:"boot_order"`
|
||||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
||||
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait"`
|
||||
HTTPIP *string `mapstructure:"http_ip" cty:"http_ip"`
|
||||
WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout"`
|
||||
SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout"`
|
||||
Type *string `mapstructure:"communicator" cty:"communicator"`
|
||||
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting"`
|
||||
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host"`
|
||||
SSHPort *int `mapstructure:"ssh_port" cty:"ssh_port"`
|
||||
SSHUsername *string `mapstructure:"ssh_username" cty:"ssh_username"`
|
||||
SSHPassword *string `mapstructure:"ssh_password" cty:"ssh_password"`
|
||||
SSHKeyPairName *string `mapstructure:"ssh_keypair_name" cty:"ssh_keypair_name"`
|
||||
SSHTemporaryKeyPairName *string `mapstructure:"temporary_key_pair_name" cty:"temporary_key_pair_name"`
|
||||
SSHClearAuthorizedKeys *bool `mapstructure:"ssh_clear_authorized_keys" cty:"ssh_clear_authorized_keys"`
|
||||
SSHPrivateKeyFile *string `mapstructure:"ssh_private_key_file" cty:"ssh_private_key_file"`
|
||||
SSHPty *bool `mapstructure:"ssh_pty" cty:"ssh_pty"`
|
||||
SSHTimeout *string `mapstructure:"ssh_timeout" cty:"ssh_timeout"`
|
||||
SSHAgentAuth *bool `mapstructure:"ssh_agent_auth" cty:"ssh_agent_auth"`
|
||||
SSHDisableAgentForwarding *bool `mapstructure:"ssh_disable_agent_forwarding" cty:"ssh_disable_agent_forwarding"`
|
||||
SSHHandshakeAttempts *int `mapstructure:"ssh_handshake_attempts" cty:"ssh_handshake_attempts"`
|
||||
SSHBastionHost *string `mapstructure:"ssh_bastion_host" cty:"ssh_bastion_host"`
|
||||
SSHBastionPort *int `mapstructure:"ssh_bastion_port" cty:"ssh_bastion_port"`
|
||||
SSHBastionAgentAuth *bool `mapstructure:"ssh_bastion_agent_auth" cty:"ssh_bastion_agent_auth"`
|
||||
SSHBastionUsername *string `mapstructure:"ssh_bastion_username" cty:"ssh_bastion_username"`
|
||||
SSHBastionPassword *string `mapstructure:"ssh_bastion_password" cty:"ssh_bastion_password"`
|
||||
SSHBastionInteractive *bool `mapstructure:"ssh_bastion_interactive" cty:"ssh_bastion_interactive"`
|
||||
SSHBastionPrivateKeyFile *string `mapstructure:"ssh_bastion_private_key_file" cty:"ssh_bastion_private_key_file"`
|
||||
SSHFileTransferMethod *string `mapstructure:"ssh_file_transfer_method" cty:"ssh_file_transfer_method"`
|
||||
SSHProxyHost *string `mapstructure:"ssh_proxy_host" cty:"ssh_proxy_host"`
|
||||
SSHProxyPort *int `mapstructure:"ssh_proxy_port" cty:"ssh_proxy_port"`
|
||||
SSHProxyUsername *string `mapstructure:"ssh_proxy_username" cty:"ssh_proxy_username"`
|
||||
SSHProxyPassword *string `mapstructure:"ssh_proxy_password" cty:"ssh_proxy_password"`
|
||||
SSHKeepAliveInterval *string `mapstructure:"ssh_keep_alive_interval" cty:"ssh_keep_alive_interval"`
|
||||
SSHReadWriteTimeout *string `mapstructure:"ssh_read_write_timeout" cty:"ssh_read_write_timeout"`
|
||||
SSHRemoteTunnels []string `mapstructure:"ssh_remote_tunnels" cty:"ssh_remote_tunnels"`
|
||||
SSHLocalTunnels []string `mapstructure:"ssh_local_tunnels" cty:"ssh_local_tunnels"`
|
||||
SSHPublicKey []byte `mapstructure:"ssh_public_key" cty:"ssh_public_key"`
|
||||
SSHPrivateKey []byte `mapstructure:"ssh_private_key" cty:"ssh_private_key"`
|
||||
WinRMUser *string `mapstructure:"winrm_username" cty:"winrm_username"`
|
||||
WinRMPassword *string `mapstructure:"winrm_password" cty:"winrm_password"`
|
||||
WinRMHost *string `mapstructure:"winrm_host" cty:"winrm_host"`
|
||||
WinRMPort *int `mapstructure:"winrm_port" cty:"winrm_port"`
|
||||
WinRMTimeout *string `mapstructure:"winrm_timeout" cty:"winrm_timeout"`
|
||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
||||
Command *string `mapstructure:"shutdown_command" cty:"shutdown_command"`
|
||||
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
||||
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
||||
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
||||
Export *common.FlatExportConfig `mapstructure:"export" cty:"export"`
|
||||
}
|
||||
|
||||
// FlatMapstructure returns a new FlatConfig.
|
||||
|
@ -243,6 +245,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
|
||||
"create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, Required: false},
|
||||
"convert_to_template": &hcldec.AttrSpec{Name: "convert_to_template", Type: cty.Bool, Required: false},
|
||||
"export": &hcldec.BlockSpec{TypeName: "export", Nested: hcldec.ObjectSpec((*common.FlatExportConfig)(nil).HCL2Spec())},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
|
|
@ -70,8 +70,14 @@ type CreateConfig struct {
|
|||
func (c *CreateConfig) Prepare() []error {
|
||||
var errs []error
|
||||
|
||||
if c.DiskSize == 0 {
|
||||
errs = append(errs, fmt.Errorf("'disk_size' is required"))
|
||||
if len(c.Storage) > 0 {
|
||||
for i, storage := range c.Storage {
|
||||
if storage.DiskSize == 0 {
|
||||
errs = append(errs, fmt.Errorf("storage[%d].'disk_size' is required", i))
|
||||
}
|
||||
}
|
||||
} else if c.DiskSize == 0 {
|
||||
errs = append(errs, fmt.Errorf("'disk_size' or 'storage' is required"))
|
||||
}
|
||||
|
||||
if c.GuestOSType == "" {
|
||||
|
@ -94,17 +100,18 @@ type StepCreateVM struct {
|
|||
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
d := state.Get("driver").(*driver.Driver)
|
||||
vmPath := fmt.Sprintf("%s/%s", s.Location.Folder, s.Location.VMName)
|
||||
|
||||
vm, err := d.FindVM(s.Location.VMName)
|
||||
vm, err := d.FindVM(vmPath)
|
||||
|
||||
if s.Force == false && err == nil {
|
||||
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it: %v", s.Location.VMName, err))
|
||||
state.Put("error", fmt.Errorf("%s already exists, you can use -force flag to destroy it: %v", vmPath, err))
|
||||
return multistep.ActionHalt
|
||||
} else if s.Force == true && err == nil {
|
||||
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", s.Location.VMName))
|
||||
ui.Say(fmt.Sprintf("the vm/template %s already exists, but deleting it due to -force flag", vmPath))
|
||||
err := vm.Destroy()
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("error destroying %s: %v", s.Location.VMName, err))
|
||||
state.Put("error", fmt.Errorf("error destroying %s: %v", vmPath, err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,115 @@ import (
|
|||
shell_local "github.com/hashicorp/packer/provisioner/shell-local"
|
||||
)
|
||||
|
||||
func TestBuild_VarArgs(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedCode int
|
||||
fileCheck
|
||||
}{
|
||||
{
|
||||
name: "json - json varfile sets an apple env var",
|
||||
args: []string{
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.json"),
|
||||
filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"apple.txt"}},
|
||||
},
|
||||
{
|
||||
name: "json - json varfile sets an apple env var, " +
|
||||
"override with banana cli var",
|
||||
args: []string{
|
||||
"-var", "fruit=banana",
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.json"),
|
||||
filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"banana.txt"}},
|
||||
},
|
||||
{
|
||||
name: "json - arg sets a pear env var",
|
||||
args: []string{
|
||||
"-var=fruit=pear",
|
||||
filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"pear.txt"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "json - inexistent var file errs",
|
||||
args: []string{
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "potato.json"),
|
||||
filepath.Join(testFixture("var-arg"), "fruit_builder.json"),
|
||||
},
|
||||
expectedCode: 1,
|
||||
fileCheck: fileCheck{notExpected: []string{"potato.txt"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - inexistent json var file errs",
|
||||
args: []string{
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "potato.json"),
|
||||
testFixture("var-arg"),
|
||||
},
|
||||
expectedCode: 1,
|
||||
fileCheck: fileCheck{notExpected: []string{"potato.txt"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - inexistent hcl var file errs",
|
||||
args: []string{
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "potato.hcl"),
|
||||
testFixture("var-arg"),
|
||||
},
|
||||
expectedCode: 1,
|
||||
fileCheck: fileCheck{notExpected: []string{"potato.hcl"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - auto varfile sets a chocolate env var",
|
||||
args: []string{
|
||||
testFixture("var-arg"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"chocolate.txt"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - hcl varfile sets a apple env var",
|
||||
args: []string{
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.hcl"),
|
||||
testFixture("var-arg"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"apple.txt"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - json varfile sets a apple env var",
|
||||
args: []string{
|
||||
"-var-file=" + filepath.Join(testFixture("var-arg"), "apple.json"),
|
||||
testFixture("var-arg"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"apple.txt"}},
|
||||
},
|
||||
|
||||
{
|
||||
name: "hcl - arg sets a tomato env var",
|
||||
args: []string{
|
||||
"-var=fruit=tomato",
|
||||
testFixture("var-arg"),
|
||||
},
|
||||
fileCheck: fileCheck{expected: []string{"tomato.txt"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
run(t, tt.args, tt.expectedCode)
|
||||
defer cleanup()
|
||||
tt.fileCheck.verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOnlyFileCommaFlags(t *testing.T) {
|
||||
c := &BuildCommand{
|
||||
Meta: testMetaFile(t),
|
||||
|
@ -217,6 +326,35 @@ func TestBuildWithNonExistingBuilder(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func run(t *testing.T, args []string, expectedCode int) {
|
||||
t.Helper()
|
||||
|
||||
c := &BuildCommand{
|
||||
Meta: testMetaFile(t),
|
||||
}
|
||||
|
||||
if code := c.Run(args); code != expectedCode {
|
||||
fatalCommand(t, c.Meta)
|
||||
}
|
||||
}
|
||||
|
||||
type fileCheck struct {
|
||||
expected, notExpected []string
|
||||
}
|
||||
|
||||
func (fc fileCheck) verify(t *testing.T) {
|
||||
for _, f := range fc.expected {
|
||||
if !fileExists(f) {
|
||||
t.Errorf("Expected to find %s", f)
|
||||
}
|
||||
}
|
||||
for _, f := range fc.notExpected {
|
||||
if fileExists(f) {
|
||||
t.Errorf("Expected to not find %s", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fileExists returns true if the filename is found
|
||||
func fileExists(filename string) bool {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
|
@ -272,6 +410,7 @@ func cleanup(moreFiles ...string) {
|
|||
os.RemoveAll("lilas.txt")
|
||||
os.RemoveAll("campanules.txt")
|
||||
os.RemoveAll("ducky.txt")
|
||||
os.RemoveAll("banana.txt")
|
||||
for _, file := range moreFiles {
|
||||
os.RemoveAll(file)
|
||||
}
|
||||
|
|
|
@ -44,14 +44,23 @@ func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
|
|||
config.Template = tpl
|
||||
|
||||
fj := &kvflag.FlagJSON{}
|
||||
// First populate fj with contents from var files
|
||||
for _, file := range m.varFiles {
|
||||
err := fj.Set(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Now read fj values back into flagvars and set as config.Variables. Only
|
||||
// add to flagVars if the key doesn't already exist, because flagVars comes
|
||||
// from the command line and should not be overridden by variable files.
|
||||
if m.flagVars == nil {
|
||||
m.flagVars = map[string]string{}
|
||||
}
|
||||
for k, v := range *fj {
|
||||
m.flagVars[k] = v
|
||||
if _, exists := m.flagVars[k]; !exists {
|
||||
m.flagVars[k] = v
|
||||
}
|
||||
}
|
||||
config.Variables = m.flagVars
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
fruit = "apple"
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"fruit": "apple"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
fruit = "chocolate" // is that even a fruit !?
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"variables": {
|
||||
"fruit": ""
|
||||
},
|
||||
"builders": [
|
||||
{
|
||||
"communicator": "none",
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"post-processors": [
|
||||
[
|
||||
{
|
||||
"name": "apple",
|
||||
"type": "shell-local",
|
||||
"inline": [ "echo {{ user `fruit` }} > {{ user `fruit` }}.txt" ]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
variable "fruit" {
|
||||
type = string
|
||||
}
|
||||
|
||||
locals {
|
||||
fruit = var.fruit
|
||||
}
|
||||
|
||||
source "null" "builder" {
|
||||
communicator = "none"
|
||||
}
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"source.null.builder",
|
||||
]
|
||||
|
||||
provisioner "shell-local" {
|
||||
inline = ["echo ${local.fruit} > ${local.fruit}.txt"]
|
||||
}
|
||||
}
|
|
@ -25,11 +25,6 @@ import (
|
|||
// * HTTP
|
||||
// * Amazon S3
|
||||
//
|
||||
//
|
||||
// \~> On Windows, using a symlink to refer to local files is currently
|
||||
// unsupported. Packer will always copy a local iso into the Packer cache
|
||||
// directory.
|
||||
//
|
||||
// Examples:
|
||||
// go-getter can guess the checksum type based on `iso_checksum` len.
|
||||
//
|
||||
|
|
|
@ -7,9 +7,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
PossibleNumbers = "0123456789"
|
||||
PossibleLowerCase = "abcdefghijklmnopqrstuvwxyz"
|
||||
PossibleUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
PossibleNumbers = "0123456789"
|
||||
PossibleLowerCase = "abcdefghijklmnopqrstuvwxyz"
|
||||
PossibleUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
PossibleSpecialCharacter = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
|
||||
PossibleAlphaNum = PossibleNumbers + PossibleLowerCase + PossibleUpperCase
|
||||
PossibleAlphaNumLower = PossibleNumbers + PossibleLowerCase
|
||||
|
|
|
@ -94,18 +94,17 @@ func (p *Parser) parse(filename string, varFiles []string, argVars map[string]st
|
|||
|
||||
// Decode variable blocks so that they are available later on. Here locals
|
||||
// can use input variables so we decode them firsthand.
|
||||
var locals []*Local
|
||||
{
|
||||
for _, file := range files {
|
||||
diags = append(diags, cfg.decodeInputVariables(file)...)
|
||||
}
|
||||
|
||||
var locals []*Local
|
||||
for _, file := range files {
|
||||
moreLocals, morediags := cfg.parseLocalVariables(file)
|
||||
diags = append(diags, morediags...)
|
||||
locals = append(locals, moreLocals...)
|
||||
}
|
||||
diags = append(diags, cfg.evaluateLocalVariables(locals)...)
|
||||
}
|
||||
|
||||
// parse var files
|
||||
|
@ -130,11 +129,17 @@ func (p *Parser) parse(filename string, varFiles []string, argVars map[string]st
|
|||
for _, filename := range hclVarFiles {
|
||||
f, moreDiags := p.ParseHCLFile(filename)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
varFiles = append(varFiles, f)
|
||||
}
|
||||
for _, filename := range jsonVarFiles {
|
||||
f, moreDiags := p.ParseJSONFile(filename)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
varFiles = append(varFiles, f)
|
||||
}
|
||||
|
||||
|
@ -145,6 +150,7 @@ func (p *Parser) parse(filename string, varFiles []string, argVars map[string]st
|
|||
diags = append(diags, moreDiags...)
|
||||
_, moreDiags = cfg.LocalVariables.Values()
|
||||
diags = append(diags, moreDiags...)
|
||||
diags = append(diags, cfg.evaluateLocalVariables(locals)...)
|
||||
|
||||
// decode the actual content
|
||||
for _, file := range files {
|
||||
|
|
|
@ -30,7 +30,7 @@ fi
|
|||
|
||||
get_prs(){
|
||||
# git log v0.10.2...c3861d167533fb797b0fae0c380806625712e5f7 |
|
||||
git log HEAD...${LAST_RELEASE} |
|
||||
git log HEAD...${LAST_RELEASE} --first-parent --oneline --grep="Merge pull request #[0-9]\+" --grep="(#[0-9]\+)$" |
|
||||
grep -o "#\([0-9]\+\)" | awk -F\# '{print $2}' | while read line
|
||||
do
|
||||
grep -q "GH-${line}" CHANGELOG.md
|
||||
|
|
158
vendor/github.com/oracle/oci-go-sdk/common/auth/certificate_retriever.go
generated
vendored
Normal file
158
vendor/github.com/oracle/oci-go-sdk/common/auth/certificate_retriever.go
generated
vendored
Normal file
|
@ -0,0 +1,158 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// x509CertificateRetriever provides an X509 certificate with the RSA private key
|
||||
type x509CertificateRetriever interface {
|
||||
Refresh() error
|
||||
CertificatePemRaw() []byte
|
||||
Certificate() *x509.Certificate
|
||||
PrivateKeyPemRaw() []byte
|
||||
PrivateKey() *rsa.PrivateKey
|
||||
}
|
||||
|
||||
// urlBasedX509CertificateRetriever retrieves PEM-encoded X509 certificates from the given URLs.
|
||||
type urlBasedX509CertificateRetriever struct {
|
||||
certURL string
|
||||
privateKeyURL string
|
||||
passphrase string
|
||||
certificatePemRaw []byte
|
||||
certificate *x509.Certificate
|
||||
privateKeyPemRaw []byte
|
||||
privateKey *rsa.PrivateKey
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func newURLBasedX509CertificateRetriever(certURL, privateKeyURL, passphrase string) x509CertificateRetriever {
|
||||
return &urlBasedX509CertificateRetriever{
|
||||
certURL: certURL,
|
||||
privateKeyURL: privateKeyURL,
|
||||
passphrase: passphrase,
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh() is failure atomic, i.e., CertificatePemRaw(), Certificate(), PrivateKeyPemRaw(), and PrivateKey() would
|
||||
// return their previous values if Refresh() fails.
|
||||
func (r *urlBasedX509CertificateRetriever) Refresh() error {
|
||||
common.Debugln("Refreshing certificate")
|
||||
|
||||
r.mux.Lock()
|
||||
defer r.mux.Unlock()
|
||||
|
||||
var err error
|
||||
|
||||
var certificatePemRaw []byte
|
||||
var certificate *x509.Certificate
|
||||
if certificatePemRaw, certificate, err = r.renewCertificate(r.certURL); err != nil {
|
||||
return fmt.Errorf("failed to renew certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
var privateKeyPemRaw []byte
|
||||
var privateKey *rsa.PrivateKey
|
||||
if r.privateKeyURL != "" {
|
||||
if privateKeyPemRaw, privateKey, err = r.renewPrivateKey(r.privateKeyURL, r.passphrase); err != nil {
|
||||
return fmt.Errorf("failed to renew private key: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
r.certificatePemRaw = certificatePemRaw
|
||||
r.certificate = certificate
|
||||
r.privateKeyPemRaw = privateKeyPemRaw
|
||||
r.privateKey = privateKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *urlBasedX509CertificateRetriever) renewCertificate(url string) (certificatePemRaw []byte, certificate *x509.Certificate, err error) {
|
||||
var body bytes.Buffer
|
||||
if body, err = httpGet(url); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get certificate from %s: %s", url, err.Error())
|
||||
}
|
||||
|
||||
certificatePemRaw = body.Bytes()
|
||||
var block *pem.Block
|
||||
block, _ = pem.Decode(certificatePemRaw)
|
||||
if block == nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse the new certificate, not valid pem data")
|
||||
}
|
||||
|
||||
if certificate, err = x509.ParseCertificate(block.Bytes); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse the new certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
return certificatePemRaw, certificate, nil
|
||||
}
|
||||
|
||||
func (r *urlBasedX509CertificateRetriever) renewPrivateKey(url, passphrase string) (privateKeyPemRaw []byte, privateKey *rsa.PrivateKey, err error) {
|
||||
var body bytes.Buffer
|
||||
if body, err = httpGet(url); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get private key from %s: %s", url, err.Error())
|
||||
}
|
||||
|
||||
privateKeyPemRaw = body.Bytes()
|
||||
if privateKey, err = common.PrivateKeyFromBytes(privateKeyPemRaw, &passphrase); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse the new private key: %s", err.Error())
|
||||
}
|
||||
|
||||
return privateKeyPemRaw, privateKey, nil
|
||||
}
|
||||
|
||||
func (r *urlBasedX509CertificateRetriever) CertificatePemRaw() []byte {
|
||||
r.mux.Lock()
|
||||
defer r.mux.Unlock()
|
||||
|
||||
if r.certificatePemRaw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := make([]byte, len(r.certificatePemRaw))
|
||||
copy(c, r.certificatePemRaw)
|
||||
return c
|
||||
}
|
||||
|
||||
func (r *urlBasedX509CertificateRetriever) Certificate() *x509.Certificate {
|
||||
r.mux.Lock()
|
||||
defer r.mux.Unlock()
|
||||
|
||||
if r.certificate == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := *r.certificate
|
||||
return &c
|
||||
}
|
||||
|
||||
func (r *urlBasedX509CertificateRetriever) PrivateKeyPemRaw() []byte {
|
||||
r.mux.Lock()
|
||||
defer r.mux.Unlock()
|
||||
|
||||
if r.privateKeyPemRaw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := make([]byte, len(r.privateKeyPemRaw))
|
||||
copy(c, r.privateKeyPemRaw)
|
||||
return c
|
||||
}
|
||||
|
||||
func (r *urlBasedX509CertificateRetriever) PrivateKey() *rsa.PrivateKey {
|
||||
r.mux.Lock()
|
||||
defer r.mux.Unlock()
|
||||
|
||||
if r.privateKey == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := *r.privateKey
|
||||
return &c
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
)
|
||||
|
||||
type instancePrincipalConfigurationProvider struct {
|
||||
keyProvider *instancePrincipalKeyProvider
|
||||
region *common.Region
|
||||
}
|
||||
|
||||
//InstancePrincipalConfigurationProvider returns a configuration for instance principals
|
||||
func InstancePrincipalConfigurationProvider() (common.ConfigurationProvider, error) {
|
||||
var err error
|
||||
var keyProvider *instancePrincipalKeyProvider
|
||||
if keyProvider, err = newInstancePrincipalKeyProvider(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create a new key provider for instance principal: %s", err.Error())
|
||||
}
|
||||
return instancePrincipalConfigurationProvider{keyProvider: keyProvider, region: nil}, nil
|
||||
}
|
||||
|
||||
//InstancePrincipalConfigurationProviderForRegion returns a configuration for instance principals with a given region
|
||||
func InstancePrincipalConfigurationProviderForRegion(region common.Region) (common.ConfigurationProvider, error) {
|
||||
var err error
|
||||
var keyProvider *instancePrincipalKeyProvider
|
||||
if keyProvider, err = newInstancePrincipalKeyProvider(); err != nil {
|
||||
return nil, fmt.Errorf("failed to create a new key provider for instance principal: %s", err.Error())
|
||||
}
|
||||
return instancePrincipalConfigurationProvider{keyProvider: keyProvider, region: ®ion}, nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
||||
return p.keyProvider.PrivateRSAKey()
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProvider) KeyID() (string, error) {
|
||||
return p.keyProvider.KeyID()
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProvider) TenancyOCID() (string, error) {
|
||||
return p.keyProvider.TenancyOCID()
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProvider) UserOCID() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProvider) KeyFingerprint() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p instancePrincipalConfigurationProvider) Region() (string, error) {
|
||||
if p.region == nil {
|
||||
return string(p.keyProvider.RegionForFederationClient()), nil
|
||||
}
|
||||
return string(*p.region), nil
|
||||
}
|
292
vendor/github.com/oracle/oci-go-sdk/common/auth/federation_client.go
generated
vendored
Normal file
292
vendor/github.com/oracle/oci-go-sdk/common/auth/federation_client.go
generated
vendored
Normal file
|
@ -0,0 +1,292 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
// Package auth provides supporting functions and structs for authentication
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// federationClient is a client to retrieve the security token for an instance principal necessary to sign a request.
|
||||
// It also provides the private key whose corresponding public key is used to retrieve the security token.
|
||||
type federationClient interface {
|
||||
PrivateKey() (*rsa.PrivateKey, error)
|
||||
SecurityToken() (string, error)
|
||||
}
|
||||
|
||||
// x509FederationClient retrieves a security token from Auth service.
|
||||
type x509FederationClient struct {
|
||||
tenancyID string
|
||||
sessionKeySupplier sessionKeySupplier
|
||||
leafCertificateRetriever x509CertificateRetriever
|
||||
intermediateCertificateRetrievers []x509CertificateRetriever
|
||||
securityToken securityToken
|
||||
authClient *common.BaseClient
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func newX509FederationClient(region common.Region, tenancyID string, leafCertificateRetriever x509CertificateRetriever, intermediateCertificateRetrievers []x509CertificateRetriever) federationClient {
|
||||
client := &x509FederationClient{
|
||||
tenancyID: tenancyID,
|
||||
leafCertificateRetriever: leafCertificateRetriever,
|
||||
intermediateCertificateRetrievers: intermediateCertificateRetrievers,
|
||||
}
|
||||
client.sessionKeySupplier = newSessionKeySupplier()
|
||||
client.authClient = newAuthClient(region, client)
|
||||
return client
|
||||
}
|
||||
|
||||
var (
|
||||
genericHeaders = []string{"date", "(request-target)"} // "host" is not needed for the federation endpoint. Don't ask me why.
|
||||
bodyHeaders = []string{"content-length", "content-type", "x-content-sha256"}
|
||||
)
|
||||
|
||||
func newAuthClient(region common.Region, provider common.KeyProvider) *common.BaseClient {
|
||||
signer := common.RequestSigner(provider, genericHeaders, bodyHeaders)
|
||||
client := common.DefaultBaseClientWithSigner(signer)
|
||||
if region == common.RegionSEA {
|
||||
client.Host = "https://auth.r1.oracleiaas.com"
|
||||
} else {
|
||||
client.Host = fmt.Sprintf(common.DefaultHostURLTemplate, "auth", string(region))
|
||||
}
|
||||
client.BasePath = "v1/x509"
|
||||
return &client
|
||||
}
|
||||
|
||||
// For authClient to sign requests to X509 Federation Endpoint
|
||||
func (c *x509FederationClient) KeyID() (string, error) {
|
||||
tenancy := c.tenancyID
|
||||
fingerprint := fingerprint(c.leafCertificateRetriever.Certificate())
|
||||
return fmt.Sprintf("%s/fed-x509/%s", tenancy, fingerprint), nil
|
||||
}
|
||||
|
||||
// For authClient to sign requests to X509 Federation Endpoint
|
||||
func (c *x509FederationClient) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
||||
return c.leafCertificateRetriever.PrivateKey(), nil
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) PrivateKey() (*rsa.PrivateKey, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if err := c.renewSecurityTokenIfNotValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.sessionKeySupplier.PrivateKey(), nil
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) SecurityToken() (token string, err error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if err = c.renewSecurityTokenIfNotValid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.securityToken.String(), nil
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) renewSecurityTokenIfNotValid() (err error) {
|
||||
if c.securityToken == nil || !c.securityToken.Valid() {
|
||||
if err = c.renewSecurityToken(); err != nil {
|
||||
return fmt.Errorf("failed to renew security token: %s", err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) renewSecurityToken() (err error) {
|
||||
if err = c.sessionKeySupplier.Refresh(); err != nil {
|
||||
return fmt.Errorf("failed to refresh session key: %s", err.Error())
|
||||
}
|
||||
|
||||
if err = c.leafCertificateRetriever.Refresh(); err != nil {
|
||||
return fmt.Errorf("failed to refresh leaf certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
updatedTenancyID := extractTenancyIDFromCertificate(c.leafCertificateRetriever.Certificate())
|
||||
if c.tenancyID != updatedTenancyID {
|
||||
err = fmt.Errorf("unexpected update of tenancy OCID in the leaf certificate. Previous tenancy: %s, Updated: %s", c.tenancyID, updatedTenancyID)
|
||||
return
|
||||
}
|
||||
|
||||
for _, retriever := range c.intermediateCertificateRetrievers {
|
||||
if err = retriever.Refresh(); err != nil {
|
||||
return fmt.Errorf("failed to refresh intermediate certificate: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if c.securityToken, err = c.getSecurityToken(); err != nil {
|
||||
return fmt.Errorf("failed to get security token: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) getSecurityToken() (securityToken, error) {
|
||||
request := c.makeX509FederationRequest()
|
||||
|
||||
var err error
|
||||
var httpRequest http.Request
|
||||
if httpRequest, err = common.MakeDefaultHTTPRequestWithTaggedStruct(http.MethodPost, "", request); err != nil {
|
||||
return nil, fmt.Errorf("failed to make http request: %s", err.Error())
|
||||
}
|
||||
|
||||
var httpResponse *http.Response
|
||||
defer common.CloseBodyIfValid(httpResponse)
|
||||
if httpResponse, err = c.authClient.Call(context.Background(), &httpRequest); err != nil {
|
||||
return nil, fmt.Errorf("failed to call: %s", err.Error())
|
||||
}
|
||||
|
||||
response := x509FederationResponse{}
|
||||
if err = common.UnmarshalResponse(httpResponse, &response); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal the response: %s", err.Error())
|
||||
}
|
||||
|
||||
return newInstancePrincipalToken(response.Token.Token)
|
||||
}
|
||||
|
||||
type x509FederationRequest struct {
|
||||
X509FederationDetails `contributesTo:"body"`
|
||||
}
|
||||
|
||||
// X509FederationDetails x509 federation details
|
||||
type X509FederationDetails struct {
|
||||
Certificate string `mandatory:"true" json:"certificate,omitempty"`
|
||||
PublicKey string `mandatory:"true" json:"publicKey,omitempty"`
|
||||
IntermediateCertificates []string `mandatory:"false" json:"intermediateCertificates,omitempty"`
|
||||
}
|
||||
|
||||
type x509FederationResponse struct {
|
||||
Token `presentIn:"body"`
|
||||
}
|
||||
|
||||
// Token token
|
||||
type Token struct {
|
||||
Token string `mandatory:"true" json:"token,omitempty"`
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) makeX509FederationRequest() *x509FederationRequest {
|
||||
certificate := c.sanitizeCertificateString(string(c.leafCertificateRetriever.CertificatePemRaw()))
|
||||
publicKey := c.sanitizeCertificateString(string(c.sessionKeySupplier.PublicKeyPemRaw()))
|
||||
var intermediateCertificates []string
|
||||
for _, retriever := range c.intermediateCertificateRetrievers {
|
||||
intermediateCertificates = append(intermediateCertificates, c.sanitizeCertificateString(string(retriever.CertificatePemRaw())))
|
||||
}
|
||||
|
||||
details := X509FederationDetails{
|
||||
Certificate: certificate,
|
||||
PublicKey: publicKey,
|
||||
IntermediateCertificates: intermediateCertificates,
|
||||
}
|
||||
return &x509FederationRequest{details}
|
||||
}
|
||||
|
||||
func (c *x509FederationClient) sanitizeCertificateString(certString string) string {
|
||||
certString = strings.Replace(certString, "-----BEGIN CERTIFICATE-----", "", -1)
|
||||
certString = strings.Replace(certString, "-----END CERTIFICATE-----", "", -1)
|
||||
certString = strings.Replace(certString, "-----BEGIN PUBLIC KEY-----", "", -1)
|
||||
certString = strings.Replace(certString, "-----END PUBLIC KEY-----", "", -1)
|
||||
certString = strings.Replace(certString, "\n", "", -1)
|
||||
return certString
|
||||
}
|
||||
|
||||
// sessionKeySupplier provides an RSA keypair which can be re-generated by calling Refresh().
|
||||
type sessionKeySupplier interface {
|
||||
Refresh() error
|
||||
PrivateKey() *rsa.PrivateKey
|
||||
PublicKeyPemRaw() []byte
|
||||
}
|
||||
|
||||
// inMemorySessionKeySupplier implements sessionKeySupplier to vend an RSA keypair.
|
||||
// Refresh() generates a new RSA keypair with a random source, and keeps it in memory.
|
||||
//
|
||||
// inMemorySessionKeySupplier is not thread-safe.
|
||||
type inMemorySessionKeySupplier struct {
|
||||
keySize int
|
||||
privateKey *rsa.PrivateKey
|
||||
publicKeyPemRaw []byte
|
||||
}
|
||||
|
||||
// newSessionKeySupplier creates and returns a sessionKeySupplier instance which generates key pairs of size 2048.
|
||||
func newSessionKeySupplier() sessionKeySupplier {
|
||||
return &inMemorySessionKeySupplier{keySize: 2048}
|
||||
}
|
||||
|
||||
// Refresh() is failure atomic, i.e., PrivateKey() and PublicKeyPemRaw() would return their previous values
|
||||
// if Refresh() fails.
|
||||
func (s *inMemorySessionKeySupplier) Refresh() (err error) {
|
||||
common.Debugln("Refreshing session key")
|
||||
|
||||
var privateKey *rsa.PrivateKey
|
||||
privateKey, err = rsa.GenerateKey(rand.Reader, s.keySize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate a new keypair: %s", err)
|
||||
}
|
||||
|
||||
var publicKeyAsnBytes []byte
|
||||
if publicKeyAsnBytes, err = x509.MarshalPKIXPublicKey(privateKey.Public()); err != nil {
|
||||
return fmt.Errorf("failed to marshal the public part of the new keypair: %s", err.Error())
|
||||
}
|
||||
publicKeyPemRaw := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: publicKeyAsnBytes,
|
||||
})
|
||||
|
||||
s.privateKey = privateKey
|
||||
s.publicKeyPemRaw = publicKeyPemRaw
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *inMemorySessionKeySupplier) PrivateKey() *rsa.PrivateKey {
|
||||
if s.privateKey == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := *s.privateKey
|
||||
return &c
|
||||
}
|
||||
|
||||
func (s *inMemorySessionKeySupplier) PublicKeyPemRaw() []byte {
|
||||
if s.publicKeyPemRaw == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c := make([]byte, len(s.publicKeyPemRaw))
|
||||
copy(c, s.publicKeyPemRaw)
|
||||
return c
|
||||
}
|
||||
|
||||
type securityToken interface {
|
||||
fmt.Stringer
|
||||
Valid() bool
|
||||
}
|
||||
|
||||
type instancePrincipalToken struct {
|
||||
tokenString string
|
||||
jwtToken *jwtToken
|
||||
}
|
||||
|
||||
func newInstancePrincipalToken(tokenString string) (newToken securityToken, err error) {
|
||||
var jwtToken *jwtToken
|
||||
if jwtToken, err = parseJwt(tokenString); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse the token string \"%s\": %s", tokenString, err.Error())
|
||||
}
|
||||
return &instancePrincipalToken{tokenString, jwtToken}, nil
|
||||
}
|
||||
|
||||
func (t *instancePrincipalToken) String() string {
|
||||
return t.tokenString
|
||||
}
|
||||
|
||||
func (t *instancePrincipalToken) Valid() bool {
|
||||
return !t.jwtToken.expired()
|
||||
}
|
100
vendor/github.com/oracle/oci-go-sdk/common/auth/instance_principal_key_provider.go
generated
vendored
Normal file
100
vendor/github.com/oracle/oci-go-sdk/common/auth/instance_principal_key_provider.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
)
|
||||
|
||||
const (
|
||||
regionURL = `http://169.254.169.254/opc/v1/instance/region`
|
||||
leafCertificateURL = `http://169.254.169.254/opc/v1/identity/cert.pem`
|
||||
leafCertificateKeyURL = `http://169.254.169.254/opc/v1/identity/key.pem`
|
||||
leafCertificateKeyPassphrase = `` // No passphrase for the private key for Compute instances
|
||||
intermediateCertificateURL = `http://169.254.169.254/opc/v1/identity/intermediate.pem`
|
||||
intermediateCertificateKeyURL = ``
|
||||
intermediateCertificateKeyPassphrase = `` // No passphrase for the private key for Compute instances
|
||||
)
|
||||
|
||||
// instancePrincipalKeyProvider implements KeyProvider to provide a key ID and its corresponding private key
|
||||
// for an instance principal by getting a security token via x509FederationClient.
|
||||
//
|
||||
// The region name of the endpoint for x509FederationClient is obtained from the metadata service on the compute
|
||||
// instance.
|
||||
type instancePrincipalKeyProvider struct {
|
||||
regionForFederationClient common.Region
|
||||
federationClient federationClient
|
||||
tenancyID string
|
||||
}
|
||||
|
||||
// newInstancePrincipalKeyProvider creates and returns an instancePrincipalKeyProvider instance based on
|
||||
// x509FederationClient.
|
||||
//
|
||||
// NOTE: There is a race condition between PrivateRSAKey() and KeyID(). These two pieces are tightly coupled; KeyID
|
||||
// includes a security token obtained from Auth service by giving a public key which is paired with PrivateRSAKey.
|
||||
// The x509FederationClient caches the security token in memory until it is expired. Thus, even if a client obtains a
|
||||
// KeyID that is not expired at the moment, the PrivateRSAKey that the client acquires at a next moment could be
|
||||
// invalid because the KeyID could be already expired.
|
||||
func newInstancePrincipalKeyProvider() (provider *instancePrincipalKeyProvider, err error) {
|
||||
var region common.Region
|
||||
if region, err = getRegionForFederationClient(regionURL); err != nil {
|
||||
err = fmt.Errorf("failed to get the region name from %s: %s", regionURL, err.Error())
|
||||
common.Logln(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
leafCertificateRetriever := newURLBasedX509CertificateRetriever(
|
||||
leafCertificateURL, leafCertificateKeyURL, leafCertificateKeyPassphrase)
|
||||
intermediateCertificateRetrievers := []x509CertificateRetriever{
|
||||
newURLBasedX509CertificateRetriever(
|
||||
intermediateCertificateURL, intermediateCertificateKeyURL, intermediateCertificateKeyPassphrase),
|
||||
}
|
||||
|
||||
if err = leafCertificateRetriever.Refresh(); err != nil {
|
||||
err = fmt.Errorf("failed to refresh the leaf certificate: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
tenancyID := extractTenancyIDFromCertificate(leafCertificateRetriever.Certificate())
|
||||
|
||||
federationClient := newX509FederationClient(
|
||||
region, tenancyID, leafCertificateRetriever, intermediateCertificateRetrievers)
|
||||
|
||||
provider = &instancePrincipalKeyProvider{regionForFederationClient: region, federationClient: federationClient, tenancyID: tenancyID}
|
||||
return
|
||||
}
|
||||
|
||||
func getRegionForFederationClient(url string) (r common.Region, err error) {
|
||||
var body bytes.Buffer
|
||||
if body, err = httpGet(url); err != nil {
|
||||
return
|
||||
}
|
||||
return common.StringToRegion(body.String()), nil
|
||||
}
|
||||
|
||||
func (p *instancePrincipalKeyProvider) RegionForFederationClient() common.Region {
|
||||
return p.regionForFederationClient
|
||||
}
|
||||
|
||||
func (p *instancePrincipalKeyProvider) PrivateRSAKey() (privateKey *rsa.PrivateKey, err error) {
|
||||
if privateKey, err = p.federationClient.PrivateKey(); err != nil {
|
||||
err = fmt.Errorf("failed to get private key: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func (p *instancePrincipalKeyProvider) KeyID() (string, error) {
|
||||
var securityToken string
|
||||
var err error
|
||||
if securityToken, err = p.federationClient.SecurityToken(); err != nil {
|
||||
return "", fmt.Errorf("failed to get security token: %s", err.Error())
|
||||
}
|
||||
return fmt.Sprintf("ST$%s", securityToken), nil
|
||||
}
|
||||
|
||||
func (p *instancePrincipalKeyProvider) TenancyOCID() (string, error) {
|
||||
return p.tenancyID, nil
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type jwtToken struct {
|
||||
raw string
|
||||
header map[string]interface{}
|
||||
payload map[string]interface{}
|
||||
}
|
||||
|
||||
func (t *jwtToken) expired() bool {
|
||||
exp := int64(t.payload["exp"].(float64))
|
||||
return exp <= time.Now().Unix()
|
||||
}
|
||||
|
||||
func parseJwt(tokenString string) (*jwtToken, error) {
|
||||
parts := strings.Split(tokenString, ".")
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("the given token string contains an invalid number of parts")
|
||||
}
|
||||
|
||||
token := &jwtToken{raw: tokenString}
|
||||
var err error
|
||||
|
||||
// Parse Header part
|
||||
var headerBytes []byte
|
||||
if headerBytes, err = decodePart(parts[0]); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the header bytes: %s", err.Error())
|
||||
}
|
||||
if err = json.Unmarshal(headerBytes, &token.header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse Payload part
|
||||
var payloadBytes []byte
|
||||
if payloadBytes, err = decodePart(parts[1]); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the payload bytes: %s", err.Error())
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewBuffer(payloadBytes))
|
||||
if err = decoder.Decode(&token.payload); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode the payload json: %s", err.Error())
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func decodePart(partString string) ([]byte, error) {
|
||||
if l := len(partString) % 4; 0 < l {
|
||||
partString += strings.Repeat("=", 4-l)
|
||||
}
|
||||
return base64.URLEncoding.DecodeString(partString)
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/oracle/oci-go-sdk/common"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// httpGet makes a simple HTTP GET request to the given URL, expecting only "200 OK" status code.
|
||||
// This is basically for the Instance Metadata Service.
|
||||
func httpGet(url string) (body bytes.Buffer, err error) {
|
||||
var response *http.Response
|
||||
if response, err = http.Get(url); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.IfDebug(func() {
|
||||
if dump, e := httputil.DumpResponse(response, true); e == nil {
|
||||
common.Logf("Dump Response %v", string(dump))
|
||||
} else {
|
||||
common.Debugln(e)
|
||||
}
|
||||
})
|
||||
|
||||
defer response.Body.Close()
|
||||
if _, err = body.ReadFrom(response.Body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("HTTP Get failed: URL: %s, Status: %s, Message: %s",
|
||||
url, response.Status, body.String())
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func extractTenancyIDFromCertificate(cert *x509.Certificate) string {
|
||||
for _, nameAttr := range cert.Subject.Names {
|
||||
value := nameAttr.Value.(string)
|
||||
if strings.HasPrefix(value, "opc-tenant:") {
|
||||
return value[len("opc-tenant:"):]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fingerprint(certificate *x509.Certificate) string {
|
||||
fingerprint := sha1.Sum(certificate.Raw)
|
||||
return colonSeparatedString(fingerprint)
|
||||
}
|
||||
|
||||
func colonSeparatedString(fingerprint [sha1.Size]byte) string {
|
||||
spaceSeparated := fmt.Sprintf("% x", fingerprint)
|
||||
return strings.Replace(spaceSeparated, " ", ":", -1)
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ovf
|
||||
|
||||
/*
|
||||
Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_VirtualSystemSettingData.xsd
|
||||
*/
|
||||
|
||||
type CIMVirtualSystemSettingData struct {
|
||||
ElementName string `xml:"ElementName"`
|
||||
InstanceID string `xml:"InstanceID"`
|
||||
|
||||
AutomaticRecoveryAction *uint8 `xml:"AutomaticRecoveryAction"`
|
||||
AutomaticShutdownAction *uint8 `xml:"AutomaticShutdownAction"`
|
||||
AutomaticStartupAction *uint8 `xml:"AutomaticStartupAction"`
|
||||
AutomaticStartupActionDelay *string `xml:"AutomaticStartupActionDelay>Interval"`
|
||||
AutomaticStartupActionSequenceNumber *uint16 `xml:"AutomaticStartupActionSequenceNumber"`
|
||||
Caption *string `xml:"Caption"`
|
||||
ConfigurationDataRoot *string `xml:"ConfigurationDataRoot"`
|
||||
ConfigurationFile *string `xml:"ConfigurationFile"`
|
||||
ConfigurationID *string `xml:"ConfigurationID"`
|
||||
CreationTime *string `xml:"CreationTime"`
|
||||
Description *string `xml:"Description"`
|
||||
LogDataRoot *string `xml:"LogDataRoot"`
|
||||
Notes []string `xml:"Notes"`
|
||||
RecoveryFile *string `xml:"RecoveryFile"`
|
||||
SnapshotDataRoot *string `xml:"SnapshotDataRoot"`
|
||||
SuspendDataRoot *string `xml:"SuspendDataRoot"`
|
||||
SwapFileDataRoot *string `xml:"SwapFileDataRoot"`
|
||||
VirtualSystemIdentifier *string `xml:"VirtualSystemIdentifier"`
|
||||
VirtualSystemType *string `xml:"VirtualSystemType"`
|
||||
}
|
||||
|
||||
/*
|
||||
Source: http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2.24.0/CIM_ResourceAllocationSettingData.xsd
|
||||
*/
|
||||
|
||||
type CIMResourceAllocationSettingData struct {
|
||||
ElementName string `xml:"ElementName"`
|
||||
InstanceID string `xml:"InstanceID"`
|
||||
|
||||
ResourceType *uint16 `xml:"ResourceType"`
|
||||
OtherResourceType *string `xml:"OtherResourceType"`
|
||||
ResourceSubType *string `xml:"ResourceSubType"`
|
||||
|
||||
AddressOnParent *string `xml:"AddressOnParent"`
|
||||
Address *string `xml:"Address"`
|
||||
AllocationUnits *string `xml:"AllocationUnits"`
|
||||
AutomaticAllocation *bool `xml:"AutomaticAllocation"`
|
||||
AutomaticDeallocation *bool `xml:"AutomaticDeallocation"`
|
||||
Caption *string `xml:"Caption"`
|
||||
Connection []string `xml:"Connection"`
|
||||
ConsumerVisibility *uint16 `xml:"ConsumerVisibility"`
|
||||
Description *string `xml:"Description"`
|
||||
HostResource []string `xml:"HostResource"`
|
||||
Limit *uint64 `xml:"Limit"`
|
||||
MappingBehavior *uint `xml:"MappingBehavior"`
|
||||
Parent *string `xml:"Parent"`
|
||||
PoolID *string `xml:"PoolID"`
|
||||
Reservation *uint64 `xml:"Reservation"`
|
||||
VirtualQuantity *uint `xml:"VirtualQuantity"`
|
||||
VirtualQuantityUnits *string `xml:"VirtualQuantityUnits"`
|
||||
Weight *uint `xml:"Weight"`
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package ovf provides functionality to unmarshal and inspect the structure
|
||||
of an OVF file. It is not a complete implementation of the specification and
|
||||
is intended to be used to import virtual infrastructure into vSphere.
|
||||
|
||||
For a complete specification of the OVF standard, refer to:
|
||||
https://www.dmtf.org/sites/default/files/standards/documents/DSP0243_2.1.0.pdf
|
||||
*/
|
||||
package ovf
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ovf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/xml"
|
||||
)
|
||||
|
||||
const (
|
||||
ovfEnvHeader = `<Environment
|
||||
xmlns="http://schemas.dmtf.org/ovf/environment/1"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:oe="http://schemas.dmtf.org/ovf/environment/1"
|
||||
xmlns:ve="http://www.vmware.com/schema/ovfenv"
|
||||
oe:id=""
|
||||
ve:esxId="%s">`
|
||||
ovfEnvPlatformSection = `<PlatformSection>
|
||||
<Kind>%s</Kind>
|
||||
<Version>%s</Version>
|
||||
<Vendor>%s</Vendor>
|
||||
<Locale>%s</Locale>
|
||||
</PlatformSection>`
|
||||
ovfEnvPropertyHeader = `<PropertySection>`
|
||||
ovfEnvPropertyEntry = `<Property oe:key="%s" oe:value="%s"/>`
|
||||
ovfEnvPropertyFooter = `</PropertySection>`
|
||||
ovfEnvFooter = `</Environment>`
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
XMLName xml.Name `xml:"http://schemas.dmtf.org/ovf/environment/1 Environment"`
|
||||
ID string `xml:"id,attr"`
|
||||
EsxID string `xml:"http://www.vmware.com/schema/ovfenv esxId,attr"`
|
||||
|
||||
Platform *PlatformSection `xml:"PlatformSection"`
|
||||
Property *PropertySection `xml:"PropertySection"`
|
||||
}
|
||||
|
||||
type PlatformSection struct {
|
||||
Kind string `xml:"Kind"`
|
||||
Version string `xml:"Version"`
|
||||
Vendor string `xml:"Vendor"`
|
||||
Locale string `xml:"Locale"`
|
||||
}
|
||||
|
||||
type PropertySection struct {
|
||||
Properties []EnvProperty `xml:"Property"`
|
||||
}
|
||||
|
||||
type EnvProperty struct {
|
||||
Key string `xml:"key,attr"`
|
||||
Value string `xml:"value,attr"`
|
||||
}
|
||||
|
||||
// Marshal marshals Env to xml by using xml.Marshal.
|
||||
func (e Env) Marshal() (string, error) {
|
||||
x, err := xml.Marshal(e)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%s", xml.Header, x), nil
|
||||
}
|
||||
|
||||
// MarshalManual manually marshals Env to xml suitable for a vApp guest.
|
||||
// It exists to overcome the lack of expressiveness in Go's XML namespaces.
|
||||
func (e Env) MarshalManual() string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(xml.Header)
|
||||
buffer.WriteString(fmt.Sprintf(ovfEnvHeader, e.EsxID))
|
||||
buffer.WriteString(fmt.Sprintf(ovfEnvPlatformSection, e.Platform.Kind, e.Platform.Version, e.Platform.Vendor, e.Platform.Locale))
|
||||
|
||||
buffer.WriteString(fmt.Sprint(ovfEnvPropertyHeader))
|
||||
for _, p := range e.Property.Properties {
|
||||
buffer.WriteString(fmt.Sprintf(ovfEnvPropertyEntry, p.Key, p.Value))
|
||||
}
|
||||
buffer.WriteString(fmt.Sprint(ovfEnvPropertyFooter))
|
||||
|
||||
buffer.WriteString(fmt.Sprint(ovfEnvFooter))
|
||||
|
||||
return buffer.String()
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ovf
|
||||
|
||||
type Envelope struct {
|
||||
References []File `xml:"References>File"`
|
||||
|
||||
// Package level meta-data
|
||||
Annotation *AnnotationSection `xml:"AnnotationSection"`
|
||||
Product *ProductSection `xml:"ProductSection"`
|
||||
Network *NetworkSection `xml:"NetworkSection"`
|
||||
Disk *DiskSection `xml:"DiskSection"`
|
||||
OperatingSystem *OperatingSystemSection `xml:"OperatingSystemSection"`
|
||||
Eula *EulaSection `xml:"EulaSection"`
|
||||
VirtualHardware *VirtualHardwareSection `xml:"VirtualHardwareSection"`
|
||||
ResourceAllocation *ResourceAllocationSection `xml:"ResourceAllocationSection"`
|
||||
DeploymentOption *DeploymentOptionSection `xml:"DeploymentOptionSection"`
|
||||
|
||||
// Content: A VirtualSystem or a VirtualSystemCollection
|
||||
VirtualSystem *VirtualSystem `xml:"VirtualSystem"`
|
||||
}
|
||||
|
||||
type VirtualSystem struct {
|
||||
Content
|
||||
|
||||
Annotation []AnnotationSection `xml:"AnnotationSection"`
|
||||
Product []ProductSection `xml:"ProductSection"`
|
||||
OperatingSystem []OperatingSystemSection `xml:"OperatingSystemSection"`
|
||||
Eula []EulaSection `xml:"EulaSection"`
|
||||
VirtualHardware []VirtualHardwareSection `xml:"VirtualHardwareSection"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
ID string `xml:"id,attr"`
|
||||
Href string `xml:"href,attr"`
|
||||
Size uint `xml:"size,attr"`
|
||||
Compression *string `xml:"compression,attr"`
|
||||
ChunkSize *int `xml:"chunkSize,attr"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
ID string `xml:"id,attr"`
|
||||
Info string `xml:"Info"`
|
||||
Name *string `xml:"Name"`
|
||||
}
|
||||
|
||||
type Section struct {
|
||||
Required *bool `xml:"required,attr"`
|
||||
Info string `xml:"Info"`
|
||||
}
|
||||
|
||||
type AnnotationSection struct {
|
||||
Section
|
||||
|
||||
Annotation string `xml:"Annotation"`
|
||||
}
|
||||
|
||||
type ProductSection struct {
|
||||
Section
|
||||
|
||||
Class *string `xml:"class,attr"`
|
||||
Instance *string `xml:"instance,attr"`
|
||||
|
||||
Product string `xml:"Product"`
|
||||
Vendor string `xml:"Vendor"`
|
||||
Version string `xml:"Version"`
|
||||
FullVersion string `xml:"FullVersion"`
|
||||
ProductURL string `xml:"ProductUrl"`
|
||||
VendorURL string `xml:"VendorUrl"`
|
||||
AppURL string `xml:"AppUrl"`
|
||||
Property []Property `xml:"Property"`
|
||||
}
|
||||
|
||||
type Property struct {
|
||||
Key string `xml:"key,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Qualifiers *string `xml:"qualifiers,attr"`
|
||||
UserConfigurable *bool `xml:"userConfigurable,attr"`
|
||||
Default *string `xml:"value,attr"`
|
||||
Password *bool `xml:"password,attr"`
|
||||
|
||||
Label *string `xml:"Label"`
|
||||
Description *string `xml:"Description"`
|
||||
|
||||
Values []PropertyConfigurationValue `xml:"Value"`
|
||||
}
|
||||
|
||||
type PropertyConfigurationValue struct {
|
||||
Value string `xml:"value,attr"`
|
||||
Configuration *string `xml:"configuration,attr"`
|
||||
}
|
||||
|
||||
type NetworkSection struct {
|
||||
Section
|
||||
|
||||
Networks []Network `xml:"Network"`
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
Name string `xml:"name,attr"`
|
||||
|
||||
Description string `xml:"Description"`
|
||||
}
|
||||
|
||||
type DiskSection struct {
|
||||
Section
|
||||
|
||||
Disks []VirtualDiskDesc `xml:"Disk"`
|
||||
}
|
||||
|
||||
type VirtualDiskDesc struct {
|
||||
DiskID string `xml:"diskId,attr"`
|
||||
FileRef *string `xml:"fileRef,attr"`
|
||||
Capacity string `xml:"capacity,attr"`
|
||||
CapacityAllocationUnits *string `xml:"capacityAllocationUnits,attr"`
|
||||
Format *string `xml:"format,attr"`
|
||||
PopulatedSize *int `xml:"populatedSize,attr"`
|
||||
ParentRef *string `xml:"parentRef,attr"`
|
||||
}
|
||||
|
||||
type OperatingSystemSection struct {
|
||||
Section
|
||||
|
||||
ID int16 `xml:"id,attr"`
|
||||
Version *string `xml:"version,attr"`
|
||||
OSType *string `xml:"osType,attr"`
|
||||
|
||||
Description *string `xml:"Description"`
|
||||
}
|
||||
|
||||
type EulaSection struct {
|
||||
Section
|
||||
|
||||
License string `xml:"License"`
|
||||
}
|
||||
|
||||
type VirtualHardwareSection struct {
|
||||
Section
|
||||
|
||||
ID *string `xml:"id,attr"`
|
||||
Transport *string `xml:"transport,attr"`
|
||||
|
||||
System *VirtualSystemSettingData `xml:"System"`
|
||||
Item []ResourceAllocationSettingData `xml:"Item"`
|
||||
}
|
||||
|
||||
type VirtualSystemSettingData struct {
|
||||
CIMVirtualSystemSettingData
|
||||
}
|
||||
|
||||
type ResourceAllocationSettingData struct {
|
||||
CIMResourceAllocationSettingData
|
||||
|
||||
Required *bool `xml:"required,attr"`
|
||||
Configuration *string `xml:"configuration,attr"`
|
||||
Bound *string `xml:"bound,attr"`
|
||||
}
|
||||
|
||||
type ResourceAllocationSection struct {
|
||||
Section
|
||||
|
||||
Item []ResourceAllocationSettingData `xml:"Item"`
|
||||
}
|
||||
|
||||
type DeploymentOptionSection struct {
|
||||
Section
|
||||
|
||||
Configuration []DeploymentOptionConfiguration `xml:"Configuration"`
|
||||
}
|
||||
|
||||
type DeploymentOptionConfiguration struct {
|
||||
ID string `xml:"id,attr"`
|
||||
Default *bool `xml:"default,attr"`
|
||||
|
||||
Label string `xml:"Label"`
|
||||
Description string `xml:"Description"`
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
Copyright (c) 2015-2017 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ovf
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
types.ManagedObjectReference
|
||||
|
||||
c *vim25.Client
|
||||
}
|
||||
|
||||
func NewManager(c *vim25.Client) *Manager {
|
||||
return &Manager{*c.ServiceContent.OvfManager, c}
|
||||
}
|
||||
|
||||
// CreateDescriptor wraps methods.CreateDescriptor
|
||||
func (m *Manager) CreateDescriptor(ctx context.Context, obj mo.Reference, cdp types.OvfCreateDescriptorParams) (*types.OvfCreateDescriptorResult, error) {
|
||||
req := types.CreateDescriptor{
|
||||
This: m.Reference(),
|
||||
Obj: obj.Reference(),
|
||||
Cdp: cdp,
|
||||
}
|
||||
|
||||
res, err := methods.CreateDescriptor(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
// CreateImportSpec wraps methods.CreateImportSpec
|
||||
func (m *Manager) CreateImportSpec(ctx context.Context, ovfDescriptor string, resourcePool mo.Reference, datastore mo.Reference, cisp types.OvfCreateImportSpecParams) (*types.OvfCreateImportSpecResult, error) {
|
||||
req := types.CreateImportSpec{
|
||||
This: m.Reference(),
|
||||
OvfDescriptor: ovfDescriptor,
|
||||
ResourcePool: resourcePool.Reference(),
|
||||
Datastore: datastore.Reference(),
|
||||
Cisp: cisp,
|
||||
}
|
||||
|
||||
res, err := methods.CreateImportSpec(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
// ParseDescriptor wraps methods.ParseDescriptor
|
||||
func (m *Manager) ParseDescriptor(ctx context.Context, ovfDescriptor string, pdp types.OvfParseDescriptorParams) (*types.OvfParseDescriptorResult, error) {
|
||||
req := types.ParseDescriptor{
|
||||
This: m.Reference(),
|
||||
OvfDescriptor: ovfDescriptor,
|
||||
Pdp: pdp,
|
||||
}
|
||||
|
||||
res, err := methods.ParseDescriptor(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
// ValidateHost wraps methods.ValidateHost
|
||||
func (m *Manager) ValidateHost(ctx context.Context, ovfDescriptor string, host mo.Reference, vhp types.OvfValidateHostParams) (*types.OvfValidateHostResult, error) {
|
||||
req := types.ValidateHost{
|
||||
This: m.Reference(),
|
||||
OvfDescriptor: ovfDescriptor,
|
||||
Host: host.Reference(),
|
||||
Vhp: vhp,
|
||||
}
|
||||
|
||||
res, err := methods.ValidateHost(ctx, m.c, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ovf
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/xml"
|
||||
)
|
||||
|
||||
func Unmarshal(r io.Reader) (*Envelope, error) {
|
||||
var e Envelope
|
||||
|
||||
dec := xml.NewDecoder(r)
|
||||
err := dec.Decode(&e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &e, nil
|
||||
}
|
|
@ -485,6 +485,7 @@ github.com/nu7hatch/gouuid
|
|||
github.com/olekukonko/tablewriter
|
||||
# github.com/oracle/oci-go-sdk v1.8.0
|
||||
github.com/oracle/oci-go-sdk/common
|
||||
github.com/oracle/oci-go-sdk/common/auth
|
||||
github.com/oracle/oci-go-sdk/core
|
||||
# github.com/outscale/osc-go v0.0.1
|
||||
github.com/outscale/osc-go/oapi
|
||||
|
@ -572,6 +573,7 @@ github.com/vmware/govmomi/find
|
|||
github.com/vmware/govmomi/list
|
||||
github.com/vmware/govmomi/nfc
|
||||
github.com/vmware/govmomi/object
|
||||
github.com/vmware/govmomi/ovf
|
||||
github.com/vmware/govmomi/property
|
||||
github.com/vmware/govmomi/session
|
||||
github.com/vmware/govmomi/task
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
var GitCommit string
|
||||
|
||||
// The main version number that is being run at the moment.
|
||||
const Version = "1.5.5"
|
||||
const Version = "1.5.6"
|
||||
|
||||
// A pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
|
|
@ -2,7 +2,7 @@ set :base_url, "https://www.packer.io/"
|
|||
|
||||
activate :hashicorp do |h|
|
||||
h.name = "packer"
|
||||
h.version = "1.5.4"
|
||||
h.version = "1.5.5"
|
||||
h.github_slug = "hashicorp/packer"
|
||||
h.website_root = "website"
|
||||
end
|
||||
|
|
|
@ -58,6 +58,9 @@ contribution here!
|
|||
* [jakobadam/packer-qemu-templates](https://github.com/jakobadam/packer-qemu-templates)
|
||||
\- QEMU templates for various operating systems
|
||||
|
||||
* [lucidone/baseliner](https://git.sr.ht/~lucidone/baseliner) - Example of using
|
||||
QEMU + Ansible with Packer
|
||||
|
||||
## Wrappers
|
||||
|
||||
- [packer-config](https://github.com/ianchesal/packer-config) - a Ruby model that lets you build Packer configurations in Ruby
|
||||
|
|
|
@ -182,7 +182,7 @@ variables are available:
|
|||
-> **Note:** Packer uses pre-built AMIs as the source for building images.
|
||||
These source AMIs may include volumes that are not flagged to be destroyed on
|
||||
termination of the instance building the new image. In addition to those
|
||||
volumes created by this builder, any volumes inn the source AMI which are not
|
||||
volumes created by this builder, any volumes in the source AMI which are not
|
||||
marked for deletion on termination will remain in your account.
|
||||
|
||||
## Build function template engine variables
|
||||
|
|
|
@ -33,6 +33,9 @@ authentication see the documentation on [Required Keys and
|
|||
OCIDs](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/apisigningkey.htm)
|
||||
([Oracle Cloud
|
||||
IDs](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/identifiers.htm)).
|
||||
Alternatively you can use [Instance
|
||||
Principals](https://docs.cloud.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm)
|
||||
in which case you don't need the above user authorization.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
|
@ -65,11 +68,6 @@ builder.
|
|||
- `compartment_ocid` (string) - The OCID of the
|
||||
[compartment](https://docs.us-phoenix-1.oraclecloud.com/Content/GSG/Tasks/choosingcompartments.htm)
|
||||
|
||||
- `fingerprint` (string) - Fingerprint for the OCI API signing key. Overrides
|
||||
value provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present.
|
||||
|
||||
- `shape` (string) - The template that determines the number of CPUs, amount
|
||||
of memory, and other resources allocated to a newly created instance.
|
||||
|
||||
|
@ -90,41 +88,51 @@ builder.
|
|||
|
||||
### Optional
|
||||
|
||||
- `use_instance_principals` (boolean) - Whether to use [Instance
|
||||
Principals](https://docs.cloud.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm)
|
||||
instead of User Principals. If this key is set to true, setting any one of the `access_cfg_file`,
|
||||
`access_cfg_file_account`, `region`, `tenancy_ocid`, `user_ocid`, `key_file`, `fingerprint`,
|
||||
`pass_phrase` will result in configuration validation errors.
|
||||
Defaults to `false`.
|
||||
|
||||
- `access_cfg_file` (string) - The path to the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm).
|
||||
This cannot be used along with the `use_instance_principals` key.
|
||||
Defaults to `$HOME/.oci/config`.
|
||||
|
||||
- `access_cfg_file_account` (string) - The specific account in the [OCI
|
||||
config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
to use. Defaults to `DEFAULT`.
|
||||
- `access_cfg_file_account` (string) - The specific account in the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm) to use.
|
||||
This cannot be used along with the `use_instance_principals` key.
|
||||
Defaults to `DEFAULT`.
|
||||
|
||||
- `region` (string) - An Oracle Cloud Infrastructure region. Overrides value provided by the
|
||||
[OCI config file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present. This cannot be used along with the `use_instance_principals` key.
|
||||
|
||||
- `tenancy_ocid` (string) - The OCID of your tenancy. Overrides value provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm) if present.
|
||||
This cannot be used along with the `use_instance_principals` key.
|
||||
|
||||
- `user_ocid` (string) - The OCID of the user calling the OCI API. Overrides value provided by the
|
||||
[OCI config file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present. This cannot be used along with the `use_instance_principals` key.
|
||||
|
||||
- `key_file` (string) - Full path and filename of the OCI API signing key. Overrides value provided
|
||||
by the [OCI config file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present. This cannot be used along with the `use_instance_principals` key.
|
||||
|
||||
- `fingerprint` (string) - Fingerprint for the OCI API signing key. Overrides value provided by the
|
||||
[OCI config file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm) if
|
||||
present. This cannot be used along with the `use_instance_principals` key.
|
||||
|
||||
- `pass_phrase` (string) - Pass phrase used to decrypt the OCI API signing key. Overrides value provided
|
||||
by the [OCI config file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present. This cannot be used along with the `use_instance_principals` key.
|
||||
|
||||
- `image_name` (string) - The name to assign to the resulting custom image.
|
||||
|
||||
- `key_file` (string) - Full path and filename of the OCI API signing key.
|
||||
Overrides value provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present.
|
||||
|
||||
- `pass_phrase` (string) - Pass phrase used to decrypt the OCI API signing
|
||||
key. Overrides value provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present.
|
||||
|
||||
- `region` (string) - An Oracle Cloud Infrastructure region. Overrides value
|
||||
provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present.
|
||||
|
||||
- `tenancy_ocid` (string) - The OCID of your tenancy. Overrides value
|
||||
provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present.
|
||||
|
||||
- `user_ocid` (string) - The OCID of the user calling the OCI API. Overrides
|
||||
value provided by the [OCI config
|
||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
||||
if present.
|
||||
- `instance_name` (string) - The name to assign to the instance used for the image creation process.
|
||||
If not set a name of the form `instanceYYYYMMDDhhmmss` will be used.
|
||||
|
||||
- `use_private_ip` (boolean) - Use private ip addresses to connect to the
|
||||
instance via ssh.
|
||||
|
@ -191,3 +199,46 @@ substituted with the letter `a` and OCIDS have been shortened for brevity.
|
|||
"type": "oracle-oci"
|
||||
}
|
||||
```
|
||||
|
||||
## Using Instance Principals
|
||||
|
||||
Here is a basic example. Note that account specific configuration has been
|
||||
substituted with the letter `a` and OCIDS have been shortened for brevity.
|
||||
|
||||
```json
|
||||
{
|
||||
"use_instance_principals": "true",
|
||||
"availability_domain": "aaaa:PHX-AD-1",
|
||||
"base_image_ocid": "ocid1.image.oc1.phx.aaaaaaaa5yu6pw3riqtuhxzov7fdngi4tsteganmao54nq3pyxu3hxcuzmoa",
|
||||
"compartment_ocid": "ocid1.compartment.oc1..aaa",
|
||||
"image_name": "ExampleImage",
|
||||
"shape": "VM.Standard2.1",
|
||||
"ssh_username": "opc",
|
||||
"subnet_ocid": "ocid1.subnet.oc1..aaa",
|
||||
"type": "oracle-oci"
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
[opc@packerhost ~]$ packer build packer.json
|
||||
oracle-oci: output will be in this color.
|
||||
|
||||
==> oracle-oci: Creating temporary ssh key for instance...
|
||||
==> oracle-oci: Creating instance...
|
||||
==> oracle-oci: Created instance (ocid1.instance.oc1.phx.aaa).
|
||||
==> oracle-oci: Waiting for instance to enter 'RUNNING' state...
|
||||
==> oracle-oci: Instance 'RUNNING'.
|
||||
==> oracle-oci: Instance has IP: 10.10.10.10.
|
||||
==> oracle-oci: Using ssh communicator to connect: 10.10.10.10
|
||||
==> oracle-oci: Waiting for SSH to become available...
|
||||
==> oracle-oci: Connected to SSH!
|
||||
==> oracle-oci: Creating image from instance...
|
||||
==> oracle-oci: Image created.
|
||||
==> oracle-oci: Terminating instance (ocid1.instance.oc1.phx.aaa)...
|
||||
==> oracle-oci: Terminated instance.
|
||||
Build 'oracle-oci' finished.
|
||||
|
||||
==> Builds finished. The artifacts of successful builds are:
|
||||
--> oracle-oci: An image was created: 'ExampleImage' (OCID: ocid1.image.oc1.phx.aaa) in region 'us-phoenix-1'
|
||||
[opc@packerhost ~]$
|
||||
```
|
||||
|
|
|
@ -3,7 +3,7 @@ description: |
|
|||
The osc-bsusurrogate Packer builder is like the chroot builder, but does not
|
||||
require running inside an Outscale virtual machine.
|
||||
layout: docs
|
||||
page_title: 'Outacale BSU Surrogate - Builders'
|
||||
page_title: 'Outscale BSU Surrogate - Builders'
|
||||
sidebar_current: 'docs-builders-osc-bsusurrogate'
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ description: |
|
|||
The osc-bsuvolume Packer builder is like the BSU builder, but is intended to
|
||||
create BSU volumes rather than a machine image.
|
||||
layout: docs
|
||||
page_title: 'Amazon BSU Volume - Builders'
|
||||
page_title: 'Outscale BSU Volume - Builders'
|
||||
sidebar_current: 'docs-builders-osc-bsuvolume'
|
||||
---
|
||||
|
||||
|
@ -11,7 +11,7 @@ sidebar_current: 'docs-builders-osc-bsuvolume'
|
|||
|
||||
Type: `osc-bsuvolume`
|
||||
|
||||
The `osc-bsuvolume` Packer builder is able to create Ouscale Block Stogate Unit
|
||||
The `osc-bsuvolume` Packer builder is able to create Outscale Block Stogate Unit
|
||||
volumes which are prepopulated with filesystems or data.
|
||||
|
||||
This builder builds BSU volumes by launching an Outscale VM from a source OMI,
|
||||
|
@ -133,7 +133,7 @@ builder.
|
|||
|
||||
- `snapshot_users` (array of strings) - A list of account IDs that have
|
||||
access to create volumes from the snapshot(s). By default no additional
|
||||
users other than the user creating the OMIS has permissions to create
|
||||
users other than the user creating the OMI has permissions to create
|
||||
volumes from the backing snapshot(s).
|
||||
|
||||
- `source_omi_filter` (object) - Filters used to populate the `source_omi` field.
|
||||
|
@ -155,8 +155,8 @@ builder.
|
|||
}
|
||||
```
|
||||
|
||||
This selects an Ubuntu 16.04 HVM BSU OMIS from Canonical. NOTE:
|
||||
This will fail unless *exactly* one OMIS is returned. In the above example,
|
||||
This selects an Ubuntu 16.04 HVM BSU OMI from Canonical. NOTE:
|
||||
This will fail unless *exactly* one OMI is returned. In the above example,
|
||||
`most_recent` will cause this to succeed by selecting the newest image.
|
||||
|
||||
- `ssh_keypair_name` (string) - If specified, this is the key that will be used for SSH with the machine. The key must match a key pair name loaded up into Outscale. By default, this is blank, and Packer will generate a temporary keypair unless [`ssh_password`](../templates/communicator.html#ssh_password) is used. [`ssh_private_key_file`](../templates/communicator.html#ssh_private_key_file) or `ssh_agent_auth` must be specified when `ssh_keypair_name` is utilized.
|
||||
|
@ -251,7 +251,7 @@ builder.
|
|||
environmental variables. See the configuration reference in the section above
|
||||
for more information on what environmental variables Packer will look for.
|
||||
|
||||
Further information on locating OMIS IDs and their relationship to VM
|
||||
Further information on locating OMI's IDs and their relationship to VM
|
||||
types and regions can be found in the Outscale Documentation [reference](https://wiki.outscale.net/display/EN/Official+OMIs+Reference).
|
||||
|
||||
## Accessing the Instance to Debug
|
||||
|
@ -278,5 +278,5 @@ variables are available:
|
|||
-> **Note:** Packer uses pre-built OMIs as the source for building images.
|
||||
These source OMIs may include volumes that are not flagged to be destroyed on
|
||||
termination of the instance building the new image. In addition to those
|
||||
volumes created by this builder, any volumes inn the source OMI which are not
|
||||
volumes created by this builder, any volumes in the source OMI which are not
|
||||
marked for deletion on termination will remain in your account.
|
||||
|
|
|
@ -180,6 +180,9 @@ builder.
|
|||
|
||||
- `unmount_iso` (bool) - If true, remove the mounted ISO from the template
|
||||
after finishing. Defaults to `false`.
|
||||
|
||||
`onboot` (boolean) - Specifies whether a VM will be started during system
|
||||
bootup. Defaults to `false`.
|
||||
|
||||
- `qemu_agent` (boolean) - Disables QEMU Agent option for this VM. When enabled,
|
||||
then `qemu-guest-agent` must be installed on the guest. When disabled, then
|
||||
|
|
|
@ -82,6 +82,17 @@ necessary for this build to succeed and can be found further down the page.
|
|||
|
||||
<%= partial "partials/helper/communicator/WinRM-not-required" %>
|
||||
|
||||
### Export Configuration
|
||||
<%= partial "partials/builder/vsphere/common/ExportConfig" %>
|
||||
|
||||
### Optional:
|
||||
|
||||
<%= partial "partials/builder/vsphere/common/ExportConfig-not-required" %>
|
||||
|
||||
#### Output Configuration:
|
||||
|
||||
<%= partial "partials/builder/vsphere/common/OutputConfig-not-required" %>
|
||||
|
||||
## Working with Clusters
|
||||
#### Standalone Hosts
|
||||
Only use the `host` option. Optionally specify a `resource_pool`:
|
||||
|
|
|
@ -106,6 +106,17 @@ from the datastore. Example:
|
|||
### Floppy Configuration
|
||||
<%= partial "partials/builder/vsphere/iso/FloppyConfig-not-required" %>
|
||||
|
||||
### Export Configuration
|
||||
<%= partial "partials/builder/vsphere/common/ExportConfig" %>
|
||||
|
||||
### Optional:
|
||||
|
||||
<%= partial "partials/builder/vsphere/common/ExportConfig-not-required" %>
|
||||
|
||||
#### Output Configuration:
|
||||
|
||||
<%= partial "partials/builder/vsphere/common/OutputConfig-not-required" %>
|
||||
|
||||
### Extra Configuration Parameters
|
||||
<%= partial "partials/builder/vsphere/common/ConfigParamsConfig-not-required" %>
|
||||
|
||||
|
|
|
@ -3,24 +3,24 @@
|
|||
# See https://www.netlify.com/docs/redirects/ for documentation. Please do not
|
||||
# modify or delete existing redirects without first verifying internally.
|
||||
|
||||
/docs/installation.html /docs/install/index.html
|
||||
/docs/command-line/machine-readable.html /docs/commands/index.html
|
||||
/docs/command-line/introduction.html /docs/commands/index.html
|
||||
/docs/templates/introduction.html /docs/templates/index.html
|
||||
/docs/builders/azure-setup.html /docs/builders/azure.html
|
||||
/docs/templates/veewee-to-packer.html /guides/veewee-to-packer.html
|
||||
/docs/extend/developing-plugins.html /docs/extending/plugins.html
|
||||
/docs/extending/developing-plugins.html /docs/extending/plugins.html
|
||||
/docs/extend/builder.html /docs/extending/custom-builders.html
|
||||
/docs/getting-started/setup.html /docs/getting-started/install.html
|
||||
/docs/other/community.html /community-tools.html
|
||||
/downloads-community.html /community-tools.html
|
||||
/community /community.html
|
||||
/community/index.html /community.html
|
||||
/docs/other/environmental-variables.html /docs/other/environment-variables.html
|
||||
/docs/platforms.html /docs/builders/index.html
|
||||
/intro/platforms.html /docs/builders/index.html
|
||||
/docs/templates/configuration-templates.html /docs/templates/engine.html
|
||||
/docs/machine-readable/* /docs/commands/index.html
|
||||
/docs/command-line/* /docs/commands/:splat
|
||||
/docs/extend/* /docs/extending/:splat
|
||||
/docs/installation.html /docs/install/index.html 301!
|
||||
/docs/command-line/machine-readable.html /docs/commands/index.html 301!
|
||||
/docs/command-line/introduction.html /docs/commands/index.html 301!
|
||||
/docs/templates/introduction.html /docs/templates/index.html 301!
|
||||
/docs/builders/azure-setup.html /docs/builders/azure.html 301!
|
||||
/docs/templates/veewee-to-packer.html /guides/veewee-to-packer.html 301!
|
||||
/docs/extend/developing-plugins.html /docs/extending/plugins.html 301!
|
||||
/docs/extending/developing-plugins.html /docs/extending/plugins.html 301!
|
||||
/docs/extend/builder.html /docs/extending/custom-builders.html 301!
|
||||
/docs/getting-started/setup.html /docs/getting-started/install.html 301!
|
||||
/docs/other/community.html /community-tools.html 301!
|
||||
/downloads-community.html /community-tools.html 301!
|
||||
/community /community.html 301!
|
||||
/community/index.html /community.html 301!
|
||||
/docs/other/environmental-variables.html /docs/other/environment-variables.html 301!
|
||||
/docs/platforms.html /docs/builders/index.html 301!
|
||||
/intro/platforms.html /docs/builders/index.html 301!
|
||||
/docs/templates/configuration-templates.html /docs/templates/engine.html 301!
|
||||
/docs/machine-readable/* /docs/commands/index.html 301!
|
||||
/docs/command-line/* /docs/commands/:splat 301!
|
||||
/docs/extend/* /docs/extending/:splat 301!
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
usually a project (also called the "tenant") with whom the image is
|
||||
shared.
|
||||
|
||||
- `image_auto_accept_members` (bool) - When true, perform the image accept so the members can see the image in their
|
||||
project. This requires a user with priveleges both in the build project and
|
||||
in the members provided. Defaults to false.
|
||||
|
||||
- `image_disk_format` (string) - Disk format of the resulting image. This option works if
|
||||
use_blockstorage_volume is true.
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<!-- Code generated from the comments of the DriverConfig struct in builder/vmware/common/driver_config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `cleanup_remote_cache` (bool) - When set to true, Packer will cleanup the cache folder where the ISO file is stored during the build on the remote machine.
|
||||
By default, this is set to false.
|
||||
|
||||
- `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is
|
||||
/Applications/VMware Fusion.app but this setting allows you to
|
||||
customize this.
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
for linked clones. Defaults to `false`.
|
||||
|
||||
- `convert_to_template` (bool) - Convert VM to a template. Defaults to `false`.
|
||||
|
||||
|
||||
- `export` (\*common.ExportConfig) - Export
|
|
@ -0,0 +1,25 @@
|
|||
<!-- Code generated from the comments of the ExportConfig struct in builder/vsphere/common/step_export.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `name` (string) - name of the ovf. defaults to the name of the VM
|
||||
|
||||
- `force` (bool) - overwrite ovf if it exists
|
||||
|
||||
- `images` (bool) - include iso and img image files that are attached to the VM
|
||||
|
||||
- `manifest` (string) - generate manifest using sha1, sha256, sha512. Defaults to 'sha256'. Use 'none' for no manifest.
|
||||
|
||||
- `options` ([]string) - Advanced ovf export options. Options can include:
|
||||
* mac - MAC address is exported for all ethernet devices
|
||||
* uuid - UUID is exported for all virtual machines
|
||||
* extraconfig - all extra configuration options are exported for a virtual machine
|
||||
* nodevicesubtypes - resource subtypes for CD/DVD drives, floppy drives, and serial and parallel ports are not exported
|
||||
|
||||
For example, adding the following export config option would output the mac addresses for all Ethernet devices in the ovf file:
|
||||
|
||||
```json
|
||||
...
|
||||
"export": {
|
||||
"options": ["mac"]
|
||||
},
|
||||
```
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!-- Code generated from the comments of the ExportConfig struct in builder/vsphere/common/step_export.go; DO NOT EDIT MANUALLY -->
|
||||
You may optionally export an ovf from VSphere to the instance running Packer.
|
||||
|
||||
Example usage:
|
||||
|
||||
```json
|
||||
...
|
||||
"vm_name": "example-ubuntu",
|
||||
...
|
||||
"export": {
|
||||
"force": true,
|
||||
"output_directory": "./output_vsphere"
|
||||
},
|
||||
```
|
||||
The above configuration would create the following files:
|
||||
|
||||
```
|
||||
./output_vsphere/example-ubuntu-disk-0.vmdk
|
||||
./output_vsphere/example-ubuntu.mf
|
||||
./output_vsphere/example-ubuntu.ovf
|
||||
```
|
|
@ -0,0 +1,10 @@
|
|||
<!-- Code generated from the comments of the OutputConfig struct in builder/vsphere/common/output_config.go; DO NOT EDIT MANUALLY -->
|
||||
|
||||
- `output_directory` (string) - This setting specifies the directory that
|
||||
artifacts from the build, such as the virtual machine files and disks,
|
||||
will be output to. The path to the directory may be relative or
|
||||
absolute. If relative, the path is relative to the working directory
|
||||
packer is executed from. This directory must not exist or, if
|
||||
created, must be empty prior to running the builder. By default this is
|
||||
"output-BUILDNAME" where "BUILDNAME" is the name of the build.
|
||||
|
|
@ -4,4 +4,7 @@
|
|||
for linked clones. Defaults to `false`.
|
||||
|
||||
- `convert_to_template` (bool) - Convert VM to a template. Defaults to `false`.
|
||||
|
||||
- `export` (\*common.ExportConfig) - Configuration for exporting VM to an ovf file.
|
||||
The VM will not be exported if no [Export Configuration](#export-configuration) is specified.
|
||||
|
|
@ -12,10 +12,6 @@ go-getter supports the following protocols:
|
|||
* HTTP
|
||||
* Amazon S3
|
||||
|
||||
\~> On Windows, using a symlink to refer to local files is currently
|
||||
unsupported. Packer will always copy a local iso into the Packer cache
|
||||
directory.
|
||||
|
||||
Examples:
|
||||
go-getter can guess the checksum type based on `iso_checksum` len.
|
||||
|
||||
|
|
Loading…
Reference in New Issue