Skip to content

Commit

Permalink
Add schema for provisioners
Browse files Browse the repository at this point in the history
  • Loading branch information
radeksimko committed Jun 2, 2021
1 parent f0b01c2 commit 5db493d
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 51 deletions.
59 changes: 59 additions & 0 deletions internal/schema/0.12/provisioner_block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package schema

import (
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/schema"
)

func provisionerBlock(v *version.Version) *schema.BlockSchema {
return &schema.BlockSchema{
Description: lang.Markdown("Provisioner to model specific actions on the local machine or on a remote machine " +
"in order to prepare servers or other infrastructure objects for service"),
Labels: []*schema.LabelSchema{
{
Name: "type",
Description: lang.PlainText("Type of provisioner to use, e.g. `remote-exec` or `file`"),
IsDepKey: true,
},
},
Body: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"when": {
Expr: schema.ExprConstraints{
schema.KeywordExpr{
Keyword: "create",
Description: lang.Markdown("Run the provisioner when the resource is created"),
},
schema.KeywordExpr{
Keyword: "destroy",
Description: lang.Markdown("Run the provisioner when the resource is destroyed"),
},
},
IsOptional: true,
Description: lang.Markdown("When to run the provisioner - `create` or `destroy`, defaults to `create` " +
"(i.e. after creation of the resource)"),
},
"on_failure": {
IsOptional: true,
Expr: schema.ExprConstraints{
schema.KeywordExpr{
Keyword: "fail",
Description: lang.Markdown("Raise an error and stop applying (the default behavior). If this is a creation provisioner, taint the resource."),
},
schema.KeywordExpr{
Keyword: "continue",
Description: lang.Markdown("Ignore the error and continue with creation or destruction"),
},
},
Description: lang.Markdown("What to do when the provisioner run fails to finish - `fail` (default), " +
"or `continue` (ignore the error)"),
},
},
Blocks: map[string]*schema.BlockSchema{
"connection": connectionBlock,
},
},
DependentBody: ProvisionerBodies(v),
}
}
147 changes: 147 additions & 0 deletions internal/schema/0.12/provisioners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package schema

import (
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/lang"
"github.com/hashicorp/hcl-lang/schema"
"github.com/zclconf/go-cty/cty"
)

// See https://github.com/hashicorp/terraform/blob/v0.12.0/command/internal_plugin_list.go

func ProvisionerBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema {
m := map[schema.SchemaKey]*schema.BodySchema{
labelKey("file"): FileProvisioner,
labelKey("local-exec"): LocalExecProvisioner,
labelKey("remote-exec"): RemoteExecProvisioner,
}

// Vendor provisioners are deprecated in 0.13.4+
// See https://discuss.hashicorp.com/t/notice-terraform-to-begin-deprecation-of-vendor-tool-specific-provisioners-starting-in-terraform-0-13-4/13997
// Some of these provisioners have complex schemas
// but we can at least helpfully list their names
m[labelKey("chef")] = &schema.BodySchema{}
m[labelKey("salt-masterless")] = &schema.BodySchema{}
m[labelKey("habitat")] = &schema.BodySchema{}
if v.GreaterThanOrEqual(v0_12_2) {
// See https://github.com/hashicorp/terraform/commit/615110e13
m[labelKey("puppet")] = &schema.BodySchema{}
}

return m
}

func labelKey(value string) schema.SchemaKey {
return schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{{Index: 0, Value: value}},
})
}

var FileProvisioner = &schema.BodySchema{
Description: lang.Markdown("Provisioner used to copy files or directories from the machine executing Terraform" +
" to the newly created resource.\n\n" +
"[`file` provisioner](https://www.terraform.io/docs/language/resources/provisioners/file.html)"),
Attributes: map[string]*schema.AttributeSchema{
"source": {
IsOptional: true,
Expr: schema.LiteralTypeOnly(cty.String),
Description: lang.Markdown("The source file or folder. It can be specified as relative " +
"to the current working directory or as an absolute path. This attribute cannot be " +
"specified with `content`."),
},
"content": {
IsOptional: true,
Expr: schema.LiteralTypeOnly(cty.String),
Description: lang.Markdown("The content to copy on the destination. If destination is a file," +
" the content will be written on that file, in case of a directory a file named `tf-file-content`" +
" is created. It's recommended to use a file as the destination. This attribute cannot be " +
"specified with `source`."),
},
"destination": {
IsRequired: true,
Expr: schema.LiteralTypeOnly(cty.String),
Description: lang.Markdown("The destination path. It must be specified as an absolute path."),
},
},
}

