github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/plugin/internal/toproto/toproto.go (about) 1 package toproto 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/convert" 14 "github.com/zclconf/go-cty/cty/msgpack" 15 "google.golang.org/grpc/codes" 16 "google.golang.org/grpc/status" 17 ) 18 19 // BodySchema converts schema.BodySchema to proto.BodySchema 20 func BodySchema(body *hclext.BodySchema) *proto.BodySchema { 21 if body == nil { 22 return &proto.BodySchema{} 23 } 24 25 attributes := make([]*proto.BodySchema_Attribute, len(body.Attributes)) 26 for idx, attr := range body.Attributes { 27 attributes[idx] = &proto.BodySchema_Attribute{Name: attr.Name, Required: attr.Required} 28 } 29 30 blocks := make([]*proto.BodySchema_Block, len(body.Blocks)) 31 for idx, block := range body.Blocks { 32 blocks[idx] = &proto.BodySchema_Block{ 33 Type: block.Type, 34 LabelNames: block.LabelNames, 35 Body: BodySchema(block.Body), 36 } 37 } 38 39 return &proto.BodySchema{ 40 Mode: SchemaMode(body.Mode), 41 Attributes: attributes, 42 Blocks: blocks, 43 } 44 } 45 46 // SchemaMode converts hclext.SchemaMode to proto.SchemaMode 47 func SchemaMode(mode hclext.SchemaMode) proto.SchemaMode { 48 switch mode { 49 case hclext.SchemaDefaultMode: 50 return proto.SchemaMode_SCHEMA_MODE_DEFAULT 51 case hclext.SchemaJustAttributesMode: 52 return proto.SchemaMode_SCHEMA_MODE_JUST_ATTRIBUTES 53 default: 54 panic(fmt.Sprintf("invalid SchemaMode: %s", mode)) 55 } 56 } 57 58 // BodyContent converts schema.BodyContent to proto.BodyContent 59 func BodyContent(body *hclext.BodyContent, sources map[string][]byte) *proto.BodyContent { 60 if body == nil { 61 return &proto.BodyContent{} 62 } 63 64 attributes := map[string]*proto.BodyContent_Attribute{} 65 for idx, attr := range body.Attributes { 66 bytes, ok := sources[attr.Range.Filename] 67 if !ok { 68 panic(fmt.Sprintf("failed to encode to protocol buffers: source code not available: name=%s", attr.Range.Filename)) 69 } 70 71 attributes[idx] = &proto.BodyContent_Attribute{ 72 Name: attr.Name, 73 Expression: Expression(attr.Expr, bytes), 74 Range: Range(attr.Range), 75 NameRange: Range(attr.NameRange), 76 } 77 } 78 79 blocks := make([]*proto.BodyContent_Block, len(body.Blocks)) 80 for idx, block := range body.Blocks { 81 labelRanges := make([]*proto.Range, len(block.LabelRanges)) 82 for idx, labelRange := range block.LabelRanges { 83 labelRanges[idx] = Range(labelRange) 84 } 85 86 blocks[idx] = &proto.BodyContent_Block{ 87 Type: block.Type, 88 Labels: block.Labels, 89 Body: BodyContent(block.Body, sources), 90 DefRange: Range(block.DefRange), 91 TypeRange: Range(block.TypeRange), 92 LabelRanges: labelRanges, 93 } 94 } 95 96 return &proto.BodyContent{ 97 Attributes: attributes, 98 Blocks: blocks, 99 } 100 } 101 102 // Rule converts tflint.Rule to proto.EmitIssue_Rule 103 func Rule(rule tflint.Rule) *proto.EmitIssue_Rule { 104 if rule == nil { 105 panic("failed to encode to protocol buffers: rule should not be nil") 106 } 107 return &proto.EmitIssue_Rule{ 108 Name: rule.Name(), 109 Enabled: rule.Enabled(), 110 Severity: Severity(rule.Severity()), 111 Link: rule.Link(), 112 } 113 } 114 115 // Expression converts hcl.Expression to proto.Expression 116 func Expression(expr hcl.Expression, source []byte) *proto.Expression { 117 out := &proto.Expression{ 118 Bytes: expr.Range().SliceBytes(source), 119 Range: Range(expr.Range()), 120 } 121 122 if boundExpr, ok := expr.(*hclext.BoundExpr); ok { 123 val, marks, err := Value(boundExpr.Val, cty.DynamicPseudoType) 124 if err != nil { 125 panic(fmt.Errorf("cannot marshal the bound expr: %w", err)) 126 } 127 out.Value = val 128 out.ValueMarks = marks 129 } 130 return out 131 } 132 133 // Severity converts severity to proto.EmitIssue_Severity 134 func Severity(severity tflint.Severity) proto.EmitIssue_Severity { 135 switch severity { 136 case tflint.ERROR: 137 return proto.EmitIssue_SEVERITY_ERROR 138 case tflint.WARNING: 139 return proto.EmitIssue_SEVERITY_WARNING 140 case tflint.NOTICE: 141 return proto.EmitIssue_SEVERITY_NOTICE 142 } 143 144 return proto.EmitIssue_SEVERITY_ERROR 145 } 146 147 // Range converts hcl.Range to proto.Range 148 func Range(rng hcl.Range) *proto.Range { 149 return &proto.Range{ 150 Filename: rng.Filename, 151 Start: Pos(rng.Start), 152 End: Pos(rng.End), 153 } 154 } 155 156 // Pos converts hcl.Pos to proto.Range_Pos 157 func Pos(pos hcl.Pos) *proto.Range_Pos { 158 return &proto.Range_Pos{ 159 Line: int64(pos.Line), 160 Column: int64(pos.Column), 161 Byte: int64(pos.Byte), 162 } 163 } 164 165 // Value converts cty.Value to msgpack and serialized value marks 166 func Value(value cty.Value, ty cty.Type) ([]byte, []*proto.ValueMark, error) { 167 // Convert first to get the actual cty.Path 168 value, err := convert.Convert(value, ty) 169 if err != nil { 170 return nil, nil, err 171 } 172 173 value, pvm := value.UnmarkDeepWithPaths() 174 valueMarks := make([]*proto.ValueMark, len(pvm)) 175 for idx, m := range pvm { 176 path, err := AttributePath(m.Path) 177 if err != nil { 178 return nil, nil, err 179 } 180 181 valueMarks[idx] = &proto.ValueMark{Path: path} 182 if _, exists := m.Marks[marks.Sensitive]; exists { 183 valueMarks[idx].Sensitive = true 184 } 185 if _, exists := m.Marks[marks.Ephemeral]; exists { 186 valueMarks[idx].Ephemeral = true 187 } 188 } 189 190 val, err := msgpack.Marshal(value, ty) 191 if err != nil { 192 return nil, nil, err 193 } 194 195 return val, valueMarks, nil 196 } 197 198 // AttributePath converts cty.Path to proto.AttributePath 199 func AttributePath(path cty.Path) (*proto.AttributePath, error) { 200 steps := make([]*proto.AttributePath_Step, len(path)) 201 202 for idx, step := range path { 203 switch s := step.(type) { 204 case cty.IndexStep: 205 switch s.Key.Type() { 206 case cty.String: 207 steps[idx] = &proto.AttributePath_Step{ 208 Selector: &proto.AttributePath_Step_ElementKeyString{ElementKeyString: s.Key.AsString()}, 209 } 210 case cty.Number: 211 v, _ := s.Key.AsBigFloat().Int64() 212 steps[idx] = &proto.AttributePath_Step{ 213 Selector: &proto.AttributePath_Step_ElementKeyInt{ElementKeyInt: v}, 214 } 215 default: 216 return nil, fmt.Errorf("unknown index step key type: %s", s.Key.Type().GoString()) 217 } 218 case cty.GetAttrStep: 219 steps[idx] = &proto.AttributePath_Step{ 220 Selector: &proto.AttributePath_Step_AttributeName{AttributeName: s.Name}, 221 } 222 default: 223 return nil, fmt.Errorf("unknown attribute path step: %T", s) 224 } 225 } 226 227 return &proto.AttributePath{Steps: steps}, nil 228 } 229 230 // Config converts tflint.Config to proto.ApplyGlobalConfig_Config 231 func Config(config *tflint.Config) *proto.ApplyGlobalConfig_Config { 232 if config == nil { 233 return &proto.ApplyGlobalConfig_Config{Rules: make(map[string]*proto.ApplyGlobalConfig_RuleConfig)} 234 } 235 236 rules := map[string]*proto.ApplyGlobalConfig_RuleConfig{} 237 for name, rule := range config.Rules { 238 rules[name] = &proto.ApplyGlobalConfig_RuleConfig{Name: rule.Name, Enabled: rule.Enabled} 239 } 240 return &proto.ApplyGlobalConfig_Config{ 241 Rules: rules, 242 DisabledByDefault: config.DisabledByDefault, 243 Only: config.Only, 244 Fix: config.Fix, 245 } 246 } 247 248 // GetModuleContentOption converts tflint.GetModuleContentOption to proto.GetModuleContent_Option 249 func GetModuleContentOption(opts *tflint.GetModuleContentOption) *proto.GetModuleContent_Option { 250 if opts == nil { 251 return &proto.GetModuleContent_Option{} 252 } 253 254 return &proto.GetModuleContent_Option{ 255 ModuleCtx: ModuleCtxType(opts.ModuleCtx), 256 ExpandMode: ExpandMode(opts.ExpandMode), 257 Hint: GetModuleContentHint(opts.Hint), 258 } 259 } 260 261 // ModuleCtxType converts tflint.ModuleCtxType to proto.ModuleCtxType 262 func ModuleCtxType(ty tflint.ModuleCtxType) proto.ModuleCtxType { 263 switch ty { 264 case tflint.SelfModuleCtxType: 265 return proto.ModuleCtxType_MODULE_CTX_TYPE_SELF 266 case tflint.RootModuleCtxType: 267 return proto.ModuleCtxType_MODULE_CTX_TYPE_ROOT 268 default: 269 panic(fmt.Sprintf("invalid ModuleCtxType: %s", ty.String())) 270 } 271 } 272 273 // ExpandMode converts tflint.ExpandMode to proto.GetModuleContent_ExpandMode 274 func ExpandMode(mode tflint.ExpandMode) proto.GetModuleContent_ExpandMode { 275 switch mode { 276 case tflint.ExpandModeExpand: 277 return proto.GetModuleContent_EXPAND_MODE_EXPAND 278 case tflint.ExpandModeNone: 279 return proto.GetModuleContent_EXPAND_MODE_NONE 280 default: 281 panic(fmt.Sprintf("invalid ExpandMode: %s", mode)) 282 } 283 } 284 285 // GetModuleContentHint converts tflint.GetModuleContentHint to proto.GetModuleContentHint 286 func GetModuleContentHint(hint tflint.GetModuleContentHint) *proto.GetModuleContent_Hint { 287 return &proto.GetModuleContent_Hint{ 288 ResourceType: hint.ResourceType, 289 } 290 } 291 292 // Error converts error to gRPC error status with details 293 func Error(code codes.Code, err error) error { 294 if err == nil { 295 return nil 296 } 297 298 var errCode proto.ErrorCode 299 if errors.Is(err, tflint.ErrUnknownValue) { 300 errCode = proto.ErrorCode_ERROR_CODE_UNKNOWN_VALUE 301 } else if errors.Is(err, tflint.ErrNullValue) { 302 errCode = proto.ErrorCode_ERROR_CODE_NULL_VALUE 303 } else if errors.Is(err, tflint.ErrUnevaluable) { 304 errCode = proto.ErrorCode_ERROR_CODE_UNEVALUABLE 305 } else if errors.Is(err, tflint.ErrSensitive) { 306 errCode = proto.ErrorCode_ERROR_CODE_SENSITIVE 307 } 308 309 if errCode == proto.ErrorCode_ERROR_CODE_UNSPECIFIED { 310 return status.Error(code, err.Error()) 311 } 312 313 st := status.New(code, err.Error()) 314 dt, err := st.WithDetails(&proto.ErrorDetail{Code: errCode}) 315 if err != nil { 316 return status.Error(codes.Unknown, fmt.Sprintf("Failed to add ErrorDetail: code=%d error=%s", code, err.Error())) 317 } 318 319 return dt.Err() 320 }