diff --git a/.github/workflows/benchmarking.yml b/.github/workflows/benchmarking.yml
index fc245f42a8..2e56bf909f 100644
--- a/.github/workflows/benchmarking.yml
+++ b/.github/workflows/benchmarking.yml
@@ -62,7 +62,7 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
@@ -214,7 +214,7 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
# Increase timeout to 8h, default timeout is 6h
@@ -362,7 +362,7 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
@@ -461,7 +461,7 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
@@ -558,7 +558,7 @@ jobs:
runs-on: [ self-hosted, us-east-2, x64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
options: --init
steps:
diff --git a/.github/workflows/build-build-tools-image.yml b/.github/workflows/build-build-tools-image.yml
new file mode 100644
index 0000000000..251423e701
--- /dev/null
+++ b/.github/workflows/build-build-tools-image.yml
@@ -0,0 +1,105 @@
+name: Build build-tools image
+
+on:
+ workflow_call:
+ inputs:
+ image-tag:
+ description: "build-tools image tag"
+ required: true
+ type: string
+ outputs:
+ image-tag:
+ description: "build-tools tag"
+ value: ${{ inputs.image-tag }}
+ image:
+ description: "build-tools image"
+ value: neondatabase/build-tools:${{ inputs.image-tag }}
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+concurrency:
+ group: build-build-tools-image-${{ inputs.image-tag }}
+
+# No permission for GITHUB_TOKEN by default; the **minimal required** set of permissions should be granted in each job.
+permissions: {}
+
+jobs:
+ check-image:
+ uses: ./.github/workflows/check-build-tools-image.yml
+
+ # This job uses older version of GitHub Actions because it's run on gen2 runners, which don't support node 20 (for newer versions)
+ build-image:
+ needs: [ check-image ]
+ if: needs.check-image.outputs.found == 'false'
+
+ strategy:
+ matrix:
+ arch: [ x64, arm64 ]
+
+ runs-on: ${{ fromJson(format('["self-hosted", "dev", "{0}"]', matrix.arch)) }}
+
+ env:
+ IMAGE_TAG: ${{ inputs.image-tag }}
+
+ steps:
+ - name: Check `input.tag` is correct
+ env:
+ INPUTS_IMAGE_TAG: ${{ inputs.image-tag }}
+ CHECK_IMAGE_TAG : ${{ needs.check-image.outputs.image-tag }}
+ run: |
+ if [ "${INPUTS_IMAGE_TAG}" != "${CHECK_IMAGE_TAG}" ]; then
+ echo "'inputs.image-tag' (${INPUTS_IMAGE_TAG}) does not match the tag of the latest build-tools image 'inputs.image-tag' (${CHECK_IMAGE_TAG})"
+ exit 1
+ fi
+
+ - uses: actions/checkout@v3
+
+ # Use custom DOCKER_CONFIG directory to avoid conflicts with default settings
+ # The default value is ~/.docker
+ - name: Set custom docker config directory
+ run: |
+ mkdir -p /tmp/.docker-custom
+ echo DOCKER_CONFIG=/tmp/.docker-custom >> $GITHUB_ENV
+
+ - uses: docker/setup-buildx-action@v2
+
+ - uses: docker/login-action@v2
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+
+ - uses: docker/build-push-action@v4
+ with:
+ context: .
+ provenance: false
+ push: true
+ pull: true
+ file: Dockerfile.build-tools
+ cache-from: type=registry,ref=neondatabase/build-tools:cache-${{ matrix.arch }}
+ cache-to: type=registry,ref=neondatabase/build-tools:cache-${{ matrix.arch }},mode=max
+ tags: neondatabase/build-tools:${{ inputs.image-tag }}-${{ matrix.arch }}
+
+ - name: Remove custom docker config directory
+ run: |
+ rm -rf /tmp/.docker-custom
+
+ merge-images:
+ needs: [ build-image ]
+ runs-on: ubuntu-latest
+
+ env:
+ IMAGE_TAG: ${{ inputs.image-tag }}
+
+ steps:
+ - uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+
+ - name: Create multi-arch image
+ run: |
+ docker buildx imagetools create -t neondatabase/build-tools:${IMAGE_TAG} \
+ neondatabase/build-tools:${IMAGE_TAG}-x64 \
+ neondatabase/build-tools:${IMAGE_TAG}-arm64
diff --git a/.github/workflows/build_and_push_docker_image.yml b/.github/workflows/build_and_push_docker_image.yml
deleted file mode 100644
index 892e21114b..0000000000
--- a/.github/workflows/build_and_push_docker_image.yml
+++ /dev/null
@@ -1,124 +0,0 @@
-name: Build and Push Docker Image
-
-on:
- workflow_call:
- inputs:
- dockerfile-path:
- required: true
- type: string
- image-name:
- required: true
- type: string
- outputs:
- build-tools-tag:
- description: "tag generated for build tools"
- value: ${{ jobs.tag.outputs.build-tools-tag }}
-
-jobs:
- check-if-build-tools-dockerfile-changed:
- runs-on: ubuntu-latest
- outputs:
- docker_file_changed: ${{ steps.dockerfile.outputs.docker_file_changed }}
- steps:
- - name: Check if Dockerfile.buildtools has changed
- id: dockerfile
- run: |
- if [[ "$GITHUB_EVENT_NAME" != "pull_request" ]]; then
- echo "docker_file_changed=false" >> $GITHUB_OUTPUT
- exit
- fi
- updated_files=$(gh pr --repo neondatabase/neon diff ${{ github.event.pull_request.number }} --name-only)
- if [[ $updated_files == *"Dockerfile.buildtools"* ]]; then
- echo "docker_file_changed=true" >> $GITHUB_OUTPUT
- fi
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- tag:
- runs-on: ubuntu-latest
- needs: [ check-if-build-tools-dockerfile-changed ]
- outputs:
- build-tools-tag: ${{steps.buildtools-tag.outputs.image_tag}}
-
- steps:
- - name: Get buildtools tag
- env:
- DOCKERFILE_CHANGED: ${{ needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed }}
- run: |
- if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "${DOCKERFILE_CHANGED}" == "true" ]]; then
- IMAGE_TAG=$GITHUB_RUN_ID
- else
- IMAGE_TAG=pinned
- fi
-
- echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT
- shell: bash
- id: buildtools-tag
-
- kaniko:
- if: needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed == 'true'
- needs: [ tag, check-if-build-tools-dockerfile-changed ]
- runs-on: [ self-hosted, dev, x64 ]
- container: gcr.io/kaniko-project/executor:v1.7.0-debug
-
- steps:
- - name: Checkout
- uses: actions/checkout@v1
-
- - name: Configure ECR login
- run: echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
-
- - name: Kaniko build
- run: |
- /kaniko/executor \
- --reproducible \
- --snapshotMode=redo \
- --skip-unused-stages \
- --dockerfile ${{ inputs.dockerfile-path }} \
- --cache=true \
- --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache \
- --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-amd64
-
- kaniko-arm:
- if: needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed == 'true'
- needs: [ tag, check-if-build-tools-dockerfile-changed ]
- runs-on: [ self-hosted, dev, arm64 ]
- container: gcr.io/kaniko-project/executor:v1.7.0-debug
-
- steps:
- - name: Checkout
- uses: actions/checkout@v1
-
- - name: Configure ECR login
- run: echo "{\"credsStore\":\"ecr-login\"}" > /kaniko/.docker/config.json
-
- - name: Kaniko build
- run: |
- /kaniko/executor \
- --reproducible \
- --snapshotMode=redo \
- --skip-unused-stages \
- --dockerfile ${{ inputs.dockerfile-path }} \
- --cache=true \
- --cache-repo 369495373322.dkr.ecr.eu-central-1.amazonaws.com/cache \
- --destination 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-arm64
-
- manifest:
- if: needs.check-if-build-tools-dockerfile-changed.outputs.docker_file_changed == 'true'
- name: 'manifest'
- runs-on: [ self-hosted, dev, x64 ]
- needs:
- - tag
- - kaniko
- - kaniko-arm
- - check-if-build-tools-dockerfile-changed
-
- steps:
- - name: Create manifest
- run: |
- docker manifest create 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }} \
- --amend 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-amd64 \
- --amend 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}-arm64
-
- - name: Push manifest
- run: docker manifest push 369495373322.dkr.ecr.eu-central-1.amazonaws.com/${{ inputs.image-name }}:${{ needs.tag.outputs.build-tools-tag }}
diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml
index 5def619c07..2e52e7c28f 100644
--- a/.github/workflows/build_and_test.yml
+++ b/.github/workflows/build_and_test.yml
@@ -77,19 +77,25 @@ jobs:
shell: bash
id: build-tag
- build-buildtools-image:
+ check-build-tools-image:
needs: [ check-permissions ]
- uses: ./.github/workflows/build_and_push_docker_image.yml
+ uses: ./.github/workflows/check-build-tools-image.yml
+
+ build-build-tools-image:
+ needs: [ check-build-tools-image ]
+ uses: ./.github/workflows/build-build-tools-image.yml
with:
- dockerfile-path: Dockerfile.buildtools
- image-name: build-tools
+ image-tag: ${{ needs.check-build-tools-image.outputs.image-tag }}
secrets: inherit
check-codestyle-python:
- needs: [ check-permissions, build-buildtools-image ]
+ needs: [ check-permissions, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -118,10 +124,13 @@ jobs:
run: poetry run mypy .
check-codestyle-rust:
- needs: [ check-permissions, build-buildtools-image ]
+ needs: [ check-permissions, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -185,10 +194,13 @@ jobs:
run: cargo deny check --hide-inclusion-graph
build-neon:
- needs: [ check-permissions, tag, build-buildtools-image ]
+ needs: [ check-permissions, tag, build-build-tools-image ]
runs-on: [ self-hosted, gen3, large ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
# Raise locked memory limit for tokio-epoll-uring.
# On 5.10 LTS kernels < 5.10.162 (and generally mainline kernels < 5.12),
# io_uring will account the memory of the CQ and SQ as locked.
@@ -426,10 +438,13 @@ jobs:
uses: ./.github/actions/save-coverage-data
regress-tests:
- needs: [ check-permissions, build-neon, build-buildtools-image, tag ]
+ needs: [ check-permissions, build-neon, build-build-tools-image, tag ]
runs-on: [ self-hosted, gen3, large ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
# for changed limits, see comments on `options:` earlier in this file
options: --init --shm-size=512mb --ulimit memlock=67108864:67108864
strategy:
@@ -473,10 +488,13 @@ jobs:
get-benchmarks-durations:
outputs:
json: ${{ steps.get-benchmark-durations.outputs.json }}
- needs: [ check-permissions, build-buildtools-image ]
+ needs: [ check-permissions, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-benchmarks')
steps:
@@ -503,10 +521,13 @@ jobs:
echo "json=$(jq --compact-output '.' /tmp/benchmark_durations.json)" >> $GITHUB_OUTPUT
benchmarks:
- needs: [ check-permissions, build-neon, build-buildtools-image, get-benchmarks-durations ]
+ needs: [ check-permissions, build-neon, build-build-tools-image, get-benchmarks-durations ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
# for changed limits, see comments on `options:` earlier in this file
options: --init --shm-size=512mb --ulimit memlock=67108864:67108864
if: github.ref_name == 'main' || contains(github.event.pull_request.labels.*.name, 'run-benchmarks')
@@ -538,12 +559,15 @@ jobs:
# while coverage is currently collected for the debug ones
create-test-report:
- needs: [ check-permissions, regress-tests, coverage-report, benchmarks, build-buildtools-image ]
+ needs: [ check-permissions, regress-tests, coverage-report, benchmarks, build-build-tools-image ]
if: ${{ !cancelled() && contains(fromJSON('["skipped", "success"]'), needs.check-permissions.result) }}
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -584,10 +608,13 @@ jobs:
})
coverage-report:
- needs: [ check-permissions, regress-tests, build-buildtools-image ]
+ needs: [ check-permissions, regress-tests, build-build-tools-image ]
runs-on: [ self-hosted, gen3, small ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${{ needs.build-buildtools-image.outputs.build-tools-tag }}
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
strategy:
fail-fast: false
@@ -691,7 +718,7 @@ jobs:
secrets: inherit
neon-image:
- needs: [ check-permissions, build-buildtools-image, tag ]
+ needs: [ check-permissions, build-build-tools-image, tag ]
runs-on: [ self-hosted, gen3, large ]
steps:
@@ -726,8 +753,7 @@ jobs:
build-args: |
GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
BUILD_TAG=${{ needs.tag.outputs.build-tag }}
- TAG=${{ needs.build-buildtools-image.outputs.build-tools-tag }}
- REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com
+ TAG=${{ needs.build-build-tools-image.outputs.image-tag }}
provenance: false
push: true
pull: true
@@ -743,61 +769,8 @@ jobs:
run: |
rm -rf .docker-custom
- compute-tools-image:
- runs-on: [ self-hosted, gen3, large ]
- needs: [ check-permissions, build-buildtools-image, tag ]
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- with:
- submodules: true
- fetch-depth: 0
-
- # Use custom DOCKER_CONFIG directory to avoid conflicts with default settings
- # The default value is ~/.docker
- - name: Set custom docker config directory
- run: |
- mkdir -p .docker-custom
- echo DOCKER_CONFIG=$(pwd)/.docker-custom >> $GITHUB_ENV
- - uses: docker/setup-buildx-action@v3
-
- - uses: docker/login-action@v3
- with:
- username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
- password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
-
- - uses: docker/login-action@v3
- with:
- registry: 369495373322.dkr.ecr.eu-central-1.amazonaws.com
- username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
- password: ${{ secrets.AWS_SECRET_KEY_DEV }}
-
- - uses: docker/build-push-action@v5
- with:
- context: .
- build-args: |
- GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
- BUILD_TAG=${{needs.tag.outputs.build-tag}}
- TAG=${{needs.build-buildtools-image.outputs.build-tools-tag}}
- REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com
- provenance: false
- push: true
- pull: true
- file: Dockerfile.compute-tools
- cache-from: type=registry,ref=neondatabase/compute-tools:cache
- cache-to: type=registry,ref=neondatabase/compute-tools:cache,mode=max
- tags: |
- 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:${{needs.tag.outputs.build-tag}}
- neondatabase/compute-tools:${{needs.tag.outputs.build-tag}}
-
- - name: Remove custom docker config directory
- if: always()
- run: |
- rm -rf .docker-custom
-
compute-node-image:
- needs: [ check-permissions, build-buildtools-image, tag ]
+ needs: [ check-permissions, build-build-tools-image, tag ]
runs-on: [ self-hosted, gen3, large ]
strategy:
@@ -837,15 +810,15 @@ jobs:
username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
password: ${{ secrets.AWS_SECRET_KEY_DEV }}
- - uses: docker/build-push-action@v5
+ - name: Build compute-node image
+ uses: docker/build-push-action@v5
with:
context: .
build-args: |
GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
PG_VERSION=${{ matrix.version }}
- BUILD_TAG=${{needs.tag.outputs.build-tag}}
- TAG=${{needs.build-buildtools-image.outputs.build-tools-tag}}
- REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com
+ BUILD_TAG=${{ needs.tag.outputs.build-tag }}
+ TAG=${{ needs.build-build-tools-image.outputs.image-tag }}
provenance: false
push: true
pull: true
@@ -856,6 +829,25 @@ jobs:
369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
neondatabase/compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
+ - name: Build compute-tools image
+ # compute-tools are Postgres independent, so build it only once
+ if: ${{ matrix.version == 'v16' }}
+ uses: docker/build-push-action@v5
+ with:
+ target: compute-tools-image
+ context: .
+ build-args: |
+ GIT_VERSION=${{ github.event.pull_request.head.sha || github.sha }}
+ BUILD_TAG=${{ needs.tag.outputs.build-tag }}
+ TAG=${{ needs.build-build-tools-image.outputs.image-tag }}
+ provenance: false
+ push: true
+ pull: true
+ file: Dockerfile.compute-node
+ tags: |
+ 369495373322.dkr.ecr.eu-central-1.amazonaws.com/compute-tools:${{ needs.tag.outputs.build-tag }}
+ neondatabase/compute-tools:${{ needs.tag.outputs.build-tag }}
+
- name: Remove custom docker config directory
if: always()
run: |
@@ -903,7 +895,7 @@ jobs:
docker push 369495373322.dkr.ecr.eu-central-1.amazonaws.com/vm-compute-node-${{ matrix.version }}:${{needs.tag.outputs.build-tag}}
test-images:
- needs: [ check-permissions, tag, neon-image, compute-node-image, compute-tools-image ]
+ needs: [ check-permissions, tag, neon-image, compute-node-image ]
runs-on: [ self-hosted, gen3, small ]
steps:
@@ -937,7 +929,8 @@ jobs:
fi
- name: Verify docker-compose example
- run: env REPOSITORY=369495373322.dkr.ecr.eu-central-1.amazonaws.com TAG=${{needs.tag.outputs.build-tag}} ./docker-compose/docker_compose_test.sh
+ timeout-minutes: 20
+ run: env TAG=${{needs.tag.outputs.build-tag}} ./docker-compose/docker_compose_test.sh
- name: Print logs and clean up
if: always()
@@ -1217,3 +1210,11 @@ jobs:
time aws s3 cp --only-show-errors s3://${BUCKET}/${S3_KEY} s3://${BUCKET}/${PREFIX}/${FILENAME}
done
+
+ pin-build-tools-image:
+ needs: [ build-build-tools-image, promote-images, regress-tests ]
+ if: github.ref_name == 'main'
+ uses: ./.github/workflows/pin-build-tools-image.yml
+ with:
+ from-tag: ${{ needs.build-build-tools-image.outputs.image-tag }}
+ secrets: inherit
diff --git a/.github/workflows/check-build-tools-image.yml b/.github/workflows/check-build-tools-image.yml
new file mode 100644
index 0000000000..28646dfc19
--- /dev/null
+++ b/.github/workflows/check-build-tools-image.yml
@@ -0,0 +1,58 @@
+name: Check build-tools image
+
+on:
+ workflow_call:
+ outputs:
+ image-tag:
+ description: "build-tools image tag"
+ value: ${{ jobs.check-image.outputs.tag }}
+ found:
+ description: "Whether the image is found in the registry"
+ value: ${{ jobs.check-image.outputs.found }}
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+# No permission for GITHUB_TOKEN by default; the **minimal required** set of permissions should be granted in each job.
+permissions: {}
+
+jobs:
+ check-image:
+ runs-on: ubuntu-latest
+ outputs:
+ tag: ${{ steps.get-build-tools-tag.outputs.image-tag }}
+ found: ${{ steps.check-image.outputs.found }}
+
+ steps:
+ - name: Get build-tools image tag for the current commit
+ id: get-build-tools-tag
+ env:
+ COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ LAST_BUILD_TOOLS_SHA=$(
+ gh api \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ --method GET \
+ --field path=Dockerfile.build-tools \
+ --field sha=${COMMIT_SHA} \
+ --field per_page=1 \
+ --jq ".[0].sha" \
+ "/repos/${GITHUB_REPOSITORY}/commits"
+ )
+ echo "image-tag=${LAST_BUILD_TOOLS_SHA}" | tee -a $GITHUB_OUTPUT
+
+ - name: Check if such tag found in the registry
+ id: check-image
+ env:
+ IMAGE_TAG: ${{ steps.get-build-tools-tag.outputs.image-tag }}
+ run: |
+ if docker manifest inspect neondatabase/build-tools:${IMAGE_TAG}; then
+ found=true
+ else
+ found=false
+ fi
+
+ echo "found=${found}" | tee -a $GITHUB_OUTPUT
diff --git a/.github/workflows/cleanup-caches-by-a-branch.yml b/.github/workflows/cleanup-caches-by-a-branch.yml
new file mode 100644
index 0000000000..d8c225dedb
--- /dev/null
+++ b/.github/workflows/cleanup-caches-by-a-branch.yml
@@ -0,0 +1,32 @@
+# A workflow from
+# https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries
+
+name: cleanup caches by a branch
+on:
+ pull_request:
+ types:
+ - closed
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cleanup
+ run: |
+ gh extension install actions/gh-actions-cache
+
+ echo "Fetching list of cache key"
+ cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
+
+ ## Setting this to not fail the workflow while deleting cache keys.
+ set +e
+ echo "Deleting caches..."
+ for cacheKey in $cacheKeysForPR
+ do
+ gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
+ done
+ echo "Done"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge
diff --git a/.github/workflows/neon_extra_builds.yml b/.github/workflows/neon_extra_builds.yml
index 1c9763cc00..5a2f9d6645 100644
--- a/.github/workflows/neon_extra_builds.yml
+++ b/.github/workflows/neon_extra_builds.yml
@@ -26,6 +26,17 @@ jobs:
with:
github-event-name: ${{ github.event_name}}
+ check-build-tools-image:
+ needs: [ check-permissions ]
+ uses: ./.github/workflows/check-build-tools-image.yml
+
+ build-build-tools-image:
+ needs: [ check-build-tools-image ]
+ uses: ./.github/workflows/build-build-tools-image.yml
+ with:
+ image-tag: ${{ needs.check-build-tools-image.outputs.image-tag }}
+ secrets: inherit
+
check-macos-build:
needs: [ check-permissions ]
if: |
@@ -123,7 +134,7 @@ jobs:
run: ./run_clippy.sh
check-linux-arm-build:
- needs: [ check-permissions ]
+ needs: [ check-permissions, build-build-tools-image ]
timeout-minutes: 90
runs-on: [ self-hosted, dev, arm64 ]
@@ -137,7 +148,10 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY_DEV }}
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:pinned
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -244,12 +258,15 @@ jobs:
cargo nextest run --package remote_storage --test test_real_azure
check-codestyle-rust-arm:
- needs: [ check-permissions ]
+ needs: [ check-permissions, build-build-tools-image ]
timeout-minutes: 90
runs-on: [ self-hosted, dev, arm64 ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
steps:
@@ -316,14 +333,17 @@ jobs:
run: cargo deny check
gather-rust-build-stats:
- needs: [ check-permissions ]
+ needs: [ check-permissions, build-build-tools-image ]
if: |
contains(github.event.pull_request.labels.*.name, 'run-extra-build-stats') ||
contains(github.event.pull_request.labels.*.name, 'run-extra-build-*') ||
github.ref_name == 'main'
runs-on: [ self-hosted, gen3, large ]
container:
- image: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/rust:pinned
+ image: ${{ needs.build-build-tools-image.outputs.image }}
+ credentials:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
options: --init
env:
diff --git a/.github/workflows/pin-build-tools-image.yml b/.github/workflows/pin-build-tools-image.yml
new file mode 100644
index 0000000000..c941692066
--- /dev/null
+++ b/.github/workflows/pin-build-tools-image.yml
@@ -0,0 +1,72 @@
+name: 'Pin build-tools image'
+
+on:
+ workflow_dispatch:
+ inputs:
+ from-tag:
+ description: 'Source tag'
+ required: true
+ type: string
+ workflow_call:
+ inputs:
+ from-tag:
+ description: 'Source tag'
+ required: true
+ type: string
+
+defaults:
+ run:
+ shell: bash -euo pipefail {0}
+
+concurrency:
+ group: pin-build-tools-image-${{ inputs.from-tag }}
+
+permissions: {}
+
+jobs:
+ tag-image:
+ runs-on: ubuntu-latest
+
+ env:
+ FROM_TAG: ${{ inputs.from-tag }}
+ TO_TAG: pinned
+
+ steps:
+ - name: Check if we really need to pin the image
+ id: check-manifests
+ run: |
+ docker manifest inspect neondatabase/build-tools:${FROM_TAG} > ${FROM_TAG}.json
+ docker manifest inspect neondatabase/build-tools:${TO_TAG} > ${TO_TAG}.json
+
+ if diff ${FROM_TAG}.json ${TO_TAG}.json; then
+ skip=true
+ else
+ skip=false
+ fi
+
+ echo "skip=${skip}" | tee -a $GITHUB_OUTPUT
+
+ - uses: docker/login-action@v3
+ if: steps.check-manifests.outputs.skip == 'false'
+ with:
+ username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
+ password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
+
+ - name: Tag build-tools with `${{ env.TO_TAG }}` in Docker Hub
+ if: steps.check-manifests.outputs.skip == 'false'
+ run: |
+ docker buildx imagetools create -t neondatabase/build-tools:${TO_TAG} \
+ neondatabase/build-tools:${FROM_TAG}
+
+ - uses: docker/login-action@v3
+ if: steps.check-manifests.outputs.skip == 'false'
+ with:
+ registry: 369495373322.dkr.ecr.eu-central-1.amazonaws.com
+ username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
+ password: ${{ secrets.AWS_SECRET_KEY_DEV }}
+
+ - name: Tag build-tools with `${{ env.TO_TAG }}` in ECR
+ if: steps.check-manifests.outputs.skip == 'false'
+ run: |
+ docker buildx imagetools create -t 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools:${TO_TAG} \
+ neondatabase/build-tools:${FROM_TAG}
diff --git a/.github/workflows/update_build_tools_image.yml b/.github/workflows/update_build_tools_image.yml
deleted file mode 100644
index 900724fc60..0000000000
--- a/.github/workflows/update_build_tools_image.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-name: 'Update build tools image tag'
-
-# This workflow it used to update tag of build tools in ECR.
-# The most common use case is adding/moving `pinned` tag to `${GITHUB_RUN_IT}` image.
-
-on:
- workflow_dispatch:
- inputs:
- from-tag:
- description: 'Source tag'
- required: true
- type: string
- to-tag:
- description: 'Destination tag'
- required: true
- type: string
- default: 'pinned'
-
-defaults:
- run:
- shell: bash -euo pipefail {0}
-
-permissions: {}
-
-jobs:
- tag-image:
- runs-on: [ self-hosted, gen3, small ]
-
- env:
- ECR_IMAGE: 369495373322.dkr.ecr.eu-central-1.amazonaws.com/build-tools
- DOCKER_HUB_IMAGE: docker.io/neondatabase/build-tools
- FROM_TAG: ${{ inputs.from-tag }}
- TO_TAG: ${{ inputs.to-tag }}
-
- steps:
- # Use custom DOCKER_CONFIG directory to avoid conflicts with default settings
- # The default value is ~/.docker
- - name: Set custom docker config directory
- run: |
- mkdir -p .docker-custom
- echo DOCKER_CONFIG=$(pwd)/.docker-custom >> $GITHUB_ENV
-
- - uses: docker/login-action@v2
- with:
- username: ${{ secrets.NEON_DOCKERHUB_USERNAME }}
- password: ${{ secrets.NEON_DOCKERHUB_PASSWORD }}
-
- - uses: docker/login-action@v2
- with:
- registry: 369495373322.dkr.ecr.eu-central-1.amazonaws.com
- username: ${{ secrets.AWS_ACCESS_KEY_DEV }}
- password: ${{ secrets.AWS_SECRET_KEY_DEV }}
-
- - uses: actions/setup-go@v5
- with:
- go-version: '1.21'
-
- - name: Install crane
- run: |
- go install github.com/google/go-containerregistry/cmd/crane@a0658aa1d0cc7a7f1bcc4a3af9155335b6943f40 # v0.18.0
-
- - name: Copy images
- run: |
- crane copy "${ECR_IMAGE}:${FROM_TAG}" "${ECR_IMAGE}:${TO_TAG}"
- crane copy "${ECR_IMAGE}:${FROM_TAG}" "${DOCKER_HUB_IMAGE}:${TO_TAG}"
-
- - name: Remove custom docker config directory
- if: always()
- run: |
- rm -rf .docker-custom
diff --git a/.gitignore b/.gitignore
index 3f4495c9e7..2c38cdcc59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ test_output/
neon.iml
/.neon
/integration_tests/.neon
+compaction-suite-results.*
# Coverage
*.profraw
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2e447fba47..164eb77f58 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -74,16 +74,11 @@ We're using the following approach to make it work:
For details see [`approved-for-ci-run.yml`](.github/workflows/approved-for-ci-run.yml)
-## How do I add the "pinned" tag to an buildtools image?
-We use the `pinned` tag for `Dockerfile.buildtools` build images in our CI/CD setup, currently adding the `pinned` tag is a manual operation.
+## How do I make build-tools image "pinned"
-You can call it from GitHub UI: https://github.com/neondatabase/neon/actions/workflows/update_build_tools_image.yml,
-or using GitHub CLI:
+It's possible to update the `pinned` tag of the `build-tools` image using the `pin-build-tools-image.yml` workflow.
```bash
-gh workflow -R neondatabase/neon run update_build_tools_image.yml \
- -f from-tag=6254913013 \
- -f to-tag=pinned \
-
-# Default `-f to-tag` is `pinned`, so the parameter can be omitted.
-```
\ No newline at end of file
+gh workflow -R neondatabase/neon run pin-build-tools-image.yml \
+ -f from-tag=cc98d9b00d670f182c507ae3783342bd7e64c31e
+```
diff --git a/Cargo.lock b/Cargo.lock
index abb335e97c..dead212156 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3498,6 +3498,7 @@ dependencies = [
"num_cpus",
"once_cell",
"pageserver_api",
+ "pageserver_compaction",
"pin-project-lite",
"postgres",
"postgres-protocol",
@@ -3588,6 +3589,53 @@ dependencies = [
"workspace_hack",
]
+[[package]]
+name = "pageserver_compaction"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-compression",
+ "async-stream",
+ "async-trait",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "clap",
+ "const_format",
+ "consumption_metrics",
+ "criterion",
+ "crossbeam-utils",
+ "either",
+ "fail",
+ "flate2",
+ "futures",
+ "git-version",
+ "hex",
+ "hex-literal",
+ "humantime",
+ "humantime-serde",
+ "itertools",
+ "metrics",
+ "once_cell",
+ "pageserver_api",
+ "pin-project-lite",
+ "rand 0.8.5",
+ "smallvec",
+ "svg_fmt",
+ "sync_wrapper",
+ "thiserror",
+ "tokio",
+ "tokio-io-timeout",
+ "tokio-util",
+ "tracing",
+ "tracing-error",
+ "tracing-subscriber",
+ "url",
+ "utils",
+ "walkdir",
+ "workspace_hack",
+]
+
[[package]]
name = "parking"
version = "2.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index 98fbc9c4f4..90b02b30ec 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ members = [
"control_plane",
"control_plane/attachment_service",
"pageserver",
+ "pageserver/compaction",
"pageserver/ctl",
"pageserver/client",
"pageserver/pagebench",
@@ -199,6 +200,7 @@ consumption_metrics = { version = "0.1", path = "./libs/consumption_metrics/" }
metrics = { version = "0.1", path = "./libs/metrics/" }
pageserver_api = { version = "0.1", path = "./libs/pageserver_api/" }
pageserver_client = { path = "./pageserver/client" }
+pageserver_compaction = { version = "0.1", path = "./pageserver/compaction/" }
postgres_backend = { version = "0.1", path = "./libs/postgres_backend/" }
postgres_connection = { version = "0.1", path = "./libs/postgres_connection/" }
postgres_ffi = { version = "0.1", path = "./libs/postgres_ffi/" }
diff --git a/Dockerfile.buildtools b/Dockerfile.build-tools
similarity index 100%
rename from Dockerfile.buildtools
rename to Dockerfile.build-tools
diff --git a/Dockerfile.compute-node b/Dockerfile.compute-node
index 149ca5109b..c73b9ce5c9 100644
--- a/Dockerfile.compute-node
+++ b/Dockerfile.compute-node
@@ -891,7 +891,17 @@ ENV BUILD_TAG=$BUILD_TAG
USER nonroot
# Copy entire project to get Cargo.* files with proper dependencies for the whole project
COPY --chown=nonroot . .
-RUN cd compute_tools && cargo build --locked --profile release-line-debug-size-lto
+RUN cd compute_tools && mold -run cargo build --locked --profile release-line-debug-size-lto
+
+#########################################################################################
+#
+# Final compute-tools image
+#
+#########################################################################################
+
+FROM debian:bullseye-slim AS compute-tools-image
+
+COPY --from=compute-tools /home/nonroot/target/release-line-debug-size-lto/compute_ctl /usr/local/bin/compute_ctl
#########################################################################################
#
diff --git a/Dockerfile.compute-tools b/Dockerfile.compute-tools
deleted file mode 100644
index cc305cc556..0000000000
--- a/Dockerfile.compute-tools
+++ /dev/null
@@ -1,32 +0,0 @@
-# First transient image to build compute_tools binaries
-# NB: keep in sync with rust image version in .github/workflows/build_and_test.yml
-ARG REPOSITORY=neondatabase
-ARG IMAGE=build-tools
-ARG TAG=pinned
-ARG BUILD_TAG
-
-FROM $REPOSITORY/$IMAGE:$TAG AS rust-build
-WORKDIR /home/nonroot
-
-# Enable https://github.com/paritytech/cachepot to cache Rust crates' compilation results in Docker builds.
-# Set up cachepot to use an AWS S3 bucket for cache results, to reuse it between `docker build` invocations.
-# cachepot falls back to local filesystem if S3 is misconfigured, not failing the build.
-ARG RUSTC_WRAPPER=cachepot
-ENV AWS_REGION=eu-central-1
-ENV CACHEPOT_S3_KEY_PREFIX=cachepot
-ARG CACHEPOT_BUCKET=neon-github-dev
-#ARG AWS_ACCESS_KEY_ID
-#ARG AWS_SECRET_ACCESS_KEY
-ARG BUILD_TAG
-ENV BUILD_TAG=$BUILD_TAG
-
-COPY . .
-
-RUN set -e \
- && mold -run cargo build -p compute_tools --locked --release \
- && cachepot -s
-
-# Final image that only has one binary
-FROM debian:bullseye-slim
-
-COPY --from=rust-build /home/nonroot/target/release/compute_ctl /usr/local/bin/compute_ctl
diff --git a/README.md b/README.md
index 1c4f32d286..95926b4628 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Neon is a serverless open-source alternative to AWS Aurora Postgres. It separates storage and compute and substitutes the PostgreSQL storage layer by redistributing data across a cluster of nodes.
## Quick start
-Try the [Neon Free Tier](https://neon.tech) to create a serverless Postgres instance. Then connect to it with your preferred Postgres client (psql, dbeaver, etc) or use the online [SQL Editor](https://neon.tech/docs/get-started-with-neon/query-with-neon-sql-editor/). See [Connect from any application](https://neon.tech/docs/connect/connect-from-any-app/) for connection instructions.
+Try the [Neon Free Tier](https://neon.tech/github) to create a serverless Postgres instance. Then connect to it with your preferred Postgres client (psql, dbeaver, etc) or use the online [SQL Editor](https://neon.tech/docs/get-started-with-neon/query-with-neon-sql-editor/). See [Connect from any application](https://neon.tech/docs/connect/connect-from-any-app/) for connection instructions.
Alternatively, compile and run the project [locally](#running-local-installation).
@@ -230,6 +230,10 @@ postgres=# select * from t;
> cargo neon stop
```
+#### Handling build failures
+
+If you encounter errors during setting up the initial tenant, it's best to stop everything (`cargo neon stop`) and remove the `.neon` directory. Then fix the problems, and start the setup again.
+
## Running tests
Ensure your dependencies are installed as described [here](https://github.com/neondatabase/neon#dependency-installation-notes).
@@ -259,6 +263,12 @@ You can use [`flamegraph-rs`](https://github.com/flamegraph-rs/flamegraph) or th
> It's a [general thing with Rust / lld / mold](https://crbug.com/919499#c16), not specific to this repository.
> See [this PR for further instructions](https://github.com/neondatabase/neon/pull/6764).
+## Cleanup
+
+For cleaning up the source tree from build artifacts, run `make clean` in the source directory.
+
+For removing every artifact from build and configure steps, run `make distclean`, and also consider removing the cargo binaries in the `target` directory, as well as the database in the `.neon` directory. Note that removing the `.neon` directory will remove your database, with all data in it. You have been warned!
+
## Documentation
[docs](/docs) Contains a top-level overview of all available markdown documentation.
diff --git a/compute_tools/src/spec.rs b/compute_tools/src/spec.rs
index b515f9f408..d5fd2c9462 100644
--- a/compute_tools/src/spec.rs
+++ b/compute_tools/src/spec.rs
@@ -676,8 +676,15 @@ pub fn handle_grants(
GRANT CREATE ON SCHEMA public TO web_access;\n\
END IF;\n\
END IF;\n\
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO neon_superuser WITH GRANT OPTION;\n\
- ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO neon_superuser WITH GRANT OPTION;\n\
+ IF EXISTS(\n\
+ SELECT nspname\n\
+ FROM pg_catalog.pg_namespace\n\
+ WHERE nspname = 'public'\n\
+ )\n\
+ THEN\n\
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO neon_superuser WITH GRANT OPTION;\n\
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO neon_superuser WITH GRANT OPTION;\n\
+ END IF;\n\
END\n\
$$;"
.to_string();
diff --git a/control_plane/attachment_service/src/auth.rs b/control_plane/attachment_service/src/auth.rs
new file mode 100644
index 0000000000..ef47abf8c7
--- /dev/null
+++ b/control_plane/attachment_service/src/auth.rs
@@ -0,0 +1,9 @@
+use utils::auth::{AuthError, Claims, Scope};
+
+pub fn check_permission(claims: &Claims, required_scope: Scope) -> Result<(), AuthError> {
+ if claims.scope != required_scope {
+ return Err(AuthError("Scope mismatch. Permission denied".into()));
+ }
+
+ Ok(())
+}
diff --git a/control_plane/attachment_service/src/http.rs b/control_plane/attachment_service/src/http.rs
index f9c4535bd5..f1153c2c18 100644
--- a/control_plane/attachment_service/src/http.rs
+++ b/control_plane/attachment_service/src/http.rs
@@ -10,8 +10,8 @@ use pageserver_api::shard::TenantShardId;
use pageserver_client::mgmt_api;
use std::sync::Arc;
use std::time::{Duration, Instant};
-use utils::auth::SwappableJwtAuth;
-use utils::http::endpoint::{auth_middleware, request_span};
+use utils::auth::{Scope, SwappableJwtAuth};
+use utils::http::endpoint::{auth_middleware, check_permission_with, request_span};
use utils::http::request::{must_get_query_param, parse_request_param};
use utils::id::{TenantId, TimelineId};
@@ -25,12 +25,12 @@ use utils::{
id::NodeId,
};
-use pageserver_api::control_api::{ReAttachRequest, ValidateRequest};
-
-use control_plane::attachment_service::{
- AttachHookRequest, InspectRequest, NodeConfigureRequest, NodeRegisterRequest,
- TenantShardMigrateRequest,
+use pageserver_api::controller_api::{
+ NodeConfigureRequest, NodeRegisterRequest, TenantShardMigrateRequest,
};
+use pageserver_api::upcall_api::{ReAttachRequest, ValidateRequest};
+
+use control_plane::attachment_service::{AttachHookRequest, InspectRequest};
/// State available to HTTP request handlers
#[derive(Clone)]
@@ -64,6 +64,8 @@ fn get_state(request: &Request
) -> &HttpState {
/// Pageserver calls into this on startup, to learn which tenants it should attach
async fn handle_re_attach(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::GenerationsApi)?;
+
let reattach_req = json_request::(&mut req).await?;
let state = get_state(&req);
json_response(StatusCode::OK, state.service.re_attach(reattach_req).await?)
@@ -72,6 +74,8 @@ async fn handle_re_attach(mut req: Request) -> Result, ApiE
/// Pageserver calls into this before doing deletions, to confirm that it still
/// holds the latest generation for the tenants with deletions enqueued
async fn handle_validate(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::GenerationsApi)?;
+
let validate_req = json_request::(&mut req).await?;
let state = get_state(&req);
json_response(StatusCode::OK, state.service.validate(validate_req))
@@ -81,6 +85,8 @@ async fn handle_validate(mut req: Request) -> Result, ApiEr
/// (in the real control plane this is unnecessary, because the same program is managing
/// generation numbers and doing attachments).
async fn handle_attach_hook(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let attach_req = json_request::(&mut req).await?;
let state = get_state(&req);
@@ -95,6 +101,8 @@ async fn handle_attach_hook(mut req: Request) -> Result, Ap
}
async fn handle_inspect(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let inspect_req = json_request::(&mut req).await?;
let state = get_state(&req);
@@ -106,6 +114,8 @@ async fn handle_tenant_create(
service: Arc,
mut req: Request,
) -> Result, ApiError> {
+ check_permissions(&req, Scope::PageServerApi)?;
+
let create_req = json_request::(&mut req).await?;
json_response(
StatusCode::CREATED,
@@ -164,6 +174,8 @@ async fn handle_tenant_location_config(
mut req: Request,
) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
+
let config_req = json_request::(&mut req).await?;
json_response(
StatusCode::OK,
@@ -178,6 +190,8 @@ async fn handle_tenant_time_travel_remote_storage(
mut req: Request,
) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
+
let time_travel_req = json_request::(&mut req).await?;
let timestamp_raw = must_get_query_param(&req, "travel_to")?;
@@ -211,6 +225,7 @@ async fn handle_tenant_delete(
req: Request,
) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
deletion_wrapper(service, move |service| async move {
service.tenant_delete(tenant_id).await
@@ -223,6 +238,8 @@ async fn handle_tenant_timeline_create(
mut req: Request,
) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
+
let create_req = json_request::(&mut req).await?;
json_response(
StatusCode::CREATED,
@@ -237,6 +254,8 @@ async fn handle_tenant_timeline_delete(
req: Request,
) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
+
let timeline_id: TimelineId = parse_request_param(&req, "timeline_id")?;
deletion_wrapper(service, move |service| async move {
@@ -250,6 +269,7 @@ async fn handle_tenant_timeline_passthrough(
req: Request,
) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
let Some(path) = req.uri().path_and_query() else {
// This should never happen, our request router only calls us if there is a path
@@ -293,11 +313,15 @@ async fn handle_tenant_locate(
service: Arc,
req: Request,
) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
json_response(StatusCode::OK, service.tenant_locate(tenant_id)?)
}
async fn handle_node_register(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let register_req = json_request::(&mut req).await?;
let state = get_state(&req);
state.service.node_register(register_req).await?;
@@ -305,17 +329,23 @@ async fn handle_node_register(mut req: Request) -> Result,
}
async fn handle_node_list(req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let state = get_state(&req);
json_response(StatusCode::OK, state.service.node_list().await?)
}
async fn handle_node_drop(req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let state = get_state(&req);
let node_id: NodeId = parse_request_param(&req, "node_id")?;
json_response(StatusCode::OK, state.service.node_drop(node_id).await?)
}
async fn handle_node_configure(mut req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let node_id: NodeId = parse_request_param(&req, "node_id")?;
let config_req = json_request::(&mut req).await?;
if node_id != config_req.node_id {
@@ -335,6 +365,8 @@ async fn handle_tenant_shard_split(
service: Arc,
mut req: Request,
) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
let split_req = json_request::(&mut req).await?;
@@ -348,6 +380,8 @@ async fn handle_tenant_shard_migrate(
service: Arc,
mut req: Request,
) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let tenant_shard_id: TenantShardId = parse_request_param(&req, "tenant_shard_id")?;
let migrate_req = json_request::(&mut req).await?;
json_response(
@@ -360,22 +394,30 @@ async fn handle_tenant_shard_migrate(
async fn handle_tenant_drop(req: Request) -> Result, ApiError> {
let tenant_id: TenantId = parse_request_param(&req, "tenant_id")?;
+ check_permissions(&req, Scope::PageServerApi)?;
+
let state = get_state(&req);
json_response(StatusCode::OK, state.service.tenant_drop(tenant_id).await?)
}
async fn handle_tenants_dump(req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let state = get_state(&req);
state.service.tenants_dump()
}
async fn handle_scheduler_dump(req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let state = get_state(&req);
state.service.scheduler_dump()
}
async fn handle_consistency_check(req: Request) -> Result, ApiError> {
+ check_permissions(&req, Scope::Admin)?;
+
let state = get_state(&req);
json_response(StatusCode::OK, state.service.consistency_check().await?)
@@ -432,6 +474,12 @@ where
.await
}
+fn check_permissions(request: &Request, required_scope: Scope) -> Result<(), ApiError> {
+ check_permission_with(request, |claims| {
+ crate::auth::check_permission(claims, required_scope)
+ })
+}
+
pub fn make_router(
service: Arc,
auth: Option>,
diff --git a/control_plane/attachment_service/src/lib.rs b/control_plane/attachment_service/src/lib.rs
index e950a57e57..ce613e858f 100644
--- a/control_plane/attachment_service/src/lib.rs
+++ b/control_plane/attachment_service/src/lib.rs
@@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};
use utils::seqwait::MonotonicCounter;
+mod auth;
mod compute_hook;
pub mod http;
pub mod metrics;
diff --git a/control_plane/attachment_service/src/node.rs b/control_plane/attachment_service/src/node.rs
index 09162701ac..1f9dcef033 100644
--- a/control_plane/attachment_service/src/node.rs
+++ b/control_plane/attachment_service/src/node.rs
@@ -1,4 +1,4 @@
-use control_plane::attachment_service::{NodeAvailability, NodeSchedulingPolicy};
+use pageserver_api::controller_api::{NodeAvailability, NodeSchedulingPolicy};
use serde::Serialize;
use utils::id::NodeId;
diff --git a/control_plane/attachment_service/src/persistence.rs b/control_plane/attachment_service/src/persistence.rs
index 4f336093cf..1b98cc7655 100644
--- a/control_plane/attachment_service/src/persistence.rs
+++ b/control_plane/attachment_service/src/persistence.rs
@@ -6,10 +6,10 @@ use std::time::Duration;
use self::split_state::SplitState;
use camino::Utf8Path;
use camino::Utf8PathBuf;
-use control_plane::attachment_service::NodeSchedulingPolicy;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::Connection;
+use pageserver_api::controller_api::NodeSchedulingPolicy;
use pageserver_api::models::TenantConfig;
use pageserver_api::shard::{ShardCount, ShardNumber, TenantShardId};
use serde::{Deserialize, Serialize};
diff --git a/control_plane/attachment_service/src/reconciler.rs b/control_plane/attachment_service/src/reconciler.rs
index 751b06f93a..ce91c1f5e9 100644
--- a/control_plane/attachment_service/src/reconciler.rs
+++ b/control_plane/attachment_service/src/reconciler.rs
@@ -1,6 +1,6 @@
use crate::persistence::Persistence;
use crate::service;
-use control_plane::attachment_service::NodeAvailability;
+use pageserver_api::controller_api::NodeAvailability;
use pageserver_api::models::{
LocationConfig, LocationConfigMode, LocationConfigSecondary, TenantConfig,
};
diff --git a/control_plane/attachment_service/src/scheduler.rs b/control_plane/attachment_service/src/scheduler.rs
index 7059071bee..3224751e47 100644
--- a/control_plane/attachment_service/src/scheduler.rs
+++ b/control_plane/attachment_service/src/scheduler.rs
@@ -255,7 +255,7 @@ impl Scheduler {
pub(crate) mod test_utils {
use crate::node::Node;
- use control_plane::attachment_service::{NodeAvailability, NodeSchedulingPolicy};
+ use pageserver_api::controller_api::{NodeAvailability, NodeSchedulingPolicy};
use std::collections::HashMap;
use utils::id::NodeId;
/// Test helper: synthesize the requested number of nodes, all in active state.
diff --git a/control_plane/attachment_service/src/service.rs b/control_plane/attachment_service/src/service.rs
index 8a80d0c746..02c1a65545 100644
--- a/control_plane/attachment_service/src/service.rs
+++ b/control_plane/attachment_service/src/service.rs
@@ -9,19 +9,17 @@ use std::{
use anyhow::Context;
use control_plane::attachment_service::{
- AttachHookRequest, AttachHookResponse, InspectRequest, InspectResponse, NodeAvailability,
- NodeConfigureRequest, NodeRegisterRequest, NodeSchedulingPolicy, TenantCreateResponse,
- TenantCreateResponseShard, TenantLocateResponse, TenantLocateResponseShard,
- TenantShardMigrateRequest, TenantShardMigrateResponse,
+ AttachHookRequest, AttachHookResponse, InspectRequest, InspectResponse,
};
use diesel::result::DatabaseErrorKind;
use futures::{stream::FuturesUnordered, StreamExt};
use hyper::StatusCode;
+use pageserver_api::controller_api::{
+ NodeAvailability, NodeConfigureRequest, NodeRegisterRequest, NodeSchedulingPolicy,
+ TenantCreateResponse, TenantCreateResponseShard, TenantLocateResponse,
+ TenantLocateResponseShard, TenantShardMigrateRequest, TenantShardMigrateResponse,
+};
use pageserver_api::{
- control_api::{
- ReAttachRequest, ReAttachResponse, ReAttachResponseTenant, ValidateRequest,
- ValidateResponse, ValidateResponseTenant,
- },
models::{
self, LocationConfig, LocationConfigListResponse, LocationConfigMode, ShardParameters,
TenantConfig, TenantCreateRequest, TenantLocationConfigRequest,
@@ -29,6 +27,10 @@ use pageserver_api::{
TenantShardSplitResponse, TenantTimeTravelRequest, TimelineCreateRequest, TimelineInfo,
},
shard::{ShardCount, ShardIdentity, ShardNumber, ShardStripeSize, TenantShardId},
+ upcall_api::{
+ ReAttachRequest, ReAttachResponse, ReAttachResponseTenant, ValidateRequest,
+ ValidateResponse, ValidateResponseTenant,
+ },
};
use pageserver_client::mgmt_api;
use tokio_util::sync::CancellationToken;
diff --git a/control_plane/attachment_service/src/tenant_state.rs b/control_plane/attachment_service/src/tenant_state.rs
index 02f0171c29..c14fe6699e 100644
--- a/control_plane/attachment_service/src/tenant_state.rs
+++ b/control_plane/attachment_service/src/tenant_state.rs
@@ -1,7 +1,7 @@
use std::{collections::HashMap, sync::Arc, time::Duration};
use crate::{metrics, persistence::TenantShardPersistence};
-use control_plane::attachment_service::NodeAvailability;
+use pageserver_api::controller_api::NodeAvailability;
use pageserver_api::{
models::{LocationConfig, LocationConfigMode, TenantConfig},
shard::{ShardIdentity, TenantShardId},
diff --git a/control_plane/src/attachment_service.rs b/control_plane/src/attachment_service.rs
index 4a1d316fe7..92342b478b 100644
--- a/control_plane/src/attachment_service.rs
+++ b/control_plane/src/attachment_service.rs
@@ -2,8 +2,12 @@ use crate::{background_process, local_env::LocalEnv};
use camino::{Utf8Path, Utf8PathBuf};
use hyper::Method;
use pageserver_api::{
+ controller_api::{
+ NodeConfigureRequest, NodeRegisterRequest, TenantCreateResponse, TenantLocateResponse,
+ TenantShardMigrateRequest, TenantShardMigrateResponse,
+ },
models::{
- ShardParameters, TenantCreateRequest, TenantShardSplitRequest, TenantShardSplitResponse,
+ TenantCreateRequest, TenantShardSplitRequest, TenantShardSplitResponse,
TimelineCreateRequest, TimelineInfo,
},
shard::TenantShardId,
@@ -11,12 +15,12 @@ use pageserver_api::{
use pageserver_client::mgmt_api::ResponseErrorMessageExt;
use postgres_backend::AuthType;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
-use std::str::FromStr;
+use std::{fs, str::FromStr};
use tokio::process::Command;
use tracing::instrument;
use url::Url;
use utils::{
- auth::{Claims, Scope},
+ auth::{encode_from_key_file, Claims, Scope},
id::{NodeId, TenantId},
};
@@ -24,7 +28,7 @@ pub struct AttachmentService {
env: LocalEnv,
listen: String,
path: Utf8PathBuf,
- jwt_token: Option,
+ private_key: Option>,
public_key: Option,
postgres_port: u16,
client: reqwest::Client,
@@ -55,126 +59,6 @@ pub struct InspectResponse {
pub attachment: Option<(u32, NodeId)>,
}
-#[derive(Serialize, Deserialize)]
-pub struct TenantCreateResponseShard {
- pub shard_id: TenantShardId,
- pub node_id: NodeId,
- pub generation: u32,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct TenantCreateResponse {
- pub shards: Vec,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct NodeRegisterRequest {
- pub node_id: NodeId,
-
- pub listen_pg_addr: String,
- pub listen_pg_port: u16,
-
- pub listen_http_addr: String,
- pub listen_http_port: u16,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct NodeConfigureRequest {
- pub node_id: NodeId,
-
- pub availability: Option,
- pub scheduling: Option,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct TenantLocateResponseShard {
- pub shard_id: TenantShardId,
- pub node_id: NodeId,
-
- pub listen_pg_addr: String,
- pub listen_pg_port: u16,
-
- pub listen_http_addr: String,
- pub listen_http_port: u16,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct TenantLocateResponse {
- pub shards: Vec,
- pub shard_params: ShardParameters,
-}
-
-/// Explicitly migrating a particular shard is a low level operation
-/// TODO: higher level "Reschedule tenant" operation where the request
-/// specifies some constraints, e.g. asking it to get off particular node(s)
-#[derive(Serialize, Deserialize, Debug)]
-pub struct TenantShardMigrateRequest {
- pub tenant_shard_id: TenantShardId,
- pub node_id: NodeId,
-}
-
-#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
-pub enum NodeAvailability {
- // Normal, happy state
- Active,
- // Offline: Tenants shouldn't try to attach here, but they may assume that their
- // secondary locations on this node still exist. Newly added nodes are in this
- // state until we successfully contact them.
- Offline,
-}
-
-impl FromStr for NodeAvailability {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> Result {
- match s {
- "active" => Ok(Self::Active),
- "offline" => Ok(Self::Offline),
- _ => Err(anyhow::anyhow!("Unknown availability state '{s}'")),
- }
- }
-}
-
-/// FIXME: this is a duplicate of the type in the attachment_service crate, because the
-/// type needs to be defined with diesel traits in there.
-#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq)]
-pub enum NodeSchedulingPolicy {
- Active,
- Filling,
- Pause,
- Draining,
-}
-
-impl FromStr for NodeSchedulingPolicy {
- type Err = anyhow::Error;
-
- fn from_str(s: &str) -> Result {
- match s {
- "active" => Ok(Self::Active),
- "filling" => Ok(Self::Filling),
- "pause" => Ok(Self::Pause),
- "draining" => Ok(Self::Draining),
- _ => Err(anyhow::anyhow!("Unknown scheduling state '{s}'")),
- }
- }
-}
-
-impl From for String {
- fn from(value: NodeSchedulingPolicy) -> String {
- use NodeSchedulingPolicy::*;
- match value {
- Active => "active",
- Filling => "filling",
- Pause => "pause",
- Draining => "draining",
- }
- .to_string()
- }
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct TenantShardMigrateResponse {}
-
impl AttachmentService {
pub fn from_env(env: &LocalEnv) -> Self {
let path = Utf8PathBuf::from_path_buf(env.base_data_dir.clone())
@@ -204,12 +88,11 @@ impl AttachmentService {
.pageservers
.first()
.expect("Config is validated to contain at least one pageserver");
- let (jwt_token, public_key) = match ps_conf.http_auth_type {
+ let (private_key, public_key) = match ps_conf.http_auth_type {
AuthType::Trust => (None, None),
AuthType::NeonJWT => {
- let jwt_token = env
- .generate_auth_token(&Claims::new(None, Scope::PageServerApi))
- .unwrap();
+ let private_key_path = env.get_private_key_path();
+ let private_key = fs::read(private_key_path).expect("failed to read private key");
// If pageserver auth is enabled, this implicitly enables auth for this service,
// using the same credentials.
@@ -235,7 +118,7 @@ impl AttachmentService {
} else {
std::fs::read_to_string(&public_key_path).expect("Can't read public key")
};
- (Some(jwt_token), Some(public_key))
+ (Some(private_key), Some(public_key))
}
};
@@ -243,7 +126,7 @@ impl AttachmentService {
env: env.clone(),
path,
listen,
- jwt_token,
+ private_key,
public_key,
postgres_port,
client: reqwest::ClientBuilder::new()
@@ -397,7 +280,10 @@ impl AttachmentService {
.into_iter()
.map(|s| s.to_string())
.collect::>();
- if let Some(jwt_token) = &self.jwt_token {
+ if let Some(private_key) = &self.private_key {
+ let claims = Claims::new(None, Scope::PageServerApi);
+ let jwt_token =
+ encode_from_key_file(&claims, private_key).expect("failed to generate jwt token");
args.push(format!("--jwt-token={jwt_token}"));
}
@@ -422,7 +308,7 @@ impl AttachmentService {
)],
background_process::InitialPidFile::Create(self.pid_file()),
|| async {
- match self.status().await {
+ match self.ready().await {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
@@ -468,6 +354,20 @@ impl AttachmentService {
Ok(())
}
+ fn get_claims_for_path(path: &str) -> anyhow::Result