Skip to content

Commit a5da4cd

Browse files
committed
feat: Enhance CI workflows and service management features
- Added steps to build the `temps-captcha-wasm` crate in CI workflows, ensuring WebAssembly components are built during releases. - Integrated Bun installation in CI workflows to streamline JavaScript dependency management. - Updated `ParameterStrategy` implementations for Postgres, Redis, and MongoDB to include new updateable keys, enhancing service configuration flexibility. - Introduced methods to verify Docker images are pullable before upgrades in MongoDB, Redis, and S3 services, improving reliability during service updates. - Refactored sidebar components in the web application to improve responsiveness and user experience across different screen sizes.
1 parent 3a30ff5 commit a5da4cd

File tree

14 files changed

+514
-120
lines changed

14 files changed

+514
-120
lines changed

.github/workflows/release.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ jobs:
6161
cd web
6262
bun install
6363
64+
- name: Build WASM
65+
run: |
66+
cd crates/temps-captcha-wasm
67+
bun run build
68+
6469
- name: Build release binary
6570
run: cargo build --release --bin temps
6671
env:
@@ -138,6 +143,11 @@ jobs:
138143
cd web
139144
bun install
140145
146+
- name: Build WASM
147+
run: |
148+
cd crates/temps-captcha-wasm
149+
bun run build
150+
141151
- name: Build release binary
142152
run: cargo build --release --bin temps --target x86_64-apple-darwin
143153
env:
@@ -215,6 +225,11 @@ jobs:
215225
cd web
216226
bun install
217227
228+
- name: Build WASM
229+
run: |
230+
cd crates/temps-captcha-wasm
231+
bun run build
232+
218233
- name: Build release binary
219234
run: cargo build --release --bin temps --target aarch64-apple-darwin
220235
env:

.github/workflows/rust-tests.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,19 @@ jobs:
2525
- name: Install wasm-pack
2626
run: curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
2727

28+
- name: Install Bun
29+
uses: oven-sh/setup-bun@v2
30+
with:
31+
bun-version: latest
32+
2833
- name: Cache dependencies
2934
uses: Swatinem/rust-cache@v2
3035

36+
- name: Build WASM
37+
run: |
38+
cd crates/temps-captcha-wasm
39+
bun run build
40+
3141
- name: Check workspace
3242
run: cargo check --workspace --all-targets
3343

@@ -61,9 +71,19 @@ jobs:
6171
- name: Install wasm-pack
6272
run: curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
6373

74+
- name: Install Bun
75+
uses: oven-sh/setup-bun@v2
76+
with:
77+
bun-version: latest
78+
6479
- name: Cache dependencies
6580
uses: Swatinem/rust-cache@v2
6681

82+
- name: Build WASM
83+
run: |
84+
cd crates/temps-captcha-wasm
85+
bun run build
86+
6787
- name: Run clippy
6888
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
6989

@@ -112,9 +132,19 @@ jobs:
112132
- name: Install wasm-pack
113133
run: curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
114134

135+
- name: Install Bun
136+
uses: oven-sh/setup-bun@v2
137+
with:
138+
bun-version: latest
139+
115140
- name: Cache dependencies
116141
uses: Swatinem/rust-cache@v2
117142

143+
- name: Build WASM
144+
run: |
145+
cd crates/temps-captcha-wasm
146+
bun run build
147+
118148
- name: Verify TimescaleDB is ready
119149
run: |
120150
echo "Checking if TimescaleDB port is open..."

crates/temps-providers/src/externalsvc/mongodb.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use async_trait::async_trait;
33
use bollard::exec::CreateExecOptions;
44
use bollard::query_parameters::{InspectContainerOptions, StopContainerOptions};
55
use bollard::{body_full, Docker};
6-
use futures::StreamExt;
6+
use futures::{StreamExt, TryStreamExt};
77
use mongodb::bson::doc;
88
use mongodb::options::ClientOptions;
99
use mongodb::Client as MongoClient;
@@ -417,6 +417,49 @@ impl MongodbService {
417417

418418
Ok(databases)
419419
}
420+
421+
/// Verify that a Docker image can be pulled without actually downloading the full image
422+
/// Attempts to pull the image - fails if it doesn't exist or cannot be accessed
423+
#[allow(dead_code)]
424+
async fn verify_image_pullable(&self, image: &str) -> Result<()> {
425+
// Parse image name and tag
426+
let (image_name, tag) = if let Some((name, tag)) = image.split_once(':') {
427+
(name.to_string(), tag.to_string())
428+
} else {
429+
(image.to_string(), "latest".to_string())
430+
};
431+
432+
info!("Attempting to pull Docker image: {}", image);
433+
434+
// Try to pull the image - this will fail if it doesn't exist
435+
let result = self
436+
.docker
437+
.create_image(
438+
Some(bollard::query_parameters::CreateImageOptions {
439+
from_image: Some(image_name.clone()),
440+
tag: Some(tag.clone()),
441+
..Default::default()
442+
}),
443+
None,
444+
None,
445+
)
446+
.try_collect::<Vec<_>>()
447+
.await;
448+
449+
match result {
450+
Ok(_) => {
451+
info!("Docker image {} is available and pullable", image);
452+
Ok(())
453+
}
454+
Err(e) => {
455+
error!("Failed to pull Docker image {}: {}", image, e);
456+
Err(anyhow::anyhow!(
457+
"Cannot upgrade: Docker image '{}' is not available or cannot be pulled. Error: {}",
458+
image, e
459+
))
460+
}
461+
}
462+
}
420463
}
421464

