Compare commits
1 Commits
main
...
renovate/c
Author | SHA1 | Date | |
---|---|---|---|
91305d0ef6 |
812
Cargo.lock
generated
812
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
|||||||
# Networking
|
|
||||||
|
|
||||||
We've got multiple hosts potentially in play, and multiple containers that wants
|
|
||||||
to expose themselves on the primary host, but only 1 process can take a tcp port
|
|
||||||
on the same host at a time.
|
|
||||||
|
|
||||||
1. The primary instance, will bind to common tcp ports as required (80, 443) can
|
|
||||||
be changed.
|
|
||||||
1. TODO: TLS termination
|
|
||||||
1. Will send traffic to peers hosting their versions of a url.
|
|
||||||
1. On the peers, each container will bind to a port (TBD), maybe we will do some
|
|
||||||
long running connections instead. SOCK protocol
|
|
@@ -1,9 +1,5 @@
|
|||||||
# Norun
|
# Norun
|
||||||
|
|
||||||
## Server
|
|
||||||
|
|
||||||
Many servers
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
norun subscribe apps
|
norun subscribe apps
|
||||||
```
|
```
|
||||||
@@ -22,7 +18,6 @@ name = "hello-world"
|
|||||||
[container]
|
[container]
|
||||||
image = "kasperhermansen/hello-world"
|
image = "kasperhermansen/hello-world"
|
||||||
version = "latest" # default
|
version = "latest" # default
|
||||||
replicas = 3
|
|
||||||
|
|
||||||
[expose]
|
[expose]
|
||||||
port = 8080
|
port = 8080
|
||||||
|
@@ -8,24 +8,7 @@ pub struct PublishRequest {
|
|||||||
}
|
}
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
#[derive(Clone, Copy, PartialEq, ::prost::Message)]
|
||||||
pub struct PublishResponse {}
|
pub struct PublishResponse {
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct GetTopicRequest {
|
|
||||||
#[prost(string, tag = "1")]
|
|
||||||
pub topic: ::prost::alloc::string::String,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct GetTopicResponse {
|
|
||||||
#[prost(message, optional, tag = "1")]
|
|
||||||
pub projects: ::core::option::Option<Projects>,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct Projects {
|
|
||||||
#[prost(message, repeated, tag = "1")]
|
|
||||||
pub projects: ::prost::alloc::vec::Vec<Project>,
|
|
||||||
}
|
}
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
@@ -35,37 +18,9 @@ pub struct Project {
|
|||||||
#[prost(string, tag="2")]
|
#[prost(string, tag="2")]
|
||||||
pub image: ::prost::alloc::string::String,
|
pub image: ::prost::alloc::string::String,
|
||||||
#[prost(string, tag="3")]
|
#[prost(string, tag="3")]
|
||||||
#[prost(uint32, optional, tag="2")]
|
|
||||||
pub port: ::core::option::Option<u32>,
|
|
||||||
#[prost(oneof="project::ProjectType", tags="3, 4")]
|
|
||||||
pub project_type: ::core::option::Option<project::ProjectType>,
|
|
||||||
}
|
|
||||||
/// Nested message and enum types in `Project`.
|
|
||||||
pub mod project {
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Oneof)]
|
|
||||||
pub enum ProjectType {
|
|
||||||
#[prost(message, tag="3")]
|
|
||||||
Container(super::Container),
|
|
||||||
#[prost(message, tag="4")]
|
|
||||||
Compose(super::Compose),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct Container {
|
|
||||||
#[prost(string, tag="1")]
|
|
||||||
pub image: ::prost::alloc::string::String,
|
|
||||||
#[prost(string, tag="2")]
|
|
||||||
pub version: ::prost::alloc::string::String,
|
pub version: ::prost::alloc::string::String,
|
||||||
#[prost(uint32, optional, tag="4")]
|
#[prost(uint32, optional, tag="4")]
|
||||||
pub port: ::core::option::Option<u32>,
|
pub port: ::core::option::Option<u32>,
|
||||||
}
|
}
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct Compose {
|
|
||||||
#[prost(map="string, bytes", tag="1")]
|
|
||||||
pub files: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::vec::Vec<u8>>,
|
|
||||||
}
|
|
||||||
include!("norun.v1.tonic.rs");
|
include!("norun.v1.tonic.rs");
|
||||||
// @@protoc_insertion_point(module)
|
// @@protoc_insertion_point(module)
|
@@ -109,31 +109,6 @@ pub mod registry_service_client {
|
|||||||
.insert(GrpcMethod::new("norun.v1.RegistryService", "Publish"));
|
.insert(GrpcMethod::new("norun.v1.RegistryService", "Publish"));
|
||||||
self.inner.unary(req, path, codec).await
|
self.inner.unary(req, path, codec).await
|
||||||
}
|
}
|
||||||
pub async fn get_topic(
|
|
||||||
&mut self,
|
|
||||||
request: impl tonic::IntoRequest<super::GetTopicRequest>,
|
|
||||||
) -> std::result::Result<
|
|
||||||
tonic::Response<super::GetTopicResponse>,
|
|
||||||
tonic::Status,
|
|
||||||
> {
|
|
||||||
self.inner
|
|
||||||
.ready()
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
tonic::Status::new(
|
|
||||||
tonic::Code::Unknown,
|
|
||||||
format!("Service was not ready: {}", e.into()),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
|
||||||
let path = http::uri::PathAndQuery::from_static(
|
|
||||||
"/norun.v1.RegistryService/GetTopic",
|
|
||||||
);
|
|
||||||
let mut req = request.into_request();
|
|
||||||
req.extensions_mut()
|
|
||||||
.insert(GrpcMethod::new("norun.v1.RegistryService", "GetTopic"));
|
|
||||||
self.inner.unary(req, path, codec).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Generated server implementations.
|
/// Generated server implementations.
|
||||||
@@ -147,13 +122,6 @@ pub mod registry_service_server {
|
|||||||
&self,
|
&self,
|
||||||
request: tonic::Request<super::PublishRequest>,
|
request: tonic::Request<super::PublishRequest>,
|
||||||
) -> std::result::Result<tonic::Response<super::PublishResponse>, tonic::Status>;
|
) -> std::result::Result<tonic::Response<super::PublishResponse>, tonic::Status>;
|
||||||
async fn get_topic(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<super::GetTopicRequest>,
|
|
||||||
) -> std::result::Result<
|
|
||||||
tonic::Response<super::GetTopicResponse>,
|
|
||||||
tonic::Status,
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RegistryServiceServer<T: RegistryService> {
|
pub struct RegistryServiceServer<T: RegistryService> {
|
||||||
@@ -280,52 +248,6 @@ pub mod registry_service_server {
|
|||||||
};
|
};
|
||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
"/norun.v1.RegistryService/GetTopic" => {
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
struct GetTopicSvc<T: RegistryService>(pub Arc<T>);
|
|
||||||
impl<
|
|
||||||
T: RegistryService,
|
|
||||||
> tonic::server::UnaryService<super::GetTopicRequest>
|
|
||||||
for GetTopicSvc<T> {
|
|
||||||
type Response = super::GetTopicResponse;
|
|
||||||
type Future = BoxFuture<
|
|
||||||
tonic::Response<Self::Response>,
|
|
||||||
tonic::Status,
|
|
||||||
>;
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
request: tonic::Request<super::GetTopicRequest>,
|
|
||||||
) -> Self::Future {
|
|
||||||
let inner = Arc::clone(&self.0);
|
|
||||||
let fut = async move {
|
|
||||||
<T as RegistryService>::get_topic(&inner, request).await
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let accept_compression_encodings = self.accept_compression_encodings;
|
|
||||||
let send_compression_encodings = self.send_compression_encodings;
|
|
||||||
let max_decoding_message_size = self.max_decoding_message_size;
|
|
||||||
let max_encoding_message_size = self.max_encoding_message_size;
|
|
||||||
let inner = self.inner.clone();
|
|
||||||
let fut = async move {
|
|
||||||
let inner = inner.0;
|
|
||||||
let method = GetTopicSvc(inner);
|
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec)
|
|
||||||
.apply_compression_config(
|
|
||||||
accept_compression_encodings,
|
|
||||||
send_compression_encodings,
|
|
||||||
)
|
|
||||||
.apply_max_message_size_config(
|
|
||||||
max_decoding_message_size,
|
|
||||||
max_encoding_message_size,
|
|
||||||
);
|
|
||||||
let res = grpc.unary(method, req).await;
|
|
||||||
Ok(res)
|
|
||||||
};
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
Ok(
|
Ok(
|
||||||
|
@@ -21,11 +21,6 @@ tonic = { workspace = true }
|
|||||||
tokio-util = "0.7.15"
|
tokio-util = "0.7.15"
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
notmad = "0.7.2"
|
notmad = "0.7.2"
|
||||||
bollard = "0.19.1"
|
|
||||||
futures-util = "0.3.31"
|
|
||||||
dirs = "6.0.0"
|
|
||||||
uuid = { version = "1.17.0", features = ["serde", "v4"] }
|
|
||||||
ron = "0.10.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
|
@@ -1,17 +1,12 @@
|
|||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{
|
cli::{publish::PublishCommand, serve::ServeCommand},
|
||||||
node::NodeCommand, publish::PublishCommand, serve::ServeCommand,
|
|
||||||
subscribe::SubscribeCommand,
|
|
||||||
},
|
|
||||||
state::ClientState,
|
state::ClientState,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod node;
|
|
||||||
mod publish;
|
mod publish;
|
||||||
mod serve;
|
mod serve;
|
||||||
mod subscribe;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about)]
|
#[command(author, version, about)]
|
||||||
@@ -29,8 +24,6 @@ struct Cli {
|
|||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum CliSubcommands {
|
enum CliSubcommands {
|
||||||
Node(NodeCommand),
|
|
||||||
Subscribe(SubscribeCommand),
|
|
||||||
Publish(PublishCommand),
|
Publish(PublishCommand),
|
||||||
Serve(ServeCommand),
|
Serve(ServeCommand),
|
||||||
}
|
}
|
||||||
@@ -41,9 +34,7 @@ pub async fn execute() -> anyhow::Result<()> {
|
|||||||
let state = ClientState::new(&cmd.server_url);
|
let state = ClientState::new(&cmd.server_url);
|
||||||
|
|
||||||
match cmd.subcommands {
|
match cmd.subcommands {
|
||||||
CliSubcommands::Node(cmd) => cmd.execute(&state).await,
|
|
||||||
CliSubcommands::Publish(cmd) => cmd.execute(&state).await,
|
CliSubcommands::Publish(cmd) => cmd.execute(&state).await,
|
||||||
CliSubcommands::Serve(cmd) => cmd.execute().await,
|
CliSubcommands::Serve(cmd) => cmd.execute().await,
|
||||||
CliSubcommands::Subscribe(cmd) => cmd.execute(&state).await,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
use crate::{cli::node::start::StartCommand, state::ClientState};
|
|
||||||
|
|
||||||
mod start;
|
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
|
||||||
pub struct NodeCommand {
|
|
||||||
#[clap(subcommand)]
|
|
||||||
commands: NodeCommands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(clap::Subcommand)]
|
|
||||||
pub enum NodeCommands {
|
|
||||||
Start(StartCommand),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeCommand {
|
|
||||||
pub async fn execute(&self, state: &ClientState) -> anyhow::Result<()> {
|
|
||||||
match &self.commands {
|
|
||||||
NodeCommands::Start(start_command) => start_command.execute(state).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
use crate::{node::services::node_service::NodeServiceState, state::ClientState};
|
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
|
||||||
pub struct StartCommand {}
|
|
||||||
|
|
||||||
impl StartCommand {
|
|
||||||
pub async fn execute(&self, state: &ClientState) -> anyhow::Result<()> {
|
|
||||||
notmad::Mad::builder()
|
|
||||||
.add(state.node_service())
|
|
||||||
.run()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +1,12 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{grpc_client::GrpcClientState, project_file, state::ClientState};
|
use crate::{grpc_client::GrpcClientState, models::ProjectTag, project_file, state::ClientState};
|
||||||
|
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
pub struct PublishCommand {
|
pub struct PublishCommand {
|
||||||
|
#[arg(value_parser = clap::value_parser!(ProjectTag))]
|
||||||
|
project_tag: ProjectTag,
|
||||||
|
|
||||||
#[arg(long = "project-path", default_value = ".")]
|
#[arg(long = "project-path", default_value = ".")]
|
||||||
project_path: PathBuf,
|
project_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
use norun_grpc_interface::project::ProjectType;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
container_runtime::ContainerRuntimeState, grpc_client::GrpcClientState, models::port::Port,
|
|
||||||
state::ClientState,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
|
||||||
pub struct SubscribeCommand {
|
|
||||||
#[arg(long)]
|
|
||||||
topic: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SubscribeCommand {
|
|
||||||
pub async fn execute(&self, state: &ClientState) -> anyhow::Result<()> {
|
|
||||||
let projects = state.grpc_client().subscribe(&self.topic).await?;
|
|
||||||
|
|
||||||
let runtime = state.container_runtime();
|
|
||||||
|
|
||||||
println!("printing found projects (len={})", projects.projects.len());
|
|
||||||
for project in projects.projects {
|
|
||||||
println!("project: {project:?}");
|
|
||||||
|
|
||||||
match project.project_type {
|
|
||||||
Some(project_type) => match project_type {
|
|
||||||
ProjectType::Container(container) => {
|
|
||||||
runtime
|
|
||||||
.ensure_running(
|
|
||||||
&project.name,
|
|
||||||
&format!("{}:{}", container.image, container.version),
|
|
||||||
vec![Port {
|
|
||||||
host_port: 38080,
|
|
||||||
container_port: 80,
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
ProjectType::Compose(compose) => {
|
|
||||||
// Allocate a local project
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,105 +0,0 @@
|
|||||||
use std::{collections::HashMap, sync::LazyLock};
|
|
||||||
|
|
||||||
use bollard::{
|
|
||||||
Docker,
|
|
||||||
query_parameters::{
|
|
||||||
CreateContainerOptionsBuilder, CreateImageOptionsBuilder, ListContainersOptionsBuilder,
|
|
||||||
StartContainerOptionsBuilder,
|
|
||||||
},
|
|
||||||
secret::{ContainerCreateBody, HostConfig, PortBinding},
|
|
||||||
};
|
|
||||||
use futures_util::TryStreamExt;
|
|
||||||
|
|
||||||
use crate::{models::port::Port, state::ClientState};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ContainerRuntime {
|
|
||||||
client: Docker,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContainerRuntime {
|
|
||||||
#[tracing::instrument(skip(self), level = "trace")]
|
|
||||||
pub async fn ensure_running(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
image: &str,
|
|
||||||
ports: Vec<Port>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
tracing::debug!("ensuring that image is running");
|
|
||||||
|
|
||||||
let containers = self
|
|
||||||
.client
|
|
||||||
.list_containers(Some(
|
|
||||||
ListContainersOptionsBuilder::default()
|
|
||||||
.all(true)
|
|
||||||
.filters(&HashMap::from([(
|
|
||||||
"name".to_string(),
|
|
||||||
vec![name.to_string()],
|
|
||||||
)]))
|
|
||||||
.build(),
|
|
||||||
))
|
|
||||||
.await?;
|
|
||||||
if !containers.is_empty() {
|
|
||||||
// Reconcile difference
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = self
|
|
||||||
.client
|
|
||||||
.create_image(
|
|
||||||
Some(CreateImageOptionsBuilder::new().from_image(image).build()),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.try_collect::<Vec<_>>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let ports: HashMap<_, _> = ports
|
|
||||||
.iter()
|
|
||||||
.map(|p| {
|
|
||||||
(
|
|
||||||
format!("{}/tcp", p.container_port),
|
|
||||||
Some(vec![PortBinding {
|
|
||||||
host_ip: Some("0.0.0.0".into()),
|
|
||||||
host_port: Some(p.host_port.to_string()),
|
|
||||||
}]),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
self.client
|
|
||||||
.create_container(
|
|
||||||
Some(CreateContainerOptionsBuilder::new().name(name).build()),
|
|
||||||
ContainerCreateBody {
|
|
||||||
image: Some(image.into()),
|
|
||||||
host_config: Some(HostConfig {
|
|
||||||
port_bindings: Some(ports),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.client
|
|
||||||
.start_container(name, Some(StartContainerOptionsBuilder::default().build()))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ContainerRuntimeState {
|
|
||||||
fn container_runtime(&self) -> ContainerRuntime;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContainerRuntimeState for ClientState {
|
|
||||||
fn container_runtime(&self) -> ContainerRuntime {
|
|
||||||
static CLIENT: LazyLock<bollard::Docker> = LazyLock::new(|| {
|
|
||||||
Docker::connect_with_defaults().expect("to be able to connect to a docker daemon")
|
|
||||||
});
|
|
||||||
|
|
||||||
ContainerRuntime {
|
|
||||||
client: CLIENT.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,8 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use norun_grpc_interface::{PublishRequest, registry_service_client::RegistryServiceClient};
|
||||||
|
|
||||||
use norun_grpc_interface::{
|
|
||||||
GetTopicRequest, Projects, PublishRequest, registry_service_client::RegistryServiceClient,
|
|
||||||
};
|
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
use tonic::transport::Channel;
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
@@ -30,23 +26,6 @@ impl GrpcClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn subscribe(&self, topic: &str) -> anyhow::Result<Projects> {
|
|
||||||
tracing::trace!("calling subscribe via. grpc on registry");
|
|
||||||
|
|
||||||
let mut registry_client = self.get_registry_client().await?;
|
|
||||||
|
|
||||||
let res = registry_client
|
|
||||||
.get_topic(GetTopicRequest {
|
|
||||||
topic: topic.to_string(),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let res = res.into_inner();
|
|
||||||
|
|
||||||
res.projects
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("failed to get projects from server"))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_registry_client(&self) -> anyhow::Result<RegistryServiceClient<Channel>> {
|
async fn get_registry_client(&self) -> anyhow::Result<RegistryServiceClient<Channel>> {
|
||||||
let client = self
|
let client = self
|
||||||
.registry_client
|
.registry_client
|
||||||
@@ -72,39 +51,9 @@ impl From<ProjectFile> for norun_grpc_interface::Project {
|
|||||||
fn from(value: ProjectFile) -> Self {
|
fn from(value: ProjectFile) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: value.project.name,
|
name: value.project.name,
|
||||||
|
image: value.container.image,
|
||||||
|
version: value.container.version,
|
||||||
port: value.expose.and_then(|e| e.port),
|
port: value.expose.and_then(|e| e.port),
|
||||||
project_type: Some({
|
|
||||||
match (value.container, value.compose) {
|
|
||||||
(None, None) => panic!("either a container or compose is required"),
|
|
||||||
(Some(_), Some(_)) => {
|
|
||||||
panic!("either a container or compose is required, but not both")
|
|
||||||
}
|
|
||||||
(Some(container), None) => {
|
|
||||||
norun_grpc_interface::project::ProjectType::Container(
|
|
||||||
norun_grpc_interface::Container {
|
|
||||||
image: container.image,
|
|
||||||
version: container.version,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
(None, Some(compose)) => norun_grpc_interface::project::ProjectType::Compose(
|
|
||||||
norun_grpc_interface::Compose {
|
|
||||||
// TODO: dirty hack to get files out for compose, it should instead
|
|
||||||
// be transformed into an intermediary format, or simply extraced from the <From>
|
|
||||||
files: compose
|
|
||||||
.include
|
|
||||||
.into_iter()
|
|
||||||
.map(|i| {
|
|
||||||
(
|
|
||||||
i.to_string_lossy().to_string(),
|
|
||||||
std::fs::read(&i).expect("to be able to read include file"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<HashMap<_, _>>(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,6 @@
|
|||||||
use norun_grpc_interface::{registry_service_server::RegistryService, *};
|
use norun_grpc_interface::{registry_service_server::RegistryService, *};
|
||||||
|
|
||||||
use crate::{
|
use crate::{server::services::registry::RegistryServiceState, state::ServerState};
|
||||||
server::services::registry::{self, RegistryServiceState},
|
|
||||||
state::ServerState,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct GrpcRegistryService {
|
pub struct GrpcRegistryService {
|
||||||
pub state: ServerState,
|
pub state: ServerState,
|
||||||
@@ -11,7 +8,7 @@ pub struct GrpcRegistryService {
|
|||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl RegistryService for GrpcRegistryService {
|
impl RegistryService for GrpcRegistryService {
|
||||||
#[tracing::instrument(skip(self), level = "trace")]
|
#[tracing::instrument(skip(self), level = "debug")]
|
||||||
async fn publish(
|
async fn publish(
|
||||||
&self,
|
&self,
|
||||||
request: tonic::Request<PublishRequest>,
|
request: tonic::Request<PublishRequest>,
|
||||||
@@ -33,34 +30,4 @@ impl RegistryService for GrpcRegistryService {
|
|||||||
|
|
||||||
Ok(tonic::Response::new(PublishResponse {}))
|
Ok(tonic::Response::new(PublishResponse {}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self), level = "trace")]
|
|
||||||
async fn get_topic(
|
|
||||||
&self,
|
|
||||||
request: tonic::Request<GetTopicRequest>,
|
|
||||||
) -> std::result::Result<tonic::Response<GetTopicResponse>, tonic::Status> {
|
|
||||||
tracing::debug!("subscribe called");
|
|
||||||
|
|
||||||
let req = request.into_inner();
|
|
||||||
|
|
||||||
let projects = self
|
|
||||||
.state
|
|
||||||
.registry_service()
|
|
||||||
.get_topic(&req.topic)
|
|
||||||
.await
|
|
||||||
.inspect_err(|e| tracing::warn!("failed to subscribe on topic: {}", e))
|
|
||||||
.map_err(|e| tonic::Status::internal(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(tonic::Response::new(GetTopicResponse {
|
|
||||||
projects: Some(projects.into()),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<registry::Projects> for Projects {
|
|
||||||
fn from(value: registry::Projects) -> Self {
|
|
||||||
Self {
|
|
||||||
projects: value.projects.into_iter().collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,15 +7,9 @@ mod state;
|
|||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
mod services;
|
|
||||||
|
|
||||||
mod grpc_client;
|
mod grpc_client;
|
||||||
mod grpc_server;
|
mod grpc_server;
|
||||||
|
|
||||||
mod container_runtime;
|
|
||||||
|
|
||||||
mod node;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
|
@@ -1,24 +1,2 @@
|
|||||||
pub mod project_tag;
|
pub mod project_tag;
|
||||||
pub use project_tag::*;
|
pub use project_tag::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
pub mod port {
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub struct Port {
|
|
||||||
pub host_port: usize,
|
|
||||||
pub container_port: usize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub struct Project {
|
|
||||||
pub id: Uuid,
|
|
||||||
pub spec: ProjectSpec,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub enum ProjectSpec {
|
|
||||||
Compose {},
|
|
||||||
Container {},
|
|
||||||
}
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
pub mod config;
|
|
||||||
|
|
||||||
pub mod services;
|
|
@@ -1,15 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Config {
|
|
||||||
node_name: Option<String>,
|
|
||||||
|
|
||||||
subscriptions: BTreeMap<String, Subscription>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Subscription {
|
|
||||||
enabled: Option<bool>,
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
pub mod config_service;
|
|
||||||
pub mod data_store;
|
|
||||||
pub mod node_service;
|
|
@@ -1,31 +0,0 @@
|
|||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
use crate::{node::config::Config, state::ClientState};
|
|
||||||
|
|
||||||
pub struct ConfigService {}
|
|
||||||
|
|
||||||
impl ConfigService {
|
|
||||||
pub async fn get_config(&self) -> anyhow::Result<Config> {
|
|
||||||
let config_file_path = dirs::config_dir()
|
|
||||||
.context("failed to get config dir")?
|
|
||||||
.join("norun")
|
|
||||||
.join("node")
|
|
||||||
.join("config.toml");
|
|
||||||
|
|
||||||
let config_file_content = tokio::fs::read_to_string(&config_file_path).await?;
|
|
||||||
|
|
||||||
let config: Config = toml::from_str(&config_file_content)?;
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ConfigServiceState {
|
|
||||||
fn config_service(&self) -> ConfigService;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigServiceState for ClientState {
|
|
||||||
fn config_service(&self) -> ConfigService {
|
|
||||||
ConfigService {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,69 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use notmad::{Component, MadError};
|
|
||||||
use tokio::sync::Mutex;
|
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
|
|
||||||
use crate::state::ClientState;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct DataStore {
|
|
||||||
sender: tokio::sync::mpsc::Sender<()>,
|
|
||||||
receiver: Arc<Mutex<tokio::sync::mpsc::Receiver<()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataStore {
|
|
||||||
pub async fn execute(&self, cancellation_token: CancellationToken) -> anyhow::Result<()> {
|
|
||||||
let mut rec = self.receiver.lock().await;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let msg = tokio::select! {
|
|
||||||
_ = cancellation_token.cancelled() => {
|
|
||||||
return Ok(())
|
|
||||||
},
|
|
||||||
item = rec.recv() => {
|
|
||||||
match item {
|
|
||||||
Some(item) => item,
|
|
||||||
None => return Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tracing::debug!("handling item");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn publish(&self) -> anyhow::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Component for DataStore {
|
|
||||||
fn name(&self) -> Option<String> {
|
|
||||||
Some("norun/node/data-store".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(&self, cancellation_token: CancellationToken) -> Result<(), MadError> {
|
|
||||||
self.execute(cancellation_token)
|
|
||||||
.await
|
|
||||||
.map_err(notmad::MadError::Inner)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataStoreState {
|
|
||||||
fn data_store(&self) -> DataStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataStoreState for ClientState {
|
|
||||||
fn data_store(&self) -> DataStore {
|
|
||||||
let (sender, receiver) = tokio::sync::mpsc::channel(100);
|
|
||||||
|
|
||||||
DataStore {
|
|
||||||
sender,
|
|
||||||
receiver: Arc::new(Mutex::new(receiver)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
use notmad::{Component, MadError};
|
|
||||||
use tokio_util::sync::CancellationToken;
|
|
||||||
|
|
||||||
use crate::state::ClientState;
|
|
||||||
|
|
||||||
pub struct NodeService {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Component for NodeService {
|
|
||||||
fn name(&self) -> Option<String> {
|
|
||||||
Some("norun/node".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn setup(&self) -> Result<(), MadError> {
|
|
||||||
tracing::info!("starting norun/node!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run(&self, cancellation_token: CancellationToken) -> Result<(), MadError> {
|
|
||||||
cancellation_token.cancelled().await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NodeServiceState {
|
|
||||||
fn node_service(&self) -> NodeService;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NodeServiceState for ClientState {
|
|
||||||
fn node_service(&self) -> NodeService {
|
|
||||||
NodeService {}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -8,9 +8,8 @@ const NORUN_PROJECT_FILE_NAME: &str = "norun.toml";
|
|||||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||||
pub struct ProjectFile {
|
pub struct ProjectFile {
|
||||||
pub project: ProjectDecl,
|
pub project: ProjectDecl,
|
||||||
pub container: Option<ContainerDecl>,
|
pub container: ContainerDecl,
|
||||||
pub expose: Option<ExposeDecl>,
|
pub expose: Option<ExposeDecl>,
|
||||||
pub compose: Option<ComposeDecl>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||||
@@ -24,11 +23,6 @@ pub struct ContainerDecl {
|
|||||||
pub version: String,
|
pub version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
|
||||||
pub struct ComposeDecl {
|
|
||||||
pub include: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||||
pub struct ExposeDecl {
|
pub struct ExposeDecl {
|
||||||
pub port: Option<u32>,
|
pub port: Option<u32>,
|
||||||
@@ -78,11 +72,10 @@ port = 8080
|
|||||||
project: ProjectDecl {
|
project: ProjectDecl {
|
||||||
name: "hello-world".into(),
|
name: "hello-world".into(),
|
||||||
},
|
},
|
||||||
container: Some(ContainerDecl {
|
container: ContainerDecl {
|
||||||
image: "kasperhermansen/hello-world".into(),
|
image: "kasperhermansen/hello-world".into(),
|
||||||
version: "latest".into(),
|
version: "latest".into(),
|
||||||
}),
|
},
|
||||||
compose: None,
|
|
||||||
expose: Some(ExposeDecl { port: Some(8080) }),
|
expose: Some(ExposeDecl { port: Some(8080) }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
use std::sync::{Arc, LazyLock};
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
sync::{Arc, LazyLock},
|
||||||
|
};
|
||||||
|
|
||||||
use norun_grpc_interface::Project;
|
use norun_grpc_interface::Project;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -22,25 +25,6 @@ impl RegistryService {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_topic(&self, topic: &str) -> anyhow::Result<Projects> {
|
|
||||||
tracing::debug!("get projects for topic");
|
|
||||||
|
|
||||||
let projects = {
|
|
||||||
let store = self.store.lock().await;
|
|
||||||
store
|
|
||||||
.iter()
|
|
||||||
.filter(|i| i.name == topic)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Projects { projects })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Projects {
|
|
||||||
pub projects: Vec<Project>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RegistryServiceState {
|
pub trait RegistryServiceState {
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
pub mod project_registry;
|
|
@@ -1,104 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
|
|
||||||
use crate::{models::Project, state::ClientState};
|
|
||||||
|
|
||||||
pub struct ProjectRegistry {
|
|
||||||
state_dir: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectRegistry {
|
|
||||||
pub async fn get_project(&self, project: &Project) -> anyhow::Result<Option<Project>> {
|
|
||||||
let project_dir = self.project_file(project)?;
|
|
||||||
|
|
||||||
if !project_dir.exists() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let project_content = tokio::fs::read_to_string(&project_dir)
|
|
||||||
.await
|
|
||||||
.context("failed to read ron file")?;
|
|
||||||
|
|
||||||
let project: Project = ron::from_str(&project_content)
|
|
||||||
.context(format!("failed to read: '{}'", project_dir.display()))?;
|
|
||||||
|
|
||||||
Ok(Some(project))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_project(&self, project: &Project) -> anyhow::Result<()> {
|
|
||||||
match self.get_project(project).await {
|
|
||||||
Ok(_) => anyhow::bail!("project already exists"),
|
|
||||||
Err(_) => {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let project_file_path = self.project_file(project)?;
|
|
||||||
if let Some(project_file) = project_file_path.parent() {
|
|
||||||
tokio::fs::create_dir_all(project_file)
|
|
||||||
.await
|
|
||||||
.context("create ron project dir")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut project_file = tokio::fs::File::create_new(&project_file_path)
|
|
||||||
.await
|
|
||||||
.context("create project file")?;
|
|
||||||
|
|
||||||
let project_content = ron::to_string(project)?;
|
|
||||||
|
|
||||||
project_file
|
|
||||||
.write_all(&project_content.as_bytes())
|
|
||||||
.await
|
|
||||||
.context("write project file")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_project(&self, project: &Project) -> anyhow::Result<()> {
|
|
||||||
let project_file_path = self.project_file(project)?;
|
|
||||||
if let Some(project_file) = project_file_path.parent() {
|
|
||||||
tokio::fs::create_dir_all(project_file)
|
|
||||||
.await
|
|
||||||
.context("update ron project dir")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut project_file = tokio::fs::File::create_new(&project_file_path)
|
|
||||||
.await
|
|
||||||
.context("update project file")?;
|
|
||||||
|
|
||||||
let project_content = ron::to_string(project)?;
|
|
||||||
|
|
||||||
project_file
|
|
||||||
.write_all(project_content.as_bytes())
|
|
||||||
.await
|
|
||||||
.context("update project file")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_file(&self, project: &Project) -> anyhow::Result<PathBuf> {
|
|
||||||
let project_dir = self
|
|
||||||
.state_dir
|
|
||||||
.join(project.id.to_string())
|
|
||||||
.join("project.ron");
|
|
||||||
|
|
||||||
Ok(project_dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ProjectRegistryState {
|
|
||||||
fn project_registry(&self) -> ProjectRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectRegistryState for ClientState {
|
|
||||||
fn project_registry(&self) -> ProjectRegistry {
|
|
||||||
ProjectRegistry {
|
|
||||||
state_dir: dirs::state_dir()
|
|
||||||
.expect("to be able to find state")
|
|
||||||
.join("norun")
|
|
||||||
.join("projects"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,7 +2,7 @@
|
|||||||
name = "hello-world"
|
name = "hello-world"
|
||||||
|
|
||||||
[container]
|
[container]
|
||||||
image = "library/hello-world"
|
image = "kasperhermansen/hello-world"
|
||||||
version = "latest"
|
version = "latest"
|
||||||
|
|
||||||
[expose]
|
[expose]
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "nginx"
|
|
||||||
|
|
||||||
[container]
|
|
||||||
image = "library/nginx"
|
|
||||||
version = "latest"
|
|
||||||
|
|
||||||
[expose]
|
|
||||||
port = 80
|
|
@@ -2,44 +2,20 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package norun.v1;
|
package norun.v1;
|
||||||
|
|
||||||
|
service RegistryService {
|
||||||
|
rpc Publish(PublishRequest) returns (PublishResponse) {}
|
||||||
|
}
|
||||||
|
|
||||||
message PublishRequest {
|
message PublishRequest {
|
||||||
Project project = 1;
|
Project project = 1;
|
||||||
}
|
}
|
||||||
message PublishResponse {}
|
message PublishResponse {}
|
||||||
|
|
||||||
message GetTopicRequest {
|
|
||||||
string topic = 1;
|
|
||||||
}
|
|
||||||
message GetTopicResponse {
|
|
||||||
Projects projects = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
service RegistryService {
|
|
||||||
rpc Publish(PublishRequest) returns (PublishResponse) {}
|
|
||||||
rpc GetTopic(GetTopicRequest) returns (GetTopicResponse) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Projects {
|
|
||||||
repeated Project projects = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Project {
|
message Project {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
optional uint32 port = 2;
|
|
||||||
|
|
||||||
oneof project_type {
|
string image = 2;
|
||||||
Container container = 3;
|
string version = 3;
|
||||||
Compose compose = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Container {
|
optional uint32 port = 4;
|
||||||
string image = 1;
|
|
||||||
string version = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Compose {
|
|
||||||
map<string, bytes> files = 1;
|
|
||||||
}
|
}
|
||||||
|
11
mise.toml
11
mise.toml
@@ -1,11 +0,0 @@
|
|||||||
[tasks."node"]
|
|
||||||
env = { RUST_LOG = "norun=trace,notmad=debug,info" }
|
|
||||||
run = "cargo run -p norun -- node"
|
|
||||||
|
|
||||||
[tasks."test"]
|
|
||||||
alias = ["t"]
|
|
||||||
run = "cargo nextest run"
|
|
||||||
|
|
||||||
[tasks."generate"]
|
|
||||||
alias = ["g", "gen"]
|
|
||||||
run = "buf generate"
|
|
3
renovate.json
Normal file
3
renovate.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
Reference in New Issue
Block a user