diff --git a/risedev.yml b/risedev.yml index b4b558face0e5..db8f6fe5600e2 100644 --- a/risedev.yml +++ b/risedev.yml @@ -1621,3 +1621,33 @@ template: # If `user-managed` is true, user is responsible for starting the service # to serve at the above address and port in any way they see fit. user-managed: false + + # Sql Server service backed by docker. + sqlserver: + # Note: Sql Server is now only for connector purpose. + # Id to be picked-up by services + id: sqlserver-${port} + + # address of mssql + address: "127.0.0.1" + + # listen port of mssql + port: 1433 + + # Note: + # - This will be used to initialize the Sql Server instance if it's fresh. + # - In user-managed mode, these configs are not validated by risedev. + # They are passed as-is to risedev-env default user for Sql Server operations. + user: SA + password: "YourPassword123" + database: "master" + + # The docker image. Can be overridden to use a different version. + image: "mcr.microsoft.com/mssql/server:2022-latest" + + # If set to true, data will be persisted at data/{id}. + persist-data: true + + # If `user-managed` is true, user is responsible for starting the service + # to serve at the above address and port in any way they see fit. + user-managed: false diff --git a/src/risedevtool/src/bin/risedev-compose.rs b/src/risedevtool/src/bin/risedev-compose.rs index 89bb0592d0d85..0547fda6b7008 100644 --- a/src/risedevtool/src/bin/risedev-compose.rs +++ b/src/risedevtool/src/bin/risedev-compose.rs @@ -222,6 +222,7 @@ fn main() -> Result<()> { ServiceConfig::Redis(_) | ServiceConfig::MySql(_) | ServiceConfig::Postgres(_) + | ServiceConfig::SqlServer(_) | ServiceConfig::SchemaRegistry(_) => return Err(anyhow!("not supported")), }; compose.container_name = service.id().to_string(); diff --git a/src/risedevtool/src/bin/risedev-dev.rs b/src/risedevtool/src/bin/risedev-dev.rs index 8b9b9493d6acb..c367473eb8e77 100644 --- a/src/risedevtool/src/bin/risedev-dev.rs +++ b/src/risedevtool/src/bin/risedev-dev.rs @@ -28,7 +28,7 @@ use risedev::{ ConfigureTmuxTask, DummyService, EnsureStopService, ExecuteContext, FrontendService, GrafanaService, KafkaService, MetaNodeService, MinioService, MySqlService, PostgresService, PrometheusService, PubsubService, RedisService, SchemaRegistryService, ServiceConfig, - SqliteConfig, Task, TempoService, RISEDEV_NAME, + SqlServerService, SqliteConfig, Task, TempoService, RISEDEV_NAME, }; use tempfile::tempdir; use thiserror_ext::AsReport; @@ -353,6 +353,24 @@ fn task_main( ctx.pb .set_message(format!("postgres {}:{}", c.address, c.port)); } + ServiceConfig::SqlServer(c) => { + let mut ctx = + ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); + // only `c.password` will be used in `SqlServerService` as the password for user `sa`. + SqlServerService::new(c.clone()).execute(&mut ctx)?; + if c.user_managed { + let mut task = + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; + task.execute(&mut ctx)?; + } else { + let mut task = risedev::LogReadyCheckTask::new( + "SQL Server is now ready for client connections.", + )?; + task.execute(&mut ctx)?; + } + ctx.pb + .set_message(format!("sqlserver {}:{}", c.address, c.port)); + } } let service_id = service.id().to_string(); diff --git a/src/risedevtool/src/config.rs b/src/risedevtool/src/config.rs index bf768f8e68cd1..e4ba9acdf4e19 100644 --- a/src/risedevtool/src/config.rs +++ b/src/risedevtool/src/config.rs @@ -175,6 +175,7 @@ impl ConfigExpander { "redpanda" => ServiceConfig::RedPanda(serde_yaml::from_str(&out_str)?), "mysql" => ServiceConfig::MySql(serde_yaml::from_str(&out_str)?), "postgres" => ServiceConfig::Postgres(serde_yaml::from_str(&out_str)?), + "sqlserver" => ServiceConfig::SqlServer(serde_yaml::from_str(&out_str)?), "schema-registry" => { ServiceConfig::SchemaRegistry(serde_yaml::from_str(&out_str)?) } diff --git a/src/risedevtool/src/risedev_env.rs b/src/risedevtool/src/risedev_env.rs index c40acf3170708..4860d30e01485 100644 --- a/src/risedevtool/src/risedev_env.rs +++ b/src/risedevtool/src/risedev_env.rs @@ -118,6 +118,24 @@ pub fn generate_risedev_env(services: &Vec) -> String { writeln!(env, r#"PGPASSWORD="{password}""#,).unwrap(); writeln!(env, r#"PGDATABASE="{database}""#,).unwrap(); } + ServiceConfig::SqlServer(c) => { + let host = &c.address; + let port = &c.port; + let user = &c.user; + let password = &c.password; + let database = &c.database; + // These envs are used by `sqlcmd`. + writeln!(env, r#"SQLCMDSERVER="{host}""#,).unwrap(); + writeln!(env, r#"SQLCMDPORT="{port}""#,).unwrap(); + writeln!(env, r#"SQLCMDUSER="{user}""#,).unwrap(); + writeln!(env, r#"SQLCMDPASSWORD="{password}""#,).unwrap(); + writeln!(env, r#"SQLCMDDBNAME="{database}""#,).unwrap(); + writeln!( + env, + r#"RISEDEV_SQLSERVER_WITH_OPTIONS_COMMON="connector='sqlserver-cdc',hostname='{host}',port='{port}',username='{user}',password='{password}',database.name='{database}'""#, + ) + .unwrap(); + } _ => {} } } diff --git a/src/risedevtool/src/service_config.rs b/src/risedevtool/src/service_config.rs index fb95dbb520a0d..9d08a6f602251 100644 --- a/src/risedevtool/src/service_config.rs +++ b/src/risedevtool/src/service_config.rs @@ -412,6 +412,26 @@ pub struct PostgresConfig { pub persist_data: bool, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct SqlServerConfig { + #[serde(rename = "use")] + phantom_use: Option, + pub id: String, + + pub port: u16, + pub address: String, + + pub user: String, + pub password: String, + pub database: String, + + pub image: String, + pub user_managed: bool, + pub persist_data: bool, +} + /// All service configuration #[derive(Clone, Debug, PartialEq)] pub enum ServiceConfig { @@ -434,6 +454,7 @@ pub enum ServiceConfig { RedPanda(RedPandaConfig), MySql(MySqlConfig), Postgres(PostgresConfig), + SqlServer(SqlServerConfig), } impl ServiceConfig { @@ -457,6 +478,7 @@ impl ServiceConfig { Self::Opendal(c) => &c.id, Self::MySql(c) => &c.id, Self::Postgres(c) => &c.id, + Self::SqlServer(c) => &c.id, Self::SchemaRegistry(c) => &c.id, } } @@ -482,6 +504,7 @@ impl ServiceConfig { Self::Opendal(_) => None, Self::MySql(c) => Some(c.port), Self::Postgres(c) => Some(c.port), + Self::SqlServer(c) => Some(c.port), Self::SchemaRegistry(c) => Some(c.port), } } @@ -506,6 +529,7 @@ impl ServiceConfig { Self::Opendal(_c) => false, Self::MySql(c) => c.user_managed, Self::Postgres(c) => c.user_managed, + Self::SqlServer(c) => c.user_managed, Self::SchemaRegistry(c) => c.user_managed, } } diff --git a/src/risedevtool/src/task.rs b/src/risedevtool/src/task.rs index 94985269cb353..dc82ede836cbd 100644 --- a/src/risedevtool/src/task.rs +++ b/src/risedevtool/src/task.rs @@ -30,6 +30,7 @@ mod prometheus_service; mod pubsub_service; mod redis_service; mod schema_registry_service; +mod sql_server_service; mod task_configure_minio; mod task_etcd_ready_check; mod task_kafka_ready_check; @@ -70,6 +71,7 @@ pub use self::prometheus_service::*; pub use self::pubsub_service::*; pub use self::redis_service::*; pub use self::schema_registry_service::SchemaRegistryService; +pub use self::sql_server_service::*; pub use self::task_configure_minio::*; pub use self::task_etcd_ready_check::*; pub use self::task_kafka_ready_check::*; diff --git a/src/risedevtool/src/task/sql_server_service.rs b/src/risedevtool/src/task/sql_server_service.rs new file mode 100644 index 0000000000000..a00e10b7a721a --- /dev/null +++ b/src/risedevtool/src/task/sql_server_service.rs @@ -0,0 +1,50 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::task::docker_service::{DockerService, DockerServiceConfig}; +use crate::SqlServerConfig; + +impl DockerServiceConfig for SqlServerConfig { + fn id(&self) -> String { + self.id.clone() + } + + fn is_user_managed(&self) -> bool { + self.user_managed + } + + fn image(&self) -> String { + self.image.clone() + } + + fn envs(&self) -> Vec<(String, String)> { + vec![ + ("ACCEPT_EULA".to_owned(), "Y".to_owned()), + ("MSSQL_AGENT_ENABLED".to_owned(), "true".to_owned()), + ("MSSQL_SA_PASSWORD".to_owned(), self.password.clone()), + ] + } + + fn ports(&self) -> Vec<(String, String)> { + vec![(self.port.to_string(), "1433".to_owned())] + } + + fn data_path(&self) -> Option { + self.persist_data + .then(|| "/var/lib/sqlserver/data".to_owned()) + } +} + +/// Docker-backed Sql Server service. +pub type SqlServerService = DockerService;