var LocalExecProvisioner = &schema.BodySchema{
Description: lang.Markdown("Invokes a local executable after a resource is created. " +
"This invokes a process on the machine running Terraform, not on the resource.\n\n" +
"[`local-exec` provisioner](https://www.terraform.io/docs/language/resources/provisioners/local-exec.html)"),
Attributes: map[string]*schema.AttributeSchema{
"command": {
IsRequired: true,
Expr: schema.LiteralTypeOnly(cty.String),
Description: lang.Markdown("This is the command to execute. It can be provided as a relative path " +
"to the current working directory or as an absolute path. It is evaluated in a shell, " +
"and can use environment variables or Terraform variables."),
},
"interpreter": {
IsOptional: true,
Expr: schema.ExprConstraints{
schema.ListExpr{
Elem: schema.LiteralTypeOnly(cty.String),
},
},
Description: lang.Markdown("If provided, this is a list of interpreter arguments used to execute " +
"the command. The first argument is the interpreter itself. It can be provided as a relative " +
"path to the current working directory or as an absolute path. The remaining arguments are " +
"appended prior to the command. This allows building command lines of the form " +
"`\"/bin/bash\", \"-c\", \"echo foo\"`. If interpreter is unspecified, sensible defaults " +
"will be chosen based on the system OS."),
},
"working_dir": {
IsOptional: true,
Expr: schema.LiteralTypeOnly(cty.String),
Description: lang.Markdown("If provided, specifies the working directory where command will be executed. " +
"It can be provided as as a relative path to the current working directory or as an absolute path. " +
"The directory must exist."),
},
"environment": {
IsOptional: true,
Expr: schema.ExprConstraints{
schema.MapExpr{
Elem: schema.LiteralTypeOnly(cty.String),
},
},
Description: lang.Markdown("Map of key value pairs representing the environment of the executed command. " +
"Inherits the current process environment."),
},
},
}

var RemoteExecProvisioner = &schema.BodySchema{
Description: lang.Markdown("Invokes a script on a remote resource after it is created. " +
"This can be used to run a configuration management tool, bootstrap into a cluster, etc.\n\n" +
"[`remote-exec` provisioner](https://www.terraform.io/docs/language/resources/provisioners/remote-exec.html)"),
Attributes: map[string]*schema.AttributeSchema{
"inline": {
IsOptional: true,
Expr: schema.ExprConstraints{
schema.ListExpr{
Elem: schema.LiteralTypeOnly(cty.String),
},
},
Description: lang.Markdown("A list of command strings. They are executed in the order they are provided." +
" This cannot be provided with `script` or `scripts`."),
},
"script": {
IsOptional: true,
Expr: schema.LiteralTypeOnly(cty.String),
Description: lang.Markdown("A path (relative or absolute) to a local script that will be copied " +
"to the remote resource and then executed. This cannot be provided with `inline` or `scripts`."),
},
"scripts": {
IsOptional: true,
Expr: schema.ExprConstraints{
schema.ListExpr{
Elem: schema.LiteralTypeOnly(cty.String),
},
},
Description: lang.Markdown("A list of paths (relative or absolute) to local scripts that will be copied " +
"to the remote resource and then executed. They are executed in the order they are provided." +
" This cannot be provided with `inline` or `script`."),
},
},
}
54 changes: 3 additions & 51 deletions internal/schema/0.12/resource_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ func resourceBlockSchema(v *version.Version) *schema.BlockSchema {
},
},
Blocks: map[string]*schema.BlockSchema{
"lifecycle": lifecycleBlock,
"connection": connectionBlock,
"lifecycle": lifecycleBlock,
"connection": connectionBlock,
"provisioner": provisionerBlock(v),
},
},
}
Expand Down Expand Up @@ -123,55 +124,6 @@ var lifecycleBlock = &schema.BlockSchema{
},
}

var provisionerBlock = &schema.BlockSchema{
Description: lang.Markdown("Provisioner to model specific actions on the local machine or on a remote machine " +
"in order to prepare servers or other infrastructure objects for service"),
Labels: []*schema.LabelSchema{
{
Name: "type",
Description: lang.PlainText("Type of provisioner to use, e.g. `remote-exec` or `file`"),
IsDepKey: true,
},
},
Body: &schema.BodySchema{
Attributes: map[string]*schema.AttributeSchema{
"when": {
Expr: schema.ExprConstraints{
schema.KeywordExpr{
Keyword: "create",
Description: lang.Markdown("Run the provisioner when the resource is created"),
},
schema.KeywordExpr{
Keyword: "destroy",
Description: lang.Markdown("Run the provisioner when the resource is destroyed"),
},
},
IsOptional: true,
Description: lang.Markdown("When to run the provisioner - `create` or `destroy`, defaults to `create` " +
"(i.e. after creation of the resource)"),
},
"on_failure": {
IsOptional: true,
Expr: schema.ExprConstraints{
schema.KeywordExpr{
Keyword: "fail",
Description: lang.Markdown("Raise an error and stop applying (the default behavior). If this is a creation provisioner, taint the resource."),
},
schema.KeywordExpr{
Keyword: "continue",
Description: lang.Markdown("Ignore the error and continue with creation or destruction"),
},
},
Description: lang.Markdown("What to do when the provisioner run fails to finish - `fail` (default), " +
"or `continue` (ignore the error)"),
},
},
Blocks: map[string]*schema.BlockSchema{
"connection": connectionBlock,
},
},
}

