-
Notifications
You must be signed in to change notification settings - Fork 177
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
Changes to onboarding token api to include quota in body #2600
base: main
Are you sure you want to change the base?
Conversation
rchikatw
commented
May 8, 2024
•
edited
edited
- Passing nil quota to function means unlimited quota
- Quota will be sent as HTTP request body
- Sample request: {"quota": "123Mi"} or no body
@rchikatw Why use 0 or unlimited/unbound? why not have it a pointer and use nil instead? |
controllers/util/provider.go
Outdated
@@ -19,14 +19,15 @@ import ( | |||
|
|||
// GenerateOnboardingToken generates a token valid for a duration of "tokenLifetimeInHours". | |||
// The token content is predefined and signed by the private key which'll be read from supplied "privateKeyPath". | |||
func GenerateOnboardingToken(tokenLifetimeInHours int, privateKeyPath string) (string, error) { | |||
func GenerateOnboardingToken(tokenLifetimeInHours int, privateKeyPath, quota string) (string, 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.
Quota shouldn't be a string, it should be wither a number (assuming a specific unit) or a size object.
Also it should preferably be optional (a pointer) so we can generate a token that does not have a quota
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 have considered this as a string because in ClusterResourceQuota we also need to mention the unit. It will be better if we get that from the UI itself.
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.
Even it that case this should not be a string but a k8s quantity object
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.
Okay will do the changes according
services/types.go
Outdated
@@ -3,4 +3,5 @@ package services | |||
type OnboardingTicket struct { | |||
ID string `json:"id"` | |||
ExpirationDate int64 `json:"expirationDate,string"` | |||
Quota string `json:"quota"` |
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.
Quota shouldn't be a string, it should be wither a number (assuming a specific unit) or a size object.
Also it should preferably be optional (a pointer) so we can generate a token that does not have a quota
@@ -16,14 +16,15 @@ const ( | |||
func HandleMessage(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int) { | |||
switch r.Method { | |||
case "POST": | |||
handlePost(w, tokenLifetimeInHours) | |||
quota := r.URL.Query().Get("quota") |
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.
If this is a Post request the common practice is to use the request body to convey payload not the URL query line
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.
Okay. i will consider that.
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: rchikatw The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
aabb7fd
to
45e90bc
Compare
type Message struct { | ||
Quota string `json:"quota"` | ||
} | ||
var quota Message |
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 don't think we should be processing the body before sending it to
handlePost
cause in the switch you are only deciding the method - No need to create a type if we are not going to initialize it, can get away w/ var. Unrelated one, for encoding & decoding you only need fields to be exported not necessarily the struct.
type Message struct { | |
Quota string `json:"quota"` | |
} | |
var quota Message | |
var quota = struct { Quota string `json:"quota"`}{} |
} | ||
var quota Message | ||
err := json.NewDecoder(r.Body).Decode("a) | ||
if err != 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.
nit, can perform err handling in the same statement/line.
094ccac
to
f7803db
Compare
// When length is 0 that means either request body is empty or | ||
// quota parameter is not passed in that case quota is considerred | ||
// to be unlimited |
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.
no, we already established that empty body represents unlimited quota and better not to club that w/ empty value for quota.
btw, as mentioned in the comment, I don't think quota
is a param.
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.
Yes, correct, we are only checking with the request body only. I missed updating this comment.
default: | ||
handleUnsupportedMethod(w, r) | ||
} | ||
} | ||
|
||
func handlePost(w http.ResponseWriter, tokenLifetimeInHours int) { | ||
if onboardingToken, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath); err != nil { | ||
func handlePost(w http.ResponseWriter, tokenLifetimeInHours int, r *http.Request) { |
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.
could you pls swap 2nd & 3rd param?
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.
Done
in body Signed-off-by: rchikatw <rchikatw@redhat.com>
@rchikatw: The following test failed, say
Full PR test history. Your PR dashboard. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
tokenExpirationDate := time.Now(). | ||
Add(time.Duration(tokenLifetimeInHours) * time.Hour). | ||
Unix() | ||
|
||
payload, err := json.Marshal(services.OnboardingTicket{ | ||
ID: uuid.New().String(), | ||
ExpirationDate: tokenExpirationDate, | ||
StorageQuota: quantity, |
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.
@rchikatw How does the marshaled form of quantity look like? Is it just a string containing value and unit? is it unmarshalabe?.
Also, why do we need a complex data type in the token? why can we just normalize the quantity to an int representing the number of MiB? This will be beneficial for users who want to read the token content in other languages (e.g. Bash, Phyton, etc.)
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.
After marshal quantity will look like <size><Unit>
ex: 1Gi.
Is it unmarshalabe? -> Yes it.
Why do we need a complex type for quantity? In your previous comment you suggested to using k8s quantity Refer: #2600 (comment)
Advantages of using quantity:
- we can validate quota unit is valid (using parseQuantity function) no need to add manual checks for the unit
- You have also suggested using a pointer so we can generate a token that does not have a quota. Refer Changes to onboarding token api to include quota in body #2600 (comment)
If we don't want to use complex data type then we should not allow the user to select the unit in the UI and make quantity int that should solve our problem not using complex data type.
Let me know which approach should I use k8s quantity or int
ExpirationDate int64 `json:"expirationDate,string"` | ||
ID string `json:"id"` | ||
ExpirationDate int64 `json:"expirationDate,string"` | ||
StorageQuota *resource.Quantity `json:"storageQuota,omitempty"` |
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.
Can we please keep the token fields as non-complex types? in this case I would expect an int
StorageQuota *resource.Quantity `json:"storageQuota,omitempty"` | |
StorageQuotaInMiB *int `json:"storageQuotaInMiB,omitempty"` |
func handlePost(w http.ResponseWriter, tokenLifetimeInHours int) { | ||
if onboardingToken, err := util.GenerateOnboardingToken(tokenLifetimeInHours, onboardingPrivateKeyFilePath); err != nil { | ||
func handlePost(w http.ResponseWriter, r *http.Request, tokenLifetimeInHours int) { | ||
quantity, err := getQuantity(r) |
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.
Why Quantity? are we expecting the user to select both size and unit? or are we expecting a standardized unit?
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.
Yes from the UI design slides user can select both size and unit.
func getQuantity(r *http.Request) (*resource.Quantity, error) { | ||
var quota = struct { | ||
StorageQuota string `json:"storageQuota"` | ||
}{} | ||
err := json.NewDecoder(r.Body).Decode("a) | ||
if err != nil && err.Error() != "EOF" { | ||
return nil, err | ||
} | ||
|
||
// When length is 0 that means request body is empty and quota | ||
// is unlimited | ||
if len(quota.StorageQuota) == 0 { | ||
return nil, nil | ||
} | ||
quantity, err := resource.ParseQuantity(quota.StorageQuota) | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid StorageQuota value sent in request body: %v", err) | ||
} | ||
return &quantity, 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 am not sure about the logic here. If the UI is passing us a quantity I would expect to see 2 fields in the body. One for the unit and one for the size. But if there is an assumption about the unit then there is no reason to parse it as a quantity, it should be just an int.
Anything else just add unnecessary complexity to the system
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.
UI will be passed as a string in the body with the format 1Gi/1Mi/1Ti
to indicate quantity. I will decode and parse it to validate the correctness of the passed value. Invalid values will look like 1/1gi/1ti
.