-
Notifications
You must be signed in to change notification settings - Fork 8
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
Env helper layers #598
Env helper layers #598
Conversation
1d1127b
to
70f4366
Compare
4e317c3
to
36632a1
Compare
@schneems i like this idea of reducing layer boilerplate for common operations like settings environment variables. my one complaint is that there are two different layer types (
enum EnvVar {
Basic(String, String) // key, val
Full(Scope, ModificationBehavior, String, String)
}
env_layer::create(&context, layer_name!("my_env"), [
EnvVar::Basic("JRUBY_OPTS", "-Xcompile.invokedynamic=false"),
EnvVar::Full(Scope::All, ModificationBehavior::Override, "BUNDLE_CLEAN", "1"),
])?;
env_layer::builder()
.set("JRUBY_OPTS", "-Xcompile.invokedynamic=false")
.set_with_options("BUNDLE_CLEAN", "1", Scope::All, ModificationBehavior::Override)
.build()?; apart from that, i think this is a useful addition. |
My specific need was to set default env vars, but I didn't want to make N different types of ways to handle all the variants (3xScope multiplied by 5xbehavior). I was trying to re-use as much as possible and since I would state that my minimum goals are:
To that end I've considered the builder (sketch): let layer = env_layer::builder(
&context,
layer_name!("configure_env")
)
.default_all(
"JRUBY_OPTS",
"-Xcompile.invokedynamic=false"
)
.set_with_options(
"BUNDLE_CLEAN",
"1",
Scope::All, ModificationBehavior::Override
)
.build()?; Versus this proposal: let layer = env_layer::set_envs(
&context,
layer_name!("configure_env"),
LayerEnv::new()
.chainable_insert(
Scope::All,
ModificationBehavior::Default,
"JRUBY_OPTS",
"-Xcompile.invokedynamic=false",
)
.chainable_insert(
Scope::All,
ModificationBehavior::Override,
"BUNDLE_CLEAN",
"1",
)
)?; I feel like they're both in the same ballpark. I'm also open to updating my opinion based on use some real world use cases from other existing buildpacks. Maybe there are other operations that are common and itchy enough that they're worth scratching. One note is that we've got similarly named modules with |
36632a1
to
fced2f0
Compare
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 opposed to the feature, it can be useful and is a good first example of a generic layer that is not buildpack specific.
However, the implementation introduces extra APIs for environment variables I'd like to avoid. The way layer environment variables work in CNBs is not immediately obvious to start with and therefore the APIs of libcnb in this area inherit this.
Couldn't we just have an SetEnvironmentLayer
struct that only has a LayerEnv
field that can be populated using the already existing API? That layer can then be used via context.handle_layer
as any other layer, removing the set_envs
function which has an IMO misleading name and is really just a handle_layer
with extra steps.
I'm not sold on the DefaultEnvLayer
, which introduces a lot of code to avoid specifying Scope
and ModificationBehavior
when calling chainable_insert
. In fact, it removes the ability to scope the environment variables. I think this case can be handled via SetEnvironmentLayer
.
Bottom line: I think having SetEnvironmentLayer
without any extra convenience to build LayerEnv
is the way to go for this feature. If we think LayerEnv
is too tedious to use we should focus our efforts there so that the improvements are available for everyone, not just users of this layer.
Happy to discuss and/or pair if you want to. :)
libherokubuildpack/src/env_layer.rs
Outdated
///# } | ||
/// | ||
///# fn build(&self, context: BuildContext<Self>) -> libcnb::Result<BuildResult, Self::Error> { | ||
/// // Don't forget to apply context.platform.env() too; |
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 reads as if you'd always need to do that - which is not the case. If it were, libcnb should do that automatically. Can we remove 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'm aiming for a higher level abstraction with this API. For someone who wants just to do one or two things and have it mostly do the right thing. I'm worried that someone like that might not understand the finer points of the difference between the different environments and when they're applied or not and just copy and paste example code.
I think most people would want the user's env vars which is confusingly labeled/buried as platform env. One way to solve this documentation issue could be to provide a simple interface for applying platform env. Maybe something like:
let env = Env::from_current()
.with_user_vars(context.platform.env())
I appreciate the 👍 on I'm interested in pushing the case on the
Yes. That was my original implementation. I added the function when I made the PR. My thought was that if someone just ™️ wanted to set an environment variable that Aspirationally: I want to provide two levels of interfaces:
For the "minimal complexity" case, I'm imagining someone wanting to build a buildpack that only sets the environment variables. If they do that, what are they likely to need? How can we give them what they need while minimizing the number of things they need to learn or be exposed to? The downside of adding the functions is that they increase the API surface area a developer might have to comb through to get what they want. The upside is that if they want to do one thing and do it succinctly and directly, then it's right there. |
It seems that:
I'm not saying we shouldn't try and make libcnb.rs both as easy to use as possible and with as little boilerplate, but I do think that "absolute beginner buildpack authors who want something minimal and are easily confused" aren't perhaps our primary target audience? ie: I'm not sure we should be trying to hide things like the layer concept just for the sake of trying to avoid them needing to know about it. (If we can come up with a better env interface in general, great, but I think it's fair for us to expect buildpack authors to know how what layers are in the context of the CNB spec.) |
Coming back to this. In the case of the Some light heuristics from this observation process:
I'll remove the I see |
While going down the path to make the modifications I agreed to I'm reminded that with a different layer abstraction, this might not be needed. For example, if there are more abstractions based on
In that scenario the |
When a developer needs to set environment variables only, they have to make a layer struct and pass it to `handle_layer`. Instead of forcing them to write a boilerplate struct, this commit provides two utility structs: - `DefaultEnvLayer` - Used to set default environment variables - `ConfigureEnvLayer` - Can be used with `LayerEnv::new()` to configure any environment variables (versus `DefaultEnvLayer` only creates default environment variables). An alternative to providing structs could be to provide functions that accept a context and layer name in addition to the required env data.
Added functions so people don't have to bypass the `handle_layer` dance alltogether: - set_default - set_envs After writing that I realized that the `env` namespace could cause confusion since there's commonly an `env` variable: ```rust // This is confusing let env = ... env::set_default(... ``` So I changed the module name to `env_layer` so it avoids confusion. There's some redundancy in `env_layer::DefaultEnvLayer` but I thought it would be more confusing making it `env_layer::Default` (collision with `std::default::Default`).
More rationale given here #598 (comment). I am also removing `set_envs` as it does little for ergonomics.
fced2f0
to
0c82572
Compare
///# } | ||
///# } | ||
/// ``` | ||
pub struct ConfigureEnvLayer<B: libcnb::Buildpack> { |
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.
If we end up keeping this (versus a read/write layer concept), we need to align on naming. We already have a LayerEnv
which this takes, so I didn't want to call it EnvLayer
. We could use Set
instead of Configure
. The module name is meh as well, I'm open to suggestions.
I'm closing this in favor of #805 |
When a developer needs to set environment variables only, they have to make a layer struct and pass it to
handle_layer
. Instead of forcing them to write a boilerplate struct, this commit provides two utility structs:DefaultEnvLayer
- Used to set default environment variablesConfigureEnvLayer
- Can be used withLayerEnv::new()
to configure any environment variables (versusDefaultEnvLayer
only creates default environment variables).An alternative to providing structs could be to provide functions that accept a context and layer name in addition to the required env data.
GUS-W-13815854.