Skip to content

Spin up should check required variables #3213

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

seun-ja
Copy link
Contributor

@seun-ja seun-ja commented Aug 5, 2025

Closes #3115

The PR checks if the variable default isn't provided when required. The check is handled by a new hook: VariableSorterExecutorHooks.

So now it's captured before runtime, you'd get some error like this

seun-ja added 5 commits August 3, 2025 12:21
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
@@ -135,6 +136,21 @@ impl LocalLoader {
})
}

fn env_checker((key, val): (String, Variable)) -> anyhow::Result<(String, Variable)> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This function name needs to be more descriptive. What is it checking?

@@ -135,6 +136,21 @@ impl LocalLoader {
})
}

fn env_checker((key, val): (String, Variable)) -> anyhow::Result<(String, Variable)> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not clear why this takes a tuple rather than two parameters?

@@ -88,7 +89,7 @@ impl LocalLoader {

let variables = variables
.into_iter()
.map(|(name, v)| Ok((name.to_string(), locked_variable(v)?)))
.map(|(name, v)| Self::env_checker((name.to_string(), locked_variable(v)?)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Doing this here means appears to mean we can't even build the LockedApp without having the variables available. How does this worth with e.g. spin registry push or spin cloud deploy?

@@ -135,6 +136,21 @@ impl LocalLoader {
})
}

fn env_checker((key, val): (String, Variable)) -> anyhow::Result<(String, Variable)> {
if val.default.is_none() {
if std::env::var(env_key(None, key.as_ref())).is_err() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can't assume that variables will come from the host environment. They could come from Vault or Azure KeyVault - for example passwords or tokens would typically be stashed in vaults and it would be rude to demand users create dummy EVs for them. Variable sources are defined in the runtime config file - you can't validate them until you have the runtime config providers lined up.

@@ -135,6 +136,21 @@ impl LocalLoader {
})
}

fn env_checker((key, val): (String, Variable)) -> anyhow::Result<(String, Variable)> {
if val.default.is_none() {
if std::env::var(env_key(None, key.as_ref())).is_err() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

How do you know that the user is using the default prefix?

@@ -135,6 +136,21 @@ impl LocalLoader {
})
}

fn env_checker((key, val): (String, Variable)) -> anyhow::Result<(String, Variable)> {
if val.default.is_none() {
if std::env::var(env_key(None, key.as_ref())).is_err() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The environment provider appears to support a dotenv_path setting to merge values from a file - not clear that this is respected here?

@@ -88,7 +89,7 @@ impl LocalLoader {

let variables = variables
.into_iter()
.map(|(name, v)| Ok((name.to_string(), locked_variable(v)?)))
.map(|(name, v)| Self::env_checker((name.to_string(), locked_variable(v)?)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe a nit, but doing this in the loader produces an error message that implies we can't understand the manifest (usually meaning invalid TOML or incorrect schema): that's potentially misleading.

@karthik2804
Copy link
Contributor

I think adding a new hook before execution of the CLI for the variables factor in crates/trigger/src/cl as discussed offline earlier might be a better place to run the check.

seun-ja added 3 commits August 6, 2025 18:25
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
Signed-off-by: Aminu 'Seun Joshua <[email protected]>
@seun-ja
Copy link
Contributor Author

seun-ja commented Aug 6, 2025

Interestingly, the test failing in CI doesn't fail locally. Investigating it at the moment.

Signed-off-by: Aminu 'Seun Joshua <[email protected]>
@seun-ja seun-ja requested a review from itowlson August 7, 2025 17:46
Copy link
Collaborator

@itowlson itowlson left a comment

Choose a reason for hiding this comment

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

I feel like I stuffed up communication in my previous review, sorry. The Spin variables system allows the user to configure variables to be read from multiple different sources. You cannot rely on the environment being the only source. You need to rethink this PR to completely forget about the environment. It needs to go through a configured ProviderResolver or whatever it's called at the moment, the thing that gets set up in the VariablesFactor and checks all configured sources for values. That will do the environment lookup, and any other lookups - the validation code should not be attempting a partial emulation of its behaviour, you should just use what exists.

@@ -64,12 +64,13 @@ spin-oci = { path = "crates/oci" }
spin-plugins = { path = "crates/plugins" }
spin-runtime-factors = { path = "crates/runtime-factors" }
spin-telemetry = { path = "crates/telemetry", features = [
"tracing-log-compat",
"tracing-log-compat",
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a few of these unrelated layout changes - maybe turn off auto formatting for this file?

@@ -100,6 +101,8 @@ impl<T: RuntimeFactors> TestEnvironment<T> {
pub async fn build_locked_app(manifest: &toml::Table) -> anyhow::Result<LockedApp> {
let toml_str = toml::to_string(manifest).context("failed serializing manifest")?;
let dir = tempfile::tempdir().context("failed creating tempdir")?;
// `foo` variable is set to require. As we're not providing a default value, env is checked.
std::env::set_var(env_key(None, "foo"), "baz");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still say we should be able to transform a TOML manifest into a LockedApp without doing validation on variables.

use spin_factors_executor::ExecutorHooks;
use spin_variables::{VariableProviderConfiguration, VariableSourcer};

/// Implements TriggerHooks, sorting required variables
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorting? Maybe comment on why they need sorting?

use spin_variables::{VariableProviderConfiguration, VariableSourcer};

/// Implements TriggerHooks, sorting required variables
pub struct VariableSorterExecutorHooks {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure about the naming here. Why is it an "Executor" hook?

tokio = { workspace = true, features = ["fs", "rt"] }
toml = { workspace = true }
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think Ryan had tried to push TOML dependencies into the runtime config crate, so that the trigger crate was abstracted from the physical representation of the runtime config? (For e.g. SpinKube scenarios.) I am not sure though.

}

impl VariableSourcer for VariableSorterExecutorHooks {
fn variable_env_checker(&self, key: String, val: spin_app::Variable) -> anyhow::Result<()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

variable_env_checker is a noun. I think the idea here is something like check_variable_has_value or check_variable_resolves.

return self.check(key, val, dotenv_path, prefix);
}

Err(anyhow::anyhow!("No environment variable provider found"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure what "provider" refers to in this context. Have I misunderstood the purpose of this function and it's actually trying to check if the env provider is present?

_ = std::env::set_current_dir(path);
}

match std::env::var(env_key(prefix, &key)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can't do this. There are providers other than the environment one. It is legal for a variable to come from Vault or Azure KeyVault or the static provider rather than the environment one. I tried to stress this in my previous review, sorry - this is fundamental.

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.

spin up should check required variables
3 participants