Repository: shuttle-hq/shuttle (Rust)

Issue:
Add resource to get Shuttle service info
It's not ideal, but there may be some cases where the app will need to know some information about the environment it's running on, most notably the public URL. I'm currently using a [custom resource](https://github.com/sd2k/recipes/blob/eeb0e512906742baf215dffb5a31587de90b8f74/crates/recipe-shuttle/src/service_info.rs) to get the service name and creating a URL out of that, but it might be nice to provide a first-class Resource for the same thing. Happy to send a (more generalized) PR if you think it'd be useful!


Discussion: Hey @sd2k, apologies for the delayed reply. Is this something you're still interested in working on? We've had another request for this now, and your proposed solution seems interesting!
Hey @oddgrd, yeah I can fire off a PR. I originally wrote it against Shuttle 0.8.0 or something and there's been some big changes to custom resources since then so I'm not 100% confident in it, but that's what reviews are for, right? 😂 
Great, happy to hear that! 🥳 Yeah, it's changed quite a bit since then, so feel free to open a PR or a draft PR with any questions you may have. It may be useful to look at PRs for other resources that have been added more recently, like the Turso resource.



Fix (patch):
diff --git a/.circleci/config.yml b/.circleci/config.yml
index a0c6300023..2f5f149c7f 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -97,6 +97,7 @@ commands:
             shuttle-shared-db = { path = "$PWD/resources/shared-db" }
             shuttle-secrets = { path = "$PWD/resources/secrets" }
             shuttle-static-folder = { path = "$PWD/resources/static-folder" }
+            shuttle-service-info = { path = "$PWD/resources/service-info" }
             shuttle-turso = { path = "$PWD/resources/turso" }
 
             shuttle-axum = { path = "$PWD/services/shuttle-axum" }
@@ -617,6 +618,7 @@ workflows:
                 - resources/persist
                 - resources/secrets
                 - resources/static-folder
+                - resources/service-info
                 - resources/turso
                 - services/shuttle-actix-web
                 - services/shuttle-axum
@@ -831,6 +833,7 @@ workflows:
                   "resources/secrets",
                   "resources/shared-db",
                   "resources/static-folder",
+                  "resources/service-info",
                   "resources/turso",
                 ]
           name: publish-<< matrix.path >>
diff --git a/DEVELOPING.md b/DEVELOPING.md
index 8e16c5264c..2fcb7d2922 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -136,6 +136,7 @@ shuttle-persist = { path = "[base]/shuttle/resources/persist" }
 shuttle-shared-db = { path = "[base]/shuttle/resources/shared-db" }
 shuttle-secrets = { path = "[base]/shuttle/resources/secrets" }
 shuttle-static-folder = { path = "[base]/shuttle/resources/static-folder" }
+shuttle-service-info = { path = "[base]/shuttle/resources/service-info" }
 shuttle-turso = { path = "[base]/shuttle/resources/turso" }
 
 shuttle-axum = { path = "[base]/shuttle/services/shuttle-axum" }
diff --git a/Makefile b/Makefile
index 5fd0ee5312..0f780b9d8f 100644
--- a/Makefile
+++ b/Makefile
@@ -255,8 +255,9 @@ publish: publish-resources publish-cargo-shuttle
 
 publish-resources: publish-resources/aws-rds \
 	publish-resources/persist \
-	publish-resources/shared-db
-	publish-resources/static-folder
+	publish-resources/shared-db \
+	publish-resources/static-folder \
+	publish-resources/service-info
 
 publish-cargo-shuttle: publish-resources/secrets
 	cd cargo-shuttle; cargo publish
diff --git a/common/src/models/resource.rs b/common/src/models/resource.rs
index a1728349a7..bdde9e5726 100644
--- a/common/src/models/resource.rs
+++ b/common/src/models/resource.rs
@@ -22,6 +22,7 @@ pub fn get_resources_table(resources: &Vec<Response>, service_name: &str) -> Str
                 Type::StaticFolder => "Static Folder",
                 Type::Persist => "Persist",
                 Type::Turso => "Turso",
+                Type::ServiceInfo => "Service Info",
                 Type::Custom => "Custom",
             };
 
diff --git a/common/src/resource.rs b/common/src/resource.rs
index 3c29591fe2..ea24d7d2ce 100644
--- a/common/src/resource.rs
+++ b/common/src/resource.rs
@@ -36,6 +36,7 @@ pub enum Type {
     StaticFolder,
     Persist,
     Turso,
+    ServiceInfo,
     Custom,
 }
 
@@ -82,6 +83,7 @@ impl Display for Type {
             Type::StaticFolder => write!(f, "static_folder"),
             Type::Persist => write!(f, "persist"),
             Type::Turso => write!(f, "turso"),
+            Type::ServiceInfo => write!(f, "service_info"),
             Type::Custom => write!(f, "custom"),
         }
     }
diff --git a/deployer/prepare.sh b/deployer/prepare.sh
index 1fcd647fe8..8f7c0d6a2a 100755
--- a/deployer/prepare.sh
+++ b/deployer/prepare.sh
@@ -41,6 +41,7 @@ if [[ $PROD != "true" ]]; then
     shuttle-shared-db = { path = "/usr/src/shuttle/resources/shared-db" }
     shuttle-secrets = { path = "/usr/src/shuttle/resources/secrets" }
     shuttle-static-folder = { path = "/usr/src/shuttle/resources/static-folder" }
