mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
checks: always reference the nested data source from the check graph node (#32946)
* checks: always reference the nested data source from the check block graph node * goimports
This commit is contained in:
parent
c960b16e87
commit
84dc498b90
@ -30,8 +30,10 @@ func TestContextChecks(t *testing.T) {
|
|||||||
configs map[string]string
|
configs map[string]string
|
||||||
plan map[string]checksTestingStatus
|
plan map[string]checksTestingStatus
|
||||||
planError string
|
planError string
|
||||||
|
planWarning string
|
||||||
apply map[string]checksTestingStatus
|
apply map[string]checksTestingStatus
|
||||||
applyError string
|
applyError string
|
||||||
|
applyWarning string
|
||||||
state *states.State
|
state *states.State
|
||||||
provider *MockProvider
|
provider *MockProvider
|
||||||
providerHook func(*MockProvider)
|
providerHook func(*MockProvider)
|
||||||
@ -107,12 +109,14 @@ check "failing" {
|
|||||||
messages: []string{"negative number"},
|
messages: []string{"negative number"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
planWarning: "Check block assertion failed: negative number",
|
||||||
apply: map[string]checksTestingStatus{
|
apply: map[string]checksTestingStatus{
|
||||||
"failing": {
|
"failing": {
|
||||||
status: checks.StatusFail,
|
status: checks.StatusFail,
|
||||||
messages: []string{"negative number"},
|
messages: []string{"negative number"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
applyWarning: "Check block assertion failed: negative number",
|
||||||
provider: &MockProvider{
|
provider: &MockProvider{
|
||||||
Meta: "checks",
|
Meta: "checks",
|
||||||
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
||||||
@ -164,12 +168,14 @@ check "failing" {
|
|||||||
messages: []string{"positive number"},
|
messages: []string{"positive number"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
planWarning: "Check block assertion failed: positive number",
|
||||||
apply: map[string]checksTestingStatus{
|
apply: map[string]checksTestingStatus{
|
||||||
"failing": {
|
"failing": {
|
||||||
status: checks.StatusFail,
|
status: checks.StatusFail,
|
||||||
messages: []string{"positive number"},
|
messages: []string{"positive number"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
applyWarning: "Check block assertion failed: positive number",
|
||||||
provider: &MockProvider{
|
provider: &MockProvider{
|
||||||
Meta: "checks",
|
Meta: "checks",
|
||||||
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
||||||
@ -229,6 +235,7 @@ check "nested_data_block" {
|
|||||||
messages: []string{"negative number"},
|
messages: []string{"negative number"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
planWarning: "2 warnings:\n\n- Check block assertion failed: negative number\n- Check block assertion failed: negative number",
|
||||||
apply: map[string]checksTestingStatus{
|
apply: map[string]checksTestingStatus{
|
||||||
"nested_data_block": {
|
"nested_data_block": {
|
||||||
status: checks.StatusPass,
|
status: checks.StatusPass,
|
||||||
@ -238,6 +245,7 @@ check "nested_data_block" {
|
|||||||
messages: []string{"negative number"},
|
messages: []string{"negative number"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
applyWarning: "Check block assertion failed: negative number",
|
||||||
provider: &MockProvider{
|
provider: &MockProvider{
|
||||||
Meta: "checks",
|
Meta: "checks",
|
||||||
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
||||||
@ -300,6 +308,7 @@ check "resource_block" {
|
|||||||
status: checks.StatusUnknown,
|
status: checks.StatusUnknown,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
planWarning: "Check block assertion known after apply: The condition could not be evaluated at this time, a result will be known when this plan is applied.",
|
||||||
apply: map[string]checksTestingStatus{
|
apply: map[string]checksTestingStatus{
|
||||||
"resource_block": {
|
"resource_block": {
|
||||||
status: checks.StatusPass,
|
status: checks.StatusPass,
|
||||||
@ -393,6 +402,7 @@ check "error" {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
planWarning: "data source read failed: something bad happened and the provider couldn't read the data source",
|
||||||
apply: map[string]checksTestingStatus{
|
apply: map[string]checksTestingStatus{
|
||||||
"error": {
|
"error": {
|
||||||
status: checks.StatusFail,
|
status: checks.StatusFail,
|
||||||
@ -401,6 +411,7 @@ check "error" {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
applyWarning: "data source read failed: something bad happened and the provider couldn't read the data source",
|
||||||
provider: &MockProvider{
|
provider: &MockProvider{
|
||||||
Meta: "checks",
|
Meta: "checks",
|
||||||
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
||||||
@ -423,6 +434,107 @@ check "error" {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}, "failing nested data source should prevent checks from executing": {
|
||||||
|
configs: map[string]string{
|
||||||
|
"main.tf": `
|
||||||
|
provider "checks" {}
|
||||||
|
|
||||||
|
resource "checks_object" "resource_block" {
|
||||||
|
number = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
check "error" {
|
||||||
|
data "checks_object" "data_block" {}
|
||||||
|
|
||||||
|
assert {
|
||||||
|
condition = checks_object.resource_block.number >= 0
|
||||||
|
error_message = "negative number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
state: states.BuildState(func(state *states.SyncState) {
|
||||||
|
state.SetResourceInstanceCurrent(
|
||||||
|
addrs.Resource{
|
||||||
|
Mode: addrs.ManagedResourceMode,
|
||||||
|
Type: "checks_object",
|
||||||
|
Name: "resource_block",
|
||||||
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
||||||
|
&states.ResourceInstanceObjectSrc{
|
||||||
|
Status: states.ObjectReady,
|
||||||
|
AttrsJSON: []byte(`{"number": -1}`),
|
||||||
|
},
|
||||||
|
addrs.AbsProviderConfig{
|
||||||
|
Provider: addrs.NewDefaultProvider("test"),
|
||||||
|
Module: addrs.RootModule,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
plan: map[string]checksTestingStatus{
|
||||||
|
"error": {
|
||||||
|
status: checks.StatusFail,
|
||||||
|
messages: []string{
|
||||||
|
"data source read failed: something bad happened and the provider couldn't read the data source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
planWarning: "data source read failed: something bad happened and the provider couldn't read the data source",
|
||||||
|
apply: map[string]checksTestingStatus{
|
||||||
|
"error": {
|
||||||
|
status: checks.StatusFail,
|
||||||
|
messages: []string{
|
||||||
|
"data source read failed: something bad happened and the provider couldn't read the data source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
applyWarning: "data source read failed: something bad happened and the provider couldn't read the data source",
|
||||||
|
provider: &MockProvider{
|
||||||
|
Meta: "checks",
|
||||||
|
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
|
||||||
|
ResourceTypes: map[string]providers.Schema{
|
||||||
|
"checks_object": {
|
||||||
|
Block: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"number": {
|
||||||
|
Type: cty.Number,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DataSources: map[string]providers.Schema{
|
||||||
|
"checks_object": {
|
||||||
|
Block: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"number": {
|
||||||
|
Type: cty.Number,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PlanResourceChangeFn: func(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||||
|
return providers.PlanResourceChangeResponse{
|
||||||
|
PlannedState: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"number": cty.NumberIntVal(-1),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ApplyResourceChangeFn: func(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
|
||||||
|
return providers.ApplyResourceChangeResponse{
|
||||||
|
NewState: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"number": cty.NumberIntVal(-1),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ReadDataSourceFn: func(request providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||||
|
return providers.ReadDataSourceResponse{
|
||||||
|
Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "data source read failed", "something bad happened and the provider couldn't read the data source")},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"check failing in state and passing after plan and apply": {
|
"check failing in state and passing after plan and apply": {
|
||||||
configs: map[string]string{
|
configs: map[string]string{
|
||||||
@ -609,7 +721,7 @@ check "error" {
|
|||||||
plan, diags := ctx.Plan(configs, initialState, &PlanOpts{
|
plan, diags := ctx.Plan(configs, initialState, &PlanOpts{
|
||||||
Mode: plans.NormalMode,
|
Mode: plans.NormalMode,
|
||||||
})
|
})
|
||||||
if validateError(t, "planning", test.planError, diags) {
|
if validateCheckDiagnostics(t, "planning", test.planWarning, test.planError, diags) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
validateCheckResults(t, "planning", test.plan, plan.Checks)
|
validateCheckResults(t, "planning", test.plan, plan.Checks)
|
||||||
@ -621,7 +733,7 @@ check "error" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state, diags := ctx.Apply(plan, configs)
|
state, diags := ctx.Apply(plan, configs)
|
||||||
if validateError(t, "apply", test.applyError, diags) {
|
if validateCheckDiagnostics(t, "apply", test.applyWarning, test.applyError, diags) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
validateCheckResults(t, "apply", test.apply, state.CheckResults)
|
validateCheckResults(t, "apply", test.apply, state.CheckResults)
|
||||||
@ -629,16 +741,31 @@ check "error" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateError(t *testing.T, stage string, expected string, actual tfdiags.Diagnostics) bool {
|
func validateCheckDiagnostics(t *testing.T, stage string, expectedWarning, expectedError string, actual tfdiags.Diagnostics) bool {
|
||||||
if expected != "" {
|
if expectedError != "" {
|
||||||
if !actual.HasErrors() {
|
if !actual.HasErrors() {
|
||||||
t.Errorf("expected %s to error with \"%s\", but no errors were returned", stage, expected)
|
t.Errorf("expected %s to error with \"%s\", but no errors were returned", stage, expectedError)
|
||||||
} else if expected != actual.Err().Error() {
|
} else if expectedError != actual.Err().Error() {
|
||||||
t.Errorf("expected %s to error with \"%s\" but found \"%s\"", stage, expected, actual.Err())
|
t.Errorf("expected %s to error with \"%s\" but found \"%s\"", stage, expectedError, actual.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we expected an error then we won't finish the rest of the test.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if expectedWarning != "" {
|
||||||
|
warnings := actual.ErrWithWarnings()
|
||||||
|
if actual.ErrWithWarnings() == nil {
|
||||||
|
t.Errorf("expected %s to warn with \"%s\", but no errors were returned", stage, expectedWarning)
|
||||||
|
} else if expectedWarning != warnings.Error() {
|
||||||
|
t.Errorf("expected %s to warn with \"%s\" but found \"%s\"", stage, expectedWarning, warnings)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if actual.ErrWithWarnings() != nil {
|
||||||
|
t.Errorf("expected %s to produce no diagnostics but found \"%s\"", stage, actual.ErrWithWarnings())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assertNoErrors(t, actual)
|
assertNoErrors(t, actual)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package terraform
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/checks"
|
"github.com/hashicorp/terraform/internal/checks"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
@ -92,11 +94,27 @@ func (n *nodeExpandCheck) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|||||||
func (n *nodeExpandCheck) References() []*addrs.Reference {
|
func (n *nodeExpandCheck) References() []*addrs.Reference {
|
||||||
var refs []*addrs.Reference
|
var refs []*addrs.Reference
|
||||||
for _, assert := range n.config.Asserts {
|
for _, assert := range n.config.Asserts {
|
||||||
|
// Check blocks reference anything referenced by conditions or messages
|
||||||
|
// in their check rules.
|
||||||
condition, _ := lang.ReferencesInExpr(assert.Condition)
|
condition, _ := lang.ReferencesInExpr(assert.Condition)
|
||||||
message, _ := lang.ReferencesInExpr(assert.ErrorMessage)
|
message, _ := lang.ReferencesInExpr(assert.ErrorMessage)
|
||||||
refs = append(refs, condition...)
|
refs = append(refs, condition...)
|
||||||
refs = append(refs, message...)
|
refs = append(refs, message...)
|
||||||
}
|
}
|
||||||
|
if n.config.DataResource != nil {
|
||||||
|
// We'll also always reference our nested data block if it exists, as
|
||||||
|
// there is nothing enforcing that it has to also be referenced by our
|
||||||
|
// conditions or messages.
|
||||||
|
//
|
||||||
|
// We don't need to make this addr absolute, because the check block and
|
||||||
|
// the data resource are always within the same module/instance.
|
||||||
|
traversal, _ := hclsyntax.ParseTraversalAbs(
|
||||||
|
[]byte(n.config.DataResource.Addr().String()),
|
||||||
|
n.config.DataResource.DeclRange.Filename,
|
||||||
|
n.config.DataResource.DeclRange.Start)
|
||||||
|
ref, _ := addrs.ParseRef(traversal)
|
||||||
|
refs = append(refs, ref)
|
||||||
|
}
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user