diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7ac4eccc6e..959be1b111 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,7 @@ "features": { "ghcr.io/devcontainers/features/docker-in-docker": {}, "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/node:1": { "version": "22" }, + "ghcr.io/devcontainers/features/node:1": { "version": "24" }, "ghcr.io/devcontainers/features/python:1": {}, "ghcr.io/devcontainers/features/sshd:1": {}, "ghcr.io/devcontainers-extra/features/kind:1": {}, diff --git a/.github/actions/setup-node-env/action.yaml b/.github/actions/setup-node-env/action.yaml index 9aadf6b842..546f967760 100644 --- a/.github/actions/setup-node-env/action.yaml +++ b/.github/actions/setup-node-env/action.yaml @@ -37,6 +37,8 @@ runs: git config --global url."https://x-access-token:${GIT_ACCESS_TOKEN}@github.com/".insteadOf "https://github.com/" git config --global --add url."https://x-access-token:${GIT_ACCESS_TOKEN}@github.com/".insteadOf "git@github.com:" git config --global --add url."https://x-access-token:${GIT_ACCESS_TOKEN}@github.com/".insteadOf "ssh://git@github.com/" + echo "machine github.com login x-access-token password ${GIT_ACCESS_TOKEN}" >> ~/.netrc + chmod 600 ~/.netrc env: GIT_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }} - name: Setup Node.js diff --git a/.github/scripts/end2end/configure-e2e-endpoints.sh b/.github/scripts/end2end/configure-e2e-endpoints.sh index 9013418c5e..5fd2eb9aef 100755 --- a/.github/scripts/end2end/configure-e2e-endpoints.sh +++ b/.github/scripts/end2end/configure-e2e-endpoints.sh @@ -131,21 +131,4 @@ if ! grep -q "backbeat-api.zenko.local" /etc/hosts 2>/dev/null; then echo "127.0.0.1 ${ZENKO_HOSTS}" | sudo tee -a /etc/hosts fi -# --- Export endpoint variables --- -# These use the ingress hostnames, reachable from outside the cluster. - -export CLOUDSERVER_HOST="s3.zenko.local" -export CLOUDSERVER_ENDPOINT="http://s3.zenko.local" -export BACKBEAT_API_ENDPOINT="http://backbeat-api.zenko.local" -export VAULT_ENDPOINT="http://iam.zenko.local" -export VAULT_STS_ENDPOINT="http://sts.zenko.local" -export VAULT_AUTH_HOST="vault-auth.zenko.local" -export KAFKA_CONNECT_URL="http://kafka-connect.zenko.local/connectors" - echo "=== Endpoints configured for out-of-cluster access ===" -echo " S3: ${CLOUDSERVER_ENDPOINT}" -echo " Backbeat API: ${BACKBEAT_API_ENDPOINT}" -echo " Vault IAM: ${VAULT_ENDPOINT}" -echo " Vault STS: ${VAULT_STS_ENDPOINT}" -echo " Vault Auth: http://${VAULT_AUTH_HOST}" -echo " Kafka Connect: ${KAFKA_CONNECT_URL}" diff --git a/.github/scripts/end2end/setup-e2e-env.sh b/.github/scripts/end2end/setup-e2e-env.sh index 6867959d71..3261b6b19f 100755 --- a/.github/scripts/end2end/setup-e2e-env.sh +++ b/.github/scripts/end2end/setup-e2e-env.sh @@ -48,9 +48,6 @@ export MONGO_REPLICA_SET_HOSTS="localhost:${MONGO_PORT}" # --- 5. Credentials from K8s secrets --- export ADMIN_ACCESS_KEY_ID=$(kubectl get secret ${ZENKO_NAME}-management-vault-admin-creds.v1 -o jsonpath='{.data.accessKey}' | base64 -d) export ADMIN_SECRET_ACCESS_KEY=$(kubectl get secret ${ZENKO_NAME}-management-vault-admin-creds.v1 -o jsonpath='{.data.secretKey}' | base64 -d) -export ZENKO_ACCESS_KEY=$(kubectl get secret ${ZENKO_NAME}-account-zenko -o jsonpath='{.data.AccessKeyId}' | base64 -d) -export ZENKO_SECRET_KEY=$(kubectl get secret ${ZENKO_NAME}-account-zenko -o jsonpath='{.data.SecretAccessKey}' | base64 -d) -export ZENKO_SESSION_TOKEN=$(kubectl get secret ${ZENKO_NAME}-account-zenko -o jsonpath='{.data.SessionToken}' | base64 -d) # CRR account credentials _src_secret="${ZENKO_NAME}-account-${CRR_SOURCE_ACCOUNT_NAME:-crr-source-account}" @@ -72,11 +69,8 @@ export CRR_DESTINATION_INFO="{\"AccessKeyId\":\"${DESTINATION_ACCESS_KEY}\",\"Se export KEYCLOAK_TEST_USER="${OIDC_USERNAME}-norights" export KEYCLOAK_TEST_PASSWORD=${OIDC_PASSWORD} export KEYCLOAK_TEST_HOST=${OIDC_ENDPOINT} -export KEYCLOAK_TEST_PORT="80" export KEYCLOAK_TEST_REALM_NAME=${OIDC_REALM} export KEYCLOAK_REALM=${OIDC_REALM} # cli-testing KeycloakSetup hook reads KEYCLOAK_REALM from env -export KEYCLOAK_TEST_CLIENT_ID=${OIDC_CLIENT_ID} -export KEYCLOAK_TEST_GRANT_TYPE="password" # --- 7. Test backend env vars --- export AWS_BACKEND_SOURCE_LOCATION AWS_BACKEND_DESTINATION_LOCATION @@ -131,40 +125,8 @@ export TARGET_VERSION=$(sed -n 's/^VERSION="\([^"]*\)"/\1/p' "${ZENKO_ROOT}/VERS if [ "${SKIP_CTST:-}" = "1" ]; then echo "SKIP_CTST=1 set, skipping CTST-specific setup" else - # CTST account & user names - export ZENKO_ACCOUNT_NAME="zenko-ctst" - export STORAGE_MANAGER_USER_NAME="storage_manager" - export STORAGE_ACCOUNT_OWNER_USER_NAME="storage_account_owner" - export DATA_CONSUMER_USER_NAME="data_consumer" - export DATA_ACCESSOR_USER_NAME="data_accessor" - # env vars used by cli-testing's Keycloak.ts seeder - export ACCOUNT="${ZENKO_ACCOUNT_NAME}" - export STORAGE_MANAGER="${STORAGE_MANAGER_USER_NAME}" - export STORAGE_ACCOUNT_OWNER="${STORAGE_ACCOUNT_OWNER_USER_NAME}" - export DATA_CONSUMER="${DATA_CONSUMER_USER_NAME}" - export DATA_ACCESSOR="${DATA_ACCESSOR_USER_NAME}" + export ACCOUNT="zenko-ctst" # cli-testing Keycloak seeder (fallback would be wrong 'AccountTest') export SEED_KEYCLOAK_DEFAULT_ROLES=true - export ZENKO_PORT="80" - - # PRA admin credentials (may not exist for non-PRA runs; ignore errors) - export ADMIN_PRA_ACCESS_KEY_ID=$(kubectl get secret ${ZENKO_NAME}-pra-management-vault-admin-creds.v1 -o jsonpath='{.data.accessKey}' 2>/dev/null | base64 -d 2>/dev/null || echo "") - export ADMIN_PRA_SECRET_ACCESS_KEY=$(kubectl get secret ${ZENKO_NAME}-pra-management-vault-admin-creds.v1 -o jsonpath='{.data.secretKey}' 2>/dev/null | base64 -d 2>/dev/null || echo "") - - # --- 11. Service user credentials --- - BACKBEAT_LCBP_1_CREDS=$(kubectl get secret -l app.kubernetes.io/name=backbeat-lcbp-user-creds,app.kubernetes.io/instance=${ZENKO_NAME} -o jsonpath='{.items[0].data.backbeat-lifecycle-bp-1\.json}' | base64 -d) - BACKBEAT_LCC_1_CREDS=$(kubectl get secret -l app.kubernetes.io/name=backbeat-lcc-user-creds,app.kubernetes.io/instance=${ZENKO_NAME} -o jsonpath='{.items[0].data.backbeat-lifecycle-conductor-1\.json}' | base64 -d) - BACKBEAT_LCOP_1_CREDS=$(kubectl get secret -l app.kubernetes.io/name=backbeat-lcop-user-creds,app.kubernetes.io/instance=${ZENKO_NAME} -o jsonpath='{.items[0].data.backbeat-lifecycle-op-1\.json}' | base64 -d) - BACKBEAT_QP_1_CREDS=$(kubectl get secret -l app.kubernetes.io/name=backbeat-qp-user-creds,app.kubernetes.io/instance=${ZENKO_NAME} -o jsonpath='{.items[0].data.backbeat-qp-1\.json}' | base64 -d) - SORBET_FWD_2_ACCESSKEY=$(kubectl get secret -l app.kubernetes.io/name=sorbet-fwd-creds,app.kubernetes.io/instance=${ZENKO_NAME} -o jsonpath='{.items[0].data.accessKey}' | base64 -d) - SORBET_FWD_2_SECRETKEY=$(kubectl get secret -l app.kubernetes.io/name=sorbet-fwd-creds,app.kubernetes.io/instance=${ZENKO_NAME} -o jsonpath='{.items[0].data.secretKey}' | base64 -d) - export SERVICE_USERS_CREDENTIALS=$(echo '{"backbeat-lifecycle-bp-1":'"${BACKBEAT_LCBP_1_CREDS}"',"backbeat-lifecycle-conductor-1":'"${BACKBEAT_LCC_1_CREDS}"',"backbeat-lifecycle-op-1":'"${BACKBEAT_LCOP_1_CREDS}"',"backbeat-qp-1":'"${BACKBEAT_QP_1_CREDS}"',"sorbet-fwd-2":{"accessKey":"'"${SORBET_FWD_2_ACCESSKEY}"'","secretKey":"'"${SORBET_FWD_2_SECRETKEY}"'"}}' | jq -R) - - # --- 12. Kafka topics for sorbet --- - SORBET_CONFIG=$(kubectl get secret -l app.kubernetes.io/name=cold-sorbet-config-e2e-azure-archive,app.kubernetes.io/instance=${ZENKO_NAME} \ - -o jsonpath='{.items[0].data.config\.json}' | base64 -di) - export KAFKA_DEAD_LETTER_TOPIC=$(echo "${SORBET_CONFIG}" | jq -r '."kafka-dead-letter-topic"') - export KAFKA_OBJECT_TASK_TOPIC=$(echo "${SORBET_CONFIG}" | jq -r '."kafka-object-task-topic"') - export KAFKA_GC_REQUEST_TOPIC=$(echo "${SORBET_CONFIG}" | jq -r '."kafka-gc-request-topic"') # --- 13. Kafka host from backbeat config + port-forward --- KAFKA_HOST_PORT_ORIG=$(kubectl get secret -l app.kubernetes.io/name=backbeat-config,app.kubernetes.io/instance=${ZENKO_NAME} \ @@ -235,19 +197,7 @@ else fi export PROMETHEUS_SERVICE="${PROMETHEUS_SVC}.${NAMESPACE}.svc.cluster.local" - # --- 14. Zenko CR metadata --- - export TIME_PROGRESSION_FACTOR=$(kubectl get zenko ${ZENKO_NAME} -o jsonpath="{.metadata.annotations.zenko\.io/time-progression-factor}") - export INSTANCE_ID=$(kubectl get zenko ${ZENKO_NAME} -o jsonpath='{.status.instanceID}') - export KAFKA_CLEANER_INTERVAL=$(kubectl get zenko ${ZENKO_NAME} -o jsonpath='{.spec.kafkaCleaner.interval}') - export SORBETD_RESTORE_TIMEOUT=$(kubectl get zenko ${ZENKO_NAME} -o jsonpath='{.spec.sorbet.server.azure.restoreTimeout}') - - # Backbeat API (use ingress — already exported as BACKBEAT_API_ENDPOINT) - export BACKBEAT_API_HOST="backbeat-api.zenko.local" - export BACKBEAT_API_PORT="80" - - # Utilization service - export UTILIZATION_SERVICE_HOST=$(kubectl get zenko ${ZENKO_NAME} -o jsonpath='{.spec.scuba.api.ingress.hostname}') - export UTILIZATION_SERVICE_PORT="80" + # --- 14. Zenko CR metadata (now fetched by tests_common/configuration.ts via k8s API) --- # Azure archive settings export AZURE_ARCHIVE_ACCESS_TIER="Hot" @@ -286,11 +236,7 @@ else "subdomain":"${SUBDOMAIN}", "DRSubdomain":"${DR_SUBDOMAIN:-}", "ssl":false, - "port":"${ZENKO_PORT}", - "AccountName":"${ZENKO_ACCOUNT_NAME}", - "AdminAccessKey":"${ADMIN_ACCESS_KEY_ID}", - "AdminSecretKey":"${ADMIN_SECRET_ACCESS_KEY}", - "VaultAuthHost":"${VAULT_AUTH_HOST}", + "port":"80", "NotificationDestination":"${NOTIF_DEST_NAME}", "NotificationDestinationTopic":"${NOTIF_DEST_TOPIC}", "NotificationDestinationAlt":"${NOTIF_ALT_DEST_NAME}", @@ -304,20 +250,11 @@ else "PrometheusEndpoint":"http://localhost:${PROMETHEUS_PORT}", "KafkaHosts":"${KAFKA_HOST_PORT}", "KafkaAuthHosts":"${KAFKA_AUTH_HOST_PORT}", - "KafkaConnectUrl":"${KAFKA_CONNECT_URL}", "KeycloakUsername":"${OIDC_USERNAME}", "KeycloakPassword":"${OIDC_PASSWORD}", "KeycloakTestPassword":"${KEYCLOAK_TEST_PASSWORD}", "KeycloakHost":"${OIDC_HOST}", - "KeycloakPort":"${KEYCLOAK_TEST_PORT}", "KeycloakRealm":"${KEYCLOAK_TEST_REALM_NAME}", - "KeycloakClientId":"${KEYCLOAK_TEST_CLIENT_ID}", - "KeycloakGrantType":"${KEYCLOAK_TEST_GRANT_TYPE}", - "StorageManagerUsername":"${STORAGE_MANAGER_USER_NAME}", - "StorageAccountOwnerUsername":"${STORAGE_ACCOUNT_OWNER_USER_NAME}", - "DataConsumerUsername":"${DATA_CONSUMER_USER_NAME}", - "DataAccessorUsername":"${DATA_ACCESSOR_USER_NAME}", - "ServiceUsersCredentials":${SERVICE_USERS_CREDENTIALS}, "AzureAccountName":"${AZURE_ACCOUNT_NAME}", "AzureAccountKey":"${AZURE_SECRET_KEY}", "AzureArchiveContainer":"${AZURE_ARCHIVE_BUCKET_NAME}", @@ -325,19 +262,6 @@ else "AzureArchiveAccessTier":"${AZURE_ARCHIVE_ACCESS_TIER}", "AzureArchiveManifestTier":"${AZURE_ARCHIVE_MANIFEST_ACCESS_TIER}", "AzureArchiveQueue":"${AZURE_ARCHIVE_QUEUE_NAME:-}", - "TimeProgressionFactor":"${TIME_PROGRESSION_FACTOR}", - "KafkaObjectTaskTopic":"${KAFKA_OBJECT_TASK_TOPIC}", - "KafkaGCRequestTopic":"${KAFKA_GC_REQUEST_TOPIC}", - "KafkaDeadLetterQueueTopic":"${KAFKA_DEAD_LETTER_TOPIC}", - "InstanceID":"${INSTANCE_ID}", - "BackbeatApiHost":"${BACKBEAT_API_HOST}", - "BackbeatApiPort":"${BACKBEAT_API_PORT}", - "KafkaCleanerInterval":"${KAFKA_CLEANER_INTERVAL}", - "SorbetdRestoreTimeout":"${SORBETD_RESTORE_TIMEOUT}", - "DRAdminAccessKey":"${ADMIN_PRA_ACCESS_KEY_ID}", - "DRAdminSecretKey":"${ADMIN_PRA_SECRET_ACCESS_KEY}", - "UtilizationServiceHost":"${UTILIZATION_SERVICE_HOST}", - "UtilizationServicePort":"${UTILIZATION_SERVICE_PORT}", "KubeconfigPath":"${KUBECONFIG:-${HOME}/.kube/config}" } EOF @@ -358,24 +282,11 @@ if [ -n "${GITHUB_ENV:-}" ]; then # Don't do it for Codespace echo "MONGO_AUTH_PASSWORD=$MONGO_AUTH_PASSWORD" >> "$GITHUB_ENV" echo "ADMIN_ACCESS_KEY_ID=$ADMIN_ACCESS_KEY_ID" >> "$GITHUB_ENV" echo "ADMIN_SECRET_ACCESS_KEY=$ADMIN_SECRET_ACCESS_KEY" >> "$GITHUB_ENV" - echo "ZENKO_ACCESS_KEY=$ZENKO_ACCESS_KEY" >> "$GITHUB_ENV" - echo "ZENKO_SECRET_KEY=$ZENKO_SECRET_KEY" >> "$GITHUB_ENV" - echo "ZENKO_SESSION_TOKEN=$ZENKO_SESSION_TOKEN" >> "$GITHUB_ENV" echo "KEYCLOAK_TEST_USER=$KEYCLOAK_TEST_USER" >> "$GITHUB_ENV" echo "KEYCLOAK_TEST_PASSWORD=$KEYCLOAK_TEST_PASSWORD" >> "$GITHUB_ENV" echo "KEYCLOAK_TEST_HOST=$KEYCLOAK_TEST_HOST" >> "$GITHUB_ENV" - echo "KEYCLOAK_TEST_PORT=$KEYCLOAK_TEST_PORT" >> "$GITHUB_ENV" echo "KEYCLOAK_TEST_REALM_NAME=$KEYCLOAK_TEST_REALM_NAME" >> "$GITHUB_ENV" echo "KEYCLOAK_REALM=$KEYCLOAK_REALM" >> "$GITHUB_ENV" - echo "KEYCLOAK_TEST_CLIENT_ID=$KEYCLOAK_TEST_CLIENT_ID" >> "$GITHUB_ENV" - echo "KEYCLOAK_TEST_GRANT_TYPE=$KEYCLOAK_TEST_GRANT_TYPE" >> "$GITHUB_ENV" - echo "CLOUDSERVER_HOST=$CLOUDSERVER_HOST" >> "$GITHUB_ENV" - echo "CLOUDSERVER_ENDPOINT=$CLOUDSERVER_ENDPOINT" >> "$GITHUB_ENV" - echo "BACKBEAT_API_ENDPOINT=$BACKBEAT_API_ENDPOINT" >> "$GITHUB_ENV" - echo "VAULT_ENDPOINT=$VAULT_ENDPOINT" >> "$GITHUB_ENV" - echo "VAULT_STS_ENDPOINT=$VAULT_STS_ENDPOINT" >> "$GITHUB_ENV" - echo "VAULT_AUTH_HOST=$VAULT_AUTH_HOST" >> "$GITHUB_ENV" - echo "KAFKA_CONNECT_URL=$KAFKA_CONNECT_URL" >> "$GITHUB_ENV" echo "NODE_EXTRA_CA_CERTS=$NODE_EXTRA_CA_CERTS" >> "$GITHUB_ENV" echo "MOCHA_FILE=$MOCHA_FILE" >> "$GITHUB_ENV" echo "VERIFY_CERTIFICATES=$VERIFY_CERTIFICATES" >> "$GITHUB_ENV" diff --git a/tests/functional/ctst/common/common.ts b/tests/functional/ctst/common/common.ts index f12828fcae..8be21bb601 100644 --- a/tests/functional/ctst/common/common.ts +++ b/tests/functional/ctst/common/common.ts @@ -1,6 +1,7 @@ import { ListObjectVersionsOutput } from '@aws-sdk/client-s3'; import { Given, setDefaultTimeout, Then, When } from '@cucumber/cucumber'; import { CacheHelper, Constants, Identity, IdentityEnum, S3, Utils } from 'cli-testing'; +import { config, ZENKO_ACCOUNT_NAME } from 'tests_common/configuration'; import Zenko from 'world/Zenko'; import { parseGoDuration, safeJsonParse } from './utils'; import assert from 'assert'; @@ -39,8 +40,7 @@ export async function cleanS3Bucket( // Do not try to clean a bucket with compliance retention return; } - Identity.useIdentity(IdentityEnum.ACCOUNT, world.getSaved('accountName') || - world.parameters.AccountName); + Identity.useIdentity(IdentityEnum.ACCOUNT, world.getSaved('accountName') || ZENKO_ACCOUNT_NAME); world.resetCommand(); world.addCommandParameter({ bucket: bucketName }); const createdObjects = world.getCreatedObjects(); @@ -143,14 +143,14 @@ async function createBucket(world: Zenko, versioning: string, bucketName: string Given('a {string} bucket with dot', async function (this: Zenko, versioning: string) { const preName = this.getSaved('accountName') || - this.parameters.AccountName || Constants.ACCOUNT_NAME; + ZENKO_ACCOUNT_NAME; await createBucket(this, versioning, `${preName}.${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); }); Given('a {string} bucket', async function (this: Zenko, versioning: string) { const preName = this.getSaved('accountName') || - this.parameters.AccountName || Constants.ACCOUNT_NAME; + ZENKO_ACCOUNT_NAME; await createBucket(this, versioning, `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); }); @@ -310,7 +310,7 @@ Then('i {string} be able to add user metadata to object {string}', Then('kafka consumed messages should not take too much place on disk', { timeout: -1 }, async function (this: Zenko) { - const kfkcIntervalSeconds = parseGoDuration(this.parameters.KafkaCleanerInterval); + const kfkcIntervalSeconds = parseGoDuration(config.ZenkoCR.KafkaCleanerInterval); const checkInterval = kfkcIntervalSeconds * (1000 + 5000); const timeoutID = setTimeout(() => { @@ -326,7 +326,7 @@ Then('kafka consumed messages should not take too much place on disk', { timeout const ignoredTopics = ['dead-letter']; const allTopics = await kafkaAdmin.listTopics(); const topics: string[] = allTopics - .filter(t => (t.includes(this.parameters.InstanceID) && + .filter(t => (t.includes(config.ZenkoCR.InstanceID) && !ignoredTopics.some(e => t.includes(e)))); const previousOffsets = await getTopicsOffsets(topics, kafkaAdmin); diff --git a/tests/functional/ctst/common/hooks.ts b/tests/functional/ctst/common/hooks.ts index da7e1a157f..54ad61b56c 100644 --- a/tests/functional/ctst/common/hooks.ts +++ b/tests/functional/ctst/common/hooks.ts @@ -1,5 +1,6 @@ import { Before, + BeforeAll, After, setParallelCanAssign, parallelCanAssignHelpers, @@ -7,6 +8,7 @@ import { } from '@cucumber/cucumber'; import Zenko from '../world/Zenko'; import { CacheHelper, Identity, WorkCoordination } from 'cli-testing'; +import { initConfig, config } from 'tests_common/configuration'; import { prepareQuotaScenarios, teardownQuotaScenarios } from 'steps/quotas/quotas'; import { prepareUtilizationScenarios } from 'steps/utilization/utilizationAPI'; import { prepareMetricsScenarios } from './utils'; @@ -18,7 +20,14 @@ import { } from './utils'; import { createKubeCustomObjectClient, waitForZenkoToStabilize } from 'steps/utils/kubernetes'; -import 'cli-testing/hooks/KeycloakSetup'; +BeforeAll(async function () { + // Some hooks are defined in cli-testing and use the configuration, + // we need to have this run before anything else + await initConfig(); + console.log("before all ", config); +}); + +// import 'cli-testing/hooks/KeycloakSetup'; import 'cli-testing/hooks/Logger'; import 'cli-testing/hooks/versionTags'; diff --git a/tests/functional/ctst/steps/azureArchive.ts b/tests/functional/ctst/steps/azureArchive.ts index b85e9a3f46..5f3d6e36b7 100644 --- a/tests/functional/ctst/steps/azureArchive.ts +++ b/tests/functional/ctst/steps/azureArchive.ts @@ -3,6 +3,7 @@ import path from 'path'; import assert from 'assert'; import { safeJsonParse, request } from '../common/utils'; import { Given, Then, When } from '@cucumber/cucumber'; +import { config, BACKBEAT_API_HOST, BACKBEAT_API_PORT } from 'tests_common/configuration'; import { AzureHelper, S3, Constants, Utils } from 'cli-testing'; import util from 'util'; import { exec } from 'child_process'; @@ -319,7 +320,7 @@ Then('blob for object {string} fails to rehydrate', const tarName = await isObjectRehydrated(this, objectName); // wait for restore to fail and end up in dead letter queue - const restoreTimeoutSeconds = parseInt(this.parameters.SorbetdRestoreTimeout); + const restoreTimeoutSeconds = parseInt(config.ZenkoCR.SorbetdRestoreTimeout); await Utils.sleep(restoreTimeoutSeconds * 1000 + 1000); assert(tarName); // restoreTimeout is set to 30s in the config @@ -360,9 +361,9 @@ When('i run sorbetctl to retry failed restore for {string} location', { timeout: 10 * 60 * 1000 }, async function (this: Zenko, location: string) { const command = `./ctst/sorbetctl forward list failed --trigger-retry --skip-invalid \ --limit 300 \ - --kafka-dead-letter-topic=${this.parameters.KafkaDeadLetterQueueTopic} \ - --kafka-object-task-topic=${this.parameters.KafkaObjectTaskTopic} \ - --kafka-gc-request-topic=${this.parameters.KafkaGCRequestTopic} \ + --kafka-dead-letter-topic=${config.KafkaTopics.DeadLetterQueue} \ + --kafka-object-task-topic=${config.KafkaTopics.ObjectTasks} \ + --kafka-gc-request-topic=${config.KafkaTopics.GcRequest} \ --kafka-brokers ${this.parameters.KafkaHosts}`; try { this.logger.debug('Running command', { command, location }); @@ -376,11 +377,13 @@ When('i run sorbetctl to retry failed restore for {string} location', } }); -When('i wait for {int} days', { timeout: 10 * 60 * 1000 }, async function (this: Zenko, days: number) { - const realTimeDay = days * 24 * 60 * 60 * 1000 / - (this.parameters.TimeProgressionFactor > 1 ? this.parameters.TimeProgressionFactor : 1); - await Utils.sleep(realTimeDay); -}); +When('i wait for {int} days', { timeout: 10 * 60 * 1000 }, + async function (this: Zenko, days: number) { + const tpf = config.ZenkoCR.TimeProgressionFactor; + const realTimeDay = days * 24 * 60 * 60 * 1000 / + (tpf > 1 ? tpf : 1); + await Utils.sleep(realTimeDay); + }); Then('object {string} should expire in {int} days', async function (this: Zenko, objectName: string, days: number) { const objName = objectName || this.getSaved('objectName'); @@ -401,9 +404,10 @@ Then('object {string} should expire in {int} days', async function (this: Zenko, const expiryDate = new Date(expireResDate[1]).getTime(); const lastModified = new Date(head.LastModified).getTime(); const diff = (expiryDate - lastModified) / 1000 / 86400; - const realTimeDays = days / (this.parameters.TimeProgressionFactor > 1 ? this.parameters.TimeProgressionFactor : 1); + const tpf = config.ZenkoCR.TimeProgressionFactor; + const realTimeDays = days / (tpf > 1 ? tpf : 1); assert.ok(diff >= realTimeDays && diff < realTimeDays + 0.005, - `Expected ${realTimeDays} but got ${diff} ; ${this.parameters.TimeProgressionFactor}`); + `Expected ${realTimeDays} but got ${diff} ; ${tpf}`); }); Given('that lifecycle is {string} for the {string} location', @@ -415,8 +419,8 @@ Given('that lifecycle is {string} for the {string} location', path = `/_/lifecycle/resume/${location}`; } const options = { - hostname: this.parameters.BackbeatApiHost, - port: this.parameters.BackbeatApiPort, + hostname: BACKBEAT_API_HOST, + port: BACKBEAT_API_PORT, method: 'POST', path, }; @@ -443,7 +447,8 @@ Given('an azure archive location {string}', { timeout: 15 * 60 * 1000 }, }, }, }; - const result = await this.managementAPIRequest('POST', `/config/${this.parameters.InstanceID}/location`, {}, + const result = await this.managementAPIRequest( + 'POST', `/config/${config.ZenkoCR.InstanceID}/location`, {}, locationConfig); assert.strictEqual(result.statusCode, 201); this.addToSaved('locationName', locationName); @@ -453,7 +458,11 @@ Given('an azure archive location {string}', { timeout: 15 * 60 * 1000 }, When('i change azure archive location {string} container target', { timeout: 15 * 60 * 1000 }, async function (this: Zenko, locationName: string) { - const result = await this.managementAPIRequest('GET', `/config/overlay/view/${this.parameters.InstanceID}`); + const instanceID = config.ZenkoCR.InstanceID; + const result = await this.managementAPIRequest( + 'GET', + `/config/overlay/view/${instanceID}`, + ); if ('err' in result) { assert.ifError(result.err); } else { @@ -465,7 +474,7 @@ When('i change azure archive location {string} container target', { timeout: 15 details.bucketName = this.parameters.AzureArchiveContainer2; auth.accountKey = this.parameters.AzureAccountKey; const putResult = await this.managementAPIRequest('PUT', - `/config/${this.parameters.InstanceID}/location/${locationName}`, + `/config/${config.ZenkoCR.InstanceID}/location/${locationName}`, {}, locationConfig); if ('err' in putResult) { diff --git a/tests/functional/ctst/steps/cloudserverAuth.ts b/tests/functional/ctst/steps/cloudserverAuth.ts index a75a104be4..a069960413 100644 --- a/tests/functional/ctst/steps/cloudserverAuth.ts +++ b/tests/functional/ctst/steps/cloudserverAuth.ts @@ -1,6 +1,7 @@ import { When, Then } from '@cucumber/cucumber'; import Zenko from '../world/Zenko'; import { Utils, S3, Constants } from 'cli-testing'; +import { ZENKO_ACCOUNT_NAME } from 'tests_common/configuration'; import { strict as assert } from 'assert'; interface DeleteObjectsResult { @@ -36,7 +37,7 @@ When('the user tries to perform CreateBucket', async function (this: Zenko) { this.resetCommand(); this.useSavedIdentity(); const preName = this.getSaved('accountName') || - this.parameters.AccountName || Constants.ACCOUNT_NAME; + ZENKO_ACCOUNT_NAME; const usedBucketName = `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); this.addToSaved('bucketName', usedBucketName); this.addCommandParameter({ bucket: usedBucketName }); diff --git a/tests/functional/ctst/steps/iam-policies/common.ts b/tests/functional/ctst/steps/iam-policies/common.ts index 19e3f5e1f6..99d40b2bee 100644 --- a/tests/functional/ctst/steps/iam-policies/common.ts +++ b/tests/functional/ctst/steps/iam-policies/common.ts @@ -3,6 +3,7 @@ import { strict as assert } from 'assert'; import Zenko from '../../world/Zenko'; import { CacheHelper, ClientOptions, Command, Identity, VaultAuth } from 'cli-testing'; import { runActionAgainstBucket } from 'steps/utils/utils'; +import { VAULT_AUTH_HOST, ZENKO_ACCOUNT_NAME } from 'tests_common/configuration'; When('the user tries to perform {string} on the bucket', async function (this: Zenko, action: string) { await runActionAgainstBucket(this, action); @@ -21,22 +22,18 @@ When('the user tries to perform vault auth {string}', async function (this: Zenk + 'Make sure the `IAMSession` and `AssumedSession` world parameter are defined.'); } - if (!this.parameters.VaultAuthHost) { - throw new Error('Vault auth endpoint is not set. Make sure the `VaultAuthHost` world parameter is defined.'); - } - const vaultAuthClientOptions: ClientOptions = { AccessKey: userCredentials.accessKeyId, SecretKey: userCredentials.secretAccessKey, SessionToken: userCredentials.sessionToken, - ip: this.parameters.VaultAuthHost, + ip: VAULT_AUTH_HOST, ssl: CacheHelper.parameters ? CacheHelper.parameters.ssl as boolean : undefined, }; switch (action) { case 'GetAccountInfo': this.setResult(await VaultAuth.getAccounts([ - lastIdentity.accountName || this.parameters.AccountName, + lastIdentity.accountName || ZENKO_ACCOUNT_NAME, ], null, null, { // @ts-expect-error accountNames is not generated by CTST yet accountNames: true, diff --git a/tests/functional/ctst/steps/notifications.ts b/tests/functional/ctst/steps/notifications.ts index 5d4eead837..93b873b42e 100644 --- a/tests/functional/ctst/steps/notifications.ts +++ b/tests/functional/ctst/steps/notifications.ts @@ -186,7 +186,7 @@ When('i subscribe to {string} notifications for destination {int}', this.addCommandParameter({ notificationConfiguration: `'${JSON.stringify(destinationConfig)}'` }); } await S3.putBucketNotificationConfiguration(this.getCommandParameters()); - await waitForBucketInConnectorPipeline(this.parameters.KafkaConnectUrl, this.getSaved('bucketName')); + await waitForBucketInConnectorPipeline(this.getSaved('bucketName')); }); When('i subscribe to {string} notifications for destination {int} with {string} filter', @@ -235,7 +235,7 @@ When('i subscribe to {string} notifications for destination {int} with {string} this.addCommandParameter({ notificationConfiguration: `'${JSON.stringify(destinationConfig)}'` }); } await S3.putBucketNotificationConfiguration(this.getCommandParameters()); - await waitForBucketInConnectorPipeline(this.parameters.KafkaConnectUrl, this.getSaved('bucketName')); + await waitForBucketInConnectorPipeline(this.getSaved('bucketName')); }); When('i unsubscribe from {string} notifications for destination {int}', diff --git a/tests/functional/ctst/steps/replication.ts b/tests/functional/ctst/steps/replication.ts index 7e0311355a..2067236a0b 100644 --- a/tests/functional/ctst/steps/replication.ts +++ b/tests/functional/ctst/steps/replication.ts @@ -3,6 +3,7 @@ import Zenko from '../world/Zenko'; import { createAndRunPod, getZenkoVersion } from 'steps/utils/kubernetes'; import assert from 'assert'; import { IdentityEnum, Identity, Utils } from 'cli-testing'; +import { ZENKO_ACCOUNT_NAME } from 'tests_common/configuration'; import { GetObjectCommand, DeleteBucketCommand, @@ -26,7 +27,7 @@ When('the job to replicate existing objects with status {string} is executed', const s3utilsVersion = zenkoVersion.spec.versions.s3utils; const credentials = Identity.getCredentialsForIdentity( IdentityEnum.ACCOUNT, - this.parameters.AccountName + ZENKO_ACCOUNT_NAME ); const podManifest = { apiVersion: 'v1', diff --git a/tests/functional/ctst/steps/reporting/storageUsageReporting.ts b/tests/functional/ctst/steps/reporting/storageUsageReporting.ts index fd769b3aa9..3da60cc6c5 100644 --- a/tests/functional/ctst/steps/reporting/storageUsageReporting.ts +++ b/tests/functional/ctst/steps/reporting/storageUsageReporting.ts @@ -1,6 +1,7 @@ import { Given, When, Then } from '@cucumber/cucumber'; import { strict as assert } from 'assert'; import Zenko from '../../world/Zenko'; +import { config } from 'tests_common/configuration'; import { IdentityEnum } from 'cli-testing'; interface LocationUsage { @@ -15,15 +16,14 @@ interface ReportingUsageResponse { } Given('an identity with the {string} keycloak persona', function (this: Zenko, persona: string) { - const username = (this.parameters as Record)[persona] || persona; - this.addToSaved('keycloakPersona', username); + this.addToSaved('keycloakPersona', persona); }); async function fetchStorageUsageReport(world: Zenko) { const persona = world.getSaved('keycloakPersona'); const result = await world.managementAPIRequest( 'GET', - `/instance/${world.parameters.InstanceID}/reporting/usage`, + `/instance/${config.ZenkoCR.InstanceID}/reporting/usage`, {}, {}, persona, diff --git a/tests/functional/ctst/steps/utilization/utilizationAPI.ts b/tests/functional/ctst/steps/utilization/utilizationAPI.ts index f76cef9a72..6bdcf22578 100644 --- a/tests/functional/ctst/steps/utilization/utilizationAPI.ts +++ b/tests/functional/ctst/steps/utilization/utilizationAPI.ts @@ -1,6 +1,7 @@ import { When, Then, ITestCaseHookParameter } from '@cucumber/cucumber'; import { strict as assert } from 'assert'; import Zenko from '../../world/Zenko'; +import { config } from 'tests_common/configuration'; import { Command } from 'cli-testing'; import { Identity } from 'cli-testing'; import ScubaClient, { ScubaMetrics } from 'scubaclient'; @@ -27,8 +28,8 @@ When('the user retrieves utilization metrics using scubaclient for metric type { this.addToSaved('metricType', metricType); const client = new ScubaClient({ - port: parseInt(this.parameters.UtilizationServicePort), - host: this.parameters.UtilizationServiceHost, + port: 80, + host: config.ZenkoCR.UtilizationServiceHost, useHttps: false, auth: { awsV4: { diff --git a/tests/functional/ctst/steps/utils/kafka.ts b/tests/functional/ctst/steps/utils/kafka.ts index 17fd96a4fa..9332437727 100644 --- a/tests/functional/ctst/steps/utils/kafka.ts +++ b/tests/functional/ctst/steps/utils/kafka.ts @@ -1,4 +1,5 @@ import { Utils } from 'cli-testing'; +import { KAFKA_CONNECT_URL } from 'tests_common/configuration'; interface ConnectorInfo { info: { @@ -20,7 +21,6 @@ interface ConnectorInfo { * test proceeds to trigger events. */ export async function waitForBucketInConnectorPipeline( - kafkaConnectUrl: string, bucketName: string, timeoutMs = 120000, intervalMs = 1000, @@ -29,7 +29,7 @@ export async function waitForBucketInConnectorPipeline( let lastConnectorCount = 0; while (Date.now() < deadline) { try { - const response = await fetch(`${kafkaConnectUrl}?expand=info`, { + const response = await fetch(`${KAFKA_CONNECT_URL}?expand=info`, { signal: AbortSignal.timeout(5000), }); const connectors = await response.json() as Record; diff --git a/tests/functional/ctst/steps/utils/utils.ts b/tests/functional/ctst/steps/utils/utils.ts index 3d0b601297..054760fc03 100644 --- a/tests/functional/ctst/steps/utils/utils.ts +++ b/tests/functional/ctst/steps/utils/utils.ts @@ -12,6 +12,7 @@ import { } from 'cli-testing'; import { extractPropertyFromResults, s3FunctionExtraParams, safeJsonParse } from 'common/utils'; import Zenko from 'world/Zenko'; +import { ZENKO_ACCOUNT_NAME } from 'tests_common/configuration'; import assert from 'assert'; import constants from 'common/constants'; import { getLocationConfigs } from './kubernetes'; @@ -180,7 +181,7 @@ async function createBucketWithConfiguration( retentionMode?: string) { world.resetCommand(); const preName = world.getSaved('accountName') || - world.parameters.AccountName || Constants.ACCOUNT_NAME; + ZENKO_ACCOUNT_NAME; const usedBucketName = bucketName || `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); world.addToSaved('bucketName', usedBucketName); diff --git a/tests/functional/ctst/world/Zenko.ts b/tests/functional/ctst/world/Zenko.ts index f1f75c7b07..34b17438ce 100644 --- a/tests/functional/ctst/world/Zenko.ts +++ b/tests/functional/ctst/world/Zenko.ts @@ -25,11 +25,7 @@ import { import { extractPropertyFromResults } from '../common/utils'; import ZenkoDrctl from 'steps/dr/drctl'; import assert from 'assert'; - -interface ServiceUsersCredentials { - accessKey: string; - secretKey: string; -} +import { config, ServiceUserCredentials, ZENKO_ACCOUNT_NAME } from 'tests_common/configuration'; // Zenko entities export interface SavedIdentity { @@ -50,13 +46,7 @@ export enum EntityType { } export interface ZenkoWorldParameters extends ClientOptions { - AccountName: string; - AccountAccessKey: string; - AccountSecretKey: string; - DRAdminAccessKey?: string; - DRAdminSecretKey?: string; DRSubdomain?: string; - VaultAuthHost: string; NotificationDestination: string; NotificationDestinationTopic: string; NotificationDestinationAlt: string; @@ -68,21 +58,12 @@ export interface ZenkoWorldParameters extends ClientOptions { KafkaExternalIps: string; KafkaHosts: string; KafkaAuthHosts: string; - KafkaConnectUrl: string; PrometheusService: string; PrometheusEndpoint: string; KeycloakUsername: string; KeycloakPassword: string; KeycloakHost: string; - KeycloakPort: string; KeycloakRealm: string; - KeycloakClientId: string; - KeycloakGrantType: string; - StorageManagerUsername: string; - StorageAccountOwnerUsername: string; - DataConsumerUsername: string; - DataAccessorUsername: string; - ServiceUsersCredentials: string; KeycloakTestPassword: string; AzureAccountName: string; AzureAccountKey: string; @@ -91,18 +72,7 @@ export interface ZenkoWorldParameters extends ClientOptions { AzureArchiveAccessTier: string; AzureArchiveManifestTier: string; AzureArchiveQueue: string; - TimeProgressionFactor: number; - KafkaDeadLetterQueueTopic: string; - KafkaObjectTaskTopic: string; - KafkaGCRequestTopic: string; - InstanceID: string; - BackbeatApiHost: string; - BackbeatApiPort: string; - KafkaCleanerInterval: string; - SorbetdRestoreTimeout: string; - UtilizationServiceHost: string; - UtilizationServicePort: string; - [key: string]: unknown; + KubeconfigPath?: string; } /** @@ -142,17 +112,23 @@ export default class Zenko extends World { */ constructor(options: IWorldOptions) { super(options); + Logger.createLogger(this); - // store service users credentials from world parameters - if (this.parameters.ServiceUsersCredentials) { - const serviceUserCredentials = - JSON.parse(this.parameters.ServiceUsersCredentials) as Record; - for (const serviceUserName in serviceUserCredentials) { - if (!Identity.hasIdentity(IdentityEnum.SERVICE_USER, serviceUserName, this.parameters.AccountName)) { - Identity.addIdentity(IdentityEnum.SERVICE_USER, serviceUserName, { - accessKeyId: serviceUserCredentials[serviceUserName].accessKey, - secretAccessKey: serviceUserCredentials[serviceUserName].secretKey, - }, this.parameters.AccountName); + if (config?.ServiceUsers) { + const serviceUsers = config.ServiceUsers; + const entries: [string, ServiceUserCredentials][] = [ + ['backbeat-lifecycle-bp-1', serviceUsers.BackbeatLifecycleBp1], + ['backbeat-lifecycle-conductor-1', serviceUsers.BackbeatLifecycleConductor1], + ['backbeat-lifecycle-op-1', serviceUsers.BackbeatLifecycleOp1], + ['backbeat-qp-1', serviceUsers.BackbeatQp1], + ['sorbet-fwd-2', serviceUsers.SorbetFwd2], + ]; + for (const [name, creds] of entries) { + if (creds && !Identity.hasIdentity(IdentityEnum.SERVICE_USER, name, ZENKO_ACCOUNT_NAME)) { + Identity.addIdentity(IdentityEnum.SERVICE_USER, name, { + accessKeyId: creds.accessKey, + secretAccessKey: creds.secretKey, + }, ZENKO_ACCOUNT_NAME); } } } @@ -164,24 +140,22 @@ export default class Zenko extends World { CacheHelper.savedAcrossTests[Zenko.PRA_INSTALL_COUNT_KEY] = 0; - - if (this.parameters.AccountName && !Identity.hasIdentity(IdentityEnum.ACCOUNT, this.parameters.AccountName)) { - Identity.addIdentity(IdentityEnum.ACCOUNT, this.parameters.AccountName, { - accessKeyId: this.parameters.AccountAccessKey, - secretAccessKey: this.parameters.AccountSecretKey, + if (!Identity.hasIdentity(IdentityEnum.ACCOUNT, ZENKO_ACCOUNT_NAME)) { + const { credentials } = config.ZenkoAccount; + Identity.addIdentity(IdentityEnum.ACCOUNT, ZENKO_ACCOUNT_NAME, { + accessKeyId: credentials.accessKeyId, + secretAccessKey: credentials.secretAccessKey, + sessionToken: credentials.sessionToken, }); } - if (this.parameters.AccountName) { - Identity.useIdentity(IdentityEnum.ACCOUNT, this.parameters.AccountName); - Identity.defaultAccountName = this.parameters.AccountName; - } + Identity.useIdentity(IdentityEnum.ACCOUNT, ZENKO_ACCOUNT_NAME); + Identity.defaultAccountName = ZENKO_ACCOUNT_NAME; - if (this.parameters.AdminAccessKey && this.parameters.AdminSecretKey && - !Identity.hasIdentity(IdentityEnum.ADMIN, Zenko.PRIMARY_SITE_NAME)) { + if (!Identity.hasIdentity(IdentityEnum.ADMIN, Zenko.PRIMARY_SITE_NAME)) { Identity.addIdentity(IdentityEnum.ADMIN, Zenko.PRIMARY_SITE_NAME, { - accessKeyId: this.parameters.AdminAccessKey, - secretAccessKey: this.parameters.AdminSecretKey, + accessKeyId: config.AdminCredentials.accessKey, + secretAccessKey: config.AdminCredentials.secretKey, }, undefined, undefined, undefined, this.parameters.subdomain); Zenko.sites['source'] = { @@ -193,13 +167,13 @@ export default class Zenko extends World { if (this.needsSecondarySite()) { if (!Identity.hasIdentity(IdentityEnum.ADMIN, Zenko.SECONDARY_SITE_NAME)) { Identity.addIdentity(IdentityEnum.ADMIN, Zenko.SECONDARY_SITE_NAME, { - accessKeyId: this.parameters.DRAdminAccessKey!, - secretAccessKey: this.parameters.DRAdminSecretKey!, + accessKeyId: config.PRAAdmin!.accessKey, + secretAccessKey: config.PRAAdmin!.secretKey, }, undefined, undefined, undefined, this.parameters.DRSubdomain); } Zenko.sites['sink'] = { - accountName: `dr${this.parameters.AccountName}`, + accountName: `dr${ZENKO_ACCOUNT_NAME}`, adminIdentityName: Zenko.SECONDARY_SITE_NAME, }; } @@ -210,7 +184,7 @@ export default class Zenko extends World { } private needsSecondarySite() { - return this.parameters.DRAdminAccessKey && this.parameters.DRAdminSecretKey && this.parameters.DRSubdomain; + return config.PRAAdmin && this.parameters.DRSubdomain; } /** @@ -265,19 +239,19 @@ export default class Zenko extends World { await this.prepareIamUser(); break; case EntityType.STORAGE_MANAGER: - await this.prepareARWWI(this.parameters.StorageManagerUsername || 'storage_manager', + await this.prepareARWWI('storage_manager', 'storage-manager-role', this.parameters.KeycloakTestPassword); break; case EntityType.STORAGE_ACCOUNT_OWNER: - await this.prepareARWWI(this.parameters.StorageAccountOwnerUsername || 'storage_account_owner', + await this.prepareARWWI('storage_account_owner', 'storage-account-owner-role', this.parameters.KeycloakTestPassword); break; case EntityType.DATA_CONSUMER: - await this.prepareARWWI(this.parameters.DataConsumerUsername || 'data_consumer', + await this.prepareARWWI('data_consumer', 'data-consumer-role', this.parameters.KeycloakTestPassword); break; case EntityType.DATA_ACCESSOR: - await this.prepareARWWI(this.parameters.DataAccessorUsername || 'data_accessor', + await this.prepareARWWI('data_accessor', 'data-accessor-role', this.parameters.KeycloakTestPassword); break; case EntityType.ASSUME_ROLE_USER: @@ -316,10 +290,10 @@ export default class Zenko extends World { ARWWIName, ARWWIPassword || '123', this.parameters.KeycloakHost || 'keycloak.zenko.local', - this.parameters.KeycloakPort || '80', - `/auth/realms/${this.parameters.KeycloakRealm || 'zenko'}/protocol/openid-connect/token`, - this.parameters.KeycloakClientId || Constants.K_CLIENT, - this.parameters.KeycloakGrantType || 'password', + '80', + `/auth/realms/${this.parameters.KeycloakRealm}/protocol/openid-connect/token`, + Constants.K_CLIENT, + 'password', ); if (!webIdentityToken) { throw new Error('Error when trying to get a WebIdentity token.'); @@ -721,10 +695,10 @@ export default class Zenko extends World { } } - const accountName = this.sites['source']?.accountName || CacheHelper.parameters.AccountName!; + const accountName = this.sites['source']?.accountName || ZENKO_ACCOUNT_NAME; const accountAccessKeys = Identity.getCredentialsForIdentity( IdentityEnum.ACCOUNT, this.sites['source']?.accountName - || CacheHelper.parameters.AccountName!) || { + || ZENKO_ACCOUNT_NAME) || { accessKeyId: '', secretAccessKey: '', }; @@ -989,13 +963,13 @@ export default class Zenko extends World { username?: string, ): Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> { const token = await this.getWebIdentityToken( - username || this.parameters.KeycloakUsername || 'storage_manager', - this.parameters.KeycloakPassword || '123', - this.parameters.KeycloakHost || 'keycloak.zenko.local', - this.parameters.KeycloakPort || '80', - `/auth/realms/${this.parameters.KeycloakRealm || 'zenko'}/protocol/openid-connect/token`, - this.parameters.KeycloakClientId || Constants.K_CLIENT, - this.parameters.KeycloakGrantType || 'password', + username || this.parameters.KeycloakUsername, + this.parameters.KeycloakPassword, + this.parameters.KeycloakHost, + '80', + `/auth/realms/${this.parameters.KeycloakRealm}/protocol/openid-connect/token`, + Constants.K_CLIENT, + 'password', ); const axiosInstance = axios.create(); const protocol = this.parameters.ssl === false ? 'http://' : 'https://'; @@ -1042,7 +1016,7 @@ export default class Zenko extends World { async addWebsiteEndpoint(this: Zenko, endpoint: string): Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> { return await this.managementAPIRequest('POST', - `/config/${this.parameters.InstanceID}/website/endpoint`, + `/config/${config.ZenkoCR.InstanceID}/website/endpoint`, { 'Content-Type': 'application/json', }, @@ -1052,7 +1026,7 @@ export default class Zenko extends World { async deleteLocation(this: Zenko, locationName: string): Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> { return await this.managementAPIRequest('DELETE', - `/config/${this.parameters.InstanceID}/location/${locationName}`); + `/config/${config.ZenkoCR.InstanceID}/location/${locationName}`); } saveCreatedObject(objectName: string, versionId: string) { diff --git a/tests/functional/mocha/.mocharc.js b/tests/functional/mocha/.mocharc.js index c0efbe6b89..665f82df74 100644 --- a/tests/functional/mocha/.mocharc.js +++ b/tests/functional/mocha/.mocharc.js @@ -5,4 +5,5 @@ module.exports = { 'exit': true, 'reporter': 'mocha-multi-reporters', 'reporter-options': 'configFile=mocha/mocha-reporter.json', + 'require': ['ts-node/register', 'tsconfig-paths/register', './mocha/setup.ts'], }; diff --git a/tests/functional/mocha/VaultClient.js b/tests/functional/mocha/VaultClient.js index dc34e779dc..002b313725 100644 --- a/tests/functional/mocha/VaultClient.js +++ b/tests/functional/mocha/VaultClient.js @@ -1,4 +1,5 @@ const fs = require('fs'); +const { getConfig, VAULT_ENDPOINT } = require('tests_common/configuration'); const { IAMClient, DetachUserPolicyCommand, @@ -91,8 +92,7 @@ class VaultClient { * @returns {object} - returns an IAM client */ static getIamClient(accessKey, secretKey, sessionToken) { - const endpoint = process.env.VAULT_ENDPOINT - || 'http://localhost:8600'; + const endpoint = VAULT_ENDPOINT; const info = { endpoint, region: 'us-east-1', @@ -129,32 +129,16 @@ class VaultClient { * @returns {object} Vault endpoint information */ static getEndpointInformation() { - let host = '127.0.0.1'; - let port = 8600; + const res = /^https?:\/\/([^:]*)(:[0-9]+)?\/?$/.exec(VAULT_ENDPOINT); + let [host, port] = res.slice(1); + port = port ? parseInt(port.substring(1), 10) : 80; let ca; let cert; let key; - if (process.env.VAULT_ENDPOINT) { - const res = /^https?:\/\/([^:]*)(:[0-9]+)?\/?$/.exec( - process.env.VAULT_ENDPOINT, - ); - [host, port] = res.slice(1); - port = port ? parseInt(port.substring(1), 10) : 80; - const https = process.env.VAULT_ENDPOINT.startsWith('https://'); - if (https) { - ca = fs.readFileSync( - process.env.VAULT_SSL_CA || '/conf/ca.crt', - 'ascii', - ); - cert = fs.readFileSync( - process.env.VAULT_SSL_CERT || '/conf/test.crt', - 'ascii', - ); - key = fs.readFileSync( - process.env.VAULT_SSL_KEY || '/conf/test.key', - 'ascii', - ); - } + if (VAULT_ENDPOINT.startsWith('https://')) { + ca = fs.readFileSync(process.env.VAULT_SSL_CA || '/conf/ca.crt', 'ascii'); + cert = fs.readFileSync(process.env.VAULT_SSL_CERT || '/conf/test.crt', 'ascii'); + key = fs.readFileSync(process.env.VAULT_SSL_KEY || '/conf/test.key', 'ascii'); } return { host, @@ -172,8 +156,8 @@ class VaultClient { */ static getAdminClient() { const adminCredentials = { - accessKey: process.env.ADMIN_ACCESS_KEY_ID, - secretKeyValue: process.env.ADMIN_SECRET_ACCESS_KEY, + accessKey: getConfig().AdminCredentials.accessKey, + secretKeyValue: getConfig().AdminCredentials.secretKey, }; const info = this.getEndpointInformation(); return new vaultclient.Client( diff --git a/tests/functional/mocha/iam_policies/cloudserver/utils.js b/tests/functional/mocha/iam_policies/cloudserver/utils.js index aba4c3ca76..5815ddfd98 100644 --- a/tests/functional/mocha/iam_policies/cloudserver/utils.js +++ b/tests/functional/mocha/iam_policies/cloudserver/utils.js @@ -1,9 +1,7 @@ const aws4 = require('aws4'); const http = require('http'); const { makeGETRequest, getResponseBody, makeUpdateRequest } = require('../../utils/request'); - -const DEFAULT_HOST = process.env.CLOUDSERVER_HOST; -const DEFAULT_PORT = process.env.CLOUDSERVER_PORT || '80'; +const { CLOUDSERVER_HOST } = require('tests_common/configuration'); // eslint-disable-next-line default-param-last function makeApiCallGeneric(mode = 'GET', body, userCredentials, query, cb) { @@ -59,8 +57,8 @@ function putObjectResponseCode(userCredentials, bucketName, cb, fileName) { function putObjectVersionResponseCode(userCredentials, bucketName, cb, fileName) { const signOptions = { - host: DEFAULT_HOST, - port: DEFAULT_PORT, + host: CLOUDSERVER_HOST, + port: 80, service: 's3', method: 'PUT', path: `/${bucketName}/${fileName}`, diff --git a/tests/functional/mocha/s3SDK.js b/tests/functional/mocha/s3SDK.js index 5fd30a32da..ecfbdedb0a 100644 --- a/tests/functional/mocha/s3SDK.js +++ b/tests/functional/mocha/s3SDK.js @@ -1,6 +1,6 @@ const { S3Client } = require('@aws-sdk/client-s3'); -const { IAMClient } = require('@aws-sdk/client-iam'); const { NodeHttpHandler } = require('@smithy/node-http-handler'); +const { getConfig, CLOUDSERVER_ENDPOINT } = require('tests_common/configuration'); const sharedHttpHandler = new NodeHttpHandler({ requestTimeout: 0, @@ -11,42 +11,20 @@ function createS3Client(config) { return new S3Client(config); } -const scalityS3Client = createS3Client({ - credentials: { - accessKeyId: process.env.ZENKO_ACCESS_KEY, - secretAccessKey: process.env.ZENKO_SECRET_KEY, - sessionToken: process.env.ZENKO_SESSION_TOKEN, - }, - tls: false, - endpoint: process.env.CLOUDSERVER_ENDPOINT, - region: 'us-east-1', - forcePathStyle: true, - // disable node sdk retries and timeout to prevent InvalidPart - // and SocketHangUp errors. If retries are allowed, sdk will send - // another request after first request has already deleted parts, - // causing InvalidPart. Meanwhile, if request takes too long to finish, - // sdk will create SocketHangUp error before response. - maxAttempts: 1, - requestHandler: sharedHttpHandler, -}); +// Defers client lookup until first method call, after the mocha root hook +// has populated the config. +function lazy(factory) { + let instance = null; + return new Proxy({}, { + get(_, prop) { + if (!instance) instance = factory(); + return instance[prop]; + }, + }); +} -const scalityIAMClient = new IAMClient({ - credentials: { - accessKeyId: process.env.ZENKO_ACCESS_KEY, - secretAccessKey: process.env.ZENKO_SECRET_KEY, - sessionToken: process.env.ZENKO_SESSION_TOKEN, - }, - tls: false, - endpoint: process.env.VAULT_ENDPOINT, - region: 'us-east-1', - // disable node sdk retries and timeout to prevent InvalidPart - // and SocketHangUp errors. If retries are allowed, sdk will send - // another request after first request has already deleted parts, - // causing InvalidPart. Meanwhile, if request takes too long to finish, - // sdk will create SocketHangUp error before response. - maxAttempts: 1, - requestHandler: sharedHttpHandler, -}); +const scalityS3Client = lazy(() => getConfig().ZenkoAccount.s3Client); +const scalityIAMClient = lazy(() => getConfig().ZenkoAccount.iamClient); const verifyCerts = process.env.VERIFY_CERTIFICATES ? process.env.VERIFY_CERTIFICATES : true; @@ -83,7 +61,7 @@ const altScalityS3Client = createS3Client({ secretAccessKey: process.env.AWS_SECRET_KEY, }, tls: false, - endpoint: process.env.CLOUDSERVER_ENDPOINT, + endpoint: CLOUDSERVER_ENDPOINT, region: 'us-east-1', forcePathStyle: true, maxAttempts: 1, @@ -93,7 +71,7 @@ const altScalityS3Client = createS3Client({ function getS3Client(accessKey, secretKey, sessionToken) { const config = { tls: false, - endpoint: process.env.CLOUDSERVER_ENDPOINT, + endpoint: CLOUDSERVER_ENDPOINT, region: 'us-east-1', forcePathStyle: true, credentials: { diff --git a/tests/functional/mocha/setup.ts b/tests/functional/mocha/setup.ts new file mode 100644 index 0000000000..66a0c97888 --- /dev/null +++ b/tests/functional/mocha/setup.ts @@ -0,0 +1,5 @@ +import { initConfig } from '../tests_common/configuration.js'; + +export const mochaHooks = { + beforeAll: () => initConfig(), +}; diff --git a/tests/functional/mocha/stsSDK.js b/tests/functional/mocha/stsSDK.js index e664d48bfe..c3829f26d5 100644 --- a/tests/functional/mocha/stsSDK.js +++ b/tests/functional/mocha/stsSDK.js @@ -1,8 +1,9 @@ const { STSClient } = require('@aws-sdk/client-sts'); +const { VAULT_STS_ENDPOINT } = require('tests_common/configuration'); function getSTSClient(accessKey, secretKey, sessionToken) { const config = { - endpoint: process.env.VAULT_STS_ENDPOINT, + endpoint: VAULT_STS_ENDPOINT, region: 'us-east-1', maxAttempts: 1, tls: false, diff --git a/tests/functional/mocha/utils/getWebIdentityToken.js b/tests/functional/mocha/utils/getWebIdentityToken.js index 8a02914c44..76eee39857 100644 --- a/tests/functional/mocha/utils/getWebIdentityToken.js +++ b/tests/functional/mocha/utils/getWebIdentityToken.js @@ -4,11 +4,11 @@ const assert = require('assert'); const USER_1_PASSWORD = process.env.KEYCLOAK_TEST_PASSWORD || '123'; const HOST_1_URL = process.env.KEYCLOAK_TEST_HOST || 'http://keycloak.zenko.local'; -const HOST_1_PORT = parseInt(process.env.KEYCLOAK_TEST_PORT, 10) || 80; +const HOST_1_PORT = 80; const REALM_NAME = process.env.KEYCLOAK_TEST_REALM_NAME || 'zenko'; const KEYCLOAK_PATH = `/auth/realms/${REALM_NAME}/protocol/openid-connect/token`; -const CLIENT_ID = process.env.KEYCLOAK_TEST_CLIENT_ID || 'zenko-ui'; -const GRANT_TYPE = process.env.KEYCLOAK_TEST_GRANT_TYPE || 'password'; +const CLIENT_ID = 'zenko-ui'; +const GRANT_TYPE = 'password'; /** diff --git a/tests/functional/mocha/utils/request.js b/tests/functional/mocha/utils/request.js index 2987a35738..7c746197c1 100644 --- a/tests/functional/mocha/utils/request.js +++ b/tests/functional/mocha/utils/request.js @@ -1,19 +1,21 @@ const http = require('http'); const aws4 = require('aws4'); - -const DEFAULT_HOST = process.env.CLOUDSERVER_HOST; -const DEFAULT_PORT = process.env.CLOUDSERVER_PORT || '80'; - -const accessKeyId = process.env.ZENKO_ACCESS_KEY; -const secretAccessKey = process.env.ZENKO_SECRET_KEY; -const sessionToken = process.env.ZENKO_SESSION_TOKEN; +const { getConfig, CLOUDSERVER_HOST } = require('tests_common/configuration'); const defaultOptions = { - host: DEFAULT_HOST, - port: DEFAULT_PORT, + host: CLOUDSERVER_HOST, + port: 80, service: 's3', }; -const credentials = { accessKeyId, secretAccessKey, sessionToken }; + +const getCredentials = () => { + const { ZenkoAccount } = getConfig(); + return { + accessKeyId: ZenkoAccount.credentials.accessKeyId, + secretAccessKey: ZenkoAccount.credentials.secretAccessKey, + sessionToken: ZenkoAccount.credentials.sessionToken, + }; +}; function getResponseBody(res, cb, isXml = false) { res.setEncoding('utf8'); @@ -43,7 +45,7 @@ function makeGETRequest(path, cb, userCredentials) { method: 'GET', path, }; - options = aws4.sign(options, userCredentials || credentials); + options = aws4.sign(options, userCredentials || getCredentials()); const req = http.request(options, res => cb(null, res)); req.on('error', err => cb(err)); @@ -65,7 +67,7 @@ function makeUpdateRequest(path, cb, userCredentials, body, mode = 'POST') { method: mode || 'POST', path, }; - options = aws4.sign(options, userCredentials || credentials); + options = aws4.sign(options, userCredentials || getCredentials()); const req = http.request(options, res => cb(null, res)); req.on('error', err => cb(err)); diff --git a/tests/functional/tests_common/configuration.ts b/tests/functional/tests_common/configuration.ts new file mode 100644 index 0000000000..306150f946 --- /dev/null +++ b/tests/functional/tests_common/configuration.ts @@ -0,0 +1,223 @@ +import { S3Client } from '@aws-sdk/client-s3'; +import { IAMClient } from '@aws-sdk/client-iam'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; +import { getSecretByLabel, getSecretByName, getCustomObject } from "./kubernetes"; + +const ZENKO_NAME = 'end2end'; +const INSTANCE_SELECTOR = `app.kubernetes.io/instance=${ZENKO_NAME}`; + +export const ZENKO_ACCOUNT_NAME = 'zenko-ctst'; +export const CLOUDSERVER_HOST = 's3.zenko.local'; +export const CLOUDSERVER_ENDPOINT = 'http://s3.zenko.local'; +export const VAULT_ENDPOINT = 'http://iam.zenko.local'; +export const VAULT_AUTH_HOST = 'vault-auth.zenko.local'; +export const VAULT_STS_ENDPOINT = 'http://sts.zenko.local'; +export const BACKBEAT_API_HOST = 'backbeat-api.zenko.local'; +export const BACKBEAT_API_ENDPOINT = 'http://backbeat-api.zenko.local'; +export const BACKBEAT_API_PORT = '80'; +export const KAFKA_CONNECT_URL = 'http://kafka-connect.zenko.local/connectors'; + +export interface ServiceUserCredentials { + accessKey: string; + secretKey: string; +} + +export interface AccountCredentials { + accessKeyId: string; + secretAccessKey: string; + sessionToken?: string; +} + +// Minimal Zenko CR type (fields used by tests only) +interface ZenkoCR { + metadata: { + annotations?: Record; + }; + spec: { + kafkaCleaner: { + interval?: string; + }; + sorbet: { + server: { + azure: { + restoreTimeout?: string; + }; + }; + }; + scuba: { + api: { + ingress: { + hostname?: string; + }; + }; + }; + }; + status: { + instanceID: string; + }; +} + +export interface TestsConfiguration { + KafkaTopics: { + DeadLetterQueue: string; + ObjectTasks: string; + GcRequest: string; + } + ServiceUsers: { + BackbeatLifecycleBp1: ServiceUserCredentials; + BackbeatLifecycleConductor1: ServiceUserCredentials; + BackbeatLifecycleOp1: ServiceUserCredentials; + BackbeatQp1: ServiceUserCredentials; + SorbetFwd2: ServiceUserCredentials; + }; + PRAAdmin?: ServiceUserCredentials; + AdminCredentials: ServiceUserCredentials; + ZenkoAccount: { + credentials: AccountCredentials; + s3Client: S3Client; + iamClient: IAMClient; + }; + ZenkoCR: { + TimeProgressionFactor: number; + InstanceID: string; + KafkaCleanerInterval: string; + SorbetdRestoreTimeout: string; + UtilizationServiceHost: string; + }; +} + +export const config = {} as TestsConfiguration; + +export const initConfig = async (): Promise => { + if (Object.keys(config).length > 0) return; + const zenkoAccountCredentials = await loadZenkoAccount(); + const clients = createClients(zenkoAccountCredentials); + Object.assign(config, { + KafkaTopics: await loadKafkaTopics(), + ServiceUsers: await loadServiceUsers(), + PRAAdmin: await loadPRACredentials(), + AdminCredentials: await loadAdminCredentials(), + ZenkoCR: await loadZenkoCR(), + ZenkoAccount: { + credentials: zenkoAccountCredentials, + iamClient: clients.iamClient, + s3Client: clients.s3Client, + }, + }); +}; + +const createClients = (zenkoAccount: AccountCredentials): { s3Client: S3Client; iamClient: IAMClient } => { + const sharedHttpHandler = new NodeHttpHandler({ requestTimeout: 0, connectionTimeout: 0 }); + const credentials = { + accessKeyId: zenkoAccount.accessKeyId, + secretAccessKey: zenkoAccount.secretAccessKey, + sessionToken: zenkoAccount.sessionToken, + }; + return { + s3Client: new S3Client({ + credentials, + tls: false, + endpoint: CLOUDSERVER_ENDPOINT, + region: 'us-east-1', + forcePathStyle: true, + maxAttempts: 1, + requestHandler: sharedHttpHandler, + }), + iamClient: new IAMClient({ + credentials, + tls: false, + endpoint: VAULT_ENDPOINT, + region: 'us-east-1', + maxAttempts: 1, + requestHandler: sharedHttpHandler, + }), + }; +}; + +const loadKafkaTopics = async (): Promise => { + const labelSelector = + 'app.kubernetes.io/name=cold-sorbet-config-e2e-azure-archive' + + `,${INSTANCE_SELECTOR}`; + const raw = await getSecretByLabel(labelSelector, 'config.json'); + const config = JSON.parse(raw) as Record; + return { + DeadLetterQueue: config['kafka-dead-letter-topic'], + ObjectTasks: config['kafka-object-task-topic'], + GcRequest: config['kafka-gc-request-topic'], + }; +}; + +const loadServiceUsers = async (): Promise => { + const lcBp = 'backbeat-lifecycle-bp-1'; + const lcConductor = 'backbeat-lifecycle-conductor-1'; + const lcOp = 'backbeat-lifecycle-op-1'; + const qp = 'backbeat-qp-1'; + const serviceUsers: Record = { + 'backbeat-lcbp-user-creds': lcBp, + 'backbeat-lcc-user-creds': lcConductor, + 'backbeat-lcop-user-creds': lcOp, + 'backbeat-qp-user-creds': qp, + }; + const fetchedCreds: Record = {}; + for (const [secretName, userName] of Object.entries(serviceUsers)) { + const label = `app.kubernetes.io/name=${secretName},${INSTANCE_SELECTOR}`; + const raw = await getSecretByLabel(label, `${userName}.json`); + fetchedCreds[userName] = JSON.parse(raw) as ServiceUserCredentials; + } + const sorbetSelector = + `app.kubernetes.io/name=sorbet-fwd-creds,${INSTANCE_SELECTOR}`; + const accessKey = await getSecretByLabel(sorbetSelector, 'accessKey'); + const secretKey = await getSecretByLabel(sorbetSelector, 'secretKey'); + return { + BackbeatLifecycleBp1: fetchedCreds[lcBp], + BackbeatLifecycleConductor1: fetchedCreds[lcConductor], + BackbeatLifecycleOp1: fetchedCreds[lcOp], + BackbeatQp1: fetchedCreds[qp], + SorbetFwd2: { accessKey, secretKey }, + }; +}; + +const loadPRACredentials = async (): Promise => { + const praSecretName = `${ZENKO_NAME}-pra-management-vault-admin-creds.v1`; + try { + const accessKey = await getSecretByName(praSecretName, 'accessKey'); + const secretKey = await getSecretByName(praSecretName, 'secretKey'); + return { accessKey, secretKey }; + } catch { + // PRA admin credentials are optional : may not exist for non-PRA runs + return undefined; + } +}; + +const loadAdminCredentials = async (): Promise => { + const secretName = `${ZENKO_NAME}-management-vault-admin-creds.v1`; + const accessKey = await getSecretByName(secretName, 'accessKey'); + const secretKey = await getSecretByName(secretName, 'secretKey'); + return { accessKey, secretKey }; +}; + +const loadZenkoAccount = async (): Promise => { + const secretName = `${ZENKO_NAME}-account-zenko`; + const accessKeyId = await getSecretByName(secretName, 'AccessKeyId'); + const secretAccessKey = await getSecretByName(secretName, 'SecretAccessKey'); + let sessionToken: string | undefined; + try { + sessionToken = await getSecretByName(secretName, 'SessionToken'); + } catch { + sessionToken = undefined; + } + return { accessKeyId, secretAccessKey, sessionToken }; +}; + +const loadZenkoCR = async (): Promise => { + const cr = await getCustomObject( + 'zenko.io', 'v1alpha2', 'zenkos', ZENKO_NAME, + ) as unknown as ZenkoCR; + return { + TimeProgressionFactor: Number(cr.metadata.annotations?.['zenko.io/time-progression-factor'] || '1'), + InstanceID: cr.status.instanceID, + KafkaCleanerInterval: cr.spec.kafkaCleaner.interval || '', + SorbetdRestoreTimeout: cr.spec.sorbet.server.azure.restoreTimeout || '', + UtilizationServiceHost: cr.spec.scuba.api.ingress.hostname || '', + }; +}; diff --git a/tests/functional/tests_common/kubernetes.ts b/tests/functional/tests_common/kubernetes.ts new file mode 100644 index 0000000000..1548f2f60b --- /dev/null +++ b/tests/functional/tests_common/kubernetes.ts @@ -0,0 +1,71 @@ +import { CacheHelper, KubernetesHelper, Utils } from 'cli-testing'; +import { + V1Job, + Watch, + V1ObjectMeta, + AppsV1Api, + V1Deployment, + AppsApi, + CustomObjectsApi, + V1PersistentVolumeClaim, + CoreV1Api, + BatchV1Api, + V1Pod, +} from '@kubernetes/client-node'; + +const ensureClients = () => { + // TODO : review this + if (!KubernetesHelper.clientCore) { + KubernetesHelper.init(CacheHelper.parameters); + } +}; + +export const getSecretByLabel = async ( + labelSelector: string, + dataField: string, + namespace = 'default', +): Promise => { + ensureClients(); + const coreClient = KubernetesHelper.clientCore as CoreV1Api; + const secretList = await coreClient.listNamespacedSecret({ + namespace, + labelSelector, + }); + const secret = secretList.items[0]; + if (!secret?.data?.[dataField]) { + throw new Error( + `Secret field "${dataField}" not found for label "${labelSelector}"`, + ); + } + return Buffer.from(secret.data[dataField], 'base64').toString('utf-8'); +} + +export const getSecretByName = async ( + secretName: string, + dataField: string, + namespace = 'default', +): Promise => { + ensureClients(); + const coreClient = KubernetesHelper.clientCore as CoreV1Api; + const secret = await coreClient.readNamespacedSecret({ name: secretName, namespace }); + if (!secret?.data?.[dataField]) { + throw new Error( + `Secret field "${dataField}" not found in secret "${secretName}"`, + ); + } + return Buffer.from(secret.data[dataField], 'base64').toString('utf-8'); +} + +export const getCustomObject = async ( + group: string, + version: string, + plural: string, + name: string, + namespace = 'default', +): Promise> => { + ensureClients(); + const customClient = KubernetesHelper.customObject as CustomObjectsApi; + return await customClient.getNamespacedCustomObject({ + group, version, namespace, plural, name, + }) as Record; +} \ No newline at end of file diff --git a/tests/functional/tsconfig.json b/tests/functional/tsconfig.json index 90436de81d..8d86c14019 100644 --- a/tests/functional/tsconfig.json +++ b/tests/functional/tsconfig.json @@ -17,7 +17,8 @@ "cli-testing": ["./node_modules/cli-testing"], "common/*": ["./ctst/common/*"], "steps/*": ["./ctst/steps/*"], - "world/*": ["./ctst/world/*"] + "world/*": ["./ctst/world/*"], + "tests_common/*": ["./tests_common/*"] }, "esModuleInterop": true }, @@ -29,7 +30,9 @@ "ignore": ["(?:^|/)node_modules/(?!cli-testing)"] }, "include": [ - "ctst/**/*" + "ctst/**/*", + "tests_common/**/*", + "mocha/**/*.ts" ], "exclude": [ "node_modules"