Is it possible to modify an object namespace in a Mutating Webhook? - kubernetes

I have created a Mutating Webhook for a Namespaced CRD. In this Webhook, I would like to dynamically change the namespace of the resource, no matter what the user specified.
When I try to create a resource, I get the following error:
Error from server (BadRequest): error when creating "crd.yaml": the namespace of the provided object does not match the namespace sent on the request
Is it possible to perform this change and, if so, am I missing any configuration?

Sadly, it might not be possible: looking at the code here, you can see that if the resource you're attempting to create already has a namespace (the request), and your webhook attempts to mutate the object to replace the namespace (the object), then there will be a mismatch.
if err := EnsureObjectNamespaceMatchesRequestNamespace(ExpectedNamespaceForScope(requestNamespace, strategy.NamespaceScoped()), objectMeta); err != nil {
return err
}
You can see that the function EnsureObjectNamespaceMatchesRequestNamespace takes in two things: the expected namespace from the request and the object's metadata. If the request coming in is namespaced, then the request's namespace will be returned.
If you look at the function, you can see that your case falls through to the default case:
switch {
case objNamespace == requestNamespace:
// already matches, no-op
return nil
case objNamespace == metav1.NamespaceNone:
// unset, default to request namespace
obj.SetNamespace(requestNamespace)
return nil
case requestNamespace == metav1.NamespaceNone:
// cluster-scoped, clear namespace
obj.SetNamespace(metav1.NamespaceNone)
return nil
default:
// mismatch, error
return errors.NewBadRequest("the namespace of the provided object does not match the namespace sent on the request")
}
Did you end up figuring out a workaround for yourself?

Related

Unable to match exception instance in test case

The following piece of code returns an instance of an exception if a configuration is missing
if (host == "" || redirectUrl == "" || successUrlParameter == "" || failUrlParameter == "") {
//checktest-USerTransactionService-finishResetCredentials-return exception if host configuration parameters are present
(Some(MissingConfigurationException()),errorRediectedUrl,None)
} else {...}
I am testing this and am matching it like the following
errorThrown mustBe Some(MissingConfigurationException())
The test case fails even though the values seem to be equal.
Expected :Some(utilities.MissingConfigurationException: Server Error. Missing Configuration)
Actual :Some(utilities.MissingConfigurationException: Server Error. Missing Configuration)
how should I compare the expected vs actual value?
Exceptions are compared by reference not by value. Thus, two identical values will always be different, unless they are the same instance.
So, you have to check for the class of the instance.
However. Scalatest provides a better syntax for checking for class and using options.
errorThrown.value shouldBe a [MissingConfigurationException]
Reference:
https://www.scalatest.org/user_guide/using_matchers#checkingAnObjectsClass
https://www.scalatest.org/user_guide/using_OptionValues

How to check if a field was explicitly set in a custom resource

Update:
I found that my code actually working. The reason that reconcile loop get false at first is because I have another operator existing in my cluster and doing boolean flipping automatically. After deleted that operator, my code works as expected.
This question is related with Kubernetes and Operator-SDK. Let's say I have a Custom Resource which spec is shows as following:
apiVersion: my.example.com/v1alpha1
kind: MyStore
metadata:
name: mystore
spec:
name: "sample-store"
address: "sample-address"
zip: "sample-zip"
open: true
Where the last field open is boolean type used to indicate whether the store is open or not. In reconcile loop of my operator, I would like to know whether a CR of MyStore kind was explicitly set value of open field. For example:
If the value CR's open field has been explicitly set as true or false, then reconcile loop should take this value directly.
If the CR does not have open field explicitly set, or this field is not existing, reconcile loop should consider the default value of open as true.
Currently I tried this way, which is set the type of open field as pointer of boolean in my struct:
type MyStoreSpec struct {
Name string `json:"name"`
Address string `json:"address"`
Zip string `json:"zip"`
Open *bool `json:"open"` // Here I use *bool as type
}
type MyStore stuct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MyStoreSpec `json:"spec,omitempty"`
}
Then in reconcile loop I check the existing of open field like this:
store := &examplev1beta1.MyStore{}
r.client.Get(context.TODO(), request.NamespacedName, store)
if store.Spec.Open == nil {
a := true
store.Spec.Open = &a
}
The idea of above code is to check whether the pointer of open field exists: if pointer is null, set true to open field. And the idea is from this question Go: How to check if a struct property was explicitly set to a zero value?
But above code is not working as I expected: if open field of a CR is not exists (not have value be explicitly set), the value of store.Spec.Open will be parsed as false but not nil.
Is there any other ways to do the field value checking?
Try adding omitempty with your json tag.
type MyStoreSpec struct {
Name string `json:"name"`
Address string `json:"address"`
Zip string `json:"zip"`
Open *bool `json:"open,omitempty"` // Here I use *bool as type
}
Rest of the code should work.

