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  }