github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/plugin/internal/host2plugin/server.go (about) 1 package host2plugin 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/hashicorp/go-plugin" 8 "github.com/terraform-linters/tflint-plugin-sdk/internal" 9 "github.com/terraform-linters/tflint-plugin-sdk/logger" 10 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/fromproto" 11 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/interceptor" 12 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/plugin2host" 13 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/proto" 14 "github.com/terraform-linters/tflint-plugin-sdk/plugin/internal/toproto" 15 "github.com/terraform-linters/tflint-plugin-sdk/tflint" 16 "google.golang.org/grpc" 17 "google.golang.org/grpc/codes" 18 "google.golang.org/grpc/status" 19 ) 20 21 // GRPCServer is a plugin-side implementation. Plugin must implement a server that returns a response for a request from host. 22 // The behavior as gRPC server is implemented in the SDK, and the actual behavior is delegated to impl. 23 type GRPCServer struct { 24 proto.UnimplementedRuleSetServer 25 26 impl tflint.RuleSet 27 broker *plugin.GRPCBroker 28 config *tflint.Config 29 30 // TFLint v0.41 and earlier does not check version constraints, 31 // so it returns an error in that case. 32 constraintChecked bool 33 } 34 35 var _ proto.RuleSetServer = &GRPCServer{} 36 37 // ServeOpts is an option for serving a plugin. 38 // Each plugin can pass a RuleSet that represents its own functionality. 39 type ServeOpts struct { 40 RuleSet tflint.RuleSet 41 } 42 43 // Serve is a wrapper of plugin.Serve. This is entrypoint of all plugins. 44 func Serve(opts *ServeOpts) { 45 plugin.Serve(&plugin.ServeConfig{ 46 HandshakeConfig: handshakeConfig, 47 Plugins: map[string]plugin.Plugin{ 48 "ruleset": &RuleSetPlugin{impl: opts.RuleSet}, 49 }, 50 GRPCServer: func(opts []grpc.ServerOption) *grpc.Server { 51 opts = append(opts, grpc.UnaryInterceptor(interceptor.RequestLogging("host2plugin"))) 52 return grpc.NewServer(opts...) 53 }, 54 Logger: logger.Logger(), 55 }) 56 } 57 58 // GetName returns the name of the plugin. 59 func (s *GRPCServer) GetName(ctx context.Context, req *proto.GetName_Request) (*proto.GetName_Response, error) { 60 return &proto.GetName_Response{Name: s.impl.RuleSetName()}, nil 61 } 62 63 // GetVersion returns the version of the plugin. 64 func (s *GRPCServer) GetVersion(ctx context.Context, req *proto.GetVersion_Request) (*proto.GetVersion_Response, error) { 65 return &proto.GetVersion_Response{Version: s.impl.RuleSetVersion()}, nil 66 } 67 68 // GetRuleNames returns the list of rule names provided by the plugin. 69 func (s *GRPCServer) GetRuleNames(ctx context.Context, req *proto.GetRuleNames_Request) (*proto.GetRuleNames_Response, error) { 70 return &proto.GetRuleNames_Response{Names: s.impl.RuleNames()}, nil 71 } 72 73 // GetVersionConstraint returns a constraint of TFLint versions. 74 func (s *GRPCServer) GetVersionConstraint(ctx context.Context, req *proto.GetVersionConstraint_Request) (*proto.GetVersionConstraint_Response, error) { 75 s.constraintChecked = true 76 return &proto.GetVersionConstraint_Response{Constraint: s.impl.VersionConstraint()}, nil 77 } 78 79 // GetSDKVersion returns the SDK version. 80 func (s *GRPCServer) GetSDKVersion(ctx context.Context, req *proto.GetSDKVersion_Request) (*proto.GetSDKVersion_Response, error) { 81 return &proto.GetSDKVersion_Response{Version: SDKVersion}, nil 82 } 83 84 // GetConfigSchema returns the config schema of the plugin. 85 func (s *GRPCServer) GetConfigSchema(ctx context.Context, req *proto.GetConfigSchema_Request) (*proto.GetConfigSchema_Response, error) { 86 return &proto.GetConfigSchema_Response{Schema: toproto.BodySchema(s.impl.ConfigSchema())}, nil 87 } 88 89 // ApplyGlobalConfig applies a common config to the plugin. 90 func (s *GRPCServer) ApplyGlobalConfig(ctx context.Context, req *proto.ApplyGlobalConfig_Request) (*proto.ApplyGlobalConfig_Response, error) { 91 // TFLint v0.41 and earlier does not check version constraints. 92 if !s.constraintChecked { 93 return nil, status.Error(codes.FailedPrecondition, fmt.Sprintf("failed to satisfy version constraints; tflint-ruleset-%s requires >= 0.42, but TFLint version is 0.40 or 0.41", s.impl.RuleSetName())) 94 } 95 96 if req.Config == nil { 97 return nil, status.Error(codes.InvalidArgument, "config should not be null") 98 } 99 100 s.config = fromproto.Config(req.Config) 101 if err := s.impl.ApplyGlobalConfig(s.config); err != nil { 102 return nil, toproto.Error(codes.FailedPrecondition, err) 103 } 104 return &proto.ApplyGlobalConfig_Response{}, nil 105 } 106 107 // ApplyConfig applies the plugin config retrieved from the host to the plugin. 108 func (s *GRPCServer) ApplyConfig(ctx context.Context, req *proto.ApplyConfig_Request) (*proto.ApplyConfig_Response, error) { 109 if req.Content == nil { 110 return nil, status.Error(codes.InvalidArgument, "content should not be null") 111 } 112 113 content, diags := fromproto.BodyContent(req.Content) 114 if diags.HasErrors() { 115 return nil, toproto.Error(codes.InvalidArgument, diags) 116 } 117 if err := s.impl.ApplyConfig(content); err != nil { 118 return nil, toproto.Error(codes.FailedPrecondition, err) 119 } 120 return &proto.ApplyConfig_Response{}, nil 121 } 122 123 // Check calls plugin rules with a gRPC client that can send requests 124 // to the host process. 125 func (s *GRPCServer) Check(ctx context.Context, req *proto.Check_Request) (*proto.Check_Response, error) { 126 conn, err := s.broker.Dial(req.Runner) 127 if err != nil { 128 return nil, toproto.Error(codes.InvalidArgument, err) 129 } 130 defer conn.Close() 131 132 client := proto.NewRunnerClient(conn) 133 resp, err := client.GetFiles(ctx, &proto.GetFiles_Request{}) 134 if err != nil { 135 return nil, toproto.Error(codes.FailedPrecondition, err) 136 } 137 138 internalRunner := &plugin2host.GRPCClient{Client: client, Fixer: internal.NewFixer(resp.Files), FixEnabled: s.config.Fix} 139 runner, err := s.impl.NewRunner(internalRunner) 140 if err != nil { 141 return nil, toproto.Error(codes.FailedPrecondition, err) 142 } 143 144 for _, rule := range s.impl.BuiltinImpl().EnabledRules { 145 if err := rule.Check(runner); err != nil { 146 return nil, toproto.Error(codes.Aborted, fmt.Errorf(`failed to check "%s" rule: %s`, rule.Name(), err)) 147 } 148 if internalRunner.Fixer.HasChanges() { 149 internalRunner.Fixer.FormatChanges() 150 if err := internalRunner.ApplyChanges(); err != nil { 151 return nil, toproto.Error(codes.Aborted, fmt.Errorf(`failed to apply fixes by "%s" rule: %s`, rule.Name(), err)) 152 } 153 } 154 } 155 return &proto.Check_Response{}, nil 156 }