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

feat: add list subcommand features to meta_cli #775

Merged
merged 9 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ source .venv/bin/activate # depends on your shell

```bash
ghjk x dev-compose all # or only the envs required (e.g. base prisma s3)
ghjk x build-tgraph # build typegraph
ghjk x test-e2e # all tests
ghjk x test-e2e runtimes/prisma/full_prisma_mapping_test.ts # isolated test
ghjk x # more test tasks are availaible
ghjk x dev-compsoe # shutdown all envs
ghjk x dev-compose # shutdown all envs
```

There are many more developer scripts in the `dev` folder, however most of them should only be needed for advanced tasks.
Expand Down
42 changes: 42 additions & 0 deletions Cargo.lock

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

9 changes: 7 additions & 2 deletions dev/tasks-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ const tasks: Record<string, DenoTaskDefArgs> = {
desc: "Print a command you can use to install system items",
fn: async ($) => {
$.logger.info("pipe me to a shell");
const uname = await $`uname -a`.text();
if (/(Ubuntu|Debian|Linux pop-os)/.test(uname)) {
const osRelease = await $`cat /etc/os-release`.text();
if (/(Ubuntu|Debian|Linux pop-os)/.test(osRelease)) {
console.log(
`sudo apt update && ` +
`sudo apt install -y --no-install-recommends ` +
`gcc-multilib pkg-config libssl-dev libclang-dev perl make`,
);
} else if (/Fedora|Red Hat|CentOS/.test(osRelease)) {
console.log(
`sudo dnf install -y ` +
`gcc gcc-c++ pkg-config openssl-devel clang-devel perl make`,
);
} else {
$.logger.error("unable to determine platform");
$.logGroup("install the following manually");
Expand Down
10 changes: 8 additions & 2 deletions libs/common/src/typegraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,18 @@ impl Typegraph {
}

pub fn get_key(&self) -> Result<String> {
let path = self.get_path()?;
Ok(format!("{}#{}", path, self.name()?))
}

pub fn get_path(&self) -> Result<String> {
let path = self
.path
.as_ref()
.ok_or_else(|| anyhow::anyhow!("typegraph path not set, cannot get id"))?
.to_str()
.ok_or_else(|| anyhow::anyhow!("typegraph path is not valid unicode"))?;
Ok(format!("{}#{}", path, self.name()?))
.ok_or_else(|| anyhow::anyhow!("typegraph path is not valid unicode"))?
.to_owned();
Ok(path)
}
}
2 changes: 2 additions & 0 deletions meta-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ default = []
typegate = ["dep:typegate_engine"]

[dependencies]
tabled = "0.15.0"

# internal
typegate_engine = { workspace = true, optional = true }
common.workspace = true
Expand Down
212 changes: 212 additions & 0 deletions meta-cli/src/cli/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0.
// SPDX-License-Identifier: MPL-2.0

use super::{Action, ConfigArgs, NodeArgs};
use crate::config::{Config, PathOption};
use crate::deploy::actors::console::ConsoleActor;
use crate::deploy::actors::task::list::{ListAction, ListActionGenerator};
use crate::deploy::actors::task::TaskFinishStatus;
use crate::deploy::actors::task_manager::{Report, StopReason, TaskManagerInit, TaskSource};
use crate::interlude::*;
use anyhow::Result;
use async_trait::async_trait;
use clap::Parser;
use common::graphql::Query;
use common::node::Node;
use serde::Deserialize;
use tabled::{settings::Style, Table, Tabled};

#[derive(Parser, Debug)]
pub struct List {
#[command(flatten)]
node: NodeArgs,
/// Target typegate (cf config)
#[clap(short, long)]
pub target: String,

#[clap(long)]
max_parallel_loads: Option<usize>,
}

#[async_trait]
impl Action for List {
#[tracing::instrument]
async fn run(&self, args: ConfigArgs) -> Result<()> {
let dir = args.dir()?;
let config_path = args.config.clone();
let config = Arc::new(Config::load_or_find(config_path, &dir)?);

let mut node_config = config.node(&self.node, &self.target);
if self.target == "dev" {
node_config
.url
.set_port(Some(7891))
.map_err(|_| anyhow::anyhow!("cannot base"))?;
}
let node = node_config.build(dir.clone()).await?;

let task_source = TaskSource::Discovery(dir.clone().into());

let console = ConsoleActor::new(Arc::clone(&config)).start();

let action_generator = ListActionGenerator::new(
config.dir().unwrap_or_log().into(),
dir.clone().into(),
config
.prisma_migrations_base_dir(PathOption::Absolute)
.into(),
true,
);

let mut init = TaskManagerInit::<ListAction>::new(
config.clone(),
action_generator,
console,
task_source,
);

if let Some(max_parallel_tasks) = self.max_parallel_loads {
init = init.max_parallel_tasks(max_parallel_tasks);
}

let report = init.run().await;

match report.stop_reason {
StopReason::Error => bail!("failed"),
StopReason::Manual | StopReason::ManualForced => {
bail!("cancelled")
}
StopReason::Natural => {}
StopReason::Restart => panic!("restart not supported for list"),
};

let typegraphs = report.into_typegraph_info()?;
self.display_typegraphs(typegraphs, node).await
}
}

#[derive(Debug, Clone, Deserialize)]
struct TypegraphInfo {
name: String,
url: Option<String>,
path: Option<String>,
}

impl List {
async fn fetch_typegraphs(&self, node: Node) -> Result<Vec<TypegraphInfo>> {
let query = r#"
query {
typegraphs {
name
url
}
}
"#;
let response = node
.post("/typegate")
.unwrap()
.gql(query.into(), None)
.await?;
response
.data("typegraphs")
.map_err(|err| anyhow::anyhow!(err))
}

async fn display_typegraphs(
&self,
local_typegraphs: Vec<TypegraphInfo>,
node: Node,
) -> Result<()> {
let mut typegraph_entries: Vec<TypegraphEntry> = local_typegraphs
.into_iter()
.map(|tg| TypegraphEntry {
name: tg.name,
path: tg.path.unwrap_or_else(|| "-".to_string()),
url: tg.url.unwrap_or_else(|| "-".to_string()),
target: "-".to_string(),
})
.collect();

match self.fetch_typegraphs(node).await {
Ok(fetched_typegraphs) => {
for fetched_tg in fetched_typegraphs {
if let Some(existing_entry) = typegraph_entries
.iter_mut()
.find(|t| t.name == fetched_tg.name)
{
existing_entry
.update_info(fetched_tg.url.unwrap_or_default(), self.target.clone());
} else {
typegraph_entries.push(TypegraphEntry {
name: fetched_tg.name,
path: "-".to_string(),
url: fetched_tg.url.unwrap_or_default(),
target: self.target.clone(),
});
}
}
}
Err(err) => eprintln!("Error fetching {} typegraphs: {}", self.target, err),
}

let mut table = Table::new(typegraph_entries);
table.with(Style::blank());
println!("{table}");
Ok(())
}
}

#[derive(Debug, Clone, Tabled)]
struct TypegraphEntry {
#[tabled(rename = "NAME")]
name: String,
#[tabled(rename = "PATH")]
path: String,
#[tabled(rename = "URL")]
url: String,
#[tabled(rename = "TARGET")]
target: String,
}

impl TypegraphEntry {
fn update_info(&mut self, url: String, target: String) {
self.url = url;
self.target = target;
}
}

trait ListTypegraphInfo {
fn into_typegraph_info(self) -> Result<Vec<TypegraphInfo>>;
}

impl ListTypegraphInfo for Report<ListAction> {
fn into_typegraph_info(self) -> Result<Vec<TypegraphInfo>> {
let mut typegraphs = vec![];

for entry in self.entries.into_iter() {
match entry.status {
TaskFinishStatus::Finished(results) => {
for (_, info) in results.into_iter() {
let info = info.unwrap();
let path = String::from(entry.path.to_str().unwrap());
typegraphs.push(TypegraphInfo {
name: info.typegraph,
url: None,
path: Some(path),
});
}
}
TaskFinishStatus::Cancelled => {
tracing::error!("serialization cancelled for {:?}", entry.path);
return Err(ferr!("cancelled"));
}
TaskFinishStatus::Error => {
tracing::error!("serialization failed for {:?}", entry.path);
return Err(ferr!("failed"));
}
}
}

Ok(typegraphs)
}
}
3 changes: 3 additions & 0 deletions meta-cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) mod deploy;
pub(crate) mod dev;
pub(crate) mod doctor;
pub(crate) mod gen;
pub(crate) mod list;
pub(crate) mod new;
pub(crate) mod serialize;
pub(crate) mod typegate;
Expand Down Expand Up @@ -74,6 +75,8 @@ pub(crate) enum Commands {
Deploy(deploy::DeploySubcommand),
/// Undeploy typegraph(s) from typegate
Undeploy(undeploy::Undeploy),
/// List typegraph(s) from typegate
List(list::List),
/// Access metagen generators
Gen(gen::Gen),
/// Upgrade
Expand Down
1 change: 1 addition & 0 deletions meta-cli/src/deploy/actors/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
pub mod action;
mod command;
pub mod deploy;
pub mod list;
pub mod serialize;

use self::action::{ActionFinalizeContext, ActionResult, TaskAction};
Expand Down
Loading
Loading