Skip to content

Commit

Permalink
[suiop][pulumi] update command (#20731)
Browse files Browse the repository at this point in the history
## Description 

Command to trigger a dependency update for all pulumi programs in the
given directory. This will make codemod updates for things like PRO-194
much easier

## Test plan 

```
cargo run -- p u ~/mysten/sui-operations/pulumi/apps              
   Compiling suiop-cli v1.40.0 (/Users/jkjensen/mysten/sui/crates/suiop-cli)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.20s
     Running `/Users/jkjensen/mysten/sui/target/debug/suiop p u /Users/jkjensen/mysten/sui-operations/pulumi/apps`
2024-12-24T17:23:14.098064Z  INFO suioplib::cli::pulumi::deps: Processing subdirectory: /Users/jkjensen/mysten/sui-operations/pulumi/apps/suifrens
2024-12-24T17:23:14.098584Z  INFO suioplib::cli::pulumi::deps: Updating dependencies for python
2024-12-24T17:23:14.098591Z  INFO suioplib::cli::pulumi::deps: Updating dependencies for python project at /Users/jkjensen/mysten/sui-operations/pulumi/apps/suifrens
2024-12-24T17:23:15.316311Z  INFO suioplib::cli::pulumi::deps: Processing subdirectory: /Users/jkjensen/mysten/sui-operations/pulumi/apps/suifrens/__pycache__

...

2024-12-24T17:26:08.786248Z  INFO suioplib::cli::pulumi::deps: Successfully updated dependencies
```

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] gRPC:
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
  • Loading branch information
after-ephemera authored Dec 27, 2024
1 parent aa99382 commit 4aa53d7
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 36 deletions.
68 changes: 33 additions & 35 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/suiop-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ thiserror.workspace = true
strsim = "0.11.1"
futures-timer = "3.0.3"
tempfile.workspace = true
kube = { version = "0.96.0", features = ["client"] }
kube = { version = "0.97.0", features = ["client"] }
k8s-openapi = { version = "0.23.0", features = ["latest"] }


Expand Down
124 changes: 124 additions & 0 deletions crates/suiop-cli/src/cli/pulumi/deps.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::{command::CommandOptions, run_cmd};
use anyhow::Result;
use serde_yaml::Value;
use std::fs;
use std::path::{Path, PathBuf};
use tracing::{debug, info};

fn update_dependencies(path: &Path, runtime: &str) -> Result<()> {
info!(
"Updating dependencies for {} project at {}",
runtime,
path.display()
);

let mut cmd_opts = CommandOptions::new(false, false);
cmd_opts.current_dir = Some(path.to_path_buf());
let output = match runtime {
"go" => run_cmd(vec!["go", "get", "-u"], Some(cmd_opts.clone()))
.and_then(|_o| run_cmd(vec!["go", "mod", "tidy"], Some(cmd_opts))),
"python" => run_cmd(vec!["poetry", "update"], Some(cmd_opts)),
"typescript" => run_cmd(vec!["pnpm", "update"], Some(cmd_opts)),
_ => unreachable!(),
}?;
debug!(
"Command output: {:?}",
String::from_utf8_lossy(&output.stdout)
);
if !output.stderr.is_empty() {
debug!(
"Command stderr: {:?}",
String::from_utf8_lossy(&output.stderr)
);
}

Ok(())
}

fn process_directory(
dir_path: &Path,
runtime_filter: &Option<String>,
) -> Result<Vec<(PathBuf, anyhow::Error)>> {
let mut errors = Vec::new();

let pulumi_yaml = dir_path.join("Pulumi.yaml");

if pulumi_yaml.exists() {
let update_result = (|| -> Result<()> {
let contents = fs::read_to_string(&pulumi_yaml)?;
let yaml: Value = serde_yaml::from_str(&contents)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;

let runtime = yaml["runtime"]
.as_str()
.or_else(|| yaml["runtime"]["name"].as_str())
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"No runtime field found in Pulumi.yaml",
)
})?;

let runtime = runtime.to_lowercase();
if !["typescript", "go", "python"].contains(&runtime.as_str()) {
return Ok(());
}

if runtime_filter.as_ref().map_or(true, |f| f == &runtime) {
info!("Updating dependencies for {}", runtime);
update_dependencies(dir_path, &runtime)?;
}
Ok(())
})();

if let Err(e) = update_result {
errors.push((dir_path.to_path_buf(), e));
}
}

// Recurse into subdirectories
for entry in fs::read_dir(dir_path)? {
let entry = entry?;
let path = entry.path();
let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if path.is_dir()
&& !file_name.starts_with('.')
&& !file_name.contains("common")
&& !file_name.contains("node_modules")
{
info!("Processing subdirectory: {}", path.display());
match process_directory(&path, runtime_filter) {
Ok(mut sub_errors) => errors.append(&mut sub_errors),
Err(e) => errors.push((path, e)),
}
}
}
Ok(errors)
}

pub fn update_deps_cmd(filepath: PathBuf, runtime: Option<String>) -> Result<()> {
if !filepath.exists() || !filepath.is_dir() {
return Err(anyhow::anyhow!(
"Specified path does not exist or is not a directory",
));
}

let errors = process_directory(&filepath, &runtime)?;
if !errors.is_empty() {
let error_messages = errors
.into_iter()
.map(|(path, error)| format!("- {}: {}", path.display(), error))
.collect::<Vec<_>>()
.join("\n");
Err(anyhow::anyhow!(
"Failed to update dependencies in the following directories:\n{}",
error_messages
))
} else {
info!("Successfully updated dependencies");
Ok(())
}
}
Loading

0 comments on commit 4aa53d7

Please sign in to comment.