Unverified Commit 291121dd authored by Sylvia Moss's avatar Sylvia Moss Committed by GitHub
Browse files

(2) Implement datasources (#10440)

parent ab984090
Showing with 245 additions and 145 deletions
+245 -145
......@@ -12,7 +12,6 @@ import (
"strings"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/plugin"
)
......@@ -130,22 +129,23 @@ func discoverAndLoad() error {
}
// TODO: validate correctness of plugins loaded by checking them against the output of the `describe` command.
builders, provisioners, postProcessors := config.GetPlugins()
if len(builders) == 0 &&
len(provisioners) == 0 &&
len(postProcessors) == 0 {
return fmt.Errorf("couldn't load any Builder/Provisioner/Post-Processor from the plugin binary")
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")
}
return checkHCL2ConfigSpec(builders, provisioners, postProcessors)
return checkHCL2ConfigSpec(plugins)
}
// 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(builders packer.BuilderStore, provisioners packer.ProvisionerStore, postProcessors packer.PostProcessorStore) error {
func checkHCL2ConfigSpec(plugins plugin.Plugins) error {
var errs *packersdk.MultiError
for _, b := range builders.List() {
builder, err := builders.Start(b)
for _, b := range plugins.Builders.List() {
builder, err := plugins.Builders.Start(b)
if err != nil {
return packersdk.MultiErrorAppend(err, errs)
}
......@@ -153,8 +153,8 @@ func checkHCL2ConfigSpec(builders packer.BuilderStore, provisioners packer.Provi
errs = packersdk.MultiErrorAppend(fmt.Errorf("builder %q does not contain the required hcl2spec configuration", b), errs)
}
}
for _, p := range provisioners.List() {
provisioner, err := provisioners.Start(p)
for _, p := range plugins.Provisioners.List() {
provisioner, err := plugins.Provisioners.Start(p)
if err != nil {
return packersdk.MultiErrorAppend(err, errs)
}
......@@ -162,8 +162,8 @@ func checkHCL2ConfigSpec(builders packer.BuilderStore, provisioners packer.Provi
errs = packersdk.MultiErrorAppend(fmt.Errorf("provisioner %q does not contain the required hcl2spec configuration", p), errs)
}
}
for _, pp := range postProcessors.List() {
postProcessor, err := postProcessors.Start(pp)
for _, pp := range plugins.PostProcessors.List() {
postProcessor, err := plugins.PostProcessors.Start(pp)
if err != nil {
return packersdk.MultiErrorAppend(err, errs)
}
......@@ -171,6 +171,15 @@ func checkHCL2ConfigSpec(builders packer.BuilderStore, provisioners packer.Provi
errs = packersdk.MultiErrorAppend(fmt.Errorf("post-processor %q does not contain the required hcl2spec configuration", pp), errs)
}
}
for _, d := range plugins.DataSources.List() {
datasource, err := plugins.DataSources.Start(d)
if err != nil {
return packersdk.MultiErrorAppend(err, errs)
}
if len(datasource.ConfigSpec()) == 0 {
errs = packersdk.MultiErrorAppend(fmt.Errorf("datasource %q does not contain the required hcl2spec configuration", d), errs)
}
}
if errs != nil && len(errs.Errors) > 0 {
return errs
}
......
......@@ -70,6 +70,7 @@ func (m *Meta) GetConfigFromHCL(cla *MetaArgs) (*hcl2template.PackerConfig, int)
BuilderSchemas: m.CoreConfig.Components.BuilderStore,
ProvisionersSchemas: m.CoreConfig.Components.ProvisionerStore,
PostProcessorsSchemas: m.CoreConfig.Components.PostProcessorStore,
DatasourceSchemas: m.CoreConfig.Components.DatasourceStore,
}
cfg, diags := parser.Parse(cla.Path, cla.VarFiles, cla.Vars)
return cfg, writeDiags(m.Ui, parser.Files(), diags)
......@@ -147,7 +148,7 @@ func (c *BuildCommand) RunContext(buildCtx context.Context, cla *BuildArgs) int
if ret != 0 {
return ret
}
diags := packerStarter.Initialize()
diags := packerStarter.Initialize(packer.InitializeOptions{})
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
......
......@@ -371,6 +371,17 @@ func TestBuild(t *testing.T) {
},
expectedCode: 1,
},
{
name: "hcl - execute and use datasource",
args: []string{
testFixture("hcl", "datasource.pkr.hcl"),
},
fileCheck: fileCheck{
expectedContent: map[string]string{
"chocolate.txt": "chocolate",
},
},
},
}
for _, tt := range tc {
......@@ -849,6 +860,9 @@ func testCoreConfigBuilder(t *testing.T) *packer.CoreConfig {
"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 },
},
}
return &packer.CoreConfig{
Components: components,
......
......@@ -60,7 +60,7 @@ func (c *ConsoleCommand) RunContext(ctx context.Context, cla *ConsoleArgs) int {
return ret
}
_ = packerStarter.Initialize()
_ = packerStarter.Initialize(packer.InitializeOptions{})
// Determine if stdin is a pipe. If so, we evaluate directly.
if c.StdinPiped() {
......
......@@ -11,7 +11,7 @@ type CoreWrapper struct {
*packer.Core
}
func (c *CoreWrapper) Initialize() hcl.Diagnostics {
func (c *CoreWrapper) Initialize(_ packer.InitializeOptions) hcl.Diagnostics {
err := c.Core.Initialize()
if err != nil {
return hcl.Diagnostics{
......
......@@ -13,8 +13,8 @@ import (
texttemplate "text/template"
"github.com/hashicorp/hcl/v2/hclwrite"
hcl2shim "github.com/hashicorp/packer-plugin-sdk/hcl2helper"
"github.com/hashicorp/packer-plugin-sdk/template"
hcl2shim "github.com/hashicorp/packer/hcl2template/shim"
"github.com/mitchellh/mapstructure"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
......
......@@ -46,7 +46,7 @@ func (c *InspectCommand) RunContext(ctx context.Context, cla *InspectArgs) int {
}
// here we ignore init diags to allow unknown variables to be used
_ = packerStarter.Initialize()
_ = packerStarter.Initialize(packer.InitializeOptions{})
return packerStarter.InspectConfig(packer.InspectConfigOptions{
Ui: c.Ui,
......
......@@ -212,7 +212,9 @@ var PostProcessors = map[string]packersdk.PostProcessor{
"yandex-import": new(yandeximportpostprocessor.PostProcessor),
}
var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)")
var Datasources = map[string]packersdk.Datasource{}
var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner|datasource)-(.+)")
func (c *PluginCommand) Run(args []string) int {
// This is an internal call (users should not call this directly) so we're
......@@ -261,6 +263,13 @@ func (c *PluginCommand) Run(args []string) int {
return 1
}
server.RegisterPostProcessor(postProcessor)
case "datasource":
datasource, found := Datasources[pluginName]
if !found {
c.Ui.Error(fmt.Sprintf("Could not load datasource: %s", pluginName))
return 1
}
server.RegisterDatasource(datasource)
}
server.Serve()
......
data "mock" "content" {
foo = "chocolate"
}
source "file" "chocolate" {
content = data.mock.content.foo
target = "chocolate.txt"
}
build {
sources = [
"sources.file.chocolate",
]
}
packer {
required_version = ">= v1.0.0"
}
data "mock" "content" {
foo = "content"
}
source "file" "chocolate" {
target = "chocolate.txt"
content = data.mock.content.foo
}
build {
sources = ["source.file.chocolate"]
}
......@@ -56,7 +56,9 @@ func (c *ValidateCommand) RunContext(ctx context.Context, cla *ValidateArgs) int
return 0
}
diags := packerStarter.Initialize()
diags := packerStarter.Initialize(packer.InitializeOptions{
SkipDatasourcesExecution: true,
})
ret = writeDiags(c.Ui, nil, diags)
if ret != 0 {
return ret
......
......@@ -5,6 +5,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)
func TestValidateCommand(t *testing.T) {
......@@ -45,6 +46,29 @@ func TestValidateCommand(t *testing.T) {
}
}
func TestValidateCommand_SkipDatasourceExecution(t *testing.T) {
datasourceMock := &packersdk.MockDatasource{}
meta := testMetaFile(t)
meta.CoreConfig.Components.DatasourceStore = packersdk.MapOfDatasource{
"mock": func() (packersdk.Datasource, error) {
return datasourceMock, nil
},
}
c := &ValidateCommand{
Meta: meta,
}
args := []string{filepath.Join(testFixture("validate"), "datasource.pkr.hcl")}
if code := c.Run(args); code != 0 {
fatalCommand(t, c.Meta)
}
if datasourceMock.ExecuteCalled {
t.Fatalf("Datasource should not be executed on validation")
}
if !datasourceMock.OutputSpecCalled {
t.Fatalf("Datasource OutPutSpec should be called on validation")
}
}
func TestValidateCommand_SyntaxOnly(t *testing.T) {
tt := []struct {
path string
......
......@@ -30,6 +30,7 @@ type config struct {
Builders packer.MapOfBuilder `json:"-"`
Provisioners packer.MapOfProvisioner `json:"-"`
PostProcessors packer.MapOfPostProcessor `json:"-"`
Datasources packer.MapOfDatasource `json:"-"`
Plugins plugin.Config
}
......@@ -186,5 +187,17 @@ func (c *config) discoverInternalComponents() error {
}
}
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)
return c.Plugins.Client(bin).Datasource()
}
}
}
return nil
}
This diff is collapsed.
......@@ -33,6 +33,9 @@ func getBasicParser() *Parser {
"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 },
},
}
}
......@@ -62,7 +65,7 @@ func testParse(t *testing.T, tests []parseTest) {
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)
moreDiags := gotCfg.Initialize()
moreDiags := gotCfg.Initialize(packer.InitializeOptions{})
gotDiags = append(gotDiags, moreDiags...)
if tt.parseWantDiags == (gotDiags == nil) {
t.Fatalf("Parser.parse() unexpected %q diagnostics.", gotDiags)
......@@ -125,13 +128,39 @@ var (
Tags: []MockTag{},
}
// everything in the tests is a basicNestedMockConfig this allow to test
// each known type to packer ( and embedding ) in one go.
builderBasicNestedMockConfig = 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{},
Datasource: "string",
}
basicMockBuilder = &MockBuilder{
Config: MockConfig{
NestedMockConfig: basicNestedMockConfig,
Nested: basicNestedMockConfig,
NestedMockConfig: builderBasicNestedMockConfig,
Nested: builderBasicNestedMockConfig,
NestedSlice: []NestedMockConfig{
basicNestedMockConfig,
basicNestedMockConfig,
builderBasicNestedMockConfig,
builderBasicNestedMockConfig,
},
},
}
......@@ -226,6 +255,7 @@ var cmpOpts = []cmp.Option{
PackerConfig{},
Variable{},
SourceBlock{},
Datasource{},
ProvisionerBlock{},
PostProcessorBlock{},
packer.CoreBuild{},
......
......@@ -7,6 +7,7 @@ import (
"time"
"github.com/hashicorp/hcl/v2/hcldec"
"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"
......@@ -26,6 +27,7 @@ type NestedMockConfig struct {
NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string"`
NamedString NamedString `mapstructure:"named_string"`
Tags []MockTag `mapstructure:"tag"`
Datasource string `mapstructure:"data_source"`
}
type MockTag struct {
......@@ -103,6 +105,32 @@ func (b *MockProvisioner) Provision(ctx context.Context, ui packersdk.Ui, comm p
return nil
}
//////
// MockDatasource
//////
type MockDatasource struct {
Config MockConfig
}
var _ packersdk.Datasource = new(MockDatasource)
func (d *MockDatasource) ConfigSpec() hcldec.ObjectSpec {
return d.Config.FlatMapstructure().HCL2Spec()
}
func (d *MockDatasource) OutputSpec() hcldec.ObjectSpec {
return d.Config.FlatMapstructure().HCL2Spec()
}
func (d *MockDatasource) Configure(raws ...interface{}) error {
return d.Config.Prepare(raws...)
}
func (d *MockDatasource) Execute() (cty.Value, error) {
return hcl2helper.HCL2ValueFromConfig(d.Config, d.OutputSpec()), nil
}
//////
// MockPostProcessor
//////
......
......@@ -23,6 +23,7 @@ type FlatMockConfig struct {
NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string" cty:"named_map_string_string" hcl:"named_map_string_string"`
NamedString *NamedString `mapstructure:"named_string" cty:"named_string" hcl:"named_string"`
Tags []FlatMockTag `mapstructure:"tag" cty:"tag" hcl:"tag"`
Datasource *string `mapstructure:"data_source" cty:"data_source" hcl:"data_source"`
Nested *FlatNestedMockConfig `mapstructure:"nested" cty:"nested" hcl:"nested"`
NestedSlice []FlatNestedMockConfig `mapstructure:"nested_slice" cty:"nested_slice" hcl:"nested_slice"`
}
......@@ -52,6 +53,7 @@ func (*FlatMockConfig) HCL2Spec() map[string]hcldec.Spec {
"named_map_string_string": &hcldec.AttrSpec{Name: "named_map_string_string", Type: cty.Map(cty.String), Required: false},
"named_string": &hcldec.AttrSpec{Name: "named_string", Type: cty.String, Required: false},
"tag": &hcldec.BlockListSpec{TypeName: "tag", Nested: hcldec.ObjectSpec((*FlatMockTag)(nil).HCL2Spec())},
"data_source": &hcldec.AttrSpec{Name: "data_source", Type: cty.String, Required: false},
"nested": &hcldec.BlockSpec{TypeName: "nested", Nested: hcldec.ObjectSpec((*FlatNestedMockConfig)(nil).HCL2Spec())},
"nested_slice": &hcldec.BlockListSpec{TypeName: "nested_slice", Nested: hcldec.ObjectSpec((*FlatNestedMockConfig)(nil).HCL2Spec())},
}
......@@ -98,6 +100,7 @@ type FlatNestedMockConfig struct {
NamedMapStringString NamedMapStringString `mapstructure:"named_map_string_string" cty:"named_map_string_string" hcl:"named_map_string_string"`
NamedString *NamedString `mapstructure:"named_string" cty:"named_string" hcl:"named_string"`
Tags []FlatMockTag `mapstructure:"tag" cty:"tag" hcl:"tag"`
Datasource *string `mapstructure:"data_source" cty:"data_source" hcl:"data_source"`
}
// FlatMapstructure returns a new FlatNestedMockConfig.
......@@ -124,6 +127,7 @@ func (*FlatNestedMockConfig) HCL2Spec() map[string]hcldec.Spec {
"named_map_string_string": &hcldec.AttrSpec{Name: "named_map_string_string", Type: cty.Map(cty.String), Required: false},
"named_string": &hcldec.AttrSpec{Name: "named_string", Type: cty.String, Required: false},
"tag": &hcldec.BlockListSpec{TypeName: "tag", Nested: hcldec.ObjectSpec((*FlatMockTag)(nil).HCL2Spec())},
"data_source": &hcldec.AttrSpec{Name: "data_source", Type: cty.String, Required: false},
}
return s
}
......@@ -20,6 +20,7 @@ const (
variablesLabel = "variables"
variableLabel = "variable"
localsLabel = "locals"
dataSourceLabel = "data"
buildLabel = "build"
communicatorLabel = "communicator"
)
......@@ -31,6 +32,7 @@ var configSchema = &hcl.BodySchema{
{Type: variablesLabel},
{Type: variableLabel, LabelNames: []string{"name"}},
{Type: localsLabel},
{Type: dataSourceLabel, LabelNames: []string{"type", "name"}},
{Type: buildLabel},
{Type: communicatorLabel, LabelNames: []string{"type", "name"}},
},
......@@ -60,6 +62,8 @@ type Parser struct {
ProvisionersSchemas packer.ProvisionerStore
PostProcessorsSchemas packer.PostProcessorStore
DatasourceSchemas packer.DatasourceStore
}
const (
......@@ -129,6 +133,7 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
builderSchemas: p.BuilderSchemas,
provisionersSchemas: p.ProvisionersSchemas,
postProcessorsSchemas: p.PostProcessorsSchemas,
datasourceSchemas: p.DatasourceSchemas,
parser: p,
files: files,
}
......@@ -155,6 +160,11 @@ func (p *Parser) Parse(filename string, varFiles []string, argVars map[string]st
diags = append(diags, cfg.decodeInputVariables(file)...)
}
for _, file := range files {
morediags := p.decodeDatasources(file, cfg)
diags = append(diags, morediags...)
}
for _, file := range files {
moreLocals, morediags := cfg.parseLocalVariables(file)
diags = append(diags, morediags...)
......@@ -247,13 +257,14 @@ func sniffCoreVersionRequirements(body hcl.Body) ([]VersionConstraint, hcl.Diagn
return constraints, diags
}
func (cfg *PackerConfig) Initialize() hcl.Diagnostics {
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 {
......@@ -328,3 +339,41 @@ func (p *Parser) decodeConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
return diags
}
func (p *Parser) decodeDatasources(file *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
var diags hcl.Diagnostics
body := dynblock.Expand(file.Body, cfg.EvalContext(nil))
content, moreDiags := body.Content(configSchema)
diags = append(diags, moreDiags...)
for _, block := range content.Blocks {
switch block.Type {
case dataSourceLabel:
datasource, moreDiags := p.decodeDataBlock(block)
diags = append(diags, moreDiags...)
if moreDiags.HasErrors() {
continue
}
ref := datasource.Ref()
if existing, found := cfg.Datasources[ref]; found {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate " + dataSourceLabel + " block",
Detail: fmt.Sprintf("This "+dataSourceLabel+" block has the "+
"same data type and name as a previous block declared "+
"at %s. Each "+dataSourceLabel+" must have a unique name per builder type.",
existing.block.DefRange.Ptr()),
Subject: datasource.block.DefRange.Ptr(),
})
continue
}
if cfg.Datasources == nil {
cfg.Datasources = Datasources{}
}
cfg.Datasources[ref] = *datasource
}
}
return diags
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment