From 638b9e7ffc875b63984b57e31f78fa241dc85169 Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Tue, 11 Feb 2025 21:55:42 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20version(..,=20type:=20"semver")?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` cnquery> version("1.2.3", type: 'semver') version: 1.2.3 cnquery> version("1:1.2.3", type: 'semver') version '1:1.2.3' is not a semantic version version: 1:1.2.3 cnquery> version("1:1.2.3") version: 1:1.2.3 ``` Also adds support for parsing named arguments on builtin type conversion functions (like `version`). Signed-off-by: Dominik Richter --- llx/builtin_global.go | 45 ++++++++++++++++++++++++++-- llx/builtin_version.go | 4 +++ mqlc/typemaps.go | 27 ++++++++++++----- providers/core/resources/mql_test.go | 15 ++++++++++ 4 files changed, 81 insertions(+), 10 deletions(-) diff --git a/llx/builtin_global.go b/llx/builtin_global.go index 2a1edf18c..3be71ec3d 100644 --- a/llx/builtin_global.go +++ b/llx/builtin_global.go @@ -6,6 +6,7 @@ package llx import ( "errors" "strconv" + "strings" "go.mondoo.com/cnquery/v11/types" ) @@ -197,14 +198,52 @@ func typeofCallV2(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, } func versionCall(e *blockExecutor, f *Function, ref uint64) (*RawData, uint64, error) { - if len(f.Args) != 1 { - return nil, 0, errors.New("Called `version` with " + strconv.Itoa(len(f.Args)) + " arguments, expected one") + if len(f.Args) == 0 { + return nil, 0, errors.New("called `version` with no arguments, expected one") } - res, dref, err := e.resolveValue(f.Args[0], ref) + arg := f.Args[0] + if arg.Type != string(types.String) { + return nil, 0, errors.New("called `version` with incorrect argument type, expected string") + } + + res, dref, err := e.resolveValue(arg, ref) if err != nil || dref != 0 || res == nil { return res, dref, err } + raw, ok := res.Value.(string) + if !ok { + return nil, 0, errors.New("called `version` with unsupported type (expected string)") + } + + version := NewVersion(raw) + + for i := 1; i < len(f.Args); i++ { + arg := f.Args[i] + if !types.Type(arg.Type).IsMap() { + return nil, 0, errors.New("called `version` with unknown argument, expected one string") + } + for k, v := range arg.Map { + switch k { + case "type": + t, ok := v.RawData().Value.(string) + if !ok { + return nil, 0, errors.New("unsupported `type` value in `version` call") + } + typ := strings.ToLower(t) + switch typ { + case "semver": + if !version.IsSemver() { + return &RawData{Error: errors.New("version '" + raw + "' is not a semantic version"), Value: raw, Type: types.Version}, 0, nil + } + case "all": + break + default: + return nil, 0, errors.New("unauppoerws `type=" + t + "` in `version` call") + } + } + } + } return &RawData{Type: types.Version, Value: res.Value}, 0, nil } diff --git a/llx/builtin_version.go b/llx/builtin_version.go index a9b705ed8..2aa0599fd 100644 --- a/llx/builtin_version.go +++ b/llx/builtin_version.go @@ -37,6 +37,10 @@ func NewVersion(s string) Version { } } +func (v *Version) IsSemver() bool { + return v.Version != nil && v.epoch == 0 +} + func parseEpoch(v string) (*semver.Version, int) { prefix := reEpoch.FindString(v) if prefix == "" { diff --git a/mqlc/typemaps.go b/mqlc/typemaps.go index 57a34cac4..525eb5bf0 100644 --- a/mqlc/typemaps.go +++ b/mqlc/typemaps.go @@ -36,14 +36,27 @@ func compileTypeConversion(llxID string, typ types.Type) fieldCompiler { return types.Nil, errNotConversion } - arg := call.Function[0] - if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil { - return types.Nil, errors.New("failed to get parameter for '" + id + "'") + otherMap := map[string]*llx.Primitive{} + args := []*llx.Primitive{} + for i := range call.Function { + arg := call.Function[i] + if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil { + return types.Nil, errors.New("failed to get parameter for '" + id + "'") + } + + argValue, err := c.compileExpression(arg.Value) + if err != nil { + return types.Nil, err + } + if arg.Name != "" { + otherMap[arg.Name] = argValue + } else { + args = append(args, argValue) + } } - argValue, err := c.compileExpression(arg.Value) - if err != nil { - return types.Nil, err + if len(otherMap) != 0 { + args = append(args, llx.MapPrimitive(otherMap, types.Any)) } c.addChunk(&llx.Chunk{ @@ -51,7 +64,7 @@ func compileTypeConversion(llxID string, typ types.Type) fieldCompiler { Id: llxID, Function: &llx.Function{ Type: string(typ), - Args: []*llx.Primitive{argValue}, + Args: args, }, }) diff --git a/providers/core/resources/mql_test.go b/providers/core/resources/mql_test.go index 471bde072..2ee32bdda 100644 --- a/providers/core/resources/mql_test.go +++ b/providers/core/resources/mql_test.go @@ -997,6 +997,21 @@ func TestVersion(t *testing.T) { }, }) }) + + t.Run("version type set to semver", func(t *testing.T) { + x.TestSimple(t, []testutils.SimpleTest{ + { + Code: "version('1.2.3', type: 'semver')", + ResultIndex: 2, Expectation: "1.2.3.", + }, + }) + x.TestSimpleErrors(t, []testutils.SimpleTest{ + { + Code: "version('1:1.2.3', type: 'semver')", + ResultIndex: 2, Expectation: "error", + }, + }) + }) } func TestResource_Default(t *testing.T) {