+    shuttle-service-info = { path = "/usr/src/shuttle/resources/service-info" }
     shuttle-turso = { path = "/usr/src/shuttle/resources/turso" }
 
     shuttle-actix-web = { path = "/usr/src/shuttle/services/shuttle-actix-web" }
diff --git a/deployer/src/persistence/resource/mod.rs b/deployer/src/persistence/resource/mod.rs
index f00f8eb7d8..d9a612b669 100644
--- a/deployer/src/persistence/resource/mod.rs
+++ b/deployer/src/persistence/resource/mod.rs
@@ -66,6 +66,7 @@ pub enum Type {
     StaticFolder,
     Persist,
     Turso,
+    ServiceInfo,
     Custom,
 }
 
@@ -77,6 +78,7 @@ impl From<Type> for shuttle_common::resource::Type {
             Type::StaticFolder => Self::StaticFolder,
             Type::Persist => Self::Persist,
             Type::Turso => Self::Turso,
+            Type::ServiceInfo => Self::ServiceInfo,
             Type::Custom => Self::Custom,
         }
     }
@@ -90,6 +92,7 @@ impl From<shuttle_common::resource::Type> for Type {
             shuttle_common::resource::Type::StaticFolder => Self::StaticFolder,
             shuttle_common::resource::Type::Persist => Self::Persist,
             shuttle_common::resource::Type::Turso => Self::Turso,
+            shuttle_common::resource::Type::ServiceInfo => Self::ServiceInfo,
             shuttle_common::resource::Type::Custom => Self::Custom,
         }
     }
@@ -103,6 +106,7 @@ impl Display for Type {
             Type::StaticFolder => write!(f, "static_folder"),
             Type::Persist => write!(f, "persist"),
             Type::Turso => write!(f, "turso"),
+            Type::ServiceInfo => write!(f, "service_info"),
             Type::Custom => write!(f, "custom"),
         }
     }
@@ -123,6 +127,7 @@ impl FromStr for Type {
                 "static_folder" => Ok(Self::StaticFolder),
                 "persist" => Ok(Self::Persist),
                 "turso" => Ok(Self::Turso),
+                "service_info" => Ok(Self::ServiceInfo),
                 "custom" => Ok(Self::Custom),
                 _ => Err(format!("'{s}' is an unknown resource type")),
             }
@@ -173,6 +178,7 @@ mod tests {
             Type::StaticFolder,
             Type::Persist,
             Type::Turso,
+            Type::ServiceInfo,
             Type::Custom,
         ];
 
diff --git a/resources/service-info/Cargo.toml b/resources/service-info/Cargo.toml
new file mode 100644
index 0000000000..22266498b8
--- /dev/null
+++ b/resources/service-info/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "shuttle-service-info"
+version = "0.24.0"
+edition = "2021"
+license = "Apache-2.0"
+description = "Plugin to get Shuttle service information"
+keywords = ["shuttle-service", "service-info"]
+
+[dependencies]
+async-trait = "0.1.56"
+serde = { version = "1.0.0", features = ["derive"] }
+shuttle-service = { path = "../../service", version = "0.24.0" }
diff --git a/resources/service-info/README.md b/resources/service-info/README.md
new file mode 100644
index 0000000000..a286cb8a1a
--- /dev/null
+++ b/resources/service-info/README.md
@@ -0,0 +1,22 @@
+# Shuttle Service Info
+
+This plugin allows applications to obtain certain information about their runtime environment.
+
+## Usage
+
+Add `shuttle-service-info` to the dependencies for your service.
+
+You can get this resource using the `shuttle_service_info::ShuttleServiceInfo` attribute to get a `ServiceInfo`. This struct will contain information such as the Shuttle service name.
+
+```rust
+#[shuttle_runtime::main]
+async fn app(
+    #[shuttle_service_info::ShuttleServiceInfo] service_info: shuttle_service_info::ServiceInfo,
+) -> __ { ... }
+```
+
+#### Example projects that use `shuttle-service-info`
+
+| Framework | Link                                                                                       |
+| --------- | ------------------------------------------------------------------------------------------ |
+| Axum      | [axum example](https://github.com/shuttle-hq/shuttle-examples/tree/main/axum/service-info) |
diff --git a/resources/service-info/src/lib.rs b/resources/service-info/src/lib.rs
new file mode 100644
index 0000000000..711aed5f44
--- /dev/null
+++ b/resources/service-info/src/lib.rs
@@ -0,0 +1,45 @@
+use async_trait::async_trait;
+use serde::{Deserialize, Serialize};
+use shuttle_service::{error::Error, Factory, ResourceBuilder, Type};
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct ServiceInfo {
+    /// The Shuttle service name.
+    service_name: String,
+}
+
+impl ServiceInfo {
+    /// Get the Shuttle service name.
+    pub fn service_name(&self) -> &str {
+        &self.service_name
+    }
+}
+
+pub struct ShuttleServiceInfo;
+
+#[async_trait]
+impl ResourceBuilder<ServiceInfo> for ShuttleServiceInfo {
+    fn new() -> Self {
+        Self
+    }
+
+    const TYPE: Type = Type::ServiceInfo;
+
+    type Config = ();
+
+    type Output = ServiceInfo;
+
+    fn config(&self) -> &Self::Config {
+        &()
+    }
+
+    async fn output(self, factory: &mut dyn Factory) -> Result<Self::Output, Error> {
+        Ok(ServiceInfo {
+            service_name: factory.get_service_name().to_string(),
+        })
+    }
+
+    async fn build(build_data: &Self::Output) -> Result<ServiceInfo, Error> {
+        Ok(build_data.clone())
+    }
+}