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 }