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  }