diff --git a/cmd/grafana-app-sdk/generate.go b/cmd/grafana-app-sdk/generate.go index 8cc782f0..225445b6 100644 --- a/cmd/grafana-app-sdk/generate.go +++ b/cmd/grafana-app-sdk/generate.go @@ -31,6 +31,7 @@ var generateCmd = &cobra.Command{ RunE: generateCmdFunc, } +//nolint:goconst func setupGenerateCmd() { generateCmd.PersistentFlags().StringP("gogenpath", "g", "pkg/generated/", "Path to directory where generated go code will reside") diff --git a/codegen/jennies/schema.go b/codegen/jennies/schema.go index 84bb9150..93224f2a 100644 --- a/codegen/jennies/schema.go +++ b/codegen/jennies/schema.go @@ -6,7 +6,9 @@ import ( "fmt" "go/format" "path/filepath" + "strings" + "cuelang.org/go/cue" "github.com/grafana/codejen" "github.com/grafana/grafana-app-sdk/codegen" @@ -46,15 +48,19 @@ func (s *SchemaGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { files := make(codejen.Files, 0) if s.OnlyUseCurrentVersion { + sf, err := s.getSelectableFields(kind.Version(meta.Current)) + if err != nil { + return nil, err + } b := bytes.Buffer{} - err := templates.WriteSchema(templates.SchemaMetadata{ + err = templates.WriteSchema(templates.SchemaMetadata{ Package: meta.MachineName, Group: meta.APIResource.Group, Version: meta.Current, Kind: meta.Kind, Plural: meta.PluralMachineName, Scope: meta.APIResource.Scope, - SelectableFields: kind.Version(meta.Current).SelectableFields, + SelectableFields: sf, FuncPrefix: prefix, }, &b) if err != nil { @@ -71,15 +77,19 @@ func (s *SchemaGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { }) } else { for _, ver := range kind.Versions() { + sf, err := s.getSelectableFields(&ver) + if err != nil { + return nil, err + } b := bytes.Buffer{} - err := templates.WriteSchema(templates.SchemaMetadata{ + err = templates.WriteSchema(templates.SchemaMetadata{ Package: ToPackageName(ver.Version), Group: meta.APIResource.Group, Version: ver.Version, Kind: meta.Kind, Plural: meta.PluralMachineName, Scope: meta.APIResource.Scope, - SelectableFields: ver.SelectableFields, + SelectableFields: sf, FuncPrefix: prefix, }, &b) if err != nil { @@ -99,3 +109,44 @@ func (s *SchemaGenerator) Generate(kind codegen.Kind) (codejen.Files, error) { return files, nil } + +func (*SchemaGenerator) getSelectableFields(ver *codegen.KindVersion) ([]templates.SchemaMetadataSeletableField, error) { + fields := make([]templates.SchemaMetadataSeletableField, 0) + if len(ver.SelectableFields) == 0 { + return fields, nil + } + // Check each field in the CUE (TODO: make this OpenAPI instead?) to check if the field is optional + for _, s := range ver.SelectableFields { + fieldPath := s + if len(s) > 1 && s[0] == '.' { + fieldPath = s[1:] + } + parts := strings.Split(fieldPath, ".") + if len(parts) <= 1 { + return nil, fmt.Errorf("invalid selectable field path: %s", s) + } + field := parts[len(parts)-1] + parts = parts[:len(parts)-1] + path := make([]cue.Selector, 0) + for _, p := range parts { + path = append(path, cue.Str(p)) + } + if val := ver.Schema.LookupPath(cue.MakePath(path...).Optional()); val.Err() == nil { + // Simplest way to check if it's an optional field is to try to look it up as non-optional, then try optional + if lookup := val.LookupPath(cue.MakePath(cue.Str(field))); lookup.Exists() { + fields = append(fields, templates.SchemaMetadataSeletableField{ + Field: s, + Optional: false, + }) + } else if optional := val.LookupPath(cue.MakePath(cue.Str(field).Optional())); optional.Exists() { + fields = append(fields, templates.SchemaMetadataSeletableField{ + Field: s, + Optional: true, + }) + } else { + return nil, fmt.Errorf("invalid selectable field path: %s", fieldPath) + } + } + } + return fields, nil +} diff --git a/codegen/templates/schema.tmpl b/codegen/templates/schema.tmpl index 866bf865..286da9de 100644 --- a/codegen/templates/schema.tmpl +++ b/codegen/templates/schema.tmpl @@ -14,13 +14,17 @@ import ( var ( schema{{.Kind}} = resource.NewSimpleSchema("{{.Group}}", "{{.Version}}", &{{.Kind}}{}, &{{.Kind}}List{}, resource.WithKind("{{.Kind}}"), resource.WithPlural("{{.Plural}}"), resource.WithScope(resource.{{.Scope}}Scope) {{if gt $sfl 0}}, resource.WithSelectableFields([]resource.SelectableField{ {{ range .SelectableFields }}resource.SelectableField{ - FieldSelector: "{{.}}", + FieldSelector: "{{.Field}}", FieldValueFunc: func(o resource.Object) (string, error) { cast, ok := o.(*{{$root.Kind}}) if !ok { return "", fmt.Errorf("provided object must be of type *{{$root.Kind}}") + }{{ if .Optional }} + if cast.{{$root.ToObjectPath .Field}} == nil { + return "", nil } - return cast.{{$root.ToObjectPath .}}, nil + return *cast.{{$root.ToObjectPath .Field}}, nil{{ else }} + return cast.{{$root.ToObjectPath .Field}}, nil{{ end }} }, }, {{ end }} }){{ end }}) diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 9930c1e3..a9f3bbb5 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -115,12 +115,20 @@ type SchemaMetadata struct { Kind string Plural string Scope string - SelectableFields []string + SelectableFields []SchemaMetadataSeletableField FuncPrefix string } +type SchemaMetadataSeletableField struct { + Field string + Optional bool +} + func (SchemaMetadata) ToObjectPath(s string) string { parts := make([]string, 0) + if len(s) > 0 && s[0] == '.' { + s = s[1:] + } for i, part := range strings.Split(s, ".") { if i == 0 && part == "metadata" { part = "ObjectMeta"