Skip to content

Commit 6590610

Browse files
authored
fix(backend): update only 'running' containers (#80)
+ added skip of not 'running' containers to avoid potential errors + added hints to the ui + edited readme
1 parent ebe90da commit 6590610

File tree

7 files changed

+88
-35
lines changed

7 files changed

+88
-35
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ Automatic updates are disabled by default. You can choose only what you need.
129129

130130
- **Image pull** performed for containers marked for **check**;
131131
- If there is a **new image** for any group's container and it is **marked for auto-update**, the update process begins;
132+
* [protected](#custom-labels) containers will be skipped
133+
* not `running` containers will be skipped
132134
- After that, all containers in the group are stopped in **order from most dependent**;
133135
- Then, **in reverse order** (from most dependable):
134136
- Updatable containers being recreated and started;
@@ -253,4 +255,3 @@ See [/backend/README.md](/backend/README.md) and [/frontend/README.md](/frontend
253255
- Dozzle integration or something more universal (list of urls for redirects?)
254256
- Swarm support?
255257
- Try to add release notes (from labels or something)
256-
- Do not update stopped containers

backend/core/container/util/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@
2828
)
2929
from .get_container_entrypoint_str import get_container_entrypoint_str
3030
from .is_protected_container import is_protected_container
31+
from .is_running_container import is_running_container
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from python_on_whales.components.container.models import (
2+
ContainerInspectResult,
3+
)
4+
5+
6+
def is_running_container(container: ContainerInspectResult) -> bool:
7+
return bool(
8+
container.state and container.state.status == "running"
9+
)

backend/core/containers_core.py

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
get_container_config,
2727
merge_container_config_with_image,
2828
update_containers_data_after_check,
29+
is_running_container,
2930
)
3031
from backend.core.container import (
3132
GroupCheckProgressCache,
@@ -170,8 +171,23 @@ def _will_update(gc: ContainerGroupItem) -> bool:
170171
and gc.new_image
171172
and gc.action == "update"
172173
and not gc.protected
174+
and is_running_container(gc.container)
173175
)
174176

177+
def _will_skip(gc: ContainerGroupItem) -> bool:
178+
"""Whether to skip container"""
179+
if gc.protected:
180+
logging.info(
181+
f"Container {gc.name} labeled with {TUGTAINER_PROTECTED_LABEL}, skipping."
182+
)
183+
return True
184+
elif not is_running_container(gc.container):
185+
logging.info(
186+
f"Container {gc.name} is not running, skipping."
187+
)
188+
return True
189+
return False
190+
175191
def _group_state_to_result(
176192
group: ContainerGroup,
177193
) -> GroupCheckResult:
@@ -223,7 +239,7 @@ async def _run_commands(commands: list[list[str]]):
223239
result = _group_state_to_result(group)
224240
await update_containers_data_after_check(result)
225241
logging.info(
226-
f"""Group check completed.
242+
f"""Group check completed.
227243
================================================================="""
228244
)
229245
CACHE.update({"status": ECheckStatus.DONE, "result": result})
@@ -232,20 +248,11 @@ async def _run_commands(commands: list[list[str]]):
232248
logging.info("Starting to update a group...")
233249
CACHE.update({"status": ECheckStatus.UPDATING})
234250

235-
protected_containers = [
236-
gc for gc in group.containers if gc.protected
237-
]
238-
for gc in protected_containers:
239-
logging.info(
240-
f"Container {gc.name} labeled with {TUGTAINER_PROTECTED_LABEL} and will be skipped."
241-
)
242-
243-
# Starting from most dependent
251+
# Getting containers configs and stopping them,
252+
# from most dependent to most dependable.
244253
for gc in group.containers[::-1]:
245-
# Skipping protected containers
246-
if gc.protected:
254+
if _will_skip(gc):
247255
continue
248-
# Getting configs for all containers
249256
try:
250257
logging.info(
251258
f"Getting config for container {gc.container.name}..."
@@ -261,7 +268,6 @@ async def _run_commands(commands: list[list[str]]):
261268
================================================================="""
262269
)
263270
return await _on_stop_fail()
264-
# Stopping all containers
265271
try:
266272
logging.info(f"Stopping container {gc.container.name}...")
267273
await client.container.stop(gc.name)
@@ -273,17 +279,15 @@ async def _run_commands(commands: list[list[str]]):
273279
)
274280
return await _on_stop_fail()
275281

276-
# Starting from most dependable.
277-
# At that moment all containers should be stopped.
278-
# Will update/start them in dependency order
282+
# Updating and/or starting containers,
283+
# from most dependable to most dependent.
279284

280285
# Indicates was there an exception during the update
281286
# If True, the following updates will not be processed.
282287
any_failed: bool = False
283288

