Skip to content

Commit 701b08e

Browse files
committed
more fixes
1 parent 8cf2b0a commit 701b08e

File tree

3 files changed

+206
-2
lines changed

3 files changed

+206
-2
lines changed

.github/workflows/ci.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,80 @@ jobs:
8989
flags: app,ui
9090
fail_ci_if_error: false
9191

92+
compose-trigger-log-smoke:
93+
name: Compose Trigger Log Smoke
94+
runs-on: ubuntu-latest
95+
permissions:
96+
contents: read
97+
98+
steps:
99+
- name: Checkout
100+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
101+
102+
- name: Start labeled workload container
103+
run: |
104+
docker run -d \
105+
--name dd-compose-label-test \
106+
--label dd.watch=true \
107+
--label dd.compose.file=/tmp/ci-stack/docker-compose.yml \
108+
--label dd.compose.auto=true \
109+
--label dd.compose.prune=false \
110+
alpine:3.21 sleep 600
111+
112+
- name: Build drydock image
113+
run: docker build -t drydock:ci-trigger --build-arg DD_VERSION=ci .
114+
115+
- name: Start drydock
116+
run: |
117+
docker run -d \
118+
--name drydock-ci-trigger \
119+
--volume /var/run/docker.sock:/var/run/docker.sock \
120+
--env DD_WATCHER_LOCAL_WATCHBYDEFAULT=true \
121+
--env DD_WATCHER_LOCAL_WATCHATSTART=true \
122+
--env DD_WATCHER_LOCAL_WATCHEVENTS=true \
123+
drydock:ci-trigger
124+
125+
- name: Assert compose trigger creation from labels
126+
run: |
127+
found=0
128+
for i in $(seq 1 60); do
129+
logs="$(docker logs drydock-ci-trigger 2>&1 || true)"
130+
131+
if echo "$logs" | grep -Eq 'trigger\.dockercompose\..*dd-compose-label-test'; then
132+
found=1
133+
echo "Found dockercompose trigger registration log"
134+
break
135+
fi
136+
sleep 2
137+
done
138+
139+
if [ "$found" -ne 1 ]; then
140+
echo "Did not find dockercompose trigger creation log for dd-compose-label-test"
141+
docker logs drydock-ci-trigger 2>&1 || true
142+
exit 1
143+
fi
144+
145+
logs="$(docker logs drydock-ci-trigger 2>&1 || true)"
146+
if ! echo "$logs" | grep -Eq '"auto":("true"|true)'; then
147+
echo "Did not find compose trigger auto configuration in logs"
148+
docker logs drydock-ci-trigger 2>&1 || true
149+
exit 1
150+
fi
151+
if ! echo "$logs" | grep -Eq '"prune":("false"|false)'; then
152+
echo "Did not find compose trigger prune configuration in logs"
153+
docker logs drydock-ci-trigger 2>&1 || true
154+
exit 1
155+
fi
156+
157+
- name: Dump drydock logs on failure
158+
if: failure()
159+
run: docker logs drydock-ci-trigger 2>&1 || true
160+
161+
- name: Cleanup containers
162+
if: always()
163+
run: |
164+
docker rm -f drydock-ci-trigger dd-compose-label-test >/dev/null 2>&1 || true
165+
92166
build:
93167
name: Build
94168
runs-on: ubuntu-latest

app/watchers/providers/docker/Docker.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,84 @@ describe('Docker Watcher', () => {
12261226
expect(storeContainer.updateContainer).toHaveBeenCalledWith(existingContainer);
12271227
});
12281228

