-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add support to set the GC Stack the CLI should use by default #4940
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
base: master
Are you sure you want to change the base?
Conversation
} | ||
|
||
// GetDefaultProject retrieves the default project for the given stack ID. | ||
func (c *Client) GetDefaultProject(stackID int64) (int64, 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.
I don't know what the long-term plans are here.
The new REST API does a few things differently:
- StackID is mandatory
- Auth header is different (using Bearer instead of Token)
Should we change the existing Client, or add a new one?
What do you think about using the auto-generated Go SDK?
Moving to the new API completely is impossible until we enforce StackID (maybe in v2?). So yeah, we will be living in this mixed APIs state for a while.
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.
until we enforce StackID (maybe in v2?).
Is it really a strict requirement? It seems we can get the StackID from the token just by calling /account/me
, or is only available in /v3
?
What do you think about using the auto-generated Go SDK?
It sounds to me a great idea to explore.
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 we change the existing Client, or add a new one?
As discussed above, I'd suggest adding a new client, so we keep things isolated, especially considering we're speaking about different hosts or different versions of the API.
What do you think about using the auto-generated Go SDK?
I think we should use it when possible, but I suspect most of the operations that are supported now (by k6) aren't supported by the latest version of the Public API. But yeah, if we start adding support into k6 for the operations available through the auto-generated client, then I'd suggest using it for those, for sure.
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.
Is it really a strict requirement? It seems we can get the StackID from the token just by calling /account/me, or is only available in /v3?
A user can be in many Stacks! Which one should we pick? 👀
That's the current problem, IMO.
I'd suggest adding a new client
I'll try to do this. I'll also ask the backend about the /me
endpoint and if they plan to include it in the new API.
That said, I'll do the smallest thing we can in this area to move this PR forward. We can keep adding things to that new client in future PRs (e.g., when I add k6 plan
I'll need a few more methods)
@@ -127,3 +129,62 @@ func handleTestAbortSignals(gs *state.GlobalState, gracefulStopHandler, onHardSt | |||
gs.SignalStop(sigC) | |||
} | |||
} | |||
|
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'm not really sure if common.go
is the right place for these shared functions.
Happy to move it around.
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.
In big packages like internal/cmd
I would prefer a specialized file. Can you move to a stack.go
or cloud_stack.go
file, please?
// TODO: Improve handling of this error. This happens when the user uses a Stack tocken instead of a Personal token. | ||
if strings.Contains(err.Error(), "Authentication failed") { | ||
return 0, "", 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.
Do we have an API to check if a token is personal or stack? If stack, from which stack?
It would be nice to be able to set it as a default and to improve this error handling.
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 guess you need someone from the backend to address this question.
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.
Looks good. Some minor things to consider.
I'm not fully versed on this area of k6 though so someone else who has a better idea might be a better person to add their approval too.
cloudapi/config.go
Outdated
if cfg.StackID.Valid { | ||
c.StackID = cfg.StackID | ||
c.StackSlug = null.StringFromPtr(nil) | ||
} | ||
if cfg.StackSlug.Valid { | ||
c.StackSlug = cfg.StackSlug | ||
c.StackID = null.StringFromPtr(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.
Shamefully, i don't know the difference between StackID
and StackSlug
. Looks like we can't work with both here. If someone did provide both, could k6 return an error (or log a warning)? The user might want to work with StackID
and instead it gets overwritten by StackSlug
as they forgot to undefine StackSlug
.
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.
StackID is a number. StackSlug is a string. You can spot it easily on the URL of your Grafana Cloud instance.
e.g. .grafana.com
StackIDs can't change. StackSlugs only change on edge cases.
IDs are pretty hard to find, while Slugs are well-known. I wanted to focus the UX on Slugs because they seem way easier to understand, find, and reference. But yeah, this is quite confusing, I know 😄
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.
Hey @dgzlopes, thanks for your contribution here 💌
Review of the user experience's definition
I feel this is complex enough that we have to pay attention to clearly document the user's scenarios. It should serve as a future reference, detailing our decisions and their rationale. I feel the current pull request's description would benefit if we could update, improve the format and create a better structured tree of the expected user cases.
I feel the new use cases and the technical decisions/limitations of the implementation are mixed together. Separating these into distinct paragraphs would enhance readability.
Review of the code
Testing: It looks like we might be missing some tests for this logic. Perhaps you were aiming to get some initial feedback before diving deeper into testing, that's perfectly fine. We can definitely plan to incorporate them in a next step.
However, if the idea was to bypass tests altogether, I'd suggest we reconsider. The logic already has a good amount of history and complexity, and adding more on top of it without the safety net of tests would make future changes very error-prone.
HTTP clients: as soon as you will add tests then you will hit the issue with faking things with the http.DefaultClient. We should inject the clients as we are doing in other parts so we can mock their behavior for easily test the scenarios.
Sparse logic: I didn’t explore this assumption in detail but I have the impression that the source code might benefit if we add some sort of StackResolver component and we delegate to it all the logic for doing the resolution.
} | ||
|
||
// GetDefaultProject retrieves the default project for the given stack ID. | ||
func (c *Client) GetDefaultProject(stackID int64) (int64, 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.
until we enforce StackID (maybe in v2?).
Is it really a strict requirement? It seems we can get the StackID from the token just by calling /account/me
, or is only available in /v3
?
What do you think about using the auto-generated Go SDK?
It sounds to me a great idea to explore.
// AccountMe retrieves the current user's account information. | ||
func (c *Client) AccountMe() (*AccountMeResponse, error) { | ||
// TODO: remove this hardcoded URL | ||
req, err := c.NewRequest("GET", "https://api.k6.io/v3/account/me", 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.
req, err := c.NewRequest("GET", "https://api.k6.io/v3/account/me", nil) | |
req, err := c.NewRequest(http.GetMethod, "https://api.k6.io/v3/account/me", nil) |
Same for the others
req.Header.Set("X-Stack-Id", fmt.Sprintf("%d", stackID)) | ||
// TODO: by default the client uses Token instead of Bearer | ||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) | ||
req.Header.Set("User-Agent", "Go-http-client") |
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.
req.Header.Set("User-Agent", "Go-http-client") |
Is it required?
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 guess Backend folks may want to know where the API is being consumed from, right @dgzlopes? 🤔
If we ever replace this custom implementation in favor of the auto-generated Go SDK, the User-Agent is yet another configuration attribute that you can customize.
Wrt the value, I think we might want to use something that explicitly let us know this request is coming from the k6 binary.
// TODO: Can't use c.Do bc it messes up the headers | ||
resp, err := http.DefaultClient.Do(req) | ||
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.
Then we need a dedicated client overwriting this different logic, otherwise testing will be quite difficult
} | ||
|
||
if len(parsed.Value) == 0 { | ||
return 0, "", fmt.Errorf("no projects found for stack ID %d", stackID) |
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.
return 0, "", fmt.Errorf("no projects found for stack ID %d", stackID) | |
return 0, "", fmt.Errorf("no projects found for stack ID %d", stackSlug) |
We should be consistent, if we prefer the Slug on input then we should return a slug on errors. Otherwise, they will experience the same difficulties we're carefully avoiding on inputs.
const suffix = ".grafana.net" | ||
if len(s) > len(suffix) && s[len(s)-len(suffix):] == suffix { | ||
return s[:len(s)-len(suffix)] | ||
} | ||
return s |
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 seems you want https://pkg.go.dev/strings#TrimSuffix
// We want to use this fully consolidated config for things like | ||
// host addresses, so users can overwrite them with env vars. | ||
consolidatedCurrentConfig, warn, err := cloudapi.GetConsolidatedConfig( | ||
jsonRawConf, gs.Env, "", nil, nil) | ||
if err != nil { | ||
return nil, err | ||
} |
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 seems to me this should be a dependency of this function instead of being part of the logic
@@ -92,18 +101,47 @@ func (c *cmdCloudLogin) run(cmd *cobra.Command, _ []string) error { | |||
show := getNullBool(cmd.Flags(), "show") | |||
reset := getNullBool(cmd.Flags(), "reset") | |||
token := getNullString(cmd.Flags(), "token") | |||
stackSlug := getNullString(cmd.Flags(), "stack-slug") |
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.
Is the pull request's description outdated, or they are intentionally different?
// TODO: Improve handling of this error. This happens when the user uses a Stack tocken instead of a Personal token. | ||
if strings.Contains(err.Error(), "Authentication failed") { | ||
return 0, "", 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 guess you need someone from the backend to address this question.
// TODO: do this in the future? But it is a breaking change... | ||
// gs.Logger.Error("please specify a projectID in your test or use `k6 cloud login` to set up a default stack") | ||
// return 0, fmt.Errorf("no projectID specified and no default stack set") |
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 can do two things here:
- Add a warning and make the user aware of the future changes without breaking the current logic
- We can start having feature flags with breaking changes, we might also have some
next
feature flag, where we enable all the feature in the shape ofv2
. So, the users that don't care about breaking changes can benefit of the new features.
GrafanaStackName string `json:"grafana_stack_name"` | ||
GrafanaStackID int `json:"grafana_stack_id"` |
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 tested the endpoint, and looks like it could return a null grafana_stack_name
and/or grafana_stack_id
.
Is that only a particular case for my account? Or an edge case that we should handle?
Because, in such case, I think we should make this nil
-able, and/or somehow explicitly document that it can be the case.
I'm wondering the wide variety of corner cases we can find once we release this to users 🤔
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) | ||
req.Header.Set("User-Agent", "Go-http-client") | ||
|
||
// TODO: Can't use c.Do bc it messes up the headers |
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 think this is yet another indicator that we should likely have a different client implementation for this.
What?
Current behaviour:
This PR supports setting a Grafana Cloud Stack that would be used by default when running Cloud commands. It is implemented in a way that doesn't break the above behavior, but it improves the experience for all new users.
I have added a few things in this PR:
Support in the login command to pass the stack slug
Both in the interactive mode and as a flag (using
--stack
). In interactive mode, if the user passes a personal token, a default stack is provided, making it smoother for users with only one Stack. This default is omitted if a stack token is being used (ideally, in the future, we would set the stack this token is part of).Test run and upload archive behave differently when no projectID is set
If the user has configured their default stack, these commands will run in the default project of the stack. A warning is shown in the CLI telling them about this behaviour.
Ability to set the stack slug/id in the script or as an environment variable
If the user wants to set it there, they can do so too. Here is a small example:
Why?
The current experience wasn't designed around Grafana Cloud, where Stack is the higher-level concept. This PR is based on the proposal listed on #4008 but with a better UX: users inputting the slug instead of the ID, which is hard to find and identify.
Having this is a requirement for adding extra commands like a plan command as mentioned in #3733 or letting users reference their projects by their name, as proposed in #1817
Checklist
make check
) and all pass.Checklist: Documentation (only for k6 maintainers and if relevant)
Please do not merge this PR until the following items are filled out.
Related PR(s)/Issue(s)