Skip to content

Commit d2b2df9

Browse files
Auto seed Keycloak from CTST and introduce coordinate
1 parent 68b05cd commit d2b2df9

File tree

8 files changed

+1957
-617
lines changed

8 files changed

+1957
-617
lines changed

.github/scripts/end2end/run-e2e-ctst.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ kubectl run $POD_NAME \
5454
--rm \
5555
--attach=True \
5656
--image-pull-policy=IfNotPresent \
57-
--env=TARGET_VERSION=$VERSION \
58-
--env=AZURE_BLOB_URL=$AZURE_BACKEND_ENDPOINT \
57+
--env=TARGET_VERSION=$VERSION \
58+
--env=SEED_KEYCLOAK_DEFAULT_ROLES=true \
59+
--env=AZURE_BLOB_URL=$AZURE_BACKEND_ENDPOINT \
5960
--env=AZURE_QUEUE_URL=$AZURE_BACKEND_QUEUE_ENDPOINT \
6061
--env=VERBOSE=1 \
6162
--override-type strategic \

.github/workflows/end2end.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,12 @@ jobs:
393393
GIT_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }}
394394
- uses: actions/setup-node@v4
395395
with:
396-
node-version: '20'
396+
node-version: '22'
397397
cache: yarn
398398
cache-dependency-path: tests/ctst/yarn.lock
399399
- name: Install ctst test dependencies
400400
working-directory: tests/ctst
401-
run: yarn install
401+
run: yarn install --network-concurrency=1
402402
- name: Lint ctst tests
403403
working-directory: tests/ctst
404404
run: yarn lint
@@ -421,6 +421,9 @@ jobs:
421421
SORBET_TAG=$(yq eval '.sorbet.tag' deps.yaml)
422422
DRCTL_TAG=$(yq eval .drctl.tag deps.yaml)
423423
EOF
424+
- name: Prepare build context
425+
run: cp .github/scripts/mocks/aws/mock-metadata.tar.gz tests/ctst/mock-metadata.tar.gz
426+
424427
- name: Build and push CI image
425428
uses: docker/build-push-action@v5
426429
with:
@@ -430,6 +433,7 @@ jobs:
430433
CTST_TAG=${{ env.CTST_TAG }}
431434
SORBET_TAG=${{ env.SORBET_TAG }}
432435
DRCTL_TAG=${{ env.DRCTL_TAG}}
436+
GIT_ACCESS_TOKEN=${{ steps.app-token.outputs.token }}
433437
tags: "${{ env.E2E_CTST_IMAGE_NAME }}:${{ env.E2E_IMAGE_TAG }}"
434438
cache-from: type=gha,scope=end2end-ctst
435439
cache-to: type=gha,mode=max,scope=end2end-ctst

tests/ctst/Dockerfile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
ARG SORBET_TAG=latest
22
ARG CTST_TAG=latest
33
ARG DRCTL_TAG=latest
4+
ARG GIT_ACCESS_TOKEN=
45

56
FROM ghcr.io/scality/sorbet:$SORBET_TAG AS sorbet
67
FROM ghcr.io/scality/zenko-drctl:$DRCTL_TAG AS drctl
@@ -9,7 +10,12 @@ FROM ghcr.io/scality/cli-testing:$CTST_TAG
910
COPY package.json /tmp/package.json
1011

1112
USER root
12-
RUN npm install typescript@5.8.3 -g
13+
RUN npm install typescript@5.9.2 -g
14+
15+
# Configure git to use the access token for private repositories
16+
RUN git config --global url.https://x-access-token:${GIT_ACCESS_TOKEN}@github.com/.insteadOf https://github.com/ && \
17+
git config --global url.https://x-access-token:${GIT_ACCESS_TOKEN}@github.com/.insteadOf github.com: && \
18+
git config --global url.https://x-access-token:${GIT_ACCESS_TOKEN}@github.com/.insteadOf ssh://git@github.com/
1319

1420
# CTST does it if needed, but better to merge dependencies
1521
# here so we benefit from Docker layer caching

