Compare commits

...

74 Commits

Author SHA1 Message Date
Megan Marsh 00da5f634d add json example of packer config 2021-03-15 15:59:01 -07:00
Megan Marsh c9410fa1a5 fix error messaging in wrappedmain. Stderr gets eaten by panicwrap, so we need to write to stdout, which then gets unpacked into error and output messages using the ErrorPrefix and OutputPrefix 2021-03-15 15:38:22 -07:00
Megan Marsh 60c961c021 Merge pull request #10457 from teddylear/feature/recursivefmt-2
Adding recursive flag to formatter to format subdirectories
2021-03-15 11:01:01 -07:00
Adrien Delorme f32b67c3bb Simplify error message when config file can't be "stat'd" (#10763)
* remove confusing message when a stat error happens while trying to list HCL2 files

* leave early if our first GetHCL2Files has errors
2021-03-15 14:07:07 +01:00
Adrien Delorme 160d932cce remove weir "Cannot tell wether " + path + " is a directory" error 2021-03-15 12:04:16 +01:00
Adrien Delorme e0fe579837 un-remove tests 2021-03-15 12:00:19 +01:00
Adrien Delorme fe21ff905d test that folders containing a - file won't hang forever 2021-03-15 11:51:21 +01:00
Adrien Delorme 8ee8420408 simplify return 2021-03-15 11:49:03 +01:00
Adrien Delorme 25ee6a19a6 flatten if a little 2021-03-15 11:48:13 +01:00
Adrien Delorme a6321ac137 remove debug line 2021-03-15 11:46:07 +01:00
Adrien Delorme 832c0f2b2a actually run fmt tests and then remove debug statements 2021-03-15 11:41:14 +01:00
Kyle 33cf6bf454 Fix logic for checking for KMS keys (#10754) 2021-03-15 11:37:13 +01:00
teddylear 22d373c279 Adding more debug logic 2021-03-15 11:36:10 +01:00
Dom Del Nano 77ce2b39d8 Fix the version parsing in ChecksumFileEntry.init() so that plugins whose name contain v's can packer init (#10760)
* fix
* test
2021-03-15 11:31:21 +01:00
Shane Lee 4bbeec4733 Update urls for the bootstrap scripts used by salt-masterless provider (#10755)
* Fix salt masterless url. Use saltproject.io

* Specify Tls12
2021-03-12 14:59:34 -05:00
Sylvia Moss 7ae42bbb21 Update PULL_REQUEST_TEMPLATE.md (#10758) 2021-03-12 17:10:11 +01:00
finchr d1254a5e48 Fix for issue #7413 - Allow non-zero exit codes for inspec provisioner (#10723) 2021-03-12 16:47:21 +01:00
Wilken Rivera cf65b7b494 Remove remote plugin docs for exoscale (#10757)
* Remove remote plugin docs for exoscale

* Add link to github repo for Exoscale components
2021-03-12 10:13:52 -05:00
Adrien Delorme be7d7313c5 add tests for piping fmt 2021-03-12 11:25:10 +01:00
Adrien Delorme 7d30a5d79d remove duplicate tests 2021-03-12 11:18:38 +01:00
Adrien Delorme a115b428ac simplify fmt test case a little 2021-03-12 11:07:26 +01:00
Adrien Delorme 4e22147909 Merge remote-tracking branch 'origin/master' into pr/teddylear/10457 2021-03-12 10:38:04 +01:00
Megan Marsh f1017a19d3 update changelog 2021-03-11 16:58:02 -08:00
Megan Marsh 3ced522659 Merge pull request #10750 from hashicorp/azr-plugin-real-world-examples
Give a list of working projects to checkout
2021-03-11 16:43:00 -08:00
Megan Marsh 6c8e997d19 Update website/content/docs/plugins/creation/index.mdx 2021-03-11 16:42:55 -08:00
Megan Marsh 44f0be2314 Merge pull request #10748 from hashicorp/fix_virtualbox_winrmhost
make Packer respect winrm_host flag in virtualbox connect func
2021-03-11 16:41:37 -08:00
Wilken Rivera bf9bb7b5c7 Merge pull request #10738 from twiggy/patch-1
Wrap more than one extra var in quotes.
2021-03-11 18:43:49 -05:00
Thomas Dreibholz 1a5678dd86 Added VirtualBox ISO builder option to create additional disks (#10674) 2021-03-11 16:09:30 +01:00
Adrien Delorme b2d692c33d Give a list of working projects to checkout 2021-03-11 11:48:23 +01:00
Megan Marsh e8b3a0e3bf make virtualbox, hyperv, openstack, and parallels builders respect winrm_host by correctly passing communicator config Host() func to commhost instead of just SSHHost 2021-03-10 15:36:19 -08:00
Megan Marsh 9944031580 Merge pull request #10743 from hashicorp/fix_10547
modify logging to make overrides clearer in face of vagrant always st…
2021-03-10 09:49:26 -08:00
Megan Marsh 35604a98a7 actually use the partials created in code generation inside the digitalocean docs page (#10742) 2021-03-10 13:24:00 +01:00
Megan Marsh 8bb3df7121 modify logging to make overrides clearer in face of vagrant always streaming to stdout. Add tests for config override. Make sure that user overrides of ssh_host and ssh_port are respected. 2021-03-09 16:40:49 -08:00
Megan Marsh d2ec658e78 Merge pull request #10724 from jkl73/image-project-update
Update public GCP image project as gce-uefi-images is be deprecated
2021-03-09 14:42:03 -08:00
Megan Marsh bb54b8bf10 Merge pull request #10709 from hashicorp/stubout-post-processor-exoscale-import
Shim "exoscale-import" post-processor
2021-03-09 13:14:39 -08:00
Wilken Rivera 1e27138857 plugins: Update Packer plugin documentation with details for remote plugin docs (#10718)
* Add instructions for registering remote plugin documentation

* Add documentation to plugins page about tiers and namespaces

* Update tiers

* Update website/content/docs/plugins/creation/index.mdx

Co-authored-by: Megan Marsh <megan@hashicorp.com>

* Add missing section for single-component install

Co-authored-by: Megan Marsh <megan@hashicorp.com>
2021-03-09 13:06:11 -05:00
Wilken Rivera 3c282de6c3 Add maps for statically vendored components
This change adds a new set of maps for builders, provisioners, and
post-processors that store reference to components that were once part
of Packer and are now vendored. This file acts as a single place for
defining this vendored components, which are then merged into the main
components maps to be used in Packer.

Quick test to ensure the exoscale-import post-processor is still loaded
```
// Validate that exoscale-import is in the know post-procoessors list
~>  packer.test build docker_centos_shell_provisioner.pkr.hcl
Error: Unknown post-processor type "badlynamed-import"

  on docker_centos_shell_provisioner.pkr.hcl line 18:
  (source code not available)

known post-processors: [ucloud-import digitalocean-import docker-push
googlecompute-export manifest vsphere-template docker-tag vsphere checksum
docker-import exoscale-import yandex-export compress googlecompute-import
yandex-import vagrant-cloud alicloud-import amazon-import artifice shell-local
docker-save vagrant]

// Validate that exoscale-import get loaded
~>  packer.test build docker_centos_shell_provisioner.pkr.hcl
Error: Failed preparing post-processor-block "exoscale-import" ""

  on docker_centos_shell_provisioner.pkr.hcl line 18:
  (source code not available)

4 error(s) occurred:

* api_key must be set
* api_secret must be set
* image_bucket must be set
* template_zone must be set

==> Wait completed after 2 microseconds

==> Builds finished but no artifacts were created.
```
2021-03-09 10:09:13 -05:00
Wilken Rivera beceace7b7 Move to remote plugin docs for exoscale 2021-03-09 10:04:04 -05:00
Wilken Rivera 6f23bc0d97 Vendor exoscale-import plugin
This change will vendor the new version of the exoscale-import
post-processor component, but remove all of its code from Packer. After
the v1.8.0 release this change should be removed entirely.

This vendor process is being used as a workaround for decoupling the
exoscale-import component without causing a breaking change in Packer.

Users of Exoscale are encouraged to leverage `packer init` for
installing the latest version of packer-plugin-exoscale.
2021-03-09 10:02:30 -05:00
Wilken Rivera edd4567096 Remove plugin registration generation step
This change removes the generation of command/plugin.go so that plugin
registration can be managed manually. As we begin to break out plugins
we will need to begin modifying this file, possibly with stubs to allow
for the removal of plugins without breaking Packer's backwards
  compatibility with 1.7.0. After 1.8.0 is released we should be able to
  revet these changes so that we can continue to generate the plugin
  file for those plugins core to Packer.
2021-03-09 10:02:02 -05:00
Marc Falzon 9a1f2d0c97 fixup! Remove "exoscale-import" post-processor 2021-03-09 10:01:38 -05:00
Marc Falzon 125a2f1f76 Remove "exoscale-import" post-processor
This change removes the `exoscale-import` post-processor from the
upstream Packer repository, following extraction as a standalone plugin
in a dedicated repository (https://github.com/exoscale/packer-post-processor-exoscale-import)
2021-03-09 10:01:05 -05:00
Wilken Rivera 3ac5046260 Merge pull request #10727 from tasha-marreiros/feature/gcp-windows-password-timeout-#10717
#10717 make googlecompute windows password timeout configurable
2021-03-09 09:40:51 -05:00
Tasha Marreiros 8c18f8fa6d amend email 2021-03-09 09:01:07 +00:00
Megan Marsh 0ea92f6369 Merge pull request #10736 from dreibh/dreibh/virtualbox-options-2
More options for VirtualBox builder: nested virtualisation and RTC time base
2021-03-08 14:48:17 -08:00
twiggy bf1046e970 Wrap more than one extra var in quotes.
When multiple extra vars are passed the list of vars must be wrapped in "s. At least with ansible-local it will not parse correctly leading to an error stating the second extra var is a playbook file that cannot be found.
2021-03-08 11:29:33 -06:00
Kris Fremen de50cadb5c docs: fix post-processor(checksum) hcl example. (#10734) 2021-03-08 13:03:39 +01:00
Thomas Dreibholz 4f2f9f8a1e Formatting fix. 2021-03-08 12:37:00 +01:00
Thomas Dreibholz 68f810891f Ran "make generate". 2021-03-08 12:29:00 +01:00
Thomas Dreibholz be8b2968bd Added options for VRAM size, 3D acceleration and EFI screen size. 2021-03-08 12:29:00 +01:00
Thomas Dreibholz 4c9f3eb9ca Added options for nested virtualisation and RTC time base. 2021-03-08 12:29:00 +01:00
Kennith Leung f4caf5978f doc: fix sources typo (#10733) 2021-03-08 10:34:05 +01:00
Megan Marsh cd93957225 Merge pull request #10695 from hashicorp/docker_vendoring
Initial docker extraction
2021-03-05 16:26:55 -08:00
Megan Marsh 71b815a5fd reword commits 2021-03-05 16:07:32 -08:00
Wilken Rivera 79481ed7ec Add vendored components map as opposed to removing code generation for command/plugin.go 2021-03-05 15:44:04 -05:00
Wilken Rivera a101d46589 update modules.txt 2021-03-05 15:33:35 -05:00
Wilken Rivera 3058c437a3 Register remote plugins docs with https://packer.io 2021-03-05 15:33:34 -05:00
Wilken Rivera 10e1573930 Remove docker documentation from website 2021-03-05 15:33:34 -05:00
Megan Marsh c3e48ebb71 add vendored files 2021-03-05 15:33:34 -05:00
Megan Marsh 04cbcd7ae9 add docker vendoring to modules.txt 2021-03-05 15:33:34 -05:00
Megan Marsh 1c3c1f17d9 stop generating plugins file to make it possible to vendor plugins 2021-03-05 15:33:34 -05:00
Megan Marsh 9331afcf80 revendor 2021-03-05 15:33:34 -05:00
Megan Marsh a5b0e37d7e docker extraction POC 2021-03-05 15:33:34 -05:00
Hosh 6b5a3dacd4 Fix multiple files downloading overwrites same file (#10711) 2021-03-05 18:06:47 +01:00
Jiankun Lu 22cb31cb7b Update public GCP image project as gce-uefi-images is be deprecated 2021-03-03 16:12:25 -08:00
teddylear 0637601eda Fixing recursive formatting tests to work on all platforms 2021-02-11 22:08:36 -05:00
teddylear d85286e228 Merging in master 2021-02-10 17:44:11 -05:00
teddylear d3754e3021 Updating recursive formatter tests to be cleaner and table driven 2021-02-06 18:02:26 -05:00
Adrien Delorme ab7e89781a empty commit to check if I can push 2021-02-06 18:02:26 -05:00
teddylear 40a97e29db Clean up recursive format tests to be more accurate 2021-02-06 18:02:26 -05:00
teddylear 93df53a275 Refactor recursive formatting test cases to be table driven 2021-02-06 18:02:26 -05:00
teddylear 6adf1f6659 Fixing recursive fmt tests syntax and adding test case when recursive
option is off
2021-02-06 18:02:26 -05:00
teddylear 261abe0cae Setting recursive fmt to false, updatting recursive fmt test to validate
formatted files
2021-02-06 18:02:26 -05:00
teddylear ab4b3a8465 Adding recursive flag to formatter to format subdirectories 2021-02-06 18:02:23 -05:00
375 changed files with 49215 additions and 17217 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ Describe the change you are making here!
Please include tests. Check out these examples:
- https://github.com/hashicorp/packer/blob/master/builder/virtualbox/common/ssh_config_test.go#L19-L37
- https://github.com/hashicorp/packer/blob/master/builder/parallels/common/ssh_config_test.go#L34
- https://github.com/hashicorp/packer/blob/master/post-processor/compress/post-processor_test.go#L153-L182
If your PR resolves any open issue(s), please indicate them like this so they will be closed when your PR is merged:
+13 -1
View File
@@ -1,18 +1,30 @@
## 1.7.1 (Upcoming)
### FEATURES
### IMPROVEMENTS
* builder/amazon: allow creation of ebs snapshots wihtout volumes. [GH-9591]
* builder/scaleway: add support for timeout in shutdown step. [GH-10503]
* builder/virtualbox: Add template options for chipset, firmware, nic, graphics
controller, and audio controller. [GH-10671]
* builder/virtualbox: Support for "virtio" storage and ISO drive. [GH-10632]
* builder/vmware: Added "attach_snapshot" parameter to vmware vmx builder.
[GH-10651]
* core: Change template parsing error to include warning about file extensions.
[GH-10652]
* hcl2_upgrade: hcl2_upgrade command can now upgrade json var-files [GH-10676]
### BUG FIXES
* builder/amazon: Update amazon SDK to fix an SSO login issue. [GH-10668]
* builder/azure: Don't overwrite subscription id if unset. [GH-10659]
* builder/oracle-oci: Update Oracle Go SDK to fix issue with reading key file.
[GH-10560]
* builder/vmware: Added a fallback file check when trying to determine the
network-mapping configuration. [GH-10543]
* core/hcl2_upgrade: Make hcl2_upgrade command correctly translate
pause_before. [GH-10654]
* core: Templates previously could not interpolate the environment variable
PACKER_LOG_PATH. [GH-10660]
* provisioner/chef-solo: HCL2 templates can support the json_string option.
[GH-10655]
## 1.7.0 (February 17, 2021)
-1
View File
@@ -87,7 +87,6 @@
/post-processor/alicloud-import/ dongxiao.zzh@alibaba-inc.com
/post-processor/checksum/ v.tolstov@selfip.ru
/post-processor/exoscale-import/ @falzm @mcorbin
/post-processor/googlecompute-export/ crunkleton@google.com
/post-processor/yandex-export/ @GennadySpb
/post-processor/yandex-import/ @GennadySpb
+1 -1
View File
@@ -172,7 +172,7 @@ func (c *AMIConfig) Prepare(accessConfig *AccessConfig, ctx *interpolate.Context
// Prevent sharing of default KMS key encrypted volumes with other aws users
if len(c.AMIUsers) > 0 {
if len(c.AMIKmsKeyId) == 0 && c.AMIEncryptBootVolume.True() {
if len(c.AMIKmsKeyId) == 0 && len(c.AMIRegionKMSKeyIDs) == 0 && c.AMIEncryptBootVolume.True() {
errs = append(errs, fmt.Errorf("Cannot share AMI encrypted with default KMS key"))
}
if len(c.AMIRegionKMSKeyIDs) > 0 {
-11
View File
@@ -1,11 +0,0 @@
package docker
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestExportArtifact_impl(t *testing.T) {
var _ packersdk.Artifact = new(ExportArtifact)
}
-58
View File
@@ -1,58 +0,0 @@
package docker
import (
"errors"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestImportArtifact_impl(t *testing.T) {
var _ packersdk.Artifact = new(ImportArtifact)
}
func TestImportArtifactBuilderId(t *testing.T) {
a := &ImportArtifact{BuilderIdValue: "foo"}
if a.BuilderId() != "foo" {
t.Fatalf("bad: %#v", a.BuilderId())
}
}
func TestImportArtifactFiles(t *testing.T) {
a := &ImportArtifact{}
if a.Files() != nil {
t.Fatalf("bad: %#v", a.Files())
}
}
func TestImportArtifactId(t *testing.T) {
a := &ImportArtifact{IdValue: "foo"}
if a.Id() != "foo" {
t.Fatalf("bad: %#v", a.Id())
}
}
func TestImportArtifactDestroy(t *testing.T) {
d := new(MockDriver)
a := &ImportArtifact{
Driver: d,
IdValue: "foo",
}
// No error
if err := a.Destroy(); err != nil {
t.Fatalf("err: %s", err)
}
if !d.DeleteImageCalled {
t.Fatal("delete image should be called")
}
if d.DeleteImageId != "foo" {
t.Fatalf("bad: %#v", d.DeleteImageId)
}
// With an error
d.DeleteImageErr = errors.New("foo")
if err := a.Destroy(); err != d.DeleteImageErr {
t.Fatalf("err: %#v", err)
}
}
-11
View File
@@ -1,11 +0,0 @@
package docker
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestBuilder_implBuilder(t *testing.T) {
var _ packersdk.Builder = new(Builder)
}
-239
View File
@@ -1,239 +0,0 @@
package docker
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"testing"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
// RenderConfig helps create dynamic packer template configs for parsing by
// builderT without having to write the config to a file.
func RenderConfig(builderConfig map[string]interface{}, provisionerConfig []map[string]interface{}) string {
// set up basic build template
t := map[string][]map[string]interface{}{
"builders": {
// Setup basic docker config
map[string]interface{}{
"type": "test",
"image": "ubuntu",
"discard": true,
},
},
"provisioners": []map[string]interface{}{},
}
// apply special builder overrides
for k, v := range builderConfig {
t["builders"][0][k] = v
}
// Apply special provisioner overrides
t["provisioners"] = append(t["provisioners"], provisionerConfig...)
j, _ := json.Marshal(t)
return string(j)
}
// TestUploadDownload verifies that basic upload / download functionality works
func TestUploadDownload(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1")
}
dockerBuilderExtraConfig := map[string]interface{}{
"run_command": []string{"-d", "-i", "-t", "{{.Image}}", "/bin/sh"},
}
dockerProvisionerConfig := []map[string]interface{}{
{
"type": "file",
"source": "test-fixtures/onecakes/strawberry",
"destination": "/strawberry-cake",
},
{
"type": "file",
"source": "/strawberry-cake",
"destination": "my-strawberry-cake",
"direction": "download",
},
}
configString := RenderConfig(dockerBuilderExtraConfig, dockerProvisionerConfig)
// this should be a precheck
cmd := exec.Command("docker", "-v")
err := cmd.Run()
if err != nil {
t.Error("docker command not found; please make sure docker is installed")
}
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: configString,
Check: func(a []packersdk.Artifact) error {
// Verify that the thing we downloaded is the same thing we sent up.
// Complain loudly if it isn't.
inputFile, err := ioutil.ReadFile("test-fixtures/onecakes/strawberry")
if err != nil {
return fmt.Errorf("Unable to read input file: %s", err)
}
outputFile, err := ioutil.ReadFile("my-strawberry-cake")
if err != nil {
return fmt.Errorf("Unable to read output file: %s", err)
}
if sha256.Sum256(inputFile) != sha256.Sum256(outputFile) {
return fmt.Errorf("Input and output files do not match\n"+
"Input:\n%s\nOutput:\n%s\n", inputFile, outputFile)
}
return nil
},
Teardown: func() error {
// Cleanup. Honestly I don't know why you would want to get rid
// of my strawberry cake. It's so tasty! Do you not like cake? Are you a
// cake-hater? Or are you keeping all the cake all for yourself? So selfish!
os.Remove("my-strawberry-cake")
return nil
},
})
}
// TestLargeDownload verifies that files are the appropriate size after being
// downloaded. This is to identify and fix the race condition in #2793. You may
// need to use github.com/cbednarski/rerun to verify since this problem occurs
// only intermittently.
func TestLargeDownload(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1")
}
dockerProvisionerConfig := []map[string]interface{}{
{
"type": "shell",
"inline": []string{
"dd if=/dev/urandom of=/tmp/cupcake bs=1M count=2",
"dd if=/dev/urandom of=/tmp/bigcake bs=1M count=100",
"sync",
"md5sum /tmp/cupcake /tmp/bigcake",
},
},
{
"type": "file",
"source": "/tmp/cupcake",
"destination": "cupcake",
"direction": "download",
},
{
"type": "file",
"source": "/tmp/bigcake",
"destination": "bigcake",
"direction": "download",
},
}
configString := RenderConfig(map[string]interface{}{}, dockerProvisionerConfig)
// this should be a precheck
cmd := exec.Command("docker", "-v")
err := cmd.Run()
if err != nil {
t.Error("docker command not found; please make sure docker is installed")
}
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: configString,
Check: func(a []packersdk.Artifact) error {
// Verify that the things we downloaded are the right size. Complain loudly
// if they are not.
//
// cupcake should be 2097152 bytes
// bigcake should be 104857600 bytes
cupcake, err := os.Stat("cupcake")
if err != nil {
t.Fatalf("Unable to stat cupcake file: %s", err)
}
cupcakeExpected := int64(2097152)
if cupcake.Size() != cupcakeExpected {
t.Errorf("Expected cupcake to be %d bytes; found %d", cupcakeExpected, cupcake.Size())
}
bigcake, err := os.Stat("bigcake")
if err != nil {
t.Fatalf("Unable to stat bigcake file: %s", err)
}
bigcakeExpected := int64(104857600)
if bigcake.Size() != bigcakeExpected {
t.Errorf("Expected bigcake to be %d bytes; found %d", bigcakeExpected, bigcake.Size())
}
// TODO if we can, calculate a sha inside the container and compare to the
// one we get after we pull it down. We will probably have to parse the log
// or ui output to do this because we use /dev/urandom to create the file.
// if sha256.Sum256(inputFile) != sha256.Sum256(outputFile) {
// t.Fatalf("Input and output files do not match\n"+
// "Input:\n%s\nOutput:\n%s\n", inputFile, outputFile)
// }
return nil
},
Teardown: func() error {
os.Remove("cupcake")
os.Remove("bigcake")
return nil
},
})
}
// TestFixUploadOwner verifies that owner of uploaded files is the user the container is running as.
func TestFixUploadOwner(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1")
}
cmd := exec.Command("docker", "-v")
err := cmd.Run()
if err != nil {
t.Error("docker command not found; please make sure docker is installed")
}
dockerBuilderExtraConfig := map[string]interface{}{
"run_command": []string{"-d", "-i", "-t", "-u", "42", "{{.Image}}", "/bin/sh"},
}
testFixUploadOwnerProvisionersTemplate := []map[string]interface{}{
{
"type": "file",
"source": "test-fixtures/onecakes/strawberry",
"destination": "/tmp/strawberry-cake",
},
{
"type": "file",
"source": "test-fixtures/manycakes",
"destination": "/tmp/",
},
{
"type": "shell",
"inline": "touch /tmp/testUploadOwner",
},
{
"type": "shell",
"inline": []string{
"[ $(stat -c %u /tmp/strawberry-cake) -eq 42 ] || (echo 'Invalid owner of /tmp/strawberry-cake' && exit 1)",
"[ $(stat -c %u /tmp/testUploadOwner) -eq 42 ] || (echo 'Invalid owner of /tmp/testUploadOwner' && exit 1)",
"find /tmp/manycakes | xargs -n1 -IFILE /bin/sh -c '[ $(stat -c %u FILE) -eq 42 ] || (echo \"Invalid owner of FILE\" && exit 1)'",
},
},
}
configString := RenderConfig(dockerBuilderExtraConfig, testFixUploadOwnerProvisionersTemplate)
builderT.Test(t, builderT.TestCase{
Builder: &Builder{},
Template: configString,
})
}
-147
View File
@@ -1,147 +0,0 @@
package docker
import (
"io/ioutil"
"os"
"testing"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"export_path": "foo",
"image": "bar",
}
}
func testConfigStruct(t *testing.T) *Config {
var c Config
warns, errs := c.Prepare(testConfig())
if len(warns) > 0 {
t.Fatalf("bad: %#v", len(warns))
}
if errs != nil {
t.Fatalf("bad: %#v", errs)
}
return &c
}
func testConfigErr(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err == nil {
t.Fatal("should error")
}
}
func testConfigOk(t *testing.T, warns []string, err error) {
if len(warns) > 0 {
t.Fatalf("bad: %#v", warns)
}
if err != nil {
t.Fatalf("bad: %s", err)
}
}
func TestConfigPrepare_exportPath(t *testing.T) {
td, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(td)
raw := testConfig()
// No export path. This is invalid. Previously this would not error during
// validation and as a result the failure would happen at build time.
delete(raw, "export_path")
var c Config
warns, errs := c.Prepare(raw)
testConfigErr(t, warns, errs)
// Good export path
raw["export_path"] = "good"
warns, errs = c.Prepare(raw)
testConfigOk(t, warns, errs)
// Bad export path (directory)
raw["export_path"] = td
warns, errs = c.Prepare(raw)
testConfigErr(t, warns, errs)
}
func TestConfigPrepare_exportPathAndCommit(t *testing.T) {
raw := testConfig()
// Export but no commit (explicit default)
raw["commit"] = false
warns, errs := (&Config{}).Prepare(raw)
testConfigOk(t, warns, errs)
// Commit AND export specified (invalid)
raw["commit"] = true
warns, errs = (&Config{}).Prepare(raw)
testConfigErr(t, warns, errs)
// Commit but no export
delete(raw, "export_path")
warns, errs = (&Config{}).Prepare(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_exportDiscard(t *testing.T) {
raw := testConfig()
// Export but no discard (explicit default)
raw["discard"] = false
warns, errs := (&Config{}).Prepare(raw)
testConfigOk(t, warns, errs)
// Discard AND export (invalid)
raw["discard"] = true
warns, errs = (&Config{}).Prepare(raw)
testConfigErr(t, warns, errs)
// Discard but no export
raw["discard"] = true
delete(raw, "export_path")
warns, errs = (&Config{}).Prepare(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_image(t *testing.T) {
raw := testConfig()
// No image
delete(raw, "image")
var c Config
warns, errs := c.Prepare(raw)
testConfigErr(t, warns, errs)
// Good image
raw["image"] = "path"
warns, errs = c.Prepare(raw)
testConfigOk(t, warns, errs)
}
func TestConfigPrepare_pull(t *testing.T) {
raw := testConfig()
// No pull set
delete(raw, "pull")
var c Config
warns, errs := c.Prepare(raw)
testConfigOk(t, warns, errs)
if !c.Pull {
t.Fatal("should pull by default")
}
// Pull set
raw["pull"] = false
warns, errs = c.Prepare(raw)
testConfigOk(t, warns, errs)
if c.Pull {
t.Fatal("should not pull")
}
}
-7
View File
@@ -1,7 +0,0 @@
package docker
import "testing"
func TestDockerDriver_impl(t *testing.T) {
var _ Driver = new(DockerDriver)
}
-7
View File
@@ -1,7 +0,0 @@
package docker
import "testing"
func TestMockDriver_impl(t *testing.T) {
var _ Driver = new(MockDriver)
}
-68
View File
@@ -1,68 +0,0 @@
package docker
import (
"context"
"errors"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func testStepCommitState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("container_id", "foo")
return state
}
func TestStepCommit_impl(t *testing.T) {
var _ multistep.Step = new(StepCommit)
}
func TestStepCommit(t *testing.T) {
state := testStepCommitState(t)
step := new(StepCommit)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.CommitImageId = "bar"
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.CommitCalled {
t.Fatal("should've called")
}
// verify the ID is saved
idRaw, ok := state.GetOk("image_id")
if !ok {
t.Fatal("should've saved ID")
}
id := idRaw.(string)
if id != driver.CommitImageId {
t.Fatalf("bad: %#v", id)
}
}
func TestStepCommit_error(t *testing.T) {
state := testStepCommitState(t)
step := new(StepCommit)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.CommitErr = errors.New("foo")
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify the ID is not saved
if _, ok := state.GetOk("image_id"); ok {
t.Fatal("shouldn't save image ID")
}
}
-101
View File
@@ -1,101 +0,0 @@
package docker
import (
"bytes"
"context"
"errors"
"io/ioutil"
"os"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func testStepExportState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("container_id", "foo")
return state
}
func TestStepExport_impl(t *testing.T) {
var _ multistep.Step = new(StepExport)
}
func TestStepExport(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
defer os.Remove(tf.Name())
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportReader = bytes.NewReader([]byte("data!"))
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.ExportCalled {
t.Fatal("should've exported")
}
if driver.ExportID != "foo" {
t.Fatalf("bad: %#v", driver.ExportID)
}
// verify the data exported to the file
contents, err := ioutil.ReadFile(tf.Name())
if err != nil {
t.Fatalf("err: %s", err)
}
if string(contents) != "data!" {
t.Fatalf("bad: %#v", string(contents))
}
}
func TestStepExport_error(t *testing.T) {
state := testStepExportState(t)
step := new(StepExport)
defer step.Cleanup(state)
// Create a tempfile for our output path
tf, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
tf.Close()
if err := os.Remove(tf.Name()); err != nil {
t.Fatalf("err: %s", err)
}
config := state.Get("config").(*Config)
config.ExportPath = tf.Name()
driver := state.Get("driver").(*MockDriver)
driver.ExportError = errors.New("foo")
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
// verify we didn't make that file
if _, err := os.Stat(tf.Name()); err == nil {
t.Fatal("export path shouldn't exist")
}
}
-104
View File
@@ -1,104 +0,0 @@
package docker
import (
"context"
"errors"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepPull_impl(t *testing.T) {
var _ multistep.Step = new(StepPull)
}
func TestStepPull(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.PullCalled {
t.Fatal("should've pulled")
}
if driver.PullImage != config.Image {
t.Fatalf("bad: %#v", driver.PullImage)
}
}
func TestStepPull_error(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.PullError = errors.New("foo")
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify we have an error
if _, ok := state.GetOk("error"); !ok {
t.Fatal("should have error")
}
}
func TestStepPull_login(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
config.Login = true
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we pulled
if !driver.PullCalled {
t.Fatal("should've pulled")
}
// verify we logged in
if !driver.LoginCalled {
t.Fatal("should've logged in")
}
if !driver.LogoutCalled {
t.Fatal("should've logged out")
}
}
func TestStepPull_noPull(t *testing.T) {
state := testState(t)
step := new(StepPull)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
config.Pull = false
driver := state.Get("driver").(*MockDriver)
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if driver.PullCalled {
t.Fatal("shouldn't have pulled")
}
}
-97
View File
@@ -1,97 +0,0 @@
package docker
import (
"context"
"errors"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func testStepRunState(t *testing.T) multistep.StateBag {
state := testState(t)
state.Put("temp_dir", "/foo")
return state
}
func TestStepRun_impl(t *testing.T) {
var _ multistep.Step = new(StepRun)
}
func TestStepRun(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
config := state.Get("config").(*Config)
driver := state.Get("driver").(*MockDriver)
driver.StartID = "foo"
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// verify we did the right thing
if !driver.StartCalled {
t.Fatal("should've called")
}
if driver.StartConfig.Image != config.Image {
t.Fatalf("bad: %#v", driver.StartConfig.Image)
}
// verify the ID is saved
idRaw, ok := state.GetOk("container_id")
if !ok {
t.Fatal("should've saved ID")
}
id := idRaw.(string)
if id != "foo" {
t.Fatalf("bad: %#v", id)
}
// Verify we haven't called stop yet
if driver.KillCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if !driver.KillCalled {
t.Fatal("should've stopped")
}
if driver.KillID != id {
t.Fatalf("bad: %#v", driver.StopID)
}
}
func TestStepRun_error(t *testing.T) {
state := testStepRunState(t)
step := new(StepRun)
defer step.Cleanup(state)
driver := state.Get("driver").(*MockDriver)
driver.StartError = errors.New("foo")
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v", action)
}
// verify the ID is not saved
if _, ok := state.GetOk("container_id"); ok {
t.Fatal("shouldn't save container ID")
}
// Verify we haven't called stop yet
if driver.KillCalled {
t.Fatal("should not have stopped")
}
// Cleanup
step.Cleanup(state)
if driver.KillCalled {
t.Fatal("should not have stopped")
}
}
@@ -1,51 +0,0 @@
package docker
import (
"context"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packerbuilderdata"
)
func TestStepSetGeneratedData_Run(t *testing.T) {
state := testState(t)
step := new(StepSetGeneratedData)
step.GeneratedData = &packerbuilderdata.GeneratedData{State: state}
driver := state.Get("driver").(*MockDriver)
driver.Sha256Result = "80B3BB1B1696E73A9B19DEEF92F664F8979F948DF348088B61F9A3477655AF64"
state.Put("image_id", "12345")
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
t.Fatalf("Should not halt")
}
if !driver.Sha256Called {
t.Fatalf("driver.SHA256 should be called")
}
if driver.Sha256Id != "12345" {
t.Fatalf("driver.SHA256 got wrong image it: %s", driver.Sha256Id)
}
genData := state.Get("generated_data").(map[string]interface{})
imgSha256 := genData["ImageSha256"].(string)
if imgSha256 != driver.Sha256Result {
t.Fatalf("Expected ImageSha256 to be %s but was %s", driver.Sha256Result, imgSha256)
}
// Image ID not implement
state = testState(t)
step.GeneratedData = &packerbuilderdata.GeneratedData{State: state}
driver = state.Get("driver").(*MockDriver)
notImplementedMsg := "ERR_IMAGE_SHA256_NOT_FOUND"
if action := step.Run(context.TODO(), state); action != multistep.ActionContinue {
t.Fatalf("Should not halt")
}
if driver.Sha256Called {
t.Fatalf("driver.SHA256 should not be called")
}
genData = state.Get("generated_data").(map[string]interface{})
imgSha256 = genData["ImageSha256"].(string)
if imgSha256 != notImplementedMsg {
t.Fatalf("Expected ImageSha256 to be %s but was %s", notImplementedMsg, imgSha256)
}
}
-52
View File
@@ -1,52 +0,0 @@
package docker
import (
"context"
"os"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
func TestStepTempDir_impl(t *testing.T) {
var _ multistep.Step = new(StepTempDir)
}
func testStepTempDir_impl(t *testing.T) string {
state := testState(t)
step := new(StepTempDir)
defer step.Cleanup(state)
// sanity test
if _, ok := state.GetOk("temp_dir"); ok {
t.Fatalf("temp_dir should not be in state yet")
}
// run the step
if action := step.Run(context.Background(), state); action != multistep.ActionContinue {
t.Fatalf("bad action: %#v", action)
}
// Verify that we got the temp dir
dirRaw, ok := state.GetOk("temp_dir")
if !ok {
t.Fatalf("should've made temp_dir")
}
dir := dirRaw.(string)
if _, err := os.Stat(dir); err != nil {
t.Fatalf("Stat for %s failed: err: %s", err, dir)
}
// Cleanup
step.Cleanup(state)
if _, err := os.Stat(dir); err == nil {
t.Fatalf("dir should be gone")
}
return dir
}
func TestStepTempDir(t *testing.T) {
testStepTempDir_impl(t)
}
-21
View File
@@ -1,21 +0,0 @@
package docker
import (
"bytes"
"testing"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func testState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("config", testConfigStruct(t))
state.Put("driver", &MockDriver{})
state.Put("hook", &packersdk.MockHook{})
state.Put("ui", &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
@@ -1 +0,0 @@
chocolate!
@@ -1 +0,0 @@
vanilla!
@@ -1 +0,0 @@
strawberry!
-13
View File
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var DigitalOceanPluginVersion *version.PluginVersion
func init() {
DigitalOceanPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
+6
View File
@@ -210,6 +210,8 @@ type Config struct {
// - The contents of the script file will be wrapped in Packer's startup script wrapper, unless `wrap_startup_script` is disabled. See `wrap_startup_script` for more details.
// - Not supported by Windows instances. See [Startup Scripts for Windows](https://cloud.google.com/compute/docs/startupscript#providing_a_startup_script_for_windows_instances) for more details.
StartupScriptFile string `mapstructure:"startup_script_file" required:"false"`
// The time to wait for windows password to be retrieved. Defaults to "3m".
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout" required:"false"`
// For backwards compatibility this option defaults to `"true"` in the future it will default to `"false"`.
// If "true", the contents of `startup_script_file` or `"startup_script"` in the instance metadata
// is wrapped in a Packer specific script that tracks the execution and completion of the provided
@@ -542,6 +544,10 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
c.WrapStartupScriptFile = config.TriTrue
}
}
// Check windows password timeout is provided
if c.WindowsPasswordTimeout == 0 {
c.WindowsPasswordTimeout = 3 * time.Minute
}
// Check for any errors.
if errs != nil && len(errs.Errors) > 0 {
+2
View File
@@ -112,6 +112,7 @@ type FlatConfig struct {
SourceImageFamily *string `mapstructure:"source_image_family" required:"true" cty:"source_image_family" hcl:"source_image_family"`
SourceImageProjectId []string `mapstructure:"source_image_project_id" required:"false" cty:"source_image_project_id" hcl:"source_image_project_id"`
StartupScriptFile *string `mapstructure:"startup_script_file" required:"false" cty:"startup_script_file" hcl:"startup_script_file"`
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout" hcl:"windows_password_timeout"`
WrapStartupScriptFile *bool `mapstructure:"wrap_startup_script" required:"false" cty:"wrap_startup_script" hcl:"wrap_startup_script"`
Subnetwork *string `mapstructure:"subnetwork" required:"false" cty:"subnetwork" hcl:"subnetwork"`
Tags []string `mapstructure:"tags" required:"false" cty:"tags" hcl:"tags"`
@@ -236,6 +237,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"source_image_family": &hcldec.AttrSpec{Name: "source_image_family", Type: cty.String, Required: false},
"source_image_project_id": &hcldec.AttrSpec{Name: "source_image_project_id", Type: cty.List(cty.String), Required: false},
"startup_script_file": &hcldec.AttrSpec{Name: "startup_script_file", Type: cty.String, Required: false},
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
"wrap_startup_script": &hcldec.AttrSpec{Name: "wrap_startup_script", Type: cty.Bool, Required: false},
"subnetwork": &hcldec.AttrSpec{Name: "subnetwork", Type: cty.String, Required: false},
"tags": &hcldec.AttrSpec{Name: "tags", Type: cty.List(cty.String), Required: false},
+8 -7
View File
@@ -107,13 +107,14 @@ type InstanceConfig struct {
// WindowsPasswordConfig is the data structure that GCE needs to encrypt the created
// windows password.
type WindowsPasswordConfig struct {
key *rsa.PrivateKey
password string
UserName string `json:"userName"`
Modulus string `json:"modulus"`
Exponent string `json:"exponent"`
Email string `json:"email"`
ExpireOn time.Time `json:"expireOn"`
key *rsa.PrivateKey
password string
UserName string `json:"userName"`
Modulus string `json:"modulus"`
Exponent string `json:"exponent"`
Email string `json:"email"`
ExpireOn time.Time `json:"expireOn"`
WindowsPasswordTimeout time.Duration `json:"timeout"`
}
type windowsPasswordResponse struct {
+1 -2
View File
@@ -253,7 +253,6 @@ func (d *driverGCE) GetImage(name string, fromFamily bool) (*Image, error) {
"ubuntu-os-cloud",
"windows-cloud",
"windows-sql-cloud",
"gce-uefi-images",
"gce-nvme",
// misc
"google-containers",
@@ -568,7 +567,7 @@ func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string,
return
}
timeout := time.Now().Add(time.Minute * 3)
timeout := time.Now().Add(c.WindowsPasswordTimeout)
hash := sha1.New()
random := rand.Reader
@@ -60,12 +60,13 @@ func (s *StepCreateWindowsPassword) Run(ctx context.Context, state multistep.Sta
}
data := WindowsPasswordConfig{
key: priv,
UserName: c.Comm.WinRMUser,
Modulus: base64.StdEncoding.EncodeToString(priv.N.Bytes()),
Exponent: base64.StdEncoding.EncodeToString(buf[1:]),
Email: email,
ExpireOn: time.Now().Add(time.Minute * 5),
key: priv,
UserName: c.Comm.WinRMUser,
Modulus: base64.StdEncoding.EncodeToString(priv.N.Bytes()),
Exponent: base64.StdEncoding.EncodeToString(buf[1:]),
Email: email,
ExpireOn: time.Now().Add(time.Minute * 5),
WindowsPasswordTimeout: c.WindowsPasswordTimeout,
}
if s.Debug {
@@ -98,7 +99,7 @@ func (s *StepCreateWindowsPassword) Run(ctx context.Context, state multistep.Sta
ui.Message("Waiting for windows password to complete...")
select {
case err = <-errCh:
case <-time.After(c.StateTimeout):
case <-time.After(c.WindowsPasswordTimeout):
err = errors.New("time out while waiting for the password to be created")
}
}
+1 -1
View File
@@ -294,7 +294,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
// configure the communicator ssh, winrm
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: hypervcommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
Host: hypervcommon.CommHost(b.config.SSHConfig.Comm.Host()),
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
},
+1 -1
View File
@@ -334,7 +334,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
// configure the communicator ssh, winrm
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: hypervcommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
Host: hypervcommon.CommHost(b.config.SSHConfig.Comm.Host()),
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
},
+1 -1
View File
@@ -151,7 +151,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
&communicator.StepConnect{
Config: &b.config.RunConfig.Comm,
Host: CommHost(
b.config.RunConfig.Comm.SSHHost,
b.config.RunConfig.Comm.Host(),
computeClient,
b.config.SSHInterface,
b.config.SSHIPVersion),
+1 -1
View File
@@ -238,7 +238,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: parallelscommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
Host: parallelscommon.CommHost(b.config.SSHConfig.Comm.Host()),
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
},
&parallelscommon.StepUploadVersion{
+1 -1
View File
@@ -87,7 +87,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&communicator.StepConnect{
Config: &b.config.SSHConfig.Comm,
Host: parallelscommon.CommHost(b.config.SSHConfig.Comm.SSHHost),
Host: parallelscommon.CommHost(b.config.SSHConfig.Comm.Host()),
SSHConfig: b.config.SSHConfig.Comm.SSHConfigFunc(),
},
&parallelscommon.StepUploadVersion{
+1
View File
@@ -48,6 +48,7 @@ func (c *CommConfig) Prepare(ctx *interpolate.Context) (warnings []string, errs
if c.Comm.SSHHost == "" && c.SkipNatMapping {
c.Comm.SSHHost = "127.0.0.1"
c.Comm.WinRMHost = "127.0.0.1"
}
if c.HostPortMin == 0 {
+13 -7
View File
@@ -41,17 +41,23 @@ func (s *StepSSHConfig) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt
}
config.Comm.SSHHost = sshConfig.Hostname
port, err := strconv.Atoi(sshConfig.Port)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
if config.Comm.SSHHost == "" {
config.Comm.SSHHost = sshConfig.Hostname
}
if config.Comm.SSHPort == 0 {
port, err := strconv.Atoi(sshConfig.Port)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
config.Comm.SSHPort = port
}
config.Comm.SSHPort = port
if config.Comm.SSHUsername != "" {
// If user has set the username within the communicator, use the
// auth provided there.
// username, password, and/or keyfile auth provided there.
log.Printf("Overriding SSH config from Vagrant with the username, " +
"password, and private key information provided to the Packer template.")
return multistep.ActionContinue
}
log.Printf("identity file is %s", sshConfig.IdentityFile)
+78
View File
@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/multistep"
)
@@ -15,6 +16,83 @@ func TestStepSSHConfig_Impl(t *testing.T) {
}
}
func TestPrepStepSSHConfig_sshOverrides(t *testing.T) {
type testcase struct {
name string
inputSSHConfig communicator.SSH
expectedSSHConfig communicator.SSH
}
tcs := []testcase{
{
// defaults to overriding with the ssh config from vagrant\
name: "default",
inputSSHConfig: communicator.SSH{},
expectedSSHConfig: communicator.SSH{
SSHHost: "127.0.0.1",
SSHPort: 2222,
SSHUsername: "vagrant",
SSHPassword: "",
},
},
{
// respects SSH host and port overrides independent of credential
// overrides
name: "host_override",
inputSSHConfig: communicator.SSH{
SSHHost: "123.45.67.8",
SSHPort: 1234,
},
expectedSSHConfig: communicator.SSH{
SSHHost: "123.45.67.8",
SSHPort: 1234,
SSHUsername: "vagrant",
SSHPassword: "",
},
},
{
// respects credential overrides
name: "credential_override",
inputSSHConfig: communicator.SSH{
SSHUsername: "megan",
SSHPassword: "SoSecure",
},
expectedSSHConfig: communicator.SSH{
SSHHost: "127.0.0.1",
SSHPort: 2222,
SSHUsername: "megan",
SSHPassword: "SoSecure",
},
},
}
for _, tc := range tcs {
driver := &MockVagrantDriver{}
config := &Config{
Comm: communicator.Config{
SSH: tc.inputSSHConfig,
},
}
state := new(multistep.BasicStateBag)
state.Put("driver", driver)
state.Put("config", config)
step := StepSSHConfig{}
_ = step.Run(context.Background(), state)
if config.Comm.SSHHost != tc.expectedSSHConfig.SSHHost {
t.Fatalf("unexpected sshconfig host: name: %s, recieved %s", tc.name, config.Comm.SSHHost)
}
if config.Comm.SSHPort != tc.expectedSSHConfig.SSHPort {
t.Fatalf("unexpected sshconfig port: name: %s, recieved %d", tc.name, config.Comm.SSHPort)
}
if config.Comm.SSHUsername != tc.expectedSSHConfig.SSHUsername {
t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHUsername)
}
if config.Comm.SSHPassword != tc.expectedSSHConfig.SSHPassword {
t.Fatalf("unexpected sshconfig SSHUsername: name: %s, recieved %s", tc.name, config.Comm.SSHPassword)
}
}
}
func TestPrepStepSSHConfig_GlobalID(t *testing.T) {
driver := &MockVagrantDriver{}
config := &Config{}
+2 -1
View File
@@ -50,8 +50,9 @@ func (c *CommConfig) Prepare(ctx *interpolate.Context) []error {
c.SkipNatMapping = c.SSHSkipNatMapping
}
if c.Comm.SSHHost == "" {
if c.Comm.Host() == "" {
c.Comm.SSHHost = "127.0.0.1"
c.Comm.WinRMHost = "127.0.0.1"
}
if c.HostPortMin == 0 {
+26 -23
View File
@@ -8,6 +8,8 @@ import (
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"strconv"
)
// This step attaches the boot ISO, cd_files iso, and guest additions to the
@@ -69,49 +71,50 @@ func (s *StepAttachISOs) Run(ctx context.Context, state multistep.StateBag) mult
// We have three different potential isos we can attach, so let's
// assign each one its own spot so they don't conflict.
var controllerName, device, port string
var controllerName string
var device, port int
switch diskCategory {
case "boot_iso":
// figure out controller path
controllerName = "IDE Controller"
port = "0"
device = "1"
port = 0
device = 1
if s.ISOInterface == "sata" {
controllerName = "SATA Controller"
port = "1"
device = "0"
port = 15
device = 0
} else if s.ISOInterface == "virtio" {
controllerName = "VirtIO Controller"
port = "1"
device = "0"
port = 15
device = 0
}
ui.Message("Mounting boot ISO...")
case "guest_additions":
controllerName = "IDE Controller"
port = "1"
device = "0"
port = 1
device = 0
if s.GuestAdditionsInterface == "sata" {
controllerName = "SATA Controller"
port = "2"
device = "0"
port = 14
device = 0
} else if s.GuestAdditionsInterface == "virtio" {
controllerName = "VirtIO Controller"
port = "2"
device = "0"
port = 14
device = 0
}
ui.Message("Mounting guest additions ISO...")
case "cd_files":
controllerName = "IDE Controller"
port = "1"
device = "1"
port = 1
device = 1
if s.ISOInterface == "sata" {
controllerName = "SATA Controller"
port = "3"
device = "0"
port = 13
device = 0
} else if s.ISOInterface == "virtio" {
controllerName = "VirtIO Controller"
port = "3"
device = "0"
port = 13
device = 0
}
ui.Message("Mounting cd_files ISO...")
}
@@ -120,8 +123,8 @@ func (s *StepAttachISOs) Run(ctx context.Context, state multistep.StateBag) mult
command := []string{
"storageattach", vmName,
"--storagectl", controllerName,
"--port", port,
"--device", device,
"--port", strconv.Itoa(port),
"--device", strconv.Itoa(device),
"--type", "dvddrive",
"--medium", isoPath,
}
@@ -137,8 +140,8 @@ func (s *StepAttachISOs) Run(ctx context.Context, state multistep.StateBag) mult
unmountCommand := []string{
"storageattach", vmName,
"--storagectl", controllerName,
"--port", port,
"--device", device,
"--port", strconv.Itoa(port),
"--device", strconv.Itoa(device),
"--type", "dvddrive",
"--medium", "none",
}
+56 -1
View File
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"regexp"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/bootcommand"
@@ -52,6 +53,14 @@ type Config struct {
// When set to bios, the firmare is BIOS. This is the default.
// When set to efi, the firmare is EFI.
Firmware string `mapstructure:"firmware" required:"false"`
// Nested virtualization: false or true.
// When set to true, nested virtualisation (VT-x/AMD-V) is enabled.
// When set to false, nested virtualisation is disabled. This is the default.
NestedVirt bool `mapstructure:"nested_virt" required:"false"`
// RTC time base: UTC or local.
// When set to true, the RTC is set as UTC time.
// When set to false, the RTC is set as local time. This is the default.
RTCTimeBase string `mapstructure:"rtc_time_base" required:"false"`
// The size, in megabytes, of the hard disk to create for the VM. By
// default, this is 40000 (about 40 GB).
DiskSize uint `mapstructure:"disk_size" required:"false"`
@@ -75,6 +84,17 @@ type Config struct {
// When set to vmsvga, the graphics controller is VMware SVGA.
// When set to none, the graphics controller is disabled.
GfxController string `mapstructure:"gfx_controller" required:"false"`
// The VRAM size to be used. By default, this is 4 MiB.
GfxVramSize uint `mapstructure:"gfx_vram_size" required:"false"`
// 3D acceleration: true or false.
// When set to true, 3D acceleration is enabled.
// When set to false, 3D acceleration is disabled. This is the default.
GfxAccelerate3D bool `mapstructure:"gfx_accelerate_3d" required:"false"`
// Screen resolution in EFI mode: WIDTHxHEIGHT.
// When set to WIDTHxHEIGHT, it provides the given width and height as screen resolution
// to EFI, for example 1920x1080 for Full-HD resolution. By default, no screen resolution
// is set. Note, that this option only affects EFI boot, not the (default) BIOS boot.
GfxEFIResolution string `mapstructure:"gfx_efi_resolution" required:"false"`
// The guest OS type being installed. By default this is other, but you can
// get dramatic performance improvements by setting this to the proper
// value. To view all available values for this run VBoxManage list
@@ -129,6 +149,12 @@ type Config struct {
// When set to sata, the drive is attached to an AHCI SATA controller.
// When set to virtio, the drive is attached to a VirtIO controller.
ISOInterface string `mapstructure:"iso_interface" required:"false"`
// Additional disks to create. Uses `vm_name` as the disk name template and
// appends `-#` where `#` is the position in the array. `#` starts at 1 since 0
// is the default disk. Each value represents the disk image size in MiB.
// Each additional disk uses the same disk parameters as the default disk.
// Unset by default.
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false"`
// Set this to true if you would like to keep the VM registered with
// virtualbox. Defaults to false.
KeepRegistered bool `mapstructure:"keep_registered" required:"false"`
@@ -212,6 +238,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
errs, errors.New("firmware can only be bios or efi"))
}
if b.config.RTCTimeBase == "" {
b.config.RTCTimeBase = "local"
}
switch b.config.RTCTimeBase {
case "UTC", "local":
// do nothing
default:
errs = packersdk.MultiErrorAppend(
errs, errors.New("rtc_time_base can only be UTC or local"))
}
if b.config.DiskSize == 0 {
b.config.DiskSize = 40000
}
@@ -242,6 +279,24 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
errs, errors.New("Graphics controller type can only be vboxvga, vboxsvga, vmsvga, none"))
}
if b.config.GfxVramSize == 0 {
b.config.GfxVramSize = 4
} else {
if b.config.GfxVramSize < 1 || b.config.GfxVramSize > 128 {
errs = packersdk.MultiErrorAppend(
errs, errors.New("VGRAM size must be from 0 (use default) to 128"))
}
}
if b.config.GfxEFIResolution != "" {
re := regexp.MustCompile(`^[\d]+x[\d]+$`)
matched := re.MatchString(b.config.GfxEFIResolution)
if !matched {
errs = packersdk.MultiErrorAppend(
errs, errors.New("EFI resolution must be in the format WIDTHxHEIGHT, e.g. 1920x1080"))
}
}
if b.config.AudioController == "" {
b.config.AudioController = "ac97"
}
@@ -400,7 +455,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&communicator.StepConnect{
Config: &b.config.CommConfig.Comm,
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.SSHHost),
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.Host()),
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
SSHPort: vboxcommon.CommPort,
WinRMPort: vboxcommon.CommPort,
@@ -119,10 +119,15 @@ type FlatConfig struct {
GuestAdditionsURL *string `mapstructure:"guest_additions_url" required:"false" cty:"guest_additions_url" hcl:"guest_additions_url"`
Chipset *string `mapstructure:"chipset" required:"false" cty:"chipset" hcl:"chipset"`
Firmware *string `mapstructure:"firmware" required:"false" cty:"firmware" hcl:"firmware"`
NestedVirt *bool `mapstructure:"nested_virt" required:"false" cty:"nested_virt" hcl:"nested_virt"`
RTCTimeBase *string `mapstructure:"rtc_time_base" required:"false" cty:"rtc_time_base" hcl:"rtc_time_base"`
DiskSize *uint `mapstructure:"disk_size" required:"false" cty:"disk_size" hcl:"disk_size"`
NICType *string `mapstructure:"nic_type" required:"false" cty:"nic_type" hcl:"nic_type"`
AudioController *string `mapstructure:"audio_controller" required:"false" cty:"audio_controller" hcl:"audio_controller"`
GfxController *string `mapstructure:"gfx_controller" required:"false" cty:"gfx_controller" hcl:"gfx_controller"`
GfxVramSize *uint `mapstructure:"gfx_vram_size" required:"false" cty:"gfx_vram_size" hcl:"gfx_vram_size"`
GfxAccelerate3D *bool `mapstructure:"gfx_accelerate_3d" required:"false" cty:"gfx_accelerate_3d" hcl:"gfx_accelerate_3d"`
GfxEFIResolution *string `mapstructure:"gfx_efi_resolution" required:"false" cty:"gfx_efi_resolution" hcl:"gfx_efi_resolution"`
GuestOSType *string `mapstructure:"guest_os_type" required:"false" cty:"guest_os_type" hcl:"guest_os_type"`
HardDriveDiscard *bool `mapstructure:"hard_drive_discard" required:"false" cty:"hard_drive_discard" hcl:"hard_drive_discard"`
HardDriveInterface *string `mapstructure:"hard_drive_interface" required:"false" cty:"hard_drive_interface" hcl:"hard_drive_interface"`
@@ -130,6 +135,7 @@ type FlatConfig struct {
NVMePortCount *int `mapstructure:"nvme_port_count" required:"false" cty:"nvme_port_count" hcl:"nvme_port_count"`
HardDriveNonrotational *bool `mapstructure:"hard_drive_nonrotational" required:"false" cty:"hard_drive_nonrotational" hcl:"hard_drive_nonrotational"`
ISOInterface *string `mapstructure:"iso_interface" required:"false" cty:"iso_interface" hcl:"iso_interface"`
AdditionalDiskSize []uint `mapstructure:"disk_additional_size" required:"false" cty:"disk_additional_size" hcl:"disk_additional_size"`
KeepRegistered *bool `mapstructure:"keep_registered" required:"false" cty:"keep_registered" hcl:"keep_registered"`
SkipExport *bool `mapstructure:"skip_export" required:"false" cty:"skip_export" hcl:"skip_export"`
VMName *string `mapstructure:"vm_name" required:"false" cty:"vm_name" hcl:"vm_name"`
@@ -256,10 +262,15 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"guest_additions_url": &hcldec.AttrSpec{Name: "guest_additions_url", Type: cty.String, Required: false},
"chipset": &hcldec.AttrSpec{Name: "chipset", Type: cty.String, Required: false},
"firmware": &hcldec.AttrSpec{Name: "firmware", Type: cty.String, Required: false},
"nested_virt": &hcldec.AttrSpec{Name: "nested_virt", Type: cty.Bool, Required: false},
"rtc_time_base": &hcldec.AttrSpec{Name: "rtc_time_base", Type: cty.String, Required: false},
"disk_size": &hcldec.AttrSpec{Name: "disk_size", Type: cty.Number, Required: false},
"nic_type": &hcldec.AttrSpec{Name: "nic_type", Type: cty.String, Required: false},
"audio_controller": &hcldec.AttrSpec{Name: "audio_controller", Type: cty.String, Required: false},
"gfx_controller": &hcldec.AttrSpec{Name: "gfx_controller", Type: cty.String, Required: false},
"gfx_vram_size": &hcldec.AttrSpec{Name: "gfx_vram_size", Type: cty.Number, Required: false},
"gfx_accelerate_3d": &hcldec.AttrSpec{Name: "gfx_accelerate_3d", Type: cty.Bool, Required: false},
"gfx_efi_resolution": &hcldec.AttrSpec{Name: "gfx_efi_resolution", Type: cty.String, Required: false},
"guest_os_type": &hcldec.AttrSpec{Name: "guest_os_type", Type: cty.String, Required: false},
"hard_drive_discard": &hcldec.AttrSpec{Name: "hard_drive_discard", Type: cty.Bool, Required: false},
"hard_drive_interface": &hcldec.AttrSpec{Name: "hard_drive_interface", Type: cty.String, Required: false},
@@ -267,6 +278,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"nvme_port_count": &hcldec.AttrSpec{Name: "nvme_port_count", Type: cty.Number, Required: false},
"hard_drive_nonrotational": &hcldec.AttrSpec{Name: "hard_drive_nonrotational", Type: cty.Bool, Required: false},
"iso_interface": &hcldec.AttrSpec{Name: "iso_interface", Type: cty.String, Required: false},
"disk_additional_size": &hcldec.AttrSpec{Name: "disk_additional_size", Type: cty.List(cty.Number), Required: false},
"keep_registered": &hcldec.AttrSpec{Name: "keep_registered", Type: cty.Bool, Required: false},
"skip_export": &hcldec.AttrSpec{Name: "skip_export", Type: cty.Bool, Required: false},
"vm_name": &hcldec.AttrSpec{Name: "vm_name", Type: cty.String, Required: false},
+52 -31
View File
@@ -22,31 +22,50 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
driver := state.Get("driver").(vboxcommon.Driver)
ui := state.Get("ui").(packersdk.Ui)
vmName := state.Get("vmName").(string)
format := "VDI"
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, strings.ToLower(format)))
command := []string{
"createhd",
"--filename", path,
"--size", strconv.FormatUint(uint64(config.DiskSize), 10),
"--format", format,
"--variant", "Standard",
// The main disk and additional disks
diskFullPaths := []string{}
diskSizes := []uint{config.DiskSize}
if len(config.AdditionalDiskSize) == 0 {
// If there are no additional disks, use disk naming as before
diskFullPaths = append(diskFullPaths, filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, strings.ToLower(format))))
} else {
// If there are additional disks, use consistent naming with numbers
diskFullPaths = append(diskFullPaths, filepath.Join(config.OutputDir, fmt.Sprintf("%s-0.%s", config.VMName, strings.ToLower(format))))
for i, diskSize := range config.AdditionalDiskSize {
path := filepath.Join(config.OutputDir, fmt.Sprintf("%s-%d.%s", config.VMName, i+1, strings.ToLower(format)))
diskFullPaths = append(diskFullPaths, path)
diskSizes = append(diskSizes, diskSize)
}
}
ui.Say("Creating hard drive...")
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
// Create all required disks
for i := range diskFullPaths {
ui.Say(fmt.Sprintf("Creating hard drive %s with size %d MiB...", diskFullPaths[i], diskSizes[i]))
command := []string{
"createhd",
"--filename", diskFullPaths[i],
"--size", strconv.FormatUint(uint64(diskSizes[i]), 10),
"--format", format,
"--variant", "Standard",
}
err := driver.VBoxManage(command...)
if err != nil {
err := fmt.Errorf("Error creating hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
// Add the IDE controller so we can later attach the disk.
// When the hard disk controller is not IDE, this device is still used
// by VirtualBox to deliver the guest extensions.
err = driver.VBoxManage("storagectl", vmName, "--name", "IDE Controller", "--add", "ide")
err := driver.VBoxManage("storagectl", vmName, "--name", "IDE Controller", "--add", "ide")
if err != nil {
err := fmt.Errorf("Error creating disk controller: %s", err)
state.Put("error", err)
@@ -116,21 +135,23 @@ func (s *stepCreateDisk) Run(ctx context.Context, state multistep.StateBag) mult
discard = "on"
}
command = []string{
"storageattach", vmName,
"--storagectl", controllerName,
"--port", "0",
"--device", "0",
"--type", "hdd",
"--medium", path,
"--nonrotational", nonrotational,
"--discard", discard,
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
for i := range diskFullPaths {
command := []string{
"storageattach", vmName,
"--storagectl", controllerName,
"--port", strconv.FormatUint(uint64(i), 10),
"--device", "0",
"--type", "hdd",
"--medium", diskFullPaths[i],
"--nonrotational", nonrotational,
"--discard", discard,
}
if err := driver.VBoxManage(command...); err != nil {
err := fmt.Errorf("Error attaching hard drive: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
}
return multistep.ActionContinue
+21 -2
View File
@@ -26,7 +26,7 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis
name := config.VMName
commands := make([][]string, 10)
commands := make([][]string, 14)
commands[0] = []string{
"createvm", "--name", name,
"--ostype", config.GuestOSType, "--register",
@@ -59,7 +59,26 @@ func (s *stepCreateVM) Run(ctx context.Context, state multistep.StateBag) multis
"--nictype6", config.NICType,
"--nictype7", config.NICType,
"--nictype8", config.NICType}
commands[9] = []string{"modifyvm", name, "--graphicscontroller", config.GfxController}
commands[9] = []string{"modifyvm", name, "--graphicscontroller", config.GfxController, "--vram", strconv.FormatUint(uint64(config.GfxVramSize), 10)}
if config.RTCTimeBase == "UTC" {
commands[10] = []string{"modifyvm", name, "--rtcuseutc", "on"}
} else {
commands[10] = []string{"modifyvm", name, "--rtcuseutc", "off"}
}
if config.NestedVirt == true {
commands[11] = []string{"modifyvm", name, "--nested-hw-virt", "on"}
} else {
commands[11] = []string{"modifyvm", name, "--nested-hw-virt", "off"}
}
if config.GfxAccelerate3D {
commands[12] = []string{"modifyvm", name, "--accelerate3d", "on"}
} else {
commands[12] = []string{"modifyvm", name, "--accelerate3d", "off"}
}
if config.GfxEFIResolution != "" {
commands[13] = []string{"setextradata", name, "VBoxInternal2/EfiGraphicsResolution", config.GfxEFIResolution}
}
ui.Say("Creating virtual machine...")
for _, command := range commands {
+1 -1
View File
@@ -130,7 +130,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&communicator.StepConnect{
Config: &b.config.CommConfig.Comm,
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.SSHHost),
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.Host()),
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
SSHPort: vboxcommon.CommPort,
WinRMPort: vboxcommon.CommPort,
+1 -1
View File
@@ -114,7 +114,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
},
&communicator.StepConnect{
Config: &b.config.CommConfig.Comm,
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.SSHHost),
Host: vboxcommon.CommHost(b.config.CommConfig.Comm.Host()),
SSHConfig: b.config.CommConfig.Comm.SSHConfigFunc(),
SSHPort: vboxcommon.CommPort,
WinRMPort: vboxcommon.CommPort,
+8 -16
View File
@@ -389,7 +389,7 @@ func TestBuild(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
defer tt.cleanup(t)
run(t, tt.args, tt.expectedCode)
tt.fileCheck.verify(t)
tt.fileCheck.verify(t, "")
})
}
}
@@ -732,7 +732,7 @@ func TestHCL2PostProcessorForceFlag(t *testing.T) {
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
fCheck.verify(t)
fCheck.verify(t, "")
// Second build should override previous manifest
UUID, _ = uuid.GenerateUUID()
@@ -766,7 +766,7 @@ func TestHCL2PostProcessorForceFlag(t *testing.T) {
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
fCheck.verify(t)
fCheck.verify(t, "")
}
func TestBuildCommand_HCLOnlyExceptOptions(t *testing.T) {
@@ -887,19 +887,19 @@ func (fc fileCheck) expectedFiles() []string {
return expected
}
func (fc fileCheck) verify(t *testing.T) {
func (fc fileCheck) verify(t *testing.T, dir string) {
for _, f := range fc.expectedFiles() {
if !fileExists(f) {
t.Errorf("Expected to find %s", f)
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
t.Errorf("Expected to find %s: %v", f, err)
}
}
for _, f := range fc.notExpected {
if fileExists(f) {
if _, err := os.Stat(filepath.Join(dir, f)); err == nil {
t.Errorf("Expected to not find %s", f)
}
}
for file, expectedContent := range fc.expectedContent {
content, err := ioutil.ReadFile(file)
content, err := ioutil.ReadFile(filepath.Join(dir, file))
if err != nil {
t.Fatalf("ioutil.ReadFile: %v", err)
}
@@ -909,14 +909,6 @@ func (fc fileCheck) verify(t *testing.T) {
}
}
// fileExists returns true if the filename is found
func fileExists(filename string) bool {
if _, err := os.Stat(filename); err == nil {
return true
}
return false
}
// testCoreConfigBuilder creates a packer CoreConfig that has a file builder
// available. This allows us to test a builder that writes files to disk.
func testCoreConfigBuilder(t *testing.T) *packer.CoreConfig {
+2 -2
View File
@@ -161,12 +161,12 @@ func (va *FormatArgs) AddFlagSets(flags *flag.FlagSet) {
flags.BoolVar(&va.Check, "check", false, "check if the input is formatted")
flags.BoolVar(&va.Diff, "diff", false, "display the diff of formatting changes")
flags.BoolVar(&va.Write, "write", true, "overwrite source files instead of writing to stdout")
flags.BoolVar(&va.Recursive, "recursive", false, "Also process files in subdirectories")
va.MetaArgs.AddFlagSets(flags)
}
// FormatArgs represents a parsed cli line for `packer fmt`
type FormatArgs struct {
MetaArgs
Check, Diff, Write bool
Check, Diff, Write, Recursive bool
}
+2
View File
@@ -87,6 +87,8 @@ func TestHelperProcess(*testing.T) {
switch cmd {
case "console":
os.Exit((&ConsoleCommand{Meta: commandMeta()}).Run(args))
case "fmt":
os.Exit((&FormatCommand{Meta: commandMeta()}).Run(args))
case "inspect":
os.Exit((&InspectCommand{Meta: commandMeta()}).Run(args))
case "build":
+10 -6
View File
@@ -48,9 +48,10 @@ func (c *FormatCommand) RunContext(ctx context.Context, cla *FormatArgs) int {
}
formatter := hclutils.HCL2Formatter{
ShowDiff: cla.Diff,
Write: cla.Write,
Output: os.Stdout,
ShowDiff: cla.Diff,
Write: cla.Write,
Output: os.Stdout,
Recursive: cla.Recursive,
}
bytesModified, diags := formatter.Format(cla.Path)
@@ -90,6 +91,8 @@ Options:
-write=false Don't write to source files
(always disabled if using -check)
-recursive Also process files in subdirectories. By default, only the
given directory (or current directory) is processed.
`
return strings.TrimSpace(helpText)
@@ -105,8 +108,9 @@ func (*FormatCommand) AutocompleteArgs() complete.Predictor {
func (*FormatCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-check": complete.PredictNothing,
"-diff": complete.PredictNothing,
"-write": complete.PredictNothing,
"-check": complete.PredictNothing,
"-diff": complete.PredictNothing,
"-write": complete.PredictNothing,
"-recursive": complete.PredictNothing,
}
}
+124
View File
@@ -1,10 +1,15 @@
package command
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/stretchr/testify/assert"
)
@@ -50,3 +55,122 @@ func TestFmt_unfomattedTemlateDirectory(t *testing.T) {
fatalCommand(t, c.Meta)
}
}
const (
unformattedHCL = `
ami_filter_name ="amzn2-ami-hvm-*-x86_64-gp2"
ami_filter_owners =[ "137112412989" ]
`
formattedHCL = `
ami_filter_name = "amzn2-ami-hvm-*-x86_64-gp2"
ami_filter_owners = ["137112412989"]
`
)
func TestFmt_Recursive(t *testing.T) {
tests := []struct {
name string
formatArgs []string // arguments passed to format
alreadyPresentContent map[string]string
fileCheck
}{
{
name: "nested formats recursively",
formatArgs: []string{"-recursive=true"},
alreadyPresentContent: map[string]string{
"foo/bar/baz.pkr.hcl": unformattedHCL,
"foo/bar/baz/woo.pkrvars.hcl": unformattedHCL,
"potato": unformattedHCL,
"foo/bar/potato": unformattedHCL,
"bar.pkr.hcl": unformattedHCL,
"-": unformattedHCL,
},
fileCheck: fileCheck{
expectedContent: map[string]string{
"foo/bar/baz.pkr.hcl": formattedHCL,
"foo/bar/baz/woo.pkrvars.hcl": formattedHCL,
"potato": unformattedHCL,
"foo/bar/potato": unformattedHCL,
"bar.pkr.hcl": formattedHCL,
"-": unformattedHCL,
}},
},
{
name: "nested no recursive format",
formatArgs: []string{},
alreadyPresentContent: map[string]string{
"foo/bar/baz.pkr.hcl": unformattedHCL,
"foo/bar/baz/woo.pkrvars.hcl": unformattedHCL,
"bar.pkr.hcl": unformattedHCL,
"-": unformattedHCL,
},
fileCheck: fileCheck{
expectedContent: map[string]string{
"foo/bar/baz.pkr.hcl": unformattedHCL,
"foo/bar/baz/woo.pkrvars.hcl": unformattedHCL,
"bar.pkr.hcl": formattedHCL,
"-": unformattedHCL,
}},
},
}
c := &FormatCommand{
Meta: testMeta(t),
}
testDir := "test-fixtures/fmt"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDirectory := mustString(ioutil.TempDir(testDir, "test-dir-*"))
defer os.RemoveAll(tempDirectory)
createFiles(tempDirectory, tt.alreadyPresentContent)
testArgs := append(tt.formatArgs, tempDirectory)
if code := c.Run(testArgs); code != 0 {
ui := c.Meta.Ui.(*packersdk.BasicUi)
out := ui.Writer.(*bytes.Buffer)
err := ui.ErrorWriter.(*bytes.Buffer)
t.Fatalf(
"Bad exit code for test case: %s.\n\nStdout:\n\n%s\n\nStderr:\n\n%s",
tt.name,
out.String(),
err.String())
}
tt.fileCheck.verify(t, tempDirectory)
})
}
}
func Test_fmt_pipe(t *testing.T) {
tc := []struct {
piped string
command []string
env []string
expected string
}{
{unformattedHCL, []string{"fmt", "-"}, nil, formattedHCL},
}
for _, tc := range tc {
t.Run(fmt.Sprintf("echo %q | packer %s", tc.piped, tc.command), func(t *testing.T) {
p := helperCommand(t, tc.command...)
p.Stdin = strings.NewReader(tc.piped)
p.Env = append(p.Env, tc.env...)
fmt.Println(fmt.Sprintf("Path: %s", p.Path))
bs, err := p.Output()
if err != nil {
t.Fatalf("Error occurred running command %v: %s", err, bs)
}
if diff := cmp.Diff(tc.expected, string(bs)); diff != "" {
t.Fatalf("Error in diff: %s", diff)
}
})
}
}
-12
View File
@@ -24,7 +24,6 @@ import (
azuredtlbuilder "github.com/hashicorp/packer/builder/azure/dtl"
cloudstackbuilder "github.com/hashicorp/packer/builder/cloudstack"
digitaloceanbuilder "github.com/hashicorp/packer/builder/digitalocean"
dockerbuilder "github.com/hashicorp/packer/builder/docker"
filebuilder "github.com/hashicorp/packer/builder/file"
googlecomputebuilder "github.com/hashicorp/packer/builder/googlecompute"
hcloudbuilder "github.com/hashicorp/packer/builder/hcloud"
@@ -73,11 +72,6 @@ import (
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
digitaloceanimportpostprocessor "github.com/hashicorp/packer/post-processor/digitalocean-import"
dockerimportpostprocessor "github.com/hashicorp/packer/post-processor/docker-import"
dockerpushpostprocessor "github.com/hashicorp/packer/post-processor/docker-push"
dockersavepostprocessor "github.com/hashicorp/packer/post-processor/docker-save"
dockertagpostprocessor "github.com/hashicorp/packer/post-processor/docker-tag"
exoscaleimportpostprocessor "github.com/hashicorp/packer/post-processor/exoscale-import"
googlecomputeexportpostprocessor "github.com/hashicorp/packer/post-processor/googlecompute-export"
googlecomputeimportpostprocessor "github.com/hashicorp/packer/post-processor/googlecompute-import"
manifestpostprocessor "github.com/hashicorp/packer/post-processor/manifest"
@@ -125,7 +119,6 @@ var Builders = map[string]packersdk.Builder{
"azure-dtl": new(azuredtlbuilder.Builder),
"cloudstack": new(cloudstackbuilder.Builder),
"digitalocean": new(digitaloceanbuilder.Builder),
"docker": new(dockerbuilder.Builder),
"file": new(filebuilder.Builder),
"googlecompute": new(googlecomputebuilder.Builder),
"hcloud": new(hcloudbuilder.Builder),
@@ -196,11 +189,6 @@ var PostProcessors = map[string]packersdk.PostProcessor{
"checksum": new(checksumpostprocessor.PostProcessor),
"compress": new(compresspostprocessor.PostProcessor),
"digitalocean-import": new(digitaloceanimportpostprocessor.PostProcessor),
"docker-import": new(dockerimportpostprocessor.PostProcessor),
"docker-push": new(dockerpushpostprocessor.PostProcessor),
"docker-save": new(dockersavepostprocessor.PostProcessor),
"docker-tag": new(dockertagpostprocessor.PostProcessor),
"exoscale-import": new(exoscaleimportpostprocessor.PostProcessor),
"googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor),
"googlecompute-import": new(googlecomputeimportpostprocessor.PostProcessor),
"manifest": new(manifestpostprocessor.PostProcessor),
+8
View File
@@ -39,3 +39,11 @@ func (c *configDirSingleton) dir(key string) string {
c.dirs[key] = mustString(ioutil.TempDir("", "pkr-test-cfg-dir-"+key))
return c.dirs[key]
}
// fileExists returns true if the filename is found
func fileExists(filename string) bool {
if _, err := os.Stat(filename); err == nil {
return true
}
return false
}
+60
View File
@@ -0,0 +1,60 @@
package command
import (
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
// Previously core-bundled components, split into their own plugins but
// still vendored with Packer for now. Importing as library instead of
// forcing use of packer init, until packer v1.8.0
exoscaleimportpostprocessor "github.com/exoscale/packer-plugin-exoscale/post-processor/exoscale-import"
dockerbuilder "github.com/hashicorp/packer-plugin-docker/builder/docker"
dockerimportpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-import"
dockerpushpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-push"
dockersavepostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-save"
dockertagpostprocessor "github.com/hashicorp/packer-plugin-docker/post-processor/docker-tag"
)
// VendoredBuilders are builder components that were once bundled with the
// Packer core, but are now being imported from their counterpart plugin repos
var VendoredBuilders = map[string]packersdk.Builder{
"docker": new(dockerbuilder.Builder),
}
// VendoredProvisioners are provisioner components that were once bundled with the
// Packer core, but are now being imported from their counterpart plugin repos
var VendoredProvisioners = map[string]packersdk.Provisioner{}
// VendoredPostProcessors are post-processor components that were once bundled with the
// Packer core, but are now being imported from their counterpart plugin repos
var VendoredPostProcessors = map[string]packersdk.PostProcessor{
"docker-import": new(dockerimportpostprocessor.PostProcessor),
"docker-push": new(dockerpushpostprocessor.PostProcessor),
"docker-save": new(dockersavepostprocessor.PostProcessor),
"docker-tag": new(dockertagpostprocessor.PostProcessor),
"exoscale-import": new(exoscaleimportpostprocessor.PostProcessor),
}
// Upon init lets load up any plugins that were vendored manually into the default
// set of plugins.
func init() {
for k, v := range VendoredBuilders {
if _, ok := Builders[k]; ok {
continue
}
Builders[k] = v
}
for k, v := range VendoredProvisioners {
if _, ok := Provisioners[k]; ok {
continue
}
Provisioners[k] = v
}
for k, v := range VendoredPostProcessors {
if _, ok := PostProcessors[k]; ok {
continue
}
PostProcessors[k] = v
}
}
+6 -6
View File
@@ -25,7 +25,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f
github.com/digitalocean/godo v1.11.1
github.com/exoscale/egoscale v0.18.1
github.com/exoscale/packer-plugin-exoscale v0.1.0
github.com/fatih/camelcase v1.0.0
github.com/fatih/structtag v1.0.0
github.com/go-ini/ini v1.25.4
@@ -50,6 +50,7 @@ require (
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/hcl/v2 v2.8.0
github.com/hashicorp/packer-plugin-docker v0.0.2
github.com/hashicorp/packer-plugin-sdk v0.0.14
github.com/hashicorp/vault/api v1.0.4
github.com/hetznercloud/hcloud-go v1.15.1
@@ -63,7 +64,6 @@ require (
github.com/mitchellh/cli v1.1.0
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.4.0
github.com/mitchellh/panicwrap v1.0.0
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784
@@ -77,7 +77,7 @@ require (
github.com/profitbricks/profitbricks-sdk-go v4.0.2+incompatible
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7
github.com/shirou/gopsutil v3.21.1+incompatible
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible
github.com/ucloud/ucloud-sdk-go v0.16.3
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6
@@ -88,13 +88,13 @@ require (
github.com/yandex-cloud/go-sdk v0.0.0-20200921111412-ef15ded2014c
github.com/zclconf/go-cty v1.7.0
github.com/zclconf/go-cty-yaml v1.0.1
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/mobile v0.0.0-20201208152944-da85bec010a2
golang.org/x/mod v0.3.0
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11
golang.org/x/net v0.0.0-20210119194325-5f4716e94777
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
golang.org/x/tools v0.0.0-20201111133315-69daaf961d65
google.golang.org/api v0.32.0
google.golang.org/grpc v1.32.0
+59 -64
View File
@@ -62,7 +62,6 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtO
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8=
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
github.com/Azure/go-autorest/autorest/validation v0.2.0 h1:15vMO4y76dehZSq7pAaOLQxC6dZYsSrj2GQpflyM/L4=
github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI=
github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac=
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
@@ -70,7 +69,6 @@ github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1Gn
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4 h1:pSm8mp0T2OH2CPmPDPtwHPr3VAQaOwVF/JbllOPP4xA=
github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.0.0-20191115201650-bad6df29494a h1:3FwiePtHk5YJrooV799oo5jIfsgRdES25VdngJM03dU=
github.com/Azure/go-ntlmssp v0.0.0-20191115201650-bad6df29494a/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
@@ -93,11 +91,9 @@ github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e h1:/8w
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f h1:jI4DIE5Vf4oRaHfthB0oRhU+yuYuoOTurDzwAlskP00=
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd h1:S3Fr6QnkpW9VRjiEY4psQHhhbbahASuNVj52YIce7lI=
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
github.com/antchfx/xpath v1.1.11 h1:WOFtK8TVAjLm3lbgqeP0arlHpvCEeTANeWZ/csPpJkQ=
github.com/antchfx/xpath v1.1.11/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2ptWsJn8p8RDufBjBDKIxLU7BTYGOM=
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0 h1:JaCC8jz0zdMLk2m+qCCVLLLM/PL93p84w4pK3aJWj60=
github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
@@ -114,11 +110,9 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43 h1:ePCAQPf5tUc5IMcUvu6euhSGna7jzs7eiXtJXHig6Zc=
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43/go.mod h1:S6puKjZ9ZeqUPBv2hEBnMZGcM2J6mOsDRQcmxkMAND0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@@ -127,7 +121,6 @@ github.com/aws/aws-sdk-go v1.26.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.30.8/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.36.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.36.5 h1:SouM2ar1A8f+3DYWW622sDdqkkZAO3ha4j8GQjiPLFg=
github.com/aws/aws-sdk-go v1.36.5/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.37.15 h1:W7l7gLLMcYRlg6a+uvf3Zz4jYwdqYzhe5ymqwWoOhp4=
github.com/aws/aws-sdk-go v1.37.15/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
@@ -155,21 +148,21 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE=
github.com/deepmap/oapi-codegen v1.3.11/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0=
github.com/deepmap/oapi-codegen v1.5.1 h1:Xx8/OynzWhQeKFWr182hg6Ja2evS1gcqdja7qMn05yU=
github.com/deepmap/oapi-codegen v1.5.1/go.mod h1:Eb1vtV3f58zvm37CJV4UAQ1bECb0fgAVvTdonC1ftJg=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437 h1:phR13shVFOIpa1pnLBmewI9p16NEladLPvVylLPeexo=
github.com/digitalocean/go-libvirt v0.0.0-20190626172931-4d226dd6c437/go.mod h1:PRcPVAAma6zcLpFd4GZrjR/MRpood3TamjKI2m/z/Uw=
github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1 h1:j6vGflaQ2T7yOWqVgPdiRF73j/U2Zmpbbzab8nyDCRQ=
github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1/go.mod h1:QS1XzqZLcDniNYrN7EZefq3wIyb/M2WmJbql4ZKoc1Q=
github.com/digitalocean/go-libvirt v0.0.0-20210108193637-3a8ae49ba8cd h1:+96Lbk3f8glkOcsRdy3Nubga8pE40kor2OgxDzVGNZM=
github.com/digitalocean/go-libvirt v0.0.0-20210108193637-3a8ae49ba8cd/go.mod h1:gtar3MgGsIO64GgphCHw1cbyxSI6qEuTIm9+izMmlfk=
github.com/digitalocean/go-libvirt v0.0.0-20210112203132-25518eb2c840 h1:F3RVNV8SLLNhkNFcbDTgD3wAPMcrMJW6xjjI0JXy9z8=
github.com/digitalocean/go-libvirt v0.0.0-20210112203132-25518eb2c840/go.mod h1:gtar3MgGsIO64GgphCHw1cbyxSI6qEuTIm9+izMmlfk=
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8 h1:N7nH2py78LcMqYY3rZjjrsX6N7uCN7sjvaosgpXN9Ow=
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8/go.mod h1:/YnlngP1PARC0SKAZx6kaAEMOp8bNTQGqS+Ka3MctNI=
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f h1:BYkBJhHxUJJn27mhqfqWycWaEOWv9JQqLgQ2pOFJMqE=
github.com/digitalocean/go-qemu v0.0.0-20201211181942-d361e7b4965f/go.mod h1:y4Eq3ZfZQFWQwVyW0qvgo5seXUIq2C7BlHsdE+xtXL4=
@@ -187,29 +180,36 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI=
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
github.com/exoscale/egoscale v0.43.1 h1:Lhr0UOfg3t3Y56yh1DsYCjQuUHqFvsC8iUVqvub8+0Q=
github.com/exoscale/egoscale v0.43.1/go.mod h1:mpEXBpROAa/2i5GC0r33rfxG+TxSEka11g1PIXt9+zc=
github.com/exoscale/packer-plugin-exoscale v0.1.0 h1:p4ymqF1tNiTuxgSdnEjGqXehMdDQbV7BPaLsMxGav24=
github.com/exoscale/packer-plugin-exoscale v0.1.0/go.mod h1:ZmJRkxsAlmEsVYOMxYPupDkax54uZ+ph0h3W59aIMZ8=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fatih/structtag v1.0.0 h1:pTHj65+u3RKWYPSGaU290FpI/dXxTaHdVwVwbcPKmEc=
github.com/fatih/structtag v1.0.0/go.mod h1:IKitwq45uXL/yqi5mYghiD3w9H6eTOvI9vnk8tXMphA=
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/getkin/kin-openapi v0.37.0/go.mod h1:ZJSfy1PxJv2QQvH9EdBj3nupRTVvV42mkW6zKUlRBwk=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v1.5.1/go.mod h1:REp24E+25iKvxgeTfHmdUoL5x15kBiDBlnIl5bCwe2k=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=
github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
@@ -221,8 +221,9 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofrs/flock v0.7.3 h1:I0EKY9l8HZCXTMYC4F80vwT6KNypV9uYKP3Alm/hjmQ=
github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
@@ -255,6 +256,7 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -300,7 +302,6 @@ github.com/gophercloud/gophercloud v0.12.0 h1:mZrie07npp6ODiwHZolTicr5jV8Ogn43Av
github.com/gophercloud/gophercloud v0.12.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+GmkaVO5sN+k8YONibXyDO8RlsC+1bs=
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -336,11 +337,9 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@@ -353,7 +352,6 @@ github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
@@ -373,7 +371,6 @@ github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+d
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
@@ -385,7 +382,6 @@ github.com/hashicorp/hcl/v2 v2.8.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yI
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
@@ -397,23 +393,17 @@ github.com/hashicorp/packer v1.6.7-0.20210125170305-539638b0f951/go.mod h1:Z3eun
github.com/hashicorp/packer v1.6.7-0.20210126105722-aef4ced967ec/go.mod h1:2+Vo/c/fA+TD9yFc/h9jQMFm4yG+IymQIr0OdJJOPiE=
github.com/hashicorp/packer v1.6.7-0.20210208125835-f616955ebcb6/go.mod h1:7f5ZpTTRG53rQ58BcTADuTnpiBcB3wapuxl4sF2sGMM=
github.com/hashicorp/packer v1.6.7-0.20210217093213-201869d627bf/go.mod h1:+EWPPcqee4h8S/y913Dnta1eJkgiqsGXBQgB75A2qV0=
github.com/hashicorp/packer-plugin-sdk v0.0.6 h1:BN2G4APXSMvDURFdnk+6DspwsU83pZeMsbEur7NmGsA=
github.com/hashicorp/packer-plugin-sdk v0.0.6/go.mod h1:Nvh28f+Jmpp2rcaN79bULTouNkGNDRfHckhHKTAXtyU=
github.com/hashicorp/packer-plugin-docker v0.0.2 h1:j/hQTogaN2pZfZohlZTRu5YvNZg2/qtYYHkxPBxv2Oo=
github.com/hashicorp/packer-plugin-docker v0.0.2/go.mod h1:A2p9qztS4n88KsNF+qBM7BWw2HndW636GpFIjNSvbKM=
github.com/hashicorp/packer-plugin-sdk v0.0.6/go.mod h1:Nvh28f+Jmpp2rcaN79bULTouNkGNDRfHckhHKTAXtyU=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210111224258-fd30ebb797f0/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210120105339-f6fd68d2570a h1:QbS+UBmK9DZuEDPodi1pCiS66dLYI3rmUX/cowNopsk=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210120105339-f6fd68d2570a/go.mod h1:exN0C+Pe+3zu18l4nxueNjX5cfmslxUX/m/xk4IVmZQ=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210122130548-45a6ca0a9365 h1:u7DeYY9ukhSZpLE11qCFU8pxnO+YM2/85wwVXHJZdRE=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210122130548-45a6ca0a9365/go.mod h1:K7VsU0lfJBDyiUrSNnS/j+zMxSRwwH9WC9QvHv32KsU=
github.com/hashicorp/packer-plugin-sdk v0.0.10-0.20210126105622-8e1648006d93 h1:3wFACjFiBkF5sZrai0zvcWv2fHIgLa4g6ZXxbqngBhs=
github.com/hashicorp/packer-plugin-sdk v0.0.10-0.20210126105622-8e1648006d93/go.mod h1:AtWQLNfpn7cgH2SmZ1PTedwqNOhiPvzcuKfH5sDvIQ0=
github.com/hashicorp/packer-plugin-sdk v0.0.11 h1:nUurEGaJtpVDyg94bKC2xOXWf1TqQ+++GovFUnP5REI=
github.com/hashicorp/packer-plugin-sdk v0.0.11/go.mod h1:GNb0WNs7zibb8vzUZce1As64z2AW0FEMwhe2J7/NW5I=
github.com/hashicorp/packer-plugin-sdk v0.0.12 h1:eagxsrborfhc0E8zvDVCiRE9pQkjBw/6jm23jp0D8RU=
github.com/hashicorp/packer-plugin-sdk v0.0.12/go.mod h1:hs82OYeufirGG6KRENMpjBWomnIlte99X6wXAPThJ5I=
github.com/hashicorp/packer-plugin-sdk v0.0.14 h1:42WOZLmIbAYYC1WXxtlrQZN+fFdysVvTmj3jtoI6gOU=
github.com/hashicorp/packer-plugin-sdk v0.0.14/go.mod h1:tNb3XzJPnjMl3QuUdKmF47B5ImerdTakalHzUAvW0aw=
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.2 h1:yJoyfZXo4Pk2p/M/viW+YLibBFiIbKoP79gu7kDAFP0=
github.com/hashicorp/serf v0.9.2/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
@@ -429,6 +419,9 @@ github.com/hetznercloud/hcloud-go v1.15.1/go.mod h1:8lR3yHBHZWy2uGcUi9Ibt4UOoop2
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4 h1:mSmyzhwBeQt2TlHbsXYLona9pwjWAvYGwQJ2Cq/k3VE=
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4/go.mod h1:yNUVHSleURKSaYUKq4Wx0i/vjCen2aq7CvPyHd/Vj2Q=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=
github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961 h1:a2/K4HRhg31A5vafiz5yYiGMjaCxwRpyjJStfVquKds=
github.com/jdcloud-api/jdcloud-sdk-go v1.9.1-0.20190605102154-3d81a50ca961/go.mod h1:UrKjuULIWLjHFlG6aSPunArE5QX57LftMmStAZJBEX8=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
@@ -442,28 +435,22 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbgAPE/3dqlvUKzRHMNuTBKKUb6OeR/k=
github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77 h1:rJnR80lkojFgjdg/oQPhbZoY8t8uM51XMz8DrJrjabk=
github.com/klauspost/compress v0.0.0-20160131094358-f86d2e6d8a77/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.6 h1:EgWPCW6O3n1D5n99Zq3xXBt9uCwRGvpwGOusOLNBRSQ=
github.com/klauspost/compress v1.11.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817 h1:/7pPahIC+GoCm/euDCi2Pm29bAj9tc6TcK4Zcc8D3WI=
github.com/klauspost/cpuid v0.0.0-20160106104451-349c67577817/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f h1:UD9YLTi2aBhdOOThzatodQ/pGd9nd5255swS+UzHZj4=
github.com/klauspost/crc32 v0.0.0-20160114101742-999f3125931f/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/crc32 v1.2.0 h1:0VuyqOCruD33/lJ/ojXNvzVyl8Zr5zdTmj9l9qLZ86I=
github.com/klauspost/crc32 v1.2.0/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
@@ -471,11 +458,9 @@ github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec h1:PYqF3Tiz2W2Ag0e
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169 h1:YUrU1/jxRqnt0PSrKj1Uj/wEjk/fjnE80QFfi2Zlj7Q=
github.com/kr/fs v0.0.0-20131111012553-2788f0dbd169/go.mod h1:glhvuHOU9Hy7/8PwwdtnarXqLagOX0b/TbZx2zLMqEg=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -484,37 +469,41 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/linode/linodego v0.14.0 h1:0APKMjiVGyry2TTUVDiok72H6cWpFNMMrFWBFn14aFU=
github.com/linode/linodego v0.14.0/go.mod h1:2ce3S00NrDqJfp4i55ZuSlT0U3cKNELNYACWBPI8Tnw=
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9 h1:SmVbOZFWAlyQshuMfOkiAx1f5oUTsOGG5IXplAEYeeM=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL43sCxI2jhPLRv73oVVOjEKZjKkflyqxg=
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
github.com/masterzen/winrm v0.0.0-20201030141608-56ca5c5f2380 h1:uKhPH5dYpx3Z8ZAnaTGfGZUiHOWa5p5mdG8wZlh+tLo=
github.com/masterzen/winrm v0.0.0-20201030141608-56ca5c5f2380/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-tty v0.0.0-20191112051231-74040eebce08 h1:8YAWbq7rJqfbc6IaAvA2eCQuOQvf6Bs4vHKcOyWw//E=
github.com/mattn/go-tty v0.0.0-20191112051231-74040eebce08/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
@@ -528,7 +517,6 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.3 h1:gqwbsGvc0jbhAPW/26WfEoSiPANAVlR49AAVdvaTjI4=
github.com/mitchellh/go-testing-interface v1.0.3/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -538,7 +526,6 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZX
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@@ -553,11 +540,9 @@ github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784 h1:+DAetXqxv/
github.com/mitchellh/prefixedio v0.0.0-20151214002211-6e6954073784/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -567,7 +552,6 @@ github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQ
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b h1:LGItPaClbzopugAomw5VFKnG3h1dUr9QW5KOU+m8gu0=
github.com/olekukonko/tablewriter v0.0.0-20180105111133-96aac992fc8b/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/oracle/oci-go-sdk v18.0.0+incompatible h1:FLV4KixsVfF3rwyVTMI6Ryp/Q+OSb9sR5TawbfjFLN4=
github.com/oracle/oci-go-sdk v18.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
@@ -601,6 +585,7 @@ github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -613,34 +598,32 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.1+incompatible h1:2LwXWdbjXwyDgq26Yy/OT4xozlpmssQfy/rtfhWb0bY=
github.com/shirou/gopsutil v3.21.1+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible h1:bs+0lcG4RELNbE8PsBC9oaPP0/qExr0DuEGnZyocm84=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -648,7 +631,6 @@ github.com/ucloud/ucloud-sdk-go v0.16.3 h1:DCh4A5vSxFr3EvtvJL+g0Ehy4hSlEkMpQmEvx
github.com/ucloud/ucloud-sdk-go v0.16.3/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw=
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6 h1:FAWNiqocJ04wC4Znj7Ax4PGWstZijayO6ifuHHvb+vI=
github.com/ufilesdk-dev/ufile-gosdk v0.0.0-20190830075812-b4dbc4ef43a6/go.mod h1:R5FMQxkQ+QK/9Vz+jfnJP4rZIktYrRcWmuAnbOSkROI=
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1 h1:U6ufy3mLDgg9RYupntOvAF7xCmNNquyKaYaaVHo1Nnk=
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.2.4 h1:cTciPbZ/VSOzCLKclmssnfQ/jyoVyOcJ3aoJyUV1Urc=
github.com/ugorji/go v1.2.4/go.mod h1:EuaSCk8iZMdIspsu6HXH7X2UGKw1ezO4wCfGszGmmo4=
@@ -656,6 +638,10 @@ github.com/ugorji/go/codec v1.2.4 h1:C5VurWRRCKjuENsbM6GYVw8W++WVW9rSxoACKIvxzz8
github.com/ugorji/go/codec v1.2.4/go.mod h1:bWBu1+kIRWcF8uMklKaJrR6fTWQOwAlrIzX22pHwryA=
github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
@@ -695,14 +681,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -756,6 +746,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -775,8 +766,9 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -812,12 +804,14 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -836,11 +830,13 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -849,8 +845,9 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
@@ -972,7 +969,6 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200914193844-75d14daec038 h1:SnvTpXhVDJGFxzZiHbMUZTh3VjU2Vx2feJ7Zfl5+OIY=
google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200918140846-d0d605568037 h1:ujwz1DPMeHwCvo36rK5shXhAzc4GMRecrqQFaMZJBKQ=
google.golang.org/genproto v0.0.0-20200918140846-d0d605568037/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -1012,7 +1008,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -1024,12 +1019,12 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+64 -39
View File
@@ -7,6 +7,8 @@ import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
@@ -14,9 +16,9 @@ import (
)
type HCL2Formatter struct {
ShowDiff, Write bool
Output io.Writer
parser *hclparse.Parser
ShowDiff, Write, Recursive bool
Output io.Writer
parser *hclparse.Parser
}
// NewHCL2Formatter creates a new formatter, ready to format configuration files.
@@ -26,55 +28,77 @@ func NewHCL2Formatter() *HCL2Formatter {
}
}
func isHcl2FileOrVarFile(path string) bool {
if strings.HasSuffix(path, hcl2FileExt) || strings.HasSuffix(path, hcl2VarFileExt) {
return true
}
return false
}
func (f *HCL2Formatter) formatFile(path string, diags hcl.Diagnostics, bytesModified int) (int, hcl.Diagnostics) {
data, err := f.processFile(path)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("encountered an error while formatting %s", path),
Detail: err.Error(),
})
}
bytesModified += len(data)
return bytesModified, diags
}
// Format all HCL2 files in path and return the total bytes formatted.
// If any error is encountered, zero bytes will be returned.
//
// Path can be a directory or a file.
func (f *HCL2Formatter) Format(path string) (int, hcl.Diagnostics) {
var diags hcl.Diagnostics
var bytesModified int
var allHclFiles []string
var diags []*hcl.Diagnostic
if path == "-" {
allHclFiles = []string{"-"}
} else {
hclFiles, _, diags := GetHCL2Files(path, hcl2FileExt, hcl2JsonFileExt)
if diags.HasErrors() {
return 0, diags
}
hclVarFiles, _, diags := GetHCL2Files(path, hcl2VarFileExt, hcl2VarJsonFileExt)
if diags.HasErrors() {
return 0, diags
}
allHclFiles = append(hclFiles, hclVarFiles...)
if len(allHclFiles) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Cannot tell whether %s contains HCL2 configuration data", path),
})
return 0, diags
}
if path == "" {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "path is empty, cannot format",
Detail: "path is empty, cannot format",
})
return bytesModified, diags
}
if f.parser == nil {
f.parser = hclparse.NewParser()
}
var bytesModified int
for _, fn := range allHclFiles {
data, err := f.processFile(fn)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("encountered an error while formatting %s", fn),
Detail: err.Error(),
})
if s, err := os.Stat(path); err != nil || !s.IsDir() {
return f.formatFile(path, diags, bytesModified)
}
fileInfos, err := ioutil.ReadDir(path)
if err != nil {
diag := &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Cannot read hcl directory",
Detail: err.Error(),
}
diags = append(diags, diag)
return bytesModified, diags
}
for _, fileInfo := range fileInfos {
filename := filepath.Join(path, fileInfo.Name())
if fileInfo.IsDir() {
if f.Recursive {
var tempDiags hcl.Diagnostics
var tempBytesModified int
tempBytesModified, tempDiags = f.Format(filename)
bytesModified += tempBytesModified
diags = diags.Extend(tempDiags)
}
continue
}
if isHcl2FileOrVarFile(filename) {
bytesModified, diags = f.formatFile(filename, diags, bytesModified)
}
bytesModified += len(data)
}
return bytesModified, diags
@@ -84,6 +108,7 @@ func (f *HCL2Formatter) Format(path string) (int, hcl.Diagnostics) {
// overwriting the contents of the original when the f.Write is true; a diff of the changes
// will be outputted if f.ShowDiff is true.
func (f *HCL2Formatter) processFile(filename string) ([]byte, error) {
if f.Output == nil {
f.Output = os.Stdout
}
-2
View File
@@ -32,11 +32,9 @@ func TestHCL2Formatter_Format(t *testing.T) {
if diags.HasErrors() {
t.Fatalf("the call to Format failed unexpectedly %s", diags.Error())
}
if buf.String() != "" && tc.FormatExpected == false {
t.Errorf("Format(%q) should contain the name of the formatted file(s), but got %q", tc.Path, buf.String())
}
}
}
+5
View File
@@ -90,6 +90,11 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
if filename != "" {
hclFiles, jsonFiles, moreDiags := GetHCL2Files(filename, hcl2FileExt, hcl2JsonFileExt)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
// here this probably means that the file was not found, let's
// simply leave early.
return nil, diags
}
if len(hclFiles)+len(jsonFiles) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
-1
View File
@@ -57,7 +57,6 @@ func GetHCL2Files(filename, hclSuffix, jsonSuffix string) (hclFiles, jsonFiles [
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Cannot tell wether " + filename + " is a directory",
Detail: err.Error(),
})
return nil, nil, diags
+8 -8
View File
@@ -152,7 +152,7 @@ func wrappedMain() int {
// passed into commands like `packer build`
config, err := loadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading configuration: \n\n%s\n", err)
fmt.Fprintf(os.Stdout, "%s Error loading configuration: \n\n%s\n", ErrorPrefix, err)
return 1
}
@@ -166,7 +166,7 @@ func wrappedMain() int {
cacheDir, err := packersdk.CachePath()
if err != nil {
fmt.Fprintf(os.Stderr, "Error preparing cache directory: \n\n%s\n", err)
fmt.Fprintf(os.Stdout, "%s Error preparing cache directory: \n\n%s\n", ErrorPrefix, err)
return 1
}
log.Printf("[INFO] Setting cache directory: %s", cacheDir)
@@ -187,7 +187,7 @@ func wrappedMain() int {
// Set this so that we don't get colored output in our machine-
// readable UI.
if err := os.Setenv("PACKER_NO_COLOR", "1"); err != nil {
fmt.Fprintf(os.Stderr, "Packer failed to initialize UI: %s\n", err)
fmt.Fprintf(os.Stdout, "%s Packer failed to initialize UI: %s\n", ErrorPrefix, err)
return 1
}
} else {
@@ -202,13 +202,13 @@ func wrappedMain() int {
currentPID := os.Getpid()
backgrounded, err := checkProcess(currentPID)
if err != nil {
fmt.Fprintf(os.Stderr, "cannot determine if process is in "+
"background: %s\n", err)
fmt.Fprintf(os.Stdout, "%s cannot determine if process is in "+
"background: %s\n", ErrorPrefix, err)
}
if backgrounded {
fmt.Fprint(os.Stderr, "Running in background, not using a TTY\n")
fmt.Fprintf(os.Stdout, "%s Running in background, not using a TTY\n", ErrorPrefix)
} else if TTY, err := openTTY(); err != nil {
fmt.Fprintf(os.Stderr, "No tty available: %s\n", err)
fmt.Fprintf(os.Stdout, "%s No tty available: %s\n", ErrorPrefix, err)
} else {
basicUi.TTY = TTY
basicUi.PB = &packer.UiProgressBar{}
@@ -246,7 +246,7 @@ func wrappedMain() int {
}
if err != nil {
fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err)
fmt.Fprintf(os.Stdout, "%s Error executing CLI: %s\n", ErrorPrefix, err)
return 1
}
+3 -3
View File
@@ -305,12 +305,12 @@ func (e ChecksumFileEntry) Arch() string { return e.arch }
//
func (e *ChecksumFileEntry) init(req *Requirement) (err error) {
filename := e.Filename
res := strings.TrimLeft(filename, req.FilenamePrefix())
res := strings.TrimPrefix(filename, req.FilenamePrefix())
// res now looks like v0.2.12_x5.0_freebsd_amd64.zip
e.ext = filepath.Ext(res)
res = strings.TrimRight(res, e.ext)
res = strings.TrimSuffix(res, e.ext)
// res now looks like v0.2.12_x5.0_freebsd_amd64
parts := strings.Split(res, "_")
@@ -326,7 +326,7 @@ func (e *ChecksumFileEntry) init(req *Requirement) (err error) {
func (e *ChecksumFileEntry) validate(expectedVersion string, installOpts BinaryInstallationOptions) error {
if e.binVersion != expectedVersion {
return fmt.Errorf("wrong version, expected %s ", expectedVersion)
return fmt.Errorf("wrong version: '%s' does not match expected %s ", e.binVersion, expectedVersion)
}
if e.os != installOpts.OS || e.arch != installOpts.ARCH {
return fmt.Errorf("wrong system, expected %s_%s ", installOpts.OS, installOpts.ARCH)
+26
View File
@@ -27,6 +27,32 @@ var (
pluginFolderWrongChecksums = filepath.Join("testdata", "wrong_checksums")
)
func TestChecksumFileEntry_init(t *testing.T) {
expectedVersion := "v0.3.0"
req := &Requirement{
Identifier: &addrs.Plugin{
Hostname: "github.com",
Namespace: "ddelnano",
Type: "xenserver",
},
}
checkSum := &ChecksumFileEntry{
Filename: fmt.Sprintf("packer-plugin-xenserver_%s_x5.0_darwin_amd64.zip", expectedVersion),
Checksum: "0f5969b069b9c0a58f2d5786c422341c70dfe17bd68f896fcbd46677e8c913f1",
}
err := checkSum.init(req)
if err != nil {
t.Fatalf("ChecksumFileEntry.init failure: %v", err)
}
if checkSum.binVersion != expectedVersion {
t.Errorf("failed to parse ChecksumFileEntry properly expected version '%s' but found '%s'", expectedVersion, checkSum.binVersion)
}
}
func TestPlugin_ListInstallations(t *testing.T) {
type fields struct {
@@ -1,11 +0,0 @@
package dockerimport
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packersdk.PostProcessor = new(PostProcessor)
}
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var DockerImportPluginVersion *version.PluginVersion
func init() {
DockerImportPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
@@ -1,121 +0,0 @@
package dockerpush
import (
"bytes"
"context"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/docker"
dockerimport "github.com/hashicorp/packer/post-processor/docker-import"
)
func testUi() *packersdk.BasicUi {
return &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
}
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packersdk.PostProcessor = new(PostProcessor)
}
func TestPostProcessor_PostProcess(t *testing.T) {
driver := &docker.MockDriver{}
p := &PostProcessor{Driver: driver}
artifact := &packersdk.MockArtifact{
BuilderIdValue: dockerimport.BuilderId,
IdValue: "foo/bar",
}
result, keep, forceOverride, err := p.PostProcess(context.Background(), testUi(), artifact)
if _, ok := result.(packersdk.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if !keep {
t.Fatal("should keep")
}
if forceOverride {
t.Fatal("Should default to keep, but not override user wishes")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if !driver.PushCalled {
t.Fatal("should call push")
}
if driver.PushName != "foo/bar" {
t.Fatal("bad name")
}
if result.Id() != "foo/bar" {
t.Fatal("bad image id")
}
}
func TestPostProcessor_PostProcess_portInName(t *testing.T) {
driver := &docker.MockDriver{}
p := &PostProcessor{Driver: driver}
artifact := &packersdk.MockArtifact{
BuilderIdValue: dockerimport.BuilderId,
IdValue: "localhost:5000/foo/bar",
}
result, keep, forceOverride, err := p.PostProcess(context.Background(), testUi(), artifact)
if _, ok := result.(packersdk.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if !keep {
t.Fatal("should keep")
}
if forceOverride {
t.Fatal("Should default to keep, but not override user wishes")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if !driver.PushCalled {
t.Fatal("should call push")
}
if driver.PushName != "localhost:5000/foo/bar" {
t.Fatal("bad name")
}
if result.Id() != "localhost:5000/foo/bar" {
t.Fatal("bad image id")
}
}
func TestPostProcessor_PostProcess_tags(t *testing.T) {
driver := &docker.MockDriver{}
p := &PostProcessor{Driver: driver}
artifact := &packersdk.MockArtifact{
BuilderIdValue: dockerimport.BuilderId,
IdValue: "hashicorp/ubuntu:precise",
}
result, keep, forceOverride, err := p.PostProcess(context.Background(), testUi(), artifact)
if _, ok := result.(packersdk.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if !keep {
t.Fatal("should keep")
}
if forceOverride {
t.Fatal("Should default to keep, but not override user wishes")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if !driver.PushCalled {
t.Fatal("should call push")
}
if driver.PushName != "hashicorp/ubuntu:precise" {
t.Fatalf("bad name: %s", driver.PushName)
}
if result.Id() != "hashicorp/ubuntu:precise" {
t.Fatal("bad image id")
}
}
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var DockerPushPluginVersion *version.PluginVersion
func init() {
DockerPushPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
@@ -1,11 +0,0 @@
package dockersave
import (
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packersdk.PostProcessor = new(PostProcessor)
}
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var DockerSavePluginVersion *version.PluginVersion
func init() {
DockerSavePluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
@@ -1,196 +0,0 @@
package dockertag
import (
"bytes"
"context"
"testing"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/docker"
dockerimport "github.com/hashicorp/packer/post-processor/docker-import"
"github.com/stretchr/testify/assert"
)
func testConfig() map[string]interface{} {
return map[string]interface{}{
"repository": "foo",
"tag": "bar,buzz",
}
}
func testPP(t *testing.T) *PostProcessor {
var p PostProcessor
if err := p.Configure(testConfig()); err != nil {
t.Fatalf("err: %s", err)
}
return &p
}
func testUi() *packersdk.BasicUi {
return &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
}
func TestPostProcessor_ImplementsPostProcessor(t *testing.T) {
var _ packersdk.PostProcessor = new(PostProcessor)
}
func TestPostProcessor_PostProcess(t *testing.T) {
driver := &docker.MockDriver{}
p := &PostProcessor{Driver: driver}
if err := p.Configure(testConfig()); err != nil {
t.Fatalf("err: %s", err)
}
artifact := &packersdk.MockArtifact{
BuilderIdValue: dockerimport.BuilderId,
IdValue: "1234567890abcdef",
}
result, keep, forceOverride, err := p.PostProcess(context.Background(), testUi(), artifact)
if _, ok := result.(packersdk.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if !keep {
t.Fatal("should keep")
}
if !forceOverride {
t.Fatal("Should force keep no matter what user sets.")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if driver.TagImageCalled != 2 {
t.Fatal("should call TagImage")
}
if driver.TagImageImageId != "1234567890abcdef" {
t.Fatal("bad image id")
}
if driver.TagImageRepo[0] != "foo:bar" {
t.Fatal("bad repo")
}
if driver.TagImageRepo[1] != "foo:buzz" {
t.Fatal("bad repo")
}
if driver.TagImageForce {
t.Fatal("bad force. force=false in default")
}
}
func TestPostProcessor_PostProcess_Force(t *testing.T) {
driver := &docker.MockDriver{}
p := &PostProcessor{Driver: driver}
c := testConfig()
c["force"] = true
if err := p.Configure(c); err != nil {
t.Fatalf("err: %s", err)
}
artifact := &packersdk.MockArtifact{
BuilderIdValue: dockerimport.BuilderId,
IdValue: "1234567890abcdef",
}
result, keep, forceOverride, err := p.PostProcess(context.Background(), testUi(), artifact)
if _, ok := result.(packersdk.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if !keep {
t.Fatal("should keep")
}
if !forceOverride {
t.Fatal("Should force keep no matter what user sets.")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if driver.TagImageCalled != 2 {
t.Fatal("should call TagImage")
}
if driver.TagImageImageId != "1234567890abcdef" {
t.Fatal("bad image id")
}
if driver.TagImageRepo[0] != "foo:bar" {
t.Fatal("bad repo")
}
if driver.TagImageRepo[1] != "foo:buzz" {
t.Fatal("bad repo")
}
if !driver.TagImageForce {
t.Fatal("bad force")
}
}
func TestPostProcessor_PostProcess_NoTag(t *testing.T) {
driver := &docker.MockDriver{}
p := &PostProcessor{Driver: driver}
c := testConfig()
delete(c, "tag")
if err := p.Configure(c); err != nil {
t.Fatalf("err %s", err)
}
artifact := &packersdk.MockArtifact{BuilderIdValue: dockerimport.BuilderId, IdValue: "1234567890abcdef"}
result, keep, forceOverride, err := p.PostProcess(context.Background(), testUi(), artifact)
if _, ok := result.(packersdk.Artifact); !ok {
t.Fatal("should be instance of Artifact")
}
if !keep {
t.Fatal("should keep")
}
if !forceOverride {
t.Fatal("Should force keep no matter what user sets.")
}
if err != nil {
t.Fatalf("err: %s", err)
}
if driver.TagImageCalled != 1 {
t.Fatal("should call TagImage")
}
if driver.TagImageImageId != "1234567890abcdef" {
t.Fatal("bad image id")
}
if driver.TagImageRepo[0] != "foo" {
t.Fatal("bad repo")
}
if driver.TagImageForce {
t.Fatal("bad force")
}
}
func TestPostProcessor_PostProcess_Tag_vs_Tags(t *testing.T) {
testCases := []map[string]interface{}{
{
"tag": "bar,buzz",
"tags": []string{"bang"},
},
{
"tag": []string{"bar", "buzz"},
"tags": []string{"bang"},
},
{
"tag": []string{"bar"},
"tags": []string{"buzz", "bang"},
},
}
for _, tc := range testCases {
var p PostProcessor
if err := p.Configure(tc); err != nil {
t.Fatalf("err: %s", err)
}
assert.ElementsMatchf(t, p.config.Tags, []string{"bar", "buzz", "bang"},
"tag and tags fields should be combined into tags fields. Recieved: %#v",
p.config.Tags)
}
}
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var DockerTagPluginVersion *version.PluginVersion
func init() {
DockerTagPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
@@ -1,31 +0,0 @@
package exoscaleimport
const BuilderId = "packer.post-processor.exoscale-import"
type Artifact struct {
id string
}
func (a *Artifact) BuilderId() string {
return BuilderId
}
func (a *Artifact) Id() string {
return a.id
}
func (a *Artifact) Files() []string {
return nil
}
func (a *Artifact) String() string {
return a.id
}
func (a *Artifact) State(name string) interface{} {
return nil
}
func (a *Artifact) Destroy() error {
return nil
}
@@ -1,270 +0,0 @@
//go:generate mapstructure-to-hcl2 -type Config
package exoscaleimport
import (
"context"
"crypto/md5"
"encoding/base64"
"fmt"
"io"
"os"
"path/filepath"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/exoscale/egoscale"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/hashicorp/packer/builder/file"
"github.com/hashicorp/packer/builder/qemu"
"github.com/hashicorp/packer/post-processor/artifice"
"github.com/hashicorp/packer/post-processor/exoscale-import/version"
)
var (
defaultTemplateZone = "ch-gva-2"
defaultAPIEndpoint = "https://api.exoscale.com/compute"
defaultSOSEndpoint = "https://sos-" + defaultTemplateZone + ".exo.io"
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
SkipClean bool `mapstructure:"skip_clean"`
SOSEndpoint string `mapstructure:"sos_endpoint"`
APIEndpoint string `mapstructure:"api_endpoint"`
APIKey string `mapstructure:"api_key"`
APISecret string `mapstructure:"api_secret"`
ImageBucket string `mapstructure:"image_bucket"`
TemplateZone string `mapstructure:"template_zone"`
TemplateName string `mapstructure:"template_name"`
TemplateDescription string `mapstructure:"template_description"`
TemplateUsername string `mapstructure:"template_username"`
TemplateDisablePassword bool `mapstructure:"template_disable_password"`
TemplateDisableSSHKey bool `mapstructure:"template_disable_sshkey"`
}
func init() {
egoscale.UserAgent = "Packer-Exoscale/" + version.ExoscaleImportPluginVersion.FormattedVersion() + " " + egoscale.UserAgent
}
type PostProcessor struct {
config Config
}
func (p *PostProcessor) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() }
func (p *PostProcessor) Configure(raws ...interface{}) error {
p.config.TemplateZone = defaultTemplateZone
p.config.APIEndpoint = defaultAPIEndpoint
p.config.SOSEndpoint = defaultSOSEndpoint
if err := config.Decode(&p.config, nil, raws...); err != nil {
return err
}
if p.config.APIKey == "" {
p.config.APIKey = os.Getenv("EXOSCALE_API_KEY")
}
if p.config.APISecret == "" {
p.config.APISecret = os.Getenv("EXOSCALE_API_SECRET")
}
requiredArgs := map[string]*string{
"api_key": &p.config.APIKey,
"api_secret": &p.config.APISecret,
"api_endpoint": &p.config.APIEndpoint,
"sos_endpoint": &p.config.SOSEndpoint,
"image_bucket": &p.config.ImageBucket,
"template_zone": &p.config.TemplateZone,
"template_name": &p.config.TemplateName,
"template_description": &p.config.TemplateDescription,
}
errs := new(packersdk.MultiError)
for k, v := range requiredArgs {
if *v == "" {
errs = packersdk.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", k))
}
}
if len(errs.Errors) > 0 {
return errs
}
packersdk.LogSecretFilter.Set(p.config.APIKey, p.config.APISecret)
return nil
}
func (p *PostProcessor) PostProcess(ctx context.Context, ui packersdk.Ui, a packersdk.Artifact) (packersdk.Artifact, bool, bool, error) {
switch a.BuilderId() {
case qemu.BuilderId, file.BuilderId, artifice.BuilderId:
break
default:
err := fmt.Errorf(
"Unknown artifact type: %s\nCan only import from QEMU/file builders and Artifice post-processor artifacts.",
a.BuilderId())
return nil, false, false, err
}
ui.Message("Uploading template image")
url, md5sum, err := p.uploadImage(ctx, ui, a)
if err != nil {
return nil, false, false, fmt.Errorf("unable to upload image: %s", err)
}
ui.Message("Registering template")
id, err := p.registerTemplate(ctx, ui, url, md5sum)
if err != nil {
return nil, false, false, fmt.Errorf("unable to register template: %s", err)
}
if !p.config.SkipClean {
ui.Message("Deleting uploaded template image")
if err = p.deleteImage(ctx, ui, a); err != nil {
return nil, false, false, fmt.Errorf("unable to delete uploaded template image: %s", err)
}
}
return &Artifact{id}, false, false, nil
}
func (p *PostProcessor) uploadImage(ctx context.Context, ui packersdk.Ui, a packersdk.Artifact) (string, string, error) {
var (
imageFile = a.Files()[0]
bucketFile = filepath.Base(imageFile)
)
f, err := os.Open(imageFile)
if err != nil {
return "", "", err
}
defer f.Close()
fileInfo, err := f.Stat()
if err != nil {
return "", "", err
}
// For tracking image file upload progress
pf := ui.TrackProgress(imageFile, 0, fileInfo.Size(), f)
defer pf.Close()
hash := md5.New()
if _, err := io.Copy(hash, f); err != nil {
return "", "", fmt.Errorf("image checksumming failed: %s", err)
}
if _, err := f.Seek(0, 0); err != nil {
return "", "", err
}
sess := session.Must(session.NewSessionWithOptions(session.Options{Config: aws.Config{
Region: aws.String(p.config.TemplateZone),
Endpoint: aws.String(p.config.SOSEndpoint),
Credentials: credentials.NewStaticCredentials(p.config.APIKey, p.config.APISecret, "")}}))
uploader := s3manager.NewUploader(sess)
output, err := uploader.UploadWithContext(ctx, &s3manager.UploadInput{
Body: pf,
Bucket: aws.String(p.config.ImageBucket),
Key: aws.String(bucketFile),
ContentMD5: aws.String(base64.StdEncoding.EncodeToString(hash.Sum(nil))),
ACL: aws.String("public-read"),
})
if err != nil {
return "", "", err
}
return output.Location, fmt.Sprintf("%x", hash.Sum(nil)), nil
}
func (p *PostProcessor) deleteImage(ctx context.Context, ui packersdk.Ui, a packersdk.Artifact) error {
var (
imageFile = a.Files()[0]
bucketFile = filepath.Base(imageFile)
)
sess := session.Must(session.NewSessionWithOptions(session.Options{Config: aws.Config{
Region: aws.String(p.config.TemplateZone),
Endpoint: aws.String(p.config.SOSEndpoint),
Credentials: credentials.NewStaticCredentials(p.config.APIKey, p.config.APISecret, "")}}))
svc := s3.New(sess)
if _, err := svc.DeleteObject(&s3.DeleteObjectInput{
Bucket: aws.String(p.config.ImageBucket),
Key: aws.String(bucketFile),
}); err != nil {
return err
}
return nil
}
func (p *PostProcessor) registerTemplate(ctx context.Context, ui packersdk.Ui, url, md5sum string) (string, error) {
var (
passwordEnabled = !p.config.TemplateDisablePassword
sshkeyEnabled = !p.config.TemplateDisableSSHKey
regErr error
)
exo := egoscale.NewClient(p.config.APIEndpoint, p.config.APIKey, p.config.APISecret)
exo.RetryStrategy = egoscale.FibonacciRetryStrategy
zone := egoscale.Zone{Name: p.config.TemplateZone}
if resp, err := exo.GetWithContext(ctx, &zone); err != nil {
return "", fmt.Errorf("template zone lookup failed: %s", err)
} else {
zone.ID = resp.(*egoscale.Zone).ID
}
req := egoscale.RegisterCustomTemplate{
URL: url,
ZoneID: zone.ID,
Name: p.config.TemplateName,
Displaytext: p.config.TemplateDescription,
PasswordEnabled: &passwordEnabled,
SSHKeyEnabled: &sshkeyEnabled,
Details: map[string]string{"username": p.config.TemplateUsername},
Checksum: md5sum,
}
res := make([]egoscale.Template, 0)
exo.AsyncRequestWithContext(ctx, req, func(jobRes *egoscale.AsyncJobResult, err error) bool {
if err != nil {
regErr = fmt.Errorf("request failed: %s", err)
return false
} else if jobRes.JobStatus == egoscale.Pending {
// Job is not completed yet
ui.Message("template registration in progress")
return true
}
if err := jobRes.Result(&res); err != nil {
regErr = err
return false
}
if len(res) != 1 {
regErr = fmt.Errorf("unexpected response from API (expected 1 item, got %d)", len(res))
return false
}
return false
})
if regErr != nil {
return "", regErr
}
return res[0].ID.String(), nil
}
@@ -1,13 +0,0 @@
package version
import (
"github.com/hashicorp/packer-plugin-sdk/version"
packerVersion "github.com/hashicorp/packer/version"
)
var ExoscaleImportPluginVersion *version.PluginVersion
func init() {
ExoscaleImportPluginVersion = version.InitializePluginVersion(
packerVersion.Version, packerVersion.VersionPrerelease)
}
@@ -11,9 +11,9 @@ import (
"fmt"
"os/exec"
"github.com/hashicorp/packer-plugin-docker/builder/docker"
builderT "github.com/hashicorp/packer-plugin-sdk/acctest"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/docker"
"github.com/hashicorp/packer/provisioner/file"
)
+3 -1
View File
@@ -138,12 +138,12 @@ func (p *Provisioner) ProvisionDownload(ui packersdk.Ui, comm packersdk.Communic
return fmt.Errorf("Error interpolating destination: %s", err)
}
for _, src := range p.config.Sources {
dst := dst
src, err := interpolate.Render(src, &p.config.ctx)
if err != nil {
return fmt.Errorf("Error interpolating source: %s", err)
}
ui.Say(fmt.Sprintf("Downloading %s => %s", src, dst))
// ensure destination dir exists. p.config.Destination may either be a file or a dir.
dir := dst
// if it doesn't end with a /, set dir as the parent dir
@@ -152,6 +152,8 @@ func (p *Provisioner) ProvisionDownload(ui packersdk.Ui, comm packersdk.Communic
} else if !strings.HasSuffix(src, "/") && !strings.HasSuffix(src, "*") {
dst = filepath.Join(dst, filepath.Base(src))
}
ui.Say(fmt.Sprintf("Downloading %s => %s", src, dst))
if dir != "" {
err := os.MkdirAll(dir, os.FileMode(0755))
if err != nil {
+63
View File
@@ -272,6 +272,69 @@ func TestProvisionerProvision_SendsFileMultipleDirs(t *testing.T) {
}
}
func TestProvisionerProvision_DownloadsMultipleFilesToFolder(t *testing.T) {
var p Provisioner
tf1, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf1.Name())
if _, err = tf1.Write([]byte("hello")); err != nil {
t.Fatalf("error writing tempfile: %s", err)
}
tf2, err := ioutil.TempFile("", "packer")
if err != nil {
t.Fatalf("error tempfile: %s", err)
}
defer os.Remove(tf2.Name())
if _, err = tf2.Write([]byte("hello")); err != nil {
t.Fatalf("error writing tempfile: %s", err)
}
config := map[string]interface{}{
"sources": []string{tf1.Name(), tf2.Name()},
"destination": "something/",
"direction": "download",
}
if err := p.Prepare(config); err != nil {
t.Fatalf("err: %s", err)
}
b := bytes.NewBuffer(nil)
ui := &packersdk.BasicUi{
Writer: b,
PB: &packersdk.NoopProgressTracker{},
}
comm := &packersdk.MockCommunicator{}
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
if err != nil {
t.Fatalf("should successfully provision: %s", err)
}
if !strings.Contains(b.String(), tf1.Name()) {
t.Errorf("should print source filenam '%s'e; output: \n%s", tf1.Name(), b.String())
}
if !strings.Contains(b.String(), tf2.Name()) {
t.Errorf("should second source filename '%s'; output: \n%s", tf2.Name(), b.String())
}
dst1 := filepath.Join("something", filepath.Base(tf1.Name()))
if !strings.Contains(b.String(), dst1) {
t.Errorf("should print destination filename '%s'; output: \n%s", dst1, b.String())
}
dst2 := filepath.Join("something", filepath.Base(tf2.Name()))
if !strings.Contains(b.String(), dst2) {
t.Errorf("should print destination filename '%s'; output: \n%s", dst2, b.String())
}
}
func TestProvisionerProvision_SendsFileMultipleFilesToFolder(t *testing.T) {
var p Provisioner
+34 -4
View File
@@ -23,6 +23,7 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"unicode"
"golang.org/x/crypto/ssh"
@@ -59,6 +60,7 @@ type Config struct {
LocalPort int `mapstructure:"local_port"`
SSHHostKeyFile string `mapstructure:"ssh_host_key_file"`
SSHAuthorizedKeyFile string `mapstructure:"ssh_authorized_key_file"`
ValidExitCodes []int `mapstructure:"valid_exit_codes"`
}
type Provisioner struct {
@@ -157,6 +159,10 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("user: could not determine current user from environment."))
}
if p.config.ValidExitCodes == nil {
p.config.ValidExitCodes = []int{0, 101}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
@@ -170,7 +176,7 @@ func (p *Provisioner) getVersion() error {
"Error running \"%s version\": %s", p.config.Command, err.Error())
}
versionRe := regexp.MustCompile(`\w (\d+\.\d+[.\d+]*)`)
versionRe := regexp.MustCompile(`(\d+\.\d+[.\d+]*)`)
matches := versionRe.FindStringSubmatch(string(out))
if matches == nil {
return fmt.Errorf(
@@ -412,9 +418,33 @@ func (p *Provisioner) executeInspec(ui packersdk.Ui, comm packersdk.Communicator
return err
}
wg.Wait()
err = cmd.Wait()
if err != nil {
return fmt.Errorf("Non-zero exit status: %s", err)
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
exitStatus := status.ExitStatus()
// Check exit code against allowed codes (likely just 0)
validExitCode := false
for _, v := range p.config.ValidExitCodes {
if exitStatus == v {
validExitCode = true
}
}
if !validExitCode {
return fmt.Errorf(
"Inspec exited with unexpected exit status: %d. Expected exit codes are: %v",
exitStatus, p.config.ValidExitCodes)
}
}
} else {
return fmt.Errorf("Unable to get exit status: %s", err)
}
}
return nil
@@ -30,6 +30,7 @@ type FlatConfig struct {
LocalPort *int `mapstructure:"local_port" cty:"local_port" hcl:"local_port"`
SSHHostKeyFile *string `mapstructure:"ssh_host_key_file" cty:"ssh_host_key_file" hcl:"ssh_host_key_file"`
SSHAuthorizedKeyFile *string `mapstructure:"ssh_authorized_key_file" cty:"ssh_authorized_key_file" hcl:"ssh_authorized_key_file"`
ValidExitCodes []int `mapstructure:"valid_exit_codes" cty:"valid_exit_codes" hcl:"valid_exit_codes"`
}
// FlatMapstructure returns a new FlatConfig.
@@ -64,6 +65,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"local_port": &hcldec.AttrSpec{Name: "local_port", Type: cty.Number, Required: false},
"ssh_host_key_file": &hcldec.AttrSpec{Name: "ssh_host_key_file", Type: cty.String, Required: false},
"ssh_authorized_key_file": &hcldec.AttrSpec{Name: "ssh_authorized_key_file", Type: cty.String, Required: false},
"valid_exit_codes": &hcldec.AttrSpec{Name: "valid_exit_codes", Type: cty.List(cty.Number), Required: false},
}
return s
}
+41 -4
View File
@@ -1,12 +1,15 @@
package inspec
import (
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"runtime"
"strings"
"testing"
@@ -268,12 +271,17 @@ func TestProvisionerPrepare_LocalPort(t *testing.T) {
}
func TestInspecGetVersion(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 and it requires InSpec to be installed")
var p Provisioner
if os.Getenv("PACKER_ACC") == "1" {
p.config.Command = "inspec"
} else {
p.config.Command = "./test-fixtures/inspec_version.sh"
if runtime.GOOS == "windows" {
t.Skip("This test is only run with PACKER_ACC=1 and it requires InSpec to be installed")
}
}
var p Provisioner
p.config.Command = "inspec exec"
p.config.Backend = "local"
err := p.getVersion()
if err != nil {
t.Fatalf("err: %s", err)
@@ -291,3 +299,32 @@ func TestInspecGetVersionError(t *testing.T) {
t.Fatal("Error message should include command name")
}
}
func TestInspecValidExitCodes(t *testing.T) {
var p Provisioner
if os.Getenv("PACKER_ACC") == "1" {
p.config.Command = "inspec"
} else {
p.config.Command = "./test-fixtures/valid_exit_codes.sh"
if runtime.GOOS == "windows" {
t.Skip("This test is only run with PACKER_ACC=1 and it requires InSpec to be installed")
}
}
p.config.Backend = "local"
p.config.Profile = "test-fixtures/skip_control.rb"
err := p.Prepare()
if err != nil {
t.Fatalf("err: %s", err)
}
comm := &packersdk.MockCommunicator{}
ui := &packersdk.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
}
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
if err != nil {
t.Fatalf("err: %s", err)
}
}
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
cat <<EOB
4.26.13
EOB
exit 0
@@ -0,0 +1,13 @@
# copyright: 2018, The Authors
title "skip control"
control "skip-1.0" do
impact 1.0
title "skip control"
desc "skip control to generate a 101 return code"
only_if { 1 == 2 }
describe file("/tmp") do
it { should be_directory }
end
end
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
cat <<EOB
Profile: tests from test-fixtures/skip_control.rb (tests from test-fixtures/valid_exit_codes.sh)
Version: (not specified)
Target: local://
 ↺ skip-1.0: skip control
 ↺ Skipped control due to only_if condition.
Profile Summary: 0 successful controls, 0 control failures, 1 control skipped
Test Summary: 0 successful, 0 failures, 1 skipped
EOB
exit 101
+2 -2
View File
@@ -101,7 +101,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
tempDir: "/tmp/salt",
stateRoot: "/srv/salt",
pillarRoot: "/srv/pillar",
bootstrapFetchCmd: "curl -L https://bootstrap.saltstack.com -o /tmp/install_salt.sh || wget -O /tmp/install_salt.sh https://bootstrap.saltstack.com",
bootstrapFetchCmd: "curl -L https://bootstrap.saltproject.io -o /tmp/install_salt.sh || wget -O /tmp/install_salt.sh https://bootstrap.saltproject.io",
bootstrapRunCmd: "sh /tmp/install_salt.sh",
},
guestexec.WindowsOSType: {
@@ -109,7 +109,7 @@ var guestOSTypeConfigs = map[string]guestOSTypeConfig{
tempDir: "C:/Windows/Temp/salt/",
stateRoot: "C:/salt/state",
pillarRoot: "C:/salt/pillar/",
bootstrapFetchCmd: "powershell Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.ps1' -OutFile 'C:/Windows/Temp/bootstrap-salt.ps1'",
bootstrapFetchCmd: "powershell -Command \"[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]'Tls,Tls11,Tls12'; Invoke-WebRequest -Uri 'https://winbootstrap.saltproject.io' -OutFile 'C:/Windows/Temp/bootstrap-salt.ps1'\"",
bootstrapRunCmd: "Powershell C:/Windows/Temp/bootstrap-salt.ps1",
},
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2021 DeepMap, Inc.
//
// 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 runtime
// Binder is the interface implemented by types that can be bound to a query string or a parameter string
// The input can be assumed to be a valid string. If you define a Bind method you are responsible for all
// data being completely bound to the type.
//
// By convention, to approximate the behavior of Bind functions themselves,
// Binder implements Bind("") as a no-op.
type Binder interface {
Bind(src string) error
}
+463
View File
@@ -0,0 +1,463 @@
// Copyright 2019 DeepMap, Inc.
//
// 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 runtime
import (
"encoding/json"
"fmt"
"net/url"
"reflect"
"strings"
"time"
"github.com/pkg/errors"
"github.com/deepmap/oapi-codegen/pkg/types"
)
// This function binds a parameter as described in the Path Parameters
// section here to a Go object:
// https://swagger.io/docs/specification/serialization/
func BindStyledParameter(style string, explode bool, paramName string,
value string, dest interface{}) error {
if value == "" {
return fmt.Errorf("parameter '%s' is empty, can't bind its value", paramName)
}
// Everything comes in by pointer, dereference it
v := reflect.Indirect(reflect.ValueOf(dest))
// This is the basic type of the destination object.
t := v.Type()
if t.Kind() == reflect.Struct {
// We've got a destination object, we'll create a JSON representation
// of the input value, and let the json library deal with the unmarshaling
parts, err := splitStyledParameter(style, explode, true, paramName, value)
if err != nil {
return err
}
return bindSplitPartsToDestinationStruct(paramName, parts, explode, dest)
}
if t.Kind() == reflect.Slice {
// Chop up the parameter into parts based on its style
parts, err := splitStyledParameter(style, explode, false, paramName, value)
if err != nil {
return fmt.Errorf("error splitting input '%s' into parts: %s", value, err)
}
return bindSplitPartsToDestinationArray(parts, dest)
}
// Try to bind the remaining types as a base type.
return BindStringToObject(value, dest)
}
// This is a complex set of operations, but each given parameter style can be
// packed together in multiple ways, using different styles of separators, and
// different packing strategies based on the explode flag. This function takes
// as input any parameter format, and unpacks it to a simple list of strings
// or key-values which we can then treat generically.
// Why, oh why, great Swagger gods, did you have to make this so complicated?
func splitStyledParameter(style string, explode bool, object bool, paramName string, value string) ([]string, error) {
switch style {
case "simple":
// In the simple case, we always split on comma
parts := strings.Split(value, ",")
return parts, nil
case "label":
// In the label case, it's more tricky. In the no explode case, we have
// /users/.3,4,5 for arrays
// /users/.role,admin,firstName,Alex for objects
// in the explode case, we have:
// /users/.3.4.5
// /users/.role=admin.firstName=Alex
if explode {
// In the exploded case, split everything on periods.
parts := strings.Split(value, ".")
// The first part should be an empty string because we have a
// leading period.
if parts[0] != "" {
return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
}
return parts[1:], nil
} else {
// In the unexploded case, we strip off the leading period.
if value[0] != '.' {
return nil, fmt.Errorf("invalid format for label parameter '%s', should start with '.'", paramName)
}
// The rest is comma separated.
return strings.Split(value[1:], ","), nil
}
case "matrix":
if explode {
// In the exploded case, we break everything up on semicolon
parts := strings.Split(value, ";")
// The first part should always be empty string, since we started
// with ;something
if parts[0] != "" {
return nil, fmt.Errorf("invalid format for matrix parameter '%s', should start with ';'", paramName)
}
parts = parts[1:]
// Now, if we have an object, we just have a list of x=y statements.
// for a non-object, like an array, we have id=x, id=y. id=z, etc,
// so we need to strip the prefix from each of them.
if !object {
prefix := paramName + "="
for i := range parts {
parts[i] = strings.TrimPrefix(parts[i], prefix)
}
}
return parts, nil
} else {
// In the unexploded case, parameters will start with ;paramName=
prefix := ";" + paramName + "="
if !strings.HasPrefix(value, prefix) {
return nil, fmt.Errorf("expected parameter '%s' to start with %s", paramName, prefix)
}
str := strings.TrimPrefix(value, prefix)
return strings.Split(str, ","), nil
}
case "form":
var parts []string
if explode {
parts = strings.Split(value, "&")
if !object {
prefix := paramName + "="
for i := range parts {
parts[i] = strings.TrimPrefix(parts[i], prefix)
}
}
return parts, nil
} else {
parts = strings.Split(value, ",")
prefix := paramName + "="
for i := range parts {
parts[i] = strings.TrimPrefix(parts[i], prefix)
}
}
return parts, nil
}
return nil, fmt.Errorf("unhandled parameter style: %s", style)
}
// Given a set of values as a slice, create a slice to hold them all, and
// assign to each one by one.
func bindSplitPartsToDestinationArray(parts []string, dest interface{}) error {
// Everything comes in by pointer, dereference it
v := reflect.Indirect(reflect.ValueOf(dest))
// This is the basic type of the destination object.
t := v.Type()
// We've got a destination array, bind each object one by one.
// This generates a slice of the correct element type and length to
// hold all the parts.
newArray := reflect.MakeSlice(t, len(parts), len(parts))
for i, p := range parts {
err := BindStringToObject(p, newArray.Index(i).Addr().Interface())
if err != nil {
return fmt.Errorf("error setting array element: %s", err)
}
}
v.Set(newArray)
return nil
}
// Given a set of chopped up parameter parts, bind them to a destination
// struct. The exploded parameter controls whether we send key value pairs
// in the exploded case, or a sequence of values which are interpreted as
// tuples.
// Given the struct Id { firstName string, role string }, as in the canonical
// swagger examples, in the exploded case, we would pass
// ["firstName=Alex", "role=admin"], where in the non-exploded case, we would
// pass "firstName", "Alex", "role", "admin"]
//
// We punt the hard work of binding these values to the object to the json
// library. We'll turn those arrays into JSON strings, and unmarshal
// into the struct.
func bindSplitPartsToDestinationStruct(paramName string, parts []string, explode bool, dest interface{}) error {
// We've got a destination object, we'll create a JSON representation
// of the input value, and let the json library deal with the unmarshaling
var fields []string
if explode {
fields = make([]string, len(parts))
for i, property := range parts {
propertyParts := strings.Split(property, "=")
if len(propertyParts) != 2 {
return fmt.Errorf("parameter '%s' has invalid exploded format", paramName)
}
fields[i] = "\"" + propertyParts[0] + "\":\"" + propertyParts[1] + "\""
}
} else {
if len(parts)%2 != 0 {
return fmt.Errorf("parameter '%s' has invalid format, property/values need to be pairs", paramName)
}
fields = make([]string, len(parts)/2)
for i := 0; i < len(parts); i += 2 {
key := parts[i]
value := parts[i+1]
fields[i/2] = "\"" + key + "\":\"" + value + "\""
}
}
jsonParam := "{" + strings.Join(fields, ",") + "}"
err := json.Unmarshal([]byte(jsonParam), dest)
if err != nil {
return fmt.Errorf("error binding parameter %s fields: %s", paramName, err)
}
return nil
}
// This works much like BindStyledParameter, however it takes a query argument
// input array from the url package, since query arguments come through a
// different path than the styled arguments. They're also exceptionally fussy.
// For example, consider the exploded and unexploded form parameter examples:
// (exploded) /users?role=admin&firstName=Alex
// (unexploded) /users?id=role,admin,firstName,Alex
//
// In the first case, we can pull the "id" parameter off the context,
// and unmarshal via json as an intermediate. Easy. In the second case, we
// don't have the id QueryParam present, but must find "role", and "firstName".
// what if there is another parameter similar to "ID" named "role"? We can't
// tell them apart. This code tries to fail, but the moral of the story is that
// you shouldn't pass objects via form styled query arguments, just use
// the Content parameter form.
func BindQueryParameter(style string, explode bool, required bool, paramName string,
queryParams url.Values, dest interface{}) error {
// dv = destination value.
dv := reflect.Indirect(reflect.ValueOf(dest))
// intermediate value form which is either dv or dv dereferenced.
v := dv
// inner code will bind the string's value to this interface.
var output interface{}
if required {
// If the parameter is required, then the generated code will pass us
// a pointer to it: &int, &object, and so forth. We can directly set
// them.
output = dest
} else {
// For optional parameters, we have an extra indirect. An optional
// parameter of type "int" will be *int on the struct. We pass that
// in by pointer, and have **int.
// If the destination, is a nil pointer, we need to allocate it.
if v.IsNil() {
t := v.Type()
newValue := reflect.New(t.Elem())
// for now, hang onto the output buffer separately from destination,
// as we don't want to write anything to destination until we can
// unmarshal successfully, and check whether a field is required.
output = newValue.Interface()
} else {
// If the destination isn't nil, just use that.
output = v.Interface()
}
// Get rid of that extra indirect as compared to the required case,
// so the code below doesn't have to care.
v = reflect.Indirect(reflect.ValueOf(output))
}
// This is the basic type of the destination object.
t := v.Type()
k := t.Kind()
switch style {
case "form":
var parts []string
if explode {
// ok, the explode case in query arguments is very, very annoying,
// because an exploded object, such as /users?role=admin&firstName=Alex
// isn't actually present in the parameter array. We have to do
// different things based on destination type.
values, found := queryParams[paramName]
var err error
switch k {
case reflect.Slice:
// In the slice case, we simply use the arguments provided by
// http library.
if !found {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
err = bindSplitPartsToDestinationArray(values, output)
case reflect.Struct:
// This case is really annoying, and error prone, but the
// form style object binding doesn't tell us which arguments
// in the query string correspond to the object's fields. We'll
// try to bind field by field.
err = bindParamsToExplodedObject(paramName, queryParams, output)
default:
// Primitive object case. We expect to have 1 value to
// unmarshal.
if len(values) == 0 {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
if len(values) != 1 {
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
}
err = BindStringToObject(values[0], output)
}
if err != nil {
return err
}
// If the parameter is required, and we've successfully unmarshaled
// it, this assigns the new object to the pointer pointer.
if !required {
dv.Set(reflect.ValueOf(output))
}
return nil
} else {
values, found := queryParams[paramName]
if !found {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
if len(values) != 1 {
return fmt.Errorf("parameter '%s' is not exploded, but is specified multiple times", paramName)
}
parts = strings.Split(values[0], ",")
}
var err error
switch k {
case reflect.Slice:
err = bindSplitPartsToDestinationArray(parts, output)
case reflect.Struct:
err = bindSplitPartsToDestinationStruct(paramName, parts, explode, output)
default:
if len(parts) == 0 {
if required {
return fmt.Errorf("query parameter '%s' is required", paramName)
} else {
return nil
}
}
if len(parts) != 1 {
return fmt.Errorf("multiple values for single value parameter '%s'", paramName)
}
err = BindStringToObject(parts[0], output)
}
if err != nil {
return err
}
if !required {
dv.Set(reflect.ValueOf(output))
}
return nil
case "deepObject":
if !explode {
return errors.New("deepObjects must be exploded")
}
return UnmarshalDeepObject(dest, paramName, queryParams)
case "spaceDelimited", "pipeDelimited":
return fmt.Errorf("query arguments of style '%s' aren't yet supported", style)
default:
return fmt.Errorf("style '%s' on parameter '%s' is invalid", style, paramName)
}
}
// This function reflects the destination structure, and pulls the value for
// each settable field from the given parameters map. This is to deal with the
// exploded form styled object which may occupy any number of parameter names.
// We don't try to be smart here, if the field exists as a query argument,
// set its value.
func bindParamsToExplodedObject(paramName string, values url.Values, dest interface{}) error {
// Dereference pointers to their destination values
binder, v, t := indirect(dest)
if binder != nil {
return BindStringToObject(values.Get(paramName), dest)
}
if t.Kind() != reflect.Struct {
return fmt.Errorf("unmarshaling query arg '%s' into wrong type", paramName)
}
for i := 0; i < t.NumField(); i++ {
fieldT := t.Field(i)
// Skip unsettable fields, such as internal ones.
if !v.Field(i).CanSet() {
continue
}
// Find the json annotation on the field, and use the json specified
// name if available, otherwise, just the field name.
tag := fieldT.Tag.Get("json")
fieldName := fieldT.Name
if tag != "" {
tagParts := strings.Split(tag, ",")
name := tagParts[0]
if name != "" {
fieldName = name
}
}
// At this point, we look up field name in the parameter list.
fieldVal, found := values[fieldName]
if found {
if len(fieldVal) != 1 {
return fmt.Errorf("field '%s' specified multiple times for param '%s'", fieldName, paramName)
}
err := BindStringToObject(fieldVal[0], v.Field(i).Addr().Interface())
if err != nil {
return fmt.Errorf("could not bind query arg '%s' to request object: %s'", paramName, err)
}
}
}
return nil
}
// indirect
func indirect(dest interface{}) (interface{}, reflect.Value, reflect.Type) {
v := reflect.ValueOf(dest)
if v.Type().NumMethod() > 0 && v.CanInterface() {
if u, ok := v.Interface().(Binder); ok {
return u, reflect.Value{}, nil
}
}
v = reflect.Indirect(v)
t := v.Type()
// special handling for custom types which might look like an object. We
// don't want to use object binding on them, but rather treat them as
// primitive types. time.Time{} is a unique case since we can't add a Binder
// to it without changing the underlying generated code.
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
return dest, reflect.Value{}, nil
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
return dest, reflect.Value{}, nil
}
return nil, v, t
}
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2019 DeepMap, Inc.
//
// 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 runtime
import (
"errors"
"fmt"
"reflect"
"strconv"
"time"
"github.com/deepmap/oapi-codegen/pkg/types"
)
// This function takes a string, and attempts to assign it to the destination
// interface via whatever type conversion is necessary. We have to do this
// via reflection instead of a much simpler type switch so that we can handle
// type aliases. This function was the easy way out, the better way, since we
// know the destination type each place that we use this, is to generate code
// to read each specific type.
func BindStringToObject(src string, dst interface{}) error {
var err error
v := reflect.ValueOf(dst)
t := reflect.TypeOf(dst)
// We need to dereference pointers
if t.Kind() == reflect.Ptr {
v = reflect.Indirect(v)
t = v.Type()
}
// The resulting type must be settable. reflect will catch issues like
// passing the destination by value.
if !v.CanSet() {
return errors.New("destination is not settable")
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
var val int64
val, err = strconv.ParseInt(src, 10, 64)
if err == nil {
v.SetInt(val)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
var val uint64
val, err = strconv.ParseUint(src, 10, 64)
if err == nil {
v.SetUint(val)
}
case reflect.String:
v.SetString(src)
err = nil
case reflect.Float64, reflect.Float32:
var val float64
val, err = strconv.ParseFloat(src, 64)
if err == nil {
v.SetFloat(val)
}
case reflect.Bool:
var val bool
val, err = strconv.ParseBool(src)
if err == nil {
v.SetBool(val)
}
case reflect.Struct:
// if this is not of type Time or of type Date look to see if this is of type Binder.
if dstType, ok := dst.(Binder); ok {
return dstType.Bind(src)
}
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
// Don't fail on empty string.
if src == "" {
return nil
}
// Time is a special case of a struct that we handle
parsedTime, err := time.Parse(time.RFC3339Nano, src)
if err != nil {
parsedTime, err = time.Parse(types.DateFormat, src)
if err != nil {
return fmt.Errorf("error parsing '%s' as RFC3339 or 2006-01-02 time: %s", src, err)
}
}
// So, assigning this gets a little fun. We have a value to the
// dereference destination. We can't do a conversion to
// time.Time because the result isn't assignable, so we need to
// convert pointers.
if t != reflect.TypeOf(time.Time{}) {
vPtr := v.Addr()
vtPtr := vPtr.Convert(reflect.TypeOf(&time.Time{}))
v = reflect.Indirect(vtPtr)
}
v.Set(reflect.ValueOf(parsedTime))
return nil
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
// Don't fail on empty string.
if src == "" {
return nil
}
parsedTime, err := time.Parse(types.DateFormat, src)
if err != nil {
return fmt.Errorf("error parsing '%s' as date: %s", src, err)
}
parsedDate := types.Date{Time: parsedTime}
// We have to do the same dance here to assign, just like with times
// above.
if t != reflect.TypeOf(types.Date{}) {
vPtr := v.Addr()
vtPtr := vPtr.Convert(reflect.TypeOf(&types.Date{}))
v = reflect.Indirect(vtPtr)
}
v.Set(reflect.ValueOf(parsedDate))
return nil
}
// We fall through to the error case below if we haven't handled the
// destination type above.
fallthrough
default:
// We've got a bunch of types unimplemented, don't fail silently.
err = fmt.Errorf("can not bind to destination of type: %s", t.Kind())
}
if err != nil {
return fmt.Errorf("error binding string parameter: %s", err)
}
return nil
}
+357
View File
@@ -0,0 +1,357 @@
package runtime
import (
"encoding/json"
"fmt"
"github.com/deepmap/oapi-codegen/pkg/types"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
)
func marshalDeepObject(in interface{}, path []string) ([]string, error) {
var result []string
switch t := in.(type) {
case []interface{}:
// For the array, we will use numerical subscripts of the form [x],
// in the same order as the array.
for i, iface := range t {
newPath := append(path, strconv.Itoa(i))
fields, err := marshalDeepObject(iface, newPath)
if err != nil {
return nil, errors.Wrap(err, "error traversing array")
}
result = append(result, fields...)
}
case map[string]interface{}:
// For a map, each key (field name) becomes a member of the path, and
// we recurse. First, sort the keys.
keys := make([]string, len(t))
i := 0
for k := range t {
keys[i] = k
i++
}
sort.Strings(keys)
// Now, for each key, we recursively marshal it.
for _, k := range keys {
newPath := append(path, k)
fields, err := marshalDeepObject(t[k], newPath)
if err != nil {
return nil, errors.Wrap(err, "error traversing map")
}
result = append(result, fields...)
}
default:
// Now, for a concrete value, we will turn the path elements
// into a deepObject style set of subscripts. [a, b, c] turns into
// [a][b][c]
prefix := "[" + strings.Join(path, "][") + "]"
result = []string{
prefix + fmt.Sprintf("=%v", t),
}
}
return result, nil
}
func MarshalDeepObject(i interface{}, paramName string) (string, error) {
// We're going to marshal to JSON and unmarshal into an interface{},
// which will use the json pkg to deal with all the field annotations. We
// can then walk the generic object structure to produce a deepObject. This
// isn't efficient and it would be more efficient to reflect on our own,
// but it's complicated, error-prone code.
buf, err := json.Marshal(i)
if err != nil {
return "", errors.Wrap(err, "failed to marshal input to JSON")
}
var i2 interface{}
err = json.Unmarshal(buf, &i2)
if err != nil {
return "", errors.Wrap(err, "failed to unmarshal JSON")
}
fields, err := marshalDeepObject(i2, nil)
if err != nil {
return "", errors.Wrap(err, "error traversing JSON structure")
}
// Prefix the param name to each subscripted field.
for i := range fields {
fields[i] = paramName + fields[i]
}
return strings.Join(fields, "&"), nil
}
type fieldOrValue struct {
fields map[string]fieldOrValue
value string
}
func (f *fieldOrValue) appendPathValue(path []string, value string) {
fieldName := path[0]
if len(path) == 1 {
f.fields[fieldName] = fieldOrValue{value: value}
return
}
pv, found := f.fields[fieldName]
if !found {
pv = fieldOrValue{
fields: make(map[string]fieldOrValue),
}
f.fields[fieldName] = pv
}
pv.appendPathValue(path[1:], value)
}
func makeFieldOrValue(paths [][]string, values []string) fieldOrValue {
f := fieldOrValue{
fields: make(map[string]fieldOrValue),
}
for i := range paths {
path := paths[i]
value := values[i]
f.appendPathValue(path, value)
}
return f
}
func UnmarshalDeepObject(dst interface{}, paramName string, params url.Values) error {
// Params are all the query args, so we need those that look like
// "paramName["...
var fieldNames []string
var fieldValues []string
searchStr := paramName + "["
for pName, pValues := range params {
if strings.HasPrefix(pName, searchStr) {
// trim the parameter name from the full name.
pName = pName[len(paramName):]
fieldNames = append(fieldNames, pName)
if len(pValues) != 1 {
return fmt.Errorf("%s has multiple values", pName)
}
fieldValues = append(fieldValues, pValues[0])
}
}
// Now, for each field, reconstruct its subscript path and value
paths := make([][]string, len(fieldNames))
for i, path := range fieldNames {
path = strings.TrimLeft(path, "[")
path = strings.TrimRight(path, "]")
paths[i] = strings.Split(path, "][")
}
fieldPaths := makeFieldOrValue(paths, fieldValues)
err := assignPathValues(dst, fieldPaths)
if err != nil {
return errors.Wrap(err, "error assigning value to destination")
}
return nil
}
// This returns a field name, either using the variable name, or the json
// annotation if that exists.
func getFieldName(f reflect.StructField) string {
n := f.Name
tag, found := f.Tag.Lookup("json")
if found {
// If we have a json field, and the first part of it before the
// first comma is non-empty, that's our field name.
parts := strings.Split(tag, ",")
if parts[0] != "" {
n = parts[0]
}
}
return n
}
// Create a map of field names that we'll see in the deepObject to reflect
// field indices on the given type.
func fieldIndicesByJsonTag(i interface{}) (map[string]int, error) {
t := reflect.TypeOf(i)
if t.Kind() != reflect.Struct {
return nil, errors.New("expected a struct as input")
}
n := t.NumField()
fieldMap := make(map[string]int)
for i := 0; i < n; i++ {
field := t.Field(i)
fieldName := getFieldName(field)
fieldMap[fieldName] = i
}
return fieldMap, nil
}
func assignPathValues(dst interface{}, pathValues fieldOrValue) error {
//t := reflect.TypeOf(dst)
v := reflect.ValueOf(dst)
iv := reflect.Indirect(v)
it := iv.Type()
switch it.Kind() {
case reflect.Slice:
sliceLength := len(pathValues.fields)
dstSlice := reflect.MakeSlice(it, sliceLength, sliceLength)
err := assignSlice(dstSlice, pathValues)
if err != nil {
return errors.Wrap(err, "error assigning slice")
}
iv.Set(dstSlice)
return nil
case reflect.Struct:
// Some special types we care about are structs. Handle them
// here. They may be redefined, so we need to do some hoop
// jumping. If the types are aliased, we need to type convert
// the pointer, then set the value of the dereference pointer.
// We check to see if the object implements the Binder interface first.
if dst, isBinder := v.Interface().(Binder); isBinder {
return dst.Bind(pathValues.value)
}
// Then check the legacy types
if it.ConvertibleTo(reflect.TypeOf(types.Date{})) {
var date types.Date
var err error
date.Time, err = time.Parse(types.DateFormat, pathValues.value)
if err != nil {
return errors.Wrap(err, "invalid date format")
}
dst := iv
if it != reflect.TypeOf(types.Date{}) {
// Types are aliased, convert the pointers.
ivPtr := iv.Addr()
aPtr := ivPtr.Convert(reflect.TypeOf(&types.Date{}))
dst = reflect.Indirect(aPtr)
}
dst.Set(reflect.ValueOf(date))
}
if it.ConvertibleTo(reflect.TypeOf(time.Time{})) {
var tm time.Time
var err error
tm, err = time.Parse(time.RFC3339Nano, pathValues.value)
if err != nil {
// Fall back to parsing it as a date.
tm, err = time.Parse(types.DateFormat, pathValues.value)
if err != nil {
return fmt.Errorf("error parsing tim as RFC3339 or 2006-01-02 time: %s", err)
}
return errors.Wrap(err, "invalid date format")
}
dst := iv
if it != reflect.TypeOf(time.Time{}) {
// Types are aliased, convert the pointers.
ivPtr := iv.Addr()
aPtr := ivPtr.Convert(reflect.TypeOf(&time.Time{}))
dst = reflect.Indirect(aPtr)
}
dst.Set(reflect.ValueOf(tm))
}
fieldMap, err := fieldIndicesByJsonTag(iv.Interface())
if err != nil {
return errors.Wrap(err, "failed enumerating fields")
}
for _, fieldName := range sortedFieldOrValueKeys(pathValues.fields) {
fieldValue := pathValues.fields[fieldName]
fieldIndex, found := fieldMap[fieldName]
if !found {
return fmt.Errorf("field [%s] is not present in destination object", fieldName)
}
field := iv.Field(fieldIndex)
err = assignPathValues(field.Addr().Interface(), fieldValue)
if err != nil {
return errors.Wrapf(err, "error assigning field [%s]", fieldName)
}
}
return nil
case reflect.Ptr:
// If we have a pointer after redirecting, it means we're dealing with
// an optional field, such as *string, which was passed in as &foo. We
// will allocate it if necessary, and call ourselves with a different
// interface.
dstVal := reflect.New(it.Elem())
dstPtr := dstVal.Interface()
err := assignPathValues(dstPtr, pathValues)
iv.Set(dstVal)
return err
case reflect.Bool:
val, err := strconv.ParseBool(pathValues.value)
if err != nil {
return fmt.Errorf("expected a valid bool, got %s", pathValues.value)
}
iv.SetBool(val)
return nil
case reflect.Float32:
val, err := strconv.ParseFloat(pathValues.value, 32)
if err != nil {
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
}
iv.SetFloat(val)
return nil
case reflect.Float64:
val, err := strconv.ParseFloat(pathValues.value, 64)
if err != nil {
return fmt.Errorf("expected a valid float, got %s", pathValues.value)
}
iv.SetFloat(val)
return nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := strconv.ParseInt(pathValues.value, 10, 64)
if err != nil {
return fmt.Errorf("expected a valid int, got %s", pathValues.value)
}
iv.SetInt(val)
return nil
case reflect.String:
iv.SetString(pathValues.value)
return nil
default:
return errors.New("unhandled type: " + it.String())
}
}
func assignSlice(dst reflect.Value, pathValues fieldOrValue) error {
// Gather up the values
nValues := len(pathValues.fields)
values := make([]string, nValues)
// We expect to have consecutive array indices in the map
for i := 0; i < nValues; i++ {
indexStr := strconv.Itoa(i)
fv, found := pathValues.fields[indexStr]
if !found {
return errors.New("array deepObjects must have consecutive indices")
}
values[i] = fv.value
}
// This could be cleaner, but we can call into assignPathValues to
// avoid recreating this logic.
for i := 0; i < nValues; i++ {
dstElem := dst.Index(i).Addr()
err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]})
if err != nil {
return errors.Wrap(err, "error binding array")
}
}
return nil
}
func sortedFieldOrValueKeys(m map[string]fieldOrValue) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
+352
View File
@@ -0,0 +1,352 @@
// Copyright 2019 DeepMap, Inc.
//
// 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 runtime
import (
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/deepmap/oapi-codegen/pkg/types"
)
// Given an input value, such as a primitive type, array or object, turn it
// into a parameter based on style/explode definition.
func StyleParam(style string, explode bool, paramName string, value interface{}) (string, error) {
t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
// Things may be passed in by pointer, we need to dereference, so return
// error on nil.
if t.Kind() == reflect.Ptr {
if v.IsNil() {
return "", fmt.Errorf("value is a nil pointer")
}
v = reflect.Indirect(v)
t = v.Type()
}
switch t.Kind() {
case reflect.Slice:
n := v.Len()
sliceVal := make([]interface{}, n)
for i := 0; i < n; i++ {
sliceVal[i] = v.Index(i).Interface()
}
return styleSlice(style, explode, paramName, sliceVal)
case reflect.Struct:
return styleStruct(style, explode, paramName, value)
case reflect.Map:
return styleMap(style, explode, paramName, value)
default:
return stylePrimitive(style, explode, paramName, value)
}
}
func styleSlice(style string, explode bool, paramName string, values []interface{}) (string, error) {
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(values, paramName)
}
var prefix string
var separator string
switch style {
case "simple":
separator = ","
case "label":
prefix = "."
if explode {
separator = "."
} else {
separator = ","
}
case "matrix":
prefix = fmt.Sprintf(";%s=", paramName)
if explode {
separator = prefix
} else {
separator = ","
}
case "form":
prefix = fmt.Sprintf("%s=", paramName)
if explode {
separator = "&" + prefix
} else {
separator = ","
}
case "spaceDelimited":
prefix = fmt.Sprintf("%s=", paramName)
if explode {
separator = "&" + prefix
} else {
separator = " "
}
case "pipeDelimited":
prefix = fmt.Sprintf("%s=", paramName)
if explode {
separator = "&" + prefix
} else {
separator = "|"
}
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
// We're going to assume here that the array is one of simple types.
var err error
parts := make([]string, len(values))
for i, v := range values {
parts[i], err = primitiveToString(v)
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
}
}
return prefix + strings.Join(parts, separator), nil
}
func sortedKeys(strMap map[string]string) []string {
keys := make([]string, len(strMap))
i := 0
for k := range strMap {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
// This is a special case. The struct may be a date or time, in
// which case, marshal it in correct format.
func marshalDateTimeValue(value interface{}) (string, bool) {
v := reflect.Indirect(reflect.ValueOf(value))
t := v.Type()
if t.ConvertibleTo(reflect.TypeOf(time.Time{})) {
tt := v.Convert(reflect.TypeOf(time.Time{}))
timeVal := tt.Interface().(time.Time)
return timeVal.Format(time.RFC3339Nano), true
}
if t.ConvertibleTo(reflect.TypeOf(types.Date{})) {
d := v.Convert(reflect.TypeOf(types.Date{}))
dateVal := d.Interface().(types.Date)
return dateVal.Format(types.DateFormat), true
}
return "", false
}
func styleStruct(style string, explode bool, paramName string, value interface{}) (string, error) {
if timeVal, ok := marshalDateTimeValue(value); ok {
styledVal, err := stylePrimitive(style, explode, paramName, timeVal)
if err != nil {
return "", errors.Wrap(err, "failed to style time")
}
return styledVal, nil
}
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(value, paramName)
}
// Otherwise, we need to build a dictionary of the struct's fields. Each
// field may only be a primitive value.
v := reflect.ValueOf(value)
t := reflect.TypeOf(value)
fieldDict := make(map[string]string)
for i := 0; i < t.NumField(); i++ {
fieldT := t.Field(i)
// Find the json annotation on the field, and use the json specified
// name if available, otherwise, just the field name.
tag := fieldT.Tag.Get("json")
fieldName := fieldT.Name
if tag != "" {
tagParts := strings.Split(tag, ",")
name := tagParts[0]
if name != "" {
fieldName = name
}
}
f := v.Field(i)
// Unset optional fields will be nil pointers, skip over those.
if f.Type().Kind() == reflect.Ptr && f.IsNil() {
continue
}
str, err := primitiveToString(f.Interface())
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, fieldDict)
}
func styleMap(style string, explode bool, paramName string, value interface{}) (string, error) {
if style == "deepObject" {
if !explode {
return "", errors.New("deepObjects must be exploded")
}
return MarshalDeepObject(value, paramName)
}
dict, ok := value.(map[string]interface{})
if !ok {
return "", errors.New("map not of type map[string]interface{}")
}
fieldDict := make(map[string]string)
for fieldName, value := range dict {
str, err := primitiveToString(value)
if err != nil {
return "", fmt.Errorf("error formatting '%s': %s", paramName, err)
}
fieldDict[fieldName] = str
}
return processFieldDict(style, explode, paramName, fieldDict)
}
func processFieldDict(style string, explode bool, paramName string, fieldDict map[string]string) (string, error) {
var parts []string
// This works for everything except deepObject. We'll handle that one
// separately.
if style != "deepObject" {
if explode {
for _, k := range sortedKeys(fieldDict) {
v := fieldDict[k]
parts = append(parts, k+"="+v)
}
} else {
for _, k := range sortedKeys(fieldDict) {
v := fieldDict[k]
parts = append(parts, k)
parts = append(parts, v)
}
}
}
var prefix string
var separator string
switch style {
case "simple":
separator = ","
case "label":
prefix = "."
if explode {
separator = prefix
} else {
separator = ","
}
case "matrix":
if explode {
separator = ";"
prefix = ";"
} else {
separator = ","
prefix = fmt.Sprintf(";%s=", paramName)
}
case "form":
if explode {
separator = "&"
} else {
prefix = fmt.Sprintf("%s=", paramName)
separator = ","
}
case "deepObject":
{
if !explode {
return "", fmt.Errorf("deepObject parameters must be exploded")
}
for _, k := range sortedKeys(fieldDict) {
v := fieldDict[k]
part := fmt.Sprintf("%s[%s]=%s", paramName, k, v)
parts = append(parts, part)
}
separator = "&"
}
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
return prefix + strings.Join(parts, separator), nil
}
func stylePrimitive(style string, explode bool, paramName string, value interface{}) (string, error) {
strVal, err := primitiveToString(value)
if err != nil {
return "", err
}
var prefix string
switch style {
case "simple":
case "label":
prefix = "."
case "matrix":
prefix = fmt.Sprintf(";%s=", paramName)
case "form":
prefix = fmt.Sprintf("%s=", paramName)
default:
return "", fmt.Errorf("unsupported style '%s'", style)
}
return prefix + url.QueryEscape(strVal), nil
}
// Converts a primitive value to a string. We need to do this based on the
// Kind of an interface, not the Type to work with aliased types.
func primitiveToString(value interface{}) (string, error) {
var output string
// Values may come in by pointer for optionals, so make sure to dereferene.
v := reflect.Indirect(reflect.ValueOf(value))
t := v.Type()
kind := t.Kind()
switch kind {
case reflect.Int8, reflect.Int32, reflect.Int64, reflect.Int:
output = strconv.FormatInt(v.Int(), 10)
case reflect.Float32, reflect.Float64:
output = strconv.FormatFloat(v.Float(), 'f', -1, 64)
case reflect.Bool:
if v.Bool() {
output = "true"
} else {
output = "false"
}
case reflect.String:
output = v.String()
default:
return "", fmt.Errorf("unsupported type %s", reflect.TypeOf(value).String())
}
return output, nil
}
+30
View File
@@ -0,0 +1,30 @@
package types
import (
"encoding/json"
"time"
)
const DateFormat = "2006-01-02"
type Date struct {
time.Time
}
func (d Date) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Time.Format(DateFormat))
}
func (d *Date) UnmarshalJSON(data []byte) error {
var dateStr string
err := json.Unmarshal(data, &dateStr)
if err != nil {
return err
}
parsed, err := time.Parse(DateFormat, dateStr)
if err != nil {
return err
}
d.Time = parsed
return nil
}
+27
View File
@@ -0,0 +1,27 @@
package types
import (
"encoding/json"
"errors"
)
type Email string
func (e Email) MarshalJSON() ([]byte, error) {
if !emailRegex.MatchString(string(e)) {
return nil, errors.New("email: failed to pass regex validation")
}
return json.Marshal(string(e))
}
func (e *Email) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
if !emailRegex.MatchString(s) {
return errors.New("email: failed to pass regex validation")
}
*e = Email(s)
return nil
}
+11
View File
@@ -0,0 +1,11 @@
package types
import "regexp"
const (
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
)
var (
emailRegex = regexp.MustCompile(emailRegexString)
)
-31
View File
@@ -1,31 +0,0 @@
version: "2"
checks:
argument-count:
enabled: false
complex-logic:
enabled: false
file-lines:
enabled: false
method-complexity:
enabled: false
method-count:
enabled: false
method-lines:
enabled: false
nested-control-flow:
enabled: false
return-statements:
enabled: false
similar-code:
enabled: false
identical-code:
enabled: false
plugins:
gofmt:
enabled: true
golint:
enabled: true
govet:
enabled: true
+4 -1
View File
@@ -1,3 +1,6 @@
run:
timeout: 10m
linters-settings:
golint:
min-confidence: 0.3
@@ -9,8 +12,8 @@ linters-settings:
linters:
enable:
- deadcode
- dupl
- errcheck
- gocritic
- gocyclo
- goimports
- golint
-25
View File
@@ -1,25 +0,0 @@
{
"Test": false,
"Enable": [
"deadcode",
"errcheck",
"golint",
"gosimple",
"gotype",
"ineffassign",
"interfacer",
"misspell",
"structcheck",
"unconvert",
"varcheck",
"vet",
"vetshadow"
],
"Disable": [
"goconst",
"gocyclo",
"gosec",
"maligned"
],
"Skip": ["vendor"]
}
-49
View File
@@ -1,49 +0,0 @@
language: go
dist: xenial
sudo: required
go:
- "1.7.x"
- "1.8.x"
- "1.9.x"
- "1.10.x"
- "1.11.x"
- "1.12.x"
- tip
env:
- GOLANGCI_LINT_VERSION=1.15.0 GO111MODULES=on
cache: apt
addons:
apt:
update: true
packages:
- rpm
install:
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $GOPATH/bin v${GOLANGCI_LINT_VERSION}
- npm i codeclimate-test-reporter
- '[ "$(echo "$TRAVIS_GO_VERSION" | perl -pe "s/\\.[x\\d]+$//")" = "1.11" ] && go mod vendor || go get -u github.com/gofrs/uuid'
before_script:
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
script:
- go test -race -coverprofile=c.out -covermode=atomic .
after_script:
- ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
jobs:
include:
- stage: golangci-lint
go: 1.10.x
if: type = pull_request
script:
- go get -u github.com/gofrs/uuid
- golangci-lint run .
+249
View File
@@ -1,6 +1,255 @@
Changelog
=========
0.43.1
------
- change: in `NewClient()`, the `v2.Client` embedded in the `Client` struct doesn't inherit the custom `http.Client` set using `WithHTTPClient()`.
0.43.0
------
- change: [Exoscale API V2](https://openapi-v2.exoscale.com/) related code has been relocated under the `github.com/exoscale/egoscale/v2` package.
Note: `egoscale.Client` embeds a `v2.Client` initialized implicitly as a convenience.
0.42.0
------
- feature: new `SKSNodepool.AntiAffinityGroupIDs` field
- change: `SKSCluster.Level` field renamed as `SKSCluster.ServiceLevel`
0.41.0
------
- feature: new method `ListZones()`
0.40.1
------
- Improve API v2 async job tests and error reporting (#466)
0.40.0
------
- feature: new method `UpgradeSKSCluster()`
- feature: new fields `SKSCluster.Level` and `SKSCluster.CNI`
- change: `SKSCluster.EnableExoscaleCloudController` replaced with `SKSCluster.AddOns`
0.39.1
------
- fix: add missing `UpdateVirtualMachineSecurityGroups` operation metadata
0.39.0
------
- feature: add `UpdateVirtualMachineSecurityGroups` operation (#464)
0.38.0
------
- feature: add `SKSCluster.EvictNodepoolMembers()` and `ListSKSClusterVersions()` methods
0.37.1
------
- fix: `UpdateIPAddress.HealthcheckTLSSkipVerify` field always set to `false` (#462)
0.37.0
------
- feature: `NewClient()` now accepts options (460)
- fix: NLB service healthcheck TLS SNI bug (#461)
0.36.2
------
- fix: `CreateInstancePool.AntiAffinityGroupIDs` field is optional (#459)
0.36.1
------
- feature: add support for Exoscale Cloud Controller in SKS clusters
- fix: add missing tests for SKS Nodepools Security Groups
0.36.0
------
- feature: add support for Anti-Affinity Groups to Instance Pools
- feature: add support for Security Groups to SKS Nodepools
0.35.3
------
- Fix typo in version.go
0.35.2
------
- Improve API v2 errors handling (#455)
0.35.1
------
- fix: various SKS-related bugs (#454)
0.35.0
------
- feature: add support for SKS resources (#453)
0.34.0
------
- change: `BucketUsage.Usage` is now an `int64` (#451)
0.33.2
------
- fix: make `GetWithContext` return more relevant errors (#450)
0.33.1
------
- fix: `UpdateNetworkLoadBalancer` call panicking following a public API change
0.33.0
------
- feature: add support for Network Load Balancer service HTTPS health checking (#449)
0.32.0
------
- feature: add support for Instance Pool root disk size update (#448)
0.31.2
------
- fix: add missing TLS-specific parameters to `AssociateIPAddress`
0.31.1
------
- fix: Instance Pool IPv6 flag handling
0.31.0
------
- feature: add support for IPv6 in Instance Pools (#446)
0.30.0
------
- feature: add new TLS-specific parameters to managed EIP
0.29.0
------
- feature: `ListVirtualMachines` call to allow searching by `ManagerID` (#442)
- fix: remove duplicate `User-Agent` HTTP header in Runstatus calls
- tests: `*NetworkLoadBalancer*` calls are now tested using HTTP mocks
- codegen: `internal/v2` updated
0.28.1
------
- fix: Fix `ListVolumes` call to allow searching by ID (#440)
0.28.0
------
- feature: add `Manager`/`ManagerID` fields to `VirtualMachine` structure (#438)
- fix: HTTP request User Agent header handling (#439)
0.27.0
------
- feature: Add `evictInstancePoolMembers` call to Instance Pool (#437)
0.26.6
------
- change: Add support for Compute instance templates boot mode (#436)
0.26.5
------
- fix: bug in the ListNetworkLoadBalancers call (#435)
0.26.4
------
- Fixing typo in previous release
0.26.3
------
- change: updated API V2 async operation code (#434)
0.26.2
------
- change: updated OpenAPI code-generated API V2 bindings
0.26.1
------
- change: the `DisplayText` property of `RegisterCustomTemplate` is now optional (#433)
0.26.0
------
- feature: Add support for Network Load Balancer resources (#432)
0.25.0
------
- feature: Add support for `listBucketsUsage` (#431)
- change: Switch CI to Github Actions (#430)
0.24.0
------
- feature: Add export snapshot implementation (#427)
- feature: Add support for public API V2 (#425)
- change: Switch module to Go 1.14 (#429)
- change: Travis CI: set minimum Go version to 1.13
- doc: Annotate API doc regarding use of tags (#423)
- tests: fix request client timeout handling (#422)
0.23.0
------
- change: Add `Resources` field to `APIKey` (#420)
0.22.0
------
- change: Remove all references to Network Offerings (#418)
0.21.0
------
- feature: add const `NotFound` 404 on type `ErrorCode` (#417)
0.20.1
------
- fix: update the `ListAPIKeysResponse` field (#415)
0.20.0
------
- feature: Add Instance pool implementation (#410)
- feature: Add IAM implementation (#411)
0.19.0
------
- feature: add field `Description` on type `IPAddress` (#413)
- change: add Json tag `omitempty` on field `TemplateFilter` in type `ListTemplates` (#412)
0.18.1
------
+2 -1
View File
@@ -5,7 +5,8 @@ description: the Go library for Exoscale
<a href="https://gopherize.me/gopher/9c1bc7cfe1d84cf43e477dbfc4aa86332065f1fd"><img src="gopher.png" align="right" alt=""></a>
[![Build Status](https://travis-ci.org/exoscale/egoscale.svg?branch=master)](https://travis-ci.org/exoscale/egoscale) [![Maintainability](https://api.codeclimate.com/v1/badges/fcab3b624b7d3ca96a9d/maintainability)](https://codeclimate.com/github/exoscale/egoscale/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/fcab3b624b7d3ca96a9d/test_coverage)](https://codeclimate.com/github/exoscale/egoscale/test_coverage) [![GoDoc](https://godoc.org/github.com/exoscale/egoscale?status.svg)](https://godoc.org/github.com/exoscale/egoscale) [![Go Report Card](https://goreportcard.com/badge/github.com/exoscale/egoscale)](https://goreportcard.com/report/github.com/exoscale/egoscale)
[![Actions Status](https://github.com/exoscale/egoscale/workflows/CI/badge.svg?branch=master)](https://github.com/exoscale/egoscale/actions?query=workflow%3ACI+branch%3Amaster)
[![GoDoc](https://godoc.org/github.com/exoscale/egoscale?status.svg)](https://godoc.org/github.com/exoscale/egoscale) [![Go Report Card](https://goreportcard.com/badge/github.com/exoscale/egoscale)](https://goreportcard.com/report/github.com/exoscale/egoscale)
A wrapper for the [Exoscale public cloud](https://www.exoscale.com) API.
+35 -26
View File
@@ -8,13 +8,15 @@ import (
// Healthcheck represents an Healthcheck attached to an IP
type Healthcheck struct {
Interval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
Mode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp' or 'http'"`
Path string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
Port int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
StrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
StrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
Timeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
Interval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
Mode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp' or 'http'"`
Path string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
Port int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
StrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
StrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
Timeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
TLSSNI string `json:"tls-sni,omitempty" doc:"healthcheck definition: server name to present for HTTPS checks"`
TLSSkipVerify bool `json:"tls-skip-verify" doc:"healthcheck definition: bypass certificate chain verification for HTTPS checks"`
}
// IPAddress represents an IP Address
@@ -23,6 +25,7 @@ type IPAddress struct {
Associated string `json:"associated,omitempty" doc:"date the public IP address was associated"`
AssociatedNetworkID *UUID `json:"associatednetworkid,omitempty" doc:"the ID of the Network associated with the IP address"`
AssociatedNetworkName string `json:"associatednetworkname,omitempty" doc:"the name of the Network associated with the IP address"`
Description string `json:"description,omitempty" doc:"The IP address description."`
ForVirtualNetwork bool `json:"forvirtualnetwork,omitempty" doc:"the virtual network for the IP address"`
Healthcheck *Healthcheck `json:"healthcheck,omitempty" doc:"The IP healthcheck configuration"`
ID *UUID `json:"id,omitempty" doc:"public IP address id"`
@@ -89,15 +92,18 @@ func (ipaddress IPAddress) Delete(ctx context.Context, client *Client) error {
// AssociateIPAddress (Async) represents the IP creation
type AssociateIPAddress struct {
HealthcheckInterval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
HealthcheckMode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp' or 'http'"`
HealthcheckPath string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
HealthcheckPort int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
HealthcheckStrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
HealthcheckStrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
HealthcheckTimeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
ZoneID *UUID `json:"zoneid,omitempty" doc:"the ID of the availability zone you want to acquire a public IP address from"`
_ bool `name:"associateIpAddress" description:"Acquires and associates a public IP to an account."`
Description string `json:"description,omitempty" doc:"The IP address description."`
HealthcheckInterval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
HealthcheckMode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp', 'http', or 'https'"`
HealthcheckPath string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http' or 'https', ignored otherwise."`
HealthcheckPort int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
HealthcheckStrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
HealthcheckStrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
HealthcheckTimeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
HealthcheckTLSSkipVerify bool `json:"tls-skip-verify,omitempty" doc:"healthcheck definition: skip TLS verification for HTTPS checks. Default: false"`
HealthcheckTLSSNI string `json:"tls-sni,omitempty" doc:"healthcheck definition: server name to present for HTTPS checks. Default: no server name is presented"`
ZoneID *UUID `json:"zoneid,omitempty" doc:"the ID of the availability zone you want to acquire a public IP address from"`
_ bool `name:"associateIpAddress" description:"Acquires and associates a public IP to an account."`
}
// Response returns the struct to unmarshal
@@ -128,15 +134,18 @@ func (DisassociateIPAddress) AsyncResponse() interface{} {
// UpdateIPAddress (Async) represents the IP modification
type UpdateIPAddress struct {
HealthcheckInterval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
HealthcheckMode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp' or 'http'"`
HealthcheckPath string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
HealthcheckPort int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
HealthcheckStrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
HealthcheckStrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
HealthcheckTimeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
ID *UUID `json:"id" doc:"the id of the public IP address to update"`
_ bool `name:"updateIpAddress" description:"Updates an IP address"`
Description string `json:"description,omitempty" doc:"The IP address description."`
HealthcheckInterval int64 `json:"interval,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 10, minimum: 5"`
HealthcheckMode string `json:"mode,omitempty" doc:"healthcheck definition: healthcheck mode can be either 'tcp', 'http', or 'https'"`
HealthcheckPath string `json:"path,omitempty" doc:"healthcheck definition: the path against which the 'http' healthcheck will be performed. Required if mode is 'http', ignored otherwise."`
HealthcheckPort int64 `json:"port,omitempty" doc:"healthcheck definition: the port against which the healthcheck will be performed. Required if a 'mode' is provided."`
HealthcheckStrikesFail int64 `json:"strikes-fail,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'dead'. Default: 3"`
HealthcheckStrikesOk int64 `json:"strikes-ok,omitempty" doc:"healthcheck definition: number of times to retry before declaring the healthcheck 'alive'. Default: 2"`
HealthcheckTimeout int64 `json:"timeout,omitempty" doc:"healthcheck definition: time in seconds to wait for each check. Default: 2, cannot be greater than interval."`
HealthcheckTLSSNI string `json:"tls-sni,omitempty" doc:"healthcheck definition: server name to present for HTTPS checks"`
HealthcheckTLSSkipVerify bool `json:"tls-skip-verify,omitempty" doc:"healthcheck definition: bypass certificate chain verification for HTTPS checks"`
ID *UUID `json:"id" doc:"the id of the public IP address to update"`
_ bool `name:"updateIpAddress" description:"Updates an IP address"`
}
// Response returns the struct to unmarshal
@@ -166,7 +175,7 @@ type ListPublicIPAddresses struct {
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
PhysicalNetworkID *UUID `json:"physicalnetworkid,omitempty" doc:"lists all public IP addresses by physical network id"`
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs)"`
Tags []ResourceTag `json:"tags,omitempty" doc:"List resources by tags (key/value pairs). Note: multiple tags are OR'ed, not AND'ed."`
VlanID *UUID `json:"vlanid,omitempty" doc:"lists all public IP addresses by VLAN ID"`
ZoneID *UUID `json:"zoneid,omitempty" doc:"lists all public IP addresses by Zone ID"`
_ bool `name:"listPublicIpAddresses" description:"Lists all public ip addresses"`
+44 -44
View File
@@ -6,20 +6,20 @@ import (
"net/url"
)
// AffinityGroup represents an (anti-)affinity group
// AffinityGroup represents an Affinity Group.
//
// Affinity and Anti-Affinity groups provide a way to influence where VMs should run.
// Affinity and Anti-Affinity Groups provide a way to influence where VMs should run.
// See: http://docs.cloudstack.apache.org/projects/cloudstack-administration/en/stable/virtual_machines.html#affinity-groups
type AffinityGroup struct {
Account string `json:"account,omitempty" doc:"the account owning the affinity group"`
Description string `json:"description,omitempty" doc:"the description of the affinity group"`
ID *UUID `json:"id,omitempty" doc:"the ID of the affinity group"`
Name string `json:"name,omitempty" doc:"the name of the affinity group"`
Type string `json:"type,omitempty" doc:"the type of the affinity group"`
VirtualMachineIDs []UUID `json:"virtualmachineIds,omitempty" doc:"virtual machine Ids associated with this affinity group"`
Account string `json:"account,omitempty" doc:"the account owning the Affinity Group"`
Description string `json:"description,omitempty" doc:"the description of the Affinity Group"`
ID *UUID `json:"id,omitempty" doc:"the ID of the Affinity Group"`
Name string `json:"name,omitempty" doc:"the name of the Affinity Group"`
Type string `json:"type,omitempty" doc:"the type of the Affinity Group"`
VirtualMachineIDs []UUID `json:"virtualmachineIds,omitempty" doc:"virtual machine IDs associated with this Affinity Group"`
}
// ListRequest builds the ListAffinityGroups request
// ListRequest builds the ListAffinityGroups request.
func (ag AffinityGroup) ListRequest() (ListCommand, error) {
return &ListAffinityGroups{
ID: ag.ID,
@@ -27,7 +27,7 @@ func (ag AffinityGroup) ListRequest() (ListCommand, error) {
}, nil
}
// Delete removes the given Affinity Group
// Delete deletes the given Affinity Group.
func (ag AffinityGroup) Delete(ctx context.Context, client *Client) error {
if ag.ID == nil && ag.Name == "" {
return fmt.Errorf("an Affinity Group may only be deleted using ID or Name")
@@ -44,114 +44,114 @@ func (ag AffinityGroup) Delete(ctx context.Context, client *Client) error {
return client.BooleanRequestWithContext(ctx, req)
}
// AffinityGroupType represent an affinity group type
// AffinityGroupType represent an Affinity Group type.
type AffinityGroupType struct {
Type string `json:"type,omitempty" doc:"the type of the affinity group"`
Type string `json:"type,omitempty" doc:"the type of the Affinity Group"`
}
// CreateAffinityGroup (Async) represents a new (anti-)affinity group
// CreateAffinityGroup (Async) represents a new Affinity Group.
type CreateAffinityGroup struct {
Description string `json:"description,omitempty" doc:"Optional description of the affinity group"`
Name string `json:"name,omitempty" doc:"Name of the affinity group"`
Type string `json:"type" doc:"Type of the affinity group from the available affinity/anti-affinity group types"`
_ bool `name:"createAffinityGroup" description:"Creates an affinity/anti-affinity group"`
Description string `json:"description,omitempty" doc:"Optional description of the Affinity Group"`
Name string `json:"name" doc:"Name of the Affinity Group"`
Type string `json:"type" doc:"Type of the Affinity Group from the available Affinity Group Group types"`
_ bool `name:"createAffinityGroup" description:"Creates an Affinity Group Group"`
}
func (req CreateAffinityGroup) onBeforeSend(params url.Values) error {
// Name must be set, but can be empty
// Name must be set, but can be empty.
if req.Name == "" {
params.Set("name", "")
}
return nil
}
// Response returns the struct to unmarshal
// Response returns the struct to unmarshal.
func (CreateAffinityGroup) Response() interface{} {
return new(AsyncJobResult)
}
// AsyncResponse returns the struct to unmarshal the async job
// AsyncResponse returns the struct to unmarshal the async job.
func (CreateAffinityGroup) AsyncResponse() interface{} {
return new(AffinityGroup)
}
// UpdateVMAffinityGroup (Async) represents a modification of a (anti-)affinity group
// UpdateVMAffinityGroup (Async) represents a modification of an Affinity Group.
type UpdateVMAffinityGroup struct {
ID *UUID `json:"id" doc:"The ID of the virtual machine"`
AffinityGroupIDs []UUID `json:"affinitygroupids,omitempty" doc:"comma separated list of affinity groups id that are going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupnames parameter"`
AffinityGroupNames []string `json:"affinitygroupnames,omitempty" doc:"comma separated list of affinity groups names that are going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupids parameter"`
_ bool `name:"updateVMAffinityGroup" description:"Updates the affinity/anti-affinity group associations of a virtual machine. The VM has to be stopped and restarted for the new properties to take effect."`
AffinityGroupIDs []UUID `json:"affinitygroupids,omitempty" doc:"comma separated list of Affinity Groups id that are going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupnames parameter"`
AffinityGroupNames []string `json:"affinitygroupnames,omitempty" doc:"comma separated list of Affinity Groups names that are going to be applied to the virtual machine. Should be passed only when vm is created from a zone with Basic Network support. Mutually exclusive with securitygroupids parameter"`
_ bool `name:"updateVMAffinityGroup" description:"Updates the Affinity Group Group associations of a virtual machine. The VM has to be stopped and restarted for the new properties to take effect."`
}
func (req UpdateVMAffinityGroup) onBeforeSend(params url.Values) error {
// Either AffinityGroupIDs or AffinityGroupNames must be set
// Either AffinityGroupIDs or AffinityGroupNames must be set.
if len(req.AffinityGroupIDs) == 0 && len(req.AffinityGroupNames) == 0 {
params.Set("affinitygroupids", "")
}
return nil
}
// Response returns the struct to unmarshal
// Response returns the struct to unmarshal.
func (UpdateVMAffinityGroup) Response() interface{} {
return new(AsyncJobResult)
}
// AsyncResponse returns the struct to unmarshal the async job
// AsyncResponse returns the struct to unmarshal the async job.
func (UpdateVMAffinityGroup) AsyncResponse() interface{} {
return new(VirtualMachine)
}
// DeleteAffinityGroup (Async) represents an (anti-)affinity group to be deleted
// DeleteAffinityGroup (Async) represents an Affinity Group to be deleted.
type DeleteAffinityGroup struct {
ID *UUID `json:"id,omitempty" doc:"The ID of the affinity group. Mutually exclusive with name parameter"`
Name string `json:"name,omitempty" doc:"The name of the affinity group. Mutually exclusive with ID parameter"`
_ bool `name:"deleteAffinityGroup" description:"Deletes affinity group"`
ID *UUID `json:"id,omitempty" doc:"The ID of the Affinity Group. Mutually exclusive with name parameter"`
Name string `json:"name,omitempty" doc:"The name of the Affinity Group. Mutually exclusive with ID parameter"`
_ bool `name:"deleteAffinityGroup" description:"Deletes Affinity Group"`
}
// Response returns the struct to unmarshal
// Response returns the struct to unmarshal.
func (DeleteAffinityGroup) Response() interface{} {
return new(AsyncJobResult)
}
// AsyncResponse returns the struct to unmarshal the async job
// AsyncResponse returns the struct to unmarshal the async job.
func (DeleteAffinityGroup) AsyncResponse() interface{} {
return new(BooleanResponse)
}
//go:generate go run generate/main.go -interface=Listable ListAffinityGroups
// ListAffinityGroups represents an (anti-)affinity groups search
// ListAffinityGroups represents an Affinity Groups search.
type ListAffinityGroups struct {
ID *UUID `json:"id,omitempty" doc:"List the affinity group by the ID provided"`
ID *UUID `json:"id,omitempty" doc:"List the Affinity Group by the ID provided"`
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
Name string `json:"name,omitempty" doc:"Lists affinity groups by name"`
Name string `json:"name,omitempty" doc:"Lists Affinity Groups by name"`
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
Type string `json:"type,omitempty" doc:"Lists affinity groups by type"`
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"Lists affinity groups by virtual machine ID"`
_ bool `name:"listAffinityGroups" description:"Lists affinity groups"`
Type string `json:"type,omitempty" doc:"Lists Affinity Groups by type"`
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"Lists Affinity Groups by virtual machine ID"`
_ bool `name:"listAffinityGroups" description:"Lists Affinity Groups"`
}
// ListAffinityGroupsResponse represents a list of (anti-)affinity groups
// ListAffinityGroupsResponse represents a list of Affinity Groups.
type ListAffinityGroupsResponse struct {
Count int `json:"count"`
AffinityGroup []AffinityGroup `json:"affinitygroup"`
}
// ListAffinityGroupTypes represents an (anti-)affinity groups search
// ListAffinityGroupTypes represents an Affinity Groups types search.
type ListAffinityGroupTypes struct {
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
_ bool `name:"listAffinityGroupTypes" description:"Lists affinity group types available"`
_ bool `name:"listAffinityGroupTypes" description:"Lists Affinity Group types available"`
}
// Response returns the struct to unmarshal
// Response returns the struct to unmarshal.
func (ListAffinityGroupTypes) Response() interface{} {
return new(ListAffinityGroupTypesResponse)
}
// ListAffinityGroupTypesResponse represents a list of (anti-)affinity group types
// ListAffinityGroupTypesResponse represents a list of Affinity Group types.
type ListAffinityGroupTypesResponse struct {
Count int `json:"count"`
AffinityGroupType []AffinityGroupType `json:"affinitygrouptype"`
+5 -5
View File
@@ -4,12 +4,12 @@ package egoscale
import "fmt"
// Response returns the struct to unmarshal
// Response returns the struct to unmarshal.
func (ListAffinityGroups) Response() interface{} {
return new(ListAffinityGroupsResponse)
}
// ListRequest returns itself
// ListRequest returns itself.
func (ls *ListAffinityGroups) ListRequest() (ListCommand, error) {
if ls == nil {
return nil, fmt.Errorf("%T cannot be nil", ls)
@@ -17,17 +17,17 @@ func (ls *ListAffinityGroups) ListRequest() (ListCommand, error) {
return ls, nil
}
// SetPage sets the current apge
// SetPage sets the current page.
func (ls *ListAffinityGroups) SetPage(page int) {
ls.Page = page
}
// SetPageSize sets the page size
// SetPageSize sets the page size.
func (ls *ListAffinityGroups) SetPageSize(pageSize int) {
ls.PageSize = pageSize
}
// Each triggers the callback for each, valid answer or any non 404 issue
// Each triggers the callback for each, valid answer or any non 404 issue.
func (ListAffinityGroups) Each(resp interface{}, callback IterateItemFunc) {
items, ok := resp.(*ListAffinityGroupsResponse)
if !ok {
+103
View File
@@ -0,0 +1,103 @@
package egoscale
import (
"context"
"fmt"
"net/url"
)
// AntiAffinityGroup represents an Anti-Affinity Group.
type AntiAffinityGroup struct {
Account string `json:"account,omitempty" doc:"the account owning the Anti-Affinity Group"`
Description string `json:"description,omitempty" doc:"the description of the Anti-Affinity Group"`
ID *UUID `json:"id,omitempty" doc:"the ID of the Anti-Affinity Group"`
Name string `json:"name,omitempty" doc:"the name of the Anti-Affinity Group"`
Type string `json:"type,omitempty" doc:"the type of the Anti-Affinity Group"`
VirtualMachineIDs []UUID `json:"virtualmachineIds,omitempty" doc:"virtual machine IDs associated with this Anti-Affinity Group"`
}
// ListRequest builds the ListAntiAffinityGroups request.
func (ag AntiAffinityGroup) ListRequest() (ListCommand, error) {
return &ListAffinityGroups{
ID: ag.ID,
Name: ag.Name,
}, nil
}
// Delete deletes the given Anti-Affinity Group.
func (ag AntiAffinityGroup) Delete(ctx context.Context, client *Client) error {
if ag.ID == nil && ag.Name == "" {
return fmt.Errorf("an Anti-Affinity Group may only be deleted using ID or Name")
}
req := &DeleteAffinityGroup{}
if ag.ID != nil {
req.ID = ag.ID
} else {
req.Name = ag.Name
}
return client.BooleanRequestWithContext(ctx, req)
}
// CreateAntiAffinityGroup represents an Anti-Affinity Group creation.
type CreateAntiAffinityGroup struct {
Name string `json:"name" doc:"Name of the Anti-Affinity Group"`
Description string `json:"description,omitempty" doc:"Optional description of the Anti-Affinity Group"`
_ bool `name:"createAntiAffinityGroup" description:"Creates an Anti-Affinity Group"`
}
func (req CreateAntiAffinityGroup) onBeforeSend(params url.Values) error {
// Name must be set, but can be empty.
if req.Name == "" {
params.Set("name", "")
}
return nil
}
// Response returns the struct to unmarshal.
func (CreateAntiAffinityGroup) Response() interface{} {
return new(AsyncJobResult)
}
// AsyncResponse returns the struct to unmarshal the async job.
func (CreateAntiAffinityGroup) AsyncResponse() interface{} {
return new(AffinityGroup)
}
//go:generate go run generate/main.go -interface=Listable ListAntiAffinityGroups
// ListAntiAffinityGroups represents an Anti-Affinity Groups search.
type ListAntiAffinityGroups struct {
ID *UUID `json:"id,omitempty" doc:"List the Anti-Affinity Group by the ID provided"`
Keyword string `json:"keyword,omitempty" doc:"List by keyword"`
Name string `json:"name,omitempty" doc:"Lists Anti-Affinity Groups by name"`
Page int `json:"page,omitempty"`
PageSize int `json:"pagesize,omitempty"`
VirtualMachineID *UUID `json:"virtualmachineid,omitempty" doc:"Lists Anti-Affinity Groups by virtual machine ID"`
_ bool `name:"listAntiAffinityGroups" description:"Lists Anti-Affinity Groups"`
}
// ListAntiAffinityGroupsResponse represents a list of Anti-Affinity Groups.
type ListAntiAffinityGroupsResponse struct {
Count int `json:"count"`
AntiAffinityGroup []AffinityGroup `json:"antiaffinitygroup"`
}
// DeleteAntiAffinityGroup (Async) represents an Anti-Affinity Group to be deleted.
type DeleteAntiAffinityGroup struct {
ID *UUID `json:"id,omitempty" doc:"The ID of the Anti-Affinity Group. Mutually exclusive with name parameter"`
Name string `json:"name,omitempty" doc:"The name of the Anti-Affinity Group. Mutually exclusive with ID parameter"`
_ bool `name:"deleteAntiAffinityGroup" description:"Deletes Anti-Affinity Group"`
}
// Response returns the struct to unmarshal.
func (DeleteAntiAffinityGroup) Response() interface{} {
return new(AsyncJobResult)
}
// AsyncResponse returns the struct to unmarshal the async job.
func (DeleteAntiAffinityGroup) AsyncResponse() interface{} {
return new(BooleanResponse)
}

Some files were not shown because too many files have changed in this diff Show More