Skip to content

Conversation

@jeremycline
Copy link
Member

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.

@dongsupark
Copy link
Collaborator

Code looks good in general. 👍

My personal opinion is that the default option should iterate through
all available backends until one succeeds.

Looks good to me.

@jeremycline jeremycline force-pushed the provision-builder-api branch from 0af28d4 to 0fb670c Compare June 26, 2024 14:13
@jeremycline jeremycline requested a review from anhvoms June 26, 2024 15:05
@anhvoms
Copy link
Contributor

anhvoms commented Jun 26, 2024

+1 on having the default iterate through all available backend and letting user pass in their own backend

@jeremycline jeremycline force-pushed the provision-builder-api branch 2 times, most recently from 841044a to 8849b20 Compare June 28, 2024 18:41
@jeremycline
Copy link
Member Author

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.

Copy link
Collaborator

@dongsupark dongsupark left a 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:

pub struct HostnameBackend;
#[derive(Default)]
pub struct PasswordBackend;
#[derive(Default)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe UseraddBackend is missing?

@jeremycline jeremycline force-pushed the provision-builder-api branch 2 times, most recently from 35bb8a0 to 7f9f689 Compare July 3, 2024 13:58
"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")
Copy link

@SeanDougherty SeanDougherty Jul 5, 2024

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)
image
*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

Copy link
Member Author

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).

Copy link
Member Author

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?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reran the functional tests on AzL 3.0 using this branch

This has done the trick, thank you Jeremy

image

@jeremycline jeremycline force-pushed the provision-builder-api branch 2 times, most recently from bd29ce6 to 56ddd3f Compare July 5, 2024 21:09
hostname: impl Into<String>,
username: impl Into<String>,
ssh_keys: impl Into<Vec<PublicKeys>>,
) -> Self {
Copy link
Collaborator

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.

Copy link
Member Author

@jeremycline jeremycline Jul 9, 2024

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.

Copy link
Member Author

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.

@jeremycline jeremycline force-pushed the provision-builder-api branch from 624ce3e to 3b4b94b Compare July 9, 2024 20:13

/// 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.
Copy link
Member Author

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.

@jeremycline jeremycline force-pushed the provision-builder-api branch from 3b4b94b to 4199f8b Compare July 11, 2024 15:37
@jeremycline jeremycline changed the title RFC: Implement a builder-style provisioning API Implement a builder-style provisioning API Jul 11, 2024
@jeremycline jeremycline marked this pull request as ready for review July 11, 2024 15:39
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.
@jeremycline jeremycline force-pushed the provision-builder-api branch from 4199f8b to 4aef549 Compare July 11, 2024 15:40
@jeremycline
Copy link
Member Author

Okay, I cleaned up the Rust docs and ensured all the publicly exported types have documentation, I think this is now ready.

Copy link
Collaborator

@dongsupark dongsupark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@dongsupark dongsupark merged commit bc83b93 into Azure:main Jul 11, 2024
@peytonr18
Copy link
Contributor

Thank you Jeremy!!!

@jeremycline jeremycline deleted the provision-builder-api branch July 11, 2024 16:56
balakreddy pushed a commit to balakreddy/azure-init that referenced this pull request Jul 19, 2024
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants