Skip to content

Commit

Permalink
Merge pull request #32 from kosmas-valianos/parserFix
Browse files Browse the repository at this point in the history
Add support for hyphen and underscore for the Ident
  • Loading branch information
kosmas-valianos authored Aug 30, 2024
2 parents 1bc89a0 + 23ae7f6 commit 83c837e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 57 deletions.
22 changes: 11 additions & 11 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ module github.com/kosmas-valianos/gcloudfilter
go 1.22.4

require (
cloud.google.com/go/compute v1.27.0
cloud.google.com/go/resourcemanager v1.9.7
cloud.google.com/go/compute v1.28.0
cloud.google.com/go/resourcemanager v1.10.0
github.com/alecthomas/participle/v2 v2.1.1
google.golang.org/protobuf v1.34.2
)

require (
cloud.google.com/go/iam v1.1.8 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 // indirect
google.golang.org/grpc v1.64.1 // indirect
cloud.google.com/go/iam v1.2.0 // indirect
cloud.google.com/go/longrunning v0.6.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto v0.0.0-20240827150818-7e3bb234dfed // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect
google.golang.org/grpc v1.66.0 // indirect
)
44 changes: 22 additions & 22 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
cloud.google.com/go/compute v1.27.0 h1:EGawh2RUnfHT5g8f/FX3Ds6KZuIBC77hZoDrBvEZw94=
cloud.google.com/go/compute v1.27.0/go.mod h1:LG5HwRmWFKM2C5XxHRiNzkLLXW48WwvyVC0mfWsYPOM=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
cloud.google.com/go/resourcemanager v1.9.7 h1:SdvD0PaPX60+yeKoSe16mawFpM0EPuiPPihTIVlhRsY=
cloud.google.com/go/resourcemanager v1.9.7/go.mod h1:cQH6lJwESufxEu6KepsoNAsjrUtYYNXRwxm4QFE5g8A=
cloud.google.com/go/compute v1.28.0 h1:OPtBxMcheSS+DWfci803qvPly3d4w7Eu5ztKBcFfzwk=
cloud.google.com/go/compute v1.28.0/go.mod h1:DEqZBtYrDnD5PvjsKwb3onnhX+qjdCVM7eshj1XdjV4=
cloud.google.com/go/iam v1.2.0 h1:kZKMKVNk/IsSSc/udOb83K0hL/Yh/Gcqpz+oAkoIFN8=
cloud.google.com/go/iam v1.2.0/go.mod h1:zITGuWgsLZxd8OwAlX+eMFgZDXzBm7icj1PVTYG766Q=
cloud.google.com/go/longrunning v0.6.0 h1:mM1ZmaNsQsnb+5n1DNPeL0KwQd9jQRqSqSDEkBZr+aI=
cloud.google.com/go/longrunning v0.6.0/go.mod h1:uHzSZqW89h7/pasCWNYdUpwGz3PcVWhrWupreVPYLts=
cloud.google.com/go/resourcemanager v1.10.0 h1:oqO6UInOJ1ZBBEYTKPJms2+FKdGmZEYAYBKyt0oqpEI=
cloud.google.com/go/resourcemanager v1.10.0/go.mod h1:kIx3TWDCjLnUQUdjQ/e8EXsS9GJEzvcY+YMOHpADxrk=
github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
Expand All @@ -16,19 +16,19 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4 h1:CUiCqkPw1nNrNQzCCG4WA65m0nAmQiwXHpub3dNyruU=
google.golang.org/genproto v0.0.0-20240617180043-68d350f18fd4/go.mod h1:EvuUDCulqGgV80RvP1BHuom+smhX4qtlhnNatHuroGQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc=
google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/genproto v0.0.0-20240827150818-7e3bb234dfed h1:4C4dbrVFtfIp3GXJdMX1Sj25mahfn5DywOo65/2ISQ8=
google.golang.org/genproto v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:ICjniACoWvcDz8c8bOsHVKuuSGDJy1z5M4G0DM3HzTc=
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0=
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
73 changes: 68 additions & 5 deletions lexer_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

