diff --git a/actions/plugins-e2e-tests/.gitignore b/actions/plugins-e2e-tests/.gitignore new file mode 100644 index 000000000..479ff5ee3 --- /dev/null +++ b/actions/plugins-e2e-tests/.gitignore @@ -0,0 +1,17 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +node_modules/ + +coverage/ +dist/ + +.*.bun-build + +# Editor +.idea diff --git a/actions/plugins-e2e-tests/CHANGELOG.md b/actions/plugins-e2e-tests/CHANGELOG.md new file mode 100644 index 000000000..0b63e2fd2 --- /dev/null +++ b/actions/plugins-e2e-tests/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.0.1 + +- Create new action plugins-e2e-tests to run tests aginst pre-selected stack. diff --git a/actions/plugins-e2e-tests/README.md b/actions/plugins-e2e-tests/README.md new file mode 100644 index 000000000..e5e74c936 --- /dev/null +++ b/actions/plugins-e2e-tests/README.md @@ -0,0 +1,60 @@ +# Run e2e tests from plugins against specific stack + +This is a [GitHub Action][github-action] that help the execution of e2e tests on any plugin against specific selected stacks. +You need to define in which region the selected stack belong, the plugin from where are executed the tests and optionally which other plugins and datasources you want to provision when starting a Grafana instance. +Also, you need to have the **playwright** configuration and the test specifications in the plugin that run the tests and the action will do the rest. +This action use the following input parameters to run: + +| Name | Description | Default | Required | +| --------------------- | ----------------------------------------------------------------------------------------------- | ----------------- | -------- | +| `plugin_id` | Name of the plugin running the tests | | Yes | +| `stack_slug` | Name of the stack where you want to run the tests | | Yes | +| `env` | Region of the stack where you want to run the tests | | Yes | +| `other_plugins` | List of other plugins that you want to enable separated by comma | | No | +| `datasource_ids` | List of data sources that you want to enable separated by comma | | No | +| `upload_report_path ` | Name of the folder where you want to store the test report | playwright-report | No | +| `upload_videos_path` | Name of the folder where you want to store the test videos | playwright-videos | No | +| `plugin-secrets` | A JSON string containing key-value pairs of specific plugin secrets necessary to run the tests. | | No | + +## Example workflows + +This is an example of how you could use this action. + +```yml +name: Build and Test PR + +on: + pull_request: + +jobs: + e2e-tests: + permissions: + contents: write + id-token: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Get plugin specific secrets + id: get-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@5d7e361bc7e0a183cde8afe9899fb7b596d2659b # v1.2.0 + with: + repo_secrets: | + MY_SECRET1=test:token1 + MY_SECRET2=test:token2 + + - name: Run e2e cross app tests + id: e2e-cross-apps-tests + uses: grafana/shared-workflows/actions/plugins-e2e-tests@main + with: + stack_slug: "awsintegrationrevamp" + env: "dev-central" + plugin_id: "grafana-csp-app" + other_plugins: "grafana-k8s-app,grafana-asserts-app" + datasource_ids: "grafanacloud-awsintegrationrevamp-prom,grafanacloud-awsintegrationrevamp-logs" + upload_report_path: "playwright-cross-apps-report" + upload_videos_path: "playwright-cross-apps-videos" + plugin-secrets: ${{ ${{ steps.get-secrets.outputs.vault_secrets }} }} +``` diff --git a/actions/plugins-e2e-tests/action.yml b/actions/plugins-e2e-tests/action.yml new file mode 100644 index 000000000..17a59a0bd --- /dev/null +++ b/actions/plugins-e2e-tests/action.yml @@ -0,0 +1,167 @@ +name: Run e2e tests +description: Run e2e tests against specific stack and environment +inputs: + plugin_id: + description: "Name of the plugin running the tests" + required: true + + stack_slug: + description: "Name of the stack where you want to run the tests" + required: true + + env: + description: "Region of the stack where you want to run the tests" + required: true + + other_plugins: + description: "List of other plugins that you want to enable separated by comma" + required: false + + datasource_ids: + description: "List of data sources that you want to enable separated by comma" + required: false + + upload_report_path: + description: "Name of the artifact where you want to store the test report" + required: false + default: "playwright-report" + + upload_videos_path: + description: "Name of the artifact where you want to store the test videos" + required: false + default: "playwright-videos" + + plugin-secrets: + description: "A JSON string containing key-value paris of specific plugin secrets necessary to run the tests." + required: false + +runs: + using: "composite" + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Setup Node.js environment + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "20" + cache: "yarn" + + - name: Install e2e action dependencies + run: yarn install + shell: bash + + - uses: google-github-actions/auth@8254fb75a33b976a221574d287e93919e6a36f70 # v2.1.6 + id: gcloud-auth + with: + token_format: access_token + workload_identity_provider: "projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider" + service_account: "github-cloud-npm-dev-pkgs@grafanalabs-workload-identity.iam.gserviceaccount.com" + + - name: NPM registry auth + run: npx google-artifactregistry-auth --credential-config + working-directory: ${{ github.workspace }} + shell: bash + + - name: Install plugin dependencies + run: yarn install --immutable --prefer-offline + working-directory: ${{ github.workspace }} + shell: bash + + - name: Build frontend + run: yarn run build + working-directory: ${{ github.workspace }} + shell: bash + + - name: Get common secrets + id: get-common-secrets + uses: grafana/shared-workflows/actions/get-vault-secrets@5d7e361bc7e0a183cde8afe9899fb7b596d2659b # v1.2.0 + with: + common_secrets: | + HG_TOKEN=hg-ci:token + + - name: Set plugin secrets as environment variables + id: set-env-vars + if: ${{ inputs.plugin-secrets != '' }} + shell: bash + env: + SECRETS_JSON: "${{ inputs.plugin-secrets }}" + run: | + echo "Parsing and setting plugin environment variables..." + echo "$SECRETS_JSON" | jq -r 'to_entries[] | "echo \"\(.key)=\(.value)\" >> $GITHUB_ENV"' | bash + echo "Plugin environment variables set." + + - name: Generate provisioning + shell: bash + run: npx plop e2e-testing-provisioning + working-directory: ${{ github.workspace }} + env: + E2E_STACK_SLUG: ${{ inputs.stack_slug }} + E2E_ENV: ${{ inputs.env }} + HG_TOKEN: ${{ env.HG_TOKEN }} + E2E_PLUGIN_ID: ${{ inputs.plugin_id }} + E2E_OTHER_PLUGINS: ${{ inputs.other_plugins }} + E2E_DATASOURCE_IDS: ${{ inputs.datasource_ids }} + + - name: Start server + run: docker compose up -d --build --quiet-pull --timestamps + working-directory: ${{ github.workspace }} + shell: bash + + - name: Install Playwright Browsers + run: npx playwright install chromium --with-deps + working-directory: ${{ github.workspace }} + shell: bash + + - name: Run Playwright tests + shell: bash + env: + NODE_ENV: production + run: | + echo "Waiting for Grafana to be available..." + timeout=300 # 5 minutes timeout + start_time=$(date +%s) + + while ! docker logs grafana-csp-app 2>&1 | grep "Usage stats are ready to report" > /dev/null; do + current_time=$(date +%s) + elapsed=$((current_time - start_time)) + + if [ $elapsed -ge $timeout ]; then + echo "Timeout reached: Grafana did not become ready within 5 minutes." + exit 1 + fi + + echo "Waiting for Grafana..." + sleep 5 # Wait for 5 seconds before checking again + done + + echo "Grafana is ready!" + npx playwright test + working-directory: ${{ github.workspace }} + + - name: Stop grafana docker + run: docker compose down + working-directory: ${{ github.workspace }} + shell: bash + + - name: Upload E2E report + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ inputs.upload_report_path }} + path: playwright-report/ + retention-days: 30 + + - name: Upload E2E videos + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ inputs.upload_videos_path }} + path: test-results/ + retention-days: 30 + +branding: + icon: "shield" + color: "green" diff --git a/actions/plugins-e2e-tests/package.json b/actions/plugins-e2e-tests/package.json new file mode 100644 index 000000000..be075bc4b --- /dev/null +++ b/actions/plugins-e2e-tests/package.json @@ -0,0 +1,30 @@ +{ + "name": "plugins-e2e-tests", + "version": "0.0.1", + "description": "Run e2e tests from plugins against specific stack and environment.", + "private": true, + "scripts": {}, + "keywords": [ + "e2e", + "github-action", + "playwright" + ], + "author": "", + "license": "ISC", + "dependencies": { + "js-yaml": "^4.1.0", + "plop": "^4.0.1" + }, + "devDependencies": { + "@types/bun": "1.2.22", + "@eslint/js": "9.35.0", + "@types/eslint__js": "9.14.0", + "eslint": "9.35.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-jest": "29.0.1", + "eslint-plugin-prettier": "5.5.4", + "prettier": "3.6.2", + "typescript": "5.9.2", + "typescript-eslint": "8.44.0" + } +} diff --git a/actions/plugins-e2e-tests/plop-templates/docker-compose.hbs.yaml b/actions/plugins-e2e-tests/plop-templates/docker-compose.hbs.yaml new file mode 100644 index 000000000..a426e3454 --- /dev/null +++ b/actions/plugins-e2e-tests/plop-templates/docker-compose.hbs.yaml @@ -0,0 +1,23 @@ +services: + grafana: + environment: + - GF_PLUGINS_PREINSTALL_SYNC={{{GF_PLUGINS_PREINSTALL_SYNC}}} + - GF_GRAFANA_COM_SSO_API_TOKEN={{{GF_GRAFANA_COM_SSO_API_TOKEN}}} + - GF_GRAFANA_COM_URL={{{GF_GRAFANA_COM_URL}}} + - GF_GRAFANA_COM_API_URL={{{GF_GRAFANA_COM_API_URL}}} + + container_name: "{{{GF_PLUGIN_ID}}}" + restart: on-failure + platform: "linux/amd64" + build: + context: ./.config + args: + grafana_image: ${GRAFANA_IMAGE:-grafana-enterprise} + grafana_version: ${GRAFANA_VERSION:-main} + ports: + - 3000:3000/tcp + volumes: + - ./dist:/var/lib/grafana/plugins/{{{GF_PLUGIN_ID}}} + - ./provisioning:/etc/grafana/provisioning + - ./provisioning/grafana.ini:/etc/grafana/grafana.ini + - ./provisioning/license.jwt:/etc/grafana/license.jwt diff --git a/actions/plugins-e2e-tests/plopfile.mjs b/actions/plugins-e2e-tests/plopfile.mjs new file mode 100644 index 000000000..a469f4937 --- /dev/null +++ b/actions/plugins-e2e-tests/plopfile.mjs @@ -0,0 +1,473 @@ +import fs from "fs"; +import yaml from "js-yaml"; +import * as path from "path"; + +function isError(response) { + if (!response || typeof response !== "object") { + return false; + } + const keys = Object.keys(response); + return keys.includes("code") || keys.includes("message"); +} + +/** + * Checks if a value is null, undefined, an empty array, or an object with no enumerable properties. + * @param {any} value The value to check. + * @returns {boolean} True if the value is empty, otherwise false. + */ +function isEmpty(value) { + // console.log('Check is empty for: ', JSON.stringify(value)); + if (value === null || typeof value === "undefined") { + return true; + } + if (Array.isArray(value)) { + return value.length === 0; + } + if (typeof value === "string") { + return value.length === 0; + } + if (typeof value === "object") { + return Object.keys(value).length === 0; + } + return false; +} + +const HG_TOKEN = process.env.HG_TOKEN; +const APPS_YAML_FILE = path.join( + process.cwd(), + "./provisioning/plugins/apps.yaml", +); +const DATASOURCES_YAML_FILE = path.join( + process.cwd(), + "./provisioning/datasources/default.yaml", +); +const HG_REGION_SUFFIX_MAP = { + "prod-us-east": "prod-us-east-0", + "prod-eu-west": "prod-eu-west-0", + prod: "prod-us-central-0", + ops: "ops-eu-south-0", + "dev-east": "dev-us-east-0", + "dev-central": "dev-us-central-0", +}; + +const gcloudDSPattern = /grafanacloud-(\w+)-([a-z-]+)/; + +/** + * getProvisionedDSType returns the provisioned datasource type and returns an empty string if it doesn't match the pattern and the slug. + * @param datasourceName The full name of the datasource (e.g., "grafanacloud-my-slug-traces"). + * @param slug The expected slug (e.g., "my-slug"). + * @returns The datasource type (e.g., "traces"), or an empty string if criteria are not met. + */ +function getProvisionedDSType(datasourceName, slug) { + const match = gcloudDSPattern.exec(datasourceName); + if (match && match.length >= 3 && match[1] === slug) { + return match[2]; + } + return ""; +} + +/** + * Creates a predictable UID for Grafana Cloud datasources. + * If the datasource matches the pattern (e.g., grafanacloud--), the UID is simplified to "grafanacloud-"; otherwise, it uses the full name. + * @param dataSource The dataSource provisioned object + * @param stackSlug The expected slug (e.g., "staging"). + * @returns A UID string, guaranteed to be 40 characters or less. + */ +function getUid(dataSource, stackSlug) { + const datasourceName = dataSource.name; + let uid = datasourceName; + + const provisionedDSType = getProvisionedDSType(datasourceName, stackSlug); + if (provisionedDSType !== "") { + uid = "grafanacloud-" + provisionedDSType; + } + const maxLength = 40; + if (uid.length > maxLength) { + uid = uid.slice(uid.length - maxLength); + } + return uid; +} + +function formatDataSource(dataSource, stackSlug) { + if (dataSource) { + const uid = !dataSource.uid + ? getUid(dataSource, stackSlug) + : dataSource.uid; + return { + name: dataSource.name, + type: dataSource.type, + ...(uid && { uid }), + url: dataSource.url, + basicAuth: dataSource.basicAuth === 1 || dataSource.basicAuth === true, + basicAuthUser: dataSource.basicAuthUser + ? Number(dataSource.basicAuthUser) + : undefined, + isDefault: dataSource.isDefault === 1 || dataSource.isDefault === true, + jsonData: dataSource.jsonData, + secureJsonData: { + basicAuthPassword: dataSource.basicAuthPassword, + }, + }; + } + return dataSource; +} + +function removeEmptyProperties(obj) { + if (!obj || isEmpty(obj)) { + return obj; + } + // Check if the input is an object or an array + if (Array.isArray(obj)) { + // If it's an array, recursively clean each element + return obj + .map((item) => removeEmptyProperties(item)) + .filter((item) => item !== null && typeof item !== "undefined"); + } + + // Check if the input is a plain object + if (typeof obj === "object" && obj !== null) { + const newObj = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const value = obj[key]; + + // Recursively clean nested objects/arrays + const cleanedValue = removeEmptyProperties(value); + + // Check for empty values and skip them + if ( + cleanedValue !== "" && + cleanedValue !== null && + cleanedValue !== undefined && + !(Array.isArray(cleanedValue) && cleanedValue.length === 0) && + !( + typeof cleanedValue === "object" && + Object.keys(cleanedValue).length === 0 + ) + ) { + newObj[key] = cleanedValue; + } + } + } + return newObj; + } + + // If the value is not an object or array, return it as is + return obj; +} + +function isProdEnvironment(env) { + const envType = env.split("-")[0]; + return envType === "prod"; +} + +/** + * Dynamically generates the HG base API URL based on the region selected. + * @param {string} env The environment region (e.g., "prod-us-east", "dev-central"). + * @returns {string} The constructed base API URL. + */ +function getBaseUrlByEnv(env) { + const envType = env.split("-")[0]; + + let domainSuffix = "grafana"; + if (["dev", "ops"].includes(envType)) { + domainSuffix = `grafana-${envType}`; + } + const regionSuffix = + HG_REGION_SUFFIX_MAP[env] || HG_REGION_SUFFIX_MAP["dev-central"]; + return `https://hg-api-${regionSuffix}.${domainSuffix}.net`; +} + +async function fetchMultipleAppConfigs(stackSlug, env, pluginIds) { + try { + const fetchPromises = pluginIds.map((pluginId) => + fetchAppConfig(stackSlug, env, pluginId), + ); + return await Promise.all(fetchPromises); + } catch (error) { + console.error("Error fetching multiple app configs:", error.message); + throw error; + } +} + +async function fetchAppConfig(stackSlug, env, pluginId) { + try { + const baseUrl = getBaseUrlByEnv(env); + const url = `${baseUrl}/instances/${stackSlug}/provisioned-plugins/${pluginId}`; + + const response = await fetch(url, { + headers: { + "User-Agent": `plop/${pluginId}-provisioning`, + Authorization: `Bearer ${HG_TOKEN}`, + }, + }); + return response.json(); + } catch (error) { + console.error("Error fetching app config", pluginId, ":", error.message); + throw error; + } +} + +async function fetchMultipleDatasources(stackSlug, env, datasourceNames) { + try { + const fetchPromises = datasourceNames.map((dsName) => + fetchDataSource(stackSlug, env, dsName), + ); + if (fetchPromises.length > 0) { + return Promise.all(fetchPromises); + } + return Promise.all([]); + } catch (error) { + console.error("Error fetching multiple data sources:", error.message); + throw error; + } +} + +async function fetchDataSource(stackSlug, env, datasourceName) { + try { + const baseUrl = getBaseUrlByEnv(env); + const url = `${baseUrl}/instances/${stackSlug}/datasources/${datasourceName}`; + const response = await fetch(url, { + headers: { + "User-Agent": `plop/${datasourceName}-provisioning`, + Authorization: `Bearer ${HG_TOKEN}`, + }, + }); + const dataSourceWithToken = await response.json(); + const dataSourceWithNoEmptyField = + removeEmptyProperties(dataSourceWithToken); + return formatDataSource(dataSourceWithNoEmptyField, stackSlug); + } catch (error) { + console.error( + "Error fetching datasource", + datasourceName, + ":", + error.message, + ); + throw error; + } +} + +function createDataSourcesYamlFile() { + const dir = path.dirname(DATASOURCES_YAML_FILE); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + const initialContent = { + apiVersion: 1, + prune: true, + + datasources: [], + }; + fs.writeFileSync(DATASOURCES_YAML_FILE, yaml.dump(initialContent)); + return initialContent; +} + +async function fetchGrafanaConfig(stackSlug, env, pluginId) { + try { + const baseUrl = getBaseUrlByEnv(env); + const url = `${baseUrl}/instances/${stackSlug}/config`; + const response = await fetch(url, { + headers: { + "User-Agent": `plop/${pluginId}-provisioning`, + Authorization: `Bearer ${HG_TOKEN}`, + }, + }); + return response.json(); + } catch (error) { + console.error("Error fetching gcom token:", error.message); + throw error; + } +} + +function createAppsYamlFile() { + const dir = path.dirname(APPS_YAML_FILE); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + const initialContent = { + apiVersion: 1, + + apps: [], + }; + fs.writeFileSync(APPS_YAML_FILE, yaml.dump(initialContent)); + return initialContent; +} + +function addAppConfigs(yamlData, appConfigs) { + appConfigs.forEach((appConfig) => { + if (appConfig.type === "grafana-asserts-app") { + appConfig.jsonData.instanceUrl = "http://localhost:3000"; + } + yamlData.apps.push(appConfig); + console.log(`App with type '${appConfig.type}' has been added`); + }); +} + +function addDataSourceConfigs(yamlData, dataSourceConfigs = []) { + dataSourceConfigs.forEach((dsConfig, i) => { + yamlData.datasources.push(dsConfig); + console.log( + `Data source with type '${dsConfig.type}' and name '${dsConfig.name}' has been added`, + ); + }); +} + +function writeDataSourcesYamlFile(yamlData) { + const yamlString = yaml.dump(yamlData); + fs.writeFileSync(DATASOURCES_YAML_FILE, yamlString); + console.log("default.yaml data source file has been updated."); +} + +function writeAppsYamlFile(yamlData) { + const yamlString = yaml.dump(yamlData); + + // just for asserts + const fixed = yamlString.replace( + "enableGrafanaManagedLLM: true", + "enableGrafanaManagedLLM: false", + ); + fs.writeFileSync(APPS_YAML_FILE, fixed); + console.log( + "apps.yaml plugins file has been updated. Asserts prop enableGrafanaManagedLLM was disabled", + ); +} + +async function fillAnswers(answers) { + if (isProdEnvironment(answers.ENV)) { + console.error( + "For security reason, you are not allowed to provision plugins locally on production environment.", + ); + process.exit(1); + } + if (!answers.PLUGIN_IDS || answers.PLUGIN_IDS.length === 0) { + console.error( + `No plugin was selected for the stack ${answers.STACK_SLUG} on environment ${answers.ENV}.`, + ); + process.exit(1); + } + if (!answers.DATASOURCE_IDS || answers.DATASOURCE_IDS.length === 0) { + console.error( + `No data source selected for the stack ${answers.STACK_SLUG} on environment ${answers.ENV}.`, + ); + process.exit(1); + } + + const appConfigs = await fetchMultipleAppConfigs( + answers.STACK_SLUG, + answers.ENV, + answers.PLUGIN_IDS, + ); + if (isError(appConfigs) || isEmpty(appConfigs)) { + console.error( + `No app config found for the stack ${answers.STACK_SLUG} on environment ${answers.ENV}.`, + ); + process.exit(1); + } + const yamlAppsData = createAppsYamlFile(); + addAppConfigs(yamlAppsData, appConfigs); + writeAppsYamlFile(yamlAppsData); + + const dataSourceConfigs = await fetchMultipleDatasources( + answers.STACK_SLUG, + answers.ENV, + answers.DATASOURCE_IDS, + ); + if (isError(dataSourceConfigs) || isEmpty(dataSourceConfigs)) { + console.error( + `The data sources ${answers.DATASOURCE_IDS} cannot be loaded from the stack ${answers.STACK_SLUG} on environment ${answers.ENV}.`, + ); + process.exit(1); + } + const yamlDataSourcesData = createDataSourcesYamlFile(); + addDataSourceConfigs(yamlDataSourcesData, dataSourceConfigs); + writeDataSourcesYamlFile(yamlDataSourcesData); + + const grafanaConfig = await fetchGrafanaConfig( + answers.STACK_SLUG, + answers.ENV, + answers.GF_PLUGIN_ID, + ); + if (isError(grafanaConfig) || isEmpty(grafanaConfig)) { + console.error( + `No grafana config found for plugin ${answers.GF_PLUGIN_ID} on the stack ${answers.STACK_SLUG} and environment ${answers.ENV}.`, + ); + process.exit(1); + } + answers.GF_GRAFANA_COM_SSO_API_TOKEN = + grafanaConfig.hosted_grafana.hg_auth_token; + + // Use hardcoded URL for ops stack when grafana_net.url is missing + const grafanaNetUrl = + answers.STACK_SLUG === "ops" && !grafanaConfig.grafana_net?.url + ? "https://grafana-ops.com" + : grafanaConfig.grafana_net.url; + + answers.GF_GRAFANA_COM_URL = grafanaNetUrl; + answers.GF_GRAFANA_COM_API_URL = `${grafanaNetUrl}/api`; + answers.GF_PLUGINS_PREINSTALL_SYNC = answers.PLUGIN_IDS.filter( + (p) => p !== answers.GF_PLUGIN_ID, + ).join(","); +} + +export default function (plop) { + plop.setHelper("env", (text) => process.env[text]); + + plop.setGenerator("e2e-testing-provisioning", { + prompts: [], + actions: [ + async function loadRemoteProvisioning(answers) { + try { + if (!HG_TOKEN) { + console.error("HG_TOKEN environment variable is not set."); + process.exit(1); + } + + if (!process.env.E2E_STACK_SLUG) { + console.error("E2E_STACK_SLUG environment variable is not set."); + process.exit(1); + } + + if (!process.env.E2E_PLUGIN_ID) { + console.error("E2E_PLUGIN_ID environment variable is not set."); + process.exit(1); + } + answers.STACK_SLUG = process.env.E2E_STACK_SLUG; + + if (isProdEnvironment(process.env.E2E_ENV)) { + throw "For security reason, you are not allowed to provision locally production environment."; + } + answers.ENV = process.env.E2E_ENV; + + answers.GF_PLUGIN_ID = process.env.E2E_PLUGIN_ID; + const otherPlugins = process.env.E2E_OTHER_PLUGINS + ? process.env.E2E_OTHER_PLUGINS.split(",").map((i) => i.trim()) + : []; + answers.PLUGIN_IDS = [process.env.E2E_PLUGIN_ID].concat(otherPlugins); + + answers.DATASOURCE_IDS = process.env.E2E_DATASOURCE_IDS + ? process.env.E2E_DATASOURCE_IDS.split(",").map((i) => i.trim()) + : []; + answers.GF_PLUGINS_PREINSTALL_SYNC = otherPlugins.join(","); + + await fillAnswers(answers); + + return "Remote Provisioning data loaded successfully for e2e tests."; + } catch (error) { + console.error("Failed to load Remote Provisioning:", error.message); + throw error; + } + }, + { + type: "add", + path: "./docker-compose.yaml", + templateFile: "plop-templates/docker-compose.hbs.yaml", + force: true, + }, + ], + }); +}