diff --git a/.github/workflows/aws-api-mcp-upgrade-version.yml b/.github/workflows/aws-api-mcp-upgrade-version.yml new file mode 100644 index 0000000000..14e752526a --- /dev/null +++ b/.github/workflows/aws-api-mcp-upgrade-version.yml @@ -0,0 +1,222 @@ +--- +name: AWS API MCP Server - Upgrade AWS CLI Version +description: | + This workflow upgrades the AWS CLI version in src/aws-api-mcp-server using uv upgrade + and creates a pull request with the changes. +on: + workflow_dispatch: + schedule: + - cron: '0 5 * * *' # Daily at 6 AM Amsterdam time (UTC+1) +env: + BOT_USER_EMAIL: ${{ vars.BOT_USER_EMAIL || '203918161+awslabs-mcp@users.noreply.github.com' }} + BOT_USER_NAME: ${{ vars.BOT_USER_NAME || 'awslabs-mcp' }} +permissions: + actions: none + attestations: none + checks: none + contents: none + deployments: none + discussions: none + id-token: none + issues: none + models: none + packages: none + pages: none + pull-requests: none + repository-projects: none + security-events: none + statuses: none +jobs: + upgrade-awscli: + name: Upgrade AWS CLI Version + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ secrets.BOT_GITHUB_TOKEN }} + - name: Install uv + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + - name: Create upgrade branch + id: create-branch + run: | + set -euo pipefail + + TIMESTAMP="$(date +'%Y%m%d%H%M%S')" + UPGRADE_BRANCH="upgrade/aws-api-mcp-awscli-$TIMESTAMP" + + echo "::debug::Creating upgrade branch: $UPGRADE_BRANCH" + + # Configure git user + git config --local user.email "${{ env.BOT_USER_EMAIL }}" + git config --local user.name "${{ env.BOT_USER_NAME }}" + + # Create and push branch + git checkout -b "$UPGRADE_BRANCH" + git push --set-upstream origin "$UPGRADE_BRANCH" + + # Verify branch was created + if ! git ls-remote --heads origin "$UPGRADE_BRANCH" | grep -q "$UPGRADE_BRANCH"; then + echo "::error::Failed to verify branch creation: $UPGRADE_BRANCH" >&2 + exit 1 + fi + + echo "upgrade-branch=$UPGRADE_BRANCH" >> $GITHUB_OUTPUT + echo "::debug::Successfully created upgrade branch: $UPGRADE_BRANCH" + - name: Check and upgrade AWS CLI version + id: upgrade + working-directory: src/aws-api-mcp-server + run: | + set -euo pipefail + + # Get current installed version + CURRENT_VERSION=$(uv run python -c "from importlib.metadata import version; print(version('awscli'))") + echo "::debug::Current AWS CLI version: $CURRENT_VERSION" + + # Get latest version from PyPI + LATEST_VERSION=$(uv run --no-project python -c "import urllib.request, json; print(json.loads(urllib.request.urlopen('https://pypi.org/pypi/awscli/json').read())['info']['version'])") + echo "::debug::Latest AWS CLI version from PyPI: $LATEST_VERSION" + + # Set version outputs + echo "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "latest-version=$LATEST_VERSION" >> $GITHUB_OUTPUT + + # Compare versions + if [[ "$CURRENT_VERSION" == "$LATEST_VERSION" ]]; then + echo "has-changes=false" >> $GITHUB_OUTPUT + echo "::notice::AWS CLI is already up to date (version $CURRENT_VERSION)" + else + echo "has-changes=true" >> $GITHUB_OUTPUT + echo "::notice::Upgrading AWS CLI from $CURRENT_VERSION to $LATEST_VERSION" + + # Remove existing awscli dependency + echo "::debug::Removing existing awscli dependency" + uv remove awscli + + # Add new version with exact pinning + echo "::debug::Adding awscli==$LATEST_VERSION" + uv add "awscli==$LATEST_VERSION" + + # Sync dependencies + echo "::debug::Syncing dependencies" + uv sync + + echo "::debug::AWS CLI upgrade completed" + fi + - name: Configure Git and GPG securely + if: steps.upgrade.outputs.has-changes == 'true' + env: + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + run: | + set -euo pipefail # SECURITY: Strict error handling + + # Create secure temporary directory for GPG + export GNUPGHOME=$(mktemp -d) + chmod 700 "$GNUPGHOME" + echo "GNUPGHOME=$GNUPGHOME" >> $GITHUB_ENV + + echo "::debug::Setting up secure GPG environment" + + # Configure git user + git config --local user.email "${{ env.BOT_USER_EMAIL }}" + git config --local user.name "${{ env.BOT_USER_NAME }}" + + # Import GPG key without exposing secrets in command line + echo "$GPG_PRIVATE_KEY" | gpg --batch --import --quiet + echo "$GPG_KEY_ID:6:" | gpg --import-ownertrust --quiet + + # Configure git GPG settings + git config --global user.signingkey "$GPG_KEY_ID" + git config --global commit.gpgsign true + git config --global tag.gpgsign true + + # Test GPG functionality + echo "test" | gpg --batch --yes --passphrase-fd 0 --pinentry-mode loopback \ + --sign --armor --local-user "$GPG_KEY_ID" <<< "$GPG_PASSPHRASE" > /dev/null + + echo "::debug::GPG configuration completed successfully" + - name: Commit and push changes + if: steps.upgrade.outputs.has-changes == 'true' + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + run: | + set -euo pipefail + echo "::debug::Committing changes" + + # Add only the source directory + git add src/aws-api-mcp-server/ + + # Cache GPG signature + echo "commit" | gpg --batch --yes --passphrase-fd 0 --pinentry-mode loopback \ + --sign --armor --local-user "$GPG_KEY_ID" <<< "$GPG_PASSPHRASE" > /dev/null + + # Create signed commit + git commit -m "chore(aws-api-mcp-server): upgrade AWS CLI to v${{ steps.upgrade.outputs.latest-version }}" --sign + + # Pull with rebase to maintain linear history + git pull --rebase origin "${{ steps.create-branch.outputs.upgrade-branch }}" + + # Push changes + git push origin "${{ steps.create-branch.outputs.upgrade-branch }}" + + echo "::debug::Successfully committed and pushed changes" + - name: Create pull request + if: steps.upgrade.outputs.has-changes == 'true' + env: + GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} + run: | + set -euo pipefail + + UPGRADE_BRANCH="${{ steps.create-branch.outputs.upgrade-branch }}" + BASE_BRANCH="${{ github.ref_name }}" + + echo "::debug::Creating PR from $UPGRADE_BRANCH to $BASE_BRANCH" + + # Validate branch names + if [[ ! "$UPGRADE_BRANCH" =~ ^upgrade/aws-api-mcp-awscli-[0-9]{14}$ ]]; then + echo "::error::Invalid upgrade branch format: $UPGRADE_BRANCH" >&2 + exit 1 + fi + + # Create PR with validated content + PR_URL="$(gh pr create \ + --base "$BASE_BRANCH" \ + --head "$UPGRADE_BRANCH" \ + --title "chore(aws-api-mcp-server): upgrade AWS CLI to v${{ steps.upgrade.outputs.latest-version }}" \ + --body "# AWS CLI Version Upgrade + + This PR upgrades the AWS CLI version in the aws-api-mcp-server package. + + ## Changes + * Updated AWS CLI from **v${{ steps.upgrade.outputs.current-version }}** to **v${{ steps.upgrade.outputs.latest-version }}** + + ## Checklist + - [ ] Dependencies have been upgraded + - [ ] Lock file has been updated + - [ ] Tests pass with new versions + + ## Acknowledgment + By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the [project license](https://github.com/awslabs/mcp/blob/main/LICENSE).")" + + echo "::debug::Successfully created pull request $PR_URL" + echo "### :arrow_up: AWS CLI Upgrade Ready" >> $GITHUB_STEP_SUMMARY + echo "Pull request $PR_URL created for [$UPGRADE_BRANCH](https://github.com/${{ github.repository }}/tree/$UPGRADE_BRANCH) branch" >> $GITHUB_STEP_SUMMARY + - name: Secure GPG cleanup + if: always() + run: | + set +e # Don't fail on cleanup errors + echo "::debug::Performing secure cleanup" + if [[ -n "${GNUPGHOME:-}" && -d "$GNUPGHOME" ]]; then + rm -rf "$GNUPGHOME" + echo "::debug::Cleaned up GPG directory" + fi + gpgconf --kill gpg-agent 2>/dev/null || true + unset GPG_PRIVATE_KEY GPG_PASSPHRASE GPG_KEY_ID GNUPGHOME 2>/dev/null || true + echo "::debug::Secure cleanup completed" diff --git a/src/aws-api-mcp-server/pyproject.toml b/src/aws-api-mcp-server/pyproject.toml index e46c875f1e..3909172feb 100644 --- a/src/aws-api-mcp-server/pyproject.toml +++ b/src/aws-api-mcp-server/pyproject.toml @@ -10,7 +10,6 @@ requires-python = ">=3.10" dependencies = [ "mcp>=1.11.0", "pydantic>=2.10.6", - "awscli==1.42.40", "boto3>=1.38.18", "botocore>=1.38.18", "python-json-logger>=2.0.7", @@ -20,6 +19,7 @@ dependencies = [ "importlib_resources>=6.0.0", "requests>=2.32.4", "python-frontmatter>=1.1.0", + "awscli==1.42.47", ] license = {text = "Apache-2.0"} license-files = ["LICENSE", "NOTICE" ] diff --git a/src/aws-api-mcp-server/uv.lock b/src/aws-api-mcp-server/uv.lock index 5bc3fc79a8..5975cff14c 100644 --- a/src/aws-api-mcp-server/uv.lock +++ b/src/aws-api-mcp-server/uv.lock @@ -54,7 +54,7 @@ wheels = [ [[package]] name = "awscli" -version = "1.42.40" +version = "1.42.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, @@ -64,9 +64,9 @@ dependencies = [ { name = "rsa" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/c6/dce39f736f1fe59fd688fb864252392fa1d90e9a5709491f79dbe897f9d4/awscli-1.42.40.tar.gz", hash = "sha256:37ed937ecd989531d1cfa193a605a7145bb75dbaea6cd11211dae95ef56ed646", size = 1889482, upload-time = "2025-09-26T19:23:43.559Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/38/14f123b684750aa20e60c018119a4252beaae8edb66298ca633963b067ef/awscli-1.42.47.tar.gz", hash = "sha256:fec0d671529221cdd249d54b05a23d7198c22a09edf552e5c99c0b785f13f63c", size = 1891435, upload-time = "2025-10-07T19:26:33.272Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/35/27510c2ce686d5827b403e4d3d656082d795be9a9af83ae675429964e068/awscli-1.42.40-py3-none-any.whl", hash = "sha256:c27ca5c937cc6386ecc2e25cda6fc0fb545a15b13db1bdcbef5d003de6f92f99", size = 4663622, upload-time = "2025-09-26T19:23:41.703Z" }, + { url = "https://files.pythonhosted.org/packages/32/51/7a496b42f21424c182198b446bab04a62b90912495f96dfe62c0a19740c8/awscli-1.42.47-py3-none-any.whl", hash = "sha256:c11291c051c511d69119be41e5b4e20f2eb759cad977eb14a5346684e444d08e", size = 4667609, upload-time = "2025-10-07T19:26:30.266Z" }, ] [[package]] @@ -102,7 +102,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "awscli", specifier = "==1.42.40" }, + { name = "awscli", specifier = "==1.42.47" }, { name = "boto3", specifier = ">=1.38.18" }, { name = "botocore", specifier = ">=1.38.18" }, { name = "importlib-resources", specifier = ">=6.0.0" }, @@ -144,16 +144,16 @@ wheels = [ [[package]] name = "botocore" -version = "1.40.40" +version = "1.40.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/5a/43a7fea503ad14fa79819f2b3103a38977fb587a3663d1ac6e958fccf592/botocore-1.40.40.tar.gz", hash = "sha256:78eb121a16a6481ed0f6e1aebe53a4f23aa121f34466846c13a5ca48fa980e31", size = 14363370, upload-time = "2025-09-26T19:23:37.853Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/9e/65a9507f6f4d7ea1f3050a2b555faac7f4afa074ce9bb1dd12aa6fd19fc3/botocore-1.40.47.tar.gz", hash = "sha256:8eb950046ba8afc99dedb0268282b4f9a61bca2c7a6415036bff2beee5e180ca", size = 14401848, upload-time = "2025-10-07T19:26:26.686Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/5e/3bbf6d34cbf307c1b9e58e0204ceba2d35bbc0c93b4e3b3cc895aae0a5fd/botocore-1.40.40-py3-none-any.whl", hash = "sha256:68506142b3cde93145ef3ee0268f2444f2b68ada225a151f714092bbd3d6516a", size = 14031738, upload-time = "2025-09-26T19:23:35.475Z" }, + { url = "https://files.pythonhosted.org/packages/a2/16/96226328857ab02123bc7b6dc08e27aa5bd1cfa1c553a922239263014ce8/botocore-1.40.47-py3-none-any.whl", hash = "sha256:0845c5bc49fc9d45938ff3609df7ec1eff0d26c1a4edcd03e16ad2194c3a9a56", size = 14072266, upload-time = "2025-10-07T19:26:22.79Z" }, ] [[package]]