Skip to content
Open
2 changes: 1 addition & 1 deletion backend/auth-daemon/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# auth-daemon

Sidecar HTTP proxy that injects valid OIDC access tokens into GraphQL requests made from within Argo workflows. Workflows call the daemon on `localhost` instead of the GraphQL API directly; the daemon handles token refresh and auth header injection transparently.
Sidecar HTTP proxy that injects valid OIDC access tokens into GraphQL requests made from within Argo workflows. Workflows call the daemon on `localhost` instead of the GraphQL API directly; the daemon handles token refresh and auth header injection transparently.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this edit seems un-related to this pull request?


## Usage

Expand Down
26 changes: 26 additions & 0 deletions examples/conventional-templates/hello_cli_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this necessary for this PR? I don't think that it shouldn't be included in the examples/conventional-templates directory.

  1. This is not a ClusterWorkflowTemplate, it is a Workflow. These are distinct concepts:
  2. Templates in the examples directory are user facing. That will get ingested by ArgoCD and appear at https://workflows.diamond.ac.uk where users and see it and run it.
  3. It would be better to have an automated rust unit test to test CLI functionality. Not a Workflow that you have to manually submit to the cluster,

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: hello-world-cli-test
labels:
workflows.diamond.ac.uk/science-group-examples: "true"
annotations:
workflows.argoproj.io/title: Example
workflows.argoproj.io/description: |
This workflow is used to test the workflows CLI
with a simple long-running Hello World example.
workflows.diamond.ac.uk/repository: "https://github.com/DiamondLightSource/workflows"
spec:
entrypoint: hello-sleep
templates:
- name: hello-sleep
container:
image: alpine:3.18
command: [sh, -c]
args:
- |
echo "Hello World from workflows CLI"
echo "Sleeping for 20 seconds... Zz... ZZzzz.."
sleep 20
echo "Done"
156 changes: 96 additions & 60 deletions workflows-cli/src/create/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::CreateArgs;
use std::io;
use std::os::unix::fs as fs_sym;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::{fs, process};

