coodev-runner 0.1.42

A simple runner for coodev
Documentation
use crate::{
  actions::{Action, ActionSteps},
  error,
  user_config::{UserActionStep, UserCommandStep, UserStep},
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct CacheOptions {
  pub key: String,
  pub path: String,
}

#[derive(Debug, Clone)]
pub struct SaveCacheAction {}

#[derive(Debug, Clone)]
pub struct RestoreCacheAction {}

#[derive(Debug, Clone)]
pub struct CacheAction {}

fn parse_cache_options(step: &UserActionStep) -> crate::Result<CacheOptions> {
  let with = step
    .with
    .clone()
    .ok_or(error::Error::workflow_config_error(
      "No `with` options provided",
    ))?;
  let cache_options: CacheOptions = serde_yaml::from_value(with).map_err(|_| {
    error::Error::workflow_config_error(
      "`path` and `key` are required in `with` options for `cache` action",
    )
  })?;

  Ok(cache_options)
}

fn create_save_cache_step(
  name: impl Into<String>,
  step: UserActionStep,
) -> crate::Result<UserStep> {
  let CacheOptions { key, path } = parse_cache_options(&step)?;

  let run = format!(
    "
    echo \"Save cache with key: $COODEV_CACHE_DIR/{key} and path: {path}\"
    
    if [ -d {path} ]; then
      tar -czf $COODEV_CACHE_DIR/{key}.tar.gz -C {path} .
      echo \"Cache saved\"
    else
      echo \"No cache found\"
    fi
    "
  );

  let name = Some(name.into());
  let continue_on_error = step.continue_on_error.unwrap_or(true);
  let timeout = step.timeout.unwrap_or("10m".to_string());

  Ok(UserStep::Command(UserCommandStep {
    name,
    image: Some(String::from("ubuntu")),
    run,
    continue_on_error: Some(continue_on_error),
    environments: None,
    secrets: None,
    volumes: None,
    security_opts: None,
    timeout: Some(timeout),
  }))
}

fn create_restore_cache_step(
  name: impl Into<String>,
  step: UserActionStep,
) -> crate::Result<UserStep> {
  let CacheOptions { key, path } = parse_cache_options(&step)?;

  let run = format!(
    "
    echo \"Restore cache with key: $COODEV_CACHE_DIR/{key} and path: {path}\"
    
    if [ -f $COODEV_CACHE_DIR/{key}.tar.gz ]; then
      if [ ! -d {path} ]; then
        mkdir -p {path}
      fi

      tar -zxf \"$COODEV_CACHE_DIR/{key}.tar.gz\" -C {path}
      
      echo \"Restore cache succeed\"
    else
      echo \"No cache found\"
    fi
    "
  );

  let name = Some(name.into());
  let timeout = step.timeout.unwrap_or("10m".to_string());

  Ok(UserStep::Command(UserCommandStep {
    name,
    image: Some(String::from("ubuntu")),
    run,
    continue_on_error: None,
    environments: None,
    secrets: None,
    volumes: None,
    security_opts: None,
    timeout: Some(timeout),
  }))
}

impl Action for SaveCacheAction {
  fn normalize(&self, step: UserActionStep) -> crate::Result<ActionSteps> {
    let name = &step
      .name
      .clone()
      .unwrap_or_else(|| "Save cache".to_string());

    Ok(ActionSteps {
      pre: None,
      run: create_save_cache_step(name, step)?,
      post: None,
    })
  }
}

impl Action for RestoreCacheAction {
  fn normalize(&self, step: UserActionStep) -> crate::Result<ActionSteps> {
    let name = &step
      .name
      .clone()
      .unwrap_or_else(|| "Restore cache".to_string());
    Ok(ActionSteps {
      pre: None,
      run: create_restore_cache_step(name, step)?,
      post: None,
    })
  }
}

impl Action for CacheAction {
  fn normalize(&self, step: UserActionStep) -> crate::Result<ActionSteps> {
    let name = &step.name.clone().unwrap_or_else(|| "Cache".to_string());
    let post_name = format!("Post {}", name);

    Ok(ActionSteps {
      pre: None,
      run: create_restore_cache_step(name, step.clone())?,
      post: Some(create_save_cache_step(post_name, step)?),
    })
  }
}

impl SaveCacheAction {
  pub fn new() -> Self {
    Self {}
  }
}

impl RestoreCacheAction {
  pub fn new() -> Self {
    Self {}
  }
}

impl CacheAction {
  pub fn new() -> Self {
    Self {}
  }
}

#[cfg(test)]
mod tests {
  use crate::utils::test::run_workflow;

  #[tokio::test]
  #[ignore]
  async fn test_cache_absolute_dir() -> anyhow::Result<()> {
    let yaml = r#"
jobs:
  test-job:
    name: Test Job
    working-directories: 
      - /root/test
    steps:
      - name: Cache
        uses: cache
        with:
          key: some-key-${COODEV_SHA}
          path: /root/test
      - name: List Files
        run: |
          echo "List files"
          ls /root/test
      - name: Create File
        run: |
          echo "Create file"
          echo "hello" > /root/test/hello.txt
    "#;

    let _res = run_workflow(yaml).await?;

    Ok(())
  }

  #[tokio::test]
  #[ignore]
  async fn test_cache_relative_dir() -> anyhow::Result<()> {
    let yaml = r#"
jobs:
  test-job:
    name: Test Job
    steps:
      - name: Cache
        uses: cache
        with:
          key: some-key
          path: dir
      - name: List Files
        run: |
          echo "List files"
          if [ -d dir ]; then
            ls -al --color=always dir
            echo "File content $(cat dir/hello.txt)"
          else
            echo "dir not exists"
          fi
      - name: Create File
        run: |
          echo "Create dir and file"
          mkdir -p dir
          echo "hello" > dir/hello.txt

          ls -al --color=always

          cat dir/hello.txt
    "#;

    let _res = run_workflow(yaml).await?;

    Ok(())
  }
}