@ -4,6 +4,8 @@ import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang"
)
@ -139,3 +141,117 @@ var checkRuleBlockSchema = &hcl.BodySchema{
} ,
} ,
}
// Check represents a configuration defined check block.
//
// A check block contains 0-1 data blocks, and 0-n assert blocks. The check
// block will load the data block, and execute the assert blocks as check rules
// during the plan and apply Terraform operations.
type Check struct {
Name string
DataResource * Resource
Asserts [ ] * CheckRule
DeclRange hcl . Range
}
func ( c Check ) Addr ( ) addrs . Check {
return addrs . Check {
Name : c . Name ,
}
}
func ( c Check ) Accessible ( addr addrs . Referenceable ) bool {
if check , ok := addr . ( addrs . Check ) ; ok {
return check . Equal ( c . Addr ( ) )
}
return false
}
func decodeCheckBlock ( block * hcl . Block , override bool ) ( * Check , hcl . Diagnostics ) {
var diags hcl . Diagnostics
check := & Check {
Name : block . Labels [ 0 ] ,
DeclRange : block . DefRange ,
}
if override {
// For now we'll just forbid overriding check blocks, to simplify
// the initial design. If we can find a clear use-case for overriding
// checks in override files and there's a way to define it that
// isn't confusing then we could relax this.
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Can't override check blocks" ,
Detail : "Override files cannot override check blocks." ,
Subject : check . DeclRange . Ptr ( ) ,
} )
return check , diags
}
content , moreDiags := block . Body . Content ( checkBlockSchema )
diags = append ( diags , moreDiags ... )
if ! hclsyntax . ValidIdentifier ( check . Name ) {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Invalid check block name" ,
Detail : badIdentifierDetail ,
Subject : & block . LabelRanges [ 0 ] ,
} )
}
for _ , block := range content . Blocks {
switch block . Type {
case "data" :
if check . DataResource != nil {
diags = diags . Append ( & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Multiple data resource blocks" ,
Detail : fmt . Sprintf ( "This check block already has a data resource defined at %s." , check . DataResource . DeclRange . Ptr ( ) ) ,
Subject : block . DefRange . Ptr ( ) ,
} )
continue
}
data , moreDiags := decodeDataBlock ( block , override , true )
diags = append ( diags , moreDiags ... )
if ! moreDiags . HasErrors ( ) {
// Connect this data block back up to this check block.
data . Container = check
// Finally, save the data block.
check . DataResource = data
}
case "assert" :
assert , moreDiags := decodeCheckRuleBlock ( block , override )
diags = append ( diags , moreDiags ... )
if ! moreDiags . HasErrors ( ) {
check . Asserts = append ( check . Asserts , assert )
}
default :
panic ( fmt . Sprintf ( "unhandled check nested block %q" , block . Type ) )
}
}
if len ( check . Asserts ) == 0 {
diags = append ( diags , & hcl . Diagnostic {
Severity : hcl . DiagError ,
Summary : "Zero assert blocks" ,
Detail : "Check blocks must have at least one assert block." ,
Subject : check . DeclRange . Ptr ( ) ,
} )
}
return check , diags
}
var checkBlockSchema = & hcl . BodySchema {
Blocks : [ ] hcl . BlockHeaderSchema {
{ Type : "data" , LabelNames : [ ] string { "type" , "name" } } ,
{ Type : "assert" } ,
} ,
}