284289
for gc in group.containers:
285-
# Skipping protected containers
286-
if gc.protected:
290+
if _will_skip(gc):
287291
continue
288292
c_name = gc.name
289293
# Updating container
@@ -354,9 +358,13 @@ async def _run_commands(commands: list[list[str]]):
354358
if await wait_for_container_healthy(
355359
client, rolled_back, host.container_hc_timeout
356360
):
357-
logging.warning("Container is healthy!")
361+
logging.warning(
362+
"Container is healthy after rolling back!"
363+
)
358364
continue
359-
logging.warning("Container is unhealthy!")
365+
logging.warning(
366+
"Container is unhealthy after rolling back!"
367+
)
360368
# Failed to roll back
361369
except Exception as e:
362370
logging.exception(e)
@@ -475,7 +483,9 @@ async def check_all(update: bool):
475483
Function performs checks in separate threads for each host.
476484
Should not raises errors, only logging.
477485
"""
478-
CACHE = ProcessCache[AllCheckProgressCache](ALL_CONTAINERS_STATUS_KEY)
486+
CACHE = ProcessCache[AllCheckProgressCache](
487+
ALL_CONTAINERS_STATUS_KEY
488+
)
479489
try:
480490
STATUS = CACHE.get()
481491
if STATUS and STATUS.get("status") not in _ALLOW_STATUSES:

frontend/public/i18n/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@
122122
"CHECKED_AT": "Last check",
123123
"UPDATED_AT": "Last update",
124124
"UPDATE": "Update",
125+
"UPDATE_DISABLED_HINTS": {
126+
"PROTECTED": "The protected container cannot be updated from the app.\n",
127+
"NOT_RUNNING": "The container must be in running state.\n"
128+
},
125129
"CHECK": "Check",
126130
"MAIN_BTN_HINT": "If a container is a part of a compose project, all project's containers will be involved in the process.",
127131
"PROTECTED": "Protected",
Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1+
@let _loading = loading();
2+
13
<p-button-group>
24
<p-button
3-
[loading]="loading() == 'check'"
4-
[disabled]="loading() == 'update'"
5+
[loading]="_loading == 'check'"
6+
[disabled]="_loading == 'update'"
57
[label]="'CONTAINERS.CHECK' | translate"
68
(onClick)="check(false)"
79
></p-button>
8-
@if (!item()?.protected) {
9-
<p-button
10-
[loading]="loading() == 'update'"
11-
[disabled]="!item()?.update_available || loading() == 'check'"
12-
[label]="'CONTAINERS.UPDATE' | translate"
13-
severity="success"
14-
(onClick)="check(true)"
15-
></p-button>
16-
}
10+
<p-button
11+
[loading]="_loading == 'update'"
12+
[disabled]="_loading == 'check' || cantUpdate()"
13+
[pTooltip]="updateButtonTooltip"
14+
[tooltipDisabled]="!showUpdateTooltip()"
15+
[label]="'CONTAINERS.UPDATE' | translate"
16+
severity="success"
17+
(onClick)="check(true)"
18+
></p-button>
1719
</p-button-group>
20+
21+
<ng-template #updateButtonTooltip>
22+
@let _item = item();
23+
@if (_item.status != 'running') {
24+
{{ 'CONTAINERS.UPDATE_DISABLED_HINTS.NOT_RUNNING' | translate }}
25+
}
26+
@if (_item.protected) {
27+
{{ 'CONTAINERS.UPDATE_DISABLED_HINTS.PROTECTED' | translate }}
28+
}
29+
</ng-template>

frontend/src/app/shared/components/container-actions/container-actions.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ChangeDetectionStrategy,
33
Component,
4+
computed,
45
DestroyRef,
56
inject,
67
input,
@@ -11,6 +12,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
1112
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
1213
import { ButtonModule } from 'primeng/button';
1314
import { ButtonGroupModule } from 'primeng/buttongroup';
15+
import { TooltipModule } from 'primeng/tooltip';
1416
import { finalize } from 'rxjs';
1517
import { ToastService } from 'src/app/core/services/toast.service';
1618
import { ContainersApiService } from 'src/app/entities/containers/containers-api.service';
@@ -22,7 +24,7 @@ import { IGroupCheckProgressCache } from 'src/app/entities/progress-cache/progre
2224
*/
2325
@Component({
2426
selector: 'app-container-actions',
25-
imports: [ButtonGroupModule, ButtonModule, TranslatePipe],
27+
imports: [ButtonGroupModule, ButtonModule, TranslatePipe, TooltipModule],
2628
templateUrl: './container-actions.html',
2729
styleUrl: './container-actions.scss',
2830
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -49,6 +51,20 @@ export class ContainerActions {
4951
* Loading flag
5052
*/
5153
protected readonly loading = signal<'check' | 'update'>(null);
54+
/**
55+
* Container cannot be updated
56+
*/
57+
protected readonly cantUpdate = computed<boolean>(() => {
58+
const item = this.item();
59+
return !item.update_available || item.status != 'running' || item.protected;
60+
});
61+
/**
62+
* Whether to show update button tooltip
63+
*/
64+
protected readonly showUpdateTooltip = computed<boolean>(() => {
65+
const item = this.item();
66+
return item.update_available && (item.status != 'running' || item.protected);
67+
});
5268

5369
/**
5470
* Run check/update process

0 commit comments

Comments
 (0)