Merge remote-tracking branch 'upstream/master' into feature/datadisk-change_name
This commit is contained in:
commit
5340af4d49
.codecov.ymlmodules.txt
.github
CHANGELOG.mdMakefilebuilder
amazon/common
azure/arm
googlecompute
openstack
oracle/oci
proxmox
qemu
vmware
common
iso
vmx
vsphere
command
common
hcl2template
scripts
vendor
github.com
oracle/oci-go-sdk/common/auth
certificate_retriever.goconfiguration.gofederation_client.goinstance_principal_key_provider.gojwt.goutils.go
vmware/govmomi/ovf
version
website
|
@ -4,6 +4,7 @@ comment:
|
||||||
require_changes: true # only comment on changes in coverage
|
require_changes: true # only comment on changes in coverage
|
||||||
require_base: yes # [yes :: must have a base report to post]
|
require_base: yes # [yes :: must have a base report to post]
|
||||||
require_head: yes # [yes :: must have a head 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
|
branches: # branch names that can post comment
|
||||||
- "master"
|
- "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
|
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
|
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:
|
### IMPROVEMENTS:
|
||||||
* builder/azure: Add support for configurable KeyVault SKU [GH-8879]
|
* builder/azure: Add support for configurable KeyVault SKU [GH-8879]
|
||||||
* builder/hyperv: Add `first_boot_device` setting to allow the selection of the
|
* 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]
|
initial device or device class used for booting the VM. [GH-8714]
|
||||||
* builder/hyperv: Fix Hyper-V compacted disk size comparison [GH-8811]
|
* 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
|
* builder/tencentcloud: Show tencentcloud image id after copy to desination
|
||||||
region. [GH-8763]
|
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
|
* builder/vmware-iso: Do not perform dial test of NIC when ssh bastion is
|
||||||
required [GH-8877]
|
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 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.
|
* builder/vsphere-iso: Add support for eagerly zeroed / scrubbed disks.
|
||||||
[GH-8756]
|
[GH-8756]
|
||||||
* builder/vsphere-iso: Add the remote iso first so that it is first in boot
|
* builder/vsphere-iso: Add the remote iso first so that it is first in boot
|
||||||
order, and clarify boot behavior. [GH-8732]
|
order, and clarify boot behavior. [GH-8732]
|
||||||
* communicator/ssh: Add flag to enable support for keyboard-interactive auth to
|
* communicator/ssh: Add flag to enable support for keyboard-interactive auth to
|
||||||
connect bastion [GH-8847]
|
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
|
* core/hcl2: Add support in HCL2 configs for dynamic blocks, document for loops
|
||||||
and splat expressions [GH-8720]
|
and splat expressions [GH-8720]
|
||||||
* core/hcl2: Fix HCL2 local variables decoding to allow local usage within
|
* core/hcl2: Fix HCL2 local variables decoding to allow local usage within
|
||||||
|
@ -29,17 +42,28 @@
|
||||||
### Bug Fixes:
|
### Bug Fixes:
|
||||||
* bilder/proxmox: Bump proxmox-api-go to fix upstream bug where users hit open
|
* bilder/proxmox: Bump proxmox-api-go to fix upstream bug where users hit open
|
||||||
file limit. [GH-8800]
|
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
|
* builder/azure: Fix HCL2 bug that prevented Azure and other builders from
|
||||||
loading properly. [GH-8785]
|
loading properly. [GH-8785]
|
||||||
* builder/googlecompute: Fix WinRMPassword template engine. [GH-8890]
|
* 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
|
* builder/proxmox: Add new validation to catch that template_name cannot
|
||||||
contain spaces. [GH-8799]
|
contain spaces. [GH-8799]
|
||||||
* builder/vagrant: Fix path validation in ssh config step. [GH-8826]
|
* 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.
|
* builder/virtualbox: Remove all floppy controllers before adding a new one.
|
||||||
[GH-8828]
|
[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]
|
* builder/vsphere: Fix network object interface panic. [GH-8753]
|
||||||
* core/hcl2: Fix crash when an unset variable is used [GH-8837]
|
* 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: 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
|
* core: Fix "build" template engine interpolation for certain fields in certain
|
||||||
provisioners. [GH-8771]
|
provisioners. [GH-8771]
|
||||||
* core: Fix bug where user var recursion could fail intermittently when used
|
* 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]
|
can disable plugins by renaming the extension [GH-8735]
|
||||||
* provisioner/shell: "inline" config option is now a template engine. [GH-8883]
|
* provisioner/shell: "inline" config option is now a template engine. [GH-8883]
|
||||||
|
|
||||||
|
|
||||||
## 1.5.4 (February 14, 2020)
|
## 1.5.4 (February 14, 2020)
|
||||||
no-change release to fix code-signing on OSX binaries. Since checksums for these
|
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.
|
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
|
ci-lint: install-lint-deps ## On ci only lint newly added Go source files
|
||||||
@echo "==> Running linter on 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
|
fmt: ## Format Go code
|
||||||
|
|
|
@ -49,24 +49,6 @@ type StepRunSpotInstance struct {
|
||||||
func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
|
||||||
state multistep.StateBag, marketOptions *ec2.LaunchTemplateInstanceMarketOptionsRequest) *ec2.RequestLaunchTemplateData {
|
state multistep.StateBag, marketOptions *ec2.LaunchTemplateInstanceMarketOptionsRequest) *ec2.RequestLaunchTemplateData {
|
||||||
blockDeviceMappings := s.LaunchMappings.BuildEC2BlockDeviceMappings()
|
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
|
// Convert the BlockDeviceMapping into a
|
||||||
// LaunchTemplateBlockDeviceMappingRequest. These structs are identical,
|
// LaunchTemplateBlockDeviceMappingRequest. These structs are identical,
|
||||||
// except for the EBS field -- on one, that field contains a
|
// 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{
|
launchRequest := &ec2.LaunchTemplateBlockDeviceMappingRequest{
|
||||||
DeviceName: mapping.DeviceName,
|
DeviceName: mapping.DeviceName,
|
||||||
Ebs: (*ec2.LaunchTemplateEbsBlockDeviceRequest)(mapping.Ebs),
|
Ebs: (*ec2.LaunchTemplateEbsBlockDeviceRequest)(mapping.Ebs),
|
||||||
NoDevice: mapping.NoDevice,
|
|
||||||
VirtualName: mapping.VirtualName,
|
VirtualName: mapping.VirtualName,
|
||||||
}
|
}
|
||||||
launchMappingRequests = append(launchMappingRequests, launchRequest)
|
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))
|
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.")
|
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) {
|
WinRMConfig: func(multistep.StateBag) (*communicator.WinRMConfig, error) {
|
||||||
return &communicator.WinRMConfig{
|
return &communicator.WinRMConfig{
|
||||||
Username: b.config.UserName,
|
Username: b.config.UserName,
|
||||||
Password: b.config.Comm.WinRMPassword,
|
Password: b.config.Password,
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/packer/common/random"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
"github.com/masterzen/winrm"
|
"github.com/masterzen/winrm"
|
||||||
|
@ -529,7 +531,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
|
||||||
provideDefaultValues(c)
|
provideDefaultValues(c)
|
||||||
setRuntimeValues(c)
|
setRuntimeValues(c)
|
||||||
setUserNamePassword(c)
|
err = setUserNamePassword(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// copy singular blocks
|
// copy singular blocks
|
||||||
for _, kv := range c.AzureTag {
|
for _, kv := range c.AzureTag {
|
||||||
|
@ -653,23 +658,34 @@ func setRuntimeValues(c *Config) {
|
||||||
c.tmpKeyVaultName = tempName.KeyVaultName
|
c.tmpKeyVaultName = tempName.KeyVaultName
|
||||||
}
|
}
|
||||||
|
|
||||||
func setUserNamePassword(c *Config) {
|
func setUserNamePassword(c *Config) error {
|
||||||
|
// SSH comm
|
||||||
if c.Comm.SSHUsername == "" {
|
if c.Comm.SSHUsername == "" {
|
||||||
c.Comm.SSHUsername = DefaultUserName
|
c.Comm.SSHUsername = DefaultUserName
|
||||||
}
|
}
|
||||||
|
|
||||||
c.UserName = c.Comm.SSHUsername
|
c.UserName = c.Comm.SSHUsername
|
||||||
|
|
||||||
if c.Comm.SSHPassword != "" {
|
if c.Comm.SSHPassword != "" {
|
||||||
c.Password = c.Comm.SSHPassword
|
c.Password = c.Comm.SSHPassword
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure password settings using Azure generated credentials
|
// WinRM comm
|
||||||
c.Password = c.tmpAdminPassword
|
if c.Comm.WinRMUser == "" {
|
||||||
if c.Comm.WinRMPassword == "" {
|
c.Comm.WinRMUser = DefaultUserName
|
||||||
c.Comm.WinRMPassword = c.Password
|
|
||||||
}
|
}
|
||||||
|
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 {
|
func setCustomData(c *Config) error {
|
||||||
|
@ -1039,6 +1055,34 @@ func isValidAzureName(re *regexp.Regexp, rgn string) bool {
|
||||||
!strings.HasSuffix(rgn, "-")
|
!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)) {
|
func (c *Config) validateLocationZoneResiliency(say func(s string)) {
|
||||||
// Docs on regions that support Availibility Zones:
|
// Docs on regions that support Availibility Zones:
|
||||||
// https://docs.microsoft.com/en-us/azure/availability-zones/az-overview#regions-that-support-availability-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)
|
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" {
|
if c.VMSize != "override_vm_size" {
|
||||||
t.Errorf("Expected 'vm_size' to be set to 'override_vm_size', but found %q!", c.VMSize)
|
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 {
|
if c.diskCachingType != compute.CachingTypesNone {
|
||||||
t.Errorf("Expected 'disk_caching_type' to be set to 'None', but found %q!", c.diskCachingType)
|
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) {
|
func TestConfigShouldDefaultVMSizeToStandardA1(t *testing.T) {
|
||||||
|
@ -574,7 +595,7 @@ func TestWinRMConfigShouldSetRoundTripDecorator(t *testing.T) {
|
||||||
config := getArmBuilderConfiguration()
|
config := getArmBuilderConfiguration()
|
||||||
config["communicator"] = "winrm"
|
config["communicator"] = "winrm"
|
||||||
config["winrm_username"] = "username"
|
config["winrm_username"] = "username"
|
||||||
config["winrm_password"] = "password"
|
config["winrm_password"] = "Password123"
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
_, err := c.Prepare(config, getPackerConfiguration())
|
_, 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 {
|
func getArmBuilderConfiguration() map[string]string {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
for _, v := range requiredConfigValues {
|
for _, v := range requiredConfigValues {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/packer/helper/multistep"
|
"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
|
// supplied public key. This is possible if a private_key_file was
|
||||||
// specified.
|
// specified.
|
||||||
if sshPublicKey != "" {
|
if sshPublicKey != "" {
|
||||||
sshMetaKey := "sshKeys"
|
sshMetaKey := "ssh-keys"
|
||||||
sshKeys := fmt.Sprintf("%s:%s", c.Comm.SSHUsername, sshPublicKey)
|
sshPublicKey = strings.TrimSuffix(sshPublicKey, "\n")
|
||||||
|
sshKeys := fmt.Sprintf("%s:%s %s", c.Comm.SSHUsername, sshPublicKey, c.Comm.SSHUsername)
|
||||||
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
|
if confSshKeys, exists := instanceMetadata[sshMetaKey]; exists {
|
||||||
sshKeys = fmt.Sprintf("%s\n%s", sshKeys, confSshKeys)
|
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.")
|
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||||
|
|
||||||
// ensure our key is listed
|
// 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) {
|
func TestCreateInstanceMetadata_noPublicKey(t *testing.T) {
|
||||||
state := testState(t)
|
state := testState(t)
|
||||||
c := state.Get("config").(*Config)
|
c := state.Get("config").(*Config)
|
||||||
image := StubImage("test-image", "test-project", []string{}, 100)
|
image := StubImage("test-image", "test-project", []string{}, 100)
|
||||||
sshKeys := c.Metadata["sshKeys"]
|
sshKeys := c.Metadata["ssh-keys"]
|
||||||
|
|
||||||
// create our metadata
|
// create our metadata
|
||||||
metadata, err := c.createInstanceMetadata(image, "")
|
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.")
|
assert.True(t, err == nil, "Metadata creation should have succeeded.")
|
||||||
|
|
||||||
// ensure the ssh metadata hasn't changed
|
// 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) {
|
func TestCreateInstanceMetadata_metadataFile(t *testing.T) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ type FlatConfig struct {
|
||||||
ImageMetadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata"`
|
ImageMetadata map[string]string `mapstructure:"metadata" required:"false" cty:"metadata"`
|
||||||
ImageVisibility *images.ImageVisibility `mapstructure:"image_visibility" required:"false" cty:"image_visibility"`
|
ImageVisibility *images.ImageVisibility `mapstructure:"image_visibility" required:"false" cty:"image_visibility"`
|
||||||
ImageMembers []string `mapstructure:"image_members" required:"false" cty:"image_members"`
|
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"`
|
ImageDiskFormat *string `mapstructure:"image_disk_format" required:"false" cty:"image_disk_format"`
|
||||||
ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags"`
|
ImageTags []string `mapstructure:"image_tags" required:"false" cty:"image_tags"`
|
||||||
ImageMinDisk *int `mapstructure:"image_min_disk" required:"false" cty:"image_min_disk"`
|
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},
|
"metadata": &hcldec.BlockAttrsSpec{TypeName: "metadata", ElementType: cty.String, Required: false},
|
||||||
"image_visibility": &hcldec.AttrSpec{Name: "image_visibility", Type: 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_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_disk_format": &hcldec.AttrSpec{Name: "image_disk_format", Type: cty.String, Required: false},
|
||||||
"image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.List(cty.String), Required: false},
|
"image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.List(cty.String), Required: false},
|
||||||
"image_min_disk": &hcldec.AttrSpec{Name: "image_min_disk", Type: cty.Number, Required: false},
|
"image_min_disk": &hcldec.AttrSpec{Name: "image_min_disk", Type: cty.Number, Required: false},
|
||||||
|
|
|
@ -22,6 +22,10 @@ type ImageConfig struct {
|
||||||
// usually a project (also called the "tenant") with whom the image is
|
// usually a project (also called the "tenant") with whom the image is
|
||||||
// shared.
|
// shared.
|
||||||
ImageMembers []string `mapstructure:"image_members" required:"false"`
|
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
|
// Disk format of the resulting image. This option works if
|
||||||
// use_blockstorage_volume is true.
|
// use_blockstorage_volume is true.
|
||||||
ImageDiskFormat string `mapstructure:"image_disk_format" required:"false"`
|
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
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/hashicorp/packer/packer"
|
"github.com/hashicorp/packer/packer"
|
||||||
"github.com/hashicorp/packer/template/interpolate"
|
"github.com/hashicorp/packer/template/interpolate"
|
||||||
ocicommon "github.com/oracle/oci-go-sdk/common"
|
ocicommon "github.com/oracle/oci-go-sdk/common"
|
||||||
|
ociauth "github.com/oracle/oci-go-sdk/common/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -27,6 +28,18 @@ type Config struct {
|
||||||
|
|
||||||
configProvider ocicommon.ConfigurationProvider
|
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"`
|
AccessCfgFile string `mapstructure:"access_cfg_file"`
|
||||||
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
|
AccessCfgFileAccount string `mapstructure:"access_cfg_file_account"`
|
||||||
|
|
||||||
|
@ -86,6 +99,60 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||||
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
return fmt.Errorf("Failed to mapstructure Config: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errs *packer.MultiError
|
||||||
|
if es := c.Comm.Prepare(&c.ctx); len(es) > 0 {
|
||||||
|
errs = packer.MultiErrorAppend(errs, es...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenancyOCID string
|
||||||
|
|
||||||
|
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
|
// Determine where the SDK config is located
|
||||||
if c.AccessCfgFile == "" {
|
if c.AccessCfgFile == "" {
|
||||||
c.AccessCfgFile, err = getDefaultOCISettingsPath()
|
c.AccessCfgFile, err = getDefaultOCISettingsPath()
|
||||||
|
@ -137,17 +204,12 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||||
return err
|
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 == "" {
|
if userOCID, _ := configProvider.UserOCID(); userOCID == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("'user_ocid' must be specified"))
|
errs, errors.New("'user_ocid' must be specified"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tenancyOCID, _ := configProvider.TenancyOCID()
|
tenancyOCID, _ = configProvider.TenancyOCID()
|
||||||
if tenancyOCID == "" {
|
if tenancyOCID == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("'tenancy_ocid' must be specified"))
|
errs, errors.New("'tenancy_ocid' must be specified"))
|
||||||
|
@ -164,6 +226,7 @@ func (c *Config) Prepare(raws ...interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.configProvider = configProvider
|
c.configProvider = configProvider
|
||||||
|
}
|
||||||
|
|
||||||
if c.AvailabilityDomain == "" {
|
if c.AvailabilityDomain == "" {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
|
|
|
@ -57,6 +57,7 @@ type FlatConfig struct {
|
||||||
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
WinRMUseSSL *bool `mapstructure:"winrm_use_ssl" cty:"winrm_use_ssl"`
|
||||||
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure"`
|
||||||
WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm"`
|
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"`
|
AccessCfgFile *string `mapstructure:"access_cfg_file" cty:"access_cfg_file"`
|
||||||
AccessCfgFileAccount *string `mapstructure:"access_cfg_file_account" cty:"access_cfg_file_account"`
|
AccessCfgFileAccount *string `mapstructure:"access_cfg_file_account" cty:"access_cfg_file_account"`
|
||||||
UserID *string `mapstructure:"user_ocid" cty:"user_ocid"`
|
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_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_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},
|
"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": &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},
|
"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},
|
"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{} {
|
func testConfig(accessConfFile *os.File) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
|
|
||||||
"availability_domain": "aaaa:PHX-AD-3",
|
"availability_domain": "aaaa:PHX-AD-3",
|
||||||
"access_cfg_file": accessConfFile.Name(),
|
"access_cfg_file": accessConfFile.Name(),
|
||||||
|
|
||||||
|
@ -252,6 +253,36 @@ func TestConfig(t *testing.T) {
|
||||||
t.Errorf("Expected ConfigProvider.KeyFingerprint: %s, got %s", expected, fingerprint)
|
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
|
// 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"`
|
ISOStoragePool string `mapstructure:"iso_storage_pool"`
|
||||||
Agent bool `mapstructure:"qemu_agent"`
|
Agent bool `mapstructure:"qemu_agent"`
|
||||||
SCSIController string `mapstructure:"scsi_controller"`
|
SCSIController string `mapstructure:"scsi_controller"`
|
||||||
|
Onboot bool `mapstructure:"onboot"`
|
||||||
|
|
||||||
TemplateName string `mapstructure:"template_name"`
|
TemplateName string `mapstructure:"template_name"`
|
||||||
TemplateDescription string `mapstructure:"template_description"`
|
TemplateDescription string `mapstructure:"template_description"`
|
||||||
|
|
|
@ -91,6 +91,7 @@ type FlatConfig struct {
|
||||||
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool"`
|
ISOStoragePool *string `mapstructure:"iso_storage_pool" cty:"iso_storage_pool"`
|
||||||
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent"`
|
Agent *bool `mapstructure:"qemu_agent" cty:"qemu_agent"`
|
||||||
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller"`
|
SCSIController *string `mapstructure:"scsi_controller" cty:"scsi_controller"`
|
||||||
|
Onboot *bool `mapstructure:"onboot" cty:"onboot"`
|
||||||
TemplateName *string `mapstructure:"template_name" cty:"template_name"`
|
TemplateName *string `mapstructure:"template_name" cty:"template_name"`
|
||||||
TemplateDescription *string `mapstructure:"template_description" cty:"template_description"`
|
TemplateDescription *string `mapstructure:"template_description" cty:"template_description"`
|
||||||
UnmountISO *bool `mapstructure:"unmount_iso" cty:"unmount_iso"`
|
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},
|
"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},
|
"qemu_agent": &hcldec.AttrSpec{Name: "qemu_agent", Type: cty.Bool, Required: false},
|
||||||
"scsi_controller": &hcldec.AttrSpec{Name: "scsi_controller", Type: cty.String, 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_name": &hcldec.AttrSpec{Name: "template_name", Type: cty.String, Required: false},
|
||||||
"template_description": &hcldec.AttrSpec{Name: "template_description", 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},
|
"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),
|
QemuNetworks: generateProxmoxNetworkAdapters(c.NICs),
|
||||||
QemuDisks: generateProxmoxDisks(c.Disks),
|
QemuDisks: generateProxmoxDisks(c.Disks),
|
||||||
Scsihw: c.SCSIController,
|
Scsihw: c.SCSIController,
|
||||||
|
Onboot: c.Onboot,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.VMID == 0 {
|
if c.VMID == 0 {
|
||||||
|
|
|
@ -39,31 +39,6 @@ var accels = map[string]struct{}{
|
||||||
"whpx": {},
|
"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{
|
var diskInterface = map[string]bool{
|
||||||
"ide": true,
|
"ide": true,
|
||||||
"scsi": 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"))
|
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 {
|
if _, ok := diskInterface[b.config.DiskInterface]; !ok {
|
||||||
errs = packer.MultiErrorAppend(
|
errs = packer.MultiErrorAppend(
|
||||||
errs, errors.New("unrecognized disk interface type"))
|
errs, errors.New("unrecognized disk interface type"))
|
||||||
|
|
|
@ -16,6 +16,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DriverConfig struct {
|
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
|
// Path to "VMware Fusion.app". By default this is
|
||||||
// /Applications/VMware Fusion.app but this setting allows you to
|
// /Applications/VMware Fusion.app but this setting allows you to
|
||||||
// customize this.
|
// customize this.
|
||||||
|
|
|
@ -26,6 +26,9 @@ type RemoteDriverMock struct {
|
||||||
UploadErr error
|
UploadErr error
|
||||||
DownloadErr error
|
DownloadErr error
|
||||||
|
|
||||||
|
RemovedCachePath string
|
||||||
|
CacheRemoved bool
|
||||||
|
|
||||||
ReloadVMErr error
|
ReloadVMErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +69,8 @@ func (d *RemoteDriverMock) Download(src, dst string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RemoteDriverMock) RemoveCache(localPath string) error {
|
func (d *RemoteDriverMock) RemoveCache(localPath string) error {
|
||||||
|
d.RemovedCachePath = localPath
|
||||||
|
d.CacheRemoved = true
|
||||||
return nil
|
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{
|
&vmwcommon.StepRemoteUpload{
|
||||||
Key: "iso_path",
|
Key: "iso_path",
|
||||||
Message: "Uploading ISO to remote machine...",
|
Message: "Uploading ISO to remote machine...",
|
||||||
|
DoCleanup: b.config.DriverConfig.CleanUpRemoteCache,
|
||||||
Checksum: b.config.ISOChecksum,
|
Checksum: b.config.ISOChecksum,
|
||||||
ChecksumType: b.config.ISOChecksumType,
|
ChecksumType: b.config.ISOChecksumType,
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,6 +34,7 @@ type FlatConfig struct {
|
||||||
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
||||||
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc"`
|
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc"`
|
||||||
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval"`
|
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"`
|
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path"`
|
||||||
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type"`
|
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type"`
|
||||||
RemoteDatastore *string `mapstructure:"remote_datastore" required:"false" cty:"remote_datastore"`
|
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},
|
"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},
|
"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},
|
"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},
|
"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_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
|
||||||
"remote_datastore": &hcldec.AttrSpec{Name: "remote_datastore", 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"`
|
BootCommand []string `mapstructure:"boot_command" cty:"boot_command"`
|
||||||
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc"`
|
DisableVNC *bool `mapstructure:"disable_vnc" cty:"disable_vnc"`
|
||||||
BootKeyInterval *string `mapstructure:"boot_key_interval" cty:"boot_key_interval"`
|
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"`
|
FusionAppPath *string `mapstructure:"fusion_app_path" required:"false" cty:"fusion_app_path"`
|
||||||
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type"`
|
RemoteType *string `mapstructure:"remote_type" required:"false" cty:"remote_type"`
|
||||||
RemoteDatastore *string `mapstructure:"remote_datastore" required:"false" cty:"remote_datastore"`
|
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},
|
"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},
|
"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},
|
"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},
|
"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_type": &hcldec.AttrSpec{Name: "remote_type", Type: cty.String, Required: false},
|
||||||
"remote_datastore": &hcldec.AttrSpec{Name: "remote_datastore", 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 = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner.Run(ctx, state)
|
b.runner.Run(ctx, state)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ type Config struct {
|
||||||
// Convert VM to a template. Defaults to `false`.
|
// Convert VM to a template. Defaults to `false`.
|
||||||
ConvertToTemplate bool `mapstructure:"convert_to_template"`
|
ConvertToTemplate bool `mapstructure:"convert_to_template"`
|
||||||
|
|
||||||
|
Export *common.ExportConfig `mapstructure:"export"`
|
||||||
|
|
||||||
ctx interpolate.Context
|
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.WaitIpConfig.Prepare()...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
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 {
|
if len(errs.Errors) > 0 {
|
||||||
return nil, errs
|
return nil, errs
|
||||||
|
|
|
@ -3,6 +3,7 @@ package clone
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,6 +93,7 @@ type FlatConfig struct {
|
||||||
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
||||||
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
||||||
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
||||||
|
Export *common.FlatExportConfig `mapstructure:"export" cty:"export"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
// 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},
|
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
|
||||||
"create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, 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},
|
"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
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,17 +49,18 @@ type StepCloneVM struct {
|
||||||
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCloneVM) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
d := state.Get("driver").(*driver.Driver)
|
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 {
|
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
|
return multistep.ActionHalt
|
||||||
} else if s.Force == true && err == nil {
|
} 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()
|
err := vm.Destroy()
|
||||||
if err != nil {
|
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"
|
"strings"
|
||||||
"time"
|
"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/object"
|
||||||
"github.com/vmware/govmomi/vim25/mo"
|
"github.com/vmware/govmomi/vim25/mo"
|
||||||
"github.com/vmware/govmomi/vim25/types"
|
"github.com/vmware/govmomi/vim25/types"
|
||||||
|
@ -688,6 +693,27 @@ func (vm *VirtualMachine) AddConfigParams(params map[string]string) error {
|
||||||
return err
|
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) {
|
func findNetworkAdapter(l object.VirtualDeviceList) (types.BaseVirtualEthernetCard, error) {
|
||||||
c := l.SelectByType((*types.VirtualEthernetCard)(nil))
|
c := l.SelectByType((*types.VirtualEthernetCard)(nil))
|
||||||
if len(c) == 0 {
|
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 = packerCommon.NewRunner(steps, b.config.PackerConfig, ui)
|
||||||
b.runner.Run(ctx, state)
|
b.runner.Run(ctx, state)
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ type Config struct {
|
||||||
CreateSnapshot bool `mapstructure:"create_snapshot"`
|
CreateSnapshot bool `mapstructure:"create_snapshot"`
|
||||||
// Convert VM to a template. Defaults to `false`.
|
// Convert VM to a template. Defaults to `false`.
|
||||||
ConvertToTemplate bool `mapstructure:"convert_to_template"`
|
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
|
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.WaitIpConfig.Prepare()...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...)
|
||||||
errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)
|
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 {
|
if len(errs.Errors) > 0 {
|
||||||
return warnings, errs
|
return warnings, errs
|
||||||
|
|
|
@ -3,6 +3,7 @@ package iso
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
"github.com/hashicorp/packer/builder/vsphere/common"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -119,6 +120,7 @@ type FlatConfig struct {
|
||||||
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
Timeout *string `mapstructure:"shutdown_timeout" cty:"shutdown_timeout"`
|
||||||
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
CreateSnapshot *bool `mapstructure:"create_snapshot" cty:"create_snapshot"`
|
||||||
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
ConvertToTemplate *bool `mapstructure:"convert_to_template" cty:"convert_to_template"`
|
||||||
|
Export *common.FlatExportConfig `mapstructure:"export" cty:"export"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlatMapstructure returns a new FlatConfig.
|
// 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},
|
"shutdown_timeout": &hcldec.AttrSpec{Name: "shutdown_timeout", Type: cty.String, Required: false},
|
||||||
"create_snapshot": &hcldec.AttrSpec{Name: "create_snapshot", Type: cty.Bool, 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},
|
"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
|
return s
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,8 +70,14 @@ type CreateConfig struct {
|
||||||
func (c *CreateConfig) Prepare() []error {
|
func (c *CreateConfig) Prepare() []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
if c.DiskSize == 0 {
|
if len(c.Storage) > 0 {
|
||||||
errs = append(errs, fmt.Errorf("'disk_size' is required"))
|
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 == "" {
|
if c.GuestOSType == "" {
|
||||||
|
@ -94,17 +100,18 @@ type StepCreateVM struct {
|
||||||
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
func (s *StepCreateVM) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
d := state.Get("driver").(*driver.Driver)
|
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 {
|
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
|
return multistep.ActionHalt
|
||||||
} else if s.Force == true && err == nil {
|
} 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()
|
err := vm.Destroy()
|
||||||
if err != nil {
|
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"
|
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) {
|
func TestBuildOnlyFileCommaFlags(t *testing.T) {
|
||||||
c := &BuildCommand{
|
c := &BuildCommand{
|
||||||
Meta: testMetaFile(t),
|
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
|
// fileExists returns true if the filename is found
|
||||||
func fileExists(filename string) bool {
|
func fileExists(filename string) bool {
|
||||||
if _, err := os.Stat(filename); err == nil {
|
if _, err := os.Stat(filename); err == nil {
|
||||||
|
@ -272,6 +410,7 @@ func cleanup(moreFiles ...string) {
|
||||||
os.RemoveAll("lilas.txt")
|
os.RemoveAll("lilas.txt")
|
||||||
os.RemoveAll("campanules.txt")
|
os.RemoveAll("campanules.txt")
|
||||||
os.RemoveAll("ducky.txt")
|
os.RemoveAll("ducky.txt")
|
||||||
|
os.RemoveAll("banana.txt")
|
||||||
for _, file := range moreFiles {
|
for _, file := range moreFiles {
|
||||||
os.RemoveAll(file)
|
os.RemoveAll(file)
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,15 +44,24 @@ func (m *Meta) Core(tpl *template.Template) (*packer.Core, error) {
|
||||||
config.Template = tpl
|
config.Template = tpl
|
||||||
|
|
||||||
fj := &kvflag.FlagJSON{}
|
fj := &kvflag.FlagJSON{}
|
||||||
|
// First populate fj with contents from var files
|
||||||
for _, file := range m.varFiles {
|
for _, file := range m.varFiles {
|
||||||
err := fj.Set(file)
|
err := fj.Set(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
for k, v := range *fj {
|
||||||
|
if _, exists := m.flagVars[k]; !exists {
|
||||||
m.flagVars[k] = v
|
m.flagVars[k] = v
|
||||||
}
|
}
|
||||||
|
}
|
||||||
config.Variables = m.flagVars
|
config.Variables = m.flagVars
|
||||||
|
|
||||||
// Init the core
|
// Init the core
|
||||||
|
|
|
@ -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
|
// * HTTP
|
||||||
// * Amazon S3
|
// * 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:
|
// Examples:
|
||||||
// go-getter can guess the checksum type based on `iso_checksum` len.
|
// go-getter can guess the checksum type based on `iso_checksum` len.
|
||||||
//
|
//
|
||||||
|
|
|
@ -10,6 +10,7 @@ var (
|
||||||
PossibleNumbers = "0123456789"
|
PossibleNumbers = "0123456789"
|
||||||
PossibleLowerCase = "abcdefghijklmnopqrstuvwxyz"
|
PossibleLowerCase = "abcdefghijklmnopqrstuvwxyz"
|
||||||
PossibleUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
PossibleUpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
PossibleSpecialCharacter = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||||
|
|
||||||
PossibleAlphaNum = PossibleNumbers + PossibleLowerCase + PossibleUpperCase
|
PossibleAlphaNum = PossibleNumbers + PossibleLowerCase + PossibleUpperCase
|
||||||
PossibleAlphaNumLower = PossibleNumbers + PossibleLowerCase
|
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
|
// Decode variable blocks so that they are available later on. Here locals
|
||||||
// can use input variables so we decode them firsthand.
|
// can use input variables so we decode them firsthand.
|
||||||
|
var locals []*Local
|
||||||
{
|
{
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
diags = append(diags, cfg.decodeInputVariables(file)...)
|
diags = append(diags, cfg.decodeInputVariables(file)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var locals []*Local
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
moreLocals, morediags := cfg.parseLocalVariables(file)
|
moreLocals, morediags := cfg.parseLocalVariables(file)
|
||||||
diags = append(diags, morediags...)
|
diags = append(diags, morediags...)
|
||||||
locals = append(locals, moreLocals...)
|
locals = append(locals, moreLocals...)
|
||||||
}
|
}
|
||||||
diags = append(diags, cfg.evaluateLocalVariables(locals)...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse var files
|
// parse var files
|
||||||
|
@ -130,11 +129,17 @@ func (p *Parser) parse(filename string, varFiles []string, argVars map[string]st
|
||||||
for _, filename := range hclVarFiles {
|
for _, filename := range hclVarFiles {
|
||||||
f, moreDiags := p.ParseHCLFile(filename)
|
f, moreDiags := p.ParseHCLFile(filename)
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
|
if moreDiags.HasErrors() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
varFiles = append(varFiles, f)
|
varFiles = append(varFiles, f)
|
||||||
}
|
}
|
||||||
for _, filename := range jsonVarFiles {
|
for _, filename := range jsonVarFiles {
|
||||||
f, moreDiags := p.ParseJSONFile(filename)
|
f, moreDiags := p.ParseJSONFile(filename)
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
|
if moreDiags.HasErrors() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
varFiles = append(varFiles, f)
|
varFiles = append(varFiles, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,6 +150,7 @@ func (p *Parser) parse(filename string, varFiles []string, argVars map[string]st
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
_, moreDiags = cfg.LocalVariables.Values()
|
_, moreDiags = cfg.LocalVariables.Values()
|
||||||
diags = append(diags, moreDiags...)
|
diags = append(diags, moreDiags...)
|
||||||
|
diags = append(diags, cfg.evaluateLocalVariables(locals)...)
|
||||||
|
|
||||||
// decode the actual content
|
// decode the actual content
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
|
|
|
@ -30,7 +30,7 @@ fi
|
||||||
|
|
||||||
get_prs(){
|
get_prs(){
|
||||||
# git log v0.10.2...c3861d167533fb797b0fae0c380806625712e5f7 |
|
# 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
|
grep -o "#\([0-9]\+\)" | awk -F\# '{print $2}' | while read line
|
||||||
do
|
do
|
||||||
grep -q "GH-${line}" CHANGELOG.md
|
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/olekukonko/tablewriter
|
||||||
# github.com/oracle/oci-go-sdk v1.8.0
|
# github.com/oracle/oci-go-sdk v1.8.0
|
||||||
github.com/oracle/oci-go-sdk/common
|
github.com/oracle/oci-go-sdk/common
|
||||||
|
github.com/oracle/oci-go-sdk/common/auth
|
||||||
github.com/oracle/oci-go-sdk/core
|
github.com/oracle/oci-go-sdk/core
|
||||||
# github.com/outscale/osc-go v0.0.1
|
# github.com/outscale/osc-go v0.0.1
|
||||||
github.com/outscale/osc-go/oapi
|
github.com/outscale/osc-go/oapi
|
||||||
|
@ -572,6 +573,7 @@ github.com/vmware/govmomi/find
|
||||||
github.com/vmware/govmomi/list
|
github.com/vmware/govmomi/list
|
||||||
github.com/vmware/govmomi/nfc
|
github.com/vmware/govmomi/nfc
|
||||||
github.com/vmware/govmomi/object
|
github.com/vmware/govmomi/object
|
||||||
|
github.com/vmware/govmomi/ovf
|
||||||
github.com/vmware/govmomi/property
|
github.com/vmware/govmomi/property
|
||||||
github.com/vmware/govmomi/session
|
github.com/vmware/govmomi/session
|
||||||
github.com/vmware/govmomi/task
|
github.com/vmware/govmomi/task
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
var GitCommit string
|
var GitCommit string
|
||||||
|
|
||||||
// The main version number that is being run at the moment.
|
// 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)
|
// 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
|
// 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|
|
activate :hashicorp do |h|
|
||||||
h.name = "packer"
|
h.name = "packer"
|
||||||
h.version = "1.5.4"
|
h.version = "1.5.5"
|
||||||
h.github_slug = "hashicorp/packer"
|
h.github_slug = "hashicorp/packer"
|
||||||
h.website_root = "website"
|
h.website_root = "website"
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,6 +58,9 @@ contribution here!
|
||||||
* [jakobadam/packer-qemu-templates](https://github.com/jakobadam/packer-qemu-templates)
|
* [jakobadam/packer-qemu-templates](https://github.com/jakobadam/packer-qemu-templates)
|
||||||
\- QEMU templates for various operating systems
|
\- QEMU templates for various operating systems
|
||||||
|
|
||||||
|
* [lucidone/baseliner](https://git.sr.ht/~lucidone/baseliner) - Example of using
|
||||||
|
QEMU + Ansible with Packer
|
||||||
|
|
||||||
## Wrappers
|
## Wrappers
|
||||||
|
|
||||||
- [packer-config](https://github.com/ianchesal/packer-config) - a Ruby model that lets you build Packer configurations in Ruby
|
- [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.
|
-> **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
|
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
|
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.
|
marked for deletion on termination will remain in your account.
|
||||||
|
|
||||||
## Build function template engine variables
|
## 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)
|
OCIDs](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/apisigningkey.htm)
|
||||||
([Oracle Cloud
|
([Oracle Cloud
|
||||||
IDs](https://docs.us-phoenix-1.oraclecloud.com/Content/General/Concepts/identifiers.htm)).
|
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
|
## Configuration Reference
|
||||||
|
|
||||||
|
@ -65,11 +68,6 @@ builder.
|
||||||
- `compartment_ocid` (string) - The OCID of the
|
- `compartment_ocid` (string) - The OCID of the
|
||||||
[compartment](https://docs.us-phoenix-1.oraclecloud.com/Content/GSG/Tasks/choosingcompartments.htm)
|
[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
|
- `shape` (string) - The template that determines the number of CPUs, amount
|
||||||
of memory, and other resources allocated to a newly created instance.
|
of memory, and other resources allocated to a newly created instance.
|
||||||
|
|
||||||
|
@ -90,41 +88,51 @@ builder.
|
||||||
|
|
||||||
### Optional
|
### 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
|
- `access_cfg_file` (string) - The path to the [OCI config
|
||||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm).
|
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`.
|
Defaults to `$HOME/.oci/config`.
|
||||||
|
|
||||||
- `access_cfg_file_account` (string) - The specific account in the [OCI
|
- `access_cfg_file_account` (string) - The specific account in the [OCI config
|
||||||
config
|
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm) to use.
|
||||||
file](https://docs.us-phoenix-1.oraclecloud.com/Content/API/Concepts/sdkconfig.htm)
|
This cannot be used along with the `use_instance_principals` key.
|
||||||
to use. Defaults to `DEFAULT`.
|
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.
|
- `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.
|
- `instance_name` (string) - The name to assign to the instance used for the image creation process.
|
||||||
Overrides value provided by the [OCI config
|
If not set a name of the form `instanceYYYYMMDDhhmmss` will be used.
|
||||||
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.
|
|
||||||
|
|
||||||
- `use_private_ip` (boolean) - Use private ip addresses to connect to the
|
- `use_private_ip` (boolean) - Use private ip addresses to connect to the
|
||||||
instance via ssh.
|
instance via ssh.
|
||||||
|
@ -191,3 +199,46 @@ substituted with the letter `a` and OCIDS have been shortened for brevity.
|
||||||
"type": "oracle-oci"
|
"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
|
The osc-bsusurrogate Packer builder is like the chroot builder, but does not
|
||||||
require running inside an Outscale virtual machine.
|
require running inside an Outscale virtual machine.
|
||||||
layout: docs
|
layout: docs
|
||||||
page_title: 'Outacale BSU Surrogate - Builders'
|
page_title: 'Outscale BSU Surrogate - Builders'
|
||||||
sidebar_current: 'docs-builders-osc-bsusurrogate'
|
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
|
The osc-bsuvolume Packer builder is like the BSU builder, but is intended to
|
||||||
create BSU volumes rather than a machine image.
|
create BSU volumes rather than a machine image.
|
||||||
layout: docs
|
layout: docs
|
||||||
page_title: 'Amazon BSU Volume - Builders'
|
page_title: 'Outscale BSU Volume - Builders'
|
||||||
sidebar_current: 'docs-builders-osc-bsuvolume'
|
sidebar_current: 'docs-builders-osc-bsuvolume'
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ sidebar_current: 'docs-builders-osc-bsuvolume'
|
||||||
|
|
||||||
Type: `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.
|
volumes which are prepopulated with filesystems or data.
|
||||||
|
|
||||||
This builder builds BSU volumes by launching an Outscale VM from a source OMI,
|
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
|
- `snapshot_users` (array of strings) - A list of account IDs that have
|
||||||
access to create volumes from the snapshot(s). By default no additional
|
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).
|
volumes from the backing snapshot(s).
|
||||||
|
|
||||||
- `source_omi_filter` (object) - Filters used to populate the `source_omi` field.
|
- `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 selects an Ubuntu 16.04 HVM BSU OMI from Canonical. NOTE:
|
||||||
This will fail unless *exactly* one OMIS is returned. In the above example,
|
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.
|
`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.
|
- `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
|
environmental variables. See the configuration reference in the section above
|
||||||
for more information on what environmental variables Packer will look for.
|
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).
|
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
|
## Accessing the Instance to Debug
|
||||||
|
@ -278,5 +278,5 @@ variables are available:
|
||||||
-> **Note:** Packer uses pre-built OMIs as the source for building images.
|
-> **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
|
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
|
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.
|
marked for deletion on termination will remain in your account.
|
||||||
|
|
|
@ -181,6 +181,9 @@ builder.
|
||||||
- `unmount_iso` (bool) - If true, remove the mounted ISO from the template
|
- `unmount_iso` (bool) - If true, remove the mounted ISO from the template
|
||||||
after finishing. Defaults to `false`.
|
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,
|
- `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
|
then `qemu-guest-agent` must be installed on the guest. When disabled, then
|
||||||
`ssh_host` should be used. Defaults to `true`.
|
`ssh_host` should be used. Defaults to `true`.
|
||||||
|
|
|
@ -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" %>
|
<%= 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
|
## Working with Clusters
|
||||||
#### Standalone Hosts
|
#### Standalone Hosts
|
||||||
Only use the `host` option. Optionally specify a `resource_pool`:
|
Only use the `host` option. Optionally specify a `resource_pool`:
|
||||||
|
|
|
@ -106,6 +106,17 @@ from the datastore. Example:
|
||||||
### Floppy Configuration
|
### Floppy Configuration
|
||||||
<%= partial "partials/builder/vsphere/iso/FloppyConfig-not-required" %>
|
<%= 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
|
### Extra Configuration Parameters
|
||||||
<%= partial "partials/builder/vsphere/common/ConfigParamsConfig-not-required" %>
|
<%= partial "partials/builder/vsphere/common/ConfigParamsConfig-not-required" %>
|
||||||
|
|
||||||
|
|
|
@ -3,24 +3,24 @@
|
||||||
# See https://www.netlify.com/docs/redirects/ for documentation. Please do not
|
# See https://www.netlify.com/docs/redirects/ for documentation. Please do not
|
||||||
# modify or delete existing redirects without first verifying internally.
|
# modify or delete existing redirects without first verifying internally.
|
||||||
|
|
||||||
/docs/installation.html /docs/install/index.html
|
/docs/installation.html /docs/install/index.html 301!
|
||||||
/docs/command-line/machine-readable.html /docs/commands/index.html
|
/docs/command-line/machine-readable.html /docs/commands/index.html 301!
|
||||||
/docs/command-line/introduction.html /docs/commands/index.html
|
/docs/command-line/introduction.html /docs/commands/index.html 301!
|
||||||
/docs/templates/introduction.html /docs/templates/index.html
|
/docs/templates/introduction.html /docs/templates/index.html 301!
|
||||||
/docs/builders/azure-setup.html /docs/builders/azure.html
|
/docs/builders/azure-setup.html /docs/builders/azure.html 301!
|
||||||
/docs/templates/veewee-to-packer.html /guides/veewee-to-packer.html
|
/docs/templates/veewee-to-packer.html /guides/veewee-to-packer.html 301!
|
||||||
/docs/extend/developing-plugins.html /docs/extending/plugins.html
|
/docs/extend/developing-plugins.html /docs/extending/plugins.html 301!
|
||||||
/docs/extending/developing-plugins.html /docs/extending/plugins.html
|
/docs/extending/developing-plugins.html /docs/extending/plugins.html 301!
|
||||||
/docs/extend/builder.html /docs/extending/custom-builders.html
|
/docs/extend/builder.html /docs/extending/custom-builders.html 301!
|
||||||
/docs/getting-started/setup.html /docs/getting-started/install.html
|
/docs/getting-started/setup.html /docs/getting-started/install.html 301!
|
||||||
/docs/other/community.html /community-tools.html
|
/docs/other/community.html /community-tools.html 301!
|
||||||
/downloads-community.html /community-tools.html
|
/downloads-community.html /community-tools.html 301!
|
||||||
/community /community.html
|
/community /community.html 301!
|
||||||
/community/index.html /community.html
|
/community/index.html /community.html 301!
|
||||||
/docs/other/environmental-variables.html /docs/other/environment-variables.html
|
/docs/other/environmental-variables.html /docs/other/environment-variables.html 301!
|
||||||
/docs/platforms.html /docs/builders/index.html
|
/docs/platforms.html /docs/builders/index.html 301!
|
||||||
/intro/platforms.html /docs/builders/index.html
|
/intro/platforms.html /docs/builders/index.html 301!
|
||||||
/docs/templates/configuration-templates.html /docs/templates/engine.html
|
/docs/templates/configuration-templates.html /docs/templates/engine.html 301!
|
||||||
/docs/machine-readable/* /docs/commands/index.html
|
/docs/machine-readable/* /docs/commands/index.html 301!
|
||||||
/docs/command-line/* /docs/commands/:splat
|
/docs/command-line/* /docs/commands/:splat 301!
|
||||||
/docs/extend/* /docs/extending/:splat
|
/docs/extend/* /docs/extending/:splat 301!
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
usually a project (also called the "tenant") with whom the image is
|
usually a project (also called the "tenant") with whom the image is
|
||||||
shared.
|
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
|
- `image_disk_format` (string) - Disk format of the resulting image. This option works if
|
||||||
use_blockstorage_volume is true.
|
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 -->
|
<!-- 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
|
- `fusion_app_path` (string) - Path to "VMware Fusion.app". By default this is
|
||||||
/Applications/VMware Fusion.app but this setting allows you to
|
/Applications/VMware Fusion.app but this setting allows you to
|
||||||
customize this.
|
customize this.
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
|
|
||||||
- `convert_to_template` (bool) - Convert VM to a template. 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.
|
||||||
|
|
|
@ -5,3 +5,6 @@
|
||||||
|
|
||||||
- `convert_to_template` (bool) - Convert VM to a template. 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
|
* HTTP
|
||||||
* Amazon S3
|
* 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:
|
Examples:
|
||||||
go-getter can guess the checksum type based on `iso_checksum` len.
|
go-getter can guess the checksum type based on `iso_checksum` len.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue