Compare commits

..

54 Commits

Author SHA1 Message Date
Adrien Delorme acb194c4a6 WIP 2021-02-11 14:02:36 +01:00
Adrien Delorme ffbb110167 Test running plugins with fake and fresh plugins (#10595)
* show error as to why plugin discovery failed
* allow to run more manual tests after a plugin installation
* test that a freshly installed external plugin can run
2021-02-09 17:07:59 +01:00
mflorin 9afaa5a21f Profitbricks builder fixes (#10549) 2021-02-09 16:56:06 +01:00
Sylvia Moss f5006d0842 fix empty locals and source name with blank space (#10593) 2021-02-09 14:57:42 +01:00
Jessi 88c516b2d5 Add IMDSv2 support for AWS EBS builder (#10546) 2021-02-09 11:47:54 +01:00
Bryce Kalow d7bb60ea86 website: update next and nextjs-scripts (#10570)
website: update next and nextjs-scripts
2021-02-08 14:48:35 -06:00
Megan Marsh 08e67f8990 Merge pull request #10587 from hashicorp/azr_doc_init_less_highlights
Document init with less highlights
2021-02-08 11:09:04 -08:00
Wilken Rivera 8c3b3ca00f Update website/content/docs/commands/init.mdx 2021-02-08 13:34:14 -05:00
Bryce Kalow 4a686ef66d chore: upgrade nextjs-scripts & next 2021-02-08 17:47:18 +00:00
Sylvia Moss f616955ebc Fix issue when loading datasource single plugin type (#10589) 2021-02-08 13:58:35 +01:00
Adrien Delorme a1b2e71005 Update website/content/docs/commands/init.mdx
Co-authored-by: Sylvia Moss <moss@hashicorp.com>
2021-02-08 13:53:29 +01:00
Sylvia Moss d53488db68 Add aws_secretsmanager transformation to hcl2_upgrade (#10553) 2021-02-08 11:28:26 +01:00
Adrien Delorme b6cfe16444 Document the PKR_GITHUB_API_TOKEN setting in /configure#env vars 2021-02-08 10:59:04 +01:00
Adrien Delorme 4c02123142 init documentation: have less highlights and more text 2021-02-08 10:58:14 +01:00
Megan Marsh 13320650f0 document builder id of artifact for each builder's output on website (#10580)
* document builder id of artifact for each builder's output on website

* document postprocessor artifact ids on website

* Fix links to new communicator page

Co-authored-by: Wilken Rivera <dev@wilkenrivera.com>
2021-02-05 19:49:28 -05:00
Megan Marsh 2324f433f7 Merge pull request #10579 from hashicorp/fix-single-plugin-loading
Fix issue when loading single plugin type `packer-provisioner-comment` built with latest SDK
2021-02-05 14:06:27 -08:00
Megan Marsh ece5e94c3d remove dev line (#10578) 2021-02-05 16:47:41 -05:00
Wilken Rivera ad239bd2b9 Fix markdown link checker issues (#10575)
* Remove exclusions for docs on master

* Update extending redirects with new page locations

* Update documentation links for SaltStack
2021-02-05 16:46:53 -05:00
Wilken Rivera 39d550054d Fix lint errors 2021-02-05 16:46:24 -05:00
Wilken Rivera ef4d35097b Fix issue when loading single plugin type
This changes wraps the plugin client start call with an anonymous function so that Packer starts a
new plugin for each occurrence of a particular plugin block.

Before only one subprocess was being created causing subsquent calls to fail as it was trying start
an already started plugin subprocess.

Before Change
```
???????: failed loading comment: error dial unix /tmp/packer-plugin358226172: connect: no such file or directory
2021/02/05 16:09:13 On error:
2021/02/05 16:09:13 Waiting on builds to complete...

2021/02/05 16:09:13 Starting build run: null.basic-example
2021/02/05 16:09:13 Running builder:
2021/02/05 16:09:13 [INFO] (telemetry) Starting builder
  on examples/basic-example.pkr.hcl line 29:
  (source code not available)

dial unix /tmp/packer-plugin358226172: connect: no such file or directory

???????: failed loading comment: error dial unix /tmp/packer-plugin358226172: connect: no such file or directory

2021/02/05 16:09:13 packer.test plugin: [INFO] communicator disabled, will not connect
2021/02/05 16:09:13 packer.test plugin: Unable to load communicator config from state to populate provisionHookData
  on examples/basic-example.pkr.hcl line 38:
2021/02/05 16:09:13 packer.test plugin: Running the provision hook
  (source code not available)

dial unix /tmp/packer-plugin358226172: connect: no such file or directory

???????: failed loading comment: error dial unix /tmp/packer-plugin358226172: connect: no such file or directory

  on examples/basic-example.pkr.hcl line 42:
  (source code not available)

2021/02/05 16:09:13 [INFO] (telemetry) Starting provisioner comment
dial unix /tmp/packer-plugin358226172: connect: no such file or directory
```

After change
```

null.basic-example: output will be in this color.

==> null.basic-example:   ____                   _
==> null.basic-example:  | __ )    ___    __ _  (_)  _ __
==> null.basic-example:  |  _ \   / _ \  / _` | | | | '_ \
==> null.basic-example:  | |_) | |  __/ | (_| | | | | | | |
==> null.basic-example:  |____/   \___|  \__, | |_| |_| |_|
==> null.basic-example:                  |___/
==> null.basic-example:
==> null.basic-example: Running local shell script: /tmp/packer-shell646549657
    null.basic-example: This is a shell script
==> null.basic-example: Pausing at breakpoint provisioner.
==> null.basic-example: Press enter to continue.
==> null.basic-example: In the middle of Provisioning run
==> null.basic-example: Running local shell script: /tmp/packer-shell177279484
    null.basic-example: This is another shell script
==> null.basic-example:   _____               _
==> null.basic-example:  | ____|  _ __     __| |
==> null.basic-example:  |  _|   | '_ \   / _` |
==> null.basic-example:  | |___  | | | | | (_| |
==> null.basic-example:  |_____| |_| |_|  \__,_|
==> null.basic-example:
Build 'null.basic-example' finished after 1 second 32 milliseconds.

==> Wait completed after 1 second 32 milliseconds

==> Builds finished. The artifacts of successful builds are:
--> null.basic-example: Did not export anything. This is the null builder
 Please enter the commit message for your changes. Lines starting
```
2021-02-05 16:35:01 -05:00
Megan Marsh a63ad19b0c Merge pull request #10577 from hashicorp/fix_build_run_diags
plugin loading failure should be considered an error so build will no…
2021-02-05 13:31:40 -08:00
Megan Marsh ca123721a6 plugin loading failure should be considered an error so build will not try to run 2021-02-05 11:06:52 -08:00
Alex Chan 540effbbc0 Correct the spelling of heirarchical/hierarchical (#10576) 2021-02-05 16:32:28 +01:00
Adrien Delorme 692433721d Add some acceptance testing for Packer init commands (#10566) 2021-02-05 14:06:28 +01:00
Adrien Delorme 0f34592daa packer init doc clarification (#10558)
* Packer has no state like Terraform has

* init command docs: tell where a plugin will be installed

* docs: add a note that packer init does not work with legacy JSON templates

* docs: add a not that packer init does not install single-plugin binaries

* the plugin getter => packer init

* Update init.mdx

* grammar

* link to how to install plugins manually

* Update website/content/docs/commands/init.mdx

Co-authored-by: Sylvia Moss <moss@hashicorp.com>

* Update website/content/docs/commands/init.mdx

Co-authored-by: Sylvia Moss <moss@hashicorp.com>

Co-authored-by: Sylvia Moss <moss@hashicorp.com>
2021-02-05 13:26:58 +01:00
Megan Marsh ba1ad9ea15 Vsphere example update (#10574)
* fix ubuntu json template example

* add hcl template example

* add hcl alpine example

* add hcl versions of alpine and macos; update macos json

* add hcl version of windows template, call fix on windows json
2021-02-05 13:21:29 +01:00
Sylvia Moss ef4afafde9 Fix force flag for hcl2 provisioners and post-processors (#10571) 2021-02-05 10:57:14 +01:00
Adrien Delorme f588c46fb6 add some docs for the cache dir (#10568) 2021-02-05 10:56:03 +01:00
Adrien Delorme 1075120109 use the SDK version 0.0.11 (#10572) 2021-02-04 17:52:40 +01:00
Sylvia Moss 3ddb17ad86 Don't allow data sources to be used inside data sources (#10559) 2021-02-04 11:25:44 +01:00
Adrien Delorme 92ccd5fa1d init: show successful installs in cyan (#10557) 2021-02-04 11:11:42 +01:00
Wilken Rivera 8ccd164f25 Update diagnostic detail to display list of PostProcessors (#10564) 2021-02-04 10:56:59 +01:00
Adrien Delorme ed091163be HCL2 Parse packer.required_plugins block + packer init (#10304)
This adds the new `required_plugins` block to be nested under the packer block.

Example:
```hcl
packer {
  required_plugins {
    aws = {
      version = ">= 2.7.0"
      source = "azr/aws"
    }
    azure = ">= 2.7.0"
  }
}
```

For example on darwin_amd64 Packer will install those under :
* "${PACKER_HOME_DIR}/plugin/github.com/azr/amazon/packer-plugin-amazon_2.7.0_x5.0_darwin_amd64"
* "${PACKER_HOME_DIR}/plugin/github.com/hashicorp/azure/packer-plugin-azure_2.7.0_x5.0_darwin_amd64_x5"

+ docs
+ tests
2021-02-02 18:05:04 +01:00
Megan Marsh ac014fc1c3 Merge pull request #10542 from MarckK/tags
edit examples of docker tags for post processor hcl syntax to be array of strings
2021-01-29 11:29:46 -08:00
Kara de la Marck 61f03cc137 docs: fix instructions to show docker tags for post processor hcl syntax as array of strings 2021-01-29 18:58:13 +00:00
Megan Marsh 18970bfbe4 Merge pull request #10534 from timblaktu/patch-1
Improve Documentation for Build-level Source Block
2021-01-27 14:01:47 -08:00
Tim Black 300604675b Improve Documentation for Build-level Source Block
The docs for top-level and build-level source blocks did not make it clear enough, early enough in the page, how packer merges their contents and fails when ambiguities  arise from redefinition. I know the info it there, but it's at the end and I feel that my modification shortens the page overall while putting the most useful info front and center. 

Also, there is simply an error in the note at the end of this page, which further confuses the situation for new readers trying to use HCL and reuse source blocks. It's referring to a non-existent amazon source which is probably a past copy/paste error. 

It took me a day to get re-usable source blocks working, bc I skimmed the page at first (I know slap my wrist), but didn't really grasp what it was saying in the Note at the end, and found it confusing. It wasn't until I found the Issues and PRs related to this feature getting added last April/May that I realized how it was implemented that I started to grasp what this was saying.
2021-01-27 11:12:42 -08:00
Wilken Rivera f36554fa0e Update redirects and URLs to legacy JSON template docs (#10530) 2021-01-27 09:08:59 -05:00
Miguel Hernández 782cf058b5 Add 'skip_create_ami' option to the amazon-ebs builder (#10531) 2021-01-27 09:00:42 -05:00
Megan Marsh a5a1344948 Merge pull request #10516 from hashicorp/fix_uint8_hcl
fix uint8
2021-01-26 10:22:13 -08:00
Megan Marsh 90fb09e52a linting 2021-01-26 10:09:29 -08:00
Megan Marsh 21df997061 Handle case where list is empty
Co-authored-by: Sylvia Moss <moss@hashicorp.com>
2021-01-26 10:02:16 -08:00
Megan Marsh fbbda0f9d9 Sensitive locals (#10509)
* Allow locals to be delcared as individual blocks, and give them the Sensitive flag

* add docs for new local block

* linting

* add tests

* modified parsing to use schema, check for dupes properly

* update comment

fix wording a liiitle

* add tests for duplicate variables definition in two different files

* remove unnecessary slice initialisation

* fix crash by returning when decode error is hit

* parseLocalVariables: only treat a local vars if its not nil

also return in case of error
return locals in case of error too

* fix duplicate_locals test for windows

Co-authored-by: Adrien Delorme <azr@users.noreply.github.com>
2021-01-26 10:21:44 +01:00
Megan Marsh e3b14d888b create util function in hcl2 template that will load config values into hcl values without panicing if it finds something it cannot handle 2021-01-25 12:25:41 -08:00
Megan Marsh c8d5d56f61 fix 2021-01-25 11:41:42 -08:00
Megan Marsh a8c8d247bb fix uint8 2021-01-25 11:41:42 -08:00
Wilken Rivera ea7fef699f Test against deployment url (#10501)
* Test against deployment url

* Remove infinite redirect configuration

* Add DEPLOYMENT_URL for builds against master

* website: Update README

* tip on seconds

* Test with GHA timeout_minutes clause

* Add continue on error for poll job

* Add empty url check

* Move to pull-request path filter

* Remove www for packer.io

* Apply suggestions from code review

* Update path filter

Vercel deploys on any change under the website directory.

* Use custom action as test

Co-authored-by: Adrien Delorme <azr@users.noreply.github.com>
2021-01-25 10:28:34 -05:00
Roman Mingazeev e588029d6a yandex: some fix (#10522) 2021-01-25 12:26:21 +01:00
Megan Marsh c3266cc3b0 remove indexing from cdrom command and let qemu handle it (#10519) 2021-01-25 10:54:41 +01:00
Megan Marsh 37dcf6183c skip credential validation if we are not exporting an image (#10520) 2021-01-25 10:53:51 +01:00
Megan Marsh 3242b7ee10 read iops and throughput as pointers so we can test for the nil case;… (#10518) 2021-01-25 10:49:37 +01:00
Wilken Rivera 48a31d1b6a Slight markdown fixes (#10517) 2021-01-25 10:43:41 +01:00
Wilken Rivera 85bb4e7a1e Merge pull request #10514 from hashicorp/fix_source_ami_docs
remove confusing from_scratch reference which is not relevant in this…
2021-01-22 12:54:28 -05:00
Megan Marsh 3aab42b770 remove confusing from_scratch reference which is not relevant in this config 2021-01-22 09:36:17 -08:00
622 changed files with 69223 additions and 10390 deletions
+28 -5
View File
@@ -1,14 +1,37 @@
on: [pull_request]
name: Check Markdown links for modified files
on:
pull_request:
paths:
- 'website/**'
name: Check markdown links on modified website files
jobs:
vercel-deployment-poll:
runs-on: ubuntu-latest
timeout-minutes: 3 #cancel job if no deployment is found within x minutes
outputs:
url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
steps:
- name: Wait for Vercel preview deployment to be ready
uses: nywilken/wait-for-vercel-preview@master
id: waitForVercelPreviewDeployment
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 600 # in seconds, set really high to leverage job timeout-minutes values
allow_inactive: true # needed to ensure we get a URL for a previously released deployment
markdown-link-check:
needs: vercel-deployment-poll
if: ${{ needs.vercel-deployment-poll.outputs.url != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1
- name: Get Deployment URL
run:
echo "DEPLOYMENT_URL=${{ needs.vercel-deployment-poll.outputs.url }}" >> $GITHUB_ENV
- name: Checkout source branch
uses: actions/checkout@master
- name: Check links
uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
use-quiet-mode: 'yes'
file-extension: 'mdx'
check-modified-files-only: 'yes'
folder-path: 'website/content'
@@ -6,6 +6,9 @@ jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- name: Set deployment URL env
run:
echo "DEPLOYMENT_URL=https://packer-git-master.hashicorp.vercel.app" >> $GITHUB_ENV
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1
with:
+1 -1
View File
@@ -162,7 +162,7 @@ type Config struct {
//filter, but will cause Packer to fail if the `source_ami` does not exist.
SourceAmiFilter awscommon.AmiFilterOptions `mapstructure:"source_ami_filter" required:"false"`
// Key/value pair tags to apply to the volumes that are *launched*. This is
// a [template engine](/docs/templates/engine), see [Build template
// a [template engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
RootVolumeTags map[string]string `mapstructure:"root_volume_tags" required:"false"`
// Same as [`root_volume_tags`](#root_volume_tags) but defined as a
+4 -4
View File
@@ -16,11 +16,11 @@ type AMIConfig struct {
// The name of the resulting AMI that will appear when managing AMIs in the
// AWS console or via APIs. This must be unique. To help make this unique,
// use a function like timestamp (see [template
// engine](/docs/templates/engine) for more info).
// engine](/docs/templates/legacy_json_templates/engine) for more info).
AMIName string `mapstructure:"ami_name" required:"true"`
// The description to set for the resulting
// AMI(s). By default this description is empty. This is a
// [template engine](/docs/templates/engine), see [Build template
// [template engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
AMIDescription string `mapstructure:"ami_description" required:"false"`
// The type of virtualization for the AMI
@@ -47,7 +47,7 @@ type AMIConfig struct {
// validation of the ami_regions configuration option. Default false.
AMISkipRegionValidation bool `mapstructure:"skip_region_validation" required:"false"`
// Key/value pair tags applied to the AMI. This is a [template
// engine](/docs/templates/engine), see [Build template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
AMITags map[string]string `mapstructure:"tags" required:"false"`
// Same as [`tags`](#tags) but defined as a singular repeatable block
@@ -137,7 +137,7 @@ type AMIConfig struct {
AMISkipBuildRegion bool `mapstructure:"skip_save_build_region"`
// Key/value pair tags to apply to snapshot. They will override AMI tags if
// already applied to snapshot. This is a [template
// engine](/docs/templates/engine), see [Build template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
SnapshotTags map[string]string `mapstructure:"snapshot_tags" required:"false"`
// Same as [`snapshot_tags`](#snapshot_tags) but defined as a singular
+9 -9
View File
@@ -76,7 +76,7 @@ type BlockDevice struct {
// See the documentation on
// [IOPs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html)
// for more information
IOPS int64 `mapstructure:"iops" required:"false"`
IOPS *int64 `mapstructure:"iops" required:"false"`
// Suppresses the specified device included in the block device mapping of
// the AMI.
NoDevice bool `mapstructure:"no_device" required:"false"`
@@ -86,7 +86,7 @@ type BlockDevice struct {
// See the documentation on
// [Throughput](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_EbsBlockDevice.html)
// for more information
Throughput int64 `mapstructure:"throughput" required:"false"`
Throughput *int64 `mapstructure:"throughput" required:"false"`
// The virtual device name. See the documentation on Block Device Mapping
// for more information.
VirtualName string `mapstructure:"virtual_name" required:"false"`
@@ -150,12 +150,12 @@ func (blockDevice BlockDevice) BuildEC2BlockDeviceMapping() *ec2.BlockDeviceMapp
switch blockDevice.VolumeType {
case "io1", "io2", "gp3":
ebsBlockDevice.Iops = aws.Int64(blockDevice.IOPS)
ebsBlockDevice.Iops = blockDevice.IOPS
}
// Throughput is only valid for gp3 types
if blockDevice.VolumeType == "gp3" {
ebsBlockDevice.Throughput = aws.Int64(blockDevice.Throughput)
ebsBlockDevice.Throughput = blockDevice.Throughput
}
// You cannot specify Encrypted if you specify a Snapshot ID
@@ -191,28 +191,28 @@ func (b *BlockDevice) Prepare(ctx *interpolate.Context) error {
}
if ratio, ok := iopsRatios[b.VolumeType]; b.VolumeSize != 0 && ok {
if b.IOPS/b.VolumeSize > ratio {
if b.IOPS != nil && (*b.IOPS/b.VolumeSize > ratio) {
return fmt.Errorf("%s: the maximum ratio of provisioned IOPS to requested volume size "+
"(in GiB) is %v:1 for %s volumes", b.DeviceName, ratio, b.VolumeType)
}
if b.IOPS < minIops || b.IOPS > maxIops {
if b.IOPS != nil && (*b.IOPS < minIops || *b.IOPS > maxIops) {
return fmt.Errorf("IOPS must be between %d and %d for device %s",
minIops, maxIops, b.DeviceName)
}
}
if b.VolumeType == "gp3" {
if b.Throughput < minThroughput || b.Throughput > maxThroughput {
if b.Throughput != nil && (*b.Throughput < minThroughput || *b.Throughput > maxThroughput) {
return fmt.Errorf("Throughput must be between %d and %d for device %s",
minThroughput, maxThroughput, b.DeviceName)
}
if b.IOPS < minIopsGp3 || b.IOPS > maxIopsGp3 {
if b.IOPS != nil && (*b.IOPS < minIopsGp3 || *b.IOPS > maxIopsGp3) {
return fmt.Errorf("IOPS must be between %d and %d for device %s",
minIopsGp3, maxIopsGp3, b.DeviceName)
}
} else if b.Throughput > 0 {
} else if b.Throughput != nil {
return fmt.Errorf("Throughput is not available for device %s",
b.DeviceName)
}
+24 -24
View File
@@ -54,7 +54,7 @@ func TestBlockDevice(t *testing.T) {
VolumeType: "io1",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: 1000,
IOPS: aws.Int64(1000),
},
Result: &ec2.BlockDeviceMapping{
@@ -73,7 +73,7 @@ func TestBlockDevice(t *testing.T) {
VolumeType: "io2",
VolumeSize: 8,
DeleteOnTermination: true,
IOPS: 1000,
IOPS: aws.Int64(1000),
},
Result: &ec2.BlockDeviceMapping{
@@ -168,8 +168,8 @@ func TestBlockDevice(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 8,
Throughput: 125,
IOPS: 3000,
Throughput: aws.Int64(125),
IOPS: aws.Int64(3000),
DeleteOnTermination: true,
Encrypted: config.TriTrue,
},
@@ -219,7 +219,7 @@ func TestIOPSValidation(t *testing.T) {
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io1",
IOPS: 1000,
IOPS: aws.Int64(1000),
},
ok: true,
},
@@ -227,7 +227,7 @@ func TestIOPSValidation(t *testing.T) {
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "io2",
IOPS: 1000,
IOPS: aws.Int64(1000),
},
ok: true,
},
@@ -237,7 +237,7 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 50,
IOPS: 1000,
IOPS: aws.Int64(1000),
},
ok: true,
},
@@ -246,7 +246,7 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 100,
IOPS: 1000,
IOPS: aws.Int64(1000),
},
ok: true,
},
@@ -256,7 +256,7 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "io1",
VolumeSize: 10,
IOPS: 2000,
IOPS: aws.Int64(2000),
},
ok: false,
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 50:1 for io1 volumes",
@@ -266,7 +266,7 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: 30000,
IOPS: aws.Int64(30000),
},
ok: false,
msg: "/dev/sdb: the maximum ratio of provisioned IOPS to requested volume size (in GiB) is 500:1 for io2 volumes",
@@ -277,7 +277,7 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 500,
IOPS: 99999,
IOPS: aws.Int64(99999),
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
@@ -288,7 +288,7 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "io2",
VolumeSize: 50,
IOPS: 10,
IOPS: aws.Int64(10),
},
ok: false,
msg: "IOPS must be between 100 and 64000 for device /dev/sdb",
@@ -299,8 +299,8 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 50,
Throughput: 125,
IOPS: 99999,
Throughput: aws.Int64(125),
IOPS: aws.Int64(99999),
},
ok: false,
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
@@ -311,8 +311,8 @@ func TestIOPSValidation(t *testing.T) {
DeviceName: "/dev/sdb",
VolumeType: "gp3",
VolumeSize: 50,
Throughput: 125,
IOPS: 10,
Throughput: aws.Int64(125),
IOPS: aws.Int64(10),
},
ok: false,
msg: "IOPS must be between 3000 and 16000 for device /dev/sdb",
@@ -346,8 +346,8 @@ func TestThroughputValidation(t *testing.T) {
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: 125,
IOPS: 3000,
Throughput: aws.Int64(125),
IOPS: aws.Int64(3000),
},
ok: true,
},
@@ -355,8 +355,8 @@ func TestThroughputValidation(t *testing.T) {
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: 1000,
IOPS: 3000,
Throughput: aws.Int64(1000),
IOPS: aws.Int64(3000),
},
ok: true,
},
@@ -365,8 +365,8 @@ func TestThroughputValidation(t *testing.T) {
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: 1001,
IOPS: 3000,
Throughput: aws.Int64(1001),
IOPS: aws.Int64(3000),
},
ok: false,
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
@@ -376,8 +376,8 @@ func TestThroughputValidation(t *testing.T) {
device: BlockDevice{
DeviceName: "/dev/sdb",
VolumeType: "gp3",
Throughput: 124,
IOPS: 3000,
Throughput: aws.Int64(124),
IOPS: aws.Int64(3000),
},
ok: false,
msg: "Throughput must be between 125 and 1000 for device /dev/sdb",
+47 -4
View File
@@ -1,5 +1,5 @@
//go:generate struct-markdown
//go:generate mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement
//go:generate mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement,MetadataOptions
package common
@@ -44,6 +44,20 @@ type SecurityGroupFilterOptions struct {
config.NameValueFilter `mapstructure:",squash"`
}
// Configures the metadata options.
// See [Configure IMDS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html) for details.
type MetadataOptions struct {
// A string to enable or disble the IMDS endpoint for an instance. Defaults to enabled.
// Accepts either "enabled" or "disabled"
HttpEndpoint string `mapstructure:"http_endpoint" required:"false"`
// A string to either set the use of IMDSv2 for the instance to optional or required. Defaults to "optional".
// Accepts either "optional" or "required"
HttpTokens string `mapstructure:"http_tokens" required:"false"`
// A numerical value to set an upper limit for the amount of hops allowed when communicating with IMDS endpoints.
// Defaults to 1.
HttpPutResponseHopLimit int64 `mapstructure:"http_put_response_hop_limit" required:"false"`
}
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
@@ -176,7 +190,7 @@ type RunConfig struct {
SecurityGroupFilter SecurityGroupFilterOptions `mapstructure:"security_group_filter" required:"false"`
// Key/value pair tags to apply to the instance that is that is *launched*
// to create the EBS volumes. This is a [template
// engine](/docs/templates/engine), see [Build template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
RunTags map[string]string `mapstructure:"run_tags" required:"false"`
// Same as [`run_tags`](#run_tags) but defined as a singular repeatable
@@ -196,8 +210,7 @@ type RunConfig struct {
SecurityGroupIds []string `mapstructure:"security_group_ids" required:"false"`
// The source AMI whose root volume will be copied and
// provisioned on the currently running instance. This must be an EBS-backed
// AMI with a root volume snapshot that you have access to. Note: this is not
// used when from_scratch is set to true.
// AMI with a root volume snapshot that you have access to.
SourceAmi string `mapstructure:"source_ami" required:"true"`
// Filters used to populate the `source_ami`
// field. JSON Example:
@@ -427,6 +440,9 @@ type RunConfig struct {
// 10m
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout" required:"false"`
// [Metadata Settings](#metadata-settings)
Metadata MetadataOptions `mapstructure:"metadata_options" required:"false"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`
@@ -487,6 +503,33 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error {
// Validation
errs := c.Comm.Prepare(ctx)
if c.Metadata.HttpEndpoint == "" {
c.Metadata.HttpEndpoint = "enabled"
}
if c.Metadata.HttpTokens == "" {
c.Metadata.HttpTokens = "optional"
}
if c.Metadata.HttpPutResponseHopLimit == 0 {
c.Metadata.HttpPutResponseHopLimit = 1
}
if c.Metadata.HttpEndpoint != "enabled" && c.Metadata.HttpEndpoint != "disabled" {
msg := fmt.Errorf("http_endpoint requires either disabled or enabled as its value")
errs = append(errs, msg)
}
if c.Metadata.HttpTokens != "optional" && c.Metadata.HttpTokens != "required" {
msg := fmt.Errorf("http_tokens requires either optional or required as its value")
errs = append(errs, msg)
}
if c.Metadata.HttpPutResponseHopLimit < 1 || c.Metadata.HttpPutResponseHopLimit > 64 {
msg := fmt.Errorf("http_put_response_hop_limit requires a number between 1 and 64")
errs = append(errs, msg)
}
// Copy singular tag maps
errs = append(errs, c.RunTag.CopyOn(&c.RunTags)...)
errs = append(errs, c.SpotTag.CopyOn(&c.SpotTags)...)
+28 -1
View File
@@ -1,4 +1,4 @@
// Code generated by "mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement"; DO NOT EDIT.
// Code generated by "mapstructure-to-hcl2 -type AmiFilterOptions,SecurityGroupFilterOptions,SubnetFilterOptions,VpcFilterOptions,PolicyDocument,Statement,MetadataOptions"; DO NOT EDIT.
package common
@@ -35,6 +35,33 @@ func (*FlatAmiFilterOptions) HCL2Spec() map[string]hcldec.Spec {
return s
}
// FlatMetadataOptions is an auto-generated flat version of MetadataOptions.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatMetadataOptions struct {
HttpEndpoint *string `mapstructure:"http_endpoint" required:"false" cty:"http_endpoint" hcl:"http_endpoint"`
HttpTokens *string `mapstructure:"http_tokens" required:"false" cty:"http_tokens" hcl:"http_tokens"`
HttpPutResponseHopLimit *int64 `mapstructure:"http_put_response_hop_limit" required:"false" cty:"http_put_response_hop_limit" hcl:"http_put_response_hop_limit"`
}
// FlatMapstructure returns a new FlatMetadataOptions.
// FlatMetadataOptions is an auto-generated flat version of MetadataOptions.
// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up.
func (*MetadataOptions) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } {
return new(FlatMetadataOptions)
}
// HCL2Spec returns the hcl spec of a MetadataOptions.
// This spec is used by HCL to read the fields of MetadataOptions.
// The decoded values from this spec will then be applied to a FlatMetadataOptions.
func (*FlatMetadataOptions) HCL2Spec() map[string]hcldec.Spec {
s := map[string]hcldec.Spec{
"http_endpoint": &hcldec.AttrSpec{Name: "http_endpoint", Type: cty.String, Required: false},
"http_tokens": &hcldec.AttrSpec{Name: "http_tokens", Type: cty.String, Required: false},
"http_put_response_hop_limit": &hcldec.AttrSpec{Name: "http_put_response_hop_limit", Type: cty.Number, Required: false},
}
return s
}
// FlatPolicyDocument is an auto-generated flat version of PolicyDocument.
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
type FlatPolicyDocument struct {
@@ -24,6 +24,7 @@ type StepAMIRegionCopy struct {
toDelete string
getRegionConn func(*AccessConfig, string) (ec2iface.EC2API, error)
AMISkipCreateImage bool
AMISkipBuildRegion bool
}
@@ -63,6 +64,12 @@ func (s *StepAMIRegionCopy) DeduplicateRegions(intermediary bool) {
func (s *StepAMIRegionCopy) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packersdk.Ui)
if s.AMISkipCreateImage {
ui.Say("Skipping AMI region copy...")
return multistep.ActionContinue
}
amis := state.Get("amis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
intermediary, _ := state.Get("intermediary_image").(bool)
@@ -16,6 +16,8 @@ import (
)
type StepCreateTags struct {
AMISkipCreateImage bool
Tags map[string]string
SnapshotTags map[string]string
Ctx interpolate.Context
@@ -25,6 +27,12 @@ func (s *StepCreateTags) Run(ctx context.Context, state multistep.StateBag) mult
ec2conn := state.Get("ec2").(*ec2.EC2)
session := state.Get("awsSession").(*session.Session)
ui := state.Get("ui").(packersdk.Ui)
if s.AMISkipCreateImage {
ui.Say("Skipping AMI create tags...")
return multistep.ActionContinue
}
amis := state.Get("amis").(map[string]string)
if len(s.Tags) == 0 && len(s.SnapshotTags) == 0 {
@@ -14,6 +14,8 @@ import (
)
type StepModifyAMIAttributes struct {
AMISkipCreateImage bool
Users []string
Groups []string
SnapshotUsers []string
@@ -29,6 +31,12 @@ func (s *StepModifyAMIAttributes) Run(ctx context.Context, state multistep.State
ec2conn := state.Get("ec2").(*ec2.EC2)
session := state.Get("awsSession").(*session.Session)
ui := state.Get("ui").(packersdk.Ui)
if s.AMISkipCreateImage {
ui.Say("Skipping AMI modify attributes...")
return multistep.ActionContinue
}
amis := state.Get("amis").(map[string]string)
snapshots := state.Get("snapshots").(map[string][]string)
@@ -29,6 +29,9 @@ type StepRunSourceInstance struct {
EbsOptimized bool
EnableT2Unlimited bool
ExpectedRootDevice string
HttpEndpoint string
HttpTokens string
HttpPutResponseHopLimit int64
InstanceInitiatedShutdownBehavior string
InstanceType string
IsRestricted bool
@@ -144,6 +147,10 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
runOpts.CreditSpecification = &ec2.CreditSpecificationRequest{CpuCredits: &creditOption}
}
if s.HttpEndpoint == "enabled" {
runOpts.MetadataOptions = &ec2.InstanceMetadataOptionsRequest{HttpEndpoint: &s.HttpEndpoint, HttpTokens: &s.HttpTokens, HttpPutResponseHopLimit: &s.HttpPutResponseHopLimit}
}
// Collect tags for tagging on resource creation
var tagSpecs []*ec2.TagSpecification
@@ -34,6 +34,9 @@ type StepRunSpotInstance struct {
Comm *communicator.Config
EbsOptimized bool
ExpectedRootDevice string
HttpEndpoint string
HttpTokens string
HttpPutResponseHopLimit int64
InstanceInitiatedShutdownBehavior string
InstanceType string
Region string
@@ -127,6 +130,10 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
}
if s.HttpEndpoint == "enabled" {
templateData.MetadataOptions = &ec2.LaunchTemplateInstanceMetadataOptionsRequest{HttpEndpoint: &s.HttpEndpoint, HttpTokens: &s.HttpTokens, HttpPutResponseHopLimit: &s.HttpPutResponseHopLimit}
}
// If instance type is not set, we'll just pick the lowest priced instance
// available.
if s.InstanceType != "" {
+25 -12
View File
@@ -34,6 +34,9 @@ type Config struct {
awscommon.AccessConfig `mapstructure:",squash"`
awscommon.AMIConfig `mapstructure:",squash"`
awscommon.RunConfig `mapstructure:",squash"`
// If true, Packer will not create the AMI. Useful for setting to `true`
// during a build test stage. Default `false`.
AMISkipCreateImage bool `mapstructure:"skip_create_ami" required:"false"`
// Add one or more block device mappings to the AMI. These will be attached
// when booting a new instance from your AMI. To add a block device during
// the Packer build see `launch_block_device_mappings` below. Your options
@@ -53,7 +56,7 @@ type Config struct {
// Tags to apply to the volumes that are *launched* to create the AMI.
// These tags are *not* applied to the resulting AMI unless they're
// duplicated in `tags`. This is a [template
// engine](/docs/templates/engine), see [Build template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
VolumeRunTags map[string]string `mapstructure:"run_volume_tags"`
// Same as [`run_volume_tags`](#run_volume_tags) but defined as a singular
@@ -184,6 +187,9 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs",
HttpEndpoint: b.config.Metadata.HttpEndpoint,
HttpTokens: b.config.Metadata.HttpTokens,
HttpPutResponseHopLimit: b.config.Metadata.HttpPutResponseHopLimit,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
@@ -208,6 +214,9 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs",
HttpEndpoint: b.config.Metadata.HttpEndpoint,
HttpTokens: b.config.Metadata.HttpTokens,
HttpPutResponseHopLimit: b.config.Metadata.HttpPutResponseHopLimit,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
@@ -319,6 +328,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Regions: b.config.AMIRegions,
},
&stepCreateAMI{
AMISkipCreateImage: b.config.AMISkipCreateImage,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
PollingConfig: b.config.PollingConfig,
},
@@ -330,22 +340,25 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
EncryptBootVolume: b.config.AMIEncryptBootVolume,
Name: b.config.AMIName,
OriginalRegion: *ec2conn.Config.Region,
AMISkipCreateImage: b.config.AMISkipCreateImage,
AMISkipBuildRegion: b.config.AMISkipBuildRegion,
},
&awscommon.StepModifyAMIAttributes{
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
Ctx: b.config.ctx,
GeneratedData: generatedData,
AMISkipCreateImage: b.config.AMISkipCreateImage,
Description: b.config.AMIDescription,
Users: b.config.AMIUsers,
Groups: b.config.AMIGroups,
ProductCodes: b.config.AMIProductCodes,
SnapshotUsers: b.config.SnapshotUsers,
SnapshotGroups: b.config.SnapshotGroups,
Ctx: b.config.ctx,
GeneratedData: generatedData,
},
&awscommon.StepCreateTags{
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
AMISkipCreateImage: b.config.AMISkipCreateImage,
Tags: b.config.AMITags,
SnapshotTags: b.config.SnapshotTags,
Ctx: b.config.ctx,
},
}
+4
View File
@@ -90,6 +90,7 @@ type FlatConfig struct {
VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter" hcl:"vpc_filter"`
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id" hcl:"vpc_id"`
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout" hcl:"windows_password_timeout"`
Metadata *common.FlatMetadataOptions `mapstructure:"metadata_options" required:"false" cty:"metadata_options" hcl:"metadata_options"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -142,6 +143,7 @@ type FlatConfig struct {
SSHInterface *string `mapstructure:"ssh_interface" cty:"ssh_interface" hcl:"ssh_interface"`
PauseBeforeSSM *string `mapstructure:"pause_before_ssm" cty:"pause_before_ssm" hcl:"pause_before_ssm"`
SessionManagerPort *int `mapstructure:"session_manager_port" cty:"session_manager_port" hcl:"session_manager_port"`
AMISkipCreateImage *bool `mapstructure:"skip_create_ami" required:"false" cty:"skip_create_ami" hcl:"skip_create_ami"`
AMIMappings []common.FlatBlockDevice `mapstructure:"ami_block_device_mappings" required:"false" cty:"ami_block_device_mappings" hcl:"ami_block_device_mappings"`
LaunchMappings []common.FlatBlockDevice `mapstructure:"launch_block_device_mappings" required:"false" cty:"launch_block_device_mappings" hcl:"launch_block_device_mappings"`
VolumeRunTags map[string]string `mapstructure:"run_volume_tags" cty:"run_volume_tags" hcl:"run_volume_tags"`
@@ -239,6 +241,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
"metadata_options": &hcldec.BlockSpec{TypeName: "metadata_options", Nested: hcldec.ObjectSpec((*common.FlatMetadataOptions)(nil).HCL2Spec())},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
@@ -291,6 +294,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"ssh_interface": &hcldec.AttrSpec{Name: "ssh_interface", Type: cty.String, Required: false},
"pause_before_ssm": &hcldec.AttrSpec{Name: "pause_before_ssm", Type: cty.String, Required: false},
"session_manager_port": &hcldec.AttrSpec{Name: "session_manager_port", Type: cty.Number, Required: false},
"skip_create_ami": &hcldec.AttrSpec{Name: "skip_create_ami", Type: cty.Bool, Required: false},
"ami_block_device_mappings": &hcldec.BlockListSpec{TypeName: "ami_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"launch_block_device_mappings": &hcldec.BlockListSpec{TypeName: "launch_block_device_mappings", Nested: hcldec.ObjectSpec((*common.FlatBlockDevice)(nil).HCL2Spec())},
"run_volume_tags": &hcldec.AttrSpec{Name: "run_volume_tags", Type: cty.Map(cty.String), Required: false},
+6
View File
@@ -19,6 +19,7 @@ import (
type stepCreateAMI struct {
PollingConfig *awscommon.AWSPollingConfig
image *ec2.Image
AMISkipCreateImage bool
AMISkipBuildRegion bool
}
@@ -28,6 +29,11 @@ func (s *stepCreateAMI) Run(ctx context.Context, state multistep.StateBag) multi
instance := state.Get("instance").(*ec2.Instance)
ui := state.Get("ui").(packersdk.Ui)
if s.AMISkipCreateImage {
ui.Say("Skipping AMI creation...")
return multistep.ActionContinue
}
// Create the image
amiName := config.AMIName
state.Put("intermediary_image", false)
+7 -1
View File
@@ -59,7 +59,7 @@ type Config struct {
// Tags to apply to the volumes that are *launched* to create the AMI.
// These tags are *not* applied to the resulting AMI unless they're
// duplicated in `tags`. This is a [template
// engine](/docs/templates/engine), see [Build template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
VolumeRunTags map[string]string `mapstructure:"run_volume_tags"`
// Same as [`run_volume_tags`](#run_volume_tags) but defined as a singular
@@ -207,6 +207,9 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs",
HttpEndpoint: b.config.Metadata.HttpEndpoint,
HttpTokens: b.config.Metadata.HttpTokens,
HttpPutResponseHopLimit: b.config.Metadata.HttpPutResponseHopLimit,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
@@ -230,6 +233,9 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs",
HttpEndpoint: b.config.Metadata.HttpEndpoint,
HttpTokens: b.config.Metadata.HttpTokens,
HttpPutResponseHopLimit: b.config.Metadata.HttpPutResponseHopLimit,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
@@ -113,6 +113,7 @@ type FlatConfig struct {
VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter" hcl:"vpc_filter"`
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id" hcl:"vpc_id"`
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout" hcl:"windows_password_timeout"`
Metadata *common.FlatMetadataOptions `mapstructure:"metadata_options" required:"false" cty:"metadata_options" hcl:"metadata_options"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -263,6 +264,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
"metadata_options": &hcldec.BlockSpec{TypeName: "metadata_options", Nested: hcldec.ObjectSpec((*common.FlatMetadataOptions)(nil).HCL2Spec())},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
+1 -1
View File
@@ -12,7 +12,7 @@ import (
type BlockDevice struct {
awscommon.BlockDevice `mapstructure:",squash"`
// Key/value pair tags to apply to the volume. These are retained after the builder
// completes. This is a [template engine](/docs/templates/engine), see
// completes. This is a [template engine](/docs/templates/legacy_json_templates/engine), see
// [Build template data](#build-template-data) for more information.
Tags map[string]string `mapstructure:"tags" required:"false"`
// Same as [`tags`](#tags) but defined as a singular repeatable block
+7 -1
View File
@@ -58,7 +58,7 @@ type Config struct {
// *launched* to create EBS Volumes. These tags will *not* appear in the
// tags of the resulting EBS volumes unless they're duplicated under `tags`
// in the `ebs_volumes` setting. This is a [template
// engine](/docs/templates/engine), see [Build template
// engine](/docs/templates/legacy_json_templates/engine), see [Build template
// data](#build-template-data) for more information.
//
// Note: The tags specified here will be *temporarily* applied to volumes
@@ -195,6 +195,9 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Debug: b.config.PackerDebug,
EbsOptimized: b.config.EbsOptimized,
ExpectedRootDevice: "ebs",
HttpEndpoint: b.config.Metadata.HttpEndpoint,
HttpTokens: b.config.Metadata.HttpTokens,
HttpPutResponseHopLimit: b.config.Metadata.HttpPutResponseHopLimit,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
Region: *ec2conn.Config.Region,
@@ -218,6 +221,9 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
EbsOptimized: b.config.EbsOptimized,
EnableT2Unlimited: b.config.EnableT2Unlimited,
ExpectedRootDevice: "ebs",
HttpEndpoint: b.config.Metadata.HttpEndpoint,
HttpTokens: b.config.Metadata.HttpTokens,
HttpPutResponseHopLimit: b.config.Metadata.HttpPutResponseHopLimit,
InstanceInitiatedShutdownBehavior: b.config.InstanceInitiatedShutdownBehavior,
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
@@ -115,6 +115,7 @@ type FlatConfig struct {
VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter" hcl:"vpc_filter"`
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id" hcl:"vpc_id"`
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout" hcl:"windows_password_timeout"`
Metadata *common.FlatMetadataOptions `mapstructure:"metadata_options" required:"false" cty:"metadata_options" hcl:"metadata_options"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -242,6 +243,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
"metadata_options": &hcldec.BlockSpec{TypeName: "metadata_options", Nested: hcldec.ObjectSpec((*common.FlatMetadataOptions)(nil).HCL2Spec())},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
@@ -90,6 +90,7 @@ type FlatConfig struct {
VpcFilter *common.FlatVpcFilterOptions `mapstructure:"vpc_filter" required:"false" cty:"vpc_filter" hcl:"vpc_filter"`
VpcId *string `mapstructure:"vpc_id" required:"false" cty:"vpc_id" hcl:"vpc_id"`
WindowsPasswordTimeout *string `mapstructure:"windows_password_timeout" required:"false" cty:"windows_password_timeout" hcl:"windows_password_timeout"`
Metadata *common.FlatMetadataOptions `mapstructure:"metadata_options" required:"false" cty:"metadata_options" hcl:"metadata_options"`
Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"`
PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"`
SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"`
@@ -245,6 +246,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"vpc_filter": &hcldec.BlockSpec{TypeName: "vpc_filter", Nested: hcldec.ObjectSpec((*common.FlatVpcFilterOptions)(nil).HCL2Spec())},
"vpc_id": &hcldec.AttrSpec{Name: "vpc_id", Type: cty.String, Required: false},
"windows_password_timeout": &hcldec.AttrSpec{Name: "windows_password_timeout", Type: cty.String, Required: false},
"metadata_options": &hcldec.BlockSpec{TypeName: "metadata_options", Nested: hcldec.ObjectSpec((*common.FlatMetadataOptions)(nil).HCL2Spec())},
"communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false},
"pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false},
"ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false},
+2 -2
View File
@@ -121,14 +121,14 @@ type Config struct {
// provisioners should connect to the local IP address of the instance.
UseLocalIPAddress bool `mapstructure:"use_local_ip_address" required:"false"`
// User data to launch with the instance. This is a
// template engine; see "User Data" bellow for
// template engine; see "User Data" below for
// more details. Packer will not automatically wait for a user script to
// finish before shutting down the instance this must be handled in a
// provisioner.
UserData string `mapstructure:"user_data" required:"false"`
// Path to a file that will be used for the user
// data when launching the instance. This file will be parsed as a template
// engine see User Data bellow for more
// engine see User Data below for more
// details.
UserDataFile string `mapstructure:"user_data_file" required:"false"`
// The name or ID of the zone where the instance will be
+9 -4
View File
@@ -22,8 +22,11 @@ func (s *stepCreateServer) Run(ctx context.Context, state multistep.StateBag) mu
profitbricks.SetAuth(c.PBUsername, c.PBPassword)
profitbricks.SetDepth("5")
if sshkey, ok := state.GetOk("publicKey"); ok {
c.SSHKey = sshkey.(string)
if c.Comm.SSHPublicKey != nil {
c.SSHKey = string(c.Comm.SSHPublicKey)
} else {
ui.Error("No ssh private key set; ssh authentication won't be possible. Please specify your private key in the ssh_private_key_file configuration key.")
return multistep.ActionHalt
}
ui.Say("Creating Virtual Data Center...")
img := s.getImageId(c.Image, c)
@@ -204,7 +207,7 @@ func (d *stepCreateServer) setPB(username string, password string, url string) {
func (d *stepCreateServer) checkForErrors(instance profitbricks.Resp) error {
if instance.StatusCode > 299 {
return errors.New(fmt.Sprintf("Error occurred %s", string(instance.Body)))
return fmt.Errorf("Error occurred %s", string(instance.Body))
}
return nil
}
@@ -261,7 +264,9 @@ func (d *stepCreateServer) getImageAlias(imageAlias string, location string, ui
func parseErrorMessage(raw string) (toreturn string) {
var tmp map[string]interface{}
json.Unmarshal([]byte(raw), &tmp)
if json.Unmarshal([]byte(raw), &tmp) != nil {
return ""
}
for _, v := range tmp["messages"].([]interface{}) {
for index, i := range v.(map[string]interface{}) {
+125 -7
View File
@@ -3,6 +3,9 @@ package profitbricks
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/hashicorp/packer-plugin-sdk/multistep"
@@ -22,9 +25,32 @@ func (s *stepTakeSnapshot) Run(ctx context.Context, state multistep.StateBag) mu
dcId := state.Get("datacenter_id").(string)
volumeId := state.Get("volume_id").(string)
serverId := state.Get("instance_id").(string)
comm, _ := state.Get("communicator").(packersdk.Communicator)
if comm == nil {
ui.Error("no communicator found")
return multistep.ActionHalt
}
/* sync fs changes from the provisioning step */
os, err := s.getOs(dcId, serverId)
if err != nil {
ui.Error(fmt.Sprintf("an error occurred while getting the server os: %s", err.Error()))
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Server OS is %s", os))
switch strings.ToLower(os) {
case "linux":
ui.Say("syncing file system changes")
if err := s.syncFs(ctx, comm); err != nil {
ui.Error(fmt.Sprintf("error syncing fs changes: %s", err.Error()))
return multistep.ActionHalt
}
}
snapshot := profitbricks.CreateSnapshot(dcId, volumeId, c.SnapshotName, "")
state.Put("snapshotname", c.SnapshotName)
if snapshot.StatusCode > 299 {
@@ -42,31 +68,123 @@ func (s *stepTakeSnapshot) Run(ctx context.Context, state multistep.StateBag) mu
return multistep.ActionHalt
}
s.waitTillProvisioned(snapshot.Headers.Get("Location"), *c)
ui.Say(fmt.Sprintf("Creating a snapshot for %s/volumes/%s", dcId, volumeId))
err = s.waitForRequest(snapshot.Headers.Get("Location"), *c, ui)
if err != nil {
ui.Error(fmt.Sprintf("An error occurred while waiting for the request to be done: %s", err.Error()))
return multistep.ActionHalt
}
err = s.waitTillSnapshotAvailable(snapshot.Id, *c, ui)
if err != nil {
ui.Error(fmt.Sprintf("An error occurred while waiting for the snapshot to be created: %s", err.Error()))
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *stepTakeSnapshot) Cleanup(state multistep.StateBag) {
func (s *stepTakeSnapshot) Cleanup(_ multistep.StateBag) {
}
func (d *stepTakeSnapshot) waitTillProvisioned(path string, config Config) {
d.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
func (s *stepTakeSnapshot) waitForRequest(path string, config Config, ui packersdk.Ui) error {
ui.Say(fmt.Sprintf("Watching request %s", path))
s.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
waitCount := 50
var waitInterval = 10 * time.Second
if config.Retries > 0 {
waitCount = config.Retries
}
done := false
for i := 0; i < waitCount; i++ {
request := profitbricks.GetRequestStatus(path)
ui.Say(fmt.Sprintf("request status = %s", request.Metadata.Status))
if request.Metadata.Status == "DONE" {
done = true
break
}
time.Sleep(10 * time.Second)
if request.Metadata.Status == "FAILED" {
return fmt.Errorf("Request failed: %s", request.Response)
}
time.Sleep(waitInterval)
i++
}
if done == false {
return fmt.Errorf("request not fulfilled after waiting %d seconds",
int64(waitCount)*int64(waitInterval)/int64(time.Second))
}
return nil
}
func (d *stepTakeSnapshot) setPB(username string, password string, url string) {
func (s *stepTakeSnapshot) waitTillSnapshotAvailable(id string, config Config, ui packersdk.Ui) error {
s.setPB(config.PBUsername, config.PBPassword, config.PBUrl)
waitCount := 50
var waitInterval = 10 * time.Second
if config.Retries > 0 {
waitCount = config.Retries
}
done := false
ui.Say(fmt.Sprintf("waiting for snapshot %s to become available", id))
for i := 0; i < waitCount; i++ {
snap := profitbricks.GetSnapshot(id)
ui.Say(fmt.Sprintf("snapshot status = %s", snap.Metadata.State))
if snap.StatusCode != 200 {
return fmt.Errorf("%s", snap.Response)
}
if snap.Metadata.State == "AVAILABLE" {
done = true
break
}
time.Sleep(waitInterval)
i++
ui.Say(fmt.Sprintf("... still waiting, %d seconds have passed", int64(waitInterval)*int64(i)))
}
if done == false {
return fmt.Errorf("snapshot not created after waiting %d seconds",
int64(waitCount)*int64(waitInterval)/int64(time.Second))
}
ui.Say("snapshot created")
return nil
}
func (s *stepTakeSnapshot) syncFs(ctx context.Context, comm packersdk.Communicator) error {
cmd := &packersdk.RemoteCmd{
Command: "sync",
}
if err := comm.Start(ctx, cmd); err != nil {
return err
}
if cmd.Wait() != 0 {
return fmt.Errorf("sync command exited with code %d", cmd.ExitStatus())
}
return nil
}
func (s *stepTakeSnapshot) getOs(dcId string, serverId string) (string, error) {
server := profitbricks.GetServer(dcId, serverId)
if server.StatusCode != 200 {
return "", errors.New(server.Response)
}
if server.Properties.BootVolume == nil {
return "", errors.New("no boot volume found on server")
}
volumeId := server.Properties.BootVolume.Id
volume := profitbricks.GetVolume(dcId, volumeId)
if volume.StatusCode != 200 {
return "", errors.New(volume.Response)
}
return volume.Properties.LicenceType, nil
}
func (s *stepTakeSnapshot) setPB(username string, password string, url string) {
profitbricks.SetAuth(username, password)
profitbricks.SetEndpoint(url)
}
+1 -1
View File
@@ -260,7 +260,7 @@ func (s *stepRun) getDeviceAndDriveArgs(config *Config, state multistep.StateBag
}
for i, cdPath := range cdPaths {
if config.CDROMInterface == "" {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,index=%d,media=cdrom", cdPath, i))
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,media=cdrom", cdPath))
} else if config.CDROMInterface == "virtio-scsi" {
driveArgs = append(driveArgs, fmt.Sprintf("file=%s,if=none,index=%d,id=cdrom%d,media=cdrom", cdPath, i, i))
deviceArgs = append(deviceArgs, "virtio-scsi-device", fmt.Sprintf("scsi-cd,drive=cdrom%d", i))
+19 -19
View File
@@ -52,7 +52,7 @@ func Test_UserOverrides(t *testing.T) {
},
[]string{
"-display", "gtk",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-randomflag1", "127.0.0.1-1234-http/directory",
"-randomflag2", "output/directory-myvm",
"-device", ",netdev=user.0",
@@ -66,7 +66,7 @@ func Test_UserOverrides(t *testing.T) {
},
[]string{
"-display", "partydisplay",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-device", ",netdev=user.0",
},
"User input overrides default, rest is populated as normal",
@@ -81,7 +81,7 @@ func Test_UserOverrides(t *testing.T) {
"-display", "gtk",
"-device", "somerandomdevice",
"-device", "mynetdevice,netdev=user.0",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"Net device gets added",
},
@@ -135,7 +135,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
[]string{
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"Boot value should default to once=d when diskImage isn't set",
},
@@ -164,7 +164,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes off",
},
@@ -193,7 +193,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=path_to_output,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage true, extra cdrom, detectZeroes on",
},
@@ -224,8 +224,8 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2",
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, bootable iso, cdrom",
},
@@ -256,8 +256,8 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-device", "scsi-hd,bus=scsi0.0,drive=drive1",
"-drive", "if=none,file=qemupath1,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "if=none,file=qemupath2,id=drive1,cache=writeback,discard=,format=qcow2,detect-zeroes=on",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage false, extra cdrom, detect zeroes on",
},
@@ -284,7 +284,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-device", "virtio-scsi-pci,id=scsi0",
"-device", "scsi-hd,bus=scsi0.0,drive=drive0",
"-drive", "if=none,file=output/dir/path/mydisk.qcow2,id=drive0,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"virtio-scsi interface, DiskImage false, no extra disks or cds",
},
@@ -302,8 +302,8 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"cd_path is set and DiskImage is false",
},
@@ -322,7 +322,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-display", "gtk",
"-boot", "once=d",
"-drive", "file=output/dir/path/mydisk.qcow2,if=,cache=,discard=,format=,detect-zeroes=",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"empty config",
},
@@ -345,7 +345,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
[]string{
"-boot", "once=d",
"-drive", "file=path_to_output,if=virtio,cache=writeback,format=qcow2",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"version less than 2",
},
@@ -369,8 +369,8 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-boot", "once=d",
"-drive", "file=qemupath1,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=qemupath2,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,index=1,media=cdrom",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
},
"virtio interface with extra disks",
},
@@ -395,7 +395,7 @@ func Test_DriveAndDeviceArgs(t *testing.T) {
"-display", "gtk",
"-boot", "c",
"-drive", "file=path_to_output,if=virtio,cache=writeback,discard=,format=qcow2,detect-zeroes=",
"-drive", "file=fake_cd_path.iso,index=0,media=cdrom",
"-drive", "file=fake_cd_path.iso,media=cdrom",
},
"virtio interface with disk image",
},
@@ -456,7 +456,7 @@ func Test_OptionalConfigOptionsGetSet(t *testing.T) {
"-vnc", ":5,password",
"-machine", "type=pc,accel=hvf",
"-device", ",netdev=user.0",
"-drive", "file=/path/to/test.iso,index=0,media=cdrom",
"-drive", "file=/path/to/test.iso,media=cdrom",
"-qmp", "unix:qmp_path,server,nowait",
}
@@ -33,7 +33,7 @@ type GuestAdditionsConfig struct {
// where the VirtualBox guest additions ISO will be uploaded. By default this
// is `VBoxGuestAdditions.iso` which should upload into the login directory of
// the user. This is a [configuration
// template](/docs/templates/engine) where the `Version`
// template](/docs/templates/legacy_json_templates/engine) where the `Version`
// variable is replaced with the VirtualBox version.
GuestAdditionsPath string `mapstructure:"guest_additions_path"`
// The SHA256 checksum of the guest
@@ -37,7 +37,7 @@ type VBoxManageConfig struct {
// followed by the CPUs.
// Each command itself is an array of strings, where each string is an argument to
// `VBoxManage`. Each argument is treated as a [configuration
// template](/docs/templates/engine). The only available
// template](/docs/templates/legacy_json_templates/engine). The only available
// variable is `Name` which is replaced with the unique name of the VM, which is
// required for many VBoxManage calls.
VBoxManage [][]string `mapstructure:"vboxmanage" required:"false"`
+10 -4
View File
@@ -328,21 +328,27 @@ func (d *ESX5Driver) Verify() error {
}
func (d *ESX5Driver) VerifyOvfTool(SkipExport, skipValidateCredentials bool) error {
// We don't use ovftool if we aren't exporting a VM; return without error
// if ovftool isn't on path.
if SkipExport {
return nil
}
err := d.base.VerifyOvfTool(SkipExport, skipValidateCredentials)
if err != nil {
return err
}
if skipValidateCredentials {
return nil
}
log.Printf("Verifying that ovftool credentials are valid...")
// check that password is valid by sending a dummy ovftool command
// now, so that we don't fail for a simple mistake after a long
// build
ovftool := GetOVFTool()
if skipValidateCredentials {
return nil
}
if d.Password == "" {
return fmt.Errorf("exporting the vm from esxi with ovftool requires " +
"that you set a value for remote_password")
+10
View File
@@ -92,3 +92,13 @@ func TestESX5Driver_CommHost(t *testing.T) {
t.Errorf("bad vm_address: %s", address.(string))
}
}
func TestESX5Driver_VerifyOvfTool(t *testing.T) {
driver := ESX5Driver{}
// should always skip validation if export is skipped, so this should always
// pass even when ovftool is not installed.
err := driver.VerifyOvfTool(true, false)
if err != nil {
t.Fatalf("shouldn't fail ever because should always skip check")
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ type ToolsConfig struct {
ToolsUploadFlavor string `mapstructure:"tools_upload_flavor" required:"false"`
// The path in the VM to upload the VMware tools. This only takes effect if
// `tools_upload_flavor` is non-empty. This is a [configuration
// template](/docs/templates/engine) that has a single valid variable:
// template](/docs/templates/legacy_json_templates/engine) that has a single valid variable:
// `Flavor`, which will be the value of `tools_upload_flavor`. By default
// the upload path is set to `{{.Flavor}}.iso`. This setting is not used
// when `remote_type` is `esx5`.
+1 -1
View File
@@ -66,7 +66,7 @@ type Config struct {
VMName string `mapstructure:"vm_name" required:"false"`
VMXDiskTemplatePath string `mapstructure:"vmx_disk_template_path"`
// Path to a [configuration template](/docs/templates/engine) that
// Path to a [configuration template](/docs/templates/legacy_json_templates/engine) that
// defines the contents of the virtual machine VMX file for VMware. The
// engine has access to the template variables `{{ .DiskNumber }}` and
// `{{ .DiskName }}`.
+35 -33
View File
@@ -2,34 +2,9 @@
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": true,
"vm_name": "alpine-{{timestamp}}",
"host": "esxi-1.vsphere65.test",
"CPUs": 1,
"RAM": 512,
"CPUs": 1,
"RAM": 512,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 1024,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"guest_os_type": "other3xLinux64Guest",
"iso_paths": [
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"
],
"floppy_files": [
"{{template_dir}}/answerfile",
"{{template_dir}}/setup.sh"
],
"boot_wait": "15s",
"boot_command": [
"root<enter><wait>",
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
@@ -47,16 +22,43 @@
"mount -t vfat /dev/fd0 /media/floppy<enter><wait>",
"/media/floppy/SETUP.SH<enter>"
],
"boot_wait": "15s",
"disk_controller_type": "pvscsi",
"floppy_files": [
"{{template_dir}}/answerfile",
"{{template_dir}}/setup.sh"
],
"guest_os_type": "other3xLinux64Guest",
"host": "esxi-1.vsphere65.test",
"insecure_connection": true,
"iso_paths": [
"[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"
],
"network_adapters": [
{
"network_card": "vmxnet3"
}
],
"password": "jetbrains",
"ssh_password": "jetbrains",
"ssh_username": "root",
"ssh_password": "jetbrains"
"storage": [
{
"disk_size": 1024,
"disk_thin_provisioned": true
}
],
"username": "root",
"vcenter_server": "vcenter.vsphere65.test",
"vm_name": "alpine-{{timestamp}}"
}
],
"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
"inline": [
"ls /"
],
"type": "shell"
}
]
}
}
@@ -0,0 +1,44 @@
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-iso" "autogenerated_1" {
CPUs = 1
RAM = 512
RAM_reserve_all = true
boot_command = ["root<enter><wait>", "mount -t vfat /dev/fd0 /media/floppy<enter><wait>", "setup-alpine -f /media/floppy/answerfile<enter>", "<wait5>", "jetbrains<enter>", "jetbrains<enter>", "<wait5>", "y<enter>", "<wait10><wait10><wait10><wait10>", "reboot<enter>", "<wait10><wait10>", "root<enter>", "jetbrains<enter><wait>", "mount -t vfat /dev/fd0 /media/floppy<enter><wait>", "/media/floppy/SETUP.SH<enter>"]
boot_wait = "15s"
disk_controller_type = ["pvscsi"]
floppy_files = ["${path.root}/answerfile", "${path.root}/setup.sh"]
guest_os_type = "other3xLinux64Guest"
host = "esxi-1.vsphere65.test"
insecure_connection = true
iso_paths = ["[datastore1] ISO/alpine-standard-3.8.2-x86_64.iso"]
network_adapters {
network_card = "vmxnet3"
}
password = "jetbrains"
ssh_password = "jetbrains"
ssh_username = "root"
storage {
disk_size = 1024
disk_thin_provisioned = true
}
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "alpine-${local.timestamp}"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-iso.autogenerated_1"]
provisioner "shell" {
inline = ["ls /"]
}
}
@@ -0,0 +1,25 @@
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-clone" "example_clone" {
communicator = "none"
host = "esxi-1.vsphere65.test"
insecure_connection = "true"
password = "jetbrains"
template = "alpine"
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "alpine-clone-${local.timestamp}"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-clone.example_clone"]
}
+37 -38
View File
@@ -2,49 +2,48 @@
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "macos-packer",
"host": "esxi-mac.vsphere65.test",
"guest_os_type": "darwin16_64Guest",
"CPUs": 1,
"RAM": 4096,
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "e1000e",
"usb_controller": true,
"configuration_parameters": {
"ich7m.present": "TRUE",
"smc.present": "TRUE"
},
"cdrom_type": "sata",
"iso_paths": [
"[datastore-mac] ISO/macOS 10.13.3.iso",
"[datastore-mac] ISO/VMware Tools/10.2.0/darwin.iso"
],
"iso_urls": ["{{template_dir}}/setup/out/setup.iso"],
"iso_checksum": "file:///{{template_dir}}/setup/out/sha256sums",
"boot_wait": "4m",
"CPUs": 1,
"RAM": 4096,
"boot_command": [
"<enter><wait5>",
"<leftCtrlOn><f2><leftCtrlOff>u<enter>t<enter><wait5>",
"/Volumes/setup/setup.sh<enter>"
],
"boot_wait": "4m",
"cdrom_type": "sata",
"configuration_parameters": {
"ich7m.present": "TRUE",
"smc.present": "TRUE"
},
"guest_os_type": "darwin16_64Guest",
"host": "esxi-mac.vsphere65.test",
"insecure_connection": "true",
"iso_checksum": "file:///{{template_dir}}/setup/out/sha256sums",
"iso_paths": [
"[datastore-mac] ISO/macOS 10.13.3.iso",
"[datastore-mac] ISO/VMware Tools/10.2.0/darwin.iso"
],
"iso_urls": [
"{{template_dir}}/setup/out/setup.iso"
],
"network_adapters": [
{
"network_card": "e1000e"
}
],
"password": "jetbrains",
"ssh_password": "jetbrains",
"ssh_username": "jetbrains",
"ssh_password": "jetbrains"
"storage": [
{
"disk_size": 32768,
"disk_thin_provisioned": true
}
],
"usb_controller": true,
"username": "root",
"vcenter_server": "vcenter.vsphere65.test",
"vm_name": "macos-packer"
}
]
}
}
@@ -0,0 +1,43 @@
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-iso" "example_osx" {
CPUs = 1
RAM = 4096
boot_command = ["<enter><wait5>", "<leftCtrlOn><f2><leftCtrlOff>u<enter>t<enter><wait5>", "/Volumes/setup/setup.sh<enter>"]
boot_wait = "4m"
cdrom_type = "sata"
configuration_parameters = {
"ich7m.present" = "TRUE"
"smc.present" = "TRUE"
}
guest_os_type = "darwin16_64Guest"
host = "esxi-mac.vsphere65.test"
insecure_connection = "true"
iso_checksum = "file:///${path.root}/setup/out/sha256sums"
iso_paths = ["[datastore-mac] ISO/macOS 10.13.3.iso", "[datastore-mac] ISO/VMware Tools/10.2.0/darwin.iso"]
iso_urls = ["${path.root}/setup/out/setup.iso"]
network_adapters {
network_card = "e1000e"
}
password = "jetbrains"
ssh_password = "jetbrains"
ssh_username = "jetbrains"
storage {
disk_size = 32768
disk_thin_provisioned = true
}
usb_controller = ["usb"]
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "macos-packer"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-iso.example_osx"]
}
@@ -2,36 +2,9 @@
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "example-ubuntu",
"host": "esxi-1.vsphere65.test",
"guest_os_type": "ubuntu64Guest",
"ssh_username": "jetbrains",
"ssh_password": "jetbrains",
"CPUs": 1,
"RAM": 1024,
"CPUs": 1,
"RAM": 1024,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"iso_paths": [
"[datastore1] ISO/ubuntu-16.04.3-server-amd64.iso"
],
"floppy_files": [
"{{template_dir}}/preseed.cfg"
],
"boot_command": [
"<enter><wait><f6><wait><esc><wait>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
@@ -49,14 +22,42 @@
" locale=en_US",
" file=/media/preseed.cfg",
"<enter>"
]
],
"disk_controller_type": "pvscsi",
"floppy_files": [
"{{template_dir}}/preseed.cfg"
],
"guest_os_type": "ubuntu64Guest",
"host": "esxi-1.vsphere65.test",
"insecure_connection": true,
"iso_paths": [
"[datastore1] ISO/ubuntu-16.04.3-server-amd64.iso"
],
"network_adapters": [
{
"network_card": "vmxnet3"
}
],
"password": "jetbrains",
"ssh_password": "jetbrains",
"ssh_username": "jetbrains",
"storage": [
{
"disk_size": 32768,
"disk_thin_provisioned": true
}
],
"username": "root",
"vcenter_server": "vcenter.vsphere65.test",
"vm_name": "example-ubuntu"
}
],
"provisioners": [
{
"type": "shell",
"inline": ["ls /"]
"inline": [
"ls /"
],
"type": "shell"
}
]
}
}
@@ -0,0 +1,52 @@
# source blocks are analogous to the "builders" in json templates. They are used
# in build blocks. A build block runs provisioners and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-iso" "example" {
CPUs = 1
RAM = 1024
RAM_reserve_all = true
boot_command = ["<enter><wait><f6><wait><esc><wait>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs><bs><bs><bs><bs><bs><bs><bs>",
"<bs><bs><bs>", "/install/vmlinuz",
" initrd=/install/initrd.gz", " priority=critical",
" locale=en_US", " file=/media/preseed.cfg",
"<enter>"]
disk_controller_type = ["pvscsi"]
floppy_files = ["${path.root}/preseed.cfg"]
guest_os_type = "ubuntu64Guest"
host = "esxi-1.vsphere65.test"
insecure_connection = true
iso_paths = ["[datastore1] ISO/ubuntu-16.04.3-server-amd64.iso"]
network_adapters {
network_card = "vmxnet3"
}
password = "jetbrains"
ssh_password = "jetbrains"
ssh_username = "jetbrains"
storage {
disk_size = 32768
disk_thin_provisioned = true
}
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "example-ubuntu"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-iso.example"]
provisioner "shell" {
inline = ["ls /"]
}
}
@@ -2,47 +2,47 @@
"builders": [
{
"type": "vsphere-iso",
"vcenter_server": "vcenter.vsphere65.test",
"username": "root",
"password": "jetbrains",
"insecure_connection": "true",
"vm_name": "example-windows",
"host": "esxi-1.vsphere65.test",
"guest_os_type": "windows9_64Guest",
"communicator": "winrm",
"winrm_username": "jetbrains",
"winrm_password": "jetbrains",
"CPUs": 1,
"RAM": 4096,
"CPUs": 1,
"RAM": 4096,
"RAM_reserve_all": true,
"disk_controller_type": "pvscsi",
"disk_size": 32768,
"disk_thin_provisioned": true,
"network_card": "vmxnet3",
"communicator": "winrm",
"disk_controller_type": "pvscsi",
"floppy_files": [
"{{template_dir}}/setup/"
],
"floppy_img_path": "[datastore1] ISO/VMware Tools/10.2.0/pvscsi-Windows8.flp",
"guest_os_type": "windows9_64Guest",
"host": "esxi-1.vsphere65.test",
"insecure_connection": "true",
"iso_paths": [
"[datastore1] ISO/en_windows_10_multi-edition_vl_version_1709_updated_dec_2017_x64_dvd_100406172.iso",
"[datastore1] ISO/VMware Tools/10.2.0/windows.iso"
],
"floppy_files": [
"{{template_dir}}/setup/"
"network_adapters": [
{
"network_card": "vmxnet3"
}
],
"floppy_img_path": "[datastore1] ISO/VMware Tools/10.2.0/pvscsi-Windows8.flp"
"password": "jetbrains",
"storage": [
{
"disk_size": 32768,
"disk_thin_provisioned": true
}
],
"username": "root",
"vcenter_server": "vcenter.vsphere65.test",
"vm_name": "example-windows",
"winrm_password": "jetbrains",
"winrm_username": "jetbrains"
}
],
"provisioners": [
{
"type": "windows-shell",
"inline": ["dir c:\\"]
"inline": [
"dir c:\\"
],
"type": "windows-shell"
}
]
}
}
@@ -0,0 +1,41 @@
# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "vsphere-iso" "example_windows" {
CPUs = 1
RAM = 4096
RAM_reserve_all = true
communicator = "winrm"
disk_controller_type = ["pvscsi"]
floppy_files = ["${path.root}/setup/"]
floppy_img_path = "[datastore1] ISO/VMware Tools/10.2.0/pvscsi-Windows8.flp"
guest_os_type = "windows9_64Guest"
host = "esxi-1.vsphere65.test"
insecure_connection = "true"
iso_paths = ["[datastore1] ISO/en_windows_10_multi-edition_vl_version_1709_updated_dec_2017_x64_dvd_100406172.iso", "[datastore1] ISO/VMware Tools/10.2.0/windows.iso"]
network_adapters {
network_card = "vmxnet3"
}
password = "jetbrains"
storage {
disk_size = 32768
disk_thin_provisioned = true
}
username = "root"
vcenter_server = "vcenter.vsphere65.test"
vm_name = "example-windows"
winrm_password = "jetbrains"
winrm_username = "jetbrains"
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.vsphere-iso.example_windows"]
provisioner "windows-shell" {
inline = ["dir c:\\"]
}
}
+12 -10
View File
@@ -34,16 +34,18 @@ func MergeCloudUserMetaData(usersData ...string) (string, error) {
}
for i, userData := range usersData {
w, err := data.CreatePart(textproto.MIMEHeader{
"Content-Disposition": {fmt.Sprintf("attachment; filename=\"user-data-%d\"", i)},
"Content-Type": {detectContentType(userData)},
})
if err != nil {
return "", err
}
_, err = w.Write([]byte(userData))
if err != nil {
return "", err
if len(userData) != 0 {
w, err := data.CreatePart(textproto.MIMEHeader{
"Content-Disposition": {fmt.Sprintf("attachment; filename=\"user-data-%d\"", i)},
"Content-Type": {detectContentType(userData)},
})
if err != nil {
return "", err
}
_, err = w.Write([]byte(userData))
if err != nil {
return "", err
}
}
}
return buff.String(), nil
+1
View File
@@ -53,6 +53,7 @@ func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul
if err != nil {
return StepHaltWithError(state, err)
}
ui.Say("Success image create...")
image, ok := resp.(*compute.Image)
if !ok {
+4 -3
View File
@@ -31,13 +31,14 @@ func writeSerialLogFile(ctx context.Context, state multistep.StateBag, serialLog
sdk := state.Get("sdk").(*ycsdk.SDK)
ui := state.Get("ui").(packersdk.Ui)
instanceID := state.Get("instance_id").(string)
if instanceID == "" {
instanceID, ok := state.GetOk("instance_id")
if !ok || instanceID.(string) == "" {
return nil
}
ui.Say("Try get instance's serial port output and write to file " + serialLogFile)
serialOutput, err := sdk.Compute().Instance().GetSerialPortOutput(ctx, &compute.GetInstanceSerialPortOutputRequest{
InstanceId: instanceID,
InstanceId: instanceID.(string),
})
if err != nil {
return fmt.Errorf("Failed to get serial port output for instance (id: %s): %s", instanceID, err)
+8 -10
View File
@@ -12,7 +12,7 @@ import (
"strings"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer/plugin"
"github.com/hashicorp/packer/packer"
)
const packerPluginsCheck = "packer-plugins-check"
@@ -119,7 +119,7 @@ func checkPluginName(name string) error {
// in the plugin configuration. At least one builder, provisioner, or post-processor should be found to validate the plugin's
// compatibility with Packer.
func discoverAndLoad() error {
config := plugin.Config{
config := packer.PluginConfig{
PluginMinPort: 10000,
PluginMaxPort: 25000,
}
@@ -129,20 +129,18 @@ func discoverAndLoad() error {
}
// TODO: validate correctness of plugins loaded by checking them against the output of the `describe` command.
plugins := config.GetPlugins()
if len(plugins.Builders) == 0 &&
len(plugins.Provisioners) == 0 &&
len(plugins.PostProcessors) == 0 &&
len(plugins.DataSources) == 0 {
return fmt.Errorf("couldn't load any Builder/Provisioner/Post-Processor/Datasource from the plugin binary")
if len(config.Builders.List()) == 0 &&
len(config.Provisioners.List()) == 0 &&
len(config.PostProcessors.List()) == 0 {
return fmt.Errorf("couldn't load any Builder/Provisioner/Post-Processor from the plugin binary")
}
return checkHCL2ConfigSpec(plugins)
return checkHCL2ConfigSpec(config)
}
// checkHCL2ConfigSpec checks if the hcl2spec config is present for the given plugins by validating that ConfigSpec() does not
// return an empty map of specs.
func checkHCL2ConfigSpec(plugins plugin.Plugins) error {
func checkHCL2ConfigSpec(plugins packer.PluginConfig) error {
var errs *packersdk.MultiError
for _, b := range plugins.Builders.List() {
builder, err := plugins.Builders.Start(b)
+1 -4
View File
@@ -67,10 +67,7 @@ func (m *Meta) GetConfigFromHCL(cla *MetaArgs) (*hcl2template.PackerConfig, int)
CorePackerVersion: version.SemVer,
CorePackerVersionString: version.FormattedVersion(),
Parser: hclparse.NewParser(),
BuilderSchemas: m.CoreConfig.Components.BuilderStore,
ProvisionersSchemas: m.CoreConfig.Components.ProvisionerStore,
PostProcessorsSchemas: m.CoreConfig.Components.PostProcessorStore,
DatasourceSchemas: m.CoreConfig.Components.DatasourceStore,
PluginConfig: m.CoreConfig.Components.PluginConfig,
}
cfg, diags := parser.Parse(cla.Path, cla.VarFiles, cla.Vars)
return cfg, writeDiags(m.Ui, parser.Files(), diags)
+9 -7
View File
@@ -68,13 +68,15 @@ func testMetaParallel(t *testing.T, builder *ParallelTestBuilder, locked *Locked
return Meta{
CoreConfig: &packer.CoreConfig{
Components: packer.ComponentFinder{
BuilderStore: packer.MapOfBuilder{
"parallel-test": func() (packersdk.Builder, error) { return builder, nil },
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
"lock": func() (packersdk.Builder, error) { return locked, nil },
},
ProvisionerStore: packer.MapOfProvisioner{
"sleep": func() (packersdk.Provisioner, error) { return &sleep.Provisioner{}, nil },
PluginConfig: &packer.PluginConfig{
Builders: packer.MapOfBuilder{
"parallel-test": func() (packersdk.Builder, error) { return builder, nil },
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
"lock": func() (packersdk.Builder, error) { return locked, nil },
},
Provisioners: packer.MapOfProvisioner{
"sleep": func() (packersdk.Provisioner, error) { return &sleep.Provisioner{}, nil },
},
},
},
},
+91 -15
View File
@@ -12,6 +12,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-uuid"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/builder/file"
"github.com/hashicorp/packer/builder/null"
@@ -695,6 +696,79 @@ func testHCLOnlyExceptFlags(t *testing.T, args, present, notPresent []string) {
}
}
func TestHCL2PostProcessorForceFlag(t *testing.T) {
t.Helper()
UUID, _ := uuid.GenerateUUID()
// Manifest will only clean with force if the build's PACKER_RUN_UUID are different
os.Setenv("PACKER_RUN_UUID", UUID)
defer os.Unsetenv("PACKER_RUN_UUID")
args := []string{
filepath.Join(testFixture("hcl"), "force.pkr.hcl"),
}
fCheck := fileCheck{
expectedContent: map[string]string{
"manifest.json": fmt.Sprintf(`{
"builds": [
{
"name": "potato",
"builder_type": "null",
"files": null,
"artifact_id": "Null",
"packer_run_uuid": %q,
"custom_data": null
}
],
"last_run_uuid": %q
}`, UUID, UUID),
},
}
defer fCheck.cleanup(t)
c := &BuildCommand{
Meta: testMetaFile(t),
}
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
fCheck.verify(t)
// Second build should override previous manifest
UUID, _ = uuid.GenerateUUID()
os.Setenv("PACKER_RUN_UUID", UUID)
args = []string{
"-force",
filepath.Join(testFixture("hcl"), "force.pkr.hcl"),
}
fCheck = fileCheck{
expectedContent: map[string]string{
"manifest.json": fmt.Sprintf(`{
"builds": [
{
"name": "potato",
"builder_type": "null",
"files": null,
"artifact_id": "Null",
"packer_run_uuid": %q,
"custom_data": null
}
],
"last_run_uuid": %q
}`, UUID, UUID),
},
}
c = &BuildCommand{
Meta: testMetaFile(t),
}
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
fCheck.verify(t)
}
func TestBuildCommand_HCLOnlyExceptOptions(t *testing.T) {
tests := []struct {
args []string
@@ -847,21 +921,23 @@ func fileExists(filename string) bool {
// available. This allows us to test a builder that writes files to disk.
func testCoreConfigBuilder(t *testing.T) *packer.CoreConfig {
components := packer.ComponentFinder{
BuilderStore: packer.MapOfBuilder{
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
},
ProvisionerStore: packer.MapOfProvisioner{
"shell-local": func() (packersdk.Provisioner, error) { return &shell_local.Provisioner{}, nil },
"shell": func() (packersdk.Provisioner, error) { return &shell.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &filep.Provisioner{}, nil },
},
PostProcessorStore: packer.MapOfPostProcessor{
"shell-local": func() (packersdk.PostProcessor, error) { return &shell_local_pp.PostProcessor{}, nil },
"manifest": func() (packersdk.PostProcessor, error) { return &manifest.PostProcessor{}, nil },
},
DatasourceStore: packersdk.MapOfDatasource{
"mock": func() (packersdk.Datasource, error) { return &packersdk.MockDatasource{}, nil },
PluginConfig: &packer.PluginConfig{
Builders: packer.MapOfBuilder{
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
},
Provisioners: packer.MapOfProvisioner{
"shell-local": func() (packersdk.Provisioner, error) { return &shell_local.Provisioner{}, nil },
"shell": func() (packersdk.Provisioner, error) { return &shell.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &filep.Provisioner{}, nil },
},
PostProcessors: packer.MapOfPostProcessor{
"shell-local": func() (packersdk.PostProcessor, error) { return &shell_local_pp.PostProcessor{}, nil },
"manifest": func() (packersdk.PostProcessor, error) { return &manifest.PostProcessor{}, nil },
},
DataSources: packer.MapOfDatasource{
"mock": func() (packersdk.Datasource, error) { return &packersdk.MockDatasource{}, nil },
},
},
}
return &packer.CoreConfig{
+8 -6
View File
@@ -16,12 +16,14 @@ import (
// available. This allows us to test a builder that writes files to disk.
func testCoreConfigSleepBuilder(t *testing.T) *packer.CoreConfig {
components := packer.ComponentFinder{
BuilderStore: packer.MapOfBuilder{
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
},
ProvisionerStore: packer.MapOfProvisioner{
"sleep": func() (packersdk.Provisioner, error) { return &sleep.Provisioner{}, nil },
"shell-local": func() (packersdk.Provisioner, error) { return &shell_local.Provisioner{}, nil },
PluginConfig: &packer.PluginConfig{
Builders: packer.MapOfBuilder{
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
},
Provisioners: packer.MapOfProvisioner{
"sleep": func() (packersdk.Provisioner, error) { return &sleep.Provisioner{}, nil },
"shell-local": func() (packersdk.Provisioner, error) { return &shell_local.Provisioner{}, nil },
},
},
}
return &packer.CoreConfig{
+12
View File
@@ -93,6 +93,18 @@ type BuildArgs struct {
OnError string
}
func (ia *InitArgs) AddFlagSets(flags *flag.FlagSet) {
flags.BoolVar(&ia.Upgrade, "upgrade", false, "upgrade any present plugin to the highest allowed version.")
ia.MetaArgs.AddFlagSets(flags)
}
// InitArgs represents a parsed cli line for a `packer build`
type InitArgs struct {
MetaArgs
Upgrade bool
}
// ConsoleArgs represents a parsed cli line for a `packer console`
type ConsoleArgs struct {
MetaArgs
+11
View File
@@ -3,6 +3,7 @@ package command
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/packer"
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
)
// CoreWrapper wraps a packer.Core in order to have it's Initialize func return
@@ -23,3 +24,13 @@ func (c *CoreWrapper) Initialize(_ packer.InitializeOptions) hcl.Diagnostics {
}
return nil
}
func (c *CoreWrapper) PluginRequirements() (plugingetter.Requirements, hcl.Diagnostics) {
return nil, hcl.Diagnostics{
&hcl.Diagnostic{
Summary: "Packer init is supported for HCL2 configuration templates only",
Detail: "Please manually install plugins or use a HCL2 configuration that will do that for you.",
Severity: hcl.DiagError,
},
}
}
+15 -13
View File
@@ -118,19 +118,21 @@ func commandMeta() Meta {
func getBareComponentFinder() packer.ComponentFinder {
return packer.ComponentFinder{
BuilderStore: packer.MapOfBuilder{
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
"amazon-ebs": func() (packersdk.Builder, error) { return &ebs.Builder{}, nil },
},
ProvisionerStore: packer.MapOfProvisioner{
"shell-local": func() (packersdk.Provisioner, error) { return &shell_local.Provisioner{}, nil },
"shell": func() (packersdk.Provisioner, error) { return &shell.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &filep.Provisioner{}, nil },
},
PostProcessorStore: packer.MapOfPostProcessor{
"shell-local": func() (packersdk.PostProcessor, error) { return &shell_local_pp.PostProcessor{}, nil },
"manifest": func() (packersdk.PostProcessor, error) { return &manifest.PostProcessor{}, nil },
PluginConfig: &packer.PluginConfig{
Builders: packer.MapOfBuilder{
"file": func() (packersdk.Builder, error) { return &file.Builder{}, nil },
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
"amazon-ebs": func() (packersdk.Builder, error) { return &ebs.Builder{}, nil },
},
Provisioners: packer.MapOfProvisioner{
"shell-local": func() (packersdk.Provisioner, error) { return &shell_local.Provisioner{}, nil },
"shell": func() (packersdk.Provisioner, error) { return &shell.Provisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &filep.Provisioner{}, nil },
},
PostProcessors: packer.MapOfPostProcessor{
"shell-local": func() (packersdk.PostProcessor, error) { return &shell_local_pp.PostProcessor{}, nil },
"manifest": func() (packersdk.PostProcessor, error) { return &manifest.PostProcessor{}, nil },
},
},
}
}
+220 -47
View File
@@ -74,9 +74,12 @@ const (
# All generated input variables will be of 'string' type as this is how Packer JSON
# views them; you can change their type later on. Read the variables type
# constraints documentation
# https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.
`
# https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.`
localsVarHeader = `
# All locals variables are generated from variables that uses expressions
# that are not allowed in HCL2 variables.
# Read the documentation for locals blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/locals`
packerBlockHeader = `
# See https://www.packer.io/docs/templates/hcl_templates/blocks/packer for more info
`
@@ -93,15 +96,28 @@ const (
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
`
amazonAmiDataHeader = `
# The amazon-ami data block is generated from your amazon builder source_ami_filter; a data
# from this block can be referenced in source and locals blocks.
# Read the documentation for data blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/data`
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon AMI Data Source here:
# https://www.packer.io/docs/datasources/amazon/ami`
amazonSecretsManagerDataHeader = `
# The amazon-secretsmanager data block is generated from your aws_secretsmanager template function; a data
# from this block can be referenced in source and locals blocks.
# Read the documentation for data blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon Secrets Manager Data Source here:
# https://www.packer.io/docs/datasources/amazon/secretsmanager`
)
var amazonSecretsManagerMap = map[string]map[string]interface{}{}
var localsVariableMap = map[string]string{}
func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2UpgradeArgs) int {
out := &bytes.Buffer{}
var output io.Writer
if err := os.MkdirAll(filepath.Dir(cla.OutputFile), 0); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to create output directory: %v", err))
@@ -131,20 +147,16 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
}
tpl := core.Template
// Packer section
if tpl.MinVersion != "" {
out.Write([]byte(packerBlockHeader))
fileContent := hclwrite.NewEmptyFile()
body := fileContent.Body()
packerBody := body.AppendNewBlock("packer", nil).Body()
packerBody.SetAttributeValue("required_version", cty.StringVal(fmt.Sprintf(">= %s", tpl.MinVersion)))
out.Write(fileContent.Bytes())
}
// OutPut Locals and Local blocks
localsContent := hclwrite.NewEmptyFile()
localsBody := localsContent.Body()
localsBody.AppendNewline()
localBody := localsBody.AppendNewBlock("locals", nil).Body()
out.Write([]byte(inputVarHeader))
localsOut := []byte{}
// Output variables section
variablesOut := []byte{}
variables := []*template.Variable{}
{
// sort variables to avoid map's randomness
@@ -157,27 +169,47 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
})
}
hasLocals := false
for _, variable := range variables {
variablesContent := hclwrite.NewEmptyFile()
variablesBody := variablesContent.Body()
variablesBody.AppendNewline()
variableBody := variablesBody.AppendNewBlock("variable", []string{variable.Key}).Body()
variableBody.SetAttributeRaw("type", hclwrite.Tokens{&hclwrite.Token{Bytes: []byte("string")}})
if variable.Default != "" || !variable.Required {
variableBody.SetAttributeValue("default", hcl2shim.HCL2ValueFromConfigValue(variable.Default))
}
sensitive := false
if isSensitiveVariable(variable.Key, tpl.SensitiveVariables) {
sensitive = true
variableBody.SetAttributeValue("sensitive", cty.BoolVal(true))
}
variablesBody.AppendNewline()
out.Write(transposeTemplatingCalls(variablesContent.Bytes()))
isLocal, out := variableTransposeTemplatingCalls(variablesContent.Bytes())
if isLocal {
if sensitive {
// Create Local block because this is sensitive
localContent := hclwrite.NewEmptyFile()
body := localContent.Body()
body.AppendNewline()
localBody := body.AppendNewBlock("local", []string{variable.Key}).Body()
localBody.SetAttributeValue("sensitive", cty.BoolVal(true))
localBody.SetAttributeValue("expression", hcl2shim.HCL2ValueFromConfigValue(variable.Default))
localsOut = append(localsOut, transposeTemplatingCalls(localContent.Bytes())...)
localsVariableMap[variable.Key] = "local"
continue
}
localBody.SetAttributeValue(variable.Key, hcl2shim.HCL2ValueFromConfigValue(variable.Default))
localsVariableMap[variable.Key] = "locals"
hasLocals = true
continue
}
variablesOut = append(variablesOut, out...)
}
fmt.Fprintln(out, `# "timestamp" template function replacement`)
fmt.Fprintln(out, `locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }`)
// Output sources section
if hasLocals {
localsOut = append(localsOut, transposeTemplatingCalls(localsContent.Bytes())...)
}
builders := []*template.Builder{}
{
@@ -187,7 +219,9 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
}
}
if err := c.writeAmazonAmiDatasource(builders, out); err != nil {
// Output amazon-ami data source section
amazonAmiOut, err := c.writeAmazonAmiDatasource(builders)
if err != nil {
return 1
}
@@ -195,30 +229,30 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
return builders[i].Type+builders[i].Name < builders[j].Type+builders[j].Name
})
out.Write([]byte(sourcesHeader))
// Output sources section
sourcesOut := []byte{}
for i, builderCfg := range builders {
sourcesContent := hclwrite.NewEmptyFile()
body := sourcesContent.Body()
body.AppendNewline()
if !c.Meta.CoreConfig.Components.BuilderStore.Has(builderCfg.Type) {
if !c.Meta.CoreConfig.Components.PluginConfig.Builders.Has(builderCfg.Type) {
c.Ui.Error(fmt.Sprintf("unknown builder type: %q\n", builderCfg.Type))
return 1
}
if builderCfg.Name == "" || builderCfg.Name == builderCfg.Type {
builderCfg.Name = fmt.Sprintf("autogenerated_%d", i+1)
}
builderCfg.Name = strings.ReplaceAll(strings.TrimSpace(builderCfg.Name), " ", "_")
sourceBody := body.AppendNewBlock("source", []string{builderCfg.Type, builderCfg.Name}).Body()
jsonBodyToHCL2Body(sourceBody, builderCfg.Config)
_, _ = out.Write(transposeTemplatingCalls(sourcesContent.Bytes()))
sourcesOut = append(sourcesOut, transposeTemplatingCalls(sourcesContent.Bytes())...)
}
// Output build section
out.Write([]byte(buildHeader))
buildContent := hclwrite.NewEmptyFile()
buildBody := buildContent.Body()
if tpl.Description != "" {
@@ -232,8 +266,10 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
}
buildBody.SetAttributeValue("sources", hcl2shim.HCL2ValueFromConfigValue(sourceNames))
buildBody.AppendNewline()
_, _ = buildContent.WriteTo(out)
buildOut := buildContent.Bytes()
// Output provisioners section
provisionersOut := []byte{}
for _, provisioner := range tpl.Provisioners {
provisionerContent := hclwrite.NewEmptyFile()
body := provisionerContent.Body()
@@ -255,8 +291,11 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
}
jsonBodyToHCL2Body(block.Body(), cfg)
out.Write(transposeTemplatingCalls(provisionerContent.Bytes()))
provisionersOut = append(provisionersOut, transposeTemplatingCalls(provisionerContent.Bytes())...)
}
// Output post-processors section
postProcessorsOut := []byte{}
for _, pps := range tpl.PostProcessors {
postProcessorContent := hclwrite.NewEmptyFile()
body := postProcessorContent.Body()
@@ -286,9 +325,69 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
jsonBodyToHCL2Body(ppBody, cfg)
}
_, _ = out.Write(transposeTemplatingCalls(postProcessorContent.Bytes()))
postProcessorsOut = append(postProcessorsOut, transposeTemplatingCalls(postProcessorContent.Bytes())...)
}
// Output amazon-secretsmanager data source section
keys := make([]string, 0, len(amazonSecretsManagerMap))
for k := range amazonSecretsManagerMap {
keys = append(keys, k)
}
sort.Strings(keys)
amazonSecretsDataOut := []byte{}
for _, dataSourceName := range keys {
datasourceContent := hclwrite.NewEmptyFile()
body := datasourceContent.Body()
body.AppendNewline()
datasourceBody := body.AppendNewBlock("data", []string{"amazon-secretsmanager", dataSourceName}).Body()
jsonBodyToHCL2Body(datasourceBody, amazonSecretsManagerMap[dataSourceName])
amazonSecretsDataOut = append(amazonSecretsDataOut, datasourceContent.Bytes()...)
}
// Write file
out := &bytes.Buffer{}
// Packer section
if tpl.MinVersion != "" {
out.Write([]byte(packerBlockHeader))
fileContent := hclwrite.NewEmptyFile()
body := fileContent.Body()
packerBody := body.AppendNewBlock("packer", nil).Body()
packerBody.SetAttributeValue("required_version", cty.StringVal(fmt.Sprintf(">= %s", tpl.MinVersion)))
out.Write(fileContent.Bytes())
}
out.Write([]byte(inputVarHeader))
out.Write(variablesOut)
if len(amazonSecretsManagerMap) > 0 {
out.Write([]byte(amazonSecretsManagerDataHeader))
out.Write(amazonSecretsDataOut)
}
if len(amazonAmiOut) > 0 {
out.Write([]byte(amazonAmiDataHeader))
out.Write(amazonAmiOut)
}
_, _ = out.Write([]byte("\n"))
fmt.Fprintln(out, `# "timestamp" template function replacement`)
fmt.Fprintln(out, `locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }`)
if len(localsOut) > 0 {
out.Write([]byte(localsVarHeader))
out.Write(localsOut)
}
out.Write([]byte(sourcesHeader))
out.Write(sourcesOut)
out.Write([]byte(buildHeader))
out.Write(buildOut)
out.Write(provisionersOut)
out.Write(postProcessorsOut)
_, _ = out.Write([]byte("}\n"))
_, _ = output.Write(hclwrite.Format(out.Bytes()))
@@ -298,9 +397,9 @@ func (c *HCL2UpgradeCommand) RunContext(buildCtx context.Context, cla *HCL2Upgra
return 0
}
func (c *HCL2UpgradeCommand) writeAmazonAmiDatasource(builders []*template.Builder, out *bytes.Buffer) error {
func (c *HCL2UpgradeCommand) writeAmazonAmiDatasource(builders []*template.Builder) ([]byte, error) {
amazonAmiOut := []byte{}
amazonAmiFilters := []map[string]interface{}{}
first := true
i := 1
for _, builder := range builders {
if strings.HasPrefix(builder.Type, "amazon-") {
@@ -308,7 +407,7 @@ func (c *HCL2UpgradeCommand) writeAmazonAmiDatasource(builders []*template.Build
sourceAmiFilterCfg := map[string]interface{}{}
if err := mapstructure.Decode(sourceAmiFilter, &sourceAmiFilterCfg); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to write amazon-ami data source: %v", err))
return err
return nil, err
}
duplicate := false
@@ -336,21 +435,17 @@ func (c *HCL2UpgradeCommand) writeAmazonAmiDatasource(builders []*template.Build
builder.Config["source_ami"] = sourceAmiDataRef
i++
if first {
out.Write([]byte(amazonAmiDataHeader))
first = false
}
datasourceContent := hclwrite.NewEmptyFile()
body := datasourceContent.Body()
body.AppendNewline()
sourceBody := body.AppendNewBlock("data", []string{"amazon-ami", dataSourceName}).Body()
jsonBodyToHCL2Body(sourceBody, sourceAmiFilterCfg)
_, _ = out.Write(transposeTemplatingCalls(datasourceContent.Bytes()))
amazonAmiOut = append(amazonAmiOut, transposeTemplatingCalls(datasourceContent.Bytes())...)
}
}
}
return nil
return amazonAmiOut, nil
}
type UnhandleableArgumentError struct {
@@ -377,14 +472,71 @@ func transposeTemplatingCalls(s []byte) []byte {
return append([]byte(fmt.Sprintf("\n# could not parse template for following block: %q\n", err)), s...)
}
funcMap := texttemplate.FuncMap{
"timestamp": func() string {
funcMap := templateCommonFunctionMap()
tpl, err := texttemplate.New("hcl2_upgrade").
Funcs(funcMap).
Parse(string(s))
if err != nil {
return fallbackReturn(err)
}
str := &bytes.Buffer{}
v := struct {
HTTPIP string
HTTPPort string
}{
HTTPIP: "{{ .HTTPIP }}",
HTTPPort: "{{ .HTTPPort }}",
}
if err := tpl.Execute(str, v); err != nil {
return fallbackReturn(err)
}
return str.Bytes()
}
func templateCommonFunctionMap() texttemplate.FuncMap {
return texttemplate.FuncMap{
"aws_secretsmanager": func(a ...string) string {
if len(a) == 2 {
for key, config := range amazonSecretsManagerMap {
nameOk := config["name"] == a[0]
keyOk := config["key"] == a[1]
if nameOk && keyOk {
return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", key)
}
}
id := fmt.Sprintf("autogenerated_%d", len(amazonSecretsManagerMap)+1)
amazonSecretsManagerMap[id] = map[string]interface{}{
"name": a[0],
"key": a[1],
}
return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", id)
}
for key, config := range amazonSecretsManagerMap {
nameOk := config["name"] == a[0]
if nameOk {
return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", key)
}
}
id := fmt.Sprintf("autogenerated_%d", len(amazonSecretsManagerMap)+1)
amazonSecretsManagerMap[id] = map[string]interface{}{
"name": a[0],
}
return fmt.Sprintf("${data.amazon-secretsmanager.%s.value}", id)
}, "timestamp": func() string {
return "${local.timestamp}"
},
"isotime": func() string {
return "${local.timestamp}"
},
"user": func(in string) string {
if _, ok := localsVariableMap[in]; ok {
// variable is now a local
return fmt.Sprintf("${local.%s}", in)
}
return fmt.Sprintf("${var.%s}", in)
},
"env": func(in string) string {
@@ -459,13 +611,34 @@ func transposeTemplatingCalls(s []byte) []byte {
return fmt.Sprintf("${build.type}")
},
}
}
// variableTransposeTemplatingCalls executes parts of blocks as go template files and replaces
// their result with their hcl2 variant for variables block only. If something goes wrong the template
// containing the go template string is returned.
// In variableTransposeTemplatingCalls the definition of aws_secretsmanager function will create a data source
// with the same name as the variable.
func variableTransposeTemplatingCalls(s []byte) (isLocal bool, body []byte) {
fallbackReturn := func(err error) []byte {
if strings.Contains(err.Error(), "unhandled") {
return append([]byte(fmt.Sprintf("\n# %s\n", err)), s...)
}
return append([]byte(fmt.Sprintf("\n# could not parse template for following block: %q\n", err)), s...)
}
funcMap := templateCommonFunctionMap()
funcMap["aws_secretsmanager"] = func(a ...string) string {
isLocal = true
return ""
}
tpl, err := texttemplate.New("hcl2_upgrade").
Funcs(funcMap).
Parse(string(s))
if err != nil {
return fallbackReturn(err)
return isLocal, fallbackReturn(err)
}
str := &bytes.Buffer{}
@@ -477,10 +650,10 @@ func transposeTemplatingCalls(s []byte) []byte {
HTTPPort: "{{ .HTTPPort }}",
}
if err := tpl.Execute(str, v); err != nil {
return fallbackReturn(err)
return isLocal, fallbackReturn(err)
}
return str.Bytes()
return isLocal, str.Bytes()
}
func jsonBodyToHCL2Body(out *hclwrite.Body, kvs map[string]interface{}) {
+5 -3
View File
@@ -19,14 +19,16 @@ func Test_hcl2_upgrade(t *testing.T) {
tc := []struct {
folder string
}{
{"hcl2_upgrade_basic"},
{"basic"},
{"minimal"},
{"source-name"},
}
for _, tc := range tc {
t.Run(tc.folder, func(t *testing.T) {
inputPath := filepath.Join(testFixture(tc.folder, "input.json"))
inputPath := filepath.Join(testFixture("hcl2_upgrade", tc.folder, "input.json"))
outputPath := inputPath + ".pkr.hcl"
expectedPath := filepath.Join(testFixture(tc.folder, "expected.pkr.hcl"))
expectedPath := filepath.Join(testFixture("hcl2_upgrade", tc.folder, "expected.pkr.hcl"))
p := helperCommand(t, "hcl2_upgrade", inputPath)
bs, err := p.CombinedOutput()
if err != nil {
+171
View File
@@ -0,0 +1,171 @@
package command
import (
"context"
"crypto/sha256"
"fmt"
"log"
"runtime"
"strings"
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
"github.com/hashicorp/packer/packer"
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
"github.com/hashicorp/packer/packer/plugin-getter/github"
"github.com/hashicorp/packer/version"
"github.com/posener/complete"
)
type InitCommand struct {
Meta
}
func (c *InitCommand) Run(args []string) int {
ctx, cleanup := handleTermInterrupt(c.Ui)
defer cleanup()
cfg, ret := c.ParseArgs(args)
if ret != 0 {
return ret
}
return c.RunContext(ctx, cfg)
}
func (c *InitCommand) ParseArgs(args []string) (*InitArgs, int) {
var cfg InitArgs
flags := c.Meta.FlagSet("init", 0)
flags.Usage = func() { c.Ui.Say(c.Help()) }
cfg.AddFlagSets(flags)
if err := flags.Parse(args); err != nil {
return &cfg, 1
}
args = flags.Args()
if len(args) != 1 {
flags.Usage()
return &cfg, 1
}
cfg.Path = args[0]
return &cfg, 0
}
func (c *InitCommand) RunContext(buildCtx context.Context, cla *InitArgs) int {
packerStarter, ret := c.GetConfig(&cla.MetaArgs)
if ret != 0 {
return ret
}
// Get plugins requirements
reqs, diags := packerStarter.PluginRequirements()
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
}
opts := plugingetter.ListInstallationsOptions{
FromFolders: c.Meta.CoreConfig.Components.PluginConfig.KnownPluginFolders,
BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
OS: runtime.GOOS,
ARCH: runtime.GOARCH,
APIVersionMajor: pluginsdk.APIVersionMajor,
APIVersionMinor: pluginsdk.APIVersionMinor,
Checksummers: []plugingetter.Checksummer{
{Type: "sha256", Hash: sha256.New()},
},
},
}
if runtime.GOOS == "windows" && opts.Ext == "" {
opts.BinaryInstallationOptions.Ext = ".exe"
}
log.Printf("[TRACE] init: %#v", opts)
getters := []plugingetter.Getter{
&github.Getter{
// In the past some terraform plugins downloads were blocked from a
// specific aws region by s3. Changing the user agent unblocked the
// downloads so having one user agent per version will help mitigate
// that a little more. Especially in the case someone forks this
// code to make it more aggressive or something.
// TODO: allow to set this from the config file or an environment
// variable.
UserAgent: "packer-getter-github-" + version.String(),
},
}
ui := &packer.ColoredUi{
Color: packer.UiColorCyan,
Ui: c.Ui,
}
for _, pluginRequirement := range reqs {
// Get installed plugins that match requirement
installs, err := pluginRequirement.ListInstallations(opts)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
log.Printf("[TRACE] for plugin %s found %d matching installation(s)", pluginRequirement.Identifier.ForDisplay(), len(installs))
if len(installs) > 0 && cla.Upgrade == false {
continue
}
newInstall, err := pluginRequirement.InstallLatest(plugingetter.InstallOptions{
InFolders: opts.FromFolders,
BinaryInstallationOptions: opts.BinaryInstallationOptions,
Getters: getters,
})
if err != nil {
c.Ui.Error(err.Error())
ret = 1
}
if newInstall != nil {
msg := fmt.Sprintf("Installed plugin %s %s in %q", pluginRequirement.Identifier.ForDisplay(), newInstall.Version, newInstall.BinaryPath)
ui.Say(msg)
}
}
return ret
}
func (*InitCommand) Help() string {
helpText := `
Usage: packer init [options] [config.pkr.hcl|folder/]
Install all the missing plugins required in a Packer config. Note that Packer
does not have a state.
This is the first command that should be executed when working with a new
or existing template.
This command is always safe to run multiple times. Though subsequent runs may
give errors, this command will never delete anything.
Options:
-upgrade On top of installing missing plugins, update
installed plugins to the latest available
version, if there is a new higher one. Note that
this still takes into consideration the version
constraint of the config.
`
return strings.TrimSpace(helpText)
}
func (*InitCommand) Synopsis() string {
return "Install missing plugins or upgrade plugins"
}
func (*InitCommand) AutocompleteArgs() complete.Predictor {
return complete.PredictNothing
}
func (*InitCommand) AutocompleteFlags() complete.Flags {
return complete.Flags{
"-upgrade": complete.PredictNothing,
}
}
+301
View File
@@ -0,0 +1,301 @@
package command
import (
"fmt"
"log"
"os"
"runtime"
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/packer-plugin-sdk/acctest"
"golang.org/x/mod/sumdb/dirhash"
)
type testCaseInit struct {
checkSkip func(*testing.T)
name string
Meta Meta
inPluginFolder map[string]string
expectedPackerConfigDirHashBeforeInit string
inConfigFolder map[string]string
packerConfigDir string
packerUserFolder string
want int
dirFiles []string
expectedPackerConfigDirHashAfterInit string
moreTests []func(*testing.T, testCaseInit)
}
type testBuild struct {
want int
}
func (tb testBuild) fn(t *testing.T, tc testCaseInit) {
bc := BuildCommand{
Meta: tc.Meta,
}
args := []string{tc.packerUserFolder}
want := tb.want
if got := bc.Run(args); got != want {
t.Errorf("BuildCommand.Run() = %v, want %v", got, want)
}
}
func TestInitCommand_Run(t *testing.T) {
// These tests will try to optimise for doing the least amount of github api
// requests whilst testing the max amount of things at once. Hopefully they
// don't require a GH token just yet. Acc tests are run on linux, darwin and
// windows, so requests are done 3 times.
cfg := &configDirSingleton{map[string]string{}}
tests := []testCaseInit{
{
nil,
// here we pre-write plugins with valid checksums, Packer will
// see those as valid installations it did.
// the directory hash before and after init should be the same,
// that's a no-op. This also should do no GH query, so it is best
// to always run it.
"already-installed-no-op",
testMetaFile(t),
map[string]string{
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_darwin_amd64": "1",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_darwin_amd64_SHA256SUM": "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_windows_amd64.exe": "1.exe",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_windows_amd64.exe_SHA256SUM": "07d8453027192ee0c4120242e6e84e2ca2328b8e0f506e2f818a1a5b82790a0b",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_linux_amd64": "1.out",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_linux_amd64_SHA256SUM": "59031c50e0dfeedfde2b4e9445754804dce3f29e4efa737eead0ca9b4f5b85a5",
},
"h1:Q5qyAOdD43hL3CquQdVfaHpOYGf0UsZ/+wVA9Ry6cbA=",
map[string]string{
`cfg.pkr.hcl`: `
packer {
required_plugins {
comment = {
source = "github.com/sylviamoss/comment"
version = "v0.2.018"
}
}
}`,
},
cfg.dir("1_pkr_config"),
cfg.dir("1_pkr_user_folder"),
0,
nil,
"h1:Q5qyAOdD43hL3CquQdVfaHpOYGf0UsZ/+wVA9Ry6cbA=",
[]func(t *testing.T, tc testCaseInit){
// test that a build will not work since plugins are broken for
// this tests (they are not binaries).
testBuild{want: 1}.fn,
},
},
{
func(t *testing.T) {
if os.Getenv(acctest.TestEnvVar) == "" {
t.Skipf("Acceptance test skipped unless env '%s' set", acctest.TestEnvVar)
}
},
// here we pre-write plugins with valid checksums, Packer will
// see those as valid installations it did.
// But because we require version 0.2.19, we will upgrade.
"already-installed-upgrade",
testMetaFile(t),
map[string]string{
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_darwin_amd64": "1",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_darwin_amd64_SHA256SUM": "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_windows_amd64.exe": "1.exe",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_windows_amd64.exe_SHA256SUM": "07d8453027192ee0c4120242e6e84e2ca2328b8e0f506e2f818a1a5b82790a0b",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_linux_amd64": "1.out",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_linux_amd64_SHA256SUM": "59031c50e0dfeedfde2b4e9445754804dce3f29e4efa737eead0ca9b4f5b85a5",
},
"h1:Q5qyAOdD43hL3CquQdVfaHpOYGf0UsZ/+wVA9Ry6cbA=",
map[string]string{
`cfg.pkr.hcl`: `
packer {
required_plugins {
comment = {
source = "github.com/sylviamoss/comment"
version = "v0.2.019"
}
}
}`,
`source.pkr.hcl`: `
source "null" "test" {
communicator = "none"
}
`,
`build.pkr.hcl`: `
build {
sources = ["source.null.test"]
provisioner "comment" {
comment = "Begin ¡"
ui = true
bubble_text = true
}
}
`,
},
cfg.dir("2_pkr_config"),
cfg.dir("2_pkr_user_folder"),
0,
[]string{
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_darwin_amd64",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_darwin_amd64_SHA256SUM",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_linux_amd64",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_linux_amd64_SHA256SUM",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_windows_amd64.exe",
"github.com/sylviamoss/comment/packer-plugin-comment_v0.2.18_x5.0_windows_amd64.exe_SHA256SUM",
map[string]string{
"darwin": "github.com/sylviamoss/comment/packer-plugin-comment_v0.2.19_x5.0_darwin_amd64_SHA256SUM",
"linux": "github.com/sylviamoss/comment/packer-plugin-comment_v0.2.19_x5.0_linux_amd64_SHA256SUM",
"windows": "github.com/sylviamoss/comment/packer-plugin-comment_v0.2.19_x5.0_windows_amd64.exe_SHA256SUM",
}[runtime.GOOS],
map[string]string{
"darwin": "github.com/sylviamoss/comment/packer-plugin-comment_v0.2.19_x5.0_darwin_amd64",
"linux": "github.com/sylviamoss/comment/packer-plugin-comment_v0.2.19_x5.0_linux_amd64",
"windows": "github.com/sylviamoss/comment/packer-plugin-comment_v0.2.19_x5.0_windows_amd64.exe",
}[runtime.GOOS],
},
map[string]string{
"darwin": "h1:ORwcCYUx8z/5n/QvuTJo2vrgKpfJA4AxlNg1G9/BCDI=",
"linux": "h1:CGym0+Nd0LEANgzqL0wx/LDjRL8bYwlpZ0HajPJo/hs=",
"windows": "h1:ag0/C1YjP7KoEDYOiJHE0K+lhFgs0tVgjriWCXVT1fg=",
}[runtime.GOOS],
[]func(t *testing.T, tc testCaseInit){
// test that a build will work as the plugin was just installed
testBuild{want: 0}.fn,
},
},
{
func(t *testing.T) {
if os.Getenv(acctest.TestEnvVar) == "" {
t.Skipf("Acceptance test skipped unless env '%s' set", acctest.TestEnvVar)
}
},
"release-with-no-binary",
testMetaFile(t),
nil,
"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
map[string]string{
`cfg.pkr.hcl`: `
packer {
required_plugins {
comment = {
source = "github.com/sylviamoss/comment"
version = "v0.2.20"
}
}
}`,
},
cfg.dir("3_pkr_config"),
cfg.dir("3_pkr_user_folder"),
1,
nil,
"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
nil,
},
{
func(t *testing.T) {
if os.Getenv(acctest.TestEnvVar) == "" {
t.Skipf("Acceptance test skipped unless env '%s' set", acctest.TestEnvVar)
}
tc := testCaseInit{}
if err := getBinary(getBinaryOptions{
GetZip: "https://github.com/azr/packer-provisioner-comment/releases/download/v1.0.0/packer-provisioner-comment_v1.0.0_" + runtime.GOOS + "_" + runtime.GOARCH + ".tar.gz",
UnzipIn: tc.inPluginFolder,
}); err != nil {
t.Fatal("getBinary: %v", err)
}
},
"release-with-no-binary",
testMetaFile(t),
nil,
"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
map[string]string{
`cfg.pkr.hcl`: `
packer {
required_plugins {
comment = {
source = "github.com/sylviamoss/comment"
version = "v0.2.20"
}
}
}`,
},
cfg.dir("3_pkr_config"),
cfg.dir("3_pkr_user_folder"),
1,
nil,
"h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.checkSkip != nil {
tt.checkSkip(t)
if t.Skipped() {
return
}
}
log.Printf("starting %s", tt.name)
createFiles(tt.packerConfigDir, tt.inPluginFolder)
t.Cleanup(func() {
_ = os.RemoveAll(tt.packerConfigDir)
})
createFiles(tt.packerUserFolder, tt.inConfigFolder)
t.Cleanup(func() {
_ = os.RemoveAll(tt.packerUserFolder)
})
hash, err := dirhash.HashDir(tt.packerConfigDir, "", dirhash.DefaultHash)
if err != nil {
t.Fatalf("HashDir: %v", err)
}
if diff := cmp.Diff(tt.expectedPackerConfigDirHashBeforeInit, hash); diff != "" {
t.Errorf("unexpected dir hash before init: %s", diff)
}
args := []string{tt.packerUserFolder}
c := &InitCommand{
Meta: tt.Meta,
}
c.CoreConfig.Components.PluginConfig.KnownPluginFolders = []string{tt.packerConfigDir}
if got := c.Run(args); got != tt.want {
t.Errorf("InitCommand.Run() = %v, want %v", got, tt.want)
}
if tt.dirFiles != nil {
dirFiles, err := dirhash.DirFiles(tt.packerConfigDir, "")
if err != nil {
t.Fatalf("DirFiles: %v", err)
}
sort.Strings(tt.dirFiles)
sort.Strings(dirFiles)
if diff := cmp.Diff(tt.dirFiles, dirFiles); diff != "" {
t.Errorf("found files differ: %v", diff)
}
}
hash, err = dirhash.HashDir(tt.packerConfigDir, "", dirhash.DefaultHash)
if err != nil {
t.Fatalf("HashDir: %v", err)
}
if diff := cmp.Diff(tt.expectedPackerConfigDirHashAfterInit, hash); diff != "" {
t.Errorf("unexpected dir hash after init: %s", diff)
}
for i, subTest := range tt.moreTests {
t.Run(fmt.Sprintf("-subtest-%d", i), func(t *testing.T) {
subTest(t, tt)
})
}
})
}
}
@@ -42,13 +42,13 @@ build {
}
post-processor "shell-local" {
only = ["sources.file.vanilla"]
only = ["file.vanilla"]
name = "tomato"
inline = [ "echo apple > tomato.txt" ]
}
post-processor "shell-local" {
only = ["sources.file.chocolate"]
only = ["file.chocolate"]
inline = [ "echo apple > unnamed.txt" ]
}
}
+12
View File
@@ -0,0 +1,12 @@
source "null" "potato" {
communicator = "none"
}
build {
sources = ["sources.null.potato"]
post-processor "manifest" {
output = "manifest.json"
strip_time = true
}
}
@@ -47,13 +47,36 @@ variable "secret_account" {
sensitive = true
}
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# The amazon-secretsmanager data block is generated from your aws_secretsmanager template function; a data
# from this block can be referenced in source and locals blocks.
# Read the documentation for data blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon Secrets Manager Data Source here:
# https://www.packer.io/docs/datasources/amazon/secretsmanager
data "amazon-secretsmanager" "autogenerated_1" {
name = "sample/app/password"
}
data "amazon-secretsmanager" "autogenerated_2" {
key = "api_key"
name = "sample/app/passwords"
}
data "amazon-secretsmanager" "autogenerated_3" {
name = "some_secret"
}
data "amazon-secretsmanager" "autogenerated_4" {
key = "with_key"
name = "some_secret"
}
# The amazon-ami data block is generated from your amazon builder source_ami_filter; a data
# from this block can be referenced in source and locals blocks.
# Read the documentation for data blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon AMI Data Source here:
# https://www.packer.io/docs/datasources/amazon/ami
data "amazon-ami" "autogenerated_1" {
filters = {
name = "ubuntu/images/*/ubuntu-xenial-16.04-amd64-server-*"
@@ -64,6 +87,22 @@ data "amazon-ami" "autogenerated_1" {
owners = ["099720109477"]
}
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# All locals variables are generated from variables that uses expressions
# that are not allowed in HCL2 variables.
# Read the documentation for locals blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/locals
local "password" {
sensitive = true
expression = "${data.amazon-secretsmanager.autogenerated_1.value}"
}
locals {
password_key = "MY_KEY_${data.amazon-secretsmanager.autogenerated_2.value}"
}
# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
@@ -135,6 +174,12 @@ build {
inline = ["echo ${var.secret_account}", "echo ${build.ID}", "echo ${build.SSHPublicKey} | head -c 14", "echo ${path.root} is not ${path.cwd}", "echo ${packer.version}", "echo ${uuidv4()}"]
max_retries = "5"
}
provisioner "shell" {
inline = ["echo ${local.password}", "echo ${data.amazon-secretsmanager.autogenerated_1.value}", "echo ${local.password_key}", "echo ${data.amazon-secretsmanager.autogenerated_2.value}"]
}
provisioner "shell" {
inline = ["echo ${data.amazon-secretsmanager.autogenerated_3.value}", "echo ${data.amazon-secretsmanager.autogenerated_4.value}"]
}
# template: hcl2_upgrade:2:38: executing "hcl2_upgrade" at <clean_resource_name>: error calling clean_resource_name: unhandled "clean_resource_name" call:
# there is no way to automatically upgrade the "clean_resource_name" call.
@@ -5,13 +5,16 @@
"aws_region": null,
"aws_secondary_region": "{{ env `AWS_DEFAULT_REGION` }}",
"aws_secret_key": "",
"aws_access_key": ""
"aws_access_key": "",
"password": "{{ aws_secretsmanager `sample/app/password` }}",
"password_key": "MY_KEY_{{ aws_secretsmanager `sample/app/passwords` `api_key` }}"
},
"sensitive-variables": [
"aws_secret_key",
"aws_access_key",
"secret_account",
"potato"
"potato",
"password"
],
"builders": [
{
@@ -128,6 +131,22 @@
"echo {{ uuid }}"
]
},
{
"type": "shell",
"inline": [
"echo {{ user `password` }}",
"echo {{ aws_secretsmanager `sample/app/password` }}",
"echo {{ user `password_key` }}",
"echo {{ aws_secretsmanager `sample/app/passwords` `api_key` }}"
]
},
{
"type": "shell",
"inline": [
"echo {{ aws_secretsmanager `some_secret` }}",
"echo {{ aws_secretsmanager `some_secret` `with_key` }}"
]
},
{
"type": "shell",
"inline": [
@@ -0,0 +1,78 @@
# This file was autogenerated by the 'packer hcl2_upgrade' command. We
# recommend double checking that everything is correct before going forward. We
# also recommend treating this file as disposable. The HCL2 blocks in this
# file can be moved to other files. For example, the variable blocks could be
# moved to their own 'variables.pkr.hcl' file, etc. Those files need to be
# suffixed with '.pkr.hcl' to be visible to Packer. To use multiple files at
# once they also need to be in the same folder. 'packer inspect folder/'
# will describe to you what is in that folder.
# Avoid mixing go templating calls ( for example ```{{ upper(`string`) }}``` )
# and HCL2 calls (for example '${ var.string_value_example }' ). They won't be
# executed together and the outcome will be unknown.
# See https://www.packer.io/docs/templates/hcl_templates/blocks/packer for more info
packer {
required_version = ">= 1.6.0"
}
# All generated input variables will be of 'string' type as this is how Packer JSON
# views them; you can change their type later on. Read the variables type
# constraints documentation
# https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.
variable "aws_access_key" {
type = string
default = ""
}
variable "aws_region" {
type = string
}
variable "aws_secret_key" {
type = string
default = ""
}
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "amazon-ebs" "autogenerated_1" {
access_key = "${var.aws_access_key}"
ami_description = "Ubuntu 16.04 LTS - expand root partition"
ami_name = "ubuntu-16-04-test-${local.timestamp}"
encrypt_boot = true
launch_block_device_mappings {
delete_on_termination = true
device_name = "/dev/sda1"
volume_size = 48
volume_type = "gp2"
}
region = "${var.aws_region}"
secret_key = "${var.aws_secret_key}"
source_ami = "ami1234567"
spot_instance_types = ["t2.small", "t2.medium", "t2.large"]
spot_price = "0.0075"
ssh_interface = "session_manager"
ssh_username = "ubuntu"
temporary_iam_instance_profile_policy_document {
Statement {
Action = ["*"]
Effect = "Allow"
Resource = ["*"]
}
Version = "2012-10-17"
}
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.amazon-ebs.autogenerated_1"]
}
@@ -0,0 +1,48 @@
{
"min_packer_version": "1.6.0",
"variables": {
"aws_region": null,
"aws_secret_key": "",
"aws_access_key": ""
},
"builders": [
{
"type": "amazon-ebs",
"region": "{{ user `aws_region` }}",
"secret_key": "{{ user `aws_secret_key` }}",
"access_key": "{{ user `aws_access_key` }}",
"ami_name": "ubuntu-16-04-test-{{ timestamp }}",
"ami_description": "Ubuntu 16.04 LTS - expand root partition",
"source_ami": "ami1234567",
"launch_block_device_mappings": [
{
"delete_on_termination": true,
"device_name": "/dev/sda1",
"volume_type": "gp2",
"volume_size": 48
}
],
"spot_price": "0.0075",
"spot_instance_types": [
"t2.small",
"t2.medium",
"t2.large"
],
"encrypt_boot": true,
"ssh_username": "ubuntu",
"temporary_iam_instance_profile_policy_document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"*"
],
"Resource": ["*"]
}
]
},
"ssh_interface": "session_manager"
}
]
}
@@ -0,0 +1,78 @@
# This file was autogenerated by the 'packer hcl2_upgrade' command. We
# recommend double checking that everything is correct before going forward. We
# also recommend treating this file as disposable. The HCL2 blocks in this
# file can be moved to other files. For example, the variable blocks could be
# moved to their own 'variables.pkr.hcl' file, etc. Those files need to be
# suffixed with '.pkr.hcl' to be visible to Packer. To use multiple files at
# once they also need to be in the same folder. 'packer inspect folder/'
# will describe to you what is in that folder.
# Avoid mixing go templating calls ( for example ```{{ upper(`string`) }}``` )
# and HCL2 calls (for example '${ var.string_value_example }' ). They won't be
# executed together and the outcome will be unknown.
# See https://www.packer.io/docs/templates/hcl_templates/blocks/packer for more info
packer {
required_version = ">= 1.6.0"
}
# All generated input variables will be of 'string' type as this is how Packer JSON
# views them; you can change their type later on. Read the variables type
# constraints documentation
# https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.
variable "aws_access_key" {
type = string
default = ""
}
variable "aws_region" {
type = string
}
variable "aws_secret_key" {
type = string
default = ""
}
# "timestamp" template function replacement
locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }
# source blocks are generated from your builders; a source can be referenced in
# build blocks. A build block runs provisioner and post-processors on a
# source. Read the documentation for source blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/source
source "amazon-ebs" "party_parrot" {
access_key = "${var.aws_access_key}"
ami_description = "Ubuntu 16.04 LTS - expand root partition"
ami_name = "ubuntu-16-04-test-${local.timestamp}"
encrypt_boot = true
launch_block_device_mappings {
delete_on_termination = true
device_name = "/dev/sda1"
volume_size = 48
volume_type = "gp2"
}
region = "${var.aws_region}"
secret_key = "${var.aws_secret_key}"
source_ami = "ami1234567"
spot_instance_types = ["t2.small", "t2.medium", "t2.large"]
spot_price = "0.0075"
ssh_interface = "session_manager"
ssh_username = "ubuntu"
temporary_iam_instance_profile_policy_document {
Statement {
Action = ["*"]
Effect = "Allow"
Resource = ["*"]
}
Version = "2012-10-17"
}
}
# a build block invokes sources and runs provisioning steps on them. The
# documentation for build blocks can be found here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
build {
sources = ["source.amazon-ebs.party_parrot"]
}
@@ -0,0 +1,49 @@
{
"min_packer_version": "1.6.0",
"variables": {
"aws_region": null,
"aws_secret_key": "",
"aws_access_key": ""
},
"builders": [
{
"type": "amazon-ebs",
"name": " party parrot ",
"region": "{{ user `aws_region` }}",
"secret_key": "{{ user `aws_secret_key` }}",
"access_key": "{{ user `aws_access_key` }}",
"ami_name": "ubuntu-16-04-test-{{ timestamp }}",
"ami_description": "Ubuntu 16.04 LTS - expand root partition",
"source_ami": "ami1234567",
"launch_block_device_mappings": [
{
"delete_on_termination": true,
"device_name": "/dev/sda1",
"volume_type": "gp2",
"volume_size": 48
}
],
"spot_price": "0.0075",
"spot_instance_types": [
"t2.small",
"t2.medium",
"t2.large"
],
"encrypt_boot": true,
"ssh_username": "ubuntu",
"temporary_iam_instance_profile_policy_document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"*"
],
"Resource": ["*"]
}
]
},
"ssh_interface": "session_manager"
}
]
}
+41
View File
@@ -0,0 +1,41 @@
package command
import (
"io/ioutil"
"log"
"os"
"path/filepath"
)
func mustString(s string, e error) string {
if e != nil {
panic(e)
}
return s
}
func createFiles(dir string, content map[string]string) {
for relPath, content := range content {
contentPath := filepath.Join(dir, relPath)
if err := os.MkdirAll(filepath.Dir(contentPath), 0777); err != nil {
panic(err)
}
if err := ioutil.WriteFile(contentPath, []byte(content), 0666); err != nil {
panic(err)
}
log.Printf("created tmp file: %s", contentPath)
}
}
type configDirSingleton struct {
dirs map[string]string
}
// when you call dir twice with the same key, the result should be the same
func (c *configDirSingleton) dir(key string) string {
if v, exists := c.dirs[key]; exists {
return v
}
c.dirs[key] = mustString(ioutil.TempDir("", "pkr-test-cfg-dir-"+key))
return c.dirs[key]
}
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/google/go-cmp/cmp"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer"
)
func TestValidateCommand(t *testing.T) {
@@ -49,7 +50,7 @@ func TestValidateCommand(t *testing.T) {
func TestValidateCommand_SkipDatasourceExecution(t *testing.T) {
datasourceMock := &packersdk.MockDatasource{}
meta := testMetaFile(t)
meta.CoreConfig.Components.DatasourceStore = packersdk.MapOfDatasource{
meta.CoreConfig.Components.PluginConfig.DataSources = packer.MapOfDatasource{
"mock": func() (packersdk.Datasource, error) {
return datasourceMock, nil
},
+6
View File
@@ -44,6 +44,12 @@ func init() {
}, nil
},
"init": func() (cli.Command, error) {
return &command.InitCommand{
Meta: *CommandMeta,
}, nil
},
"inspect": func() (cli.Command, error) {
return &command.InspectCommand{
Meta: *CommandMeta,
+36 -44
View File
@@ -14,7 +14,6 @@ import (
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/command"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/plugin"
)
// PACKERSPACE is used to represent the spaces that separate args for a command
@@ -22,16 +21,13 @@ import (
const PACKERSPACE = "-PACKERSPACE-"
type config struct {
DisableCheckpoint bool `json:"disable_checkpoint"`
DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
RawBuilders map[string]string `json:"builders"`
RawProvisioners map[string]string `json:"provisioners"`
RawPostProcessors map[string]string `json:"post-processors"`
Builders packer.MapOfBuilder `json:"-"`
Provisioners packer.MapOfProvisioner `json:"-"`
PostProcessors packer.MapOfPostProcessor `json:"-"`
Datasources packer.MapOfDatasource `json:"-"`
Plugins plugin.Config
DisableCheckpoint bool `json:"disable_checkpoint"`
DisableCheckpointSignature bool `json:"disable_checkpoint_signature"`
RawBuilders map[string]string `json:"builders"`
RawProvisioners map[string]string `json:"provisioners"`
RawPostProcessors map[string]string `json:"post-processors"`
Plugins *packer.PluginConfig
}
// decodeConfig decodes configuration in JSON format from the given io.Reader into
@@ -97,19 +93,19 @@ func (c *config) loadSingleComponent(path string) (string, error) {
switch {
case strings.HasPrefix(pluginName, "packer-builder-"):
pluginName = pluginName[len("packer-builder-"):]
c.Builders[pluginName] = func() (packersdk.Builder, error) {
c.Plugins.Builders.Set(pluginName, func() (packersdk.Builder, error) {
return c.Plugins.Client(path).Builder()
}
})
case strings.HasPrefix(pluginName, "packer-post-processor-"):
pluginName = pluginName[len("packer-post-processor-"):]
c.PostProcessors[pluginName] = func() (packersdk.PostProcessor, error) {
c.Plugins.PostProcessors.Set(pluginName, func() (packersdk.PostProcessor, error) {
return c.Plugins.Client(path).PostProcessor()
}
})
case strings.HasPrefix(pluginName, "packer-provisioner-"):
pluginName = pluginName[len("packer-provisioner-"):]
c.Provisioners[pluginName] = func() (packersdk.Provisioner, error) {
c.Plugins.Provisioners.Set(pluginName, func() (packersdk.Provisioner, error) {
return c.Plugins.Client(path).Provisioner()
}
})
}
return pluginName, nil
@@ -119,7 +115,7 @@ func (c *config) loadSingleComponent(path string) (string, error) {
// implementations from the defined plugins.
func (c *config) StartBuilder(name string) (packersdk.Builder, error) {
log.Printf("Loading builder: %s\n", name)
return c.Builders.Start(name)
return c.Plugins.Builders.Start(name)
}
// This is a proper implementation of packer.HookFunc that can be used
@@ -133,14 +129,14 @@ func (c *config) StarHook(name string) (packersdk.Hook, error) {
// packersdk.PostProcessor implementations from defined plugins.
func (c *config) StartPostProcessor(name string) (packersdk.PostProcessor, error) {
log.Printf("Loading post-processor: %s", name)
return c.PostProcessors.Start(name)
return c.Plugins.PostProcessors.Start(name)
}
// This is a proper packer.ProvisionerFunc that can be used to load
// packer.Provisioner implementations from defined plugins.
func (c *config) StartProvisioner(name string) (packersdk.Provisioner, error) {
log.Printf("Loading provisioner: %s\n", name)
return c.Provisioners.Start(name)
return c.Plugins.Provisioners.Start(name)
}
func (c *config) discoverInternalComponents() error {
@@ -153,49 +149,45 @@ func (c *config) discoverInternalComponents() error {
for builder := range command.Builders {
builder := builder
_, found := (c.Builders)[builder]
if !found {
c.Builders[builder] = func() (packersdk.Builder, error) {
bin := fmt.Sprintf("%s%splugin%spacker-builder-%s",
packerPath, PACKERSPACE, PACKERSPACE, builder)
if !c.Plugins.Builders.Has(builder) {
bin := fmt.Sprintf("%s%splugin%spacker-builder-%s",
packerPath, PACKERSPACE, PACKERSPACE, builder)
c.Plugins.Builders.Set(builder, func() (packersdk.Builder, error) {
return c.Plugins.Client(bin).Builder()
}
})
}
}
for provisioner := range command.Provisioners {
provisioner := provisioner
_, found := (c.Provisioners)[provisioner]
if !found {
c.Provisioners[provisioner] = func() (packersdk.Provisioner, error) {
bin := fmt.Sprintf("%s%splugin%spacker-provisioner-%s",
packerPath, PACKERSPACE, PACKERSPACE, provisioner)
if !c.Plugins.Provisioners.Has(provisioner) {
bin := fmt.Sprintf("%s%splugin%spacker-provisioner-%s",
packerPath, PACKERSPACE, PACKERSPACE, provisioner)
c.Plugins.Provisioners.Set(provisioner, func() (packersdk.Provisioner, error) {
return c.Plugins.Client(bin).Provisioner()
}
})
}
}
for postProcessor := range command.PostProcessors {
postProcessor := postProcessor
_, found := (c.PostProcessors)[postProcessor]
if !found {
c.PostProcessors[postProcessor] = func() (packersdk.PostProcessor, error) {
bin := fmt.Sprintf("%s%splugin%spacker-post-processor-%s",
packerPath, PACKERSPACE, PACKERSPACE, postProcessor)
if !c.Plugins.PostProcessors.Has(postProcessor) {
bin := fmt.Sprintf("%s%splugin%spacker-post-processor-%s",
packerPath, PACKERSPACE, PACKERSPACE, postProcessor)
c.Plugins.PostProcessors.Set(postProcessor, func() (packersdk.PostProcessor, error) {
return c.Plugins.Client(bin).PostProcessor()
}
})
}
}
for dataSource := range command.Datasources {
dataSource := dataSource
_, found := (c.Datasources)[dataSource]
if !found {
c.Datasources[dataSource] = func() (packersdk.Datasource, error) {
bin := fmt.Sprintf("%s%splugin%spacker-datasource-%s",
packerPath, PACKERSPACE, PACKERSPACE, dataSource)
if !c.Plugins.DataSources.Has(dataSource) {
bin := fmt.Sprintf("%s%splugin%spacker-datasource-%s",
packerPath, PACKERSPACE, PACKERSPACE, dataSource)
c.Plugins.DataSources.Set(dataSource, func() (packersdk.Datasource, error) {
return c.Plugins.Client(bin).Datasource()
}
})
}
}
+33 -22
View File
@@ -48,10 +48,13 @@ func TestLoadExternalComponentsFromConfig(t *testing.T) {
}
defer cleanUpFunc()
var cfg config
cfg.Builders = packer.MapOfBuilder{}
cfg.PostProcessors = packer.MapOfPostProcessor{}
cfg.Provisioners = packer.MapOfProvisioner{}
cfg := config{
Plugins: &packer.PluginConfig{
Builders: packer.MapOfBuilder{},
PostProcessors: packer.MapOfPostProcessor{},
Provisioners: packer.MapOfProvisioner{},
},
}
if err := decodeConfig(strings.NewReader(packerConfigData), &cfg); err != nil {
t.Fatalf("error encountered decoding configuration: %v", err)
@@ -59,16 +62,16 @@ func TestLoadExternalComponentsFromConfig(t *testing.T) {
cfg.LoadExternalComponentsFromConfig()
if len(cfg.Builders) != 1 || !cfg.Builders.Has("cloud-xyz") {
t.Errorf("failed to load external builders; got %v as the resulting config", cfg.Builders)
if len(cfg.Plugins.Builders.List()) != 1 || !cfg.Plugins.Builders.Has("cloud-xyz") {
t.Errorf("failed to load external builders; got %v as the resulting config", cfg.Plugins.Builders)
}
if len(cfg.PostProcessors) != 1 || !cfg.PostProcessors.Has("noop") {
t.Errorf("failed to load external post-processors; got %v as the resulting config", cfg.PostProcessors)
if len(cfg.Plugins.PostProcessors.List()) != 1 || !cfg.Plugins.PostProcessors.Has("noop") {
t.Errorf("failed to load external post-processors; got %v as the resulting config", cfg.Plugins.PostProcessors)
}
if len(cfg.Provisioners) != 1 || !cfg.Provisioners.Has("super-shell") {
t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Provisioners)
if len(cfg.Plugins.Provisioners.List()) != 1 || !cfg.Plugins.Provisioners.Has("super-shell") {
t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Plugins.Provisioners)
}
}
@@ -80,8 +83,13 @@ func TestLoadExternalComponentsFromConfig_onlyProvisioner(t *testing.T) {
}
defer cleanUpFunc()
var cfg config
cfg.Provisioners = packer.MapOfProvisioner{}
cfg := config{
Plugins: &packer.PluginConfig{
Builders: packer.MapOfBuilder{},
PostProcessors: packer.MapOfPostProcessor{},
Provisioners: packer.MapOfProvisioner{},
},
}
if err := decodeConfig(strings.NewReader(packerConfigData), &cfg); err != nil {
t.Fatalf("error encountered decoding configuration: %v", err)
@@ -95,16 +103,16 @@ func TestLoadExternalComponentsFromConfig_onlyProvisioner(t *testing.T) {
cfg.LoadExternalComponentsFromConfig()
if len(cfg.Builders) != 0 {
t.Errorf("loaded external builders when it wasn't supposed to; got %v as the resulting config", cfg.Builders)
if len(cfg.Plugins.Builders.List()) != 0 {
t.Errorf("loaded external builders when it wasn't supposed to; got %v as the resulting config", cfg.Plugins.Builders)
}
if len(cfg.PostProcessors) != 0 {
t.Errorf("loaded external post-processors when it wasn't supposed to; got %v as the resulting config", cfg.PostProcessors)
if len(cfg.Plugins.PostProcessors.List()) != 0 {
t.Errorf("loaded external post-processors when it wasn't supposed to; got %v as the resulting config", cfg.Plugins.PostProcessors)
}
if len(cfg.Provisioners) != 1 || !cfg.Provisioners.Has("super-shell") {
t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Provisioners)
if len(cfg.Plugins.Provisioners.List()) != 1 || !cfg.Plugins.Provisioners.Has("super-shell") {
t.Errorf("failed to load external provisioners; got %v as the resulting config", cfg.Plugins.Provisioners)
}
}
@@ -125,10 +133,13 @@ func TestLoadSingleComponent(t *testing.T) {
{pluginPath: "./non-existing-file", errorExpected: true},
}
var cfg config
cfg.Builders = packer.MapOfBuilder{}
cfg.PostProcessors = packer.MapOfPostProcessor{}
cfg.Provisioners = packer.MapOfProvisioner{}
cfg := config{
Plugins: &packer.PluginConfig{
Builders: packer.MapOfBuilder{},
PostProcessors: packer.MapOfPostProcessor{},
Provisioners: packer.MapOfProvisioner{},
},
}
for _, tc := range tt {
tc := tc
+34 -2
View File
@@ -1,7 +1,39 @@
source "null" "example" {
source "amazon-ebs" "example" {
communicator = "none"
source_ami = "potato"
ami_name = "potato"
instance_type = "potato"
}
build {
sources = ["source.null.example"]
name = "my-provisioners-are-cooler"
sources = ["source.amazon-ebs.example"]
provisioner "comment-that-works" {
}
}
packer {
required_plugins {
comment = {
source = "sylviamoss/comment"
version = "v0.2.15"
}
comment-that-works = {
source = "sylviamoss/comment"
version = "v0.2.19"
}
}
}
build {
sources = ["source.amazon-ebs.example"]
provisioner "comment-my-provisioner" {
}
provisioner "shell-local" {
inline = ["yo"]
}
}
+5 -2
View File
@@ -23,7 +23,7 @@ require (
github.com/cheggaaa/pb v1.0.27
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/digitalocean/go-qemu v0.0.0-20181112162955-dd7bb9c771b8
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/fatih/camelcase v1.0.0
@@ -33,6 +33,7 @@ require (
github.com/gobwas/glob v0.2.3
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/google/go-cmp v0.5.2
github.com/google/go-github/v33 v33.0.1-0.20210113204525-9318e629ec69
github.com/google/uuid v1.1.2
github.com/gophercloud/gophercloud v0.12.0
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c
@@ -49,7 +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-sdk v0.0.9
github.com/hashicorp/packer-plugin-sdk v0.0.11
github.com/hashicorp/vault/api v1.0.4
github.com/hetznercloud/hcloud-go v1.15.1
github.com/hyperonecom/h1-client-go v0.0.0-20191203060043-b46280e4c4a4
@@ -75,6 +76,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 v2.18.12+incompatible
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.6.1
github.com/tencentcloud/tencentcloud-sdk-go v3.0.222+incompatible
github.com/ucloud/ucloud-sdk-go v0.16.3
@@ -88,6 +90,7 @@ require (
github.com/zclconf/go-cty-yaml v1.0.1
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9
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/oauth2 v0.0.0-20200902213428-5d25da1a8d43
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
+87 -12
View File
@@ -38,6 +38,8 @@ github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1 h1:RMTyvS5bjvSWiUcfqfr/E2px
github.com/1and1/oneandone-cloudserver-sdk-go v1.0.1/go.mod h1:61apmbkVJH4kg+38ftT+/l0XxdUCVnHggqcOTqZRSEE=
github.com/Azure/azure-sdk-for-go v40.5.0+incompatible h1:CVQNKuUepSFBo6BW6gM1J9slPHLRcjn6vaw+j+causw=
github.com/Azure/azure-sdk-for-go v40.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
github.com/Azure/go-autorest/autorest v0.10.0 h1:mvdtztBqcL8se7MdrUweNieTNi4kfNG6GOJuurQJpuY=
@@ -62,10 +64,13 @@ github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yK
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=
github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY=
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=
@@ -90,8 +95,12 @@ github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f h1:jI4DIE
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=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U=
@@ -105,9 +114,11 @@ 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=
@@ -150,8 +161,16 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
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=
github.com/digitalocean/godo v1.11.1 h1:OsTh37YFKk+g6DnAOrkXJ9oDArTkRx5UTkBJ2EWAO38=
github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
@@ -170,6 +189,7 @@ github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9E
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
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=
@@ -186,6 +206,8 @@ github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3I
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-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=
@@ -243,8 +265,12 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v33 v33.0.0/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-github/v33 v33.0.1-0.20210113204525-9318e629ec69 h1:zL0/Ug5CMhV0XRb3A6vnK1SQ9kJM3VIyRxPQ5t9w8Bg=
github.com/google/go-github/v33 v33.0.1-0.20210113204525-9318e629ec69/go.mod h1:GMdDnVZY/2TsWgp/lkYnpSAh6TrzhANBBwm6k6TTEXg=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
@@ -272,6 +298,8 @@ 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=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
@@ -306,9 +334,11 @@ 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=
@@ -321,6 +351,7 @@ 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=
@@ -340,6 +371,7 @@ 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=
@@ -351,24 +383,29 @@ 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=
github.com/hashicorp/packer v1.6.6/go.mod h1:meJucaEeJro8UP1jw+KaOCpaiC4VE+itPLXY8lbIU2A=
github.com/hashicorp/packer v1.6.7-0.20210107234516-6564ee76e807/go.mod h1:fBz288Z4of8zkpDWwL/ngG1txC36jGSXS7dnUmUaLUs=
github.com/hashicorp/packer v1.6.7-0.20210112155033-1bc89e34714f/go.mod h1:sKsuxExP+0/WCnIwwCRFFEHKy9TuQrZcwqbidE2oSq8=
github.com/hashicorp/packer v1.6.7-0.20210120105538-140685fb4f8b/go.mod h1:Yaw0q8brThFN2PKMWMRcuhQBuwPVDJRHHioNuMJTS7Q=
github.com/hashicorp/packer v1.6.7-0.20210125170305-539638b0f951/go.mod h1:Z3eunaxVQ3XgQ+rW7TEH0T/PRQzCUSyCBUTkm/VL7io=
github.com/hashicorp/packer v1.6.7-0.20210126105722-aef4ced967ec/go.mod h1:2+Vo/c/fA+TD9yFc/h9jQMFm4yG+IymQIr0OdJJOPiE=
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-sdk v0.0.7-0.20210113192617-8a28198491f7 h1:2N1NAfBCmG1vIkbdlIOb/YbaYXCW40YOllWqMZDjnHM=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210113192617-8a28198491f7/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210120130732-6167b5e5b2e8 h1:50/m5nP40RaXnXyd0GHHUd+CfkmcYeTNGAY5eXQlBeY=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210120130732-6167b5e5b2e8/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210121103409-4b079ce99178 h1:AVT2ugu3+UzTDEViAxMFbUzzxgUpSVMMpbuaOEd97HY=
github.com/hashicorp/packer-plugin-sdk v0.0.7-0.20210121103409-4b079ce99178/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
github.com/hashicorp/packer-plugin-sdk v0.0.7 h1:adELlId/KOGWXmQ79L+NwYSgKES6811RVXiRCj4FE0s=
github.com/hashicorp/packer-plugin-sdk v0.0.7/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
github.com/hashicorp/packer-plugin-sdk v0.0.8 h1:/qyCO9YqALnaHSE++y+//tNy68++4SThZctqTwqikrU=
github.com/hashicorp/packer-plugin-sdk v0.0.8/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
github.com/hashicorp/packer-plugin-sdk v0.0.9 h1:PWX6g0TeAbev5zhiRR91k3Z0wVCqsivs6xyBTRmPMkQ=
github.com/hashicorp/packer-plugin-sdk v0.0.9/go.mod h1:YdWTt5w6cYfaQG7IOi5iorL+3SXnz8hI0gJCi8Db/LI=
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/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=
@@ -399,25 +436,38 @@ github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62 h1:JHCT6xuyPUrbbg
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=
github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec h1:PYqF3Tiz2W2Ag0ezyDhAobWDWlrFv7U+qct4spLeDBM=
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=
@@ -428,16 +478,20 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
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/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/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.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-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=
@@ -447,9 +501,12 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX
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=
@@ -463,6 +520,7 @@ 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=
@@ -472,6 +530,7 @@ 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=
@@ -486,8 +545,12 @@ 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=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
@@ -525,6 +588,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
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/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=
@@ -549,9 +614,12 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
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=
@@ -728,6 +796,7 @@ 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-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=
@@ -777,6 +846,7 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@@ -812,6 +882,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200711155855-7342f9734a7d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@@ -885,6 +956,7 @@ 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=
@@ -926,6 +998,8 @@ gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS
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=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516 h1:H6trpavCIuipdInWrab8l34Mf+GGVfphniHostMdMaQ=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw=
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
@@ -934,6 +1008,7 @@ 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=
+238
View File
@@ -0,0 +1,238 @@
package addrs
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"golang.org/x/net/idna"
)
// Plugin encapsulates a single plugin type.
type Plugin struct {
Type string
Namespace string
Hostname string
}
func (p Plugin) RealRelativePath() string {
ns := DefaultPluginNamespace
if p.Namespace != "" {
ns = p.Namespace
}
return ns + "/packer-plugin-" + p.Type
}
func (p Plugin) Parts() []string {
return []string{p.Hostname, p.Namespace, p.Type}
}
func (p Plugin) String() string {
return strings.Join(p.Parts(), "/")
}
// ForDisplay returns a user-friendly FQN string, simplified for readability. If
// the plugin is using the default hostname, the hostname is omitted.
func (p *Plugin) ForDisplay() string {
parts := []string{}
if p.Hostname != DefaultPluginHost {
parts = append(parts, p.Hostname)
}
if p.Namespace != DefaultPluginNamespace {
parts = append(parts, p.Namespace)
}
parts = append(parts, p.Type)
return strings.Join(parts, "/")
}
const DefaultPluginHost = "github.com"
const DefaultPluginNamespace = "hashicorp"
// ParsePluginPart processes an addrs.Plugin namespace or type string
// provided by an end-user, producing a normalized version if possible or
// an error if the string contains invalid characters.
//
// A plugin part is processed in the same way as an individual label in a DNS
// domain name: it is transformed to lowercase per the usual DNS case mapping
// and normalization rules and may contain only letters, digits, and dashes.
// Additionally, dashes may not appear at the start or end of the string.
//
// These restrictions are intended to allow these names to appear in fussy
// contexts such as directory/file names on case-insensitive filesystems,
// repository names on GitHub, etc. We're using the DNS rules in particular,
// rather than some similar rules defined locally, because the hostname part
// of an addrs.Plugin is already a hostname and it's ideal to use exactly
// the same case folding and normalization rules for all of the parts.
//
// It's valid to pass the result of this function as the argument to a
// subsequent call, in which case the result will be identical.
func ParsePluginPart(given string) (string, error) {
if len(given) == 0 {
return "", fmt.Errorf("must have at least one character")
}
// We're going to process the given name using the same "IDNA" library we
// use for the hostname portion, since it already implements the case
// folding rules we want.
//
// The idna library doesn't expose individual label parsing directly, but
// once we've verified it doesn't contain any dots we can just treat it
// like a top-level domain for this library's purposes.
if strings.ContainsRune(given, '.') {
return "", fmt.Errorf("dots are not allowed")
}
// We don't allow names containing multiple consecutive dashes, just as
// a matter of preference: they look confusing, or incorrect.
// This also, as a side-effect, prevents the use of the "punycode"
// indicator prefix "xn--" that would cause the IDNA library to interpret
// the given name as punycode, because that would be weird and unexpected.
if strings.Contains(given, "--") {
return "", fmt.Errorf("cannot use multiple consecutive dashes")
}
result, err := idna.Lookup.ToUnicode(given)
if err != nil {
return "", fmt.Errorf("must contain only letters, digits, and dashes, and may not use leading or trailing dashes: %w", err)
}
return result, nil
}
// IsPluginPartNormalized compares a given string to the result of ParsePluginPart(string)
func IsPluginPartNormalized(str string) (bool, error) {
normalized, err := ParsePluginPart(str)
if err != nil {
return false, err
}
if str == normalized {
return true, nil
}
return false, nil
}
// ParsePluginSourceString parses the source attribute and returns a plugin.
// This is intended primarily to parse the FQN-like strings
//
// The following are valid source string formats:
// name
// namespace/name
// hostname/namespace/name
func ParsePluginSourceString(str string) (*Plugin, hcl.Diagnostics) {
ret := &Plugin{
Hostname: DefaultPluginHost,
Namespace: DefaultPluginNamespace,
}
var diags hcl.Diagnostics
// split the source string into individual components
parts := strings.Split(str, "/")
if len(parts) == 0 || len(parts) > 3 {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin source string",
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`,
})
return nil, diags
}
// check for an invalid empty string in any part
for i := range parts {
if parts[i] == "" {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin source string",
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`,
})
return nil, diags
}
}
// check the 'name' portion, which is always the last part
givenName := parts[len(parts)-1]
name, err := ParsePluginPart(givenName)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin type",
Detail: fmt.Sprintf(`Invalid plugin type %q in source %q: %s"`, givenName, str, err),
})
return nil, diags
}
ret.Type = name
if len(parts) == 1 {
return ret, diags
}
if len(parts) >= 2 {
// the namespace is always the second-to-last part
givenNamespace := parts[len(parts)-2]
namespace, err := ParsePluginPart(givenNamespace)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin namespace",
Detail: fmt.Sprintf(`Invalid plugin namespace %q in source %q: %s"`, namespace, str, err),
})
return nil, diags
}
ret.Namespace = namespace
}
// Final Case: 3 parts
if len(parts) == 3 {
// the hostname is always the first part in a three-part source string
hostname := parts[0]
// TODO(azr): validate host ? Can this be something else than a
// github.com host for now?
ret.Hostname = hostname
}
// Due to how plugin executables are named and plugin git repositories
// are conventionally named, it's a reasonable and
// apparently-somewhat-common user error to incorrectly use the
// "packer-plugin-" prefix in a plugin source address. There is
// no good reason for a plugin to have the prefix "packer-" anyway,
// so we've made that invalid from the start both so we can give feedback
// to plugin developers about the packer- prefix being redundant
// and give specialized feedback to folks who incorrectly use the full
// packer-plugin- prefix to help them self-correct.
const redundantPrefix = "packer-"
const userErrorPrefix = "packer-plugin-"
if strings.HasPrefix(ret.Type, redundantPrefix) {
if strings.HasPrefix(ret.Type, userErrorPrefix) {
// Likely user error. We only return this specialized error if
// whatever is after the prefix would otherwise be a
// syntactically-valid plugin type, so we don't end up advising
// the user to try something that would be invalid for another
// reason anyway.
// (This is mainly just for robustness, because the validation
// we already did above should've rejected most/all ways for
// the suggestedType to end up invalid here.)
suggestedType := ret.Type[len(userErrorPrefix):]
if _, err := ParsePluginPart(suggestedType); err == nil {
suggestedAddr := ret
suggestedAddr.Type = suggestedType
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin type",
Detail: fmt.Sprintf("Plugin source %q has a type with the prefix %q, which isn't valid. Although that prefix is often used in the names of version control repositories for Packer plugins, plugin source strings should not include it.\n\nDid you mean %q?", ret.ForDisplay(), userErrorPrefix, suggestedAddr.ForDisplay()),
})
return nil, diags
}
}
// Otherwise, probably instead an incorrectly-named plugin, perhaps
// arising from a similar instinct to what causes there to be
// thousands of Python packages on PyPI with "python-"-prefixed
// names.
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin type",
Detail: fmt.Sprintf("Plugin source %q has a type with the prefix %q, which isn't allowed because it would be redundant to name a Packer plugin with that prefix. If you are the author of this plugin, rename it to not include the prefix.", ret, redundantPrefix),
})
return nil, diags
}
return ret, diags
}
+99 -16
View File
@@ -7,6 +7,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
@@ -17,24 +18,30 @@ import (
"github.com/zclconf/go-cty/cty"
)
const lockedVersion = "v1.5.0"
func getBasicParser() *Parser {
return &Parser{
Parser: hclparse.NewParser(),
BuilderSchemas: packer.MapOfBuilder{
"amazon-ebs": func() (packersdk.Builder, error) { return &MockBuilder{}, nil },
"virtualbox-iso": func() (packersdk.Builder, error) { return &MockBuilder{}, nil },
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
},
ProvisionersSchemas: packer.MapOfProvisioner{
"shell": func() (packersdk.Provisioner, error) { return &MockProvisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &MockProvisioner{}, nil },
},
PostProcessorsSchemas: packer.MapOfPostProcessor{
"amazon-import": func() (packersdk.PostProcessor, error) { return &MockPostProcessor{}, nil },
"manifest": func() (packersdk.PostProcessor, error) { return &MockPostProcessor{}, nil },
},
DatasourceSchemas: packersdk.MapOfDatasource{
"amazon-ami": func() (packersdk.Datasource, error) { return &MockDatasource{}, nil },
CorePackerVersion: version.Must(version.NewSemver(lockedVersion)),
CorePackerVersionString: lockedVersion,
Parser: hclparse.NewParser(),
PluginConfig: &packer.PluginConfig{
Builders: packer.MapOfBuilder{
"amazon-ebs": func() (packersdk.Builder, error) { return &MockBuilder{}, nil },
"virtualbox-iso": func() (packersdk.Builder, error) { return &MockBuilder{}, nil },
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
},
Provisioners: packer.MapOfProvisioner{
"shell": func() (packersdk.Provisioner, error) { return &MockProvisioner{}, nil },
"file": func() (packersdk.Provisioner, error) { return &MockProvisioner{}, nil },
},
PostProcessors: packer.MapOfPostProcessor{
"amazon-import": func() (packersdk.PostProcessor, error) { return &MockPostProcessor{}, nil },
"manifest": func() (packersdk.PostProcessor, error) { return &MockPostProcessor{}, nil },
},
DataSources: packer.MapOfDatasource{
"amazon-ami": func() (packersdk.Datasource, error) { return &MockDatasource{}, nil },
},
},
}
}
@@ -102,6 +109,39 @@ func testParse(t *testing.T, tests []parseTest) {
}
}
func testParse_only_Parse(t *testing.T, tests []parseTest) {
t.Helper()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCfg, gotDiags := tt.parser.Parse(tt.args.filename, tt.args.varFiles, tt.args.vars)
if tt.parseWantDiags == (gotDiags == nil) {
t.Fatalf("Parser.parse() unexpected %q diagnostics.", gotDiags)
}
if tt.parseWantDiagHasErrors != gotDiags.HasErrors() {
t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags)
}
if diff := cmp.Diff(tt.parseWantCfg, gotCfg, cmpOpts...); diff != "" {
t.Fatalf("Parser.parse() wrong packer config. %s", diff)
}
if gotCfg != nil && !tt.parseWantDiagHasErrors {
if diff := cmp.Diff(tt.parseWantCfg.InputVariables, gotCfg.InputVariables, cmpOpts...); diff != "" {
t.Fatalf("Parser.parse() unexpected input vars. %s", diff)
}
if diff := cmp.Diff(tt.parseWantCfg.LocalVariables, gotCfg.LocalVariables, cmpOpts...); diff != "" {
t.Fatalf("Parser.parse() unexpected local vars. %s", diff)
}
}
if gotDiags.HasErrors() {
return
}
})
}
}
var (
// everything in the tests is a basicNestedMockConfig this allow to test
// each known type to packer ( and embedding ) in one go.
@@ -189,6 +229,39 @@ var (
},
},
}
basicMockPostProcessorDynamicTags = &MockPostProcessor{
Config: MockConfig{
NotSquashed: "value <UNKNOWN>",
NestedMockConfig: NestedMockConfig{
String: "string",
Int: 42,
Int64: 43,
Bool: true,
Trilean: config.TriTrue,
Duration: 10 * time.Second,
MapStringString: map[string]string{
"a": "b",
"c": "d",
},
SliceString: []string{
"a",
"b",
"c",
},
SliceSliceString: [][]string{
{"a", "b"},
{"c", "d"},
},
Tags: []MockTag{
{Key: "first_tag_key", Value: "first_tag_value"},
{Key: "Component", Value: "user-service"},
{Key: "Environment", Value: "production"},
},
},
Nested: basicNestedMockConfig,
NestedSlice: []NestedMockConfig{},
},
}
basicMockCommunicator = &MockCommunicator{
Config: MockConfig{
NestedMockConfig: basicNestedMockConfig,
@@ -248,9 +321,19 @@ var ctyTypeComparer = cmp.Comparer(func(x, y cty.Type) bool {
return x.Equals(y)
})
var versionComparer = cmp.Comparer(func(x, y *version.Version) bool {
return x.Equal(y)
})
var versionConstraintComparer = cmp.Comparer(func(x, y *version.Constraint) bool {
return x.String() == y.String()
})
var cmpOpts = []cmp.Option{
ctyValueComparer,
ctyTypeComparer,
versionComparer,
versionConstraintComparer,
cmpopts.IgnoreUnexported(
PackerConfig{},
Variable{},
+63 -33
View File
@@ -20,6 +20,7 @@ const (
variablesLabel = "variables"
variableLabel = "variable"
localsLabel = "locals"
localLabel = "local"
dataSourceLabel = "data"
buildLabel = "build"
communicatorLabel = "communicator"
@@ -32,6 +33,7 @@ var configSchema = &hcl.BodySchema{
{Type: variablesLabel},
{Type: variableLabel, LabelNames: []string{"name"}},
{Type: localsLabel},
{Type: localLabel, LabelNames: []string{"name"}},
{Type: dataSourceLabel, LabelNames: []string{"type", "name"}},
{Type: buildLabel},
{Type: communicatorLabel, LabelNames: []string{"type", "name"}},
@@ -44,6 +46,9 @@ var packerBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "required_version"},
},
Blocks: []hcl.BlockHeaderSchema{
{Type: "required_plugins"},
},
}
// Parser helps you parse HCL folders. It will parse an hcl file or directory
@@ -57,13 +62,7 @@ type Parser struct {
*hclparse.Parser
BuilderSchemas packer.BuilderStore
ProvisionersSchemas packer.ProvisionerStore
PostProcessorsSchemas packer.PostProcessorStore
DatasourceSchemas packer.DatasourceStore
PluginConfig *packer.PluginConfig
}
const (
@@ -130,10 +129,6 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
Basedir: basedir,
Cwd: wd,
CorePackerVersionString: p.CorePackerVersionString,
builderSchemas: p.BuilderSchemas,
provisionersSchemas: p.ProvisionersSchemas,
postProcessorsSchemas: p.PostProcessorsSchemas,
datasourceSchemas: p.DatasourceSchemas,
parser: p,
files: files,
}
@@ -145,7 +140,7 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
}
// Before we go further, we'll check to make sure this version can read
// that file, so we can produce a version-related error message rather than
// all files, so we can produce a version-related error message rather than
// potentially-confusing downstream errors.
versionDiags := cfg.CheckCoreVersionRequirements(p.CorePackerVersion)
diags = append(diags, versionDiags...)
@@ -153,8 +148,30 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
return cfg, diags
}
// Decode required_plugins blocks and create implicit required_plugins
// blocks. Implicit required_plugins blocks happen when a builder or another
// plugin cannot be found, for example if one uses :
// source "amazon-ebs" "example" { ... }
// And no `amazon-ebs` builder can be found. This will then be the
// equivalent of having :
// packer {
// required_plugins {
// amazon = "latest"
// }
// }
// Note: using `latest` ( or actually an empty string ) in a config file
// does not work and packer will ask you to pick a version
{
for _, file := range files {
diags = append(diags, cfg.decodeRequiredPluginsBlock(file)...)
}
for _, file := range files {
diags = append(diags, cfg.decodeImplicitRequiredPluginsBlocks(file)...)
}
}
// Decode variable blocks so that they are available later on. Here locals
// can use input variables so we decode them firsthand.
// can use input variables so we decode input variables first.
{
for _, file := range files {
diags = append(diags, cfg.decodeInputVariables(file)...)
@@ -210,6 +227,11 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
diags = append(diags, cfg.collectInputVariableValues(os.Environ(), varFiles, argVars)...)
}
// parse the actual content // rest
for _, file := range cfg.files {
diags = append(diags, cfg.parser.parseConfig(file, cfg)...)
}
return cfg, diags
}
@@ -257,17 +279,8 @@ func sniffCoreVersionRequirements(body hcl.Body) ([]VersionConstraint, hcl.Diagn
return constraints, diags
}
func (cfg *PackerConfig) Initialize(opts packer.InitializeOptions) hcl.Diagnostics {
var diags hcl.Diagnostics
_, moreDiags := cfg.InputVariables.Values()
diags = append(diags, moreDiags...)
_, moreDiags = cfg.LocalVariables.Values()
diags = append(diags, moreDiags...)
diags = append(diags, cfg.evaluateDatasources(opts.SkipDatasourcesExecution)...)
diags = append(diags, cfg.evaluateLocalVariables(cfg.LocalBlocks)...)
for _, variable := range cfg.InputVariables {
func filterVarsFromLogs(inputOrLocal Variables) {
for _, variable := range inputOrLocal {
if !variable.Sensitive {
continue
}
@@ -279,22 +292,39 @@ func (cfg *PackerConfig) Initialize(opts packer.InitializeOptions) hcl.Diagnosti
return true, nil
})
}
}
// decode the actual content
for _, file := range cfg.files {
diags = append(diags, cfg.parser.decodeConfig(file, cfg)...)
func (cfg *PackerConfig) Initialize(opts packer.InitializeOptions) hcl.Diagnostics {
var diags hcl.Diagnostics
// enable packer to start plugins requested in required_plugins.
moreDiags := cfg.detectPluginBinaries()
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
return diags
}
_, moreDiags = cfg.InputVariables.Values()
diags = append(diags, moreDiags...)
_, moreDiags = cfg.LocalVariables.Values()
diags = append(diags, moreDiags...)
diags = append(diags, cfg.evaluateDatasources(opts.SkipDatasourcesExecution)...)
diags = append(diags, cfg.evaluateLocalVariables(cfg.LocalBlocks)...)
filterVarsFromLogs(cfg.InputVariables)
filterVarsFromLogs(cfg.LocalVariables)
diags = append(diags, cfg.initializeBlocks()...)
return diags
}
// decodeConfig looks in the found blocks for everything that is not a variable
// block. It should be called after parsing input variables and locals so that
// they can be referenced.
func (p *Parser) decodeConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
// parseConfig looks in the found blocks for everything that is not a variable
// block.
func (p *Parser) parseConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
var diags hcl.Diagnostics
body := dynblock.Expand(f.Body, cfg.EvalContext(nil))
body := f.Body
content, moreDiags := body.Content(configSchema)
diags = append(diags, moreDiags...)
@@ -343,7 +373,7 @@ func (p *Parser) decodeConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
func (p *Parser) decodeDatasources(file *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
var diags hcl.Diagnostics
body := dynblock.Expand(file.Body, cfg.EvalContext(nil))
body := dynblock.Expand(file.Body, cfg.EvalContext(DatasourceContext, nil))
content, moreDiags := body.Content(configSchema)
diags = append(diags, moreDiags...)
+183
View File
@@ -0,0 +1,183 @@
package hcl2template
import (
"crypto/sha256"
"fmt"
"log"
"runtime"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/dynblock"
pluginsdk "github.com/hashicorp/packer-plugin-sdk/plugin"
plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
)
// PluginRequirements returns a sorted list of plugin requirements.
func (cfg *PackerConfig) PluginRequirements() (plugingetter.Requirements, hcl.Diagnostics) {
var diags hcl.Diagnostics
var reqs plugingetter.Requirements
reqPluginsBlocks := cfg.Packer.RequiredPlugins
// Take all required plugins, make sure there are no conflicting blocks
// and append them to the list.
uniq := map[string]*RequiredPlugin{}
for _, requiredPluginsBlock := range reqPluginsBlocks {
for name, block := range requiredPluginsBlock.RequiredPlugins {
if previouslySeenBlock, found := uniq[name]; found {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicate required_plugin.%q block", name),
Detail: fmt.Sprintf("Block previously seen at %s is already named %q.\n", previouslySeenBlock.DeclRange, name) +
"Names at the left hand side of required_plugins are made available to use in your HCL2 configurations.\n" +
"To allow to calling to their features correctly two plugins have to have different accessors.",
Context: &block.DeclRange,
})
continue
}
reqs = append(reqs, &plugingetter.Requirement{
Accessor: name,
Identifier: block.Type,
VersionConstraints: block.Requirement.Required,
})
uniq[name] = block
}
}
return reqs, diags
}
func (cfg *PackerConfig) detectPluginBinaries() hcl.Diagnostics {
opts := plugingetter.ListInstallationsOptions{
FromFolders: cfg.parser.PluginConfig.KnownPluginFolders,
BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
OS: runtime.GOOS,
ARCH: runtime.GOARCH,
APIVersionMajor: pluginsdk.APIVersionMajor,
APIVersionMinor: pluginsdk.APIVersionMinor,
Checksummers: []plugingetter.Checksummer{
{Type: "sha256", Hash: sha256.New()},
},
},
}
if runtime.GOOS == "windows" && opts.Ext == "" {
opts.BinaryInstallationOptions.Ext = ".exe"
}
pluginReqs, diags := cfg.PluginRequirements()
if diags.HasErrors() {
return diags
}
for _, pluginRequirement := range pluginReqs {
sortedInstalls, err := pluginRequirement.ListInstallations(opts)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed to list installation for %s", pluginRequirement.Identifier.ForDisplay()),
Detail: err.Error(),
})
continue
}
if len(sortedInstalls) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("no plugin installed for %s %v", pluginRequirement.Identifier.ForDisplay(), pluginRequirement.VersionConstraints.String()),
Detail: "Did you run packer init for this project ?",
})
continue
}
log.Printf("[TRACE] Found the following %q installations: %v", pluginRequirement.Identifier.ForDisplay(), sortedInstalls)
install := sortedInstalls[len(sortedInstalls)-1]
err = cfg.parser.PluginConfig.DiscoverMultiPlugin(pluginRequirement.Accessor, install.BinaryPath)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Error discovering plugin %s", pluginRequirement.Identifier.ForDisplay()),
Detail: err.Error(),
})
continue
}
}
return diags
}
func (cfg *PackerConfig) initializeBlocks() hcl.Diagnostics {
// verify that all used plugins do exist and expand dynamic bodies
var diags hcl.Diagnostics
for _, build := range cfg.Builds {
for i := range build.Sources {
// here we grab a pointer to the source usage because we will set
// its body.
srcUsage := &(build.Sources[i])
if !cfg.parser.PluginConfig.Builders.Has(srcUsage.Type) {
diags = append(diags, &hcl.Diagnostic{
Summary: "Unknown " + buildSourceLabel + " type " + srcUsage.Type,
Subject: &build.HCL2Ref.DefRange,
Detail: fmt.Sprintf("known builders: %v", cfg.parser.PluginConfig.Builders.List()),
Severity: hcl.DiagError,
})
continue
}
sourceDefinition, found := cfg.Sources[srcUsage.SourceRef]
if !found {
diags = append(diags, &hcl.Diagnostic{
Summary: "Unknown " + sourceLabel + " " + srcUsage.String(),
Subject: build.HCL2Ref.DefRange.Ptr(),
Severity: hcl.DiagError,
Detail: fmt.Sprintf("Known: %v", cfg.Sources),
// TODO: show known sources as a string slice here ^.
})
continue
}
body := sourceDefinition.block.Body
if srcUsage.Body != nil {
// merge additions into source definition to get a new body.
body = hcl.MergeBodies([]hcl.Body{body, srcUsage.Body})
}
// expand any dynamic block.
body = dynblock.Expand(body, cfg.EvalContext(BuildContext, nil))
srcUsage.Body = body
}
for _, provBlock := range build.ProvisionerBlocks {
if !cfg.parser.PluginConfig.Provisioners.Has(provBlock.PType) {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Unknown "+buildProvisionerLabel+" type %q", provBlock.PType),
Subject: provBlock.HCL2Ref.TypeRange.Ptr(),
Detail: fmt.Sprintf("known "+buildProvisionerLabel+"s: %v", cfg.parser.PluginConfig.Provisioners.List()),
Severity: hcl.DiagError,
})
}
// Allow rest of the body to have dynamic blocks
provBlock.HCL2Ref.Rest = dynblock.Expand(provBlock.HCL2Ref.Rest, cfg.EvalContext(BuildContext, nil))
}
for _, ppList := range build.PostProcessorsLists {
for _, ppBlock := range ppList {
if !cfg.parser.PluginConfig.PostProcessors.Has(ppBlock.PType) {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Unknown "+buildPostProcessorLabel+" type %q", ppBlock.PType),
Subject: ppBlock.HCL2Ref.TypeRange.Ptr(),
Detail: fmt.Sprintf("known "+buildPostProcessorLabel+"s: %v", cfg.parser.PluginConfig.PostProcessors.List()),
Severity: hcl.DiagError,
})
}
// Allow the rest of the body to have dynamic blocks
ppBlock.HCL2Ref.Rest = dynblock.Expand(ppBlock.HCL2Ref.Rest, cfg.EvalContext(BuildContext, nil))
}
}
}
return diags
}
+23 -1
View File
@@ -1,4 +1,8 @@
packer {
required_version = ">= v1"
}
// starts resources to provision them.
build {
sources = [
@@ -7,6 +11,15 @@ build {
source "source.amazon-ebs.ubuntu-1604" {
string = "setting from build section"
nested {
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
}
}
}
}
provisioner "shell" {
@@ -162,7 +175,16 @@ build {
]
}
nested_slice {
tag {
key = "first_tag_key"
value = "first_tag_value"
}
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
}
}
}
+9 -1
View File
@@ -1,9 +1,17 @@
source "amazon-ebs" "ubuntu-1604" {
int = 42
nested_slice {
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
}
}
}
}
source "virtualbox-iso" "ubuntu-1204" {
string = "string"
int = 42
+5
View File
@@ -38,3 +38,8 @@ locals {
{id = "c"},
]
}
local "supersecret" {
expression = "${var.image_id}-password"
sensitive = true
}
+7
View File
@@ -0,0 +1,7 @@
data "amazon-ami" "test_0" {
string = "string"
}
data "amazon-ami" "test_1" {
string = data.amazon-ami.test_0.string
}
@@ -0,0 +1,7 @@
packer {
required_plugins {
amazon = ">= v0"
amazon = ">= v4"
}
}
+465
View File
@@ -0,0 +1,465 @@
packer {
required_version = ">= v1"
required_plugins {
amazon = ">= v0"
amazon-v1 = {
source = "amazon"
version = ">= v1"
}
amazon-v2 = {
source = "amazon"
version = ">= v2"
}
amazon-v3 = {
source = "hashicorp/amazon"
version = ">= v3"
}
amazon-v3-azr = {
source = "azr/amazon"
version = ">= v3"
}
amazon-v4 = {
source = "github.com/hashicorp/amazon"
version = ">= v4"
}
}
}
// starts resources to provision them.
build {
sources = [
"source.virtualbox-iso.ubuntu-1204",
]
source "source.amazon-v3-ebs.my-image" {
string = "setting from build section"
}
provisioner "shell" {
name = "provisioner that does something"
not_squashed = "${var.foo} ${upper(build.ID)}"
string = "string"
int = "${41 + 1}"
int64 = "${42 + 1}"
bool = "true"
trilean = true
duration = "${9 + 1}s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [for s in var.availability_zone_names : lower(s)]
slice_slice_string = [
["a","b"],
["c","d"]
]
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [for s in var.availability_zone_names : lower(s)]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
tag {
key = "first_tag_key"
value = "first_tag_value"
}
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
}
}
}
}
provisioner "file" {
not_squashed = "${var.foo} ${upper(build.ID)}"
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = local.abc_map[*].id
slice_slice_string = [
["a","b"],
["c","d"]
]
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
tag {
key = "first_tag_key"
value = "first_tag_value"
}
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
}
}
}
}
post-processor "amazon-import" {
name = "something"
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
keep_input_artifact = true
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
not_squashed = "${var.foo} ${upper(build.ID)}"
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
tag {
key = "first_tag_key"
value = "first_tag_value"
}
dynamic "tag" {
for_each = local.standard_tags
content {
key = tag.key
value = tag.value
}
}
}
}
post-processor "amazon-import" {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
not_squashed = "${var.foo} ${upper(build.ID)}"
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
}
}
post-processors {
post-processor "amazon-import" {
name = "first-nested-post-processor"
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
not_squashed = "${var.foo} ${upper(build.ID)}"
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
}
}
post-processor "amazon-import" {
name = "second-nested-post-processor"
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
not_squashed = "${var.foo} ${upper(build.ID)}"
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
}
}
}
post-processors {
post-processor "amazon-import" {
name = "third-nested-post-processor"
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
not_squashed = "${var.foo} ${upper(build.ID)}"
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
}
}
post-processor "amazon-import" {
name = "fourth-nested-post-processor"
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
not_squashed = "${var.foo} ${upper(build.ID)}"
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
}
}
}
}
+94
View File
@@ -0,0 +1,94 @@
source "amazon-v3-ebs" "my-image" {
int = 42
}
source "virtualbox-iso" "ubuntu-1204" {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
nested {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
nested_slice {
string = "string"
int = 42
int64 = 43
bool = true
trilean = true
duration = "10s"
map_string_string = {
a = "b"
c = "d"
}
slice_string = [
"a",
"b",
"c",
]
slice_slice_string = [
["a","b"],
["c","d"]
]
}
}
+39
View File
@@ -0,0 +1,39 @@
variables {
foo = "value"
// my_secret = "foo"
// image_name = "foo-image-{{user `my_secret`}}"
}
variable "image_id" {
type = string
default = "image-id-default"
}
variable "port" {
type = number
default = 42
}
variable "availability_zone_names" {
type = list(string)
default = ["A", "B", "C"]
}
locals {
feefoo = "${var.foo}_${var.image_id}"
}
locals {
standard_tags = {
Component = "user-service"
Environment = "production"
}
abc_map = [
{id = "a"},
{id = "b"},
{id = "c"},
]
}
+8
View File
@@ -0,0 +1,8 @@
// a source represents a reusable setting for a system boot/start.
source "inexistant" "ubuntu-1204" {
foo = "bar"
}
build {
sources = ["inexistant.ubuntu-1204"]
}
+5
View File
@@ -36,3 +36,8 @@ locals {
service_name = "forum"
owner = "Community Team"
}
local "supersecret" {
sensitive = true
expression = "secretvar"
}
@@ -0,0 +1,4 @@
local "sensible" {
expression = "something"
}
@@ -0,0 +1,4 @@
local "sensible" {
expression = "something"
}
+3 -2
View File
@@ -52,7 +52,7 @@ type BuildBlock struct {
Description string
// Sources is the list of sources that we want to start in this build block.
Sources []SourceRef
Sources []SourceUseBlock
// ProvisionerBlocks references a list of HCL provisioner block that will
// will be ran against the sources.
@@ -105,7 +105,8 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block, cfg *PackerConfig) (*BuildB
continue
}
build.Sources = append(build.Sources, ref)
// source with no body
build.Sources = append(build.Sources, SourceUseBlock{SourceRef: ref})
}
content, moreDiags := b.Config.Content(buildSchema)
+14 -16
View File
@@ -2,6 +2,7 @@ package hcl2template
import (
"fmt"
"strconv"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
@@ -48,37 +49,34 @@ func (p *Parser) decodePostProcessor(block *hcl.Block) (*PostProcessorBlock, hcl
return nil, diags
}
if !p.PostProcessorsSchemas.Has(postProcessor.PType) {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Unknown "+buildPostProcessorLabel+" type %q", postProcessor.PType),
Subject: block.LabelRanges[0].Ptr(),
Detail: fmt.Sprintf("known "+buildPostProcessorLabel+"s: %v", p.PostProcessorsSchemas.List()),
Severity: hcl.DiagError,
})
return nil, diags
}
return postProcessor, diags
}
func (cfg *PackerConfig) startPostProcessor(source SourceBlock, pp *PostProcessorBlock, ectx *hcl.EvalContext) (packersdk.PostProcessor, hcl.Diagnostics) {
func (cfg *PackerConfig) startPostProcessor(source SourceUseBlock, pp *PostProcessorBlock, ectx *hcl.EvalContext) (packersdk.PostProcessor, hcl.Diagnostics) {
// ProvisionerBlock represents a detected but unparsed provisioner
var diags hcl.Diagnostics
postProcessor, err := cfg.postProcessorsSchemas.Start(pp.PType)
postProcessor, err := cfg.parser.PluginConfig.PostProcessors.Start(pp.PType)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Failed loading %s", pp.PType),
Subject: pp.DefRange.Ptr(),
Detail: err.Error(),
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Failed loading %s", pp.PType),
Subject: pp.DefRange.Ptr(),
Detail: err.Error(),
})
return nil, diags
}
builderVars := source.builderVariables()
builderVars["packer_debug"] = strconv.FormatBool(cfg.debug)
builderVars["packer_force"] = strconv.FormatBool(cfg.force)
builderVars["packer_on_error"] = cfg.onError
hclPostProcessor := &HCL2PostProcessor{
PostProcessor: postProcessor,
postProcessorBlock: pp,
evalContext: ectx,
builderVariables: source.builderVariables(),
builderVariables: builderVars,
}
err = hclPostProcessor.HCL2Prepare(nil)
if err != nil {
+14 -16
View File
@@ -2,6 +2,7 @@ package hcl2template
import (
"fmt"
"strconv"
"time"
"github.com/hashicorp/hcl/v2"
@@ -84,7 +85,7 @@ func (p *Parser) decodeProvisioner(block *hcl.Block, cfg *PackerConfig) (*Provis
Override cty.Value `hcl:"override,optional"`
Rest hcl.Body `hcl:",remain"`
}
diags := gohcl.DecodeBody(block.Body, cfg.EvalContext(nil), &b)
diags := gohcl.DecodeBody(block.Body, cfg.EvalContext(BuildContext, nil), &b)
if diags.HasErrors() {
return nil, diags
}
@@ -136,36 +137,33 @@ func (p *Parser) decodeProvisioner(block *hcl.Block, cfg *PackerConfig) (*Provis
provisioner.Timeout = timeout
}
if !p.ProvisionersSchemas.Has(provisioner.PType) {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("Unknown "+buildProvisionerLabel+" type %q", provisioner.PType),
Subject: block.LabelRanges[0].Ptr(),
Detail: fmt.Sprintf("known "+buildProvisionerLabel+"s: %v", p.ProvisionersSchemas.List()),
Severity: hcl.DiagError,
})
return nil, diags
}
return provisioner, diags
}
func (cfg *PackerConfig) startProvisioner(source SourceBlock, pb *ProvisionerBlock, ectx *hcl.EvalContext) (packersdk.Provisioner, hcl.Diagnostics) {
func (cfg *PackerConfig) startProvisioner(source SourceUseBlock, pb *ProvisionerBlock, ectx *hcl.EvalContext) (packersdk.Provisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics
provisioner, err := cfg.provisionersSchemas.Start(pb.PType)
provisioner, err := cfg.parser.PluginConfig.Provisioners.Start(pb.PType)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Summary: fmt.Sprintf("failed loading %s", pb.PType),
Subject: pb.HCL2Ref.LabelsRanges[0].Ptr(),
Detail: err.Error(),
Severity: hcl.DiagError,
Summary: fmt.Sprintf("failed loading %s", pb.PType),
Subject: pb.HCL2Ref.LabelsRanges[0].Ptr(),
Detail: err.Error(),
})
return nil, diags
}
builderVars := source.builderVariables()
builderVars["packer_debug"] = strconv.FormatBool(cfg.debug)
builderVars["packer_force"] = strconv.FormatBool(cfg.force)
builderVars["packer_on_error"] = cfg.onError
hclProvisioner := &HCL2Provisioner{
Provisioner: provisioner,
provisionerBlock: pb,
evalContext: ectx,
builderVariables: source.builderVariables(),
builderVariables: builderVars,
}
if pb.Override != nil {
+82 -33
View File
@@ -17,15 +17,20 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/basic.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: Builds{
&BuildBlock{
Sources: []SourceRef{
Sources: []SourceUseBlock{
{
Type: "amazon-ebs",
Name: "ubuntu-1604",
SourceRef: SourceRef{
Type: "amazon-ebs",
Name: "ubuntu-1604",
},
},
{
SourceRef: refVBIsoUbuntu1204,
},
refVBIsoUbuntu1204,
},
ProvisionerBlocks: []*ProvisionerBlock{
{
@@ -45,7 +50,7 @@ func TestParse_build(t *testing.T) {
},
},
},
false, false,
true, true,
[]packersdk.Build{},
true,
},
@@ -53,8 +58,9 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/provisioner_untyped.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
},
true, true,
nil,
@@ -64,19 +70,31 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/provisioner_inexistent.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: Builds{
&BuildBlock{
ProvisionerBlocks: []*ProvisionerBlock{
{
PType: "inexistant",
},
},
},
},
},
true, true,
[]packersdk.Build{&packer.CoreBuild{}},
[]packersdk.Build{&packer.CoreBuild{
Provisioners: []packer.CoreBuildProvisioner{},
}},
false,
},
{"untyped post-processor",
defaultParser,
parseTestArgs{"testdata/build/post-processor_untyped.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
},
true, true,
[]packersdk.Build{&packer.CoreBuild{}},
@@ -86,19 +104,33 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/post-processor_inexistent.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: Builds{
&BuildBlock{
PostProcessorsLists: [][]*PostProcessorBlock{
{
{
PType: "inexistant",
},
},
},
},
},
},
true, true,
[]packersdk.Build{},
false,
[]packersdk.Build{&packer.CoreBuild{
PostProcessors: [][]packer.CoreBuildPostProcessor{},
}},
true,
},
{"invalid source",
defaultParser,
parseTestArgs{"testdata/build/invalid_source_reference.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: nil,
},
true, true,
[]packersdk.Build{},
@@ -108,21 +140,26 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/named.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Builds: Builds{
&BuildBlock{
Name: "somebuild",
Sources: []SourceRef{
Sources: []SourceUseBlock{
{
Type: "amazon-ebs",
Name: "ubuntu-1604",
SourceRef: SourceRef{
Type: "amazon-ebs",
Name: "ubuntu-1604",
},
},
{
SourceRef: refVBIsoUbuntu1204,
},
refVBIsoUbuntu1204,
},
},
},
},
false, false,
true, true,
[]packersdk.Build{},
true,
},
@@ -130,16 +167,22 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/post-processor_onlyexcept.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Sources: map[SourceRef]SourceBlock{
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
refAWSEBSUbuntu1604: {Type: "amazon-ebs", Name: "ubuntu-1604"},
},
Builds: Builds{
&BuildBlock{
Sources: []SourceRef{
refVBIsoUbuntu1204,
SourceRef{Type: "amazon-ebs", Name: "ubuntu-1604", LocalName: "aws-ubuntu-16.04"},
Sources: []SourceUseBlock{
{
SourceRef: refVBIsoUbuntu1204,
},
{
SourceRef: SourceRef{Type: "amazon-ebs", Name: "ubuntu-1604"},
LocalName: "aws-ubuntu-16.04",
},
},
ProvisionerBlocks: nil,
PostProcessorsLists: [][]*PostProcessorBlock{
@@ -248,16 +291,22 @@ func TestParse_build(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/build/provisioner_onlyexcept.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "build"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "build"),
Sources: map[SourceRef]SourceBlock{
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
refAWSEBSUbuntu1604: {Type: "amazon-ebs", Name: "ubuntu-1604"},
},
Builds: Builds{
&BuildBlock{
Sources: []SourceRef{
refVBIsoUbuntu1204,
SourceRef{Type: "amazon-ebs", Name: "ubuntu-1604", LocalName: "aws-ubuntu-16.04"},
Sources: []SourceUseBlock{
{
SourceRef: refVBIsoUbuntu1204,
},
{
SourceRef: SourceRef{Type: "amazon-ebs", Name: "ubuntu-1604"},
LocalName: "aws-ubuntu-16.04",
},
},
ProvisionerBlocks: []*ProvisionerBlock{
{
+1 -1
View File
@@ -102,7 +102,7 @@ func (cfg *PackerConfig) startDatasource(dataSourceStore packer.DatasourceStore,
})
}
body := block.Body
decoded, moreDiags := decodeHCL2Spec(body, cfg.EvalContext(nil), datasource)
decoded, moreDiags := decodeHCL2Spec(body, cfg.EvalContext(DatasourceContext, nil), datasource)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
return nil, diags
+37 -5
View File
@@ -15,7 +15,8 @@ func TestParse_datasource(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/datasources/basic.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "datasources"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "datasources"),
Datasources: Datasources{
{
Type: "amazon-ami",
@@ -34,7 +35,8 @@ func TestParse_datasource(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/datasources/untyped.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "datasources"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "datasources"),
},
true, true,
nil,
@@ -44,7 +46,35 @@ func TestParse_datasource(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/datasources/unnamed.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "datasources"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "datasources"),
},
true, true,
nil,
false,
},
{"not allowed usage of data source within another data source",
defaultParser,
parseTestArgs{"testdata/datasources/not-allowed.pkr.hcl", nil, nil},
&PackerConfig{
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "datasources"),
Datasources: Datasources{
{
Type: "amazon-ami",
Name: "test_0",
}: {
Type: "amazon-ami",
Name: "test_0",
},
{
Type: "amazon-ami",
Name: "test_1",
}: {
Type: "amazon-ami",
Name: "test_1",
},
},
},
true, true,
nil,
@@ -54,7 +84,8 @@ func TestParse_datasource(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/datasources/inexistent.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "datasources"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "datasources"),
Datasources: Datasources{
{
Type: "inexistant",
@@ -73,7 +104,8 @@ func TestParse_datasource(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/datasources/duplicate.pkr.hcl", nil, nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "datasources"),
CorePackerVersionString: lockedVersion,
Basedir: filepath.Join("testdata", "datasources"),
Datasources: Datasources{
{
Type: "amazon-ami",
+5 -12
View File
@@ -2,7 +2,6 @@ package hcl2template
import (
"context"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
@@ -33,18 +32,12 @@ func (p *HCL2PostProcessor) HCL2Prepare(buildVars map[string]interface{}) error
ectx = p.evalContext.NewChild()
buildValues := map[string]cty.Value{}
for k, v := range buildVars {
switch v := v.(type) {
case string:
buildValues[k] = cty.StringVal(v)
case int64:
buildValues[k] = cty.NumberIntVal(v)
case uint64:
buildValues[k] = cty.NumberUIntVal(v)
case bool:
buildValues[k] = cty.BoolVal(v)
default:
return fmt.Errorf("unhandled buildvar type: %T", v)
val, err := ConvertPluginConfigValueToHCLValue(v)
if err != nil {
return err
}
buildValues[k] = val
}
ectx.Variables = map[string]cty.Value{
buildAccessor: cty.ObjectVal(buildValues),
+5 -12
View File
@@ -2,7 +2,6 @@ package hcl2template
import (
"context"
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
@@ -37,18 +36,12 @@ func (p *HCL2Provisioner) HCL2Prepare(buildVars map[string]interface{}) error {
buildValues = p.evalContext.Variables[buildAccessor].AsValueMap()
}
for k, v := range buildVars {
switch v := v.(type) {
case string:
buildValues[k] = cty.StringVal(v)
case int64:
buildValues[k] = cty.NumberIntVal(v)
case uint64:
buildValues[k] = cty.NumberUIntVal(v)
case bool:
buildValues[k] = cty.BoolVal(v)
default:
return fmt.Errorf("unhandled buildvar type: %T", v)
val, err := ConvertPluginConfigValueToHCLValue(v)
if err != nil {
return err
}
buildValues[k] = val
}
ectx.Variables = map[string]cty.Value{
buildAccessor: cty.ObjectVal(buildValues),
+70 -31
View File
@@ -21,7 +21,9 @@ import (
type PackerConfig struct {
Packer struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}
// Directory where the config files are defined
Basedir string
@@ -50,16 +52,15 @@ type PackerConfig struct {
// Builds is the list of Build blocks defined in the config files.
Builds Builds
builderSchemas packer.BuilderStore
provisionersSchemas packer.ProvisionerStore
postProcessorsSchemas packer.PostProcessorStore
datasourceSchemas packer.DatasourceStore
except []glob.Glob
only []glob.Glob
parser *Parser
files []*hcl.File
// Fields passed as command line flags
except []glob.Glob
only []glob.Glob
force bool
debug bool
onError string
}
type ValidationOptions struct {
@@ -76,13 +77,22 @@ const (
dataAccessor = "data"
)
type BlockContext int
const (
InputVariableContext BlockContext = iota
LocalContext
BuildContext
DatasourceContext
NilContext
)
// EvalContext returns the *hcl.EvalContext that will be passed to an hcl
// decoder in order to tell what is the actual value of a var or a local and
// the list of defined functions.
func (cfg *PackerConfig) EvalContext(variables map[string]cty.Value) *hcl.EvalContext {
func (cfg *PackerConfig) EvalContext(ctx BlockContext, variables map[string]cty.Value) *hcl.EvalContext {
inputVariables, _ := cfg.InputVariables.Values()
localVariables, _ := cfg.LocalVariables.Values()
datasourceVariables, _ := cfg.Datasources.Values()
ectx := &hcl.EvalContext{
Functions: Functions(cfg.Basedir),
Variables: map[string]cty.Value{
@@ -100,9 +110,25 @@ func (cfg *PackerConfig) EvalContext(variables map[string]cty.Value) *hcl.EvalCo
"cwd": cty.StringVal(strings.ReplaceAll(cfg.Cwd, `\`, `/`)),
"root": cty.StringVal(strings.ReplaceAll(cfg.Basedir, `\`, `/`)),
}),
dataAccessor: cty.ObjectVal(datasourceVariables),
},
}
// Currently the places where you can make references to other blocks
// from one is very 'procedural', and in this specific case, we could make
// the data sources available to other datasources, but this would be
// order dependant, meaning that if you define two datasources in two
// different blocks, the second one can use the first one, but not the
// other way around; which would be totally confusing; so - for now -
// datasources can't use other datasources.
// In the future we'd like to load and execute HCL blocks using a graph
// dependency tree, so that any block can use any block whatever the
// order.
switch ctx {
case LocalContext, BuildContext:
datasourceVariables, _ := cfg.Datasources.Values()
ectx.Variables[dataAccessor] = cty.ObjectVal(datasourceVariables)
}
for k, v := range variables {
ectx.Variables[k] = v
}
@@ -150,10 +176,20 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*LocalBlock, hcl.Diag
content, moreDiags := f.Body.Content(configSchema)
diags = append(diags, moreDiags...)
var locals []*LocalBlock
locals := c.LocalBlocks
for _, block := range content.Blocks {
switch block.Type {
case localLabel:
l, moreDiags := decodeLocalBlock(block, locals)
diags = append(diags, moreDiags...)
if l != nil {
locals = append(locals, l)
}
if moreDiags.HasErrors() {
return locals, diags
}
case localsLabel:
attrs, moreDiags := block.Body.JustAttributes()
diags = append(diags, moreDiags...)
@@ -166,7 +202,7 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*LocalBlock, hcl.Diag
Subject: attr.NameRange.Ptr(),
Context: block.DefRange.Ptr(),
})
return nil, diags
return locals, diags
}
locals = append(locals, &LocalBlock{
Name: name,
@@ -176,6 +212,7 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*LocalBlock, hcl.Diag
}
}
c.LocalBlocks = locals
return locals, diags
}
@@ -221,14 +258,14 @@ func (c *PackerConfig) evaluateLocalVariables(locals []*LocalBlock) hcl.Diagnost
func (c *PackerConfig) evaluateLocalVariable(local *LocalBlock) hcl.Diagnostics {
var diags hcl.Diagnostics
value, moreDiags := local.Expr.Value(c.EvalContext(nil))
value, moreDiags := local.Expr.Value(c.EvalContext(LocalContext, nil))
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
return diags
}
c.LocalVariables[local.Name] = &Variable{
Name: local.Name,
Name: local.Name,
Sensitive: local.Sensitive,
Values: []VariableAssignment{{
Value: value,
Expr: local.Expr,
@@ -248,7 +285,7 @@ func (cfg *PackerConfig) evaluateDatasources(skipExecution bool) hcl.Diagnostics
continue
}
datasource, startDiags := cfg.startDatasource(cfg.datasourceSchemas, ref)
datasource, startDiags := cfg.startDatasource(cfg.parser.PluginConfig.DataSources, ref)
diags = append(diags, startDiags...)
if diags.HasErrors() {
continue
@@ -279,7 +316,7 @@ func (cfg *PackerConfig) evaluateDatasources(skipExecution bool) hcl.Diagnostics
// getCoreBuildProvisioners takes a list of provisioner block, starts according
// provisioners and sends parsed HCL2 over to it.
func (cfg *PackerConfig) getCoreBuildProvisioners(source SourceBlock, blocks []*ProvisionerBlock, ectx *hcl.EvalContext) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) {
func (cfg *PackerConfig) getCoreBuildProvisioners(source SourceUseBlock, blocks []*ProvisionerBlock, ectx *hcl.EvalContext) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) {
var diags hcl.Diagnostics
res := []packer.CoreBuildProvisioner{}
for _, pb := range blocks {
@@ -322,7 +359,7 @@ func (cfg *PackerConfig) getCoreBuildProvisioners(source SourceBlock, blocks []*
// getCoreBuildProvisioners takes a list of post processor block, starts
// according provisioners and sends parsed HCL2 over to it.
func (cfg *PackerConfig) getCoreBuildPostProcessors(source SourceBlock, blocksList [][]*PostProcessorBlock, ectx *hcl.EvalContext) ([][]packer.CoreBuildPostProcessor, hcl.Diagnostics) {
func (cfg *PackerConfig) getCoreBuildPostProcessors(source SourceUseBlock, blocksList [][]*PostProcessorBlock, ectx *hcl.EvalContext) ([][]packer.CoreBuildPostProcessor, hcl.Diagnostics) {
var diags hcl.Diagnostics
res := [][]packer.CoreBuildPostProcessor{}
for _, blocks := range blocksList {
@@ -375,24 +412,26 @@ func (cfg *PackerConfig) GetBuilds(opts packer.GetBuildsOptions) ([]packersdk.Bu
res := []packersdk.Build{}
var diags hcl.Diagnostics
cfg.debug = opts.Debug
cfg.force = opts.Force
cfg.onError = opts.OnError
for _, build := range cfg.Builds {
for _, from := range build.Sources {
src, found := cfg.Sources[from.Ref()]
for _, srcUsage := range build.Sources {
src, found := cfg.Sources[srcUsage.SourceRef]
if !found {
diags = append(diags, &hcl.Diagnostic{
Summary: "Unknown " + sourceLabel + " " + from.String(),
Summary: "Unknown " + sourceLabel + " " + srcUsage.String(),
Subject: build.HCL2Ref.DefRange.Ptr(),
Severity: hcl.DiagError,
Detail: fmt.Sprintf("Known: %v", cfg.Sources),
})
continue
}
src.addition = from.addition
src.LocalName = from.LocalName
pcb := &packer.CoreBuild{
BuildName: build.Name,
Type: src.String(),
Type: srcUsage.String(),
}
// Apply the -only and -except command-line options to exclude matching builds.
@@ -435,7 +474,7 @@ func (cfg *PackerConfig) GetBuilds(opts packer.GetBuildsOptions) ([]packersdk.Bu
}
}
builder, moreDiags, generatedVars := cfg.startBuilder(src, cfg.EvalContext(nil), opts)
builder, moreDiags, generatedVars := cfg.startBuilder(srcUsage, cfg.EvalContext(BuildContext, nil))
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
@@ -453,16 +492,16 @@ func (cfg *PackerConfig) GetBuilds(opts packer.GetBuildsOptions) ([]packersdk.Bu
unknownBuildValues["name"] = cty.StringVal(build.Name)
variables := map[string]cty.Value{
sourcesAccessor: cty.ObjectVal(src.ctyValues()),
sourcesAccessor: cty.ObjectVal(srcUsage.ctyValues()),
buildAccessor: cty.ObjectVal(unknownBuildValues),
}
provisioners, moreDiags := cfg.getCoreBuildProvisioners(src, build.ProvisionerBlocks, cfg.EvalContext(variables))
provisioners, moreDiags := cfg.getCoreBuildProvisioners(srcUsage, build.ProvisionerBlocks, cfg.EvalContext(BuildContext, variables))
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
}
pps, moreDiags := cfg.getCoreBuildPostProcessors(src, build.PostProcessorsLists, cfg.EvalContext(variables))
pps, moreDiags := cfg.getCoreBuildPostProcessors(srcUsage, build.PostProcessorsLists, cfg.EvalContext(BuildContext, variables))
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
@@ -564,7 +603,7 @@ func (p *PackerConfig) printBuilds() string {
fmt.Fprintf(out, "\n <no source>\n")
}
for _, source := range build.Sources {
fmt.Fprintf(out, "\n %s\n", source)
fmt.Fprintf(out, "\n %s\n", source.String())
}
fmt.Fprintf(out, "\n provisioners:\n\n")
if len(build.ProvisionerBlocks) == 0 {
@@ -604,7 +643,7 @@ func (p *PackerConfig) handleEval(line string) (out string, exit bool, diags hcl
return "", false, diags
}
val, valueDiags := expr.Value(p.EvalContext(nil))
val, valueDiags := expr.Value(p.EvalContext(NilContext, nil))
diags = append(diags, valueDiags...)
if valueDiags.HasErrors() {
return "", false, diags
+260 -7
View File
@@ -3,8 +3,11 @@ package hcl2template
import (
"testing"
"github.com/hashicorp/go-version"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/hcl2template/addrs"
. "github.com/hashicorp/packer/hcl2template/internal"
hcl2template "github.com/hashicorp/packer/hcl2template/internal"
"github.com/hashicorp/packer/packer"
"github.com/zclconf/go-cty/cty"
)
@@ -12,6 +15,7 @@ import (
var (
refVBIsoUbuntu1204 = SourceRef{Type: "virtualbox-iso", Name: "ubuntu-1204"}
refAWSEBSUbuntu1604 = SourceRef{Type: "amazon-ebs", Name: "ubuntu-1604"}
refAWSV3MyImage = SourceRef{Type: "amazon-v3-ebs", Name: "my-image"}
pTrue = pointerToBool(true)
)
@@ -23,7 +27,20 @@ func TestParser_complete(t *testing.T) {
defaultParser,
parseTestArgs{"testdata/complete", nil, nil},
&PackerConfig{
Basedir: "testdata/complete",
Packer: struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}{
VersionConstraints: []VersionConstraint{
{
Required: mustVersionConstraints(version.NewConstraint(">= v1")),
},
},
RequiredPlugins: nil,
},
CorePackerVersionString: lockedVersion,
Basedir: "testdata/complete",
InputVariables: Variables{
"foo": &Variable{
Name: "foo",
@@ -104,6 +121,13 @@ func TestParser_complete(t *testing.T) {
}),
}),
},
"supersecret": &Variable{
Name: "supersecret",
Values: []VariableAssignment{{From: "default",
Value: cty.StringVal("image-id-default-password")}},
Type: cty.String,
Sensitive: true,
},
},
Datasources: Datasources{
DatasourceRef{Type: "amazon-ami", Name: "test"}: Datasource{
@@ -118,9 +142,13 @@ func TestParser_complete(t *testing.T) {
},
Builds: Builds{
&BuildBlock{
Sources: []SourceRef{
refVBIsoUbuntu1204,
refAWSEBSUbuntu1604,
Sources: []SourceUseBlock{
{
SourceRef: refVBIsoUbuntu1204,
},
{
SourceRef: refAWSEBSUbuntu1604,
},
},
ProvisionerBlocks: []*ProvisionerBlock{
{
@@ -193,7 +221,7 @@ func TestParser_complete(t *testing.T) {
PType: "amazon-import",
PName: "something",
PostProcessor: &HCL2PostProcessor{
PostProcessor: basicMockPostProcessor,
PostProcessor: basicMockPostProcessorDynamicTags,
},
KeepInputArtifact: pTrue,
},
@@ -250,7 +278,20 @@ func TestParser_complete(t *testing.T) {
Int: 42,
Tags: []MockTag{},
},
NestedSlice: []NestedMockConfig{},
Nested: hcl2template.NestedMockConfig{
Tags: []hcl2template.MockTag{
{Key: "Component", Value: "user-service"},
{Key: "Environment", Value: "production"},
},
},
NestedSlice: []NestedMockConfig{
hcl2template.NestedMockConfig{
Tags: []hcl2template.MockTag{
{Key: "Component", Value: "user-service"},
{Key: "Environment", Value: "production"},
},
},
},
},
},
Provisioners: []packer.CoreBuildProvisioner{
@@ -274,7 +315,7 @@ func TestParser_complete(t *testing.T) {
PType: "amazon-import",
PName: "something",
PostProcessor: &HCL2PostProcessor{
PostProcessor: basicMockPostProcessor,
PostProcessor: basicMockPostProcessorDynamicTags,
},
KeepInputArtifact: pTrue,
},
@@ -350,6 +391,218 @@ func TestParser_ValidateFilterOption(t *testing.T) {
}
}
func TestParser_no_init(t *testing.T) {
defaultParser := getBasicParser()
tests := []parseTest{
{"working build with imports",
defaultParser,
parseTestArgs{"testdata/init/imports", nil, nil},
&PackerConfig{
Packer: struct {
VersionConstraints []VersionConstraint
RequiredPlugins []*RequiredPlugins
}{
VersionConstraints: []VersionConstraint{
{
Required: mustVersionConstraints(version.NewConstraint(">= v1")),
},
},
RequiredPlugins: []*RequiredPlugins{
{
RequiredPlugins: map[string]*RequiredPlugin{
"amazon": {
Name: "amazon",
Source: "",
Type: &addrs.Plugin{
Type: "amazon",
Namespace: "hashicorp",
Hostname: "github.com",
},
Requirement: VersionConstraint{
Required: mustVersionConstraints(version.NewConstraint(">= v0")),
},
},
"amazon-v1": {
Name: "amazon-v1",
Source: "amazon",
Type: &addrs.Plugin{
Type: "amazon",
Namespace: "hashicorp",
Hostname: "github.com",
},
Requirement: VersionConstraint{
Required: mustVersionConstraints(version.NewConstraint(">= v1")),
},
},
"amazon-v2": {
Name: "amazon-v2",
Source: "amazon",
Type: &addrs.Plugin{
Type: "amazon",
Namespace: "hashicorp",
Hostname: "github.com",
},
Requirement: VersionConstraint{
Required: mustVersionConstraints(version.NewConstraint(">= v2")),
},
},
"amazon-v3": {
Name: "amazon-v3",
Source: "hashicorp/amazon",
Type: &addrs.Plugin{
Type: "amazon",
Namespace: "hashicorp",
Hostname: "github.com",
},
Requirement: VersionConstraint{
Required: mustVersionConstraints(version.NewConstraint(">= v3")),
},
},
"amazon-v3-azr": {
Name: "amazon-v3-azr",
Source: "azr/amazon",
Type: &addrs.Plugin{
Type: "amazon",
Namespace: "azr",
Hostname: "github.com",
},
Requirement: VersionConstraint{
Required: mustVersionConstraints(version.NewConstraint(">= v3")),
},
},
"amazon-v4": {
Name: "amazon-v4",
Source: "github.com/hashicorp/amazon",
Type: &addrs.Plugin{
Type: "amazon",
Namespace: "hashicorp",
Hostname: "github.com",
},
Requirement: VersionConstraint{
Required: mustVersionConstraints(version.NewConstraint(">= v4")),
},
},
},
},
},
},
CorePackerVersionString: lockedVersion,
Basedir: "testdata/init/imports",
InputVariables: Variables{
"foo": &Variable{
Name: "foo",
Values: []VariableAssignment{{From: "default", Value: cty.StringVal("value")}},
Type: cty.String,
},
"image_id": &Variable{
Name: "image_id",
Values: []VariableAssignment{{From: "default", Value: cty.StringVal("image-id-default")}},
Type: cty.String,
},
"port": &Variable{
Name: "port",
Values: []VariableAssignment{{From: "default", Value: cty.NumberIntVal(42)}},
Type: cty.Number,
},
"availability_zone_names": &Variable{
Name: "availability_zone_names",
Values: []VariableAssignment{{
From: "default",
Value: cty.ListVal([]cty.Value{
cty.StringVal("A"),
cty.StringVal("B"),
cty.StringVal("C"),
}),
}},
Type: cty.List(cty.String),
},
},
Sources: map[SourceRef]SourceBlock{
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
refAWSV3MyImage: {Type: "amazon-v3-ebs", Name: "my-image"},
},
Builds: Builds{
&BuildBlock{
Sources: []SourceUseBlock{
{
SourceRef: refVBIsoUbuntu1204,
},
{
SourceRef: refAWSV3MyImage,
},
},
ProvisionerBlocks: []*ProvisionerBlock{
{
PType: "shell",
PName: "provisioner that does something",
},
{
PType: "file",
},
},
PostProcessorsLists: [][]*PostProcessorBlock{
{
{
PType: "amazon-import",
PName: "something",
KeepInputArtifact: pTrue,
},
},
{
{
PType: "amazon-import",
},
},
{
{
PType: "amazon-import",
PName: "first-nested-post-processor",
},
{
PType: "amazon-import",
PName: "second-nested-post-processor",
},
},
{
{
PType: "amazon-import",
PName: "third-nested-post-processor",
},
{
PType: "amazon-import",
PName: "fourth-nested-post-processor",
},
},
},
},
},
},
false, false,
[]packersdk.Build{},
false,
},
{"duplicate required plugin accessor fails",
defaultParser,
parseTestArgs{"testdata/init/duplicate_required_plugins", nil, nil},
nil,
true, true,
[]packersdk.Build{},
false,
},
}
testParse_only_Parse(t, tests)
}
func pointerToBool(b bool) *bool {
return &b
}
func mustVersionConstraints(vs version.Constraints, err error) version.Constraints {
if err != nil {
panic(err)
}
return vs
}
+242
View File
@@ -0,0 +1,242 @@
package hcl2template
import (
"fmt"
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/hcl2template/addrs"
"github.com/zclconf/go-cty/cty"
)
func (cfg *PackerConfig) decodeRequiredPluginsBlock(f *hcl.File) hcl.Diagnostics {
var diags hcl.Diagnostics
content, moreDiags := f.Body.Content(configSchema)
diags = append(diags, moreDiags...)
for _, block := range content.Blocks {
switch block.Type {
case packerLabel:
content, contentDiags := block.Body.Content(packerBlockSchema)
diags = append(diags, contentDiags...)
// We ignore "packer_version"" here because
// sniffCoreVersionRequirements already dealt with that
for _, innerBlock := range content.Blocks {
switch innerBlock.Type {
case "required_plugins":
reqs, reqsDiags := decodeRequiredPluginsBlock(innerBlock)
diags = append(diags, reqsDiags...)
cfg.Packer.RequiredPlugins = append(cfg.Packer.RequiredPlugins, reqs)
default:
continue
}
}
}
}
return diags
}
func (cfg *PackerConfig) decodeImplicitRequiredPluginsBlocks(f *hcl.File) hcl.Diagnostics {
// when a plugin is used but not defined in the required plugin blocks, it
// is 'implicitly used'. Here we read common configuration blocks to try to
// guess plugins.
var diags hcl.Diagnostics
content, moreDiags := f.Body.Content(configSchema)
diags = append(diags, moreDiags...)
for _, block := range content.Blocks {
switch block.Type {
case sourceLabel:
// TODO
}
}
return diags
}
// RequiredPlugin represents a declaration of a dependency on a particular
// Plugin version or source.
type RequiredPlugin struct {
Name string
Source string
Type *addrs.Plugin
Requirement VersionConstraint
DeclRange hcl.Range
}
type RequiredPlugins struct {
RequiredPlugins map[string]*RequiredPlugin
DeclRange hcl.Range
}
func decodeRequiredPluginsBlock(block *hcl.Block) (*RequiredPlugins, hcl.Diagnostics) {
attrs, diags := block.Body.JustAttributes()
ret := &RequiredPlugins{
RequiredPlugins: make(map[string]*RequiredPlugin),
DeclRange: block.DefRange,
}
for name, attr := range attrs {
expr, err := attr.Expr.Value(nil)
if err != nil {
diags = append(diags, err...)
}
nameDiags := checkPluginNameNormalized(name, attr.Expr.Range())
diags = append(diags, nameDiags...)
rp := &RequiredPlugin{
Name: name,
DeclRange: attr.Expr.Range(),
}
switch {
case expr.Type().IsPrimitiveType():
vc, reqDiags := decodeVersionConstraint(attr)
diags = append(diags, reqDiags...)
rp.Requirement = vc
rp.Type, err = addrs.ParsePluginSourceString(name)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin type",
Detail: fmt.Sprintf(`Invalid plugin type %q: %s"`, name, err),
})
}
case expr.Type().IsObjectType():
if !expr.Type().HasAttribute("version") {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No version constraint was set",
Detail: "The version field must be specified as a string. Ex: `version = \">= 1.2.0, < 2.0.0\". See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraints for docs",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
vc := VersionConstraint{
DeclRange: attr.Range,
}
constraint := expr.GetAttr("version")
if !constraint.Type().Equals(cty.String) || constraint.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid version constraint",
Detail: "Version must be specified as a string. See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraint-syntax for docs.",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
constraintStr := constraint.AsString()
constraints, err := version.NewConstraint(constraintStr)
if err != nil {
// NewConstraint doesn't return user-friendly errors, so we'll just
// ignore the provided error and produce our own generic one.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid version constraint",
Detail: "This string does not use correct version constraint syntax. See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#version-constraint-syntax for docs.",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
vc.Required = constraints
rp.Requirement = vc
if !expr.Type().HasAttribute("source") {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "No source was set",
Detail: "The source field must be specified as a string. Ex: `source = \"coolcloud\". See https://www.packer.io/docs/templates/hcl_templates/blocks/packer#specifying-plugin-requirements for docs",
Subject: attr.Expr.Range().Ptr(),
})
continue
}
source := expr.GetAttr("source")
if !source.Type().Equals(cty.String) || source.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid source",
Detail: "Source must be specified as a string. For example: " + `source = "coolcloud"`,
Subject: attr.Expr.Range().Ptr(),
})
continue
}
rp.Source = source.AsString()
p, sourceDiags := addrs.ParsePluginSourceString(rp.Source)
if sourceDiags.HasErrors() {
for _, diag := range sourceDiags {
if diag.Subject == nil {
diag.Subject = attr.Expr.Range().Ptr()
}
}
diags = append(diags, sourceDiags...)
} else {
rp.Type = p
}
attrTypes := expr.Type().AttributeTypes()
for name := range attrTypes {
if name == "version" || name == "source" {
continue
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid required_plugins object",
Detail: `required_plugins objects can only contain "version" and "source" attributes.`,
Subject: attr.Expr.Range().Ptr(),
})
break
}
default:
// should not happen
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid required_plugins syntax",
Detail: "required_plugins entries must be objects.",
Subject: attr.Expr.Range().Ptr(),
})
}
ret.RequiredPlugins[rp.Name] = rp
}
return ret, diags
}
// checkPluginNameNormalized verifies that the given string is already
// normalized and returns an error if not.
func checkPluginNameNormalized(name string, declrange hcl.Range) hcl.Diagnostics {
var diags hcl.Diagnostics
// verify that the plugin local name is normalized
normalized, err := addrs.IsPluginPartNormalized(name)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin local name",
Detail: fmt.Sprintf("%s is an invalid plugin local name: %s", name, err),
Subject: &declrange,
})
return diags
}
if !normalized {
// we would have returned this error already
normalizedPlugin, _ := addrs.ParsePluginPart(name)
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid plugin local name",
Detail: fmt.Sprintf("Plugin names must be normalized. Replace %q with %q to fix this error.", name, normalizedPlugin),
Subject: &declrange,
})
}
return diags
}

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