Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write location parser for Mutations #946

Merged
merged 5 commits into from
Nov 18, 2020

Conversation

shomron
Copy link
Contributor

@shomron shomron commented Nov 7, 2020

The location parser parses the location specification language used
for matching Kubernetes object fields.

Example:

p := NewParser("spec.containers[name:*].securityContext")
root, err := p.Parse()

Closes #915

Signed-off-by: Oren Shomron shomron@gmail.com

@shomron
Copy link
Contributor Author

shomron commented Nov 7, 2020

@maxsmythe @fedepaol @ritazh @sozercan @brycecr here's a first go at the location parser.
There are a couple of questions in the parser test cases as to whether we should prevent certain syntax.

Also - this language needs a name. Let me know your thoughts! 🙂

@fedepaol
Copy link
Contributor

fedepaol commented Nov 9, 2020

Also - this language needs a name. Let me know your thoughts! slightly_smiling_face

Wasn't it the max's syntax :P ?

Copy link
Contributor

@fedepaol fedepaol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few comments

pkg/path/parser/doc.go Outdated Show resolved Hide resolved
err error
}

func NewParser(input string) *Parser {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a consumer perspective, the flow will always be

p := NewParser("string")
p.Parse()

I think it would be better to have a single exported Parse function that hides the parser.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

pkg/path/parser/parser_test.go Outdated Show resolved Hide resolved
},
},
{
input: `"🤔☕️❗️"`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-)

// expect returns whether the next token matches our expectation,
// and if so advances to that token.
// Otherwise returns false and doesn't advance.
func (p *Parser) expect(t token.Type) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The for loop below is a bit hard to get without reading this comment first.
Would it make sense to use only expectPeek (and maybe change to something less "assertive" like peekMatches) and explicitly advance via p.next() ?

Or even better, take the "peek" value and use it as the value to switch on? I think the result will be more understandable.

Copy link
Contributor Author

@shomron shomron Nov 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Combining assertion with advancing through the tokens seems like a pattern that's out there, and combining that with optional matching felt like it streamlined the code and removed boilerplate.
I'm not married to this - but are there other ways of improving clarify? Can the function name be more descriptive? Can the comments be better, or would a description of the parser at the top help?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think the thing that confused me the most was the hidden side effect of advancing when the expectation is matched. A comment before the switch, or a better name will probably make it clearer.

Now, the hard part is to find a decent name :-)
Something like advanceWhenNext, or advanceIfNext maybe. But I am also fine with adding a comment before the switch if we don't find a better alternative, I don't want this to be blocking.

pkg/path/parser/node.go Outdated Show resolved Hide resolved
Copy link
Contributor

@maxsmythe maxsmythe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! Very clean.

re: Name

"Max's Syntax" <- ugh :p that'll either work real well or really poorly!

"Max's unplanned location-focused operator controls" -> Muloc?

"Shomron's Folly"? :p

"Put things here" -> PTH

"Yet another path language" -> YAPL

pkg/path/parser/doc.go Outdated Show resolved Hide resolved
pkg/path/parser/node.go Outdated Show resolved Hide resolved
}