tests/ctst/common/utils.ts

Lines changed: 50 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { exec } from 'child_process';
22
import http from 'http';
33
import { createHash } from 'crypto';
4-
import { Command, IAM, Identity, IdentityEnum } from 'cli-testing';
4+
import { Command, coordinate, IAM, Identity, IdentityEnum } from 'cli-testing';
55
import {
66
AttachedPolicy,
77
Group,
@@ -12,7 +12,6 @@ import {
1212
import { AWSCliOptions } from 'cli-testing';
1313
import Zenko from 'world/Zenko';
1414
import fs from 'fs';
15-
import lockFile from 'proper-lockfile';
1615
import { ITestCaseHookParameter } from '@cucumber/cucumber';
1716
import { AWSCredentials, Constants, Utils } from 'cli-testing';
1817
import { createBucketWithConfiguration, putObject } from '../steps/utils/utils';
@@ -323,83 +322,64 @@ export async function prepareMetricsScenarios(
323322
const { gherkinDocument, pickle } = scenarioConfiguration;
324323
const featureName = gherkinDocument.feature?.name?.replace(/ /g, '-').toLowerCase() || 'metrics';
325324
const filePath = `/tmp/${featureName}`;
326-
let initiated = false;
327-
let releaseLock: (() => Promise<void>) | false = false;
328-
const output: Record<string, AWSCredentials> = {};
329325

330326
const {
331327
versioning = '',
332328
jobName = 'end2end-ops-count-items',
333329
jobNamespace = `${featureName}-setup`
334330
} = options;
335331

336-
if (!fs.existsSync(filePath)) {
337-
fs.writeFileSync(filePath, JSON.stringify({
338-
ready: false,
339-
}));
340-
} else {
341-
initiated = true;
342-
}
343-
344-
if (!initiated) {
345-
try {
346-
releaseLock = await lockFile.lock(filePath, { stale: Constants.DEFAULT_TIMEOUT / 2 });
347-
} catch (err) {
348-
world.logger.error('Unable to acquire lock', { err });
349-
releaseLock = false;
350-
}
351-
}
352-
353-
if (releaseLock) {
354-
const scenarioIds = new Set<string>();
355-
356-
for (const scenario of gherkinDocument.feature?.children || []) {
357-
for (const example of scenario.scenario?.examples || []) {
358-
for (const values of example.tableBody || []) {
359-
const scenarioWithExampleID = hashStringAndKeepFirst20Characters(`${values.id}`);
360-
scenarioIds.add(scenarioWithExampleID);
332+
await coordinate(
333+
{
334+
lockName: featureName,
335+
timeout: Constants.DEFAULT_TIMEOUT,
336+
logger: world.logger,
337+
},
338+
// Work function: executed exactly once
339+
async () => {
340+
const output: Record<string, AWSCredentials> = {};
341+
const scenarioIds = new Set<string>();
342+
343+
for (const scenario of gherkinDocument.feature?.children || []) {
344+
for (const example of scenario.scenario?.examples || []) {
345+
for (const values of example.tableBody || []) {
346+
const scenarioWithExampleID = hashStringAndKeepFirst20Characters(`${values.id}`);
347+
scenarioIds.add(scenarioWithExampleID);
348+
}
361349
}
362350
}
363-
}
364-
365-
for (const scenarioId of scenarioIds) {
366-
await world.createAccount(scenarioId, true);
367-
await createBucketWithConfiguration(world, scenarioId, versioning);
368-
await putObject(world);
369-
output[scenarioId] = Identity.getCurrentCredentials()!;
370-
}
371-
372-
await createJobAndWaitForCompletion(world, jobName, jobNamespace);
373-
374-
await Utils.sleep(2000);
375-
fs.writeFileSync(filePath, JSON.stringify({
376-
ready: true,
377-
...output,
378-
}));
379-
380-
await releaseLock();
381-
} else {
382-
while (!fs.existsSync(filePath)) {
383-
await Utils.sleep(100);
384-
}
351+
352+
for (const scenarioId of scenarioIds) {
353+
await world.createAccount(scenarioId, true);
354+
await createBucketWithConfiguration(world, scenarioId, versioning);
355+
await putObject(world);
356+
output[scenarioId] = Identity.getCurrentCredentials()!;
357+
}
385358

386-
let configuration: { ready: boolean } = JSON.parse(fs.readFileSync(filePath, 'utf8')) as { ready: boolean };
387-
while (!configuration.ready) {
388-
await Utils.sleep(100);
389-
configuration = JSON.parse(fs.readFileSync(filePath, 'utf8')) as { ready: boolean };
359+
await createJobAndWaitForCompletion(world, jobName, jobNamespace);
360+
361+
await Utils.sleep(2000);
362+
363+
// Store output in a temporary file for other workers to read
364+
fs.writeFileSync(filePath, JSON.stringify({
365+
ready: true,
366+
...output,
367+
}));
368+
},
369+
// Post-processing: all workers read the configuration
370+
async () => {
371+
const configuration: Record<string, AWSCredentials> = JSON.parse(fs.readFileSync(filePath, 'utf8'));
372+
const key = hashStringAndKeepFirst20Characters(`${pickle.astNodeIds[1]}`);
373+
world.logger.debug('Scenario key', { key, from: `${pickle.astNodeIds[1]}`, configuration });
374+
375+
world.addToSaved('bucketName', key);
376+
world.addToSaved('accountName', key);
377+
world.addToSaved('accountNameForScenario', key);
378+
world.addToSaved('metricsEnvironmentSetup', true);
379+
380+
if (configuration[key]) {
381+
Identity.addIdentity(IdentityEnum.ACCOUNT, key, configuration[key], undefined, true, true);
382+
}
390383
}
391-
}
392-
393-
const configuration: typeof output = JSON.parse(fs.readFileSync(filePath, 'utf8')) as typeof output;
394-
const key = hashStringAndKeepFirst20Characters(`${pickle.astNodeIds[1]}`);
395-
world.logger.debug('Scenario key', { key, from: `${pickle.astNodeIds[1]}`, configuration });
396-
397-
world.addToSaved('bucketName', key);
398-
world.addToSaved('accountName', key);
399-
world.addToSaved('accountNameForScenario', key);
400-
world.addToSaved('metricsEnvironmentSetup', true);
401-
402-
if (configuration[key]) {
403-
Identity.addIdentity(IdentityEnum.ACCOUNT, key, configuration[key], undefined, true, true);
404-
}
384+
);
405385
}

tests/ctst/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@
99
"private": true,
1010
"dependencies": {
1111
"@kubernetes/client-node": "^0.21.0",
12+
"@types/node": "^18.19.121",
1213
"@types/proper-lockfile": "^4.1.4",
1314
"@types/qs": "^6.9.15",
1415
"assert": "^2.1.0",
1516
"aws4-axios": "^3.3.8",
1617
"kafkajs": "^2.2.4",
1718
"node-gyp": "^10.2.0",
1819
"prometheus-query": "^3.4.0",
19-
"proper-lockfile": "^4.1.2",
2020
"qs": "^6.13.0",
21-
"scubaclient": "git+https://github.com/scality/scubaclient#^1.1.2",
21+
"scubaclient": "git+https://github.com/scality/scubaclient#^1.1.3",
2222
"werelogs": "scality/werelogs#8.2.2"
2323
},
2424
"devDependencies": {
2525
"@aws-sdk/client-iam": "^3.582.0",
2626
"@aws-sdk/client-s3": "^3.583.0",
2727
"@aws-sdk/client-sts": "^3.583.0",
2828
"@eslint/compat": "^1.1.1",
29-
"cli-testing": "github:scality/cli-testing.git#1.2.4",
29+
"cli-testing": "github:scality/cli-testing.git#827cd4683b55b8b7222ed3f5672ee5d826d3685f",
3030
"eslint": "^9.9.1",
3131
"eslint-config-scality": "scality/Guidelines#8.3.0",
3232
"typescript-eslint": "^8.4.0"

0 commit comments

Comments
 (0)