github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/plugin/internal/fromproto/fromproto.go (about) 1 package fromproto 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/terraform-linters/tflint-plugin-sdk/hclext" 9 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/proto" 10 "github.com/terraform-linters/tflint-plugin-sdk/terraform/lang/marks" 11 "github.com/terraform-linters/tflint-plugin-sdk/tflint" 12 "github.com/zclconf/go-cty/cty" 13 "github.com/zclconf/go-cty/cty/msgpack" 14 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/status" 16 ) 17 18 // BodySchema converts proto.BodySchema to hclext.BodySchema 19 func BodySchema(body *proto.BodySchema) *hclext.BodySchema { 20 if body == nil { 21 return nil 22 } 23 24 attributes := make([]hclext.AttributeSchema, len(body.Attributes)) 25 for idx, attr := range body.Attributes { 26 attributes[idx] = hclext.AttributeSchema{Name: attr.Name, Required: attr.Required} 27 } 28 29 blocks := make([]hclext.BlockSchema, len(body.Blocks)) 30 for idx, block := range body.Blocks { 31 blocks[idx] = hclext.BlockSchema{ 32 Type: block.Type, 33 LabelNames: block.LabelNames, 34 Body: BodySchema(block.Body), 35 } 36 } 37 38 return &hclext.BodySchema{ 39 Mode: SchemaMode(body.Mode), 40 Attributes: attributes, 41 Blocks: blocks, 42 } 43 } 44 45 // SchemaMode converts proto.SchemaMode to hclext.SchemaMode 46 func SchemaMode(mode proto.SchemaMode) hclext.SchemaMode { 47 switch mode { 48 case proto.SchemaMode_SCHEMA_MODE_UNSPECIFIED: 49 return hclext.SchemaDefaultMode 50 case proto.SchemaMode_SCHEMA_MODE_DEFAULT: 51 return hclext.SchemaDefaultMode 52 case proto.SchemaMode_SCHEMA_MODE_JUST_ATTRIBUTES: 53 return hclext.SchemaJustAttributesMode 54 default: 55 panic(fmt.Sprintf("invalid SchemaMode: %s", mode)) 56 } 57 } 58 59 // BodyContent converts proto.BodyContent to hclext.BodyContent 60 func BodyContent(body *proto.BodyContent) (*hclext.BodyContent, hcl.Diagnostics) { 61 if body == nil { 62 return nil, nil 63 } 64 diags := hcl.Diagnostics{} 65 66 attributes := hclext.Attributes{} 67 for key, attr := range body.Attributes { 68 expr, exprDiags := Expression(attr.Expression) 69 diags = diags.Extend(exprDiags) 70 71 attributes[key] = &hclext.Attribute{ 72 Name: attr.Name, 73 Expr: expr, 74 Range: Range(attr.Range), 75 NameRange: Range(attr.NameRange), 76 } 77 } 78 79 blocks := make(hclext.Blocks, len(body.Blocks)) 80 for idx, block := range body.Blocks { 81 blockBody, contentDiags := BodyContent(block.Body) 82 diags = diags.Extend(contentDiags) 83 84 labelRanges := make([]hcl.Range, len(block.LabelRanges)) 85 for idx, labelRange := range block.LabelRanges { 86 labelRanges[idx] = Range(labelRange) 87 } 88 89 blocks[idx] = &hclext.Block{ 90 Type: block.Type, 91 Labels: block.Labels, 92 Body: blockBody, 93 DefRange: Range(block.DefRange), 94 TypeRange: Range(block.TypeRange), 95 LabelRanges: labelRanges, 96 } 97 } 98 99 return &hclext.BodyContent{ 100 Attributes: attributes, 101 Blocks: blocks, 102 }, diags 103 } 104 105 // RuleObject is an intermediate representation that satisfies the Rule interface. 106 type RuleObject struct { 107 tflint.DefaultRule 108 Data struct { 109 Name string 110 Enabled bool 111 Severity tflint.Severity 112 Link string 113 } 114 } 115 116 // Name returns the rule name 117 func (r *RuleObject) Name() string { return r.Data.Name } 118 119 // Enabled returns whether the rule is enabled 120 func (r *RuleObject) Enabled() bool { return r.Data.Enabled } 121 122 // Severity returns the severify of the rule 123 func (r *RuleObject) Severity() tflint.Severity { return r.Data.Severity } 124 125 // Link returns the link of the rule documentation if exists 126 func (r *RuleObject) Link() string { return r.Data.Link } 127 128 // Check does nothing. This is just a method to satisfy the interface 129 func (r *RuleObject) Check(tflint.Runner) error { return nil } 130 131 // Rule converts proto.EmitIssue_Rule to RuleObject 132 func Rule(rule *proto.EmitIssue_Rule) *RuleObject { 133 if rule == nil { 134 return nil 135 } 136 137 return &RuleObject{ 138 Data: struct { 139 Name string 140 Enabled bool 141 Severity tflint.Severity 142 Link string 143 }{ 144 Name: rule.Name, 145 Enabled: rule.Enabled, 146 Severity: Severity(rule.Severity), 147 Link: rule.Link, 148 }, 149 } 150 } 151 152 // Expression converts proto.Expression to hcl.Expression 153 func Expression(expr *proto.Expression) (hcl.Expression, hcl.Diagnostics) { 154 parsed, diags := hclext.ParseExpression(expr.Bytes, expr.Range.Filename, Pos(expr.Range.Start)) 155 if diags.HasErrors() { 156 return nil, diags 157 } 158 if expr.Value != nil { 159 val, err := Value(expr.Value, cty.DynamicPseudoType, expr.ValueMarks) 160 if err != nil { 161 panic(fmt.Errorf("cannot unmarshal the bound expr: %w", err)) 162 } 163 parsed = hclext.BindValue(val, parsed) 164 } 165 return parsed, diags 166 } 167 168 // Severity converts proto.EmitIssue_Severity to severity 169 func Severity(severity proto.EmitIssue_Severity) tflint.Severity { 170 switch severity { 171 case proto.EmitIssue_SEVERITY_ERROR: 172 return tflint.ERROR 173 case proto.EmitIssue_SEVERITY_WARNING: 174 return tflint.WARNING 175 case proto.EmitIssue_SEVERITY_NOTICE: 176 return tflint.NOTICE 177 } 178 179 return tflint.ERROR 180 } 181 182 // Range converts proto.Range to hcl.Range 183 func Range(rng *proto.Range) hcl.Range { 184 if rng == nil { 185 return hcl.Range{} 186 } 187 188 return hcl.Range{ 189 Filename: rng.Filename, 190 Start: Pos(rng.Start), 191 End: Pos(rng.End), 192 } 193 } 194 195 // Pos converts proto.Range_Pos to hcl.Pos 196 func Pos(pos *proto.Range_Pos) hcl.Pos { 197 if pos == nil { 198 return hcl.Pos{} 199 } 200 201 return hcl.Pos{ 202 Line: int(pos.Line), 203 Column: int(pos.Column), 204 Byte: int(pos.Byte), 205 } 206 } 207 208 // Value converts msgpack and []proto.ValueMark to cty.Value 209 func Value(value []byte, ty cty.Type, valueMarks []*proto.ValueMark) (cty.Value, error) { 210 val, err := msgpack.Unmarshal(value, ty) 211 if err != nil { 212 return cty.NilVal, err 213 } 214 215 pvm := make([]cty.PathValueMarks, len(valueMarks)) 216 for idx, mark := range valueMarks { 217 pvm[idx] = cty.PathValueMarks{ 218 Path: AttributePath(mark.Path), 219 } 220 vm := []interface{}{} 221 if mark.Sensitive { 222 vm = append(vm, marks.Sensitive) 223 } 224 if mark.Ephemeral { 225 vm = append(vm, marks.Ephemeral) 226 } 227 pvm[idx].Marks = cty.NewValueMarks(vm...) 228 } 229 230 return val.MarkWithPaths(pvm), nil 231 } 232 233 // AttributePath converts proto.AttributePath to cty.Path 234 func AttributePath(path *proto.AttributePath) cty.Path { 235 ret := cty.Path{} 236 237 for _, step := range path.Steps { 238 switch s := step.Selector.(type) { 239 case *proto.AttributePath_Step_ElementKeyString: 240 ret = ret.IndexString(s.ElementKeyString) 241 case *proto.AttributePath_Step_ElementKeyInt: 242 ret = ret.IndexInt(int(s.ElementKeyInt)) 243 case *proto.AttributePath_Step_AttributeName: 244 ret = ret.GetAttr(s.AttributeName) 245 } 246 } 247 return ret 248 } 249 250 // Config converts proto.ApplyGlobalConfig_Config to tflint.Config 251 func Config(config *proto.ApplyGlobalConfig_Config) *tflint.Config { 252 if config == nil { 253 return &tflint.Config{Rules: make(map[string]*tflint.RuleConfig)} 254 } 255 256 rules := map[string]*tflint.RuleConfig{} 257 for name, rule := range config.Rules { 258 rules[name] = &tflint.RuleConfig{Name: rule.Name, Enabled: rule.Enabled} 259 } 260 return &tflint.Config{ 261 Rules: rules, 262 DisabledByDefault: config.DisabledByDefault, 263 Only: config.Only, 264 Fix: config.Fix, 265 } 266 } 267 268 // GetModuleContentOption converts proto.GetModuleContent_Option to tflint.GetModuleContentOption 269 func GetModuleContentOption(opts *proto.GetModuleContent_Option) tflint.GetModuleContentOption { 270 if opts == nil { 271 return tflint.GetModuleContentOption{} 272 } 273 274 return tflint.GetModuleContentOption{ 275 ModuleCtx: ModuleCtxType(opts.ModuleCtx), 276 ExpandMode: ExpandMode(opts.ExpandMode), 277 Hint: GetModuleContentHint(opts.Hint), 278 } 279 } 280 281 // ModuleCtxType converts proto.ModuleCtxType to tflint.ModuleCtxType 282 func ModuleCtxType(ty proto.ModuleCtxType) tflint.ModuleCtxType { 283 switch ty { 284 case proto.ModuleCtxType_MODULE_CTX_TYPE_UNSPECIFIED: 285 return tflint.SelfModuleCtxType 286 case proto.ModuleCtxType_MODULE_CTX_TYPE_SELF: 287 return tflint.SelfModuleCtxType 288 case proto.ModuleCtxType_MODULE_CTX_TYPE_ROOT: 289 return tflint.RootModuleCtxType 290 default: 291 panic(fmt.Sprintf("invalid ModuleCtxType: %s", ty)) 292 } 293 } 294 295 // ExpandMode converts proto.GetModuleContent_ExpandMode to tflint.ExpandMode 296 func ExpandMode(mode proto.GetModuleContent_ExpandMode) tflint.ExpandMode { 297 switch mode { 298 case proto.GetModuleContent_EXPAND_MODE_UNSPECIFIED: 299 return tflint.ExpandModeExpand 300 case proto.GetModuleContent_EXPAND_MODE_EXPAND: 301 return tflint.ExpandModeExpand 302 case proto.GetModuleContent_EXPAND_MODE_NONE: 303 return tflint.ExpandModeNone 304 default: 305 panic(fmt.Sprintf("invalid ExpandMode: %s", mode)) 306 } 307 } 308 309 // GetModuleContentHint converts proto.GetModuleContent_Hint to tflint.GetModuleContentHint 310 func GetModuleContentHint(hint *proto.GetModuleContent_Hint) tflint.GetModuleContentHint { 311 if hint == nil { 312 return tflint.GetModuleContentHint{} 313 } 314 315 return tflint.GetModuleContentHint{ 316 ResourceType: hint.ResourceType, 317 } 318 } 319 320 // Error converts gRPC error status to wrapped error 321 func Error(err error) error { 322 if err == nil { 323 return nil 324 } 325 326 st, ok := status.FromError(err) 327 if !ok { 328 return err 329 } 330 331 // Unimplemented is an unexpected error, so return as-is. 332 if st.Code() == codes.Unimplemented { 333 return err 334 } 335 336 // If the error status has no details, return an error from the gRPC error status. 337 // Remove the status code because some statuses are expected and should not be shown to users. 338 if len(st.Details()) == 0 { 339 return errors.New(st.Message()) 340 } 341 342 // It is not supposed to have multiple details. The detail have an error code and will be wrapped as an error. 343 switch t := st.Details()[0].(type) { 344 case *proto.ErrorDetail: 345 switch t.Code { 346 case proto.ErrorCode_ERROR_CODE_UNKNOWN_VALUE: 347 return tflint.ErrUnknownValue 348 case proto.ErrorCode_ERROR_CODE_NULL_VALUE: 349 return tflint.ErrNullValue 350 case proto.ErrorCode_ERROR_CODE_UNEVALUABLE: 351 return fmt.Errorf("%s%w", st.Message(), tflint.ErrUnevaluable) 352 case proto.ErrorCode_ERROR_CODE_SENSITIVE: 353 return tflint.ErrSensitive 354 } 355 } 356 357 return err 358 }