mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-22 23:23:35 -06:00
Added verify command
This commit is contained in:
parent
819db3469e
commit
db69a2959b
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,3 +21,4 @@ website/node_modules
|
||||
.*.swp
|
||||
.idea
|
||||
*.test
|
||||
*.iml
|
||||
|
@ -0,0 +1,6 @@
|
||||
module "super#module" {
|
||||
}
|
||||
|
||||
module "super" {
|
||||
source = "${var.modulename}"
|
||||
}
|
11
command/test-fixtures/validate-invalid/interpolation/main.tf
Normal file
11
command/test-fixtures/validate-invalid/interpolation/main.tf
Normal file
@ -0,0 +1,11 @@
|
||||
variable "otherresourcename" {
|
||||
default = "aws_instance.web1"
|
||||
}
|
||||
|
||||
variable "vairable_with_interpolation" {
|
||||
default = "${var.otherresourcename}"
|
||||
}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
depends_on = ["${var.otherresourcename}}"]
|
||||
}
|
8
command/test-fixtures/validate-invalid/main.tf
Normal file
8
command/test-fixtures/validate-invalid/main.tf
Normal file
@ -0,0 +1,8 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
|
||||
network_interface {
|
||||
device_index = 0
|
||||
description = "Main network interface ${var.this_is_an_error}"
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
|
||||
network_interface {
|
||||
device_index = 0
|
||||
name = test
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
|
||||
network_interface {
|
||||
device_index = 0
|
||||
description = "${var.description}"
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
module "multi_module" {
|
||||
}
|
||||
|
||||
module "multi_module" {
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
provider "aws" {
|
||||
access_key = "123"
|
||||
secret_key = "233"
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
access_key = "123"
|
||||
secret_key = "233"
|
||||
region = "us-east-1"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
resource "aws_instance" "web" {
|
||||
}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
}
|
3
command/test-fixtures/validate-invalid/outputs/main.tf
Normal file
3
command/test-fixtures/validate-invalid/outputs/main.tf
Normal file
@ -0,0 +1,3 @@
|
||||
output "myvalue" {
|
||||
values = "Some value"
|
||||
}
|
9
command/test-fixtures/validate-valid/main.tf
Normal file
9
command/test-fixtures/validate-valid/main.tf
Normal file
@ -0,0 +1,9 @@
|
||||
resource "test_instance" "foo" {
|
||||
ami = "bar"
|
||||
|
||||
# This is here because at some point it caused a test failure
|
||||
network_interface {
|
||||
device_index = 0
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
58
command/validate.go
Normal file
58
command/validate.go
Normal file
@ -0,0 +1,58 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// ValidateCommand is a Command implementation that validates the terraform files
|
||||
type ValidateCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
const defaultPath = "."
|
||||
|
||||
func (c *ValidateCommand) Help() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *ValidateCommand) Run(args []string) int {
|
||||
args = c.Meta.process(args, false)
|
||||
var dirPath string
|
||||
|
||||
if len(args) == 1 {
|
||||
dirPath = args[0]
|
||||
} else {
|
||||
dirPath = "."
|
||||
}
|
||||
dir, err := filepath.Abs(dirPath)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Unable to locate directory %v\n", err.Error()))
|
||||
}
|
||||
|
||||
rtnCode := c.validate(dir)
|
||||
|
||||
return rtnCode
|
||||
}
|
||||
|
||||
func (c *ValidateCommand) Synopsis() string {
|
||||
return "Validates the Terraform files"
|
||||
}
|
||||
|
||||
func (c *ValidateCommand) validate(dir string) int {
|
||||
cfg, err := config.LoadDir(dir)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error loading files %v\n", err.Error()))
|
||||
return 1
|
||||
}
|
||||
err = cfg.Validate()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error validating: %v\n", err.Error()))
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
123
command/validate_test.go
Normal file
123
command/validate_test.go
Normal file
@ -0,0 +1,123 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/cli"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setupTest(fixturepath string) (*cli.MockUi, int) {
|
||||
ui := new(cli.MockUi)
|
||||
c := &ValidateCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
testFixturePath(fixturepath),
|
||||
}
|
||||
|
||||
code := c.Run(args)
|
||||
return ui, code
|
||||
}
|
||||
func TestValidateCommand(t *testing.T) {
|
||||
if ui, code := setupTest("validate-valid"); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFailingCommand(t *testing.T) {
|
||||
if ui, code := setupTest("validate-invalid"); code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFailingCommandMissingQuote(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/missing_quote")
|
||||
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "IDENT test") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFailingCommandMissingVariable(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/missing_var")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "config: unknown variable referenced: 'description'. define it with 'variable' blocks") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/multiple_providers")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "provider.aws: declared multiple times, you can only declare a provider once") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/multiple_modules")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "multi_module: module repeated multiple times") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameResourceMultipleTimesShouldFail(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/multiple_resources")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "aws_instance.web: resource repeated multiple times") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputWithoutValueShouldFail(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/outputs")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "output is missing required 'value' key") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/incorrectmodulename")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(ui.ErrorWriter.String(), "module name can only contain letters, numbers, dashes, and underscores") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.Contains(ui.ErrorWriter.String(), "module source cannot contain interpolations") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
|
||||
ui, code := setupTest("validate-invalid/interpolation")
|
||||
if code != 1 {
|
||||
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if !strings.Contains(ui.ErrorWriter.String(), "depends on value cannot contain interpolations") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
if !strings.Contains(ui.ErrorWriter.String(), "Variable 'vairable_with_interpolation': cannot contain interpolations") {
|
||||
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
|
||||
}
|
||||
}
|
@ -110,6 +110,12 @@ func init() {
|
||||
}, nil
|
||||
},
|
||||
|
||||
"validate": func() (cli.Command, error) {
|
||||
return &command.ValidateCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"version": func() (cli.Command, error) {
|
||||
return &command.VersionCommand{
|
||||
Meta: meta,
|
||||
|
@ -35,6 +35,7 @@ Available commands are:
|
||||
remote Configure remote state storage
|
||||
show Inspect Terraform state or plan
|
||||
taint Manually mark a resource for recreation
|
||||
validate Validates the Terraform files
|
||||
version Prints the Terraform version
|
||||
```
|
||||
|
||||
|
28
website/source/docs/commands/validate.html.markdown
Normal file
28
website/source/docs/commands/validate.html.markdown
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
layout: "docs"
|
||||
page_title: "Command: validate"
|
||||
sidebar_current: "docs-commands-validate"
|
||||
description: |-
|
||||
The `terraform validate` command is used to validate the format and structure of the terraform files.
|
||||
---
|
||||
|
||||
# Command: verify
|
||||
|
||||
The `terraform validate` command is used to validate the syntax of the terraform files.
|
||||
Terraform performs a syntax check on all the terraform files in the directory, and will display an error if the file(s)
|
||||
doesn't validate.
|
||||
|
||||
These errors include:
|
||||
|
||||
* Interpolation in variable values, depends_on, module source etc.
|
||||
|
||||
* Duplicate names in resource, modules and providers.
|
||||
|
||||
* Missing variable values.
|
||||
|
||||
## Usage
|
||||
|
||||
Usage: `terraform validate [dir]`
|
||||
|
||||
By default, `validate` requires no flags and looks in the current directory
|
||||
for the configurations.
|
@ -106,6 +106,11 @@
|
||||
<li<%= sidebar_current("docs-commands-taint") %>>
|
||||
<a href="/docs/commands/taint.html">taint</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-commands-validate") %>>
|
||||
<a href="/docs/commands/validate.html">validate</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user