From 59b8827ccb4ebd43666c09a4919ff08de629659a Mon Sep 17 00:00:00 2001 From: "Matthew M. Keeler" Date: Tue, 21 Nov 2023 13:59:36 -0500 Subject: [PATCH] ci: Replace releaser with release please (#231) --- .circleci/config.yml | 196 ---------------------- .github/actions/publish/action.yml | 33 ++++ .github/workflows/ci.yml | 153 +++++++++++++++++ .github/workflows/lint-pr-title.yml | 12 ++ .github/workflows/manual-publish.yml | 31 ++++ .github/workflows/release-please.yml | 40 +++++ .ldrelease/build.sh | 10 -- .ldrelease/config.yml | 28 ---- .release-please-manifest.json | 3 + README.md | 2 +- ldclient/version.py | 2 +- release-please-config.json | 11 ++ scripts/release.sh | 26 --- testing/impl/datasource/test_streaming.py | 8 +- testing/test_file_data_source.py | 4 +- 15 files changed, 291 insertions(+), 268 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/actions/publish/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/lint-pr-title.yml create mode 100644 .github/workflows/manual-publish.yml create mode 100644 .github/workflows/release-please.yml delete mode 100755 .ldrelease/build.sh delete mode 100644 .ldrelease/config.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json delete mode 100755 scripts/release.sh diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4a921187..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,196 +0,0 @@ -version: 2.1 - -orbs: - win: circleci/windows@1.0.0 - -workflows: - test: - jobs: - - test-linux: - name: Python 3.7 - docker-image: cimg/python:3.7 - test-build-docs: true - skip-sse-contract-tests: true - skip-contract-tests: true - - test-linux: - name: Python 3.8 - docker-image: cimg/python:3.8 - - test-linux: - name: Python 3.9 - docker-image: cimg/python:3.9 - - test-linux: - name: Python 3.10 - docker-image: cimg/python:3.10 - - test-linux: - name: Python 3.11 - docker-image: cimg/python:3.11 - - test-windows: - name: Windows Python 3 - py3: true - -jobs: - test-linux: - parameters: - docker-image: - type: string - test-packaging: - type: boolean - default: true - test-build-docs: - type: boolean - default: false - test-with-mypy: - type: boolean - default: true - skip-sse-contract-tests: - type: boolean - default: false - skip-contract-tests: - type: boolean - default: false - docker: - - image: <> - - image: redis - - image: amazon/dynamodb-local - - image: hashicorp/consul - steps: - - checkout - - run: python --version - - run: - name: install requirements - command: | - pip install --upgrade pip - pip install -r test-requirements.txt; - pip install -r test-filesource-optional-requirements.txt; - pip install -r consul-requirements.txt; - python setup.py install; - pip freeze - - run: - name: run tests - command: | - mkdir test-reports - pytest -s --cov=ldclient --cov-report=html:build/html --junitxml=test-reports/junit.xml testing -W error::SyntaxWarning - - when: - condition: <> - steps: - - run: - name: test packaging/install - command: | - sudo rm -rf dist *.egg-info - ./test-packaging/test-packaging.sh - - when: - condition: <> - steps: - - run: - name: verify typehints - command: | - export PATH="/home/circleci/.local/bin:$PATH" - make lint - - when: - condition: <> - steps: - - run: - name: verify docs can be built successfully - command: | - make docs - - - unless: - condition: <> - steps: - - run: - name: build SSE contract test service - command: cd sse-contract-tests && make build-test-service - - run: - name: start SSE contract test service - command: cd sse-contract-tests && make start-test-service - background: true - - run: - name: run SSE contract tests - command: cd sse-contract-tests && make run-contract-tests - - - unless: - condition: <> - steps: - - run: make build-contract-tests - - run: - command: make start-contract-test-service - background: true - - run: - name: run contract tests - command: TEST_HARNESS_PARAMS="-junit test-reports/contract-tests-junit.xml" make run-contract-tests - - - store_test_results: - path: test-reports - - store_artifacts: - path: build/html - - - test-windows: - executor: - name: win/vs2019 - shell: powershell.exe - parameters: - py3: - type: boolean - steps: - - checkout - - run: - name: install Python 3 - command: | - choco install pyenv-win --force - refreshenv - pyenv install 3.11.0b3 - pyenv global 3.11.0b3 - [System.Environment]::SetEnvironmentVariable('PYENV',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") - [System.Environment]::SetEnvironmentVariable('PYENV_ROOT',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") - [System.Environment]::SetEnvironmentVariable('PYENV_HOME',$env:USERPROFILE + "\.pyenv\pyenv-win\","User") - [System.Environment]::SetEnvironmentVariable('path', $env:USERPROFILE + "\.pyenv\pyenv-win\bin;" + $env:USERPROFILE + "\.pyenv\pyenv-win\shims;" + [System.Environment]::GetEnvironmentVariable('path', "User"),"User") - - run: python --version - - run: - name: set up DynamoDB - command: | - $ProgressPreference = "SilentlyContinue" - iwr -outf dynamo.zip https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.zip - mkdir dynamo - Expand-Archive -Path dynamo.zip -DestinationPath dynamo - cd dynamo - javaw -D"java.library.path=./DynamoDBLocal_lib" -jar DynamoDBLocal.jar - background: true - - run: - name: set up Consul - command: | - $ProgressPreference = "SilentlyContinue" - iwr -outf consul.zip https://releases.hashicorp.com/consul/1.4.2/consul_1.4.2_windows_amd64.zip - mkdir consul - Expand-Archive -Path consul.zip -DestinationPath consul - cd consul - sc.exe create "Consul" binPath="$(Get-Location)/consul.exe agent -dev" - sc.exe start "Consul" - - run: - name: start Redis - command: | - $ProgressPreference = "SilentlyContinue" - iwr -outf redis.zip https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.zip - mkdir redis - Expand-Archive -Path redis.zip -DestinationPath redis - cd redis - ./redis-server --service-install - ./redis-server --service-start - Start-Sleep -s 5 - ./redis-cli ping - - run: - name: install requirements - command: | - python --version - pip install -r test-requirements.txt - pip install -r consul-requirements.txt - python setup.py install - - run: - name: run tests - command: | - mkdir test-reports - python -m pytest -s --junitxml=test-reports/junit.xml testing; - - store_test_results: - path: test-reports - - store_artifacts: - path: test-reports diff --git a/.github/actions/publish/action.yml b/.github/actions/publish/action.yml new file mode 100644 index 00000000..01d3a6e9 --- /dev/null +++ b/.github/actions/publish/action.yml @@ -0,0 +1,33 @@ +name: Publish Package +description: 'Publish the package to PyPI' +inputs: + token: + description: 'Token to use for publishing.' + required: true + dry_run: + description: 'Is this a dry run. If so no package will be published.' + required: true + +runs: + using: composite + steps: + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: 3.11 + + - name: Install dependencies + shell: bash + run: | + pip install -r requirements.txt + pip install wheel + + - name: Building publishable packages + shell: bash + run: python setup.py sdist bdist_wheel + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + if: ${{ inputs.dry_run == 'false' }} + with: + password: ${{inputs.token}} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..7b12e120 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,153 @@ +name: Quality control checks + +on: + push: + branches: [ 8.x ] + paths-ignore: + - '**.md' # Do not need to run CI for markdown changes. + pull_request: + branches: [ 8.x ] + paths-ignore: + - '**.md' + +jobs: + linux: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + + services: + redis: + image: redis + ports: + - 6379:6379 + dynamodb: + image: amazon/dynamodb-local + ports: + - 8000:8000 + consul: + image: hashicorp/consul + ports: + - 8500:8500 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install requirements + run: | + pipx install virtualenv + pip install -r test-requirements.txt + pip install -r test-filesource-optional-requirements.txt + pip install -r consul-requirements.txt + python setup.py install + pip freeze + + - name: Run tests + run: pytest -s testing -W error::SyntaxWarning + + - name: Test packaging + run: | + sudo rm -rf dist *.egg-info + ./test-packaging/test-packaging.sh + + - name: Verify typehints + run: make lint + + - name: Verify docs can be successfully built + run: make docs + + - name: build SSE contract test service + if: ${{ matrix.python-version != 3.7 }} + run: | + cd sse-contract-tests + make build-test-service + + - name: start SSE contract test service + if: ${{ matrix.python-version != 3.7 }} + run: | + cd sse-contract-tests + make start-test-service & + + - name: run SSE contract tests + if: ${{ matrix.python-version != 3.7 }} + run: | + cd sse-contract-tests + make run-contract-tests + + - name: Build contract tests + if: ${{ matrix.python-version != 3.7 }} + run: make build-contract-tests + + - name: Start contract test service + if: ${{ matrix.python-version != 3.7 }} + run: make start-contract-test-service & + + - name: run contract tests + if: ${{ matrix.python-version != 3.7 }} + run: make run-contract-tests + + windows: + runs-on: windows-latest + + defaults: + run: + shell: powershell + + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup DynamoDB + run: | + $ProgressPreference = "SilentlyContinue" + iwr -outf dynamo.zip https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.zip + mkdir dynamo + Expand-Archive -Path dynamo.zip -DestinationPath dynamo + cd dynamo + cmd /c "START /b java -Djava.library.path=./DynamoDBLocal_lib -jar ./DynamoDBLocal.jar" + + - name: Setup Consul + run: | + $ProgressPreference = "SilentlyContinue" + iwr -outf consul.zip https://releases.hashicorp.com/consul/1.4.2/consul_1.4.2_windows_amd64.zip + mkdir consul + Expand-Archive -Path consul.zip -DestinationPath consul + cd consul + sc.exe create "Consul" binPath="$(Get-Location)/consul.exe agent -dev" + sc.exe start "Consul" + + - name: Setup Redis + run: | + $ProgressPreference = "SilentlyContinue" + iwr -outf redis.zip https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.zip + mkdir redis + Expand-Archive -Path redis.zip -DestinationPath redis + cd redis + ./redis-server --service-install + ./redis-server --service-start + Start-Sleep -s 5 + ./redis-cli ping + + - name: Install requirements + run: | + pip install -r test-requirements.txt + pip install -r test-filesource-optional-requirements.txt + pip install -r consul-requirements.txt + python setup.py install + pip freeze + + - name: Run tests + run: pytest -s testing -W error::SyntaxWarning diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml new file mode 100644 index 00000000..4ba79c13 --- /dev/null +++ b/.github/workflows/lint-pr-title.yml @@ -0,0 +1,12 @@ +name: Lint PR title + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + lint-pr-title: + uses: launchdarkly/gh-actions/.github/workflows/lint-pr-title.yml@main diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml new file mode 100644 index 00000000..edd263ea --- /dev/null +++ b/.github/workflows/manual-publish.yml @@ -0,0 +1,31 @@ +name: Publish Package +on: + workflow_dispatch: + inputs: + dry_run: + description: 'Is this a dry run? If so no package will be published.' + type: boolean + required: true + +jobs: + build-publish: + runs-on: ubuntu-latest + # Needed to get tokens during publishing. + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@v4 + + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.0 + name: 'Get PyPI token' + with: + aws_assume_role: ${{ vars.AWS_ROLE_ARN }} + ssm_parameter_pairs: '/production/common/releasing/pypi/token = PYPI_AUTH_TOKEN' + + - id: publish + name: Publish Package + uses: ./.github/actions/publish + with: + token: ${{env.PYPI_AUTH_TOKEN}} + dry_run: ${{ inputs.dry_run }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..a4ec47bb --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,40 @@ +name: Run Release Please + +on: + push: + branches: [ 8.x ] + +jobs: + release-package: + runs-on: ubuntu-latest + permissions: + id-token: write # Needed if using OIDC to get release secrets. + contents: write # Contents and pull-requests are for release-please to make releases. + pull-requests: write + steps: + - uses: google-github-actions/release-please-action@v3 + id: release + with: + command: manifest + token: ${{secrets.GITHUB_TOKEN}} + default-branch: 8.x + + - uses: actions/checkout@v4 + if: ${{ steps.release.outputs.releases_created }} + with: + fetch-depth: 0 # If you only need the current version keep this. + + - uses: launchdarkly/gh-actions/actions/release-secrets@release-secrets-v1.0.0 + if: ${{ steps.release.outputs.releases_created }} + name: 'Get PyPI token' + with: + aws_assume_role: ${{ vars.AWS_ROLE_ARN }} + ssm_parameter_pairs: '/production/common/releasing/pypi/token = PYPI_AUTH_TOKEN' + + - id: publish + name: Publish Package + uses: ./.github/actions/publish + if: ${{ steps.release.outputs.releases_created }} + with: + token: ${{env.PYPI_AUTH_TOKEN}} + dry_run: false diff --git a/.ldrelease/build.sh b/.ldrelease/build.sh deleted file mode 100755 index c826c9f1..00000000 --- a/.ldrelease/build.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -ue - -echo "Installing requirements" -pip install -r requirements.txt || { echo "installing requirements.txt failed" >&2; exit 1; } -pip install wheel || { echo "installing wheel failed" >&2; exit 1; } - -echo "Running setup.py sdist bdist_wheel" -python setup.py sdist bdist_wheel || { echo "setup.py sdist bdist_wheel failed" >&2; exit 1; } diff --git a/.ldrelease/config.yml b/.ldrelease/config.yml deleted file mode 100644 index 400a239f..00000000 --- a/.ldrelease/config.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: 2 - -repo: - public: python-server-sdk - private: python-server-sdk-private - -publications: - - url: https://pypi.org/project/launchdarkly-server-sdk/ - description: PyPI - - url: https://launchdarkly-python-sdk.readthedocs.io/en/latest/ - description: documentation (readthedocs.io) - -branches: - - name: main - description: 9.x - - name: 8.x - - name: 7.x - - name: 6.x - -jobs: - - docker: {} - template: - name: python - env: - LD_SKIP_DATABASE_TESTS: 1 - -sdk: - displayName: "Python" diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..7d53cf12 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "8.2.1" +} diff --git a/README.md b/README.md index c0dd2e00..5434563d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # LaunchDarkly Server-side SDK for Python -[![Circle CI](https://img.shields.io/circleci/project/launchdarkly/python-server-sdk.png)](https://circleci.com/gh/launchdarkly/python-server-sdk) +[![Actions Status](https://github.com/launchdarkly/python-server-sdk/actions/workflows/ci.yml/badge.svg?branch=8.x)](https://github.com/launchdarkly/python-server-sdk/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/launchdarkly-server-sdk.svg?maxAge=2592000)](https://pypi.python.org/pypi/launchdarkly-server-sdk) [![PyPI](https://img.shields.io/pypi/pyversions/launchdarkly-server-sdk.svg)](https://pypi.python.org/pypi/launchdarkly-server-sdk) diff --git a/ldclient/version.py b/ldclient/version.py index 277de075..294f6ebf 100644 --- a/ldclient/version.py +++ b/ldclient/version.py @@ -1 +1 @@ -VERSION = "8.2.1" +VERSION = "8.2.1" # x-release-please-version diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..90edd09a --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,11 @@ +{ + "packages": { + ".": { + "release-type": "python", + "versioning": "default", + "include-v-in-tag": false, + "extra-files": ["ldclient/version.py"], + "include-component-in-tag": false + } + } +} diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index d2b24e73..00000000 --- a/scripts/release.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash -# This script updates the version for the ldclient library and releases it to PyPi -# It will only work if you have the proper credentials set up in ~/.pypirc - -# It takes exactly one argument: the new version. -# It should be run from the root of this git repo like this: -# ./scripts/release.sh 4.0.9 - -# When done you should commit and push the changes made. - -set -uxe -echo "Starting python-server-sdk release." - -VERSION=$1 - -# Update version in ldclient/version.py - setup.py references this constant -echo "VERSION = \"${VERSION}\"" > ldclient/version.py - -# Prepare distribution -python setup.py sdist - -# Upload with Twine -pip install twine -python -m twine upload dist/* - -echo "Done with python-server-sdk release" diff --git a/testing/impl/datasource/test_streaming.py b/testing/impl/datasource/test_streaming.py index b017b9a8..8e449d25 100644 --- a/testing/impl/datasource/test_streaming.py +++ b/testing/impl/datasource/test_streaming.py @@ -21,7 +21,7 @@ # the test server running at localhost tests are *extremely* slow. It looks like a similar issue to what's # described at https://stackoverflow.com/questions/2617615/slow-python-http-server-on-localhost but we had no # luck with the advice that was given there. -start_wait = 5 +start_wait = 10 update_wait = 3 def test_request_properties(): @@ -189,7 +189,7 @@ def test_retries_on_network_error(): server.for_path('/all', two_errors_then_success) with StreamingUpdateProcessor(config, store, ready, None) as sp: - sp.start() + sp.start() ready.wait(start_wait) assert sp.initialized() server.await_request @@ -207,7 +207,7 @@ def test_recoverable_http_error(status): server.for_path('/all', two_errors_then_success) with StreamingUpdateProcessor(config, store, ready, None) as sp: - sp.start() + sp.start() ready.wait(start_wait) assert sp.initialized() server.should_have_requests(3) @@ -224,7 +224,7 @@ def test_unrecoverable_http_error(status): server.for_path('/all', error_then_success) with StreamingUpdateProcessor(config, store, ready, None) as sp: - sp.start() + sp.start() ready.wait(5) assert not sp.initialized() server.should_have_requests(1) diff --git a/testing/test_file_data_source.py b/testing/test_file_data_source.py index 17efe8a0..95e3a499 100644 --- a/testing/test_file_data_source.py +++ b/testing/test_file_data_source.py @@ -207,12 +207,12 @@ def do_auto_update_test(options): assert len(store.all(SEGMENTS, lambda x: x)) == 0 time.sleep(0.5) replace_file(path, segment_only_json) - deadline = time.time() + 10 + deadline = time.time() + 20 while time.time() < deadline: time.sleep(0.1) if len(store.all(SEGMENTS, lambda x: x)) == 1: return - assert False, "Flags were not reloaded after 10 seconds" + assert False, "Flags were not reloaded after 20 seconds" finally: os.remove(path)