var connectionBlock = &schema.BlockSchema{
Description: lang.Markdown("Connection block describing how the provisioner connects to the given instance"),
MaxItems: 1,
Expand Down
1 change: 1 addition & 0 deletions internal/schema/0.12/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
)

var (
v0_12_2 = version.Must(version.NewVersion("0.12.2"))
v0_12_6 = version.Must(version.NewVersion("0.12.6"))
v0_12_18 = version.Must(version.NewVersion("0.12.18"))
v0_12_20 = version.Must(version.NewVersion("0.12.20"))
Expand Down
48 changes: 48 additions & 0 deletions internal/schema/0.13/provisioners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package schema

import (
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/schema"

v012_mod "github.com/hashicorp/terraform-schema/internal/schema/0.12"
)

// See https://github.com/hashicorp/terraform/blob/v0.13.0/command/internal_plugin_list.go

var (
FileProvisioner = v012_mod.FileProvisioner
LocalExecProvisioner = v012_mod.LocalExecProvisioner
RemoteExecProvisioner = v012_mod.RemoteExecProvisioner
)

func ProvisionerBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema {
m := map[schema.SchemaKey]*schema.BodySchema{
labelKey("file"): FileProvisioner,
labelKey("local-exec"): LocalExecProvisioner,
labelKey("remote-exec"): RemoteExecProvisioner,
}

// Vendor provisioners are deprecated in 0.13.4+
// See https://discuss.hashicorp.com/t/notice-terraform-to-begin-deprecation-of-vendor-tool-specific-provisioners-starting-in-terraform-0-13-4/13997
// Some of these provisioners have complex schemas
// but we can at least helpfully list their names
if v.GreaterThanOrEqual(v0_13_4) {
m[labelKey("chef")] = &schema.BodySchema{IsDeprecated: true}
m[labelKey("salt-masterless")] = &schema.BodySchema{IsDeprecated: true}
m[labelKey("habitat")] = &schema.BodySchema{IsDeprecated: true}
m[labelKey("puppet")] = &schema.BodySchema{IsDeprecated: true}
} else {
m[labelKey("chef")] = &schema.BodySchema{}
m[labelKey("salt-masterless")] = &schema.BodySchema{}
m[labelKey("habitat")] = &schema.BodySchema{}
m[labelKey("puppet")] = &schema.BodySchema{}
}

return m
}

func labelKey(value string) schema.SchemaKey {
return schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{{Index: 0, Value: value}},
})
}
3 changes: 3 additions & 0 deletions internal/schema/0.13/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import (
v012_mod "github.com/hashicorp/terraform-schema/internal/schema/0.12"
)

var v0_13_4 = version.Must(version.NewVersion("0.13.4"))

func ModuleSchema(v *version.Version) *schema.BodySchema {
bs := v012_mod.ModuleSchema(v)

bs.Blocks["module"] = moduleBlockSchema
bs.Blocks["provider"] = providerBlockSchema
bs.Blocks["terraform"] = terraformBlockSchema
bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerBodies(v)

return bs
}
39 changes: 39 additions & 0 deletions internal/schema/0.14/provisioners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package schema

import (
"github.com/hashicorp/go-version"
"github.com/hashicorp/hcl-lang/schema"

v013_mod "github.com/hashicorp/terraform-schema/internal/schema/0.13"
)

// See https://github.com/hashicorp/terraform/blob/v0.14.0/command/internal_plugin_list.go

var (
FileProvisioner = v013_mod.FileProvisioner
LocalExecProvisioner = v013_mod.LocalExecProvisioner
RemoteExecProvisioner = v013_mod.RemoteExecProvisioner
)

func ProvisionerBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema {
return map[schema.SchemaKey]*schema.BodySchema{
labelKey("file"): FileProvisioner,
labelKey("local-exec"): LocalExecProvisioner,
labelKey("remote-exec"): RemoteExecProvisioner,

// Vendor provisioners are deprecated in 0.13.4+
// See https://discuss.hashicorp.com/t/notice-terraform-to-begin-deprecation-of-vendor-tool-specific-provisioners-starting-in-terraform-0-13-4/13997
// Some of these provisioners have complex schemas
// but we can at least helpfully list their names
labelKey("chef"): {IsDeprecated: true},
labelKey("salt-masterless"): {IsDeprecated: true},
labelKey("habitat"): {IsDeprecated: true},
labelKey("puppet"): {IsDeprecated: true},
}
}

func labelKey(value string) schema.SchemaKey {
return schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{{Index: 0, Value: value}},
})
}
3 changes: 3 additions & 0 deletions internal/schema/0.14/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import (

func ModuleSchema(v *version.Version) *schema.BodySchema {
bs := v013_mod.ModuleSchema(v)

bs.Blocks["variable"] = variableBlockSchema
bs.Blocks["terraform"] = terraformBlockSchema(v)
bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerBodies(v)

return bs
}
Loading

0 comments on commit 5db493d

Please sign in to comment.