Test Analysis Plugins #393
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Test Analysis Plugins | |
| defaults: | |
| run: | |
| shell: bash | |
| on: | |
| schedule: | |
| - cron: '30 18 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| PRODUCT: | |
| description: 'Product to test' | |
| required: true | |
| type: choice | |
| options: | |
| - elasticsearch | |
| - opensearch | |
| MANUAL_VERSION: | |
| description: 'Version to test' | |
| required: false | |
| type: string | |
| default: '7.10.2' | |
| jobs: | |
| build-test-matrix: | |
| name: Build Test Matrix | |
| runs-on: ubuntu-latest | |
| outputs: | |
| # This output will be a JSON string like '[{"product": "...", "version": "..."}, ...]' | |
| # or '[]' if no tests are to be run. | |
| matrix_json: ${{ steps.generate_matrix.outputs.matrix_json_string }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup bootstrap | |
| uses: ./containers/bootstrap | |
| - name: Get Date One Day Ago for Schedule | |
| id: get_date_schedule | |
| if: ${{ github.event_name == 'schedule' }} | |
| run: | | |
| if [[ "$(uname)" == "Darwin" ]]; then | |
| echo "days_ago=$(date -u -v-3d +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT | |
| else | |
| echo "days_ago=$(date -u -d '3 days ago' +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT | |
| fi | |
| cat $GITHUB_OUTPUT | |
| - name: Generate Test Matrix Configuration | |
| id: generate_matrix | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_PRODUCT: ${{ inputs.PRODUCT }} | |
| INPUT_MANUAL_VERSION: ${{ inputs.MANUAL_VERSION }} | |
| SINCE_DATE_SCHEDULE: ${{ steps.get_date_schedule.outputs.days_ago }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| FINAL_MATRIX_ENTRIES="[]" | |
| # Function to create a JSON entry for the matrix | |
| generate_matrix_entry_json() { | |
| local prod="$1" | |
| local ver="$2" | |
| # Ensure product and version are not empty before creating entry | |
| if [[ -n "$prod" && -n "$ver" ]]; then | |
| echo "{\"product\": \"$prod\", \"version\": \"$ver\"}" | |
| fi | |
| } | |
| # Function to fetch latest stable version for a given product | |
| fetch_latest_stable_version() { | |
| local product_name="$1"; local repo_url="$2"; local version_prefix="$3"; local version_regex="$4"; | |
| local latest_version="" | |
| echo "Fetching latest stable for $product_name (for dispatch without manual version)..." | |
| RELEASES_PAYLOAD=$(curl -H "Authorization: Bearer $GH_TOKEN" "$repo_url?per_page=10") # Get a few recent | |
| latest_version=$(echo "$RELEASES_PAYLOAD" \ | |
| | jq -r --arg prefix "$version_prefix" --arg regex "$version_regex" '[.[] | | |
| select(.prerelease==false and .draft==false and (.tag_name | startswith($prefix)) and (.tag_name | test($regex))) | |
| | .tag_name | sub($prefix; "")] | .[0]') | |
| if [[ -n "$latest_version" && "$latest_version" != "null" ]]; then | |
| echo "$latest_version" | |
| fi | |
| } | |
| # Function to fetch recent (last 7 days) stable versions for a product | |
| fetch_recent_schedule_versions() { | |
| local product_name="$1"; local repo_url="$2"; local version_prefix="$3"; local version_regex="$4"; | |
| local current_matrix="$FINAL_MATRIX_ENTRIES" # Use the global accumulator | |
| echo "Fetching $product_name releases since $SINCE_DATE_SCHEDULE..." | |
| RELEASES_PAYLOAD=$(curl -H "Authorization: Bearer $GH_TOKEN" "$repo_url?per_page=10") | |
| RECENT_VERSIONS_FOUND=$(echo "$RELEASES_PAYLOAD" \ | |
| | jq -r --arg since_date "$SINCE_DATE_SCHEDULE" --arg prefix "$version_prefix" --arg regex "$version_regex" '[.[] | | |
| select(.prerelease==false and .draft==false and (.published_at >= $since_date) and (.tag_name | startswith($prefix)) and (.tag_name | test($regex))) | |
| | .tag_name | sub($prefix; "")] | unique | .[]') # Get unique versions, one per line | |
| if [[ -n "$RECENT_VERSIONS_FOUND" ]]; then | |
| echo "Found recent stable $product_name versions: $RECENT_VERSIONS_FOUND" | |
| for version_item in $RECENT_VERSIONS_FOUND; do | |
| entry_json=$(generate_matrix_entry_json "$product_name" "$version_item") | |
| if [[ -n "$entry_json" ]]; then | |
| current_matrix=$(echo "$current_matrix" | jq --argjson entry "$entry_json" '. + [$entry]') | |
| fi | |
| done | |
| else | |
| echo "No stable $product_name versions found published since $SINCE_DATE_SCHEDULE for schedule." | |
| fi | |
| FINAL_MATRIX_ENTRIES="$current_matrix" # Update the global accumulator | |
| } | |
| if [[ "$EVENT_NAME" == 'schedule' ]]; then | |
| fetch_recent_schedule_versions "elasticsearch" "https://api.github.com/repos/elastic/elasticsearch/releases" "v" "^v[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$" | |
| fetch_recent_schedule_versions "opensearch" "https://api.github.com/repos/opensearch-project/OpenSearch/releases" "" "^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$" | |
| elif [[ "$EVENT_NAME" == 'workflow_dispatch' ]]; then | |
| if [[ -n "$INPUT_MANUAL_VERSION" ]]; then | |
| echo "Using manually provided version: $INPUT_MANUAL_VERSION for product: $INPUT_PRODUCT" | |
| entry=$(generate_matrix_entry_json "$INPUT_PRODUCT" "$INPUT_MANUAL_VERSION") | |
| if [[ -n "$entry" ]]; then FINAL_MATRIX_ENTRIES=$(echo "$FINAL_MATRIX_ENTRIES" | jq --argjson entry "$entry" '. + [$entry]'); fi | |
| else | |
| echo "Manual version not provided for dispatch. Fetching latest stable for $INPUT_PRODUCT..." | |
| LATEST_VER="" | |
| if [[ "$INPUT_PRODUCT" == "elasticsearch" ]]; then | |
| LATEST_VER=$(fetch_latest_stable_version "elasticsearch" "https://api.github.com/repos/elastic/elasticsearch/releases" "v" "^v[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$") | |
| elif [[ "$INPUT_PRODUCT" == "opensearch" ]]; then | |
| LATEST_VER=$(fetch_latest_stable_version "opensearch" "https://api.github.com/repos/opensearch-project/OpenSearch/releases" "" "^[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9]+)?$") | |
| fi | |
| if [[ -n "$LATEST_VER" ]]; then | |
| echo "Latest stable for $INPUT_PRODUCT is $LATEST_VER" | |
| entry=$(generate_matrix_entry_json "$INPUT_PRODUCT" "$LATEST_VER") | |
| if [[ -n "$entry" ]]; then FINAL_MATRIX_ENTRIES=$(echo "$FINAL_MATRIX_ENTRIES" | jq --argjson entry "$entry" '. + [$entry]'); fi | |
| else | |
| echo "::error::Could not find latest stable version for $INPUT_PRODUCT on dispatch." | |
| # FINAL_MATRIX_ENTRIES will remain '[]', so the next job won't run due to its 'if' condition | |
| fi | |
| fi | |
| fi | |
| echo "Generated matrix entries (multiline for readability):" | |
| echo "$FINAL_MATRIX_ENTRIES" | |
| SINGLE_LINE_JSON=$(echo "$FINAL_MATRIX_ENTRIES" | jq -c .) | |
| echo "matrix_json_string=$SINGLE_LINE_JSON" >> $GITHUB_OUTPUT | |
| test-engine-plugins: | |
| name: Test Plugins on ${{ matrix.product }} @ ${{ matrix.version }} | |
| needs: build-test-matrix | |
| if: ${{ needs.build-test-matrix.outputs.matrix_json != '[]' }} | |
| runs-on: ubuntu-latest | |
| env: | |
| SECURITY_ENABLED: true | |
| ENGINE_PASSWORD: ${{ secrets.SEARCH_APIKEY }} | |
| JAVA_OPTS: '-Xms1g -Xmx1g' | |
| CONTAINER_NAME_BASE: 'search-engine-node' | |
| PORT: '9200' | |
| WAIT_SECONDS: '600' | |
| PRODUCT_ENV: ${{ matrix.product }} | |
| VERSION_ENV: ${{ matrix.version }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.build-test-matrix.outputs.matrix_json) }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup bootstrap | |
| uses: ./containers/bootstrap | |
| - name: Display Matrix Combination | |
| run: | | |
| echo | |
| echo Testing Product: ${{ matrix.product }}, Version: ${{ matrix.version }} | |
| if [[ "${{ matrix.product }}" == "elasticsearch" ]]; then | |
| MAJOR_VERSION=$(echo "${{ matrix.version }}" | cut -d. -f1) | |
| if [[ "$MAJOR_VERSION" -lt 8 ]]; then | |
| echo SECURITY_ENABLED=false >> $GITHUB_ENV | |
| echo "Security is not enabled for ${{ matrix.product }} when versions < 8." | |
| fi | |
| else | |
| echo SECURITY_ENABLED=falase >> $GITHUB_ENV | |
| echo "Security is not enabled for ${{ matrix.product }}." | |
| fi | |
| - name: Run Search Engine (${{ matrix.product }} @ ${{ matrix.version }}) | |
| uses: ./containers/search-engine | |
| with: | |
| engine-type: ${{ matrix.product }} | |
| engine-version: ${{ matrix.version }} | |
| security-enabled: ${{ env.SECURITY_ENABLED }} | |
| engine_password: ${{ env.ENGINE_PASSWORD }} | |
| port: ${{ env.PORT }} | |
| java-opts: ${{ env.JAVA_OPTS }} | |
| container-name: ${{ env.CONTAINER_NAME_BASE }}-${{ matrix.product }}-${{ matrix.version }}-${{ github.run_id }} | |
| plugins: 'analysis-ik,analysis-pinyin,analysis-stconvert' | |
| wait-for-seconds: ${{ env.WAIT_SECONDS }} | |
| - name: Setup Test Environment Variables | |
| id: setup_test_env | |
| run: | | |
| echo "Setting up test environment variables..." | |
| PROTOCOL_VAR="http" | |
| # Set default CURL options when use -no-rc to avoid .curlrc issues | |
| CURL_OPTS_VAR="-q -s --fail --connect-timeout 10 --max-time 30" | |
| AUTH_OPTS_VAR="" | |
| CURRENT_USER_VAR="" | |
| if [[ "${{ env.SECURITY_ENABLED }}" == "true" ]]; then | |
| PROTOCOL_VAR="https" | |
| CURL_OPTS_VAR="$CURL_OPTS_VAR -k" | |
| if [[ -n "${{ env.ENGINE_PASSWORD }}" ]]; then | |
| if [[ "${{ matrix.product }}" == "elasticsearch" ]]; then CURRENT_USER_VAR="elastic"; | |
| elif [[ "${{ matrix.product }}" == "opensearch" ]]; then CURRENT_USER_VAR="admin"; | |
| else echo "Error: Unknown product '${{ matrix.product }}' for user determination" && exit 1; fi | |
| AUTH_OPTS_VAR="-u $CURRENT_USER_VAR:${{ env.ENGINE_PASSWORD }}" | |
| else | |
| echo "Error: Security enabled for ${{ matrix.product }} but ENGINE_PASSWORD_GLOBAL is not set!" && exit 1 | |
| fi | |
| fi | |
| echo "PROTOCOL=${PROTOCOL_VAR}" >> $GITHUB_OUTPUT | |
| echo "CURL_OPTS=${CURL_OPTS_VAR}" >> $GITHUB_OUTPUT | |
| echo "AUTH_OPTS=${AUTH_OPTS_VAR}" >> $GITHUB_OUTPUT | |
| echo "BASE_URL=${PROTOCOL_VAR}://localhost:${{ env.PORT }}" >> $GITHUB_OUTPUT | |
| - name: Verify Loaded Plugins | |
| if: success() | |
| env: | |
| PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }} | |
| CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }} | |
| AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }} | |
| BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }} | |
| run: | | |
| PRODUCT_ENV="${{ matrix.product }}" | |
| echo "--- Verifying loaded plugins for $PRODUCT_ENV ---" | |
| PLUGIN_LIST_CMD="curl $CURL_OPTS $AUTH_OPTS '${BASE_URL}/_cat/plugins?h=component&format=txt'" | |
| echo "Executing: $PLUGIN_LIST_CMD" | |
| PLUGIN_LIST_OUTPUT=$(eval "$PLUGIN_LIST_CMD") | |
| if [ $? -ne 0 ]; then | |
| echo "Error: Failed to fetch plugin list from $BASE_URL/_cat/plugins for $PRODUCT_ENV" | |
| curl $CURL_OPTS $AUTH_OPTS "$BASE_URL" || echo "Engine root is also not responding for $PRODUCT_ENV." | |
| exit 1 | |
| fi | |
| echo "Installed plugins reported by $PRODUCT_ENV _cat/plugins:" | |
| echo "$PLUGIN_LIST_OUTPUT" | |
| echo "$PLUGIN_LIST_OUTPUT" | grep -q "analysis-ik" || (echo "Error: IK plugin not found for $PRODUCT_ENV!" && exit 1) | |
| echo "$PLUGIN_LIST_OUTPUT" | grep -q "analysis-pinyin" || (echo "Error: Pinyin plugin not found for $PRODUCT_ENV!" && exit 1) | |
| echo "$PLUGIN_LIST_OUTPUT" | grep -q "analysis-stconvert" || (echo "Error: Stconvert plugin not found for $PRODUCT_ENV!" && exit 1) | |
| echo "All specified plugins are listed for $PRODUCT_ENV." | |
| - name: Verify IK Analyzer | |
| if: success() | |
| env: | |
| PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }} | |
| CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }} | |
| AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }} | |
| BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }} | |
| run: | | |
| PRODUCT_ENV="${{ matrix.product }}" | |
| echo "--- Verifying IK Analyzer for $PRODUCT_ENV ---" | |
| ANALYSIS_PAYLOAD_IK='{"analyzer": "ik_smart", "text": "中华人民共和国国歌"}' | |
| # Corrected: Use env vars for TNAME, VERSION_ENV, and github.run_id | |
| IK_ANALYSIS_RESULT_FILE="/tmp/ik_analysis_result_${PRODUCT_ENV}_${VERSION_ENV//./_}_${{ github.run_id }}.json" | |
| ANALYZE_CMD="curl $CURL_OPTS $AUTH_OPTS -w '%{http_code}' -o '$IK_ANALYSIS_RESULT_FILE' -X POST '${BASE_URL}/_analyze' -H 'Content-Type: application/json' -d '$ANALYSIS_PAYLOAD_IK'" | |
| HTTP_STATUS=$(eval "$ANALYZE_CMD") | |
| if [[ "$HTTP_STATUS" -ne 200 ]]; then | |
| echo "Error: IK analysis request failed with HTTP status $HTTP_STATUS for $PRODUCT_ENV. Response:" | |
| cat "$IK_ANALYSIS_RESULT_FILE" | |
| exit 1 | |
| fi | |
| # Check if the expected token is present in the analysis result | |
| if ! cat "$IK_ANALYSIS_RESULT_FILE" | jq -e '.tokens[] | select(.token=="中华人民共和国")'; then | |
| echo "Error: IK analysis for '中华人民共和国' failed on $PRODUCT_ENV." && exit 1 | |
| fi | |
| echo "IK Analyzer test passed for $PRODUCT_ENV." | |
| - name: Verify Pinyin Analyzer | |
| if: success() | |
| env: | |
| PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }} | |
| CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }} | |
| AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }} | |
| BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }} | |
| run: | | |
| PRODUCT_ENV="${{ matrix.product }}" | |
| echo "--- Verifying Pinyin Analyzer for $PRODUCT_ENV ---" | |
| ANALYSIS_PAYLOAD_PINYIN='{"analyzer": "pinyin", "text": "中华人民共和国国歌"}' | |
| # Corrected: Use env vars for TNAME, VERSION_ENV, and github.run_id | |
| PINYIN_ANALYSIS_RESULT_FILE="/tmp/pinyin_analysis_result_${PRODUCT_ENV}_${VERSION_ENV//./_}_${{ github.run_id }}.json" | |
| ANALYZE_CMD="curl ${{ env.CURL_OPTS }} ${{ env.AUTH_OPTS }} -w '%{http_code}' -o '$PINYIN_ANALYSIS_RESULT_FILE' -X POST '${{ env.BASE_URL }}/_analyze' -H 'Content-Type: application/json' -d '$ANALYSIS_PAYLOAD_PINYIN'" | |
| HTTP_STATUS=$(eval "$ANALYZE_CMD") | |
| if [[ "$HTTP_STATUS" -ne 200 ]]; then | |
| echo "Error: Pinyin analysis request failed with HTTP status $HTTP_STATUS for $PRODUCT_ENV. Response:" | |
| cat "$PINYIN_ANALYSIS_RESULT_FILE" | |
| exit 1 | |
| fi | |
| # Check if the expected token part is present in the analysis result | |
| if ! cat "$PINYIN_ANALYSIS_RESULT_FILE" | jq -e --arg token_part "zhrmghggg" '.tokens[] | select(.token | contains($token_part))'; then | |
| echo "Error: Pinyin analysis for '中华人民共和国国歌' did not produce expected token part 'zhrmghggg' on $PRODUCT_ENV." | |
| exit 1 | |
| fi | |
| echo "Pinyin Analyzer test passed for $PRODUCT_ENV." | |
| - name: Verify Stconvert Analyzer | |
| if: success() | |
| env: | |
| PROTOCOL: ${{ steps.setup_test_env.outputs.PROTOCOL }} | |
| CURL_OPTS: ${{ steps.setup_test_env.outputs.CURL_OPTS }} | |
| AUTH_OPTS: ${{ steps.setup_test_env.outputs.AUTH_OPTS }} | |
| BASE_URL: ${{ steps.setup_test_env.outputs.BASE_URL }} | |
| run: | | |
| PRODUCT_ENV="${{ matrix.product }}" | |
| echo "--- Verifying Stconvert Analyzer for $PRODUCT_ENV ---" | |
| INDEX_NAME="stconvert_index_${PRODUCT_ENV}_${VERSION_ENV//./_}_${{ github.run_id }}" | |
| STCONVERT_RESULT_FILE_PREFIX="/tmp/${INDEX_NAME}" | |
| # Define the index with stconvert analyzer | |
| INDEX_SETTINGS_PAYLOAD='{"settings":{"analysis":{"analyzer":{"my_st_analyzer":{"tokenizer":"standard","filter":["lowercase","st_token_filter"],"char_filter":["st_char_filter"]}},"filter":{"st_token_filter":{"type":"stconvert","convert_type":"t2s","keep_both":false,"delimiter":","}},"char_filter":{"st_char_filter":{"type":"stconvert","convert_type":"t2s"}}}}}' | |
| CREATE_INDEX_CMD="curl $CURL_OPTS $AUTH_OPTS -X PUT '${BASE_URL}/${INDEX_NAME}' -H 'Content-Type: application/json' -d '$INDEX_SETTINGS_PAYLOAD'" | |
| HTTP_STATUS_CREATE=$(eval "$CREATE_INDEX_CMD -w '%{http_code}' -o '${STCONVERT_RESULT_FILE_PREFIX}_create.json'") | |
| if [[ "$HTTP_STATUS_CREATE" -ne 200 && "$HTTP_STATUS_CREATE" -ne 201 ]]; then | |
| echo "Error: Failed to create index $INDEX_NAME for stconvert test. HTTP status: $HTTP_STATUS_CREATE. Response:" | |
| cat "${STCONVERT_RESULT_FILE_PREFIX}_create.json" | |
| exit 1 | |
| fi | |
| ANALYZE_PAYLOAD='{ | |
| "tokenizer": "keyword", | |
| "filter": ["lowercase", {"type": "stconvert", "convert_type": "t2s", "keep_both": false}], | |
| "char_filter": [{"type": "stconvert", "convert_type": "t2s"}], | |
| "text": "中華人民共和國國歌" | |
| }' | |
| ANALYZE_PAYLOAD="curl $CURL_OPTS $AUTH_OPTS -w '%{http_code}' -o '${STCONVERT_RESULT_FILE_PREFIX}_analyze.json' -X POST '${BASE_URL}/_analyze' -H 'Content-Type: application/json' -d '$ANALYZE_PAYLOAD'" | |
| HTTP_STATUS_ANALYZE=$(eval "$ANALYZE_PAYLOAD") | |
| if [[ "$HTTP_STATUS_ANALYZE" -ne 200 ]]; then | |
| echo "Error: Stconvert direct analysis (test 2) request failed with HTTP status $HTTP_STATUS_ANALYZE. Response:" | |
| cat "${STCONVERT_RESULT_FILE_PREFIX}_analyze.json" | |
| exit 1 | |
| fi | |
| # Verify output (single token expected due to keyword tokenizer) | |
| if ! cat "${STCONVERT_RESULT_FILE_PREFIX}_analyze.json" | jq -e --arg token_part "中华人民共和国国歌" '.tokens[] | select(.token | contains($token_part))'; then | |
| echo "Error: Stconvert analysis for '中華人民共和國國歌' did not produce expected token part '中华人民共和国国歌' on $PRODUCT_ENV." | |
| exit 1 | |
| fi | |
| echo "Stconvert Analyzer test passed for $PRODUCT_ENV." | |
| notify_on_failure: | |
| runs-on: ubuntu-latest | |
| needs: [build-test-matrix, test-engine-plugins] | |
| if: failure() | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Notify message via feishu | |
| env: | |
| FEISHU_BOT_URL: ${{ secrets.FEISHU_BOT_WEBHOOK_URL }} | |
| REPO_NAME: ${{ github.repository }} | |
| WORKFLOW_NAME: ${{ github.workflow }} | |
| RUN_ID: ${{ github.run_id }} | |
| ACTOR: ${{ github.triggering_actor }} | |
| SERVER_URL: ${{ github.server_url }} | |
| run: $GITHUB_WORKSPACE/scripts/feishu_message.sh |