diff --git a/internal/schema/0.12/provisioner_block.go b/internal/schema/0.12/provisioner_block.go new file mode 100644 index 00000000..5051ae3c --- /dev/null +++ b/internal/schema/0.12/provisioner_block.go @@ -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), + } +} diff --git a/internal/schema/0.12/provisioners.go b/internal/schema/0.12/provisioners.go new file mode 100644 index 00000000..584187e8 --- /dev/null +++ b/internal/schema/0.12/provisioners.go @@ -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`."), + }, + }, +} diff --git a/internal/schema/0.12/resource_block.go b/internal/schema/0.12/resource_block.go index 75f50e4a..99439b5b 100644 --- a/internal/schema/0.12/resource_block.go +++ b/internal/schema/0.12/resource_block.go @@ -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), }, }, } @@ -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, diff --git a/internal/schema/0.12/root.go b/internal/schema/0.12/root.go index a22cf87c..20e7a240 100644 --- a/internal/schema/0.12/root.go +++ b/internal/schema/0.12/root.go @@ -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")) diff --git a/internal/schema/0.13/provisioners.go b/internal/schema/0.13/provisioners.go new file mode 100644 index 00000000..536e35db --- /dev/null +++ b/internal/schema/0.13/provisioners.go @@ -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}}, + }) +} diff --git a/internal/schema/0.13/root.go b/internal/schema/0.13/root.go index 31671ff4..0c5556f5 100644 --- a/internal/schema/0.13/root.go +++ b/internal/schema/0.13/root.go @@ -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 } diff --git a/internal/schema/0.14/provisioners.go b/internal/schema/0.14/provisioners.go new file mode 100644 index 00000000..32361014 --- /dev/null +++ b/internal/schema/0.14/provisioners.go @@ -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}}, + }) +} diff --git a/internal/schema/0.14/root.go b/internal/schema/0.14/root.go index bacfadd0..075f1a9b 100644 --- a/internal/schema/0.14/root.go +++ b/internal/schema/0.14/root.go @@ -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 } diff --git a/internal/schema/0.15/provisioners.go b/internal/schema/0.15/provisioners.go new file mode 100644 index 00000000..ea0865ab --- /dev/null +++ b/internal/schema/0.15/provisioners.go @@ -0,0 +1,24 @@ +package schema + +import ( + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/schema" + + v014_mod "github.com/hashicorp/terraform-schema/internal/schema/0.14" +) + +// See https://github.com/hashicorp/terraform/tree/v0.15.0/builtin/provisioners + +func ProvisionerBodies(v *version.Version) map[schema.SchemaKey]*schema.BodySchema { + return map[schema.SchemaKey]*schema.BodySchema{ + labelKey("file"): v014_mod.FileProvisioner, + labelKey("local-exec"): v014_mod.LocalExecProvisioner, + labelKey("remote-exec"): v014_mod.RemoteExecProvisioner, + } +} + +func labelKey(value string) schema.SchemaKey { + return schema.NewSchemaKey(schema.DependencyKeys{ + Labels: []schema.LabelDependent{{Index: 0, Value: value}}, + }) +} diff --git a/internal/schema/0.15/root.go b/internal/schema/0.15/root.go index ad95143f..a5be54b9 100644 --- a/internal/schema/0.15/root.go +++ b/internal/schema/0.15/root.go @@ -10,5 +10,6 @@ import ( func ModuleSchema(v *version.Version) *schema.BodySchema { bs := v014_mod.ModuleSchema(v) bs.Blocks["terraform"] = patchTerraformBlockSchema(bs.Blocks["terraform"], v) + bs.Blocks["resource"].Body.Blocks["provisioner"].DependentBody = ProvisionerBodies(v) return bs }