github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/plugin/internal/plugin2host/server.go (about) 1 package plugin2host 2 3 import ( 4 "context" 5 6 "github.com/hashicorp/hcl/v2" 7 "github.com/terraform-linters/tflint-plugin-sdk/hclext" 8 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/fromproto" 9 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/proto" 10 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/toproto" 11 "github.com/terraform-linters/tflint-plugin-sdk/tflint" 12 "github.com/zclconf/go-cty/cty" 13 "github.com/zclconf/go-cty/cty/json" 14 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/status" 16 ) 17 18 // GRPCServer is a host-side implementation. Host must implement a server that returns a response for a request from plugin. 19 // The behavior as gRPC server is implemented in the SDK, and the actual behavior is delegated to impl. 20 type GRPCServer struct { 21 proto.UnimplementedRunnerServer 22 23 Impl Server 24 } 25 26 var _ proto.RunnerServer = &GRPCServer{} 27 28 // Server is the interface that the host should implement when a plugin communicates with the host. 29 type Server interface { 30 GetOriginalwd() string 31 GetModulePath() []string 32 GetModuleContent(*hclext.BodySchema, tflint.GetModuleContentOption) (*hclext.BodyContent, hcl.Diagnostics) 33 GetFile(string) (*hcl.File, error) 34 // For performance, GetFiles returns map[string][]bytes instead of map[string]*hcl.File. 35 GetFiles(tflint.ModuleCtxType) map[string][]byte 36 GetRuleConfigContent(string, *hclext.BodySchema) (*hclext.BodyContent, map[string][]byte, error) 37 EvaluateExpr(hcl.Expression, tflint.EvaluateExprOption) (cty.Value, error) 38 EmitIssue(rule tflint.Rule, message string, location hcl.Range, fixable bool) (bool, error) 39 ApplyChanges(map[string][]byte) error 40 } 41 42 // GetOriginalwd gets the original working directory. 43 func (s *GRPCServer) GetOriginalwd(ctx context.Context, req *proto.GetOriginalwd_Request) (*proto.GetOriginalwd_Response, error) { 44 return &proto.GetOriginalwd_Response{Path: s.Impl.GetOriginalwd()}, nil 45 } 46 47 // GetModulePath gets the current module path address. 48 func (s *GRPCServer) GetModulePath(ctx context.Context, req *proto.GetModulePath_Request) (*proto.GetModulePath_Response, error) { 49 return &proto.GetModulePath_Response{Path: s.Impl.GetModulePath()}, nil 50 } 51 52 // GetModuleContent gets the contents of the module based on the schema. 53 func (s *GRPCServer) GetModuleContent(ctx context.Context, req *proto.GetModuleContent_Request) (*proto.GetModuleContent_Response, error) { 54 if req.Schema == nil { 55 return nil, status.Error(codes.InvalidArgument, "schema should not be null") 56 } 57 if req.Option == nil { 58 return nil, status.Error(codes.InvalidArgument, "option should not be null") 59 } 60 61 opts := fromproto.GetModuleContentOption(req.Option) 62 body, diags := s.Impl.GetModuleContent(fromproto.BodySchema(req.Schema), opts) 63 if diags.HasErrors() { 64 return nil, toproto.Error(codes.FailedPrecondition, diags) 65 } 66 if body == nil { 67 return nil, status.Error(codes.FailedPrecondition, "response body is empty") 68 } 69 70 content := toproto.BodyContent(body, s.Impl.GetFiles(opts.ModuleCtx)) 71 72 return &proto.GetModuleContent_Response{Content: content}, nil 73 } 74 75 // GetFile returns bytes of hcl.File based on the passed file name. 76 func (s *GRPCServer) GetFile(ctx context.Context, req *proto.GetFile_Request) (*proto.GetFile_Response, error) { 77 if req.Name == "" { 78 return nil, status.Error(codes.InvalidArgument, "name should not be empty") 79 } 80 file, err := s.Impl.GetFile(req.Name) 81 if err != nil { 82 return nil, toproto.Error(codes.FailedPrecondition, err) 83 } 84 if file == nil { 85 return nil, status.Error(codes.NotFound, "file not found") 86 } 87 return &proto.GetFile_Response{File: file.Bytes}, nil 88 } 89 90 // GetFiles returns bytes of hcl.File in the self module context. 91 func (s *GRPCServer) GetFiles(ctx context.Context, req *proto.GetFiles_Request) (*proto.GetFiles_Response, error) { 92 return &proto.GetFiles_Response{Files: s.Impl.GetFiles(tflint.SelfModuleCtxType)}, nil 93 } 94 95 // GetRuleConfigContent returns BodyContent based on the rule name and config schema. 96 func (s *GRPCServer) GetRuleConfigContent(ctx context.Context, req *proto.GetRuleConfigContent_Request) (*proto.GetRuleConfigContent_Response, error) { 97 if req.Name == "" { 98 return nil, status.Error(codes.InvalidArgument, "name should not be empty") 99 } 100 if req.Schema == nil { 101 return nil, status.Error(codes.InvalidArgument, "schema should not be null") 102 } 103 104 body, sources, err := s.Impl.GetRuleConfigContent(req.Name, fromproto.BodySchema(req.Schema)) 105 if err != nil { 106 return nil, toproto.Error(codes.FailedPrecondition, err) 107 } 108 if body == nil { 109 return nil, status.Error(codes.FailedPrecondition, "response body is empty") 110 } 111 if len(sources) == 0 && !body.IsEmpty() { 112 return nil, status.Error(codes.NotFound, "config file not found") 113 } 114 115 content := toproto.BodyContent(body, sources) 116 return &proto.GetRuleConfigContent_Response{Content: content}, nil 117 } 118 119 // EvaluateExpr evals the passed expression based on the type. 120 func (s *GRPCServer) EvaluateExpr(ctx context.Context, req *proto.EvaluateExpr_Request) (*proto.EvaluateExpr_Response, error) { 121 if req.Expression == nil { 122 return nil, status.Error(codes.InvalidArgument, "expression should not be null") 123 } 124 if req.Expression.Bytes == nil { 125 return nil, status.Error(codes.InvalidArgument, "expression.bytes should not be null") 126 } 127 if req.Expression.Range == nil { 128 return nil, status.Error(codes.InvalidArgument, "expression.range should not be null") 129 } 130 if req.Option == nil { 131 return nil, status.Error(codes.InvalidArgument, "option should not be null") 132 } 133 134 expr, diags := fromproto.Expression(req.Expression) 135 if diags.HasErrors() { 136 return nil, toproto.Error(codes.InvalidArgument, diags) 137 } 138 ty, err := json.UnmarshalType(req.Option.Type) 139 if err != nil { 140 return nil, toproto.Error(codes.InvalidArgument, err) 141 } 142 143 value, err := s.Impl.EvaluateExpr(expr, tflint.EvaluateExprOption{WantType: &ty, ModuleCtx: fromproto.ModuleCtxType(req.Option.ModuleCtx)}) 144 if err != nil { 145 return nil, toproto.Error(codes.FailedPrecondition, err) 146 } 147 val, marks, err := toproto.Value(value, ty) 148 if err != nil { 149 return nil, toproto.Error(codes.FailedPrecondition, err) 150 } 151 152 return &proto.EvaluateExpr_Response{Value: val, Marks: marks}, nil 153 } 154 155 // EmitIssue emits the issue with the passed rule, message, location 156 func (s *GRPCServer) EmitIssue(ctx context.Context, req *proto.EmitIssue_Request) (*proto.EmitIssue_Response, error) { 157 if req.Rule == nil { 158 return nil, status.Error(codes.InvalidArgument, "rule should not be null") 159 } 160 if req.Range == nil { 161 return nil, status.Error(codes.InvalidArgument, "range should not be null") 162 } 163 164 applied, err := s.Impl.EmitIssue(fromproto.Rule(req.Rule), req.Message, fromproto.Range(req.Range), req.Fixable) 165 if err != nil { 166 return nil, toproto.Error(codes.FailedPrecondition, err) 167 } 168 return &proto.EmitIssue_Response{Applied: applied}, nil 169 } 170 171 // ApplyChanges applies the passed changes. 172 func (s *GRPCServer) ApplyChanges(ctx context.Context, req *proto.ApplyChanges_Request) (*proto.ApplyChanges_Response, error) { 173 err := s.Impl.ApplyChanges(req.Changes) 174 if err != nil { 175 return nil, toproto.Error(codes.InvalidArgument, err) 176 } 177 return &proto.ApplyChanges_Response{}, nil 178 }