pub fn create(args: CreateArgs) {
Expand All @@ -19,38 +19,66 @@ pub fn create(args: CreateArgs) {
}

fn generate_template_repo(args: &CreateArgs, prompt_fn: fn(&str) -> bool) -> Result<(), String> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's a lot of change here, some of it seems to be refactoring.

What actually was the cause of the bug? And what was the fix?
Is it possible to create a unit test for this bug to prevent future regressions?

// Reject workflow files
if args.name.ends_with(".yaml") || args.name.ends_with(".yml") {
return Err(format!(
"Invalid name '{}': create expects a directory name, not a workflow file",
args.name
));
}

let root_path = Path::new(&args.name);
println!("Generating Template Repo: {:?}", args.name);
let new_dir = fs::create_dir(root_path);
match new_dir {
Ok(_) => println!("Created directory: {}", args.name),
Err(e) => {
let err = format!("Failed to create directory {}: {}", args.name, e);
return Err(err);
}
println!("Generating Template Repo: {}", root_path.display());

fs::create_dir(root_path)
.map_err(|e| format!("Failed to create directory {}: {}", root_path.display(), e))?;

println!("Created directory: {}", root_path.display());

let workflows_home = Path::new(&args.workflows_home);

let conventional_src = workflows_home.join("template-boilerplate/conventional-templates");
let helm_src = workflows_home.join("template-boilerplate/helm-based-templates");

// Validate sources BEFORE asking user anything
if !conventional_src.exists() {
return Err(format!(
"Missing conventional template source directory: {}",
conventional_src.display()
));
}
if !helm_src.exists() {
return Err(format!(
"Missing helm template source directory: {}",
helm_src.display()
));
}

let conventional_dest = root_path.join("conventional-templates");
let helm_dest = root_path.join("helm-based-templates");
let workflow_home_path = Path::new(&args.workflows_home);
let conventional_src = workflow_home_path.join("template-boilerplate/conventional-templates");
let helm_src = workflow_home_path.join("template-boilerplate/helm-based-templates");

if !args.manifest {
if prompt_fn("Would you like to store conventional WorkflowTemplate manifests? (y/n)") {
copy_directory(&conventional_src, &conventional_dest);
}
let include_conventional = if args.manifest {
true
} else {
copy_directory(&conventional_src, &conventional_dest);
prompt_fn("Would you like to store conventional WorkflowTemplate manifests? (y/n)")
};

if include_conventional {
copy_directory(&conventional_src, &conventional_dest)?;
println!("Copied conventional templates");
}

if !args.helm {
if prompt_fn("Would you like to store helm-based WorkflowTemplates? (y/n)") {
copy_directory(&helm_src, &helm_dest);
}
let include_helm = if args.helm {
true
} else {
copy_directory(&helm_src, &helm_dest);
prompt_fn("Would you like to store helm-based WorkflowTemplates? (y/n)")
};

if include_helm {
copy_directory(&helm_src, &helm_dest)?;
println!("Copied helm templates");
}

Ok(())
}

Expand All @@ -59,61 +87,69 @@ fn prompt(message: &str) -> bool {
let mut selection = String::new();

loop {
selection.clear();

io::stdin()
.read_line(&mut selection)
.expect("Failed to read line");
if selection.to_lowercase().trim() == "y" {
return true;
} else if selection.to_lowercase().trim() == "n" {
return false;
} else {
println!("Invalid input. Please enter 'y' or 'n'.");

match selection.trim().to_lowercase().as_str() {
"y" => return true,
"n" => return false,
_ => println!("Invalid input. Please enter 'y' or 'n'."),
}
}
}

fn copy_directory(src: &PathBuf, dest: &PathBuf) {
if let Err(e) = fs::create_dir_all(dest) {
eprintln!(
/// Recursively copy directory contents
fn copy_directory(src: &Path, dest: &Path) -> Result<(), String> {
fs::create_dir_all(dest).map_err(|e| {
format!(
"Failed to create destination directory {}: {}",
dest.display(),
e
);
return;
}
if let Ok(entries) = fs::read_dir(src) {
for entry in entries.flatten() {
let src_path = entry.path();
let dest_path = dest.join(entry.file_name());

if src_path.is_symlink() {
if let Ok(link_target) = fs::read_link(&src_path) {
#[cfg(unix)]
if let Err(e) = fs_sym::symlink(&link_target, &dest_path) {
eprintln!("Failed to create symlink {}: {}", dest_path.display(), e);
}
}
} else if src_path.is_dir() {
if let Err(e) = fs::create_dir_all(&dest_path) {
eprintln!("Failed to create directory {}: {}", dest_path.display(), e);
continue;
}
copy_directory(&src_path, &dest_path);
} else if src_path.is_file()
&& let Err(e) = fs::copy(&src_path, &dest_path)
{
eprintln!("Failed to copy {}: {}", src_path.display(), e);
}
)
})?;

for entry in fs::read_dir(src)
.map_err(|e| format!("Failed to read source folder {}: {}", src.display(), e))?
{
let entry = entry.map_err(|e| e.to_string())?;

let src_path = entry.path();
let dest_path = dest.join(entry.file_name());

let metadata = fs::symlink_metadata(&src_path)
.map_err(|e| format!("Failed to read metadata for {}: {}", src_path.display(), e))?;

// SYMLINK
if metadata.file_type().is_symlink() {
let target = fs::read_link(&src_path)
.map_err(|e| format!("Failed to read symlink {}: {}", src_path.display(), e))?;

fs_sym::symlink(&target, &dest_path)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

you seem to have lost #[cfg(unix)]?

.map_err(|e| format!("Failed to create symlink {}: {}", dest_path.display(), e))?;
} else if metadata.is_dir() {
copy_directory(&src_path, &dest_path)?;
} else if metadata.is_file() {
fs::copy(&src_path, &dest_path).map_err(|e| {
format!(
"Failed to copy file {} -> {}: {}",
src_path.display(),
dest_path.display(),
e
)
})?;
}
} else {
eprintln!("Failed to read source folder: {}", src.display());
}

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use ::serial_test::serial;
use serial_test::serial;
struct TestCleanup {
directory: String,
}
Expand Down
Loading