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  }