Skip to content

chore: mount vsix artifact in separated folder (#2602) #11753

chore: mount vsix artifact in separated folder (#2602)

chore: mount vsix artifact in separated folder (#2602) #11753

Workflow file for this run

# cspell:ignore oidc
name: ci
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the main branch
on:
merge_group:
branches: ["main", "devel/*"]
push:
branches: ["main", "devel/*"]
tags:
- "v*.*"
pull_request:
# 'closed' is missing to avoid double triggering on PR merge
# 'edited' is missing to allow us to edit PR title/description without triggering
types: [synchronize, opened, reopened]
branches: ["main", "devel/*"]
schedule:
- cron: "0 0,6,12,18 * * *"
workflow_dispatch:
inputs:
publish:
description: "Publish a pre-release"
required: false
default: "false"
concurrency:
group: ${{ github.workflow }}-${{ github.event.ref }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
# https://docs.github.com/en/actions/learn-github-actions/environment-variables
# https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
WSLENV: HOSTNAME:CI:FORCE_COLOR:GITHUB_ACTION:GITHUB_ACTION_PATH/p:GITHUB_ACTION_REPOSITORY:GITHUB_WORKFLOW:GITHUB_WORKSPACE/p:GITHUB_PATH/p:GITHUB_ENV/p:VIRTUAL_ENV/p:SKIP_PODMAN:SKIP_DOCKER:NODE_OPTIONS:MISE_ENV
# We define a hostname because otherwise the variable might not always be accessible on runners.
HOSTNAME: gha
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
preflight:
runs-on: ubuntu-24.04
container:
image: ghcr.io/ansible/ext-builder:latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MISE_TRUSTED_CONFIG_PATHS: /
continue-on-error: false
outputs:
commit_context: ${{ steps.extract_context.outputs.context }}
builder_files: ${{ steps.paths.outputs.builder_files }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # we need tags for dynamic versioning
show-progress: false
- name: Workaround for https://github.com/actions/runner/issues/2033
run: |
chown -R $(id -u):$(id -g) $PWD
- name: Check for builder-related file changes
id: paths
uses: dorny/paths-filter@v3
with:
filters: |
builder_files:
- .config/mise.toml
- .git/workflows/ci.yaml
- Containerfile
- pyproject.toml
- tools/builder.sh
- uv.lock
- name: task setup
timeout-minutes: 2 # expected under 10s for container builds
run: |
set -ex
pwd
git config --global --add safe.directory '*'
mise install
mise list
mise cfg
mise exec -- which python3
mise exec -- printenv NODE_OPTIONS || true
task setup && task setup --status
- name: Extract commit context from conventional commit
id: extract_context
shell: bash
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -e
# Use PR title if available, otherwise use commit message
if [[ -n "$PR_TITLE" ]]; then
MESSAGE="$PR_TITLE"
echo "Using PR title: $MESSAGE"
else
MESSAGE=$(git log -1 --pretty=%s)
echo "Using commit message: $MESSAGE"
fi
# Extract context/scope from conventional commit format: type(scope): message
# Matches patterns like: feat(mcp):, fix(ui):, chore(build):, etc.
if [[ "$MESSAGE" =~ ^[a-z]+\(([a-z0-9/-]+)\): ]]; then
CONTEXT="${BASH_REMATCH[1]}"
echo "Extracted context: $CONTEXT"
echo "context=$CONTEXT" >> "$GITHUB_OUTPUT"
else
echo "No context found in message"
echo "context=" >> "$GITHUB_OUTPUT"
fi
- name: task build
timeout-minutes: 2 # expected under 1 minutes
run: |
task build && task build --status
- name: task package
timeout-minutes: 2 # expected under 1 minutes
run: |
task als:package && task als:package --status
- name: Run context-specific command
if: >-
steps.extract_context.outputs.context != '' &&
!contains(fromJSON('["build", "deps", "docs", "lint"]'), steps.extract_context.outputs.context)
run: |
task ${{ steps.extract_context.outputs.context }}
- name: task dry
run: |
task dry
- name: task lint
timeout-minutes: 4
run: |
task lint
- name: task docs
timeout-minutes: 1
run: |
task docs && task docs --status
- name: task package
timeout-minutes: 2
run: |
task package && task package --status
- name: task finish
run: task finish
- name: Upload packages archive
uses: ansible/actions/upload-artifact@main
with:
# Do not use github.ref_name as it contains slashes and we cannot sanitize it
name: ansible-extension-build-${{ github.event.number || github.run_id }}.zip
path: |
out/ansible-*.vsix
out/*.tgz
if-no-files-found: error
retention-days: 90
- name: Report unexpected failures
if: ${{ always() && failure() && github.ref == 'refs/heads/main' }}
uses: ./.github/actions/report
with:
slack_webhook_url: ${{ secrets.DEVTOOLS_CI_SLACK_URL }}
build:
name: ${{ matrix.name }}
environment: ci
env:
SKIP_DOCKER: ${{ matrix.env.SKIP_DOCKER || 0 }}
SKIP_PODMAN: ${{ matrix.env.SKIP_PODMAN || 0 }}
IS_WSL: ${{ contains(matrix.name, 'wsl') && 1 || 0 }}
TASKFILE_ARGS: --output=group --output-group-begin='::group::{{.TASK}}' --output-group-end='::endgroup::'
defaults:
run:
shell: ${{ matrix.shell || 'bash'}}
# The type of runner that the job will run on
runs-on: ${{ matrix.os || 'ubuntu-24.04' }}
outputs:
can_release_to_npm: ${{ steps.package.outputs.can_release_to_npm }}
permissions:
contents: read
id-token: write # codecov actions
checks: read # codecov actions
strategy:
fail-fast: false
matrix:
# Avoid letting github do the matrix multiplication and use manual
# includes for each job, this gives us fine control over job name.
# Order is important, keep it alphabetical: docs, lint, test*
continue-on-error:
- false
os:
- ubuntu-24.04
task-name:
- test
name:
- test (linux)
id:
- test-linux
include:
- name: test (macos)
id: test-macos
task-name: test
os: macos-15-large
env:
SKIP_PODMAN: 1
SKIP_DOCKER: 1
# only until we fix some broken tests, as we need it to pass
# in order to enable the caching
continue-on-error: true
- name: test (wsl)
id: test-wsl
task-name: test
os: wsl-runner
runs-on: self-hosted
env:
SKIP_PODMAN: 1
SKIP_DOCKER: 1
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # we need tags for dynamic versioning
show-progress: false
- name: Run setup steps (composite action)
uses: ./.github/actions/setup
with:
job_name: ${{ matrix.name }}
# https://github.com/marketplace/actions/setup-wsl
- name: Activate WSL
if: contains(matrix.name, 'wsl') && (matrix.runs-on || '') != 'self-hosted'
uses: Vampire/setup-wsl@6a8db447be7ed35f2f499c02c6e60ff77ef11278 # v6.0.0
with:
distribution: Ubuntu-24.04
set-as-default: "true"
# '-i' seems to be the only option that loads .bashrc file that we need
# https://github.com/Vampire/setup-wsl/discussions/54
wsl-shell-command: "bash -i -eo pipefail"
# https://github.com/MicrosoftDocs/WSL/blob/main/WSL/wsl-config.md#L159
wsl-conf: |
[automount]
enabled = true
root = /
options = "metadata,umask=077"
[boot]
command=/etc/init.d/dbus start
[interop]
enabled = false
appendWindowsPath = false
[network]
hostname = wsl
additional-packages: curl
dbus
dirmngr
gawk
gcc
git
gpg
gpg-agent
jq
make
python3-dev
python3-full
python3-venv
qemu-user-static
tar
unzip
xvfb
# asdf nodejs plugin requires: dirmngr gpg curl gawk
# Workaround for: https://github.com/actions/runner/issues/1864
- name: Ensure HOME is defined
run: |
set -exuo pipefail
if [ -z "${HOME:-}" ]; then
HOME=$(getent passwd "$(id -u)" | cut -d: -f6)
export HOME
fi
echo "HOME=$HOME" >> $GITHUB_ENV
- name: Ensure .env file is automatically loaded (mise)
run: |
mise reshim
mise doctor
test "${VIRTUAL_ENV:-}" = "${HOME}/.local/share/virtualenvs/vsa" || {
echo "VIRTUAL_ENV mismatch"
exit 99
}
test "$(mise exec -- which python3)" = "${HOME}/.local/share/virtualenvs/vsa/bin/python3" || {
echo "::warning::python3 mismatch $(mise exec -- which python3) != ${HOME}/.local/share/virtualenvs/vsa/bin/python3"
exit 98
}
- name: task setup
# starting podman machine can randomly get stuck on macos
timeout-minutes: 25
run: task setup && task setup --status
id: setup
- name: task build
id: build
run: |
task build && task build --status
## uncomment to debug on GHA runner
# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3
- name: task package
id: package
run: |
task package ${{ matrix.env.TASKFILE_ARGS }} && task package ${{ matrix.env.TASKFILE_ARGS }} --status
- name: configure podman
if: ${{ matrix.name == 'test (linux)' }}
run: |
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
mkdir -p ~/.config/containers
cat <<EOT >> ~/.config/containers/containers.conf
[engine]
cgroup_manager="cgroupfs"
EOT
podman info
- name: save ready_to_test=true
id: ready_to_test
if: ${{ contains(matrix.name, 'test') && success() }}
run: echo "ready_to_test=true" >> "$GITHUB_OUTPUT"
- name: task ${{ matrix.task-name }}
if: "${{ !contains(matrix.name, 'test') && steps.ready_to_test.outputs.ready_to_test == 'true' }}"
run: task ${{ matrix.task-name }} ${{ matrix.env.TASKFILE_ARGS }} && task ${{ matrix.task-name }} ${{ matrix.env.TASKFILE_ARGS }} --status
- name: task unit (ext, vue, als, mcp)
if: contains(matrix.name, 'test') && steps.ready_to_test.outputs.ready_to_test == 'true'
run: |
task unit ${{ matrix.env.TASKFILE_ARGS }} && task unit ${{ matrix.env.TASKFILE_ARGS }} --status
- name: task e2e (vscode-test)
# https://github.com/ansible/vscode-ansible/issues/1451
if: ${{ !cancelled() && contains(matrix.name, 'test') && steps.ready_to_test.outputs.ready_to_test == 'true' }}
run: |
set -e
task build 2>out/log/build-before.txt
task e2e ${{ matrix.env.TASKFILE_ARGS }}
task build 2>out/log/build-after.txt
task build --status --verbose
# Add these once e2e is fixed:
# || { task flush && task e2e ${{ matrix.env.TASKFILE_ARGS }}; }
# task e2e ${{ matrix.env.TASKFILE_ARGS }} --status
- name: task ui-selenium
# runner.os=Linux even on windows if the runner was started from inside wsl!
if: ${{ runner.os == 'Linux' && !contains(matrix.name, 'wsl') }}
run: |
task ui-selenium
task build --status
env:
# defined inside 'ci' environment
LIGHTSPEED_API_ENDPOINT: ${{ secrets.LIGHTSPEED_API_ENDPOINT }}
LIGHTSPEED_USER: ${{ secrets.LIGHTSPEED_USER }}
LIGHTSPEED_PASSWORD: ${{ secrets.LIGHTSPEED_PASSWORD }}
timeout-minutes: 30
- name: task finish
run: task finish
- name: Remove invalid files
if: ${{ always() }}
run: |
find out -name '*\?*' -exec rm -r {} \; || true
find out -name '*"*' -exec rm -r {} \; || true
find out -name '*:*' -exec rm -r {} \; || true
rm -rf out/vitebuild
- name: Upload test logs and reports as logs-${{ steps.setup.outputs.OS_VERSION }}-${{ matrix.task-name }}.zip
if: ${{ !cancelled() }}
uses: ansible/actions/upload-artifact@main
with:
name: logs-${{ steps.setup.outputs.OS_VERSION }}-${{ matrix.id }}-${{ github.run_attempt }}.zip
path: |
out/als
out/coverage
out/e2e
out/junit
out/log
out/mcp
out/ui*
out/unit
out/server
out/client
out/vitebuild
# we collect transpiled js files as sonar needs them to compute code coverage
if-no-files-found: ignore
retention-days: 90
- name: Upload test coverage data to codecov.io
if: ${{ always() && hashFiles('out/coverage/**/*coverage.xml') != '' }}
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
name: ${{ matrix.name }}
files: ./out/coverage/**/*coverage.xml
disable_search: true
fail_ci_if_error: true
use_oidc: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) }}
- name: Upload junit test results to codecov.io
if: ${{ !cancelled() && hashFiles('out/junit/**/*.xml') != '' }}
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
directory: out/junit
fail_ci_if_error: true
handle_no_reports_found: true
plugins: noop
report_type: test_results
name: ${{ matrix.id }}
# unable to use wildcards yet due to https://github.com/codecov/test-results-action/issues/110
flags: ${{ steps.setup.outputs.OS_VERSION }},${{ steps.setup.outputs.ARCH }}
use_oidc: ${{ github.event_name == 'merge_group' || github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }}
- name: Show git ignored files to debug "task ... --status" failures
if: ${{ always() && failure() }}
run: |
git diff
git status --porcelain --ignored
- name: Report unexpected failures on slack
if: ${{ always() && failure() && github.ref == 'refs/heads/main' }}
uses: ./.github/actions/report
with:
slack_webhook_url: ${{ secrets.DEVTOOLS_CI_SLACK_URL }}
builder-image:
runs-on: ubuntu-24.04
needs: [preflight]
if: needs.preflight.outputs.builder_files == 'true'
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push container image
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./tools/builder.sh ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' && '--push' || '' }}
check: # This job does nothing and is only used for the branch protection
needs:
- build
- builder-image
if: always() && !cancelled() && needs.build.result == 'success' && (needs.builder-image.result == 'success' || needs.builder-image.result == 'skipped')
permissions:
checks: read # codecov
contents: write # slack report
id-token: write # codecov
pull-requests: read # slack report
runs-on: ubuntu-24.04
steps:
- name: Checkout Source # needed by codecov uploader
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
# needed for pycobertura
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v7
- name: Download artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
path: .
- name: Remove invalid files
if: ${{ always() }}
run: |
find . -name '*\?*' -exec rm -r {} \; || true
find . -name '*"*' -exec rm -r {} \; || true
find . -name '*:*' -exec rm -r {} \; || true
- name: pycobertura diff across different platform runs
run: |
git diff
git status --porcelain --ignored
LINUX_COVERAGE=$(ls -1 logs-*linux*.zip/coverage/unit/cobertura-coverage.xml | sort | tail -1)
MACOS_COVERAGE=$(ls -1 logs-*macos*.zip/coverage/unit/cobertura-coverage.xml | sort | tail -1)
WSL_COVERAGE=$(ls -1 logs-*wsl*.zip/coverage/unit/cobertura-coverage.xml | sort | tail -1)
cp -f "${LINUX_COVERAGE}" linux.xml
cp -f "${MACOS_COVERAGE}" macos.xml
cp -f "${WSL_COVERAGE}" wsl.xml
# linux vs macos (tool needs source code to be present)
uv tool run pycobertura diff linux.xml macos.xml || true
# linux vs wsl (tool needs source code to be present)
uv tool run pycobertura diff linux.xml wsl.xml || true
- name: SonarCloud scan
# Run only for pull requests or push to main
if: >
${{ !cancelled() &&
hashFiles('**/*coverage.xml') != '' &&
(github.event_name == 'pull_request' ||
(github.event_name == 'push' && github.ref_name =='main')
)}}
uses: SonarSource/sonarqube-scan-action@v7
env:
SONAR_TOKEN: ${{ secrets.CICD_ORG_SONAR_TOKEN_CICD_BOT || secrets.AAP_ORG_SONAR_TOKEN_ANSIBLE_CICD_BOT }}
with:
args: ${{ env.SONAR_ARGS }}
# Temporarily ignore errors if the pull request is from a fork due to lack of upload secrets access
# See https://issues.redhat.com/browse/AAP-52660
continue-on-error: ${{ github.event_name == 'pull_request' && github.repository != github.event.pull_request.head.repo.full_name }}
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@release/v1 # that is a branch, not a tag
id: alls-green
with:
allowed-skips: builder-image
jobs: ${{ toJSON(needs) }}
- name: Report unexpected failures
if: ${{ always() && failure() && github.ref == 'refs/heads/main' }}
uses: ./.github/actions/report
with:
slack_webhook_url: ${{ secrets.DEVTOOLS_CI_SLACK_URL }}
publish:
if: github.ref_type == 'tag' || github.event.inputs.publish == 'true'
runs-on: ubuntu-latest
environment: release
needs:
- check
permissions:
contents: write
issues: write
checks: read
steps:
- name: Checkout Source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: node post install
run: |
corepack enable
npm config set fund false
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
- name: Download the artifact
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: ansible-extension-build-${{ github.event.number || github.run_id }}.zip
- name: Attach vsix to Github release
# cspell: ignore softprops
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
if: github.ref_type == 'tag'
with:
files: "*.vsix"
- run: |
yarn install --immutable
ls -la *.vsix
- name: Publish extension to marketplaces
run: |
./tools/helper --publish
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}
- name: Report unexpected failures
if: ${{ always() && failure() }}
uses: ./.github/actions/report
with:
slack_webhook_url: ${{ secrets.DEVTOOLS_CI_SLACK_URL }}
publish-npm:
environment: release
if: needs.build.outputs.can_release_to_npm == 'true' && (github.ref_type == 'tag' || github.event.inputs.publish == 'true')
runs-on: ubuntu-latest
needs:
- build
- check
permissions:
contents: write
issues: write
checks: read
steps:
- name: Download the artifact
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: "@ansible-ansible-language-server-build-${{ github.event.number || github.run_id }}.tgz"
- uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
- run: npm publish --access public @ansible-ansible-language-server-*.tgz
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Report unexpected failures
if: ${{ always() && failure() }}
uses: ./.github/actions/report
with:
slack_webhook_url: ${{ secrets.DEVTOOLS_CI_SLACK_URL }}