From a78dbebbf0f2496da3461d31d333922e561bcb9a Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 13:35:55 +0300 Subject: [PATCH 1/8] tox integrations with invoke and docker --- .gitignore | 1 + CONTRIBUTING.rst | 41 ++++---- Makefile | 14 --- dev_requirements.txt | 5 + docker/{slave => replica}/Dockerfile | 0 docker/replica/redis.conf | 4 + docker/slave/redis.conf | 4 - tasks.py | 49 ++++++++++ tox.ini | 138 +++++++++++++++++++++------ 9 files changed, 192 insertions(+), 64 deletions(-) delete mode 100644 Makefile create mode 100644 dev_requirements.txt rename docker/{slave => replica}/Dockerfile (100%) create mode 100644 docker/replica/redis.conf delete mode 100644 docker/slave/redis.conf create mode 100644 tasks.py diff --git a/.gitignore b/.gitignore index b59bcdf097..05c384652c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ vagrant/.vagrant env venv coverage.xml +.venv diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index edaa7b6773..5aa6e454ba 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -29,25 +29,36 @@ Here's how to get started with your code contribution: 1. Create your own fork of redis-py 2. Do the changes in your fork -3. If you need a development environment, run ``make dev`` -4. While developing, make sure the tests pass by running ``make test`` +3. Create a virtualenv and install the development dependencies from the dev_requirements.txt file: + a. python -m venv .venv + b. source .venv/bin/activate + c. pip install -r dev_requirements.txt +3. If you need a development environment, run ``invoke devenv`` +4. While developing, make sure the tests pass by running ``invoke test`` 5. If you like the change and think the project could use it, send a pull request +To see what else is part of the automation, run ``invoke -l`` + The Development Environment --------------------------- -Running ``make dev`` will create a Docker-based development environment that starts the following containers: +Running ``invoke devenv`` installs the development dependencies specified in the dev_requirements.txt. It starts all of the dockers used by this project, and leaves them running. These can be easily cleaned up with ``invoke clean``. NOTE: it is assumed that the user running these tests, can execute docker and its various commands. * A master Redis node -* A slave Redis node +* A Redis replica node * Three sentinel Redis nodes -* A test container +* A multi-python docker, with your source code mounted in /data -The slave is a replica of the master node, using the `leader-follower replication `_ feature. +The replica node, is a replica of the master node, using the `leader-follower replication `_ feature. The sentinels monitor the master node in a `sentinel high-availability configuration `_. -Meanwhile, the `test` container hosts the code from your checkout of ``redis-py`` and allows running tests against many Python versions. +Testing +------- + +Each run of tox starts and stops the various dockers required. Sometimes things get stuck, an ``invoke clean`` can help. + +Continuous Integration uses these same wrappers to run all of these tests against multiple versions of python. Feel free to test your changes against all the python versions supported, as declared by the tox.ini file (eg: tox -e py39). If you have the various python versions on your desktop, you can run *tox* by itself, to test all supported versions. Alternatively, as your source code is mounted in the **lots-of-pythons** docker, you can start exploring from there, with all supported python versions! Docker Tips ^^^^^^^^^^^ @@ -56,17 +67,17 @@ Following are a few tips that can help you work with the Docker-based developmen To get a bash shell inside of a container: -``$ docker-compose run /bin/bash`` - -**Note**: The term "service" refers to the "services" defined in the ``docker-compose.yml`` file: "master", "slave", "sentinel_1", "sentinel_2", "sentinel_3", "test". +``$ docker run -it /bin/bash`` + +**Note**: The term "service" refers to the "services" defined in the ``tox.ini`` file at the top of the repo: "master", "replicaof", "sentinel_1", "sentinel_2", "sentinel_3". Containers run a minimal Debian image that probably lacks tools you want to use. To install packages, first get a bash session (see previous tip) and then run: ``$ apt update && apt install `` -You can see the combined logging output of all containers like this: +You can see the logging output of a containers like this: -``$ docker-compose logs`` +``$ docker logs -f `` The command `make test` runs all tests in all tested Python environments. To run the tests in a single environment, like Python 3.6, use a command like this: @@ -81,13 +92,11 @@ Our test suite uses ``pytest``. You can run a specific test suite against a spec Troubleshooting ^^^^^^^^^^^^^^^ If you get any errors when running ``make dev`` or ``make test``, make sure that you -are using supported versions of Docker and docker-compose. +are using supported versions of Docker. -The included Dockerfiles and docker-compose.yml file work with the following -versions of Docker and docker-compose: +Please try at least versions of Docker. * Docker 19.03.12 -* docker-compose 1.26.2 How to Report a Bug ------------------- diff --git a/Makefile b/Makefile deleted file mode 100644 index 0d0964345a..0000000000 --- a/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: base clean dev test - -base: - docker build -t redis-py-base docker/base - -dev: base - docker-compose up -d --build - -test: dev - docker-compose run --rm test /redis-py/docker-entry.sh - -clean: - docker-compose stop - docker-compose rm -f diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 0000000000..2648127712 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1,5 @@ +flake8>=3.9.2 +pytest==6.2.5 +tox==3.24.4 +tox-docker==3.1.0 +invoke==1.6.0 diff --git a/docker/slave/Dockerfile b/docker/replica/Dockerfile similarity index 100% rename from docker/slave/Dockerfile rename to docker/replica/Dockerfile diff --git a/docker/replica/redis.conf b/docker/replica/redis.conf new file mode 100644 index 0000000000..4a1dcd7e9d --- /dev/null +++ b/docker/replica/redis.conf @@ -0,0 +1,4 @@ +bind replica 127.0.0.1 +port 6380 +save "" +replicaof master 6379 diff --git a/docker/slave/redis.conf b/docker/slave/redis.conf deleted file mode 100644 index 629ac70c62..0000000000 --- a/docker/slave/redis.conf +++ /dev/null @@ -1,4 +0,0 @@ -bind slave 127.0.0.1 -port 6380 -save "" -slaveof master 6379 diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000000..b161371894 --- /dev/null +++ b/tasks.py @@ -0,0 +1,49 @@ +import os +import shutil +import tox +from invoke import task, run + +with open('tox.ini') as fp: + lines = fp.read().split("\n") + dockers = [l.split("=")[1].strip() for l in lines if l.find("name") != -1] + +@task +def devenv(c): + """Builds a development environment: downloads, and starts all dockers specified in the tox.ini file.""" + clean(c) + cmd = 'tox -e devenv' + for d in dockers: + cmd += " --docker-dont-stop={}".format(d) + print("Running: {}".format(cmd)) + run(cmd) + +@task +def lint(c): + """Run code linters""" + run("flake8") + +@task +def all_tests(c): + """Run all linters, and tests in redis-py. This assumes you have all pythons specified in tox.ini""" + lint(c) + test(c) + +@task +def test(c): + """Run the redis-py test suite against the current python, with and without hiredis""" + run("tox -e plain") + run("tox -e hiredis") + +@task +def clean(c): + """Stop all dockers, and clean up the built binaries, if generated.""" + if os.path.isdir("build"): + shutil.rmtree("build") + if os.path.isdir("dist"): + shutil.rmtree("dist") + run("docker rm -f {}".format(' '.join(dockers))) + +@task +def package(c): + """Create the python packages""" + run("python setup.py build sdist bdist_wheel") diff --git a/tox.ini b/tox.ini index db7492d18e..275031398f 100644 --- a/tox.ini +++ b/tox.ini @@ -2,50 +2,128 @@ addopts = -s [tox] -minversion = 2.4 -envlist = {py35,py36,py37,py38,py39,pypy3}-{plain,hiredis}, flake8, covreport, codecov +minversion = 3.2.0 +requires = tox-docker +envlist = {py35,py36,py37,py38,py39,pypy3}-{plain,hiredis}, flake8 + +[docker:master] +name = master +image = redis:6.2-alpine +ports = + 6379:6379/tcp +healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',6379)) else False" +volumes = + bind:rw:{toxinidir}/docker/master/redis.conf:/usr/local/etc/redis/redis.conf + +[docker:replica] +name = replica +image = redis:6.2-alpine +links = + master:master +ports = + 6380:6380/tcp +healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',6380)) else False" +volumes = + bind:rw:{toxinidir}/docker/replica/redis.conf:/usr/local/etc/redis/redis.conf + +[docker:sentinel_1] +name = sentinel_1 +image = redis:6.2-alpine +links = + master:master +ports = + 26379:26379/tcp +healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',26379)) else False" +volumes = + bind:rw:{toxinidir}/docker/sentinel_1/sentinel.conf:/usr/local/etc/redis/sentinel.conf + +[docker:sentinel_2] +name = sentinel_2 +image = redis:6.2-alpine +links = + master:master +ports = + 26380:26380/tcp +healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',26380)) else False" +volumes = + bind:rw:{toxinidir}/docker/sentinel_2/sentinel.conf:/usr/local/etc/redis/sentinel.conf + +[docker:sentinel_3] +name = sentinel_3 +image = redis:6.2-alpine +links = + master:master +ports = + 26381:26381/tcp +healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',26381)) else False" +volumes = + bind:rw:{toxinidir}/docker/sentinel_3/sentinel.conf:/usr/local/etc/redis/sentinel.conf + +[docker:redismod] +name = redismod +image = redislabs/redismod:edge +ports = + 16379:16379/tcp +healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',16379)) else False" + +[docker:lots-of-pythons] +name = lots-of-pythons +image = redisfab/lots-of-pythons +volumes = + bind:rw:{toxinidir}:/data [testenv] -deps = - coverage - pytest >= 2.7.0 +deps = -r {toxinidir}/dev_requirements.txt +docker = + master + replica + sentinel_1 + sentinel_2 + sentinel_3 + redismod extras = hiredis: hiredis commands = - {envpython} -b -m coverage run -p -m pytest -W always {posargs} - {envpython} -b -m coverage combine --append + pytest -W always {posargs} + +[testenv:devenv] +skipsdist = true +skip_install = true +deps = -r {toxinidir}/dev_requirements.txt +docker = + master + replica + sentinel_1 + sentinel_2 + sentinel_3 + redismod + lots-of-pythons +commands = echo [testenv:flake8] -basepython = python3.6 -deps = flake8 +deps_files = dev_requirements.txt commands = flake8 skipsdist = true skip_install = true -[testenv:pypy-plain] -basepython = pypy - -[testenv:pypy-hiredis] -basepython = pypy - [testenv:pypy3-plain] basepython = pypy3 [testenv:pypy3-hiredis] basepython = pypy3 -[testenv:codecov] -deps = codecov -commands = codecov -passenv = - REDIS_* - CI - CI_* - CODECOV_* - SHIPPABLE - GITHUB_* - VCS_* - -[testenv:covreport] -deps = coverage -commands = coverage report +#[testenv:codecov] +#deps = codecov +#commands = codecov +#passenv = +# REDIS_* +# CI +# CI_* +# CODECOV_* +# SHIPPABLE +# GITHUB_* +# VCS_* +# +#[testenv:covreport] +#deps = coverage +#commands = coverage report From f4453e7015beeb325b4379a12cd482c868c2fcfe Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 14:12:10 +0300 Subject: [PATCH 2/8] github action to wrap invoke --- .github/workflows/integration.yaml | 36 ++++++++++++++++++++++++++---- CONTRIBUTING.rst | 2 +- tasks.py | 33 ++++++++++++++++++--------- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index f08a2c238f..86aca92ac3 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -5,9 +5,37 @@ on: pull_request: jobs: - integration: + + lint: + name: Code linters + runs-on: ubuntu-latest + steps: + - uses: action/checkout@v2 + - name: install python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: run code linters + run: | + pip install -r dev_requirements.txt + invoke linters + + run-tests: runs-on: ubuntu-latest + strategy: + max-parallel: 3 + matrix: + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy-3.7'] + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + name: Python ${{ matrix.python-version }} tests steps: - - uses: actions/checkout@v2 - - name: test - run: make test + - uses: action/checkout@v2 + - name: install python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: run tests + run: | + pip install -r dev_requirements.txt + invoke tests diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 5aa6e454ba..ba1ddfc5cc 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -34,7 +34,7 @@ Here's how to get started with your code contribution: b. source .venv/bin/activate c. pip install -r dev_requirements.txt 3. If you need a development environment, run ``invoke devenv`` -4. While developing, make sure the tests pass by running ``invoke test`` +4. While developing, make sure the tests pass by running ``invoke tests`` 5. If you like the change and think the project could use it, send a pull request To see what else is part of the automation, run ``invoke -l`` diff --git a/tasks.py b/tasks.py index b161371894..14ebd6b0af 100644 --- a/tasks.py +++ b/tasks.py @@ -1,15 +1,18 @@ import os import shutil -import tox from invoke import task, run with open('tox.ini') as fp: lines = fp.read().split("\n") - dockers = [l.split("=")[1].strip() for l in lines if l.find("name") != -1] + dockers = [line.split("=")[1].strip() for line in lines + if line.find("name") != -1] + @task def devenv(c): - """Builds a development environment: downloads, and starts all dockers specified in the tox.ini file.""" + """Builds a development environment: downloads, and starts all dockers + specified in the tox.ini file. + """ clean(c) cmd = 'tox -e devenv' for d in dockers: @@ -17,22 +20,29 @@ def devenv(c): print("Running: {}".format(cmd)) run(cmd) + @task -def lint(c): +def linters(c): """Run code linters""" run("flake8") + @task def all_tests(c): - """Run all linters, and tests in redis-py. This assumes you have all pythons specified in tox.ini""" - lint(c) - test(c) + """Run all linters, and tests in redis-py. This assumes you have all + the python versions specified in the tox.ini file. + """ + linters(c) + tests(c) + @task -def test(c): - """Run the redis-py test suite against the current python, with and without hiredis""" - run("tox -e plain") - run("tox -e hiredis") +def tests(c): + """Run the redis-py test suite against the current python, + with and without hiredis. + """ + run("tox -e plain -e hiredis") + @task def clean(c): @@ -43,6 +53,7 @@ def clean(c): shutil.rmtree("dist") run("docker rm -f {}".format(' '.join(dockers))) + @task def package(c): """Create the python packages""" From 457dc0493725a73ae7afb07a713f746e4d719803 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 14:17:07 +0300 Subject: [PATCH 3/8] syntax error on action --- .github/workflows/integration.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 86aca92ac3..5cbad2e532 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -9,16 +9,16 @@ jobs: lint: name: Code linters runs-on: ubuntu-latest - steps: - - uses: action/checkout@v2 - - name: install python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - name: run code linters - run: | - pip install -r dev_requirements.txt - invoke linters + steps: + - uses: action/checkout@v2 + - name: install python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: run code linters + run: | + pip install -r dev_requirements.txt + invoke linters run-tests: runs-on: ubuntu-latest From c90bd14b586f0677a3758fe8652aeb60191afae6 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 14:22:49 +0300 Subject: [PATCH 4/8] s/action/actions --- .github/workflows/integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 5cbad2e532..22c87cfd9f 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -10,7 +10,7 @@ jobs: name: Code linters runs-on: ubuntu-latest steps: - - uses: action/checkout@v2 + - uses: actions/checkout@v2 - name: install python uses: actions/setup-python@v2 with: @@ -30,7 +30,7 @@ jobs: ACTIONS_ALLOW_UNSECURE_COMMANDS: true name: Python ${{ matrix.python-version }} tests steps: - - uses: action/checkout@v2 + - uses: actions/checkout@v2 - name: install python uses: actions/setup-python@v2 with: From 3d1a00732df791e49b158b15c7212754ec52cb2f Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 14:42:14 +0300 Subject: [PATCH 5/8] Adding a test for packaging Packaging broke in the past due to dependencies that were updated, and should not have been. This exists to try and catch that. --- .github/workflows/integration.yaml | 22 ++++++++++++++++++++++ docker-entry.sh | 19 ------------------- tasks.py | 2 +- 3 files changed, 23 insertions(+), 20 deletions(-) delete mode 100755 docker-entry.sh diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 22c87cfd9f..a932360dd0 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -2,7 +2,15 @@ name: CI on: push: + paths-ignore: + - 'docs/**' + - '**/*.rst' + - '**/*.md' pull_request: + paths-ignore: + - 'docs/**' + - '**/*.rst' + - '**/*.md' jobs: @@ -39,3 +47,17 @@ jobs: run: | pip install -r dev_requirements.txt invoke tests + + build_package: + name: Validate building and installing the package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: install python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: build and install + run: | + pip install -r dev_requirements.txt + invoke package diff --git a/docker-entry.sh b/docker-entry.sh deleted file mode 100755 index dc119dcdd8..0000000000 --- a/docker-entry.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -# This is the entrypoint for "make test". It invokes Tox. If running -# outside the CI environment, it disables uploading the coverage report to codecov - -set -eu - -REDIS_MASTER="${REDIS_MASTER_HOST}":"${REDIS_MASTER_PORT}" -echo "Testing against Redis Server: ${REDIS_MASTER}" - -# skip the "codecov" env if not running on Travis -if [ "${GITHUB_ACTIONS}" = true ] ; then - echo "Skipping codecov" - export TOX_SKIP_ENV="codecov" -fi - -# use the wait-for-it util to ensure the server is running before invoking Tox -util/wait-for-it.sh ${REDIS_MASTER} -- tox -- --redis-url=redis://"${REDIS_MASTER}"/9 - diff --git a/tasks.py b/tasks.py index 14ebd6b0af..15e983bbfd 100644 --- a/tasks.py +++ b/tasks.py @@ -57,4 +57,4 @@ def clean(c): @task def package(c): """Create the python packages""" - run("python setup.py build sdist bdist_wheel") + run("python setup.py build install") From 0f3e2c06488bc649288b615d92a03e38726c3929 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 14:54:49 +0300 Subject: [PATCH 6/8] Reducing dependencies for package test --- .github/workflows/integration.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index a932360dd0..2618c336da 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -31,7 +31,7 @@ jobs: run-tests: runs-on: ubuntu-latest strategy: - max-parallel: 3 + max-parallel: 6 matrix: python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy-3.7'] env: @@ -59,5 +59,5 @@ jobs: python-version: 3.9 - name: build and install run: | - pip install -r dev_requirements.txt + pip install invoke invoke package From 397714e97701f1c5a642d7bea053d6790e9c7f87 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 17:42:11 +0300 Subject: [PATCH 7/8] removign Dockerfile --- Dockerfile | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 2ceb725d47..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM fkrull/multi-python:latest - -RUN apt update \ - && apt install -y pypy pypy-dev pypy3-dev \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /redis-py - -COPY . /redis-py From baedd6e0c09e502c9b35e83c700cfbe1f543e813 Mon Sep 17 00:00:00 2001 From: "Chayim I. Kirshen" Date: Wed, 20 Oct 2021 17:44:42 +0300 Subject: [PATCH 8/8] moved from alpine to bullseye --- tox.ini | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tox.ini b/tox.ini index 275031398f..bfe937fe9e 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ envlist = {py35,py36,py37,py38,py39,pypy3}-{plain,hiredis}, flake8 [docker:master] name = master -image = redis:6.2-alpine +image = redis:6.2-bullseye ports = 6379:6379/tcp healtcheck_cmd = python -c "import socket;print(True) if 0 == socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect_ex(('127.0.0.1',6379)) else False" @@ -17,7 +17,7 @@ volumes = [docker:replica] name = replica -image = redis:6.2-alpine +image = redis:6.2-bullseye links = master:master ports = @@ -28,7 +28,7 @@ volumes = [docker:sentinel_1] name = sentinel_1 -image = redis:6.2-alpine +image = redis:6.2-bullseye links = master:master ports = @@ -39,7 +39,7 @@ volumes = [docker:sentinel_2] name = sentinel_2 -image = redis:6.2-alpine +image = redis:6.2-bullseye links = master:master ports = @@ -50,7 +50,7 @@ volumes = [docker:sentinel_3] name = sentinel_3 -image = redis:6.2-alpine +image = redis:6.2-bullseye links = master:master ports =