-
Notifications
You must be signed in to change notification settings - Fork 9
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
refactor: Config parsing & precedence #103
base: v0.12
Are you sure you want to change the base?
Conversation
// With the change in config management (either local or global, not both), this flag is no longer needed | ||
// TODO: consider remove / deprecate | ||
// lc.cmd.Flags().BoolVar(&lc.local, "local", false, "Pin active project to the current directory") |
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.
Wanna place some emphasis here given the change from this PR makes this flag obsolete. There's nothing we can do whether the user passes --local
flag to $ hookdeck project use --local
command or not.
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.
@alexluong how does a user create a local config without this?
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 there's a way right now. The original thought process was people may write the config manually. We can certainly support a local flag for the login
command maybe?
// TODO: Consider this case. This is a breaking change. | ||
// BREAKINGCHANGE | ||
t.Run("local workspace only", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
c := Config{ | ||
LogLevel: "info", | ||
ConfigFileFlag: "./testdata/local-workspace-only.toml", | ||
} | ||
c.InitConfig() | ||
|
||
assert.Equal(t, "default", c.Profile.Name) | ||
assert.Equal(t, "", c.Profile.APIKey) | ||
assert.Equal(t, "local_workspace_id", c.Profile.TeamID) | ||
assert.Equal(t, "", c.Profile.TeamMode) | ||
}) |
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 the case I mentioned earlier.
workspace_id = "tm_123"
If anyone is in this situation (which there could very well be none), whenever they run any command, we'll ask them to login and after that they can use the CLI normally. However, when they go to another directory without this local config, we'll use the global config again.
t.Run("remove profile multiple times", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
// Arrange | ||
c := Config{LogLevel: "info"} | ||
c.ConfigFileFlag = setupTempConfig(t, "./testdata/multiple-profiles.toml") | ||
c.InitConfig() | ||
|
||
// Act | ||
err := c.Profile.RemoveProfile() | ||
|
||
// Assert | ||
assert.NoError(t, err) | ||
contentBytes, _ := ioutil.ReadFile(c.viper.ConfigFileUsed()) | ||
assert.NotContains(t, string(contentBytes), "account_2", `default profile "account_2" should be cleared`) | ||
assert.NotContains(t, string(contentBytes), `profile =`, `profile key should be cleared`) | ||
|
||
// Remove profile again | ||
|
||
c2 := Config{LogLevel: "info"} | ||
c2.ConfigFileFlag = c.ConfigFileFlag | ||
c2.InitConfig() | ||
err = c2.Profile.RemoveProfile() | ||
|
||
contentBytes, _ = ioutil.ReadFile(c2.viper.ConfigFileUsed()) | ||
assert.NoError(t, err) | ||
assert.NotContains(t, string(contentBytes), "[default]", `default profile "default" should be cleared`) | ||
assert.NotContains(t, string(contentBytes), `api_key = "test_api_key"`, `default profile "default" should be cleared`) | ||
|
||
// Now even though there are some profiles (account_1, account_3), when reading config | ||
// we won't register any profile. | ||
// TODO: Consider this case. It's not great UX. This may be an edge case only power users run into | ||
// given it requires users to be using multiple profiles. | ||
|
||
c3 := Config{LogLevel: "info"} | ||
c3.ConfigFileFlag = c.ConfigFileFlag | ||
c3.InitConfig() | ||
assert.Equal(t, "default", c3.Profile.Name, `profile should be "default"`) | ||
assert.Equal(t, "", c3.Profile.APIKey, "api key should be empty even though there are other profiles") | ||
}) |
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 an interesting case where the user may have multiple profiles but we won't be able to pick up the existing ones and they'll have to login again.
I'm just wondering if this scenario I set up here is clear from your POV. Happy to elaborate further if it's unclear.
Do you think we should do anything here to improve the DX?
It would be good to understand why we're considering this change to help discussion and also to feed into the CHANGELOG. |
@leggetter Hey, welcome back! For this, I think @alexbouchardd may be able to shed some more light on it too. From my POV, the key thing is around some potential confusion with the "local override global" model where it could be difficult to understand why the CLI behaves the way it does. For example, let's say you have a local config
then you run My example could be a bit contrived but it demonstrates the idea that it's difficult for both us (CLI) and end users to manage the relationship between global and local config files and what config should go where. Using this new mental model, it's a lot clearer how things work. A similar example: $ hookdeck project use --local
# change workspace_id in $PWD/.hookdeck/config.toml
# let's say we change to PROJECT_A
# later (next day, next week, next year, etc)
# choose a different project, let's say PROJECT_B
$ hookdeck project use
$ hookdeck whoami
Using profile PROJECT_A .....
# even though we literally just select PROJECT_B With that in mind, I can see sometimes it could be helpful to "override" the global config. The first thought is to have something similar to Ultimately, there's pros and cons and we just have to figure out which model best suits our needs. Given the CLI doesn't have a ton of configuration and is mostly around profile management, I do think being more explicit right now to make things more deterministic could be a good option and we can attempt the override model at a later time when things make sense. |
@leggetter hey so I've been thinking and I'm curious what you think about this. There's this use case that I'm imagining but I don't know if it's something worth supporting / considering. Say there's an engineering organization using Hookdeck. They may have multiple different Hookdeck projects for different workflows. They may also separate environments (dev, staging, production) via projects, or even Hookdeck organization itself. So, as a developer of said organization, I may want to do multiple things:
Or even more complicated use case, in a monorepo with both project A & B, I'd like to run some arbitrary command to proxy events for both A and B to their corresponding ports of 3000 and 4000. Say I wonder if we'd like to spend time now trying to come up with solutions for this, or if it's a future conversation? I would imagine this has an impact on the config file setup. I remembered taking some inspiration from the Doppler CLI where they need to solve this problem. They have some sort of "profile - directory" mapping within their global config file. It's an interesting solution. I didn't love their CLI as a user but I can understand the complexity. Ultimately, I'm just taking a step back and re-evaluating what problem the local |
@alexluong - I think the approach we're taking here, making global and local config mutually exclusive, is the right approach 👍 It means the scenarios you cover are still possible and avoids any unexpected side effects by global config being inherited, especially when there are very few things to configure! I think that the fact we support different config files and profiles within the same config file is potentially powerful, but I wonder if anybody uses it right now. Food for thought, and no action is required on this at present. I like CLIs (and other tools) that dynamically pick up local config and use it (e.g. detects a I don't think we need to discuss anything further in this PR. However, there's likely a few things to work out in #102 |
We already do this. It automatically picks up |
Oh. Interesting! |
Totally, I just wanted to pause to make sure the approach is right. This PR is against #102 branch anyway, so I'll open more PRs to continue implementing everything mentioned in that description. |
@alexluong and additional thought on supporting the commuting of config to source control - if we want to support that then no co credentials should be within the config file. |
@leggetter I think the main thing is that we don't really use the config much besides credentials right now, so I don't think it's recommended to committing config files. If we do have more extensible configuration in the future, it may make sense to separate config vs credentials? |
I'd love to have local project selection and to be able to check that in. |
So should we separate the config into "config" and "profiles" where profile will have secrets and config are configuration that can be checked in to version control? |
@alexluong I think we are going out of scope here. We are just trying to fix the precedence. Being able to check in the config is a good idea but I reckon best to open a different issue? |
chore: add multi-source example
@alexbouchardd yeah that's fair. In that case, any input on the PR and how we should proceed here? cc @leggetter |
@alexluong Ok, I agree splitting out the config can be out of scope for this issue. But I think we should bring the |
but not both
9e805c0
to
d5e33d0
Compare
Hey @leggetter, I've been playing around with the idea of bringing the I think it may not make sense for As for 1: user runs command (say So, if we want to support There's already a way to login and save to a local config by specifying the config file. So essentially:
Obviously this is subpar DX and should not be the final solution. Just curious what you think our next step should be regarding this flow. |
@alexluong - if we merge this, what does it resolve within #102 ? |
@leggetter soo it seems this PR's scope is not documented in #102. I think #102 requirements were part of a discussion with @alexbouchardd where we discussed some functional or behavior points. This PR is a more "internal" change where technically it doesn't affect how it's used for most users. This PR changes how we parse config from "merging local & global" into "either local or global". It is a breaking change, along with several other breaking changes from #102, so I figured it should be done together to avoid multiple breaking changes at once. |
This is a fairly impactful PR as it involves a breaking change as well as a lot of code changes related to
pkg/config
which impacts every command. Some key pointers:Global & local config
Previously, we maintain 2 config scope: global & local config. Local config can be empty. This is the precedence for config values:
Because of this, we can also save things in the local config to override the global config. For example,
$ hookdeck project use --local
will storeworkspace_id
value in the local config so that future commands in this scope will use the selected project. In this case, the local config file could be as short as:Changes
This PR removes the concept of global & local config. There's only 1 singular config. If there's a config within the local scope (
$PWD/.hookdeck/config.toml
) or if the user provides the--config /path/to/config.toml
flag, then the CLI will use this config file only and ignore the global config file should one exist.Test & refactor
Besides code to make the changes in the section above, I also refactored
pkg/config
with 2 main concerns:ConfigFS
interface to mock different filesystem scenarios)With that, I also added quite a bit of tests to ensure things behave correctly. Some main shoutouts:
TestGetConfigPath
will test that the CLI will choose the correct config file from different user input / filesystem scenarios (whether config flag is passed, whether a local config exists, etc)TestInitConfig
will test what the final config looks like with different config files. You can see different scenarios inpkg/config/testdata
. There's a README that gives some explanation for each scenario. Please feel free to add more if there's anything you'd like to test.Add more config scenarios. Something I haven't considered: backward compatibility.
Should we consider adding config versioning moving forward?
The tests currently place a very strong emphasis on profile. There's no test for other config cases like LogLevel, Base URLs, etc. Is there anything you'd like to test more? Feel free to add your own if you'd like, or comment here.