-
Notifications
You must be signed in to change notification settings - Fork 11
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
Implement a builder-style provisioning API #91
Conversation
Code looks good in general. 👍
Looks good to me. |
0af28d4
to
0fb670c
Compare
+1 on having the default iterate through all available backend and letting user pass in their own backend |
841044a
to
8849b20
Compare
Okay, I've made a few changes and this should have all the things implemented (but not documented yet). At this point I probably could break some of these changes out into smaller PRs, like the ssh authorized_key tests/changes. After a bunch of fiddling I decided I didn't love the "iterate through all the backends" approach and decided that the user needs to specify which ones they want to use. It makes the interface more verbose, and to be honest I don't really like what I've built, but my experience writing library APIs in Rust is not large. However, what I've got is, I think, service-able and can do the things we want it to. I've added some feature flags to show how a user could restrict which backends get used. |
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 the approach in general.
While I am in the middle of reading code, I left comments below:
libazureinit/src/provision/mod.rs
Outdated
pub struct HostnameBackend; | ||
#[derive(Default)] | ||
pub struct PasswordBackend; | ||
#[derive(Default)] |
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.
Maybe UseraddBackend
is missing?
35bb8a0
to
7f9f689
Compare
libazureinit/src/provision/user.rs
Outdated
"Provisioning agent created this user based on username provided in IMDS", | ||
) | ||
.arg("--groups") | ||
.arg("adm,audio,cdrom,dialout,dip,floppy,lxd,netdev,plugdev,sudo,video") |
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.
For Azure Linux, we will want to specify an alternative list of default groups for the user (example).
There's a few different ways to specify this, but perhaps an environment variable is the most logical? As that is how we are handling distro-specific knowledge in the other situations (i.e., binary paths).
To illustrate current behavior, here is a test trying to call path_useradd with this --groups
list, and there are a few groups that aren't recognized on AzL. (See photo)
*Note: this test was built off of #100 so there may be some additional differences in the code, but the result of calling useradd
should be the same as, at the time of this writing, both PRs use the same --groups list
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.
Hm, yes. I added a commit to this PR that sets it via environment variable, but I'll mull on it more over the weekend.
I do worry about the discoverability of this approach. I think it's a little weird to have a library accept configuration this way instead of exposing an API and letting the application using the library handle it (either via build-time environment variables, or CLI options, or what have you).
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 added a User
type in commit 3b4b94b and threaded it through the API. It bundles all the user-related configuration together and allows you to optionally provide a list of supplementary groups. How does that look to you?
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.
bd29ce6
to
56ddd3f
Compare
libazureinit/src/provision/mod.rs
Outdated
hostname: impl Into<String>, | ||
username: impl Into<String>, | ||
ssh_keys: impl Into<Vec<PublicKeys>>, | ||
) -> Self { |
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.
The code looks okay as long as we have only 3 parameters for new()
.
Just a thought. I am wondering how much scalable it could be, for example when we would have 10 - 20 backends.
Then callers need to pass 10 - 20 parameters to new()
, which could be tricky to follow and maintain.
However, as far as I understand, Rust does not have a native way of dealing variable args, right?
So we might need to split those into separate structs in the future.
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, new()
is just for required arguments and any change there would be an API breakage. I felt like these three would always be required, but we could also accept no arguments at all to new()
and if you don't want to create a user for whatever reason, you don't have to. At that point, though, I don't know why you'd use the API.
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 adding the group I added a dedicated type for user configuration so it reduces the number of parameters to two.
624ce3e
to
3b4b94b
Compare
|
||
/// A list of supplemental group names to add the user to. | ||
/// | ||
/// If any of the groups do not exist on the host, provisioning will fail. |
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 could, instead, probe for groups that exist and only add the user to those groups. I don't love that because it feels spooky and unexpected ("I asked it to add the user to these groups and it didn't, but also didn't fail"), but on the other hand you wouldn't have to hand-tune the list for each distribution.
3b4b94b
to
4199f8b
Compare
This provides a unified interface to provision the host with the option to select the tool used for setting the hostname, creating the user, and so on. By default, the library will try all the provisioning methods it knows of until one succeeds. Users of the library can optionally specify a subset to attempt when provisioning. This allows users to decide which tool or tools to use when provisioning. Some feature flags have been added to `azure-init` which enable provisioning with a tool, letting you build binaries for a particular platform relatively easily.
4199f8b
to
4aef549
Compare
Okay, I cleaned up the Rust docs and ensured all the publicly exported types have documentation, I think this is now ready. |
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.
Thanks
Thank you Jeremy!!! |
Implement a single `Provision` interface This provides a unified interface to provision the host with the option to select the tool used for setting the hostname, creating the user, and so on. By default, the library will try all the provisioning methods it knows of until one succeeds. Users of the library can optionally specify a subset to attempt when provisioning. This allows users to decide which tool or tools to use when provisioning. Some feature flags have been added to `azure-init` which enable provisioning with a tool, letting you build binaries for a particular platform relatively easily.
This is based on top of PR #86 so I'd recommend just looking at the last commit in the series. It is a first draft of a builder-style interface to allow library users to select the backend for tools when provisioning.
It's not ready to be merged since there's no tests, documentation, or
implementation of default behavior when no backend is explicitly
selected. Additionally, I've not gated backends behind feature flags,
either. I'd love feedback on the general approach and what to do by
default.
My personal opinion is that the default option should iterate through
all available backends until one succeeds.