Comparing document timestamps in Firestore rules

I'm running into a weird problem while writing and testing my Firestore rules. Here's what I want to achieve:
When the application starts, the user gets logged in anonymously. The
user starts a new game.
I create a 'Session' that basically consists of just a timestamp.
The player plays the game, gets a certain highscore and goes to a screen where the score can be sent to the global highscore list. When the highscore is submitted, I check if there's an existing session for this player and if the time that has passed is long enough for the highscore to be considered valid.
On the client (javascript) I use the following line to send the timestamp in my documents:
firebase.firestore.FieldValue.serverTimestamp()
This is the current ruleset. You can see that a score can only be created when the createdAt of the new higscore is later than the createdAt of the session.
service cloud.firestore {
match /databases/{database}/documents {
function isValidNewScoreEntry() {
return request.resource.data.keys().hasOnly(['createdAt', 'name', 'score']) &&
request.resource.data.createdAt is timestamp &&
request.resource.data.name is string &&
request.resource.data.score is int &&
request.resource.data.name.size() <= 20
}
match /highscores/{entry} {
allow list: if request.query.limit <= 10;
allow get: if true;
allow create: if isValidNewScoreEntry() &&
request.resource.data.createdAt > get(/databases/$(database)/documents/sessions/$(request.auth.uid)).data.createdAt;
}
function isValidNewSession() {
return request.resource.data.keys().hasOnly(['createdAt']) &&
request.resource.data.createdAt is timestamp
}
match /sessions/{entry} {
allow list: if false;
allow get: if false;
allow create: if isValidNewSession();
allow update: if isValidNewSession();
}
}
}
When I simulate/test these rules, I get an error that says that I cannot compare a 'timestamp' to a 'map'. I don't know why the 'createdAt' value is a map, but it seems like the get() method returns something different than expected.
My question is: What would be the correct way to compare the property createdAt from the newly submitted entry to the property createdAt of the existing session document, like I'm trying to do in the rules described above.
This is what a'Score' entry look like
This is what a 'Session' entry looks like
EDIT:
I've done some more digging, and found that this line works:
if request.resource.data.createdAt.toMillis() > get(/databases/$(database)/documents/sessions/$(request.auth.uid)).data.createdAt.seconds * 1000;
This makes it pretty clear that not both createdAt are the same format. The last one seems to be a basic object with the properties 'seconds' and 'nanoseconds'. I'm sure it stems from the Timestamp interface, but it gets returned as a flat object since none of the methods found here exist and give an error when trying to call them. The property 'seconds' however does exists on the second timestamp, but is not accessible on the first one.
I've found out why the timestamp is not what I expected and got cast to a 'map'.
After digging through the documentation I found that the get() method returns a resource. resource has a property data: a map. So the get() method does not return a document as I expected but a flat JSON object that gives me all properties found in de database.
https://firebase.google.com/docs/reference/rules/rules.firestore
https://firebase.google.com/docs/reference/rules/rules.firestore.Resource

How to determine what kind of error message I should return from golang API?

