-
Notifications
You must be signed in to change notification settings - Fork 314
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
Add an alternative path for user.toml #3814
Add an alternative path for user.toml #3814
Conversation
Thanks for the pull request! Here is what will happen next:
Thank you for contributing! |
@@ -101,6 +101,8 @@ pub struct Pkg { | |||
pub svc_run: PathBuf, | |||
pub svc_user: String, | |||
pub svc_group: String, | |||
pub user_path: PathBuf, |
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 sure if adding new fields here create some serialization problem. Like during the update of supervisor - we serialize this struct, store it on the disk, update the supervisor, deserialize the struct from the disk and we get empty fields for user_path
and user_config_path
. Is this something to be defensive about (by detecting if the fields are empty, for instance)?
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.
Hrm... that's a good question. I wonder if these really need to be public members of the Pkg
struct at all; maybe they should just stay function calls (which you use with your PackageForConfig
trait). You could just use the fs::user_path(&package.ident.name)
, etc. implementations directly in your trait implementation for Pkg
, get the exact same behavior, and avoid any serialization issues.
CC: @fnichol, @christophermaier, @lilic, @asymmetric We talked about it on the last meeting. |
Some food for thought:
|
I personally think that the user-config watcher should only watch the new path ( The reason is that the user-config watcher is a new feature, so this won't break anybody's workflow. I also think the old location should be deprecated with a warning, and removed in future releases. The |
18e2baf
to
0d48872
Compare
} | ||
} | ||
|
||
fn steal_path(self) -> PathBuf { |
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.
What is the intention behind "stealing" the path? Other Rust APIs conventionally use "take" to denote removal of the data from its current owning context (with replacement by some default, e.g., None
for an Option
type).
Perhaps implementing From<UserConfigPath> for PathBuf
would be more clear?
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.
Ah, the From<UserConfigPath>
looks like the way to go, thanks.
} | ||
} | ||
|
||
pub trait PackageForConfig { |
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 sure the name of this trait accurately captures the intention behind the trait; what does PackageForConfig
mean?
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'll try to come up with some better name for it. And some docs. Probably will take me more time than addressing the rest of the issues here. ;)
This trait was meant to be basically a subset of the Pkg
struct, so I could mock it in tests.
"user configuration location", | ||
ucp.get_path().display(), | ||
recommended_path.display(), | ||
); |
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 format string has three placeholders, but only two values are supplied.
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 has three values, first is the "user configuration location"
string. I did it that way, otherwise the line was over 100 chars long and rustfmt was bailing out… I'll split it out to a separate function so I'll have less indentation and more space for the string.
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.
Ah, you're right... I totally missed that.
You can also split long strings up, like we do here:
habitat/components/sup/src/main.rs
Lines 160 to 162 in cc079b6
(about: "Load a service to be started and supervised by Habitat from a package or \ | |
artifact. Services started in this manner will persist through Supervisor \ | |
restarts.") |
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.
Ah, nice. That's exactly what I need. Thanks!
cfg.load_user(&package)?; | ||
cfg.load_environment(&package)?; | ||
cfg.load_user(package)?; | ||
cfg.load_environment(package)?; |
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.
Ah, thanks for cleaning up those unnecessary &
s 👍
} | ||
} | ||
} | ||
// No user config found at all. Next time try loading only the |
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.
When is "next time"? As far as I can see, this is only ever called when creating a new Cfg
struct.
I presume this is laying the groundwork for refreshing the user.toml
content based on file watch events... in that case, is the intention to never change which user.toml
path is being looked at? If this starts up and a user.toml
in the new location is not present, but one at the old path is present, won't this always look at the old path, even if a new one shows up later? If that's the case, it may simplify this code and make it easier to reason about if we split this function into two functions: one that does the initial determination of which path we're going to look at, and the other that does the actual consumption of content. Actually, in that case, we could probably just use the original code after we figured out which path to consult.
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, we basically want to stick to a single path, which is determined at the time when we load the config for the first time.
I'll have a look at splitting the functions. I also had an idea of cleaning up the load_{default,user,environment} functions, so they don't take the self
parameter. This would be to avoid using Cfg::default
, which forces the user_config_parameter
to be an Option
, because it's value is set sometime later in the new
function.
@@ -101,6 +101,8 @@ pub struct Pkg { | |||
pub svc_run: PathBuf, | |||
pub svc_user: String, | |||
pub svc_group: String, | |||
pub user_path: PathBuf, |
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.
Hrm... that's a good question. I wonder if these really need to be public members of the Pkg
struct at all; maybe they should just stay function calls (which you use with your PackageForConfig
trait). You could just use the fs::user_path(&package.ident.name)
, etc. implementations directly in your trait implementation for Pkg
, get the exact same behavior, and avoid any serialization issues.
0d48872
to
b41f281
Compare
Updated the PR. I dropped the user config path persistence for now, I'll make it a part of the follow up PR with the user config watching functionality. But I have refactored |
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 👍
I left some feedback on ways to simplify some of the test code, but beyond that I like the changes you've made. We should be able to merge this very soon.
gossip: None, | ||
environment: environment, | ||
gossip_incarnation: 0, | ||
}); |
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 how this ended up!
file.read_to_string(&mut buf).expect( | ||
"read value from toml file", | ||
); | ||
assert_eq!(buf, self.text); |
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 implementation is more complex than it needs to be for these tests; you probably just need a write_all
and a flush
. The assert_eq!
is basically testing that Rust's I/O code is working.
Beyond that, though, I think there's a larger opportunity to simplify this test code. There's a lot of repetition in the test cases themselves around opening a file, creating a representation of the desired TOML, and then writing it. In the end, you just want one function that takes a path and some content, and writes that data to the right place. That one function could hide all the details of file opening, formatting, and writing, which are really incidental to the tests themselves. When you're reading the tests, you just want one line that says "OK, I'm creating this config file at this location"; you don't want to worry about the details of how that's done.
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 added this assert_eq, because earlier I had a reset
function that cleared a file and wrote new contents there. So I truncated it and then wrote new contents only to find out that I got a bunch of zeros at the beginning of the file, because truncating is not seeking.
Anyway, I'll have a look how to make it simpler.
In some follow up commit I'd like to add another field, that does not have an obvious default value. Either I could work it around by putting it inside Option (and then unwrap() the field with assumption that after Cfg::new() finishes, it will never be None) or just stop using Cfg::default(). I went with the latter. Signed-off-by: Krzesimir Nowak <krzesimir@kinvolk.io>
This commit makes the supervisor to look for user configuration in two locations: /hab/svc/<pkgname>/user.toml and /hab/user/<pkgname>/config/user.toml. If the first path is missing, it will try to load the configuration from the second one. The reason is to work around an issue in Kubernetes/Docker/Linux kernel. In the Habitat operator we use the Kubernetes Secret feature for putting user configuration as an initial configuration and as a source of configuration updates. Initially it worked fine, until we discovered that Kubernetes actually mounts a directory at /hab/svc/<pkgname> to put user.toml there, so all the previous contents there were effectively hidden. We worked it around with a SubPath feature of Kubernetes, which basically instead of mounting a directory at /hab/svc/<pkgname>, bind-mounts some file on the host to the /hab/svc/<pkgname>/user.toml path inside the Kubernetes pod. This unfortunately breaks updating the configuration via the Kubernetes Secret. This is because Kubernetes tries hard to make the change of the Secret atomic. The atomicity of the change is achieved with renaming. Doing the rename changes the inode of the target file and since the bind mounts are based on inodes, the change is not reflected in the pod. It may take some time before we come up with a solution for this problem and the solution to be released, so for now we would like to add the second path for the user configuration. The new /hab/user/<pkgname>/config directory can only contain user.toml file, so it is safe to mount a directory here with our own copy of the file, and we can stop using SubPath feature, so the updates should work again. Signed-off-by: Krzesimir Nowak <krzesimir@kinvolk.io>
Introduce the PackageConfigPaths trait, which will allow us to override the paths where the user configuration should be put for testing purposes. Before that, the paths were basically hardcoded to something under `/hab`. There is an environment variable (`TESTING_FS_ROOT`) for changing the filesystem root, where `hab` directory is being placed, but it is not really usable from unit tests. Signed-off-by: Krzesimir Nowak <krzesimir@kinvolk.io>
b41f281
to
2e914c9
Compare
Updated. The tests are now indeed simpler. |
Signed-off-by: Krzesimir Nowak <krzesimir@kinvolk.io>
Setting up the Package struct in this test was necessary just to have things to compile, otherwise nothing from the struct was used at all. With the TestPkg struct in place, we don't need to do the setup anymore. Signed-off-by: Krzesimir Nowak <krzesimir@kinvolk.io>
2e914c9
to
50f3aad
Compare
|
🤘 I am testing your branch against master before merging it. We do this to ensure that the master branch is never failing tests. |
Travis CI has started testing this PR. |
💖 Travis CI reports this PR passed. It always makes me feel nice when humans approve of one anothers work. I'm merging this PR now. I just want you and the contributor to answer me one question: |
The path was changed in habitat-sh/habitat#3814 Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
The path was changed in habitat-sh/habitat#3814 Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
The path was changed in habitat-sh/habitat#3814 Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
The path was changed in habitat-sh/habitat#3814 Signed-off-by: Lorenzo Manacorda <lorenzo@kinvolk.io>
This commit makes the supervisor to look for user configuration in two
locations:
/hab/svc/<pkgname>/user.toml
and/hab/user/<pkgname>/config/user.toml
. If the first path is missing, itwill try to load the configuration from the second one.
The reason is to work around an issue in Kubernetes/Docker/Linux
kernel.
In the Habitat operator we use the Kubernetes Secret feature for
putting user configuration as an initial configuration and as a source
of configuration updates. Initially it worked fine, until we
discovered that Kubernetes actually mounts a directory at
/hab/svc/<pkgname>
to putuser.toml
there, so all the previouscontents there were effectively hidden.
We worked it around with a SubPath feature of Kubernetes, which
basically instead of mounting a directory at
/hab/svc/<pkgname>
,bind-mounts some file on the host to the
/hab/svc/<pkgname>/user.toml
path inside the Kubernetes pod.
This unfortunately breaks updating the configuration via the
Kubernetes Secret. This is because Kubernetes tries hard to make the
change of the Secret atomic. The atomicity of the change is achieved
with renaming. Doing the rename changes the inode of the target file
and since the bind mounts are based on inodes, the change is not
reflected in the pod.
It may take some time before we come up with a solution for this
problem and the solution to be released, so for now we would like to
add the second path for the user configuration. The new
/hab/user/<pkgname>/config
directory can only containuser.toml
file,so it is safe to mount a directory here with our own copy of the file,
and we can stop using SubPath feature, so the updates should work
again.
Signed-off-by: Krzesimir Nowak krzesimir@kinvolk.io
Also, there seem to be an agreement that this file should be moved out of the
/hab/svc
directory, so users do not need to mess with the service specific files there.