422465
#[async_trait]
@@ -507,7 +550,7 @@ impl ExternalService for MongodbService {
507550
"port" => true, // Port can be changed
508551
"database" => false, // Don't change database name after creation
509552
"username" => false, // Don't change username after creation
510-
"password" => true, // Password can be updated
553+
"password" => false, // Password is auto-generated and cannot be changed
511554
"image" => true, // Image can be upgraded
512555
"version" => true, // Version can be upgraded
513556
_ => false,
@@ -1080,6 +1123,34 @@ impl ExternalService for MongodbService {
10801123
let (_, version) = self.get_current_docker_image().await?;
10811124
Ok(version)
10821125
}
1126+
1127+
async fn upgrade(&self, old_config: ServiceConfig, new_config: ServiceConfig) -> Result<()> {
1128+
info!("Starting MongoDB upgrade");
1129+
1130+
let _old_mongodb_config = self.get_mongodb_config(old_config)?;
1131+
let new_mongodb_config = self.get_mongodb_config(new_config)?;
1132+
1133+
// Verify the new image can be pulled BEFORE stopping the old container
1134+
let new_image = format!(
1135+
"{}:{}",
1136+
new_mongodb_config.image, new_mongodb_config.version
1137+
);
1138+
info!("Verifying new Docker image is available: {}", new_image);
1139+
self.verify_image_pullable(&new_image).await?;
1140+
info!("New Docker image verified and is available");
1141+
1142+
// Stop the old container
1143+
info!("Stopping old MongoDB container");
1144+
self.stop().await?;
1145+
1146+
// Create container with new image (keeping the same volume for data persistence)
1147+
info!("Starting MongoDB container with new image");
1148+
self.create_container(&self.docker, &new_mongodb_config)
1149+
.await?;
1150+
1151+
info!("MongoDB upgrade completed successfully");
1152+
Ok(())
1153+
}
10831154
}
10841155

10851156
#[cfg(test)]

