go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers-sdk/v1/lr/schema.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package lr
     5  
     6  import (
     7  	"errors"
     8  	"strings"
     9  
    10  	"go.mondoo.com/cnquery/providers-sdk/v1/resources"
    11  	"go.mondoo.com/cnquery/types"
    12  )
    13  
    14  func Schema(ast *LR) (*resources.Schema, error) {
    15  	provider, ok := ast.Options["provider"]
    16  	if !ok {
    17  		return nil, errors.New("missing provider name for resources to generate schema")
    18  	}
    19  
    20  	res := &resources.Schema{
    21  		Resources: make(map[string]*resources.ResourceInfo, len(ast.Resources)),
    22  	}
    23  
    24  	for i := range ast.Resources {
    25  		x, err := resourceSchema(ast.Resources[i], ast)
    26  		if err != nil {
    27  			return res, err
    28  		}
    29  
    30  		res.Resources[x.Id] = x
    31  	}
    32  
    33  	for defName, r := range ast.aliases {
    34  		x, ok := res.Resources[r.ID]
    35  		if !ok {
    36  			var err error
    37  			x, err = resourceSchema(r, ast)
    38  			if err != nil {
    39  				return res, err
    40  			}
    41  		}
    42  		res.Resources[defName] = x
    43  	}
    44  
    45  	// make sure every resource and field has the provider set
    46  	for _, v := range res.Resources {
    47  		v.Provider = provider
    48  		for _, field := range v.Fields {
    49  			field.Provider = provider
    50  		}
    51  	}
    52  
    53  	// In this block we finalize the schema. This means:
    54  	// 1: create implicit resources (eg: sshd.config => create sshd)
    55  	// 2: create implicit fields (eg: sshd.config => sshd { config: {..} })
    56  	for name, v := range res.Resources {
    57  		if !strings.Contains(name, ".") {
    58  			continue
    59  		}
    60  
    61  		rem := name
    62  		fieldInfo := v
    63  		isPrivate := v.Private
    64  		for {
    65  			last := strings.LastIndex(rem, ".")
    66  			if last == -1 {
    67  				break
    68  			}
    69  
    70  			resource := rem
    71  			basename := rem[last+1:]
    72  			rem = rem[:last]
    73  
    74  			child, ok := res.Resources[rem]
    75  			if !ok {
    76  				child = &resources.ResourceInfo{
    77  					Id:          rem,
    78  					Fields:      map[string]*resources.Field{},
    79  					IsExtension: true,
    80  					// Resource extensions do not set the provider. They are here to
    81  					// indicate that it bridges the resource chain, but it cannot
    82  					// initialize this resource! This is why no provider is set.
    83  				}
    84  				res.Resources[rem] = child
    85  			}
    86  
    87  			if _, ok := child.Fields[basename]; !ok {
    88  				child.Fields[basename] = &resources.Field{
    89  					Name:               basename,
    90  					Type:               string(types.Resource(resource)),
    91  					IsMandatory:        false, // it cannot be mandatory if we create it here
    92  					IsImplicitResource: true,
    93  					IsPrivate:          isPrivate,
    94  					Title:              fieldInfo.Title,
    95  					Desc:               fieldInfo.Desc,
    96  					Provider:           provider,
    97  				}
    98  			}
    99  
   100  			// Some of the call-chain might have been created by other resources.
   101  			// If this resource, however, is not private, then it must be accessible
   102  			// through the callchain.
   103  			if !isPrivate {
   104  				child.Fields[basename].IsPrivate = false
   105  			}
   106  
   107  			fieldInfo = child
   108  		}
   109  	}
   110  
   111  	return res, nil
   112  }
   113  
   114  func resourceInit(r *Resource, fields map[string]*resources.Field, ast *LR) (*resources.Init, error) {
   115  	inits := r.GetInitFields()
   116  	if len(inits) == 0 {
   117  		return nil, nil
   118  	}
   119  
   120  	args := []*resources.TypedArg{}
   121  	i := inits[0]
   122  	isOptional := false
   123  	for _, arg := range i.Args {
   124  		typ := arg.Type.Type(ast)
   125  		if typ == types.Unset {
   126  			return nil, errors.New("A field in the init that isn't found in the resource must have a type assigned. Field \"" + arg.ID + "\"")
   127  		}
   128  
   129  		ref, ok := fields[arg.ID]
   130  		if ok {
   131  			ftype := ref.Type
   132  			if string(typ) != ftype {
   133  				return nil, errors.New("Init field type and resource field type are different: " + r.ID + " field " + arg.ID)
   134  			}
   135  		}
   136  
   137  		if arg.Optional {
   138  			isOptional = true
   139  		} else if isOptional {
   140  			return nil, errors.New("A required argument cannot follow an optional argument. Found in init function of " + r.ID)
   141  		}
   142  
   143  		args = append(args, &resources.TypedArg{
   144  			Name:     arg.ID,
   145  			Type:     string(typ),
   146  			Optional: arg.Optional,
   147  		})
   148  	}
   149  
   150  	return &resources.Init{Args: args}, nil
   151  }
   152  
   153  func resourceFields(r *Resource, ast *LR) map[string]*resources.Field {
   154  	fields := make(map[string]*resources.Field)
   155  
   156  	for _, f := range r.Body.Fields {
   157  		if f.BasicField == nil {
   158  			continue
   159  		}
   160  		refs := []string{}
   161  
   162  		if f.BasicField.Args != nil && len(f.BasicField.Args.List) > 0 {
   163  			for _, arg := range f.BasicField.Args.List {
   164  				refs = append(refs, "\""+arg.Type+"\"")
   165  			}
   166  		}
   167  
   168  		f.Comments = SanitizeComments(f.Comments)
   169  		title, desc := extractTitleAndDescription(f.Comments)
   170  		fields[f.BasicField.ID] = &resources.Field{
   171  			Name:        f.BasicField.ID,
   172  			Type:        string(f.BasicField.Type.Type(ast)),
   173  			IsMandatory: f.BasicField.isStatic(),
   174  			Title:       title,
   175  			Desc:        desc,
   176  			Refs:        refs,
   177  			IsEmbedded:  f.BasicField.isEmbedded,
   178  		}
   179  	}
   180  
   181  	return fields
   182  }
   183  
   184  func resourceSchema(r *Resource, ast *LR) (*resources.ResourceInfo, error) {
   185  	fields := resourceFields(r, ast)
   186  	init, err := resourceInit(r, fields, ast)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	if init != nil && r.IsExtension {
   192  		return nil, errors.New("Resource '" + r.ID + "' as an init method AND is flagged as 'extends'. You cannot do both at the same time. Either this resource extends another or it is the root resource that gets extended.")
   193  	}
   194  
   195  	res := &resources.ResourceInfo{
   196  		Id:          r.ID,
   197  		Name:        r.ID,
   198  		Title:       r.title,
   199  		Desc:        r.desc,
   200  		Init:        init,
   201  		Private:     r.IsPrivate,
   202  		IsExtension: r.IsExtension,
   203  		Fields:      fields,
   204  		Defaults:    r.Defaults,
   205  	}
   206  
   207  	if r.ListType != nil {
   208  		res.ListType = string(r.ListType.Type.typeItems(ast))
   209  	}
   210  
   211  	return res, nil
   212  }