From 2f22965396d60d40cbadc631720ff8d95ebfc2e1 Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Fri, 14 Jun 2024 12:16:19 +0200
Subject: [PATCH] feat(#195): `print-spec` and `release-network` commands

---
 Cargo.toml                                    |  1 +
 xtask/res/create_network_release.gql          | 16 ++++
 xtask/res/get_release.gql                     |  4 +-
 xtask/src/main.rs                             | 23 ++++--
 xtask/src/release_runtime.rs                  | 71 ++++++++++++++---
 .../release_runtime/create_network_release.rs | 76 +++++++++++++++++++
 xtask/src/release_runtime/get_release.rs      |  6 +-
 7 files changed, 172 insertions(+), 25 deletions(-)
 create mode 100644 xtask/res/create_network_release.gql
 create mode 100644 xtask/src/release_runtime/create_network_release.rs

diff --git a/Cargo.toml b/Cargo.toml
index 4951122c6..fb533ca6d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -57,6 +57,7 @@ substrate-build-script-utils = { git = 'https://github.com/duniter/duniter-polka
 
 [dev-dependencies]
 rusty-hook = "^0.11.2"
+reqwest = { version = "0.12.0", default-features = false, features = ["rustls-tls"] }
 
 # Dependencies for specific targets
 [target.'cfg(any(target_arch="x86_64", target_arch="aarch64"))'.dependencies]
diff --git a/xtask/res/create_network_release.gql b/xtask/res/create_network_release.gql
new file mode 100644
index 000000000..694afccf1
--- /dev/null
+++ b/xtask/res/create_network_release.gql
@@ -0,0 +1,16 @@
+mutation CreateReleaseMutation($branch: String!, $description: String!, $network: String! $links: [ReleaseAssetLinkInput!]!) {
+  releaseCreate(input: {
+    clientMutationId: "duniter-v2s-xtask"
+    description: $description
+    milestones: []
+    name: $network
+    projectPath: "nodes/rust/duniter-v2s"
+    ref: $branch
+    tagName: $network
+    assets: {
+      links: $links
+    }
+  }) {
+    errors
+  }
+}
diff --git a/xtask/res/get_release.gql b/xtask/res/get_release.gql
index 9ca58b82c..7fb7ef667 100644
--- a/xtask/res/get_release.gql
+++ b/xtask/res/get_release.gql
@@ -1,6 +1,6 @@
-query GetReleaseOfProjectQuery($milestone: String!) {
+query GetReleaseOfProjectQuery($tag: String!) {
     project(fullPath: "nodes/rust/duniter-v2s") {
-        release(tagName: $milestone) {
+        release(tagName: $tag) {
             id
             tagName
             assets {
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index c6b201b9d..7e4c82382 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -51,10 +51,12 @@ enum DuniterXTaskCommand {
         /// Raw spec filepath
         raw_spec: PathBuf,
     },
+    /// Release a new network
+    ReleaseNetwork { network: String, branch: String },
     /// Release a new runtime
     ReleaseRuntime { milestone: String, branch: String },
-    /// Update raw specs locally with the files published on a Release
-    UpdateRawSpecs { milestone: String },
+    /// Print the chainSpec published on given Network Release
+    PrintSpec { network: String },
     /// Create asset in a release
     CreateAssetLink {
         tag: String,
@@ -79,8 +81,14 @@ async fn main() -> Result<()> {
             );
         std::process::exit(1);
     }
-    Command::new("rustc").arg("--version").status()?;
-    Command::new("cargo").arg("--version").status()?;
+
+    match &args.command {
+        DuniterXTaskCommand::PrintSpec { .. } => { /* no print */ }
+        _ => {
+            Command::new("rustc").arg("--version").status()?;
+            Command::new("cargo").arg("--version").status()?;
+        }
+    }
 
     match args.command {
         DuniterXTaskCommand::Build { production } => build(production),
@@ -88,12 +96,13 @@ async fn main() -> Result<()> {
         DuniterXTaskCommand::InjectRuntimeCode { runtime, raw_spec } => {
             inject_runtime_code(&raw_spec, &runtime)
         }
+        DuniterXTaskCommand::ReleaseNetwork { network, branch } => {
+            release_runtime::release_network(network, branch).await
+        }
         DuniterXTaskCommand::ReleaseRuntime { milestone, branch } => {
             release_runtime::release_runtime(milestone, branch).await
         }
-        DuniterXTaskCommand::UpdateRawSpecs { milestone } => {
-            release_runtime::update_raw_specs(milestone).await
-        }
+        DuniterXTaskCommand::PrintSpec { network } => release_runtime::print_spec(network).await,
         DuniterXTaskCommand::CreateAssetLink {
             tag,
             asset_name,
diff --git a/xtask/src/release_runtime.rs b/xtask/src/release_runtime.rs
index 53bff4a97..c39f4a441 100644
--- a/xtask/src/release_runtime.rs
+++ b/xtask/src/release_runtime.rs
@@ -15,6 +15,7 @@
 // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
 
 mod create_asset_link;
+mod create_network_release;
 mod create_release;
 mod get_changes;
 mod get_issues;
@@ -61,6 +62,50 @@ struct CoreVersion {
     //transaction_version: u32,
 }
 
+pub(super) async fn release_network(network: String, branch: String) -> Result<()> {
+    let mut release_notes = String::from(
+        "
+# Runtime
+
+",
+    );
+
+    // Generate release notes
+    let currency = network.clone();
+    let env_var = "SRTOOL_OUTPUT".to_string();
+
+    if let Ok(sr_tool_output_file) = std::env::var(env_var) {
+        let read = fs::read_to_string(sr_tool_output_file);
+        match read {
+            Ok(sr_tool_output) => {
+                release_notes.push_str(
+                    gen_release_notes(currency.to_string(), sr_tool_output)
+                        .with_context(|| {
+                            format!("Fail to generate release notes for {}", currency)
+                        })?
+                        .as_str(),
+                );
+            }
+            Err(e) => {
+                eprintln!("srtool JSON output could not be read ({}). Skipped.", e)
+            }
+        }
+    }
+
+    println!("{}", release_notes);
+    let gitlab_token =
+        std::env::var("GITLAB_TOKEN").with_context(|| "missing env var GITLAB_TOKEN")?;
+    create_network_release::create_network_release(
+        gitlab_token,
+        branch,
+        network,
+        release_notes.to_string(),
+    )
+    .await?;
+
+    Ok(())
+}
+
 pub(super) async fn release_runtime(milestone: String, branch: String) -> Result<()> {
     // TODO: check spec_version in the code and bump if necessary (with a commit)
     // TODO: create and push a git tag runtime-{spec_version}
@@ -134,20 +179,22 @@ pub(super) async fn release_runtime(milestone: String, branch: String) -> Result
     Ok(())
 }
 
-pub(super) async fn update_raw_specs(milestone: String) -> Result<()> {
-    let specs = vec!["gdev-raw.json", "gtest-raw.json", "g1-raw.json"];
-    println!("Fetching release info…");
-    let assets = get_release::get_release(milestone).await?;
-    for spec in specs {
-        if let Some(gdev_raw_specs) = assets.iter().find(|asset| asset.ends_with(spec)) {
-            println!("Downloading {}…", spec);
-            let client = reqwest::Client::new();
-            let res = client.get(gdev_raw_specs).send().await?;
-            let write_to = format!("./node/specs/{}", spec);
-            fs::write(write_to, res.bytes().await?)?;
+pub(super) async fn print_spec(network: String) -> Result<()> {
+    let spec_file = match network.clone() {
+        network if network.starts_with("g1") => "g1.json",
+        network if network.starts_with("gtest") => "gtest.json",
+        network if network.starts_with("gdev") => "gdev.json",
+        _ => {
+            return Err(anyhow!("Invalid network"));
         }
+    };
+    let assets = get_release::get_release(network).await?;
+    if let Some(gdev_spec) = assets.iter().find(|asset| asset.ends_with(spec_file)) {
+        let client = reqwest::Client::new();
+        let res = client.get(gdev_spec).send().await?;
+        let spec = String::from_utf8(res.bytes().await?.to_vec())?;
+        println!("{}", spec);
     }
-    println!("Done.");
     Ok(())
 }
 
diff --git a/xtask/src/release_runtime/create_network_release.rs b/xtask/src/release_runtime/create_network_release.rs
new file mode 100644
index 000000000..6736a94ca
--- /dev/null
+++ b/xtask/src/release_runtime/create_network_release.rs
@@ -0,0 +1,76 @@
+// Copyright 2021 Axiom-Team
+//
+// This file is part of Duniter-v2S.
+//
+// Duniter-v2S is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, version 3 of the License.
+//
+// Duniter-v2S is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
+
+use anyhow::{anyhow, Result};
+use graphql_client::{GraphQLQuery, Response};
+
+#[derive(GraphQLQuery)]
+#[graphql(
+    schema_path = "res/schema.gql",
+    query_path = "res/create_network_release.gql",
+    response_derives = "Debug"
+)]
+pub struct CreateReleaseMutation;
+
+pub(super) async fn create_network_release(
+    gitlab_token: String,
+    branch: String,
+    network: String,
+    release_notes: String,
+) -> Result<()> {
+    // this is the important line
+    let request_body = CreateReleaseMutation::build_query(create_release_mutation::Variables {
+        branch,
+        description: release_notes,
+        network,
+        links: vec![],
+    });
+
+    let client = reqwest::Client::new();
+    let res = client
+        .post("https://git.duniter.org/api/graphql")
+        .header("PRIVATE-TOKEN", gitlab_token)
+        .json(&request_body)
+        .send()
+        .await?;
+    let response_body: Response<create_release_mutation::ResponseData> = res.json().await?;
+
+    if let Some(data) = response_body.data {
+        if let Some(release_create) = data.release_create {
+            if release_create.errors.is_empty() {
+                Ok(())
+            } else {
+                println!("{} errors:", release_create.errors.len());
+                for error in release_create.errors {
+                    println!("{}", error);
+                }
+                Err(anyhow!("Logic errors"))
+            }
+        } else if let Some(errors) = response_body.errors {
+            Err(anyhow!("Errors: {:?}", errors))
+        } else {
+            Err(anyhow!("Invalid response: no release_create"))
+        }
+    } else if let Some(errors) = response_body.errors {
+        println!("{} errors:", errors.len());
+        for error in errors {
+            println!("{}", error);
+        }
+        Err(anyhow!("GraphQL errors"))
+    } else {
+        Err(anyhow!("Invalid response: no data nor errors"))
+    }
+}
diff --git a/xtask/src/release_runtime/get_release.rs b/xtask/src/release_runtime/get_release.rs
index cec00aba9..238a3504d 100644
--- a/xtask/src/release_runtime/get_release.rs
+++ b/xtask/src/release_runtime/get_release.rs
@@ -25,12 +25,10 @@ use graphql_client::{GraphQLQuery, Response};
 )]
 pub struct GetReleaseOfProjectQuery;
 
-pub(super) async fn get_release(milestone: String) -> Result<Vec<String>> {
+pub(super) async fn get_release(tag: String) -> Result<Vec<String>> {
     // this is the important line
     let request_body =
-        GetReleaseOfProjectQuery::build_query(get_release_of_project_query::Variables {
-            milestone,
-        });
+        GetReleaseOfProjectQuery::build_query(get_release_of_project_query::Variables { tag });
 
     let client = reqwest::Client::new();
     let res = client
-- 
GitLab