github.com/terraform-linters/tflint-plugin-sdk@v0.22.0/plugin/internal/fromproto/fromproto.go (about)

     1  package fromproto
     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/msgpack"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  )
    17  
    18  // BodySchema converts proto.BodySchema to hclext.BodySchema
    19  func BodySchema(body *proto.BodySchema) *hclext.BodySchema {
    20  	if body == nil {
    21  		return nil
    22  	}
    23  
    24  	attributes := make([]hclext.AttributeSchema, len(body.Attributes))
    25  	for idx, attr := range body.Attributes {
    26  		attributes[idx] = hclext.AttributeSchema{Name: attr.Name, Required: attr.Required}
    27  	}
    28  
    29  	blocks := make([]hclext.BlockSchema, len(body.Blocks))
    30  	for idx, block := range body.Blocks {
    31  		blocks[idx] = hclext.BlockSchema{
    32  			Type:       block.Type,
    33  			LabelNames: block.LabelNames,
    34  			Body:       BodySchema(block.Body),
    35  		}
    36  	}
    37  
    38  	return &hclext.BodySchema{
    39  		Mode:       SchemaMode(body.Mode),
    40  		Attributes: attributes,
    41  		Blocks:     blocks,
    42  	}
    43  }
    44  
    45  // SchemaMode converts proto.SchemaMode to hclext.SchemaMode
    46  func SchemaMode(mode proto.SchemaMode) hclext.SchemaMode {
    47  	switch mode {
    48  	case proto.SchemaMode_SCHEMA_MODE_UNSPECIFIED:
    49  		return hclext.SchemaDefaultMode
    50  	case proto.SchemaMode_SCHEMA_MODE_DEFAULT:
    51  		return hclext.SchemaDefaultMode
    52  	case proto.SchemaMode_SCHEMA_MODE_JUST_ATTRIBUTES:
    53  		return hclext.SchemaJustAttributesMode
    54  	default:
    55  		panic(fmt.Sprintf("invalid SchemaMode: %s", mode))
    56  	}
    57  }
    58  
    59  // BodyContent converts proto.BodyContent to hclext.BodyContent
    60  func BodyContent(body *proto.BodyContent) (*hclext.BodyContent, hcl.Diagnostics) {
    61  	if body == nil {
    62  		return nil, nil
    63  	}
    64  	diags := hcl.Diagnostics{}
    65  
    66  	attributes := hclext.Attributes{}
    67  	for key, attr := range body.Attributes {
    68  		expr, exprDiags := Expression(attr.Expression)
    69  		diags = diags.Extend(exprDiags)
    70  
    71  		attributes[key] = &hclext.Attribute{
    72  			Name:      attr.Name,
    73  			Expr:      expr,
    74  			Range:     Range(attr.Range),
    75  			NameRange: Range(attr.NameRange),
    76  		}
    77  	}
    78  
    79  	blocks := make(hclext.Blocks, len(body.Blocks))
    80  	for idx, block := range body.Blocks {
    81  		blockBody, contentDiags := BodyContent(block.Body)
    82  		diags = diags.Extend(contentDiags)
    83  
    84  		labelRanges := make([]hcl.Range, len(block.LabelRanges))
    85  		for idx, labelRange := range block.LabelRanges {
    86  			labelRanges[idx] = Range(labelRange)
    87  		}
    88  
    89  		blocks[idx] = &hclext.Block{
    90  			Type:        block.Type,
    91  			Labels:      block.Labels,
    92  			Body:        blockBody,
    93  			DefRange:    Range(block.DefRange),
    94  			TypeRange:   Range(block.TypeRange),
    95  			LabelRanges: labelRanges,
    96  		}
    97  	}
    98  
    99  	return &hclext.BodyContent{
   100  		Attributes: attributes,
   101  		Blocks:     blocks,
   102  	}, diags
   103  }
   104  
   105  // RuleObject is an intermediate representation that satisfies the Rule interface.
   106  type RuleObject struct {
   107  	tflint.DefaultRule
   108  	Data struct {
   109  		Name     string
   110  		Enabled  bool
   111  		Severity tflint.Severity
   112  		Link     string
   113  	}
   114  }
   115  
   116  // Name returns the rule name
   117  func (r *RuleObject) Name() string { return r.Data.Name }
   118  
   119  // Enabled returns whether the rule is enabled
   120  func (r *RuleObject) Enabled() bool { return r.Data.Enabled }
   121  
   122  // Severity returns the severify of the rule
   123  func (r *RuleObject) Severity() tflint.Severity { return r.Data.Severity }
   124  
   125  // Link returns the link of the rule documentation if exists
   126  func (r *RuleObject) Link() string { return r.Data.Link }
   127  
   128  // Check does nothing. This is just a method to satisfy the interface
   129  func (r *RuleObject) Check(tflint.Runner) error { return nil }
   130  
   131  // Rule converts proto.EmitIssue_Rule to RuleObject
   132  func Rule(rule *proto.EmitIssue_Rule) *RuleObject {
   133  	if rule == nil {
   134  		return nil
   135  	}
   136  
   137  	return &RuleObject{
   138  		Data: struct {
   139  			Name     string
   140  			Enabled  bool
   141  			Severity tflint.Severity
   142  			Link     string
   143  		}{
   144  			Name:     rule.Name,
   145  			Enabled:  rule.Enabled,
   146  			Severity: Severity(rule.Severity),
   147  			Link:     rule.Link,
   148  		},
   149  	}
   150  }
   151  
   152  // Expression converts proto.Expression to hcl.Expression
   153  func Expression(expr *proto.Expression) (hcl.Expression, hcl.Diagnostics) {
   154  	parsed, diags := hclext.ParseExpression(expr.Bytes, expr.Range.Filename, Pos(expr.Range.Start))
   155  	if diags.HasErrors() {
   156  		return nil, diags
   157  	}
   158  	if expr.Value != nil {
   159  		val, err := Value(expr.Value, cty.DynamicPseudoType, expr.ValueMarks)
   160  		if err != nil {
   161  			panic(fmt.Errorf("cannot unmarshal the bound expr: %w", err))
   162  		}
   163  		parsed = hclext.BindValue(val, parsed)
   164  	}
   165  	return parsed, diags
   166  }
   167  
   168  // Severity converts proto.EmitIssue_Severity to severity
   169  func Severity(severity proto.EmitIssue_Severity) tflint.Severity {
   170  	switch severity {
   171  	case proto.EmitIssue_SEVERITY_ERROR:
   172  		return tflint.ERROR
   173  	case proto.EmitIssue_SEVERITY_WARNING:
   174  		return tflint.WARNING
   175  	case proto.EmitIssue_SEVERITY_NOTICE:
   176  		return tflint.NOTICE
   177  	}
   178  
   179  	return tflint.ERROR
   180  }
   181  
   182  // Range converts proto.Range to hcl.Range
   183  func Range(rng *proto.Range) hcl.Range {
   184  	if rng == nil {
   185  		return hcl.Range{}
   186  	}
   187  
   188  	return hcl.Range{
   189  		Filename: rng.Filename,
   190  		Start:    Pos(rng.Start),
   191  		End:      Pos(rng.End),
   192  	}
   193  }
   194  
   195  // Pos converts proto.Range_Pos to hcl.Pos
   196  func Pos(pos *proto.Range_Pos) hcl.Pos {
   197  	if pos == nil {
   198  		return hcl.Pos{}
   199  	}
   200  
   201  	return hcl.Pos{
   202  		Line:   int(pos.Line),
   203  		Column: int(pos.Column),
   204  		Byte:   int(pos.Byte),
   205  	}
   206  }
   207  
   208  // Value converts msgpack and []proto.ValueMark to cty.Value
   209  func Value(value []byte, ty cty.Type, valueMarks []*proto.ValueMark) (cty.Value, error) {
   210  	val, err := msgpack.Unmarshal(value, ty)
   211  	if err != nil {
   212  		return cty.NilVal, err
   213  	}
   214  
   215  	pvm := make([]cty.PathValueMarks, len(valueMarks))
   216  	for idx, mark := range valueMarks {
   217  		pvm[idx] = cty.PathValueMarks{
   218  			Path: AttributePath(mark.Path),
   219  		}
   220  		vm := []interface{}{}
   221  		if mark.Sensitive {
   222  			vm = append(vm, marks.Sensitive)
   223  		}
   224  		if mark.Ephemeral {
   225  			vm = append(vm, marks.Ephemeral)
   226  		}
   227  		pvm[idx].Marks = cty.NewValueMarks(vm...)
   228  	}
   229  
   230  	return val.MarkWithPaths(pvm), nil
   231  }
   232  
   233  // AttributePath converts proto.AttributePath to cty.Path
   234  func AttributePath(path *proto.AttributePath) cty.Path {
   235  	ret := cty.Path{}
   236  
   237  	for _, step := range path.Steps {
   238  		switch s := step.Selector.(type) {
   239  		case *proto.AttributePath_Step_ElementKeyString:
   240  			ret = ret.IndexString(s.ElementKeyString)
   241  		case *proto.AttributePath_Step_ElementKeyInt:
   242  			ret = ret.IndexInt(int(s.ElementKeyInt))
   243  		case *proto.AttributePath_Step_AttributeName:
   244  			ret = ret.GetAttr(s.AttributeName)
   245  		}
   246  	}
   247  	return ret
   248  }
   249  
   250  // Config converts proto.ApplyGlobalConfig_Config to tflint.Config
   251  func Config(config *proto.ApplyGlobalConfig_Config) *tflint.Config {
   252  	if config == nil {
   253  		return &tflint.Config{Rules: make(map[string]*tflint.RuleConfig)}
   254  	}
   255  
   256  	rules := map[string]*tflint.RuleConfig{}
   257  	for name, rule := range config.Rules {
   258  		rules[name] = &tflint.RuleConfig{Name: rule.Name, Enabled: rule.Enabled}
   259  	}
   260  	return &tflint.Config{
   261  		Rules:             rules,
   262  		DisabledByDefault: config.DisabledByDefault,
   263  		Only:              config.Only,
   264  		Fix:               config.Fix,
   265  	}
   266  }
   267  
   268  // GetModuleContentOption converts proto.GetModuleContent_Option to tflint.GetModuleContentOption
   269  func GetModuleContentOption(opts *proto.GetModuleContent_Option) tflint.GetModuleContentOption {
   270  	if opts == nil {
   271  		return tflint.GetModuleContentOption{}
   272  	}
   273  
   274  	return tflint.GetModuleContentOption{
   275  		ModuleCtx:  ModuleCtxType(opts.ModuleCtx),
   276  		ExpandMode: ExpandMode(opts.ExpandMode),
   277  		Hint:       GetModuleContentHint(opts.Hint),
   278  	}
   279  }
   280  
   281  // ModuleCtxType converts proto.ModuleCtxType to tflint.ModuleCtxType
   282  func ModuleCtxType(ty proto.ModuleCtxType) tflint.ModuleCtxType {
   283  	switch ty {
   284  	case proto.ModuleCtxType_MODULE_CTX_TYPE_UNSPECIFIED:
   285  		return tflint.SelfModuleCtxType
   286  	case proto.ModuleCtxType_MODULE_CTX_TYPE_SELF:
   287  		return tflint.SelfModuleCtxType
   288  	case proto.ModuleCtxType_MODULE_CTX_TYPE_ROOT:
   289  		return tflint.RootModuleCtxType
   290  	default:
   291  		panic(fmt.Sprintf("invalid ModuleCtxType: %s", ty))
   292  	}
   293  }
   294  
   295  // ExpandMode converts proto.GetModuleContent_ExpandMode to tflint.ExpandMode
   296  func ExpandMode(mode proto.GetModuleContent_ExpandMode) tflint.ExpandMode {
   297  	switch mode {
   298  	case proto.GetModuleContent_EXPAND_MODE_UNSPECIFIED:
   299  		return tflint.ExpandModeExpand
   300  	case proto.GetModuleContent_EXPAND_MODE_EXPAND:
   301  		return tflint.ExpandModeExpand
   302  	case proto.GetModuleContent_EXPAND_MODE_NONE:
   303  		return tflint.ExpandModeNone
   304  	default:
   305  		panic(fmt.Sprintf("invalid ExpandMode: %s", mode))
   306  	}
   307  }
   308  
   309  // GetModuleContentHint converts proto.GetModuleContent_Hint to tflint.GetModuleContentHint
   310  func GetModuleContentHint(hint *proto.GetModuleContent_Hint) tflint.GetModuleContentHint {
   311  	if hint == nil {
   312  		return tflint.GetModuleContentHint{}
   313  	}
   314  
   315  	return tflint.GetModuleContentHint{
   316  		ResourceType: hint.ResourceType,
   317  	}
   318  }
   319  
   320  // Error converts gRPC error status to wrapped error
   321  func Error(err error) error {
   322  	if err == nil {
   323  		return nil
   324  	}
   325  
   326  	st, ok := status.FromError(err)
   327  	if !ok {
   328  		return err
   329  	}
   330  
   331  	// Unimplemented is an unexpected error, so return as-is.
   332  	if st.Code() == codes.Unimplemented {
   333  		return err
   334  	}
   335  
   336  	// If the error status has no details, return an error from the gRPC error status.
   337  	// Remove the status code because some statuses are expected and should not be shown to users.
   338  	if len(st.Details()) == 0 {
   339  		return errors.New(st.Message())
   340  	}
   341  
   342  	// It is not supposed to have multiple details. The detail have an error code and will be wrapped as an error.
   343  	switch t := st.Details()[0].(type) {
   344  	case *proto.ErrorDetail:
   345  		switch t.Code {
   346  		case proto.ErrorCode_ERROR_CODE_UNKNOWN_VALUE:
   347  			return tflint.ErrUnknownValue
   348  		case proto.ErrorCode_ERROR_CODE_NULL_VALUE:
   349  			return tflint.ErrNullValue
   350  		case proto.ErrorCode_ERROR_CODE_UNEVALUABLE:
   351  			return fmt.Errorf("%s%w", st.Message(), tflint.ErrUnevaluable)
   352  		case proto.ErrorCode_ERROR_CODE_SENSITIVE:
   353  			return tflint.ErrSensitive
   354  		}
   355  	}
   356  
   357  	return err
   358  }