-
Notifications
You must be signed in to change notification settings - Fork 100
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
flowey: use typestate pattern for gh variables #643
base: main
Are you sure you want to change the base?
flowey: use typestate pattern for gh variables #643
Conversation
let parent_path = ctx | ||
.get_gh_context_var() | ||
.global(GhContextVar::GITHUB__WORKSPACE); | ||
let test_pull_request_event = ctx.get_gh_context_var().event().pull_request(); |
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.
presumably this will be removed before merge, yeah?
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 just left in to show it working properly, will remove in a commit that also addresses whatever comments are left
@@ -685,10 +706,10 @@ pub trait NodeCtxBackend { | |||
uses: &str, | |||
with: BTreeMap<String, ClaimedGhParam>, | |||
condvar: Option<String>, | |||
outputs: BTreeMap<String, Vec<(String, bool)>>, | |||
outputs: BTreeMap<String, Vec<(String, bool, bool)>>, |
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 do indeed need this extra bool added to these tuples... I think we should use this opportunity to introduce a proper set of struct
types to use instead of these tuples, with field names that make the intent of each field clear
flowey/flowey_core/src/node.rs
Outdated
@@ -838,7 +838,101 @@ pub struct NodeCtx<'a> { | |||
backend: Rc<RefCell<&'a mut dyn NodeCtxBackend>>, | |||
} | |||
|
|||
impl NodeCtx<'_> { | |||
#[derive(Serialize, Deserialize)] |
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.
all these new types you're adding here should be organized under a new module, to avoid polluting this already messy namespace.
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.
moved into a new module but that also required making some fields public that weren't previously. let me know if there was a better way to go about this
flowey/flowey_core/src/node.rs
Outdated
} | ||
|
||
impl<'a> GhContextVarReader<'a, ghvarstate::Root> { | ||
pub fn global(&self, gh_var: GhContextVar) -> ReadVar<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.
please make a helper method to avoid duplicating the mostly-identical body between this and other methods
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.
added a helper for the duplicate functionality
@@ -836,20 +836,28 @@ fn resolve_flow_as_github_yaml_steps( | |||
output_steps.push(map.into()); | |||
} | |||
|
|||
for (rust_var, gh_var, is_secret) in rust_to_gh { | |||
for gh_var_state in rust_to_gh { | |||
let mut cmd = String::new(); | |||
|
|||
// flowey considers all GitHub vars to be typed as raw strings |
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.
stale comment - not actually true with the introduction of is_object
flowey/flowey_core/src/lib.rs
Outdated
@@ -7,6 +7,7 @@ | |||
//! this crate!** The crate you should be using is called `flowey`, which only | |||
//! exports user-facing types / traits. | |||
|
|||
pub mod github_context; |
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 mod should be a mod under mod node
, not a new top-level mod.
this should also help with some of the visibility issues you might've run into
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.
good call, this fixed pretty much all the visibility stuff I was having issues with
pub head: Head, | ||
} | ||
|
||
pub enum Root {} |
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.
you still want to stuff these ZSTs into their own mod state
inline module, just to avoid polluting this namespace with generic-sounding things like Root
and Event
|
||
//! Core types and traits used to read GitHub context variables. | ||
|
||
use crate::node::{user_facing::GhContextVar, ClaimVar, NodeCtx, ReadVar, StepCtx}; |
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.
each import on its own line please.
the guide has some suggestions on how to configure rust-analyzer to do this for you
flowey/flowey_core/src/node.rs
Outdated
@@ -193,8 +194,8 @@ pub enum VarClaimed {} | |||
/// is possible to infer what order steps must be run in. | |||
#[derive(Debug, Serialize, Deserialize)] | |||
pub struct WriteVar<T: Serialize + DeserializeOwned, C = VarNotClaimed> { | |||
backing_var: String, | |||
is_secret: bool, | |||
pub backing_var: 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.
yeah, this pub def won't fly 😅
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.
ditto with any other pub
you've added to user_facing
types
flowey/flowey_core/src/node.rs
Outdated
@@ -1280,7 +1266,7 @@ impl NodeCtx<'_> { | |||
|
|||
#[track_caller] | |||
#[must_use] | |||
fn new_maybe_secret_var<T>( | |||
pub fn new_maybe_secret_var<T>( |
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 is an internal helper - def don't want this public
} | ||
|
||
impl<'a> GhContextVarReader<'a, Root> { | ||
pub fn global(&self, gh_var: GhContextVar) -> ReadVar<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.
now that you have this read_var
helper, I really think that it's not that hard to swap the current const
ants with standalone helpers - especially since you've already needed to touch those files as part of this global
method refactoring.
the only thing you'll need to tweak is the user-defined secret var handling, but even that should be pretty straightforward, if you simply swap the current type that flow uses (i.e: the GhContextVar
type we want to go away), with a far more limited newtype around a String
called GhUserSecretVar
, which is what gets created at the Pipeline level, and plumbed down to a new pub fn secret()
method that hangs off this GhContextVarReader
.
flowey/flowey_core/src/node/mod.rs
Outdated
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're not the most consistent with using mod.rs
vs. name.rs
+ name/submod.rs
... but I think in this case, we should leave this file as node.rs
impl<S> GhContextVarReader<'_, S> { | ||
fn read_var<T: Serialize + DeserializeOwned>( | ||
&self, | ||
var_name: 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.
this helper can be made marginally more ergonomic by using impl AsRef<str>
here. that way - you don't need to invoke .to_string()
or .into()
when passing &'static str
args to it
@@ -22,7 +22,6 @@ pub mod build_and_run_doc_tests; | |||
pub mod build_and_run_nextest_unit_tests; | |||
pub mod build_and_run_nextest_vmm_tests; | |||
pub mod cfg_common; | |||
pub mod cfg_gh_azure_login; |
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.
On one hand, I'd prefer if we avoided dropping github-secrets functionality on the ground here, since it seems highly probably that there will come a point in the not-so-far-away future where we'll want to run some stuff in CI that relies on private auth.
On the other hand, yes, we don't currently use this secrets stuff on github, so deleting it in the face of this refactors doesn't "break" anything, and lets us avoid the work of updating the current secret-handling APIs to work with the new github var infrastructure.
I'll let @benhillis chime in on his opinion, as I'm not very cognizant of the time tradeoffs when working on this sort sort of foundational infrastructure, and I don't have a full picture of your other priorities.
My 2c: I already sketched out in another comment how you can port the current gh secret infra to this new foundation, and can discuss it more offline if need be. It don't think it'd be that hard, and it'll make life easier for whoever is next up-to-bat in this space.
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.
Talked offline but I pushed this in a half-broken state (not ready for review) to work on a different machine. I'll reintroduce this file and the API I removed for custom secrets in a commit today.
#[derive(Clone, Debug)] | ||
pub enum GhParam<C = VarNotClaimed> { | ||
Static(String), | ||
GhVar(GhContextVar), |
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.
losing this means we are slightly more inefficient when invoking certain Github Actions from flowey, as we might end up round-tripping a github native variable through a flowey Var for "no reason".
but honestly, that doesn't seem like a big deal, given the perf hit isn't particularly noticible, and could be optimized later if need be.
so this is fine
flowey/flowey_core/src/pipeline.rs
Outdated
@@ -355,6 +356,9 @@ pub struct Pipeline { | |||
gh_bootstrap_template: String, | |||
} | |||
|
|||
#[derive(Serialize, Deserialize, Clone)] | |||
pub struct GhUserSecretVar(pub 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.
lets move this to node.rs
.
also, we should try to avoid making its guts totally pub
. Maybe pub(crate)
if need be. but in general, I don't think we want users to really poke around the internal repr of this type after creating it via a Pipeline level 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.
also, this needs a doc comment
flowey/flowey_core/src/node.rs
Outdated
@@ -54,6 +57,8 @@ pub mod user_facing { | |||
pub use crate::flowey_request; | |||
pub use crate::new_flow_node; | |||
pub use crate::new_simple_flow_node; | |||
pub use crate::node::github_context::state; | |||
pub use crate::node::github_context::GhVarState; |
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 shouldn't be user-facing?
#[derive(Serialize, Deserialize)] | ||
pub struct Head { | ||
#[serde(rename = "ref")] | ||
pub head_ref: String, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct GhContextVarReaderEventPullRequest { | ||
pub head: Head, | ||
} |
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.
these types, which directly correspond to the JSON spec defined by GitHub Actions, should be in their own mod spec
, with some doc-comments pointing folks at the docs that define the schema
use serde::Deserialize; | ||
use serde::Serialize; | ||
use std::collections::BTreeMap; | ||
|
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 suspect you'll want to have a new pub mod user_facing
here which will let you slice-up what public / private parts of this new API.
and then you can pub use github_context::user_facing::*
under the node
-level user_facing
module.
Instead of hardcoding variable names that may or not be present, we can use the typestate pattern to allow only valid variables at compile time. Motivated by #475, which uses the
github.event.pull_request.head.ref
in its implementation.