1229+
test('should create and attach dockercompose trigger when dd.compose.file appears in inspect labels', async () => {
1230+
await docker.register('watcher', 'docker', 'test', {});
1231+
docker.log = createMockLogWithChild(['info', 'warn']);
1232+
mockContainer.inspect.mockResolvedValue({
1233+
Name: '/compose-container',
1234+
State: { Status: 'running' },
1235+
Config: {
1236+
Labels: {
1237+
'dd.compose.file': '/tmp/my-stack/docker-compose.yml',
1238+
'dd.compose.backup': 'true',
1239+
'dd.compose.prune': 'false',
1240+
'dd.compose.auto': 'true',
1241+
'dd.compose.threshold': 'minor',
1242+
},
1243+
},
1244+
});
1245+
const existingContainer = {
1246+
id: 'container123',
1247+
name: 'compose-container',
1248+
displayName: 'compose-container',
1249+
status: 'running',
1250+
image: { name: 'library/nginx' },
1251+
labels: {},
1252+
};
1253+
storeContainer.getContainer.mockReturnValue(existingContainer);
1254+
1255+
await docker.onDockerEvent(Buffer.from('{"Action":"start","id":"container123"}\n'));
1256+
1257+
expect(registry.ensureDockercomposeTriggerForContainer).toHaveBeenCalledWith(
1258+
'compose-container',
1259+
'/tmp/my-stack/docker-compose.yml',
1260+
{
1261+
backup: 'true',
1262+
prune: 'false',
1263+
auto: 'true',
1264+
threshold: 'minor',
1265+
},
1266+
);
1267+
expect(existingContainer.triggerInclude).toBe('dockercompose.my-stack-compose-container');
1268+
expect(storeContainer.updateContainer).toHaveBeenCalledWith(existingContainer);
1269+
});
1270+
1271+
test('should detach dockercompose trigger when compose label is removed in inspect labels', async () => {
1272+
await docker.register('watcher', 'docker', 'test', {});
1273+
docker.log = createMockLogWithChild(['info', 'warn']);
1274+
mockContainer.inspect.mockResolvedValue({
1275+
Name: '/compose-container',
1276+
State: { Status: 'running' },
1277+
Config: {
1278+
Labels: {
1279+
foo: 'bar',
1280+
},
1281+
},
1282+
});
1283+
const existingContainer = {
1284+
id: 'container123',
1285+
name: 'compose-container',
1286+
displayName: 'compose-container',
1287+
status: 'running',
1288+
image: { name: 'library/nginx' },
1289+
labels: {
1290+
'dd.compose.file': '/tmp/my-stack/docker-compose.yml',
1291+
},
1292+
triggerInclude: 'ntfy.default:major,dockercompose.my-stack-compose-container',
1293+
};
1294+
docker.composeTriggersByContainer = {
1295+
container123: 'dockercompose.my-stack-compose-container',
1296+
};
1297+
storeContainer.getContainer.mockReturnValue(existingContainer);
1298+
1299+
await docker.onDockerEvent(Buffer.from('{"Action":"start","id":"container123"}\n'));
1300+
1301+
expect(registry.ensureDockercomposeTriggerForContainer).not.toHaveBeenCalled();
1302+
expect(existingContainer.triggerInclude).toBe('ntfy.default:major');
1303+
expect(docker.composeTriggersByContainer.container123).toBeUndefined();
1304+
expect(storeContainer.updateContainer).toHaveBeenCalledWith(existingContainer);
1305+
});
1306+
12291307
test('should skip store update when inspect payload does not change tracked fields', async () => {
12301308
await docker.register('watcher', 'docker', 'test', {});
12311309
docker.log = createMockLogWithChild(['info']);

app/watchers/providers/docker/Docker.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ function appendTriggerId(triggerInclude: string | undefined, triggerId: string |
182182
return `${triggerInclude},${triggerId}`;
183183
}
184184

185+
function removeTriggerId(triggerInclude: string | undefined, triggerId: string | undefined) {
186+
if (!triggerInclude || !triggerId) {
187+
return triggerInclude;
188+
}
189+
const triggersIncluded = triggerInclude
190+
.split(/\s*,\s*/)
191+
.map((trigger) => trigger.trim())
192+
.filter((trigger) => trigger !== '' && trigger !== triggerId);
193+
return triggersIncluded.length > 0 ? triggersIncluded.join(',') : undefined;
194+
}
195+
185196
function getDockercomposeTriggerConfigurationFromLabels(labels: Record<string, string>) {
186197
const dockercomposeConfig: Record<string, string> = {};
187198

@@ -1926,7 +1937,7 @@ class Docker extends Watcher {
19261937
const containerFound = storeContainer.getContainer(containerId);
19271938

19281939
if (containerFound) {
1929-
this.updateContainerFromInspect(containerFound, containerInspect);
1940+
await this.updateContainerFromInspect(containerFound, containerInspect);
19301941
}
19311942
} catch (e: any) {
19321943
this.log.debug(
@@ -1935,7 +1946,7 @@ class Docker extends Watcher {
19351946
}
19361947
}
19371948

1938-
private updateContainerFromInspect(containerFound: Container, containerInspect: any) {
1949+
private async updateContainerFromInspect(containerFound: Container, containerInspect: any) {
19391950
const logContainer = this.log.child({
19401951
container: fullName(containerFound),
19411952
});
@@ -1988,6 +1999,47 @@ class Docker extends Watcher {
19881999
changed = true;
19892000
}
19902001

2002+
const containerId = containerFound.id;
2003+
const composeFilePath = getLabel(labelsToApply, ddComposeFile, wudComposeFile);
2004+
if (composeFilePath) {
2005+
let dockercomposeTriggerId = this.composeTriggersByContainer[containerId];
2006+
if (!dockercomposeTriggerId) {
2007+
try {
2008+
dockercomposeTriggerId = await registry.ensureDockercomposeTriggerForContainer(
2009+
newName || oldName,
2010+
composeFilePath,
2011+
getDockercomposeTriggerConfigurationFromLabels(labelsToApply),
2012+
);
2013+
this.composeTriggersByContainer[containerId] = dockercomposeTriggerId;
2014+
} catch (e: any) {
2015+
logContainer.warn(
2016+
`Unable to create dockercompose trigger for ${newName || oldName} (${e.message})`,
2017+
);
2018+
}
2019+
}
2020+
const triggerIncludeUpdated = appendTriggerId(
2021+
containerFound.triggerInclude,
2022+
dockercomposeTriggerId,
2023+
);
2024+
if (triggerIncludeUpdated !== containerFound.triggerInclude) {
2025+
containerFound.triggerInclude = triggerIncludeUpdated;
2026+
changed = true;
2027+
}
2028+
} else {
2029+
const cachedDockercomposeTriggerId = this.composeTriggersByContainer[containerId];
2030+
if (cachedDockercomposeTriggerId) {
2031+
const triggerIncludeUpdated = removeTriggerId(
2032+
containerFound.triggerInclude,
2033+
cachedDockercomposeTriggerId,
2034+
);
2035+
if (triggerIncludeUpdated !== containerFound.triggerInclude) {
2036+
containerFound.triggerInclude = triggerIncludeUpdated;
2037+
changed = true;
2038+
}
2039+
delete this.composeTriggersByContainer[containerId];
2040+
}
2041+
}
2042+
19912043
if (changed) {
19922044
storeContainer.updateContainer(containerFound);
19932045
}

0 commit comments

Comments
 (0)