Skip to content
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

Add Starlark functionality in the SDK #18

Open
PI-Victor opened this issue Aug 19, 2024 — with Linear · 2 comments
Open

Add Starlark functionality in the SDK #18

PI-Victor opened this issue Aug 19, 2024 — with Linear · 2 comments
Assignees
Labels
enhancement New feature or request triage/accepted

Comments

Copy link
Member

PI-Victor commented Aug 19, 2024

The SDK should abstract away things like creating the starlark functions and the structures related to it in a way that the user would not have to deal with them.

The definition of a network resource in the hetzner plugin will look similar to this

network(
    name = "network",
    cidr = "10.0.0.1/16",
    description = """
    The network module is responsible for managing
    the network cloud resource
    """,
)

in starlark-rs, this would translate to this:

#[starlark_module]
pub fn hetzner_module(builder: &mut GlobalsBuilder) {
  fn network<'v>(
        name: String,
        cidr: Value<'v>, // NetworkCidr needs to be defined as below to be valid for starlark downcast
    ) -> anyhow::Result<PluginMount> {
      // We downcast the Value<'v> into our own type
      let network_cird = cidr
          .downcast_ref::<PluginMount>()
          .with_context(|| "failed to interpret mount declaration")?;

        Ok(NetworkRespStruct) //pseudocode, return a local representation of a network resource that can be reused.
    }
}

all structures defined by the user have to have the same functions as the one below, in order to be available and be valid for starlark:

NOTE: This is a simplification, not the final resource definition

#[derive(Debug, PartialEq, Eq, ProvidesStaticType, NoSerialize, Allocative, Clone)]
pub struct NetworkCidr {}

impl<'v> AllocValue<'v> for NetworkCidr {
    fn alloc_value(self, heap: &'v Heap) -> Value<'v> {
        heap.alloc(self)
    }
}

impl fmt::Display for NetworkCidr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "test plugin")?;
        Ok(())
    }
}

#[starlark_value(type = "network_cird")]
impl<'v> StarlarkValue<'v> for NetworkCidr {}

When writing plugins with the skyforge SDK, the user shouldn't have to implement this themselves, the easiest way would be to generate both the function network<'v> and the struct that they need to accept as a parameter e.g.NetworkCidr .

The SDK macro, should expose a derive that can generate the above for the structs that are accepted as parameters and need to be converted, and a procedural macro for the network<'v> function.

The SDK lib should offer a way to add these to the starlark runtime, where the user defines their own structs and functions and simply passed those to the runtime to be loaded, without having to write the boilerplate.

This functionality is only implemented right now in Skycrane, but it looks like this:

 let globals = GlobalsBuilder::new().with(skycrane_std).build();
    let module = Module::new();
    // for some reason, the standard starlark env has no idea of tru and false,
    // so we manually have to add them each time.
    module.set("true", Value::new_bool(true));
    module.set("false", Value::new_bool(false));

    let mut eval = Evaluator::new(&module);

    let raw_ast = AstModule::parse("module.star", config.to_string(), &Dialect::Standard).unwrap();
    let codemap = raw_ast.codemap();
    let filtered_script = raw_ast
        .stmt_locations()
        .into_iter()
        .filter_map(|file_span| {
            let snippet = codemap.source_span(file_span.span);
            // TODO: this should be a hash map of statements
            // snippet.contains("module")
            if snippet.contains("module") {
                Some(snippet)
            } else {
                None
            }
        })
        .collect::<Vec<_>>()
        .join("\n");

    let filtered_ast =
        AstModule::parse("module.star", filtered_script, &Dialect::Standard).unwrap();

    let res = eval.eval_module(filtered_ast, &globals).unwrap();
    let cloud_mod = res
        .downcast_ref::<CloudModule>()
        .ok_or(anyhow::anyhow!("failed to interpret CloudModule"))?;

    Ok(cloud_mod.clone())

Another thing that needs to be done here is safe parsing of the string that is passed from skycrane. The config shouldn't crash because some resource definition that appears in the config is not currently defined in the plugin, so we should only load definitions that are present. This is another thing that needs to be abstracted away, somehow. The user should call a function to register all the functions they need, but these need to be in a string format. But this should be verified first.

/triage

/assign

/label enhancement

@PI-Victor PI-Victor self-assigned this Aug 19, 2024
Copy link

linear bot commented Aug 19, 2024

@PI-Victor PI-Victor transferred this issue from cloudflavor/oscar Aug 19, 2024
Copy link

linear bot commented Aug 19, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request triage/accepted
Development

No branches or pull requests

1 participant