I have a GoLang API with an SPA to consume it. What I do to errors in my API is return them until the handler where I test if an error from previous functions exist. If there is an error, I put it inside the response body, set status code to either 400 or 500 then return the response
in the handler function, to be able to create a clear message to the client side, I need to know what kind of error was returned, how do I do it?
I know about error types but I read about Dave Cheney's recommendation to just return an error along with a message (or wrap them in other words).
But if there are so many kinds of errors which might occur in the API call, then does it mean before returning the response, I need to check them all just to know what message I should return?
The first thing to say about errors is that just because there's an error interface
type error interface {
Error() string
}
Does not mean that the error returned from any given method can only have that method / information attached to it.
One common method is to define your own error interface:
type myError interface {
error // embeds the standard error interface
OtherMethod() string // can define own methods here
}
When writing methods and functions it's really important to return an error and not myError, else you couple that method to your error implementation and cause dependency nightmares for yourself later.
Now that we've decided we can return extra information from error, using our own error interfaces you've got 3 main choices.
Sentinel errors
Error Failure types
Errors with Behaviour
Sentinel errors
Sentinel errors are error values that are defined as package level variables, are exported and allow comparison to check for errors.
package myPackage
var ErrConnectionFailed = errors.New("connection failed")
func Connect() error {
// trimmed ...
return ErrConnectionFailed
}
A consumer of this example could use the connect function:
if err := myPackage.Connect(); err == myPackage.ErrConnectionFailed {
// handle connection failed state
}
You can do a comparison to check if the error returned is equal to the sentinel error of the package. The drawback is that any error created with errors.New("connection failed") will be equal, and not just the error from myPackage.
Error failure types
Slightly better than sentinel errors are error failure types.
We've already seen that you can define your own error interface, and if we say ours is now:
type MyError interface {
error
Failure() string
}
type Err struct {
failure string
}
func (e *Err) Error() string {
// implement standard error
}
func (e *Err) Failure() string {
return e.failure
}
const ConnFailed = "connection failed"
err := &Err{failure: ConnFailed}
In the consumer code you can get an error, check if it implements MyError and then do things with it.
err := myPackage.Connect()
if myErr, ok := err.(myPackage.MyError); ok {
// here you know err is a MyError
if myErr.Failure() == myPackage.ConnFailed {
// handle connection failed, could also use a switch instead of if
}
}
Now you have an idea of what caused the error which is nice. But do you really care what the cause was? Or do you only really care what you want to do to handle that error.
This is where errors with behaviour are nice.
Errors with behaviour
This is similar to defining your own error type, but instead you define methods that report information about that error. Given the example above, do you really care that the connection failed, or do you really only care if you can retry or if you need to error up the call stack again?
package myPackage
// this interface could report if the error
// is temporary and if you could retry it
type tempErr interface {
Temporary() bool
}
func (e *Err) Temporary() bool {
// return if the error is temporary or not
}
Now in the consumer (note you don't need to use the myPackage.tempErr), you can test using type assertions if the error is temporary and handle the retry case:
err := myPackage.Connect()
if tmp, ok := err.(interface { Temporary() bool }); ok && tmp.Temporary() {
// the error is temporary and you can retry the connection
}
To answer the question, it's very hard to say without the specifics of the service that you are trying to implement. But as broad advice, I would try and use the last of the 3 examples as much as possible.
If the consumer of your service sends you some input that's not valid:
err := doThing(...)
if inv, ok := err.(interface { Invalid() bool }); ok && inv.Invalid() {
// input is invalid, return 400 bad request status code etc.
}
If you want to return a specific message to a consumer, you could make that a method of your error type. Warning: this would give your packages knowledge that they are being used in a web service, etc.
err := doThing(...)
if msg, ok := err.(interface { ResponseMsg() string }); ok {
// write the message to the http response
io.WriteString(response, msg.ResponseMsg())
}
TL;DR you would need to handle all the cases, but you can create error types that make the code much easier to work with!

Calling method from console: "No current object"

I have a method that I need to test. I would like to do so from the console. Here is the method, as well as some metadata from the class:
Include HS.Common
Class Custom.class Extends Ens.BusinessOperation
{
Parameter ADAPTER = "EnsLib.EMail.OutboundAdapter";
Property Adapter As EnsLib.EMail.OutboundAdapter;
Method SendMessage(pSubject As %String, pMessage As %String, pEmailAddresses) As %Status
{
set tSC=$$$OK
set tMailMessage=##class(%Net.MailMessage).%New()
do tMailMessage.To.Insert($PIECE(pEmailAddresses,",",1))
for tI=2:1:$LENGTH(pEmailAddresses,",") {
do tMailMessage.Cc.Insert($PIECE(pEmailAddresses,",",tI))
}
set tMailMessage.Subject=pSubject
set tMailMessage.Charset="iso-8859-1"
set tSC=tMailMessage.TextData.Write(pMessage)
quit:'tSC
Set tSC1=..Adapter.SendMail(tMailMessage)
if 'tSC1 {
//Log warning about being unable to send mail.
do $SYSTEM.Status.DecomposeStatus(tSC1,.err)
$$$LOGWARNING("Could not send email: "_err(err))
kill err
}
quit tSC
}
...other methods here...
}
but when I perform this command:
set tResult = ##class(Custom.class).SendMessage("Test Subject","Test Message","my#email.com")
I get this error:
Set tSC1=..Adapter.SendMail(tMailMessage)
^
<NO CURRENT OBJECT>zSendMessage+11^Custom.class.1
I tried instantiating adapter, much like the property definition, before calling the method but that did not work. How can I call this method from a console session?
this method is an instance method, and you can't call it directly just for some class. Before, you should create an object, and then for that object, you can call any instance methods. But you still trying to call Ensemble classes, it is not so easy, because you should prepare environment, such as configured and started Ensemble Production, your class should be added as an Operation, configured and activated.
set oper=##class(Custom.class).%New("configName")
where configName - name for that operation in your production, by default it is same as class name (e.g. "Custom.class"). And now you can call your method.
write oper.SendMessage("testSubj","test body","my#mail.com")
But I would not recommend such way. It would be better if you test it through production, just sent test messages to this operation.