Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions command/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
hcppackerversiondatasource "github.com/hashicorp/packer/datasource/hcp-packer-version"
httpdatasource "github.com/hashicorp/packer/datasource/http"
nulldatasource "github.com/hashicorp/packer/datasource/null"
passworddatasource "github.com/hashicorp/packer/datasource/password"
artificepostprocessor "github.com/hashicorp/packer/post-processor/artifice"
checksumpostprocessor "github.com/hashicorp/packer/post-processor/checksum"
compresspostprocessor "github.com/hashicorp/packer/post-processor/compress"
Expand Down Expand Up @@ -73,6 +74,7 @@ var Datasources = map[string]packersdk.Datasource{
"hcp-packer-version": new(hcppackerversiondatasource.Datasource),
"http": new(httpdatasource.Datasource),
"null": new(nulldatasource.Datasource),
"password": new(passworddatasource.Datasource),
}

var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner|datasource)-(.+)")
Expand Down
126 changes: 126 additions & 0 deletions datasource/password/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config
package password

import (
"fmt"

"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/common"
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/config"
"github.com/zclconf/go-cty/cty"
)

type Config struct {
common.PackerConfig `mapstructure:",squash"`
// The desired length of password.
Length int64 `mapstructure:"length" required:"true"`
// Include special characters in the result. These are `!@#$%&*()-_=+[]{}<>:?`.
Special bool `mapstructure:"special" required:"false"`
// Include uppercase alphabet characters in the result.
Upper bool `mapstructure:"upper" required:"false"`
// Include lowercase alphabet characters in the result.
Lower bool `mapstructure:"lower" required:"false"`
// Include numeric characters in the result.
Numeric bool `mapstructure:"numeric" required:"false"`
// Minimum number of numeric characters in the result. Default value is 0
MinNumeric int64 `mapstructure:"min_numeric" required:"false"`
// Minimum number of uppercase alphabet characters in the result. Default value is 0
MinUpper int64 `mapstructure:"min_upper" required:"false"`
// Minimum number of lowercase alphabet characters in the result. Default value is 0
MinLower int64 `mapstructure:"min_lower" required:"false"`
// Minimum number of special characters in the result. Default value is 0
MinSpecial int64 `mapstructure:"min_special" required:"false"`
// Supply your own list of special characters to use for string generation.
// This overrides the default character list in the special argument.
// The `special` argument must still be set to true for any overwritten characters to be used in generation.
OverrideSpecial string `mapstructure:"override_special" required:"false"`
}

type Datasource struct {
config Config
}

type DatasourceOutput struct {
// The result of the password generation. This is the final password string.
Result string `mapstructure:"result"`
// A bcrypt hash of the generated random string
// **NOTE**: If the generated random string is greater than 72 bytes in length,
// `bcrypt_hash` will contain a hash of the first 72 bytes
BcryptHash string `mapstructure:"bcrypt_hash"`
}

func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
return d.config.FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Configure(raws ...interface{}) error {

err := config.Decode(&d.config, nil, raws...)
if err != nil {
return err
}

var errs *packersdk.MultiError

if d.config.Length < 1 ||
d.config.Length < (d.config.MinLower+d.config.MinUpper+d.config.MinNumeric+d.config.MinSpecial) {

errs = packersdk.MultiErrorAppend(
errs,
fmt.Errorf("the minimum value for length is 1 and, "+
"length must also be >= (`min_upper` + `min_lower` + `min_numeric` + `min_special`)"))
}

if errs != nil && len(errs.Errors) > 0 {
return errs
}
return nil
}

func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Execute() (cty.Value, error) {
params := StringParams{
Length: d.config.Length,
Upper: d.config.Upper,
MinUpper: d.config.MinUpper,
Lower: d.config.Lower,
MinLower: d.config.MinLower,
Numeric: d.config.Numeric,
MinNumeric: d.config.MinNumeric,
Special: d.config.Special,
MinSpecial: d.config.MinSpecial,
OverrideSpecial: d.config.OverrideSpecial,
}

result, err := CreateString(params)
if err != nil {
return cty.NullVal(cty.EmptyObject), fmt.Errorf(
"while attempting to generate a random value a read error was generated: %s",
err.Error(),
)
}

hash, err := generateHash(result)
if err != nil {
return cty.NullVal(cty.EmptyObject), fmt.Errorf(
"while attempting to generate a hash from the password an error occurred: %s",
err.Error(),
)
}

output := DatasourceOutput{
Result: result,
BcryptHash: hash,
}

return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
}
90 changes: 90 additions & 0 deletions datasource/password/data.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions datasource/password/data_acc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package password

import (
_ "embed"
"fmt"
"os/exec"
"testing"

"github.com/hashicorp/packer-plugin-sdk/acctest"
)

//go:embed test-fixtures/basic.pkr.hcl
var testDatasourceBasic string

//go:embed test-fixtures/basic-custom-charset.pkr.hcl
var testDatasourceBasicWithCustomCharset string

//go:embed test-fixtures/invalid-length.pkr.hcl
var testDataSourceInvalidLength string

//go:embed test-fixtures/empty-charset.pkr.hcl
var testDataSourceEmptyCharset string

func TestPasswordDataSource(t *testing.T) {
tests := []struct {
Name string
Path string
Error bool
}{
{
Name: "basic_test",
Path: testDatasourceBasic,
Error: false,
},
{
Name: "basic_with_custom_charset_test",
Path: testDatasourceBasicWithCustomCharset,
Error: false,
},
{
Name: "invalid_length_test",
Path: testDataSourceInvalidLength,
Error: true,
},
{
Name: "empty_charset_test",
Path: testDataSourceEmptyCharset,
Error: true,
},
}
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
testCase := &acctest.PluginTestCase{
Name: tt.Name,
Template: tt.Path,
Check: func(buildCommand *exec.Cmd, logfile string) error {
if buildCommand.ProcessState != nil {
if buildCommand.ProcessState.ExitCode() != 0 && !tt.Error {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}
if tt.Error && buildCommand.ProcessState.ExitCode() == 0 {
return fmt.Errorf("Expected Bad exit code.")
}
}
return nil
},
}
acctest.TestPlugin(t, testCase)
})
}

}
22 changes: 22 additions & 0 deletions datasource/password/test-fixtures/basic-custom-charset.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 16
override_special = "pasword"
special = true
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
23 changes: 23 additions & 0 deletions datasource/password/test-fixtures/basic.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 16
special = true
lower = true
upper = true
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
20 changes: 20 additions & 0 deletions datasource/password/test-fixtures/empty-charset.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 16
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
21 changes: 21 additions & 0 deletions datasource/password/test-fixtures/invalid-length.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
source "null" "example" {
communicator = "none"
}

data "password" "sample" {
length = 0
special = true
}

build {
name = "mybuild"
sources = [
"source.null.example"
]
provisioner "shell-local" {
inline = [
"echo password: '${data.password.sample.result}'",
"echo generated hash is '${data.password.sample.bcrypt_hash}'"
]
}
}
Loading
Loading