var parser = participle.MustBuild[grammar](
participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{
{Name: "Ident", Pattern: `-?[a-zA-Z\*]+|\*`},
{Name: "Ident", Pattern: `-?[a-zA-Z_\*-]+|\*`},
{Name: "List", Pattern: `\([^\(^\)]*\)`},
{Name: "QuotedLiteral", Pattern: `"[^"]*"|'[^']*'`},
{Name: "FloatingPointNumericConstant", Pattern: `[-+]?(\d+\.\d*|\.\d+)([eE][-+]?\d+)?`},
Expand Down Expand Up @@ -322,8 +322,9 @@ func wildcardToRegexp(pattern string) string {
return "^" + result.String() + "$"
}

func isOperator(ch byte) bool {
operators := [...]byte{':', '=', '<', '>', '~', '('}
var operators = [...]byte{':', '=', '<', '>', '~', '('}

func isOperator(ch byte, operators []byte) bool {
for i := range operators {
if operators[i] == ch {
return true
Expand All @@ -332,6 +333,68 @@ func isOperator(ch byte) bool {
return false
}

func wrapValuesWithParentheses(gcpFilter string) string {
var sb strings.Builder
sb.Grow(len(gcpFilter) + 64)
var quoted, operator, wrap bool
for i, ch := range gcpFilter {
if isOperator(gcpFilter[i], operators[:len(operators)-1]) {
operator = true
sb.WriteRune(ch)
} else if ch == '"' || ch == '\'' || ch == '(' {
// Mark to not do anything when quoted or parenthesized already
if quoted {
// End of quote
quoted = false
operator = false
} else {
// Start of quote
quoted = true
}
sb.WriteRune(ch)
} else if operator {
// Inside AttributeValue
if ch == '*' {
// No parentheses wrap in existense checks e.g. -labels.foo:*
sb.WriteRune(ch)
operator = false
} else if !quoted {
if !wrap {
// Open parentheses
sb.WriteRune('(')
sb.WriteRune(ch)
// Close parentheses. End of terms
if i == len(gcpFilter)-1 {
sb.WriteRune(')')
}
wrap = true
} else if ch == ' ' {
// Close parentheses. More terms follow
sb.WriteRune(')')
sb.WriteRune(ch)
wrap = false
operator = false
} else if i == len(gcpFilter)-1 {
// Close parentheses. End of terms
sb.WriteRune(ch)
sb.WriteRune(')')
wrap = false
operator = false
} else {
sb.WriteRune(ch)
}
} else {
// Do to not do anything when quoted or parenthesized already
sb.WriteRune(ch)
}
} else {
// Non AttributeValue characters. Keys, logical operators etc.
sb.WriteRune(ch)
}
}
return sb.String()
}

func quoteStringValues(gcpFilter string) string {
var sb strings.Builder
sb.Grow(len(gcpFilter) + 64)
Expand All @@ -346,7 +409,7 @@ func quoteStringValues(gcpFilter string) string {
continue
}

if isOperator(gcpFilter[i]) {
if isOperator(gcpFilter[i], operators[:]) {
operator = true
sb.WriteRune(ch)
} else if operator {
Expand Down Expand Up @@ -452,7 +515,7 @@ func (r resource[C]) filter() (bool, error) {

func (r resource[C]) filterResourceSubExpression(gcpFilter string) (bool, error) {
// Parse the string from gcpFilter into grammar
grammar, err := parser.ParseString("", quoteStringValues(gcpFilter))
grammar, err := parser.ParseString("", wrapValuesWithParentheses(quoteStringValues(gcpFilter)))
if err != nil {
return false, err
}
Expand Down
17 changes: 9 additions & 8 deletions lexer_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ func TestParse(t *testing.T) {
{
name: "Complex",
args: args{
gcpFilter: `labels.color="red" OR parent.id:2.5E+10 parent.id:-56 OR name:HOWL* AND name:'bOWL*'`,
gcpFilter: `labels.c-ol_or="red" OR parent.id:2.5E+10 parent.id:-56 OR name:HOWL* AND name:'bOWL*'`,
},
want: `{"terms":[{"key":"labels","attribute-key":"color","operator":"=","value":{"literal":"red"},"logical-operator":"OR"},{"key":"parent","attribute-key":"id","operator":":","value":{"number":25000000000}},{"key":"parent","attribute-key":"id","operator":":","value":{"number":-56},"logical-operator":"OR"},{"key":"name","operator":":","value":{"literal":"^HOWL.*$"},"logical-operator":"AND"},{"key":"name","operator":":","value":{"literal":"^bOWL.*$"}}]}`,
want: `{"terms":[{"key":"labels","attribute-key":"c-ol_or","operator":"=","value":{"literal":"red"},"logical-operator":"OR"},{"key":"parent","attribute-key":"id","operator":":","values":{"values":[{"number":25000000000}]}},{"key":"parent","attribute-key":"id","operator":":","values":{"values":[{"number":-56}]},"logical-operator":"OR"},{"key":"name","operator":":","value":{"literal":"^HOWL.*$"},"logical-operator":"AND"},{"key":"name","operator":":","value":{"literal":"^bOWL.*$"}}]}`,
},
{
name: "Key defined, Key undefined, Values' list",
args: args{
gcpFilter: `labels.smell:* AND -labels.volume:* labels.size=("small" 'big' 2.5E+10) OR labels.cpu:("sm*all" '*big' 2.5E+10)`,
gcpFilter: `labels.smell:* AND -labels.volume:* labels.size=(small 'big' 2.5E+10) OR labels.cpu:("sm*all" '*big' 2.5E+10)`,
},
want: `{"terms":[{"key":"labels","attribute-key":"smell","operator":":","value":{"literal":"*"},"logical-operator":"AND"},{"negation":true,"key":"labels","attribute-key":"volume","operator":":","value":{"literal":"*"}},{"key":"labels","attribute-key":"size","operator":"=","values":{"values":[{"literal":"small"},{"literal":"big"},{"number":25000000000}]},"logical-operator":"OR"},{"key":"labels","attribute-key":"cpu","operator":":","values":{"values":[{"literal":"^sm.*all$"},{"literal":"^.*big$"},{"number":25000000000}]}}]}`,
},
Expand All @@ -49,26 +49,27 @@ func TestParse(t *testing.T) {
args: args{
gcpFilter: `labels.size >= 50 OR name ~ how* OR name !~ b*ol*`,
},
want: `{"terms":[{"key":"labels","attribute-key":"size","operator":"\u003e=","value":{"number":50},"logical-operator":"OR"},{"key":"name","operator":"~","value":{"literal":"how*"},"logical-operator":"OR"},{"key":"name","operator":"!~","value":{"literal":"b*ol*"}}]}`,
want: `{"terms":[{"key":"labels","attribute-key":"size","operator":"\u003e=","values":{"values":[{"number":50}]},"logical-operator":"OR"},{"key":"name","operator":"~","value":{"literal":"how*"},"logical-operator":"OR"},{"key":"name","operator":"!~","value":{"literal":"b*ol*"}}]}`,
},
{
name: "Negations",
args: args{
gcpFilter: `NOT labels.volume:* AND -labels.color:*`,
gcpFilter: `NOT labels.volume:* AND -labels.c-ol_or:*`,
},
want: `{"terms":[{"negation":true,"key":"labels","attribute-key":"volume","operator":":","value":{"literal":"*"},"logical-operator":"AND"},{"negation":true,"key":"labels","attribute-key":"color","operator":":","value":{"literal":"*"}}]}`,
want: `{"terms":[{"negation":true,"key":"labels","attribute-key":"volume","operator":":","value":{"literal":"*"},"logical-operator":"AND"},{"negation":true,"key":"labels","attribute-key":"c-ol_or","operator":":","value":{"literal":"*"}}]}`,
},
{
name: "Parse error",
args: args{
gcpFilter: `NOT labels.volume:* AND --labels.color:*`,
gcpFilter: `NOT labels.volume:* AND labels.c-ol_or/*`,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter, err := parser.ParseString("", quoteStringValues(tt.args.gcpFilter))
filter, err := parser.ParseString("", wrapValuesWithParentheses(quoteStringValues(tt.args.gcpFilter)))
t.Log(filter)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
21 changes: 16 additions & 5 deletions projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,23 @@ func (g gcpProject) filterTerm(t term) (bool, error) {
// e.g. id:appgate-dev
return t.evaluate(g.project.GetProjectId())
case "state", "lifecyclestate":
// e.g. state:ACTIVE
if t.Value.Literal != nil {
return t.evaluate(g.project.GetState().String())
if t.ValuesList != nil {
if t.ValuesList.Values[0].Literal != nil {
// e.g. state:ACTIVE
return t.evaluate(g.project.GetState().String())
} else {
// e.g. state:1
return t.evaluate(fmt.Sprint(g.project.GetState().Number()))
}
} else {
if t.Value.Literal != nil {
// e.g. state:ACTIVE
return t.evaluate(g.project.GetState().String())
} else {
// e.g. state:1
return t.evaluate(fmt.Sprint(g.project.GetState().Number()))
}
}
// e.g. state:1
return t.evaluate(fmt.Sprint(g.project.GetState().Number()))
case "displayname", "name":
return t.evaluate(g.project.GetDisplayName())
case "createtime":
Expand Down
47 changes: 41 additions & 6 deletions projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ func TestFilterProjects(t *testing.T) {
CreateTime: timestamppb.Now(),
Etag: `W/"50f1fa462f4ec213"`,
Labels: map[string]string{
"color": "red",
"volume": "big",
"cpu": "Intel",
"size": "100",
"color": "red",
"volume": "big",
"cpu": "Intel",
"size": "100",
"d-x_c": "-100",
"ad_group": "cets-gg-sso-isc-gcp-pre-arb-devsecops",
},
},
{
Expand All @@ -73,6 +75,20 @@ func TestFilterProjects(t *testing.T) {
"size": "-2.5E+10",
},
},
{
Name: "projects/82699087621",
Parent: "organizations/448593862442",
ProjectId: "clgx-gateway-app-prf-47f4",
State: 1,
DisplayName: "clgx- Foo",
CreateTime: timestamppb.Now(),
Etag: `W/"50f1fa462f4ec213"`,
Labels: map[string]string{
"cop_ad_group": "corp-hh-insurance_uv_gr-commercialurban-databaseops",
"gcp_project_name": "clgx-gateway-app-dev",
"ad_group": "certs-hh-sso-isb-gcp-binportal-pgres_dot_dev-devsecops",
},
},
}

type args struct {
Expand Down Expand Up @@ -105,17 +121,18 @@ func TestFilterProjects(t *testing.T) {
{
name: "Timestamp, State",
args: args{
gcpFilter: "createTime <= " + fmt.Sprintf("\"%v\"", time.Now().UTC().Format(time.RFC3339)) + " AND state>=1 AND state=ACTIVE",
gcpFilter: "createTime <= " + fmt.Sprintf("\"%v\"", time.Now().UTC().Format(time.RFC3339)) + " AND state>=1 AND state=ACTIVE state!=5",
},
wantProjects: projectsArray{
projects[0],
projects[1],
projects[2],
},
},
{
name: "Conjuction having lower precedence than OR - 0",
args: args{
gcpFilter: `labels.volume:medium labels.color:red OR labels.color:blue state=1 labels.cpu:* OR -labels.foo:*`,
gcpFilter: `labels.volume:medium labels.color:red OR labels.color:blue state=(1,2,3) labels.cpu:* OR -labels.foo:*`,
},
wantProjects: projectsArray{
projects[1],
Expand All @@ -140,6 +157,24 @@ func TestFilterProjects(t *testing.T) {
projects[0],
},
},
{
name: "hyphen and underscore in labels - 0",
args: args{
gcpFilter: `name:appgate* AND labels.ad_group:cets-gg-sso-isc-gcp-pre-arb-devsecops -labels.abcd:* labels.d-x_c:-100`,
},
wantProjects: projectsArray{
projects[0],
},
},
{
name: "hyphen and underscore in labels - 1",
args: args{
gcpFilter: `name:clgx-* AND labels.cop_ad_group:corp-hh-insurance_uv_gr-commercialurban-databaseops labels.gcp_project_name:(clgx-gateway-app-de* "foo") labels.ad_group=certs-hh-sso-isb-gcp-binportal-pgres_dot_dev-devsecops`,
},
wantProjects: projectsArray{
projects[2],
},
},
{
name: "Unbalanced parentheses",
args: args{
Expand Down

0 comments on commit 83c837e

Please sign in to comment.