func isSpace(r rune) bool {
return r == ' ' || r == '\t'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also skip newlines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easy enough.

case ':':
tok = Token{Type: COLON, Literal: string(s.ch)}
case '"', '\'':
tok.Type = IDENT
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is IDENT a well-known abbreviation? I think it means "identity" or "identifier" in this case?

Not sure that that is the most apt name given it can hold:

  • A proper identifier in the form of a field name
  • A value in the form of a key-field value

No better name is coming to mind though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDENT is short for identifier, and in this case means a name that is not a keyword. I've seen that shorthand in many parsers, for example here's a search across the go source.

Our language doesn't need to differentiate between a string and an identifier so I decide not to define a separate string token. Not sure whether it's textbook but it seemed to make sense at the time ¯_(ツ)_/¯

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM, if we think of a better name we can change this later. literal maybe? I don't love any of `em

},
},
{
input: `Moo🐙`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a syntax error.

The octopus does NOT say moo

pkg/path/parser/node.go Outdated Show resolved Hide resolved
pkg/path/parser/node.go Outdated Show resolved Hide resolved
},
},
{
// TODO: Is this useful or should we make it a parsing error?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's a parsing error.

I could see someone needing to reference a nil-valued var, would we be able to have a "nil" keyword?

Part of the reason for requiring quotes post-colon is to reserve the possibility of other special keywords.

Another option could be to have an anti-quote (e.g. a backtic) to signify "this value is a JS keyword"

},
},
{
// TODO: Should this be allowed?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be allowed. It's impossible to have a list-of-lists, as an intermediate object is required to store the key fields:

{
   "spec": [
      {
           "myKey": "some-value",
           "nestedList": [
                 {"secondKey": "secondValue"}
           ]
}

Note the need to access nestedList

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consolidate imports

pkg/path/parser/node.go Outdated Show resolved Hide resolved
return "", false
}
return *l.KeyValue, true
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming is a bit of what we have in the [interface design doc] (also the additional Path type)(https://docs.google.com/document/d/1c5Z3g6Zsfmga7xod4--t6RrXamdAaMaqYzPNujtZb10/edit?ts=5f98003c)
Can we either update the PR or the doc?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the doc

expectErr: true,
},
{
// List cannot follow valid list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want to check pathentries that end in a globbed list? That should also be disallowed, as then we would be setting multiple objects to the exact same value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm - I'm not sure that be a parse error, but perhaps a validation to apply at a higher level?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a fair thought, I think the path tests will allow glob-terminated locations.

@codecov-io
Copy link

codecov-io commented Nov 17, 2020

Codecov Report

Merging #946 (15cb728) into master (5320da3) will increase coverage by 2.18%.
The diff coverage is 90.13%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #946      +/-   ##
==========================================
+ Coverage   43.57%   45.76%   +2.18%     
==========================================
  Files          52       56       +4     
  Lines        3222     3374     +152     
==========================================
+ Hits         1404     1544     +140     
- Misses       1622     1634      +12     
  Partials      196      196              
Flag Coverage Δ
unittests 45.76% <90.13%> (+2.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
pkg/mutation/mutator.go 77.77% <ø> (ø)
pkg/mutation/path/parser/node.go 0.00% <0.00%> (ø)
pkg/mutation/path/token/token.go 0.00% <0.00%> (ø)
pkg/mutation/path/token/scanner.go 90.24% <90.24%> (ø)
pkg/mutation/path/parser/parser.go 100.00% <100.00%> (ø)
...onstrainttemplate/constrainttemplate_controller.go 55.70% <0.00%> (-0.98%) ⬇️
... and 3 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5320da3...15cb728. Read the comment docs.

@shomron
Copy link
Contributor Author

shomron commented Nov 17, 2020

I think I've incorporated all feedback - let me know if there's any other changes pending.

Copy link
Contributor

@maxsmythe maxsmythe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, letting this sit overnight in case @fedepaol has any more feedback.

@shomron
Copy link
Contributor Author

shomron commented Nov 18, 2020

Sounds good - thank you for the thorough review!

return p.peekToken.Type == t
}

func (p *Parser) Parse() (*Path, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this can be private?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll keep the method exported in case the parser is exported in the future. It is the public-facing interface to the parser, even if the parser is not public.

"github.com/open-policy-agent/gatekeeper/pkg/mutation/path/token"
)

type Parser struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can be the parser private?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, fixed in latest commit.

@fedepaol
Copy link
Contributor

Just a comment about the visibility of the parser.
Really really neat! I like this!

The location parser parses the location specification language used
for matching Kubernetes object fields.

Example:

```
p := NewParser("spec.containers[name:*].securityContext")
root, err := p.Parse()
...

Closes open-policy-agent#915

Signed-off-by: Oren Shomron <shomron@gmail.com>
Signed-off-by: Oren Shomron <shomron@gmail.com>
* Relocated location parser under pkg/mutation
* Rename Object.Value -> Object.Reference
* Rename Root -> Path
* Support newlines as whitespace
* Disallow listSpec that does not follow object path segment
* In listSpec, disallow nil fieldValue if glob is not specified
* Octopus can still say Moo

Signed-off-by: Oren Shomron <shomron@gmail.com>
Signed-off-by: Oren Shomron <shomron@gmail.com>
The parser functionality is exposed behind the Parse function.
The parser struct itself is not currently needed outside the package,
so we will unexport it for now.

Signed-off-by: Oren Shomron <shomron@gmail.com>
Copy link
Contributor

@maxsmythe maxsmythe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM,

Copy link
Member

@ritazh ritazh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@maxsmythe maxsmythe merged commit 3444c16 into open-policy-agent:master Nov 18, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Write Location Parser for Mutations
6 participants