-
Notifications
You must be signed in to change notification settings - Fork 819
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
MutatingWebHookConfiguration for GameServer creation & Validation. #95
Conversation
/cc @Kuqd - you might find this interesting as well. I implemented a 1 rule server side validation on a GameServer, to provide an example: As an aside - if anyone wants to understand how the |
Interesting I ´ll have look |
7a3c34d
to
af43e71
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly small little things. Otherwise, LGTM.
pkg/apis/stable/v1alpha1/types.go
Outdated
// Validate validates the GameServer configuration. | ||
// If a GameServer is invalid there will be > 0 values in | ||
// the returned array | ||
func (gs *GameServer) Validate() []metav1.StatusCause { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The callers to Validate
will have to check the length of the returned slice to determine if the GameServer
has valid configuration. Instead, I think it might be nicer to return (bool, []metav1.StatusCause)
, so callers could simple check an ok
status, and only then look at the slice if necessary, e.g.:
if ok, causes := gs.Validate(); !ok {
// do something with causes here
}
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea - it's far more explicit. I'll make this change.
pkg/gameservers/controller.go
Outdated
// This is the main logic of this function | ||
// the rest is really just json plumbing | ||
gs.ApplyDefaults() | ||
causes := gs.Validate() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, an ok
value in addition to causes
would be nice.
@@ -159,4 +195,4 @@ subjects: | |||
roleRef: | |||
apiGroup: rbac.authorization.k8s.io | |||
kind: ClusterRole | |||
name: agones-controller | |||
name: agones-controller |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that in Gopkg.toml
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Want to do this as a separate PR? Sounds like you have much more experience with this than I do.
But this sounds like a great idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, sure. I'll pick it up. 👍
logrus.WithError(err).Fatal("Could not get executable path") | ||
} | ||
|
||
base := filepath.Dir(exec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a cool trick. Haven't see this before. 👍
pkg/gameservers/controller.go
Outdated
return review, nil | ||
} | ||
|
||
new, err := json.Marshal(gs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since new
is a built-in function, maybe newGS
would be better here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are righ! Will change.
}) | ||
} | ||
logrus.WithField("path", path).WithField("groupKind", gk).WithField("op", op).Info("Added webhook handler") | ||
wh.handlers[path] = append(wh.handlers[path], operationHandler{groupKind: gk, operation: op, handler: h}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be thread-safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting question. It shouldn't have to be, since controllers (will) get created in sequence, and not at the same time. But worth keeping an eye on.
c := gameservers.NewController(minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory) | ||
|
||
wh := webhooks.NewWebHook(certFile, keyFile) | ||
c := gameservers.NewController(wh, minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily an issue for this CL, but is there precedent in Go to support the Builder paradigm? Whenever I have to look at adding a param to this constructor it makes me nervous, and a ControllerBuilder might be a way to alleviate potential problems here.
If there is support for this I will create an issue for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could always do functional options. It's become a standard pattern.
Something like:
gameserves.NewController(minPort, maxPort, gameserves.WithWebHook(wh))
where the last argument to the constructor is variadic.
See here for more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say for Go, functional options are the more prevalent pattern:
https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
It's an interesting question. I decided not to go that route, since default values are set in the cmd
binaries, and it seemed odd to have defaults in both places and keep them in sync.
@enocom what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a straightforward refactor. Once we hit the 0.1 milestone, I would be interested in revisiting it, especially once I understand Agones better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it's less critical if we don't expect users to be calling this function directly, if all their config are provided as k8s type config. If it was a public API I'd definitely push for it, but with this I could see it either way. Would make it less error prone in the future though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stupid question :
Does go recommend a line return for long line of code ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting question - from https://golang.org/doc/effective_go.html
Line length
Go has no line length limit. Don't worry about overflowing a punched card. If a line feels too long, wrap it and indent with an extra tab.
That being said, it is fairly common practice to wrap on long lines.
cmd/controller/main.go
Outdated
|
||
pflag.String(sidecarFlag, viper.GetString(sidecarFlag), "Flag to overwrite the GameServer sidecar image that is used. Can also use SIDECAR env variable") | ||
pflag.Bool(pullSidecarFlag, viper.GetBool(pullSidecarFlag), "For development purposes, set the sidecar image to have a ImagePullPolicy of Always. Can also use ALWAYS_PULL_SIDECAR env variable") | ||
pflag.Int32(minPortFlag, 0, "Required. The minimum port that that a GameServer can be allocated to. Can also use MIN_PORT env variable.") | ||
pflag.Int32(maxPortFlag, 0, "Required. The maximum port that that a GameServer can be allocated to. Can also use MAX_PORT env variable") | ||
pflag.String(keyFileFlag, viper.GetString(certFileFlag), "Optional. Path to the key file") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copy/paste issue :) , should be
pflag.String(keyFileFlag, viper.GetString(keyFileFlag), "Optional. Path to the key file")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well spotted!
c := gameservers.NewController(minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory) | ||
|
||
wh := webhooks.NewWebHook(certFile, keyFile) | ||
c := gameservers.NewController(wh, minPort, maxPort, sidecarImage, alwaysPullSidecar, kubeClient, kubeInformationFactory, extClient, agonesClient, agonesInformerFactory) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stupid question :
Does go recommend a line return for long line of code ?
} | ||
|
||
logrus.WithField("review", review).Info("Invalid GameServer") | ||
return review, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know you could validate also in the mutating webhook. I thought you would need another webhook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the validation hook runs after the mutation hook (and all the mutations are complete) - but the data structure that comes in and out is the same on both (AdmissionReview
). But you can cancel an operation through either. I figured since we're already processing the data, it feels like overkill to do it twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sense, thanks for clarifying
|
||
err := wh.server.ListenAndServeTLS(wh.certFile, wh.keyFile) | ||
if err == http.ErrServerClosed { | ||
logrus.WithError(err).Info("webhook: https server closed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should you return nil here ? the server has been closed by request of the user so you should not return an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oooh! Also good catch!
Webhook library to make k8s webhooks easy(er) to use, as well as setting default values on GameServers via it for when they are first created. Some refactoring of GameServer sync in the controller was required and a new PortAllocation state was created. This is also makes #70 and #10 possible to implement.
af43e71
to
495420e
Compare
Webhook library to make k8s webhooks easy(er) to use, as well as setting default values on GameServers via it for when they are first created.
Some refactoring of GameServer sync in the controller was required and a new PortAllocation state was created.
This is also makes #70 and #10 possible to implement.