crates/temps-providers/src/externalsvc/postgres.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,11 @@ impl PostgresService {
382382
exposed_ports: Some(HashMap::from([("5432/tcp".to_string(), HashMap::new())])),
383383
env: Some(env_vars.iter().map(|s| s.to_string()).collect()),
384384
labels: Some(container_labels),
385+
cmd: Some(vec![
386+
"postgres".to_string(),
387+
"-c".to_string(),
388+
format!("max_connections={}", config.max_connections),
389+
]),
385390
host_config: Some(bollard::models::HostConfig {
386391
restart_policy: Some(bollard::models::RestartPolicy {
387392
name: Some(bollard::models::RestartPolicyNameEnum::ALWAYS),
@@ -765,6 +770,11 @@ impl PostgresService {
765770
format!("POSTGRES_USER=postgres"),
766771
format!("POSTGRES_PASSWORD={}", new_config.password),
767772
]),
773+
cmd: Some(vec![
774+
"postgres".to_string(),
775+
"-c".to_string(),
776+
format!("max_connections={}", new_config.max_connections),
777+
]),
768778
host_config: Some(bollard::models::HostConfig {
769779
mounts: Some(vec![bollard::models::Mount {
770780
target: Some("/var/lib/postgresql/data".to_string()),
@@ -901,6 +911,48 @@ impl PostgresService {
901911

902912
Ok(())
903913
}
914+
915+
/// Verify that a Docker image can be pulled without actually downloading the full image
916+
/// Attempts to pull the image - fails if it doesn't exist or cannot be accessed
917+
async fn verify_image_pullable(&self, image: &str) -> Result<()> {
918+
// Parse image name and tag
919+
let (image_name, tag) = if let Some((name, tag)) = image.split_once(':') {
920+
(name.to_string(), tag.to_string())
921+
} else {
922+
(image.to_string(), "latest".to_string())
923+
};
924+
925+
info!("Attempting to pull Docker image: {}", image);
926+
927+
// Try to pull the image - this will fail if it doesn't exist
928+
let result = self
929+
.docker
930+
.create_image(
931+
Some(bollard::query_parameters::CreateImageOptions {
932+
from_image: Some(image_name.clone()),
933+
tag: Some(tag.clone()),
934+
..Default::default()
935+
}),
936+
None,
937+
None,
938+
)
939+
.try_collect::<Vec<_>>()
940+
.await;
941+
942+
match result {
943+
Ok(_) => {
944+
info!("Docker image {} is available and pullable", image);
945+
Ok(())
946+
}
947+
Err(e) => {
948+
error!("Failed to pull Docker image {}: {}", image, e);
949+
Err(anyhow::anyhow!(
950+
"Cannot upgrade: Docker image '{}' is not available or cannot be pulled. Error: {}",
951+
image, e
952+
))
953+
}
954+
}
955+
}
904956
}
905957

906958
#[async_trait]
@@ -1212,7 +1264,7 @@ impl ExternalService for PostgresService {
12121264
"port" => true, // Port can be changed
12131265
"database" => false, // Don't change database name after creation
12141266
"username" => false, // Don't change username after creation
1215-
"password" => true, // Password can be updated
1267+
"password" => false, // Password is auto-generated and cannot be changed
12161268
"max_connections" => true, // Max connections can be adjusted
12171269
"ssl_mode" => true, // SSL mode can be changed
12181270
"docker_image" => true, // Docker image can be upgraded
@@ -1484,6 +1536,15 @@ impl ExternalService for PostgresService {
14841536
));
14851537
}
14861538

1539+
// Verify the new image can be pulled BEFORE stopping the old container
1540+
info!(
1541+
"Verifying new Docker image is available: {}",
1542+
new_pg_config.docker_image
1543+
);
1544+
self.verify_image_pullable(&new_pg_config.docker_image)
1545+
.await?;
1546+
info!("New Docker image verified and is available");
1547+
14871548
// Stop the old container
14881549
info!("Stopping old PostgreSQL container");
14891550
self.stop().await?;

crates/temps-providers/src/externalsvc/redis.rs

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,49 @@ impl RedisService {
428428

429429
Ok(RedisConfig::from(input_config))
430430
}
431+
432+
/// Verify that a Docker image can be pulled without actually downloading the full image
433+
/// Attempts to pull the image - fails if it doesn't exist or cannot be accessed
434+
#[allow(dead_code)]
435+
async fn verify_image_pullable(&self, image: &str) -> Result<()> {
436+
// Parse image name and tag
437+
let (image_name, tag) = if let Some((name, tag)) = image.split_once(':') {
438+
(name.to_string(), tag.to_string())
439+
} else {
440+
(image.to_string(), "latest".to_string())
441+
};
442+
443+
info!("Attempting to pull Docker image: {}", image);
444+
445+
// Try to pull the image - this will fail if it doesn't exist
446+
let result = self
447+
.docker
448+
.create_image(
449+
Some(bollard::query_parameters::CreateImageOptions {
450+
from_image: Some(image_name.clone()),
451+
tag: Some(tag.clone()),
452+
..Default::default()
453+
}),
454+
None,
455+
None,
456+
)
457+
.try_collect::<Vec<_>>()
458+
.await;
459+
460+
match result {
461+
Ok(_) => {
462+
info!("Docker image {} is available and pullable", image);
463+
Ok(())
464+
}
465+
Err(e) => {
466+
error!("Failed to pull Docker image {}: {}", image, e);
467+
Err(anyhow::anyhow!(
468+
"Cannot upgrade: Docker image '{}' is not available or cannot be pulled. Error: {}",
469+
image, e
470+
))
471+
}
472+
}
473+
}
431474
}
432475

433476
#[async_trait]
@@ -540,11 +583,11 @@ impl ExternalService for RedisService {
540583
for key in properties.keys().cloned().collect::<Vec<_>>() {
541584
// Define which fields should be editable
542585
let editable = match key.as_str() {
543-
"host" => false, // Don't change host after creation
544-
"port" => true, // Port can be changed
545-
"password" => true, // Password can be updated
546-
"image" => true, // Image can be upgraded
547-
"version" => true, // Version can be changed
586+
"host" => false, // Don't change host after creation
587+
"port" => true, // Port can be changed
588+
"password" => false, // Password is auto-generated and cannot be changed
589+
"image" => true, // Image can be upgraded
590+
"version" => true, // Version can be changed
548591
_ => false,
549592
};
550593

@@ -1051,6 +1094,31 @@ impl ExternalService for RedisService {
10511094
let (_, version) = self.get_current_docker_image().await?;
10521095
Ok(version)
10531096
}
1097+
1098+
async fn upgrade(&self, old_config: ServiceConfig, new_config: ServiceConfig) -> Result<()> {
1099+
info!("Starting Redis upgrade");
1100+
1101+
let _old_redis_config = self.get_redis_config(old_config)?;
1102+
let new_redis_config = self.get_redis_config(new_config)?;
1103+
1104+
// Verify the new image can be pulled BEFORE stopping the old container
1105+
let new_image = format!("{}:{}", new_redis_config.image, new_redis_config.version);
1106+
info!("Verifying new Docker image is available: {}", new_image);
1107+
self.verify_image_pullable(&new_image).await?;
1108+
info!("New Docker image verified and is available");
1109+
1110+
// Stop the old container
1111+
info!("Stopping old Redis container");
1112+
self.stop().await?;
1113+
1114+
// Create container with new image (keeping the same volume for data persistence)
1115+
info!("Starting Redis container with new image");
1116+
self.create_container(&self.docker, &new_redis_config, &new_redis_config.password)
1117+
.await?;
1118+
1119+
info!("Redis upgrade completed successfully");
1120+
Ok(())
1121+
}
10541122
}
10551123

10561124
#[cfg(test)]

0 commit comments

Comments
 (0)