github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/eval_conditions.go (about) 1 package durgaform 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/hcl/v2" 9 "github.com/zclconf/go-cty/cty" 10 "github.com/zclconf/go-cty/cty/convert" 11 12 "github.com/eliastor/durgaform/internal/addrs" 13 "github.com/eliastor/durgaform/internal/configs" 14 "github.com/eliastor/durgaform/internal/instances" 15 "github.com/eliastor/durgaform/internal/lang" 16 "github.com/eliastor/durgaform/internal/lang/marks" 17 "github.com/eliastor/durgaform/internal/plans" 18 "github.com/eliastor/durgaform/internal/tfdiags" 19 ) 20 21 // evalCheckRules ensures that all of the given check rules pass against 22 // the given HCL evaluation context. 23 // 24 // If any check rules produce an unknown result then they will be silently 25 // ignored on the assumption that the same checks will be run again later 26 // with fewer unknown values in the EvalContext. 27 // 28 // If any of the rules do not pass, the returned diagnostics will contain 29 // errors. Otherwise, it will either be empty or contain only warnings. 30 func evalCheckRules(typ addrs.CheckType, rules []*configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData, diagSeverity tfdiags.Severity) tfdiags.Diagnostics { 31 var diags tfdiags.Diagnostics 32 33 if len(rules) == 0 { 34 // Nothing to do 35 return nil 36 } 37 38 severity := diagSeverity.ToHCL() 39 40 for i, rule := range rules { 41 checkAddr := self.Check(typ, i) 42 43 conditionResult, ruleDiags := evalCheckRule(typ, rule, ctx, self, keyData, severity) 44 diags = diags.Append(ruleDiags) 45 ctx.Conditions().SetResult(checkAddr, conditionResult) 46 } 47 48 return diags 49 } 50 51 func evalCheckRule(typ addrs.CheckType, rule *configs.CheckRule, ctx EvalContext, self addrs.Checkable, keyData instances.RepetitionData, severity hcl.DiagnosticSeverity) (*plans.ConditionResult, tfdiags.Diagnostics) { 52 var diags tfdiags.Diagnostics 53 const errInvalidCondition = "Invalid condition result" 54 55 refs, moreDiags := lang.ReferencesInExpr(rule.Condition) 56 diags = diags.Append(moreDiags) 57 moreRefs, moreDiags := lang.ReferencesInExpr(rule.ErrorMessage) 58 diags = diags.Append(moreDiags) 59 refs = append(refs, moreRefs...) 60 61 conditionResult := &plans.ConditionResult{ 62 Address: self, 63 Result: cty.UnknownVal(cty.Bool), 64 Type: typ, 65 } 66 67 var selfReference addrs.Referenceable 68 // Only resource postconditions can refer to self 69 if typ == addrs.ResourcePostcondition { 70 switch s := self.(type) { 71 case addrs.AbsResourceInstance: 72 selfReference = s.Resource 73 default: 74 panic(fmt.Sprintf("Invalid self reference type %t", self)) 75 } 76 } 77 scope := ctx.EvaluationScope(selfReference, keyData) 78 79 hclCtx, moreDiags := scope.EvalContext(refs) 80 diags = diags.Append(moreDiags) 81 82 result, hclDiags := rule.Condition.Value(hclCtx) 83 diags = diags.Append(hclDiags) 84 85 errorValue, errorDiags := rule.ErrorMessage.Value(hclCtx) 86 diags = diags.Append(errorDiags) 87 88 if diags.HasErrors() { 89 log.Printf("[TRACE] evalCheckRule: %s: %s", typ, diags.Err().Error()) 90 } 91 92 if !result.IsKnown() { 93 // We'll wait until we've learned more, then. 94 return conditionResult, diags 95 } 96 97 if result.IsNull() { 98 diags = diags.Append(&hcl.Diagnostic{ 99 Severity: severity, 100 Summary: errInvalidCondition, 101 Detail: "Condition expression must return either true or false, not null.", 102 Subject: rule.Condition.Range().Ptr(), 103 Expression: rule.Condition, 104 EvalContext: hclCtx, 105 }) 106 conditionResult.Result = cty.False 107 conditionResult.ErrorMessage = "Condition expression must return either true or false, not null." 108 return conditionResult, diags 109 } 110 var err error 111 result, err = convert.Convert(result, cty.Bool) 112 if err != nil { 113 detail := fmt.Sprintf("Invalid condition result value: %s.", tfdiags.FormatError(err)) 114 diags = diags.Append(&hcl.Diagnostic{ 115 Severity: severity, 116 Summary: errInvalidCondition, 117 Detail: detail, 118 Subject: rule.Condition.Range().Ptr(), 119 Expression: rule.Condition, 120 EvalContext: hclCtx, 121 }) 122 conditionResult.Result = cty.False 123 conditionResult.ErrorMessage = detail 124 return conditionResult, diags 125 } 126 127 // The condition result may be marked if the expression refers to a 128 // sensitive value. 129 result, _ = result.Unmark() 130 conditionResult.Result = result 131 132 if result.True() { 133 return conditionResult, diags 134 } 135 136 var errorMessage string 137 if !errorDiags.HasErrors() && errorValue.IsKnown() && !errorValue.IsNull() { 138 var err error 139 errorValue, err = convert.Convert(errorValue, cty.String) 140 if err != nil { 141 diags = diags.Append(&hcl.Diagnostic{ 142 Severity: severity, 143 Summary: "Invalid error message", 144 Detail: fmt.Sprintf("Unsuitable value for error message: %s.", tfdiags.FormatError(err)), 145 Subject: rule.ErrorMessage.Range().Ptr(), 146 Expression: rule.ErrorMessage, 147 EvalContext: hclCtx, 148 }) 149 } else { 150 if marks.Has(errorValue, marks.Sensitive) { 151 diags = diags.Append(&hcl.Diagnostic{ 152 Severity: severity, 153 154 Summary: "Error message refers to sensitive values", 155 Detail: `The error expression used to explain this condition refers to sensitive values. Durgaform will not display the resulting message. 156 157 You can correct this by removing references to sensitive values, or by carefully using the nonsensitive() function if the expression will not reveal the sensitive data.`, 158 159 Subject: rule.ErrorMessage.Range().Ptr(), 160 Expression: rule.ErrorMessage, 161 EvalContext: hclCtx, 162 }) 163 errorMessage = "The error message included a sensitive value, so it will not be displayed." 164 } else { 165 errorMessage = strings.TrimSpace(errorValue.AsString()) 166 } 167 } 168 } 169 if errorMessage == "" { 170 errorMessage = "Failed to evaluate condition error message." 171 } 172 diags = diags.Append(&hcl.Diagnostic{ 173 Severity: severity, 174 Summary: fmt.Sprintf("%s failed", typ.Description()), 175 Detail: errorMessage, 176 Subject: rule.Condition.Range().Ptr(), 177 Expression: rule.Condition, 178 EvalContext: hclCtx, 179 }) 180 conditionResult.ErrorMessage = errorMessage 181 return conditionResult, diags 182 }