diff --git a/.github/actions/get-semantic-release-version/action.yml b/.github/actions/get-semantic-release-version/action.yml new file mode 100644 index 00000000000..89f6a8f81c1 --- /dev/null +++ b/.github/actions/get-semantic-release-version/action.yml @@ -0,0 +1,87 @@ +name: Get semantic release version +description: "" +inputs: + custom_version: # Optional input for a custom version + description: "Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing" + required: false + token: + description: "Personal Access Token" + required: true + default: "" +outputs: + release_version: + description: "The release version to use (e.g., v1.2.3)" + value: ${{ steps.get_release_version.outputs.release_version }} + version_without_prefix: + description: "The release version to use without 'v' (e.g., 1.2.3)" + value: ${{ steps.get_release_version_without_prefix.outputs.version_without_prefix }} + highest_semver_tag: + description: "The highest semantic version tag without the 'v' prefix (e.g., 1.2.3)" + value: ${{ steps.get_highest_semver.outputs.highest_semver_tag }} +runs: + using: composite + steps: + - name: Get release version + id: get_release_version + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.token }} + GIT_AUTHOR_NAME: feast-ci-bot + GIT_AUTHOR_EMAIL: feast-ci-bot@willem.co + GIT_COMMITTER_NAME: feast-ci-bot + GIT_COMMITTER_EMAIL: feast-ci-bot@willem.co + run: | + if [[ -n "${{ inputs.custom_version }}" ]]; then + VERSION_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" + echo "Using custom version: ${{ inputs.custom_version }}" + if [[ ! "${{ inputs.custom_version }}" =~ $VERSION_REGEX ]]; then + echo "Error: custom_version must match semantic versioning (e.g., v1.2.3)." + exit 1 + fi + echo "::set-output name=release_version::${{ inputs.custom_version }}" + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + echo "Using tag reference: ${GITHUB_REF#refs/tags/}" + echo "::set-output name=release_version::${GITHUB_REF#refs/tags/}" + else + echo "Defaulting to branch name: ${GITHUB_REF#refs/heads/}" + echo "::set-output name=release_version::${GITHUB_REF#refs/heads/}" + fi + - name: Get release version without prefix + id: get_release_version_without_prefix + shell: bash + env: + RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }} + run: | + if [[ "${RELEASE_VERSION}" == v* ]]; then + echo "::set-output name=version_without_prefix::${RELEASE_VERSION:1}" + else + echo "::set-output name=version_without_prefix::${RELEASE_VERSION}" + fi + - name: Get highest semver + id: get_highest_semver + shell: bash + env: + RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }} + run: | + if [[ -n "${{ inputs.custom_version }}" ]]; then + HIGHEST_SEMVER_TAG="${{ inputs.custom_version }}" + echo "::set-output name=highest_semver_tag::$HIGHEST_SEMVER_TAG" + echo "Using custom version as highest semantic version: $HIGHEST_SEMVER_TAG" + else + source infra/scripts/setup-common-functions.sh + SEMVER_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' + if echo "${RELEASE_VERSION}" | grep -P "$SEMVER_REGEX" &>/dev/null ; then + echo ::set-output name=highest_semver_tag::$(get_tag_release -m) + echo "Using infra/scripts/setup-common-functions.sh to generate highest semantic version: $HIGHEST_SEMVER_TAG" + fi + fi + - name: Check output + shell: bash + env: + RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }} + VERSION_WITHOUT_PREFIX: ${{ steps.get_release_version_without_prefix.outputs.version_without_prefix }} + HIGHEST_SEMVER_TAG: ${{ steps.get_highest_semver.outputs.highest_semver_tag }} + run: | + echo $RELEASE_VERSION + echo $VERSION_WITHOUT_PREFIX + echo $HIGHEST_SEMVER_TAG \ No newline at end of file diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index a538970714f..8f71b41d08b 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -1,30 +1,33 @@ -name: build_wheels +name: build wheels # Call this workflow from other workflows in the repository by specifying "uses: ./.github/workflows/build_wheels.yml" # Developers who are starting a new release should use this workflow to ensure wheels will be built correctly. # Devs should check out their fork, add a tag to the last master commit on their fork, and run the release off of their fork on the added tag to ensure wheels will be built correctly. on: - workflow_dispatch: - tags: - - 'v*.*.*' - workflow_call: + workflow_dispatch: # Allows manual trigger of the workflow inputs: - release_version: - description: 'The release version to use (e.g., v1.2.3)' + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false + type: string + token: + description: 'Personal Access Token' required: true + default: "" + type: string + workflow_call: + inputs: + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false type: string - highest_semver_tag: - description: 'The highest semantic version tag without the "v" prefix (e.g., 1.2.3)' + token: + description: 'Personal Access Token' required: true + default: "" type: string jobs: - get-version: - uses: ./.github/workflows/get_semantic_release_version.yaml - with: - custom_version: ${{ github.event.inputs.custom_version }} - token: ${{ github.event.inputs.token }} - build-python-wheel: name: Build wheels runs-on: ubuntu-latest @@ -42,10 +45,18 @@ jobs: registry-url: 'https://registry.npmjs.org' - name: Build UI run: make build-ui + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Build wheels + env: + VERSION: ${{ steps.get-version.outputs.release_version }} + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | git fetch --tags - git checkout v0.42.0 + git checkout ${VERSION} python -m pip install build python -m build --wheel --outdir wheelhouse/ - uses: actions/upload-artifact@v4 @@ -69,11 +80,18 @@ jobs: with: node-version-file: './ui/.nvmrc' registry-url: 'https://registry.npmjs.org' + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Build and install dependencies + env: + VERSION: ${{ steps.get-version.outputs.release_version }} # There's a `git restore` in here because `make install-go-ci-dependencies` is actually messing up go.mod & go.sum. run: | git fetch --tags - git checkout v0.42.0 + git checkout ${VERSION} pip install -U pip setuptools wheel twine make build-ui git status @@ -89,11 +107,12 @@ jobs: # We add this step so the docker images can be built as part of the pre-release verification steps. build-docker-images: + name: Build Docker images runs-on: ubuntu-latest - needs: get-version + needs: [ build-python-wheel, build-source-distribution ] strategy: matrix: - component: [ feature-server, feature-server-java, feature-transformation-server, feast-operator ] + component: [ feature-server-dev, feature-server-java, feature-transformation-server, feast-operator ] env: REGISTRY: feastdev steps: @@ -102,15 +121,23 @@ jobs: uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Build image + env: + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} + RELEASE_VERSION: ${{ steps.get-version.outputs.release_version }} run: | + echo "Building docker image for ${{ matrix.component }} with version $VERSION_WITHOUT_PREFIX and release version $RELEASE_VERSION" make build-${{ matrix.component }}-docker REGISTRY=${REGISTRY} VERSION=${VERSION_WITHOUT_PREFIX} - env: - VERSION_WITHOUT_PREFIX: ${{ needs.get-version.outputs.version_without_prefix }} verify-python-wheels: + name: Verify Python wheels runs-on: ${{ matrix.os }} - needs: [ build-python-wheel, build-source-distribution, get-version ] + needs: [ build-python-wheel, build-source-distribution ] strategy: matrix: os: [ ubuntu-latest, macos-13 ] @@ -129,8 +156,8 @@ jobs: else echo "Succeeded!" fi - VERSION_WITHOUT_PREFIX: ${{ needs.get-version.outputs.version_without_prefix }} steps: + - uses: actions/checkout@v4 - name: Setup Python id: setup-python uses: actions/setup-python@v5 @@ -160,7 +187,14 @@ jobs: if: ${{ matrix.from-source }} run: pip install dist/*tar.gz # Validate that the feast version installed is not development and is the correct version of the tag we ran it off of. + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Validate Feast Version + env: + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} run: | feast version if ! VERSION_OUTPUT=$(feast version); then diff --git a/.github/workflows/get_semantic_release_version.yml b/.github/workflows/get_semantic_release_version.yml deleted file mode 100644 index c800810dced..00000000000 --- a/.github/workflows/get_semantic_release_version.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Get semantic release version - -on: - workflow_dispatch: # Allows manual trigger of the workflow - inputs: - custom_version: # Optional input for a custom version - description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' - required: false - token: - description: 'Personal Access Token' - required: true - default: "" - type: string - -jobs: - get-version: - if: github.repository == 'feast-dev/feast' - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ github.event.inputs.token }} - GIT_AUTHOR_NAME: feast-ci-bot - GIT_AUTHOR_EMAIL: feast-ci-bot@willem.co - GIT_COMMITTER_NAME: feast-ci-bot - GIT_COMMITTER_EMAIL: feast-ci-bot@willem.co - outputs: - release_version: ${{ steps.get_release_version.outputs.release_version }} - version_without_prefix: ${{ steps.get_release_version_without_prefix.outputs.version_without_prefix }} - highest_semver_tag: ${{ steps.get_highest_semver.outputs.highest_semver_tag }} - steps: - - uses: actions/checkout@v4 - - name: Get release version - id: get_release_version - run: | - if [[ -n "${{ github.event.inputs.custom_version }}" ]]; then - VERSION_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" - echo "Using custom version: ${{ github.event.inputs.custom_version }}" - if [[ ! "${{ github.event.inputs.custom_version }}" =~ $VERSION_REGEX ]]; then - echo "Error: custom_version must match semantic versioning (e.g., v1.2.3)." - exit 1 - fi - echo "::set-output name=release_version::${{ github.event.inputs.custom_version }}" - elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then - echo "Using tag reference: ${GITHUB_REF#refs/tags/}" - echo "::set-output name=release_version::${GITHUB_REF#refs/tags/}" - else - echo "Defaulting to branch name: ${GITHUB_REF#refs/heads/}" - echo "::set-output name=release_version::${GITHUB_REF#refs/heads/}" - fi - - name: Get release version without prefix - id: get_release_version_without_prefix - env: - RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }} - run: | - if [[ "${RELEASE_VERSION}" == v* ]]; then - echo "::set-output name=version_without_prefix::${RELEASE_VERSION:1}" - else - echo "::set-output name=version_without_prefix::${RELEASE_VERSION}" - fi - - name: Get highest semver - id: get_highest_semver - env: - RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }} - run: | - if [[ -n "${{ github.event.inputs.custom_version }}" ]]; then - HIGHEST_SEMVER_TAG="${{ github.event.inputs.custom_version }}" - echo "::set-output name=highest_semver_tag::$HIGHEST_SEMVER_TAG" - echo "Using custom version as highest semantic version: $HIGHEST_SEMVER_TAG" - else - source infra/scripts/setup-common-functions.sh - SEMVER_REGEX='^v[0-9]+\.[0-9]+\.[0-9]+(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$' - if echo "${RELEASE_VERSION}" | grep -P "$SEMVER_REGEX" &>/dev/null ; then - echo ::set-output name=highest_semver_tag::$(get_tag_release -m) - echo "Using infra/scripts/setup-common-functions.sh to generate highest semantic version: $HIGHEST_SEMVER_TAG" - fi - fi - - name: Check output - env: - RELEASE_VERSION: ${{ steps.get_release_version.outputs.release_version }} - VERSION_WITHOUT_PREFIX: ${{ steps.get_release_version_without_prefix.outputs.version_without_prefix }} - HIGHEST_SEMVER_TAG: ${{ steps.get_highest_semver.outputs.highest_semver_tag }} - run: | - echo $RELEASE_VERSION - echo $VERSION_WITHOUT_PREFIX - echo $HIGHEST_SEMVER_TAG \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index eb81ca193cd..85c7c0e9141 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,6 +9,18 @@ on: custom_version: # Optional input for a custom version description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' required: false + type: string + token: + description: 'Personal Access Token' + required: true + default: "" + type: string + workflow_call: # Allows trigger the workflow from other workflow + inputs: + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false + type: string token: description: 'Personal Access Token' required: true @@ -16,35 +28,33 @@ on: type: string jobs: - get-version: - uses: ./.github/workflows/get_semantic_release_version.yaml - with: - custom_version: ${{ github.event.inputs.custom_version }} - token: ${{ github.event.inputs.token }} - publish-python-sdk: - uses: ./.github/workflows/publish_python_sdk.yaml + uses: ./.github/workflows/publish_python_sdk.yml + secrets: inherit with: custom_version: ${{ github.event.inputs.custom_version }} token: ${{ github.event.inputs.token }} build-publish-docker-images: - uses: ./.github/workflows/publish_images.yaml - needs: [ get-version, publish-python-sdk ] + uses: ./.github/workflows/publish_images.yml + needs: [ publish-python-sdk ] + secrets: inherit with: custom_version: ${{ github.event.inputs.custom_version }} token: ${{ github.event.inputs.token }} publish-helm-charts: uses: ./.github/workflows/publish_helm_charts.yml - needs: [ get-version, publish-python-sdk ] + needs: [ publish-python-sdk ] + secrets: inherit with: custom_version: ${{ github.event.inputs.custom_version }} token: ${{ github.event.inputs.token }} publish-java-sdk: uses: ./.github/workflows/publish_java_sdk.yml - needs: [ get-version, publish-python-sdk ] + needs: [ publish-python-sdk ] + secrets: inherit with: custom_version: ${{ github.event.inputs.custom_version }} token: ${{ github.event.inputs.token }} diff --git a/.github/workflows/publish_helm_charts.yml b/.github/workflows/publish_helm_charts.yml index 060bdb05d4e..9d28e2efd2f 100644 --- a/.github/workflows/publish_helm_charts.yml +++ b/.github/workflows/publish_helm_charts.yml @@ -6,6 +6,18 @@ on: custom_version: # Optional input for a custom version description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' required: false + type: string + token: + description: 'Personal Access Token' + required: true + default: "" + type: string + workflow_call: # Allows trigger of the workflow from another workflow + inputs: + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false + type: string token: description: 'Personal Access Token' required: true @@ -13,21 +25,20 @@ on: type: string jobs: - get-version: - uses: ./.github/workflows/get_semantic_release_version.yaml - with: - custom_version: ${{ github.event.inputs.custom_version }} - token: ${{ github.event.inputs.token }} - publish-helm-charts: if: github.repository == 'feast-dev/feast' runs-on: ubuntu-latest - needs: get-version env: HELM_VERSION: v3.8.0 - VERSION_WITHOUT_PREFIX: ${{ needs.get-version.outputs.version_without_prefix }} steps: - uses: actions/checkout@v4 + with: + submodules: 'true' + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Authenticate to Google Cloud uses: 'google-github-actions/auth@v1' with: @@ -44,7 +55,11 @@ jobs: - name: Validate Helm chart prior to publishing run: ./infra/scripts/helm/validate-helm-chart-publish.sh - name: Validate all version consistency + env: + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} run: ./infra/scripts/helm/validate-helm-chart-versions.sh $VERSION_WITHOUT_PREFIX - name: Publish Helm charts + env: + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} run: ./infra/scripts/helm/push-helm-charts.sh $VERSION_WITHOUT_PREFIX diff --git a/.github/workflows/publish_images.yml b/.github/workflows/publish_images.yml index 26201aaa5c2..f605fb20df1 100644 --- a/.github/workflows/publish_images.yml +++ b/.github/workflows/publish_images.yml @@ -11,18 +11,22 @@ on: required: true default: "" type: string + workflow_call: # Allows trigger of the workflow from another workflow + inputs: + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false + type: string + token: + description: 'Personal Access Token' + required: true + default: "" + type: string jobs: - get-version: - uses: ./.github/workflows/get_semantic_release_version.yaml - with: - custom_version: ${{ github.event.inputs.custom_version }} - token: ${{ github.event.inputs.token }} - build-publish-docker-images: if: github.repository == 'feast-dev/feast' runs-on: ubuntu-latest - needs: [ get-version ] strategy: matrix: component: [ feature-server, feature-server-java, feature-transformation-server, feast-helm-operator, feast-operator ] @@ -31,6 +35,13 @@ jobs: REGISTRY: feastdev steps: - uses: actions/checkout@v4 + with: + submodules: 'true' + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx @@ -52,17 +63,14 @@ jobs: run: gcloud info - run: gcloud auth configure-docker --quiet - name: Build image + env: + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} run: | make build-${{ matrix.component }}-docker REGISTRY=${REGISTRY} VERSION=${VERSION_WITHOUT_PREFIX} - env: - RELEASE_VERSION: ${{ needs.get-version.outputs.release_version }} - VERSION_WITHOUT_PREFIX: ${{ needs.get-version.outputs.version_without_prefix }} - HIGHEST_SEMVER_TAG: ${{ needs.get-version.outputs.highest_semver_tag }} - name: Push versioned images env: - RELEASE_VERSION: ${{ needs.get-version.outputs.release_version }} - VERSION_WITHOUT_PREFIX: ${{ needs.get-version.outputs.version_without_prefix }} - HIGHEST_SEMVER_TAG: ${{ needs.get-version.outputs.highest_semver_tag }} + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} + HIGHEST_SEMVER_TAG: ${{ steps.get-version.outputs.highest_semver_tag }} run: | make push-${{ matrix.component }}-docker REGISTRY=${REGISTRY} VERSION=${VERSION_WITHOUT_PREFIX} @@ -72,4 +80,3 @@ jobs: docker tag feastdev/${{ matrix.component }}:${VERSION_WITHOUT_PREFIX} feastdev/${{ matrix.component }}:latest docker push feastdev/${{ matrix.component }}:latest fi - diff --git a/.github/workflows/publish_java_sdk.yml b/.github/workflows/publish_java_sdk.yml index c158010995d..f89c384b126 100644 --- a/.github/workflows/publish_java_sdk.yml +++ b/.github/workflows/publish_java_sdk.yml @@ -6,28 +6,39 @@ on: custom_version: # Optional input for a custom version description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' required: false + type: string + token: + description: 'Personal Access Token' + required: true + default: "" + type: string + workflow_call: # Allows trigger of the workflow from another workflow + inputs: + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false + type: string token: description: 'Personal Access Token' required: true default: "" type: string -jobs: - get-version: - uses: ./.github/workflows/get_semantic_release_version.yaml - with: - custom_version: ${{ github.event.inputs.custom_version }} - token: ${{ github.event.inputs.token }} +jobs: publish-java-sdk: if: github.repository == 'feast-dev/feast' container: maven:3.6-jdk-11 runs-on: ubuntu-latest - needs: [ get-version, publish-python-sdk ] steps: - uses: actions/checkout@v4 with: submodules: 'true' + - id: get-version + uses: ./.github/actions/get-semantic-release-version + with: + custom_version: ${{ github.event.inputs.custom_version }} + token: ${{ github.event.inputs.token }} - name: Set up JDK 11 uses: actions/setup-java@v1 with: @@ -46,7 +57,7 @@ jobs: ${{ runner.os }}-it-maven- - name: Publish java sdk env: - VERSION_WITHOUT_PREFIX: ${{ needs.get-version.outputs.version_without_prefix }} + VERSION_WITHOUT_PREFIX: ${{ steps.get-version.outputs.version_without_prefix }} GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} MAVEN_SETTINGS: ${{ secrets.MAVEN_SETTINGS }} diff --git a/.github/workflows/publish_python_sdk.yml b/.github/workflows/publish_python_sdk.yml index 1a9c111de74..03d0e989b49 100644 --- a/.github/workflows/publish_python_sdk.yml +++ b/.github/workflows/publish_python_sdk.yml @@ -6,6 +6,19 @@ on: custom_version: # Optional input for a custom version description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' required: false + type: string + token: + description: 'Personal Access Token' + required: true + default: "" + type: string + + workflow_call: # Allows trigger of the workflow from another workflow + inputs: + custom_version: # Optional input for a custom version + description: 'Custom version to publish (e.g., v1.2.3) -- only edit if you know what you are doing' + required: false + type: string token: description: 'Personal Access Token' required: true @@ -13,29 +26,24 @@ on: type: string jobs: - get-version: - uses: ./.github/workflows/get_semantic_release_version.yaml + build-wheels: + uses: ./.github/workflows/build_wheels.yml + secrets: inherit with: custom_version: ${{ github.event.inputs.custom_version }} token: ${{ github.event.inputs.token }} - build_wheels: - uses: ./.github/workflows/build_wheels.yml - needs: get-version - with: - release_version: ${{ needs.get-version.outputs.release_version }} - highest_semver_tag: ${{ needs.get-version.outputs.highest_semver_tag }} - publish-python-sdk: if: github.repository == 'feast-dev/feast' runs-on: ubuntu-latest - needs: [ get-version, build_wheels ] + needs: [ build-wheels ] steps: - uses: actions/download-artifact@v4.1.7 with: name: python-wheels path: dist - - uses: pypa/gh-action-pypi-publish@v1.4.2 + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@v1.4.2 with: user: __token__ - password: ${{ secrets.PYPI_PASSWORD }} + password: ${{ secrets.PYPI_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00f65929265..acefac72506 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,23 @@ on: required: true default: true type: boolean + workflow_call: + inputs: + dry_run: + description: 'Dry Run' + required: true + default: true + type: boolean + token: + description: 'Personal Access Token' + required: true + default: "" + type: string + publish_ui: + description: 'Publish to NPM?' + required: true + default: true + type: boolean jobs: get_dry_release_versions: @@ -93,9 +110,7 @@ jobs: with: go-version: 1.22.9 - name: Build & version operator-specific release files - run: | - cd infra/feast-operator/ - make build-installer bundle + run: make -C infra/feast-operator build-installer bundle publish-web-ui-npm: needs: [ validate_version_bumps, get_dry_release_versions ] @@ -152,6 +167,10 @@ jobs: - name: Setup Helm-docs run: | brew install norwoodj/tap/helm-docs + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.22.9 - name: Release (Dry Run) if: github.event.inputs.dry_run == 'true' run: | @@ -160,3 +179,11 @@ jobs: if: github.event.inputs.dry_run == 'false' run: | npx -p @semantic-release/changelog -p @semantic-release/git -p @semantic-release/exec -p semantic-release semantic-release + + - name: Updating `stable` branch after release. + run: | + git fetch origin + # note that this checkout creates a branch called `stable` if it does not exist + git checkout -B stable + git reset --hard origin/${GITHUB_REF##*/} + git push -f origin stable \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6823221aed4..e33fb46cb07 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,6 @@ ui/.vercel # Go subprocess binaries (built during feast pip package building) sdk/python/feast/binaries/ + +# ignore the bin directory under feast operator. +infra/feast-operator/bin \ No newline at end of file diff --git a/.releaserc.js b/.releaserc.js index f2be2440057..61c6813442d 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -41,7 +41,7 @@ module.exports = { "verifyReleaseCmd": "./infra/scripts/validate-release.sh ${nextRelease.type} " + current_branch, // Bump all version files and build UI / update yarn.lock / helm charts - "prepareCmd": "python ./infra/scripts/release/bump_file_versions.py ${lastRelease.version} ${nextRelease.version}; make build-ui; make build-helm-docs" + "prepareCmd": "python ./infra/scripts/release/bump_file_versions.py ${lastRelease.version} ${nextRelease.version}; make build-ui; make build-helm-docs; make -C infra/feast-operator build-installer bundle; rm -rf infra/feast-operator/bin" }], ["@semantic-release/release-notes-generator", { diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b7ea5edf1..47cd1b1d2d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +# [0.44.0](https://github.com/feast-dev/feast/compare/v0.43.0...v0.44.0) (2025-02-04) + + +### Bug Fixes + +* Adding periodic check to fix the sporadic failures of the operator e2e tests. ([#4952](https://github.com/feast-dev/feast/issues/4952)) ([1d086be](https://github.com/feast-dev/feast/commit/1d086beb9f9726f68ababace87c58c2cc6412ca3)) +* Adding the feast-operator/bin to the .gitignore directory. Somehow it… ([#5005](https://github.com/feast-dev/feast/issues/5005)) ([1a027ee](https://github.com/feast-dev/feast/commit/1a027eec3dc38ce8a949aca842c91742b0f68b47)) +* Changed Env Vars for e2e tests ([#4975](https://github.com/feast-dev/feast/issues/4975)) ([fa0084f](https://github.com/feast-dev/feast/commit/fa0084f2ed0e9d41ff813538ee63dd4ee7371e6c)) +* Fix GitHub Actions to pass authentication ([#4963](https://github.com/feast-dev/feast/issues/4963)) ([22b9138](https://github.com/feast-dev/feast/commit/22b9138a3c0040f5779f7218522f2d96e750fbbf)), closes [#4937](https://github.com/feast-dev/feast/issues/4937) [#4939](https://github.com/feast-dev/feast/issues/4939) [#4941](https://github.com/feast-dev/feast/issues/4941) [#4940](https://github.com/feast-dev/feast/issues/4940) [#4943](https://github.com/feast-dev/feast/issues/4943) [#4944](https://github.com/feast-dev/feast/issues/4944) [#4945](https://github.com/feast-dev/feast/issues/4945) [#4946](https://github.com/feast-dev/feast/issues/4946) [#4947](https://github.com/feast-dev/feast/issues/4947) [#4948](https://github.com/feast-dev/feast/issues/4948) [#4951](https://github.com/feast-dev/feast/issues/4951) [#4954](https://github.com/feast-dev/feast/issues/4954) [#4957](https://github.com/feast-dev/feast/issues/4957) [#4958](https://github.com/feast-dev/feast/issues/4958) [#4959](https://github.com/feast-dev/feast/issues/4959) [#4960](https://github.com/feast-dev/feast/issues/4960) [#4962](https://github.com/feast-dev/feast/issues/4962) +* Fix showing selected navigation item in UI sidebar ([#4969](https://github.com/feast-dev/feast/issues/4969)) ([8ac6a85](https://github.com/feast-dev/feast/commit/8ac6a8547361708fec00a11a33c48ca3ae25f311)) +* Invalid column names in get_historical_features when there are field mappings on join keys ([#4886](https://github.com/feast-dev/feast/issues/4886)) ([c9aca2d](https://github.com/feast-dev/feast/commit/c9aca2d42254d1c4dfcc778b0d90303329901bd0)) +* Read project data from the 'projects' key while loading the registry state in the Feast UI ([#4772](https://github.com/feast-dev/feast/issues/4772)) ([cb81939](https://github.com/feast-dev/feast/commit/cb8193945932b98d5b8f750ac07d58c034870565)) +* Remove grpcurl dependency from Operator ([#4972](https://github.com/feast-dev/feast/issues/4972)) ([439e0b9](https://github.com/feast-dev/feast/commit/439e0b98819ef222b35617dfd6c97f04ca049f2f)) +* Removed the dry-run flag to test and we will add it back later. ([#5007](https://github.com/feast-dev/feast/issues/5007)) ([d112b52](https://github.com/feast-dev/feast/commit/d112b529d618f19a5602039b6d347915d7e75b88)) +* Render UI navigation items as links instead of buttons ([#4970](https://github.com/feast-dev/feast/issues/4970)) ([1267703](https://github.com/feast-dev/feast/commit/1267703d099491393ca212c38f1a63a36fe6c443)) +* Resolve Operator CRD bloat due to long field descriptions ([#4985](https://github.com/feast-dev/feast/issues/4985)) ([7593bb3](https://github.com/feast-dev/feast/commit/7593bb3ec8871dbb83403461e0b6f6863d64abc6)) +* Update manifest to add feature server image for odh ([#4973](https://github.com/feast-dev/feast/issues/4973)) ([6a1c102](https://github.com/feast-dev/feast/commit/6a1c1029b5462aaa42c82fdad421176ad1692f81)) +* Updating release workflows to refer to yml instead of yaml ([#4935](https://github.com/feast-dev/feast/issues/4935)) ([02b0a68](https://github.com/feast-dev/feast/commit/02b0a68a435ab01f26b20824f3f8a4dd4e21da8d)) +* Use locally built feast-ui package in dev feature-server image ([#4998](https://github.com/feast-dev/feast/issues/4998)) ([0145e55](https://github.com/feast-dev/feast/commit/0145e5501e2c7854628d204cb515270fac3bee7d)) + + +### Features + +* Added OWNERS file for OpenshiftCI ([#4991](https://github.com/feast-dev/feast/issues/4991)) ([86a2ee8](https://github.com/feast-dev/feast/commit/86a2ee8e3ce1cd4432749928fda7a4386dc7ce0f)) +* Adding Milvus demo to examples ([#4910](https://github.com/feast-dev/feast/issues/4910)) ([2daf852](https://github.com/feast-dev/feast/commit/2daf8527c4539a007d639ac6e3061767a9c45110)) +* Adding retrieve_online_documents endpoint ([#5002](https://github.com/feast-dev/feast/issues/5002)) ([6607d3d](https://github.com/feast-dev/feast/commit/6607d3dfa1041638d3896b25cb98677412889724)) +* Adding support to return additional features from vector retrieval for Milvus db ([#4971](https://github.com/feast-dev/feast/issues/4971)) ([6ce08d3](https://github.com/feast-dev/feast/commit/6ce08d31863b12a7a92bf5207172a05f8da077d1)) +* Creating/updating the stable branch after the release. ([#5003](https://github.com/feast-dev/feast/issues/5003)) ([e9b53cc](https://github.com/feast-dev/feast/commit/e9b53cc83ee51b906423ec2e1fac36e159d55db2)) +* Implementing online_read for MilvusOnlineStore ([#4996](https://github.com/feast-dev/feast/issues/4996)) ([92dde13](https://github.com/feast-dev/feast/commit/92dde1311c419dc3d8cbb534ed2e706fdeae1e26)) +* Improve exception message for unsupported Snowflake data types ([#4779](https://github.com/feast-dev/feast/issues/4779)) ([5992364](https://github.com/feast-dev/feast/commit/59923645e4f6a64a49bcecb7da503528af850d0f)) +* Operator add feast ui deployment ([#4930](https://github.com/feast-dev/feast/issues/4930)) ([b026d0c](https://github.com/feast-dev/feast/commit/b026d0ce30d7ce9b621679fbb33f2a9c0edaad84)) +* Updating documents to highlight v2 api for Vector Similarity Se… ([#5000](https://github.com/feast-dev/feast/issues/5000)) ([32b82a4](https://github.com/feast-dev/feast/commit/32b82a4b59bceaf9eb6662f35e77d0cae0d36550)) + # [0.43.0](https://github.com/feast-dev/feast/compare/v0.42.0...v0.43.0) (2025-01-20) diff --git a/OWNERS b/OWNERS new file mode 100644 index 00000000000..852b3fdf8c6 --- /dev/null +++ b/OWNERS @@ -0,0 +1,15 @@ +# This file is being used by RedHat for running e2e CI + +approvers: +- redhathameed +- tmihalac +- accorvin +- amsharma3 +- franciscojavierarceo +options: {} +reviewers: +- redhathameed +- tmihalac +- accorvin +- amsharma3 +- franciscojavierarceo \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index e24e15fb5cb..bbda7773b45 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -116,6 +116,7 @@ * [Hazelcast](reference/online-stores/hazelcast.md) * [ScyllaDB](reference/online-stores/scylladb.md) * [SingleStore](reference/online-stores/singlestore.md) + * [Milvus](reference/online-stores/milvus.md) * [Registries](reference/registries/README.md) * [Local](reference/registries/local.md) * [S3](reference/registries/s3.md) diff --git a/docs/reference/alpha-vector-database.md b/docs/reference/alpha-vector-database.md index ae6b47f0422..216289f1949 100644 --- a/docs/reference/alpha-vector-database.md +++ b/docs/reference/alpha-vector-database.md @@ -7,20 +7,35 @@ Vector database allows user to store and retrieve embeddings. Feast provides gen ## Integration Below are supported vector databases and implemented features: -| Vector Database | Retrieval | Indexing | -|-----------------|-----------|----------| -| Pgvector | [x] | [ ] | -| Elasticsearch | [x] | [x] | -| Milvus | [ ] | [ ] | -| Faiss | [ ] | [ ] | -| SQLite | [x] | [ ] | -| Qdrant | [x] | [x] | +| Vector Database | Retrieval | Indexing | V2 Support* | +|-----------------|-----------|----------|-------------| +| Pgvector | [x] | [ ] | [] | +| Elasticsearch | [x] | [x] | [] | +| Milvus | [x] | [x] | [x] | +| Faiss | [ ] | [ ] | [] | +| SQLite | [x] | [ ] | [] | +| Qdrant | [x] | [x] | [] | + +*Note: V2 Support means the SDK supports retrieval of features along with vector embeddings from vector similarity search. Note: SQLite is in limited access and only working on Python 3.10. It will be updated as [sqlite_vec](https://github.com/asg017/sqlite-vec/) progresses. -## Example +{% hint style="danger" %} +We will be deprecating the `retrieve_online_documents` method in the SDK in the future. +We recommend using the `retrieve_online_documents_v2` method instead, which offers easier vector index configuration +directly in the Feature View and the ability to retrieve standard features alongside your vector embeddings for richer context injection. + +Long term we will collapse the two methods into one, but for now, we recommend using the `retrieve_online_documents_v2` method. +Beyond that, we will then have `retrieve_online_documents` and `retrieve_online_documents_v2` simply point to `get_online_features` for +backwards compatibility and the adopt industry standard naming conventions. +{% endhint %} + +**Note**: Milvus implements the v2 `retrieve_online_documents_v2` method in the SDK. This will be the longer-term solution so that Data Scientists can easily enable vector similarity search by just flipping a flag. -See [https://github.com/feast-dev/feast-workshop/blob/rag/module_4_rag](https://github.com/feast-dev/feast-workshop/blob/rag/module_4_rag) for an example on how to use vector database. +## Examples + +- See the v0 [Rag Demo](https://github.com/feast-dev/feast-workshop/blob/rag/module_4_rag) for an example on how to use vector database using the `retrieve_online_documents` method (planning migration and deprecation (planning migration and deprecation). +- See the v1 [Milvus Quickstart](../../examples/rag/milvus-quickstart.ipynb) for a quickstart guide on how to use Feast with Milvus using the `retrieve_online_documents_v2` method. ### **Prepare offline embedding dataset** Run the following commands to prepare the embedding dataset: @@ -34,25 +49,23 @@ The output will be stored in `data/city_wikipedia_summaries.csv.` Use the feature_store.yaml file to initialize the feature store. This will use the data as offline store, and Pgvector as online store. ```yaml -project: feast_demo_local +project: local_rag provider: local -registry: - registry_type: sql - path: postgresql://@localhost:5432/feast +registry: data/registry.db online_store: - type: postgres + type: milvus + path: data/online_store.db vector_enabled: true - vector_len: 384 - host: 127.0.0.1 - port: 5432 - database: feast - user: "" - password: "" + embedding_dim: 384 + index_type: "IVF_FLAT" offline_store: type: file -entity_key_serialization_version: 2 +entity_key_serialization_version: 3 +# By default, no_auth for authentication and authorization, other possible values kubernetes and oidc. Refer the documentation for more details. +auth: + type: no_auth ``` Run the following command in terminal to apply the feature store configuration: @@ -63,75 +76,128 @@ feast apply Note that when you run `feast apply` you are going to apply the following Feature View that we will use for retrieval later: ```python -city_embeddings_feature_view = FeatureView( - name="city_embeddings", - entities=[item], +document_embeddings = FeatureView( + name="embedded_documents", + entities=[item, author], schema=[ - Field(name="Embeddings", dtype=Array(Float32)), + Field( + name="vector", + dtype=Array(Float32), + # Look how easy it is to enable RAG! + vector_index=True, + vector_search_metric="COSINE", + ), + Field(name="item_id", dtype=Int64), + Field(name="author_id", dtype=String), + Field(name="created_timestamp", dtype=UnixTimestamp), + Field(name="sentence_chunks", dtype=String), + Field(name="event_timestamp", dtype=UnixTimestamp), ], - source=source, - ttl=timedelta(hours=2), + source=rag_documents_source, + ttl=timedelta(hours=24), ) ``` -Then run the following command in the terminal to materialize the data to the online store: - -```shell -CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S") -feast materialize-incremental $CURRENT_TIME +Let's use the SDK to write a data frame of embeddings to the online store: +```python +store.write_to_online_store(feature_view_name='city_embeddings', df=df) ``` ### **Prepare a query embedding** +During inference (e.g., during when a user submits a chat message) we need to embed the input text. This can be thought of as a feature transformation of the input data. In this example, we'll do this with a small Sentence Transformer from Hugging Face. + ```python -from batch_score_documents import run_model, TOKENIZER, MODEL +import torch +import torch.nn.functional as F +from feast import FeatureStore +from pymilvus import MilvusClient, DataType, FieldSchema from transformers import AutoTokenizer, AutoModel - -question = "the most populous city in the U.S. state of Texas?" +from example_repo import city_embeddings_feature_view, item + +TOKENIZER = "sentence-transformers/all-MiniLM-L6-v2" +MODEL = "sentence-transformers/all-MiniLM-L6-v2" + +def mean_pooling(model_output, attention_mask): + token_embeddings = model_output[ + 0 + ] # First element of model_output contains all token embeddings + input_mask_expanded = ( + attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() + ) + return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp( + input_mask_expanded.sum(1), min=1e-9 + ) + +def run_model(sentences, tokenizer, model): + encoded_input = tokenizer( + sentences, padding=True, truncation=True, return_tensors="pt" + ) + # Compute token embeddings + with torch.no_grad(): + model_output = model(**encoded_input) + + sentence_embeddings = mean_pooling(model_output, encoded_input["attention_mask"]) + sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1) + return sentence_embeddings + +question = "Which city has the largest population in New York?" tokenizer = AutoTokenizer.from_pretrained(TOKENIZER) model = AutoModel.from_pretrained(MODEL) -query_embedding = run_model(question, tokenizer, model) -query = query_embedding.detach().cpu().numpy().tolist()[0] +query_embedding = run_model(question, tokenizer, model).detach().cpu().numpy().tolist()[0] ``` -### **Retrieve the top 5 similar documents** -First create a feature store instance, and use the `retrieve_online_documents` API to retrieve the top 5 similar documents to the specified query. +### **Retrieve the top K similar documents** +First create a feature store instance, and use the `retrieve_online_documents_v2` API to retrieve the top 5 similar documents to the specified query. ```python -from feast import FeatureStore -store = FeatureStore(repo_path=".") -features = store.retrieve_online_documents( - feature="city_embeddings:Embeddings", - query=query, - top_k=5 -).to_dict() - -def print_online_features(features): - for key, value in sorted(features.items()): - print(key, " : ", value) - -print_online_features(features) +context_data = store.retrieve_online_documents_v2( + features=[ + "city_embeddings:vector", + "city_embeddings:item_id", + "city_embeddings:state", + "city_embeddings:sentence_chunks", + "city_embeddings:wiki_summary", + ], + query=query_embedding, + top_k=3, + distance_metric='COSINE', +).to_df() ``` +### **Generate the Response** +Let's assume we have a base prompt and a function that formats the retrieved documents called `format_documents` that we +can then use to generate the response with OpenAI's chat completion API. +```python +FULL_PROMPT = format_documents(rag_context_data, BASE_PROMPT) -### Configuration +from openai import OpenAI -We offer [PGVector](https://github.com/pgvector/pgvector), [SQLite](https://github.com/asg017/sqlite-vec), [Elasticsearch](https://www.elastic.co) and [Qdrant](https://qdrant.tech/) as Online Store options for Vector Databases. - -#### Installation with SQLite +client = OpenAI( + api_key=os.environ.get("OPENAI_API_KEY"), +) +response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", "content": FULL_PROMPT}, + {"role": "user", "content": question} + ], +) -If you are using `pyenv` to manage your Python versions, you can install the SQLite extension with the following command: -```bash -PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" \ - LDFLAGS="-L/opt/homebrew/opt/sqlite/lib" \ - CPPFLAGS="-I/opt/homebrew/opt/sqlite/include" \ - pyenv install 3.10.14 +# And this will print the content. Look at the examples/rag/milvus-quickstart.ipynb for an end-to-end example. +print('\n'.join([c.message.content for c in response.choices])) ``` -And you can the Feast install package via: + +### Configuration and Installation + +We offer [Milvus](https://milvus.io/), [PGVector](https://github.com/pgvector/pgvector), [SQLite](https://github.com/asg017/sqlite-vec), [Elasticsearch](https://www.elastic.co) and [Qdrant](https://qdrant.tech/) as Online Store options for Vector Databases. + +Milvus offers a convenient local implementation for vector similarity search. To use Milvus, you can install the Feast package with the Milvus extra. + +#### Installation with Milvus ```bash -pip install feast[sqlite_vec] +pip install feast[milvus] ``` - #### Installation with Elasticsearch ```bash @@ -143,3 +209,17 @@ pip install feast[elasticsearch] ```bash pip install feast[qdrant] ``` +#### Installation with SQLite + +If you are using `pyenv` to manage your Python versions, you can install the SQLite extension with the following command: +```bash +PYTHON_CONFIGURE_OPTS="--enable-loadable-sqlite-extensions" \ + LDFLAGS="-L/opt/homebrew/opt/sqlite/lib" \ + CPPFLAGS="-I/opt/homebrew/opt/sqlite/include" \ + pyenv install 3.10.14 +``` + +And you can the Feast install package via: +```bash +pip install feast[sqlite_vec] +``` diff --git a/docs/reference/feature-servers/python-feature-server.md b/docs/reference/feature-servers/python-feature-server.md index d7374852495..348bfdcd3d8 100644 --- a/docs/reference/feature-servers/python-feature-server.md +++ b/docs/reference/feature-servers/python-feature-server.md @@ -226,13 +226,14 @@ feast serve --key /path/to/key.pem --cert /path/to/cert.pem ## API Endpoints and Permissions -| Endpoint | Resource Type | Permission | Description | -| ---------------------------- |---------------------------------|-------------------------------------------------------| ------------------------------------------------------------------------ | -| /get-online-features | FeatureView,OnDemandFeatureView | Read Online | Get online features from the feature store | -| /push | FeatureView | Write Online, Write Offline, Write Online and Offline | Push features to the feature store (online, offline, or both) | -| /write-to-online-store | FeatureView | Write Online | Write features to the online store | -| /materialize | FeatureView | Write Online | Materialize features within a specified time range | -| /materialize-incremental | FeatureView | Write Online | Incrementally materialize features up to a specified timestamp | +| Endpoint | Resource Type | Permission | Description | +|----------------------------|---------------------------------|-------------------------------------------------------|----------------------------------------------------------------| +| /get-online-features | FeatureView,OnDemandFeatureView | Read Online | Get online features from the feature store | +| /retrieve-online-documents | FeatureView | Read Online | Retrieve online documents from the feature store for RAG | +| /push | FeatureView | Write Online, Write Offline, Write Online and Offline | Push features to the feature store (online, offline, or both) | +| /write-to-online-store | FeatureView | Write Online | Write features to the online store | +| /materialize | FeatureView | Write Online | Materialize features within a specified time range | +| /materialize-incremental | FeatureView | Write Online | Incrementally materialize features up to a specified timestamp | ## How to configure Authentication and Authorization ? diff --git a/docs/reference/online-stores/milvus.md b/docs/reference/online-stores/milvus.md new file mode 100644 index 00000000000..014c7bd68a5 --- /dev/null +++ b/docs/reference/online-stores/milvus.md @@ -0,0 +1,65 @@ +# Redis online store + +## Description + +The [Milvus](https://milvus.io/) online store provides support for materializing feature values into Milvus. + +* The data model used to store feature values in Milvus is described in more detail [here](../../specs/online\_store\_format.md). + +## Getting started +In order to use this online store, you'll need to install the Milvus extra (along with the dependency needed for the offline store of choice). E.g. + +`pip install 'feast[milvus]'` + +You can get started by using any of the other templates (e.g. `feast init -t gcp` or `feast init -t snowflake` or `feast init -t aws`), and then swapping in Redis as the online store as seen below in the examples. + +## Examples + +Connecting to a local MilvusDB instance: + +{% code title="feature_store.yaml" %} +```yaml +project: my_feature_repo +registry: data/registry.db +provider: local +online_store: + type: milvus + path: "data/online_store.db" + connection_string: "localhost:6379" + embedding_dim: 128 + index_type: "FLAT" + metric_type: "COSINE" + username: "username" + password: "password" +``` +{% endcode %} + + +The full set of configuration options is available in [MilvusOnlineStoreConfig](https://rtd.feast.dev/en/latest/#feast.infra.online_stores.milvus.MilvusOnlineStoreConfig). + +## Functionality Matrix + +The set of functionality supported by online stores is described in detail [here](overview.md#functionality). +Below is a matrix indicating which functionality is supported by the Milvus online store. + +| | Milvus | +|:----------------------------------------------------------|:-------| +| write feature values to the online store | yes | +| read feature values from the online store | yes | +| update infrastructure (e.g. tables) in the online store | yes | +| teardown infrastructure (e.g. tables) in the online store | yes | +| generate a plan of infrastructure changes | no | +| support for on-demand transforms | yes | +| readable by Python SDK | yes | +| readable by Java | no | +| readable by Go | no | +| support for entityless feature views | yes | +| support for concurrent writing to the same key | yes | +| support for ttl (time to live) at retrieval | yes | +| support for deleting expired data | yes | +| collocated by feature view | no | +| collocated by feature service | no | +| collocated by entity key | no | +| vector similarity search | yes | + +To compare this set of functionality against other online stores, please see the full [functionality matrix](overview.md#functionality-matrix). diff --git a/docs/reference/online-stores/overview.md b/docs/reference/online-stores/overview.md index 04d24447058..b54329ad613 100644 --- a/docs/reference/online-stores/overview.md +++ b/docs/reference/online-stores/overview.md @@ -34,21 +34,21 @@ Details for each specific online store, such as how to configure it in a `featur Below is a matrix indicating which online stores support what functionality. -| | Sqlite | Redis | DynamoDB | Snowflake | Datastore | Postgres | Hbase | [[Cassandra](https://cassandra.apache.org/_/index.html) / [Astra DB](https://www.datastax.com/products/datastax-astra?utm_source=feast)] | [IKV](https://inlined.io) | -| :-------------------------------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | -| write feature values to the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| read feature values from the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| update infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| teardown infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| generate a plan of infrastructure changes | yes | no | no | no | no | no | no | yes | no | -| support for on-demand transforms | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| readable by Python SDK | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| readable by Java | no | yes | no | no | no | no | no | no | no | -| readable by Go | yes | yes | no | no | no | no | no | no | no | -| support for entityless feature views | yes | yes | yes | yes | yes | yes | yes | yes | yes | -| support for concurrent writing to the same key | no | yes | no | no | no | no | no | no | yes | -| support for ttl (time to live) at retrieval | no | yes | no | no | no | no | no | no | no | -| support for deleting expired data | no | yes | no | no | no | no | no | no | no | -| collocated by feature view | yes | no | yes | yes | yes | yes | yes | yes | no | -| collocated by feature service | no | no | no | no | no | no | no | no | no | -| collocated by entity key | no | yes | no | no | no | no | no | no | yes | +| | Sqlite | Redis | DynamoDB | Snowflake | Datastore | Postgres | Hbase | [[Cassandra](https://cassandra.apache.org/_/index.html) / [Astra DB](https://www.datastax.com/products/datastax-astra?utm_source=feast)] | [IKV](https://inlined.io) | Milvus | +| :-------------------------------------------------------- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- |:-------| +| write feature values to the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| read feature values from the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| update infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| teardown infrastructure (e.g. tables) in the online store | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| generate a plan of infrastructure changes | yes | no | no | no | no | no | no | yes | no | no | +| support for on-demand transforms | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| readable by Python SDK | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes | +| readable by Java | no | yes | no | no | no | no | no | no | no | no | +| readable by Go | yes | yes | no | no | no | no | no | no | no | no | +| support for entityless feature views | yes | yes | yes | yes | yes | yes | yes | yes | yes | no | +| support for concurrent writing to the same key | no | yes | no | no | no | no | no | no | yes | no | +| support for ttl (time to live) at retrieval | no | yes | no | no | no | no | no | no | no | no | +| support for deleting expired data | no | yes | no | no | no | no | no | no | no | no | +| collocated by feature view | yes | no | yes | yes | yes | yes | yes | yes | no | no | +| collocated by feature service | no | no | no | no | no | no | no | no | no | no | +| collocated by entity key | no | yes | no | no | no | no | no | no | yes | no | diff --git a/examples/README.md b/examples/README.md index e796b5000e3..f968b94b5f6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,18 +1,23 @@ # Feast Examples -1. **[Quickstart Example](https://github.com/feast-dev/feast/tree/master/examples/quickstart)**: This is a step-by-step guide for getting started with Feast. +1. **[Quickstart Example](quickstart)**: This is a step-by-step guide for getting started with Feast. -2. **[Java Demo](https://github.com/feast-dev/feast/tree/master/examples/java-demo)**: Demonstrates how to use Feast with Java feature server and deployed with Kubernetes. +2. **[Java Demo](java-demo)**: Demonstrates how to use Feast with Java feature server and deployed with Kubernetes. -3. **[Python Helm Demo](https://github.com/feast-dev/feast/tree/master/examples/python-helm-demo)**: Demonstrates Feast with Kubernetes using Helm charts and Python feature server. +3. **[Kind Quickstart](kind-quickstart)**: Demonstrates how to install and use Feast on Kind with the Helm chart. -4. **[RBAC Local](https://github.com/feast-dev/feast/tree/master/examples/rbac-local)**: Demonstrates using notebooks how configure and test Role-Based Access Control (RBAC) for securing access in Feast using OIDC authorization type with in a local environment. +4. **[Operator Quickstart](operator-quickstart)**: Demonstrates how to install and use Feast on Kubernetes with the Feast Go Operator. -5. **[RBAC Remote](https://github.com/feast-dev/feast/tree/master/examples/rbac-local)**: Demonstrates how to configure and test Role-Based Access Control (RBAC) for securing access in Feast using Kubernetes or OIDC Authentication type with in Kubernetes environment. +5. **[Credit Risk End-to-End](credit-risk-end-to-end)**: Demonstrates how to use Feast with Java feature server and deployed with Kubernetes. -6. **[Remote Offline Store](https://github.com/feast-dev/feast/tree/master/examples/remote-offline-store)**: Demonstrates how to set up and use remote offline server. +6. **[Python Helm Demo](python-helm-demo)**: Demonstrates Feast with Kubernetes using Helm charts and Python feature server. -7. **[Podman/Podman Compose_local](https://github.com/feast-dev/feast/tree/master/examples/podman_local)**: Demonstrates how to deploy Feast remote server components using Podman Compose locally. +7. **[RBAC Local](rbac-local)**: Demonstrates using notebooks how configure and test Role-Based Access Control (RBAC) for securing access in Feast using OIDC authorization type with in a local environment. -8. **[RHOAI Feast Demo](https://github.com/feast-dev/feast/tree/master/examples/rhoai-quickstart)**: Showcases Feast's core functionality using a Jupyter notebook, including fetching online feature data from a remote server and retrieving metadata from a remote registry. +8. **[RBAC Remote](rbac-remote)**: Demonstrates how to configure and test Role-Based Access Control (RBAC) for securing access in Feast using Kubernetes or OIDC Authentication type with in Kubernetes environment. +9. **[Remote Offline Store](remote-offline-store)**: Demonstrates how to set up and use remote offline server. + +10. **[Podman/Podman Compose_local](podman_local)**: Demonstrates how to deploy Feast remote server components using Podman Compose locally. + +11. **[RHOAI Feast Demo](rhoai-quickstart)**: Showcases Feast's core functionality using a Jupyter notebook, including fetching online feature data from a remote server and retrieving metadata from a remote registry. diff --git a/examples/operator-quickstart/.gitignore b/examples/operator-quickstart/.gitignore new file mode 100644 index 00000000000..335ec9573de --- /dev/null +++ b/examples/operator-quickstart/.gitignore @@ -0,0 +1 @@ +*.tar.gz diff --git a/examples/operator-quickstart/01-Install.ipynb b/examples/operator-quickstart/01-Install.ipynb new file mode 100644 index 00000000000..d703357f791 --- /dev/null +++ b/examples/operator-quickstart/01-Install.ipynb @@ -0,0 +1,489 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Install Feast on Kubernetes with the Feast Operator\n", + "## Objective\n", + "\n", + "Provide a reference implementation of a runbook to deploy a Feast environment on a Kubernetes cluster using [Kind](https://kind.sigs.k8s.io/docs/user/quick-start) and the [Feast Operator](../../infra/feast-operator/)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "* Kubernetes Cluster\n", + "* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) Kubernetes CLI tool." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install Prerequisites\n", + "\n", + "The following commands install and configure all the prerequisites on a MacOS environment. You can find the\n", + "equivalent instructions on the offical documentation pages:\n", + "* Install the `kubectl` cli.\n", + "* Install Kubernetes and Container runtime (e.g. [Colima](https://github.com/abiosoft/colima)).\n", + " * Alternatively, authenticate to an existing Kubernetes or OpenShift cluster.\n", + " \n", + "```bash\n", + "brew install colima kubectl\n", + "colima start -r containerd -k -m 3 -d 100 -c 2 --cpu-type max -a x86_64\n", + "colima list\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace/feast created\n", + "Context \"colima\" modified.\n" + ] + } + ], + "source": [ + "!kubectl create ns feast\n", + "!kubectl config set-context --current --namespace feast" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Validate the cluster setup:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME STATUS AGE\n", + "feast Active 3s\n" + ] + } + ], + "source": [ + "!kubectl get ns feast" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Deployment Architecture\n", + "The primary objective of this runbook is to guide the deployment of Feast services on a Kubernetes Kind cluster, using the `postgres` template to set up a basic feature store.\n", + "\n", + "In this notebook, we will deploy a distributed topology of Feast services, which includes:\n", + "\n", + "* `Registry Server`: Handles metadata storage for feature definitions.\n", + "* `Online Store Server`: Uses the `Registry Server` to query metadata and is responsible for low-latency serving of features.\n", + "* `Offline Store Server`: Uses the `Registry Server` to query metadata and provides access to batch data for historical feature retrieval.\n", + "\n", + "Each service is backed by a `PostgreSQL` database, which is also deployed within the same Kind cluster." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup Postgresql and Redis\n", + "Apply the included [postgres](postgres.yaml) & [redis](redis.yaml) deployments to run simple databases." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "secret/postgres-secret created\n", + "deployment.apps/postgres created\n", + "service/postgres created\n", + "deployment.apps/redis created\n", + "service/redis created\n", + "deployment.apps/redis condition met\n", + "deployment.apps/postgres condition met\n" + ] + } + ], + "source": [ + "!kubectl apply -f postgres.yaml -f redis.yaml\n", + "!kubectl wait --for=condition=available --timeout=5m deployment/redis\n", + "!kubectl wait --for=condition=available --timeout=5m deployment/postgres" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME READY STATUS RESTARTS AGE\n", + "pod/postgres-ff8d4cf48-bqvfm 1/1 Running 0 99s\n", + "pod/redis-b4756b75d-fxkst 1/1 Running 0 98s\n", + "\n", + "NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\n", + "service/postgres ClusterIP 10.43.197.94 5432/TCP 99s\n", + "service/redis ClusterIP 10.43.137.235 6379/TCP 98s\n", + "\n", + "NAME READY UP-TO-DATE AVAILABLE AGE\n", + "deployment.apps/postgres 1/1 1 1 99s\n", + "deployment.apps/redis 1/1 1 1 98s\n", + "\n", + "NAME DESIRED CURRENT READY AGE\n", + "replicaset.apps/postgres-ff8d4cf48 1 1 1 99s\n", + "replicaset.apps/redis-b4756b75d 1 1 1 98s\n" + ] + } + ], + "source": [ + "!kubectl get all" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the Feast Operator" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "namespace/feast-operator-system created\n", + "customresourcedefinition.apiextensions.k8s.io/featurestores.feast.dev created\n", + "serviceaccount/feast-operator-controller-manager created\n", + "role.rbac.authorization.k8s.io/feast-operator-leader-election-role created\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-featurestore-editor-role created\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-featurestore-viewer-role created\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-manager-role created\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-metrics-auth-role created\n", + "clusterrole.rbac.authorization.k8s.io/feast-operator-metrics-reader created\n", + "rolebinding.rbac.authorization.k8s.io/feast-operator-leader-election-rolebinding created\n", + "clusterrolebinding.rbac.authorization.k8s.io/feast-operator-manager-rolebinding created\n", + "clusterrolebinding.rbac.authorization.k8s.io/feast-operator-metrics-auth-rolebinding created\n", + "service/feast-operator-controller-manager-metrics-service created\n", + "deployment.apps/feast-operator-controller-manager created\n", + "deployment.apps/feast-operator-controller-manager condition met\n" + ] + } + ], + "source": [ + "## Use this install command from a release branch (e.g. 'v0.43-branch')\n", + "!kubectl apply -f ../../infra/feast-operator/dist/install.yaml\n", + "\n", + "## OR, for the latest code/builds, use one the following commands from the 'master' branch\n", + "# !make -C ../../infra/feast-operator install deploy IMG=quay.io/feastdev-ci/feast-operator:develop FS_IMG=quay.io/feastdev-ci/feature-server:develop\n", + "# !make -C ../../infra/feast-operator install deploy IMG=quay.io/feastdev-ci/feast-operator:$(git rev-parse HEAD) FS_IMG=quay.io/feastdev-ci/feature-server:$(git rev-parse HEAD)\n", + "\n", + "!kubectl wait --for=condition=available --timeout=5m deployment/feast-operator-controller-manager -n feast-operator-system" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install the Feast services via FeatureStore CR\n", + "Next, we'll use the running Feast Operator to install the feast services. Apply the included [reference deployment](feast.yaml) to install and configure Feast." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "secret/feast-data-stores created\n", + "featurestore.feast.dev/example created\n" + ] + } + ], + "source": [ + "!kubectl apply -f feast.yaml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Validate the running FeatureStore deployment\n", + "Validate the deployment status." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME READY STATUS RESTARTS AGE\n", + "pod/feast-example-74d5c98984-sr9bs 0/3 Init:0/1 0 4m43s\n", + "pod/postgres-ff8d4cf48-bqvfm 1/1 Running 0 7m36s\n", + "pod/redis-b4756b75d-fxkst 1/1 Running 0 7m35s\n", + "\n", + "NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE\n", + "service/feast-example-offline ClusterIP 10.43.81.130 80/TCP 4m44s\n", + "service/feast-example-online ClusterIP 10.43.74.219 80/TCP 4m43s\n", + "service/feast-example-registry ClusterIP 10.43.52.126 80/TCP 4m43s\n", + "service/postgres ClusterIP 10.43.197.94 5432/TCP 7m36s\n", + "service/redis ClusterIP 10.43.137.235 6379/TCP 7m35s\n", + "\n", + "NAME READY UP-TO-DATE AVAILABLE AGE\n", + "deployment.apps/feast-example 0/1 1 0 4m43s\n", + "deployment.apps/postgres 1/1 1 1 7m36s\n", + "deployment.apps/redis 1/1 1 1 7m35s\n", + "\n", + "NAME DESIRED CURRENT READY AGE\n", + "replicaset.apps/feast-example-74d5c98984 1 1 0 4m43s\n", + "replicaset.apps/postgres-ff8d4cf48 1 1 1 7m36s\n", + "replicaset.apps/redis-b4756b75d 1 1 1 7m35s\n", + "deployment.apps/feast-example condition met\n" + ] + } + ], + "source": [ + "!kubectl get all\n", + "!kubectl wait --for=condition=available --timeout=8m deployment/feast-example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Validate that the FeatureStore CR is in a `Ready` state." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME STATUS AGE\n", + "example Ready 10m\n" + ] + } + ], + "source": [ + "!kubectl get feast" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify that the DB includes the expected tables." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " List of relations\n", + " Schema | Name | Type | Owner \n", + "--------+-------------------------+-------+-------\n", + " public | data_sources | table | feast\n", + " public | entities | table | feast\n", + " public | feast_metadata | table | feast\n", + " public | feature_services | table | feast\n", + " public | feature_views | table | feast\n", + " public | managed_infra | table | feast\n", + " public | on_demand_feature_views | table | feast\n", + " public | permissions | table | feast\n", + " public | projects | table | feast\n", + " public | saved_datasets | table | feast\n", + " public | stream_feature_views | table | feast\n", + " public | validation_references | table | feast\n", + "(12 rows)\n", + "\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/postgres -- psql -h localhost -U feast feast -c '\\dt'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify the client `feature_store.yaml` and create the sample feature store definitions." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "project: credit_scoring_local\n", + "provider: local\n", + "offline_store:\n", + " type: duckdb\n", + "online_store:\n", + " type: redis\n", + " connection_string: redis.feast.svc.cluster.local:6379\n", + "registry:\n", + " path: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres.feast.svc.cluster.local:5432/${POSTGRES_DB}\n", + " registry_type: sql\n", + " cache_ttl_seconds: 60\n", + " sqlalchemy_config_kwargs:\n", + " echo: false\n", + " pool_pre_ping: true\n", + "auth:\n", + " type: no_auth\n", + "entity_key_serialization_version: 3\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/feast-data/credit_scoring_local/feature_repo/example_repo.py:27: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\n", + " driver = Entity(name=\"driver\", join_keys=[\"driver_id\"])\n", + "Applying changes for project credit_scoring_local\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_store.py:578: RuntimeWarning: On demand feature view is an experimental feature. This API is stable, but the functionality does not scale well for offline retrieval\n", + " warnings.warn(\n", + "Deploying infrastructure for \u001b[1m\u001b[32mdriver_hourly_stats\u001b[0m\n", + "Deploying infrastructure for \u001b[1m\u001b[32mdriver_hourly_stats_fresh\u001b[0m\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- cat feature_store.yaml\n", + "!kubectl exec deploy/feast-example -itc registry -- feast apply" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List the registered feast projects & feature views." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "NAME DESCRIPTION TAGS OWNER\n", + "credit_scoring_local A project for driver statistics {}\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "NAME ENTITIES TYPE\n", + "driver_hourly_stats {'driver'} FeatureView\n", + "driver_hourly_stats_fresh {'driver'} FeatureView\n", + "transformed_conv_rate {'driver'} OnDemandFeatureView\n", + "transformed_conv_rate_fresh {'driver'} OnDemandFeatureView\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- feast projects list\n", + "!kubectl exec deploy/feast-example -itc registry -- feast feature-views list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's verify the feast version." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "Feast SDK Version: \"0.43.0\"\n" + ] + } + ], + "source": [ + "!kubectl exec deployment/feast-example -itc registry -- feast version" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/operator-quickstart/02-Demo.ipynb b/examples/operator-quickstart/02-Demo.ipynb new file mode 100644 index 00000000000..51e26ba3616 --- /dev/null +++ b/examples/operator-quickstart/02-Demo.ipynb @@ -0,0 +1,568 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Run the \"Real-time Credit Scoring\" tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll use the following tutorial as a demonstration.\n", + "\n", + "https://github.com/feast-dev/feast-credit-score-local-tutorial/tree/f43b44b245ae2632b582f14176392cfe31f98da9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Upload the tutorial source code to the running Feast pod." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Upload the tutorial source code to the running feast pod and extract its contents." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2025-01-23 18:09:13-- https://github.com/feast-dev/feast-credit-score-local-tutorial/archive/f43b44b.tar.gz\n", + "Resolving github.com (github.com)... 140.82.112.4\n", + "Connecting to github.com (github.com)|140.82.112.4|:443... connected.\n", + "HTTP request sent, awaiting response... 302 Found\n", + "Location: https://codeload.github.com/feast-dev/feast-credit-score-local-tutorial/tar.gz/f43b44b245ae2632b582f14176392cfe31f98da9 [following]\n", + "--2025-01-23 18:09:13-- https://codeload.github.com/feast-dev/feast-credit-score-local-tutorial/tar.gz/f43b44b245ae2632b582f14176392cfe31f98da9\n", + "Resolving codeload.github.com (codeload.github.com)... 140.82.114.10\n", + "Connecting to codeload.github.com (codeload.github.com)|140.82.114.10|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: unspecified [application/x-gzip]\n", + "Saving to: ‘f43b44b.tar.gz’\n", + "\n", + "f43b44b.tar.gz [ <=> ] 45.52M 10.8MB/s in 5.0s \n", + "\n", + "2025-01-23 18:09:19 (9.12 MB/s) - ‘f43b44b.tar.gz’ saved [47734189]\n", + "\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/.gitignore\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/LICENSE\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/README.md\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/app.py\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/credit_model.py\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/credit_history.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/credit_history_sample.csv\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/loan_table.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/loan_table_sample.csv\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/training_dataset_sample.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/zipcode_table.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/data/zipcode_table_sample.csv\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/credit_history.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/credit_history_sample.csv\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/loan_table.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/loan_table_sample.csv\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/training_dataset_sample.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/zipcode_table.parquet\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/data/zipcode_table_sample.csv\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/feature_store.yaml\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/feature_repo/features.py\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/requirements.txt\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/run.py\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/streamlit.png\n", + "feast-credit-score-local-tutorial-f43b44b245ae2632b582f14176392cfe31f98da9/streamlit_app.py\n" + ] + } + ], + "source": [ + "![ -f f43b44b.tar.gz ] || wget https://github.com/feast-dev/feast-credit-score-local-tutorial/archive/f43b44b.tar.gz\n", + "!kubectl cp f43b44b.tar.gz $(kubectl get pods -l 'feast.dev/name=example' -ojsonpath=\"{.items[*].metadata.name}\"):/feast-data -c registry\n", + "!kubectl exec deploy/feast-example -itc registry -- rm -rf /feast-data/feast-credit-score-local-tutorial\n", + "!kubectl exec deploy/feast-example -itc registry -- mkdir /feast-data/feast-credit-score-local-tutorial\n", + "!kubectl exec deploy/feast-example -itc registry -- tar vfx /feast-data/f43b44b.tar.gz -C /feast-data/feast-credit-score-local-tutorial --strip-components 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Verify the client `feature_store.yaml`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Copy the `feature_store.yaml` to the tutorial directory and verify its contents." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "project: credit_scoring_local\n", + "provider: local\n", + "offline_store:\n", + " type: duckdb\n", + "online_store:\n", + " type: redis\n", + " connection_string: redis.feast.svc.cluster.local:6379\n", + "registry:\n", + " path: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres.feast.svc.cluster.local:5432/${POSTGRES_DB}\n", + " registry_type: sql\n", + " cache_ttl_seconds: 60\n", + " sqlalchemy_config_kwargs:\n", + " echo: false\n", + " pool_pre_ping: true\n", + "auth:\n", + " type: no_auth\n", + "entity_key_serialization_version: 3\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- cp -f /feast-data/credit_scoring_local/feature_repo/feature_store.yaml /feast-data/feast-credit-score-local-tutorial/feature_repo/feature_store.yaml\n", + "!kubectl exec deploy/feast-example -itc registry -- cat /feast-data/feast-credit-score-local-tutorial/feature_repo/feature_store.yaml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Apply the tutorial feature store definitions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Update the feature store definitions for the tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "No project found in the repository. Using project name credit_scoring_local defined in feature_store.yaml\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "Applying changes for project credit_scoring_local\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'driver'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_store.py:578: RuntimeWarning: On demand feature view is an experimental feature. This API is stable, but the functionality does not scale well for offline retrieval\n", + " warnings.warn(\n", + "Deploying infrastructure for \u001b[1m\u001b[32mcredit_history\u001b[0m\n", + "Deploying infrastructure for \u001b[1m\u001b[32mzipcode_features\u001b[0m\n", + "Removing infrastructure for \u001b[1m\u001b[31mdriver_hourly_stats_fresh\u001b[0m\n", + "Removing infrastructure for \u001b[1m\u001b[31mdriver_hourly_stats\u001b[0m\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- feast -c /feast-data/feast-credit-score-local-tutorial/feature_repo apply" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load data from feature views into the online store, beginning from either the previous materialize or materialize-incremental end date, or the beginning of time." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "Materializing \u001b[1m\u001b[32m2\u001b[0m feature views to \u001b[1m\u001b[32m2025-01-24 00:10:49+00:00\u001b[0m into the \u001b[1m\u001b[32mredis\u001b[0m online store.\n", + "\n", + "\u001b[1m\u001b[32mcredit_history\u001b[0m from \u001b[1m\u001b[32m2024-10-26 00:11:25+00:00\u001b[0m to \u001b[1m\u001b[32m2025-01-24 00:10:49+00:00\u001b[0m:\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "0it [00:00, ?it/s]\n", + "\u001b[1m\u001b[32mzipcode_features\u001b[0m from \u001b[1m\u001b[32m2015-01-27 00:11:41+00:00\u001b[0m to \u001b[1m\u001b[32m2025-01-24 00:10:49+00:00\u001b[0m:\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "100%|███████████████████████████████████████████████████████| 28844/28844 [00:26<00:00, 1090.11it/s]\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- bash -c 'cd /feast-data/feast-credit-score-local-tutorial/feature_repo && feast materialize-incremental $(date -u +\"%Y-%m-%dT%H:%M:%S\")'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Execute feast commands inside the client Pod" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "List the registered feast feature views & entities." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "NAME ENTITIES TYPE\n", + "credit_history {'dob_ssn'} FeatureView\n", + "zipcode_features {'zipcode'} FeatureView\n", + "total_debt_calc {'dob_ssn'} OnDemandFeatureView\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "NAME DESCRIPTION TYPE\n", + "zipcode ValueType.INT64\n", + "dob_ssn Date of birth and last four digits of social security number ValueType.STRING\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- feast -c /feast-data/feast-credit-score-local-tutorial/feature_repo feature-views list\n", + "!kubectl exec deploy/feast-example -itc registry -- feast -c /feast-data/feast-credit-score-local-tutorial/feature_repo entities list" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Train and test the model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Install the required packages." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Collecting streamlit (from -r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading streamlit-1.41.1-py2.py3-none-any.whl.metadata (8.5 kB)\n", + "Collecting shap (from -r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2))\n", + " Downloading shap-0.46.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (24 kB)\n", + "Requirement already satisfied: pandas in /opt/app-root/lib64/python3.11/site-packages (from -r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 3)) (2.2.3)\n", + "Collecting scikit-learn (from -r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 4))\n", + " Downloading scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (18 kB)\n", + "Collecting matplotlib (from -r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 5))\n", + " Downloading matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (11 kB)\n", + "Collecting altair<6,>=4.0 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading altair-5.5.0-py3-none-any.whl.metadata (11 kB)\n", + "Collecting blinker<2,>=1.0.0 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading blinker-1.9.0-py3-none-any.whl.metadata (1.6 kB)\n", + "Requirement already satisfied: cachetools<6,>=4.0 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (5.5.1)\n", + "Requirement already satisfied: click<9,>=7.0 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (8.1.8)\n", + "Requirement already satisfied: numpy<3,>=1.23 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (1.26.4)\n", + "Requirement already satisfied: packaging<25,>=20 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (24.2)\n", + "Collecting pillow<12,>=7.1.0 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl.metadata (9.1 kB)\n", + "Requirement already satisfied: protobuf<6,>=3.20 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (4.25.5)\n", + "Requirement already satisfied: pyarrow>=7.0 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (17.0.0)\n", + "Requirement already satisfied: requests<3,>=2.27 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (2.32.3)\n", + "Requirement already satisfied: rich<14,>=10.14.0 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (13.9.4)\n", + "Requirement already satisfied: tenacity<10,>=8.1.0 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (8.5.0)\n", + "Requirement already satisfied: toml<2,>=0.10.1 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (0.10.2)\n", + "Requirement already satisfied: typing-extensions<5,>=4.3.0 in /opt/app-root/lib64/python3.11/site-packages (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (4.12.2)\n", + "Collecting watchdog<7,>=2.1.5 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl.metadata (44 kB)\n", + "Collecting gitpython!=3.1.19,<4,>=3.0.7 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading GitPython-3.1.44-py3-none-any.whl.metadata (13 kB)\n", + "Collecting pydeck<1,>=0.8.0b4 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading pydeck-0.9.1-py2.py3-none-any.whl.metadata (4.1 kB)\n", + "Collecting tornado<7,>=6.0.3 (from streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.5 kB)\n", + "Collecting scipy (from shap->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2))\n", + " Downloading scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n", + "Requirement already satisfied: tqdm>=4.27.0 in /opt/app-root/lib64/python3.11/site-packages (from shap->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2)) (4.67.1)\n", + "Collecting slicer==0.0.8 (from shap->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2))\n", + " Downloading slicer-0.0.8-py3-none-any.whl.metadata (4.0 kB)\n", + "Collecting numba (from shap->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2))\n", + " Downloading numba-0.61.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.8 kB)\n", + "Requirement already satisfied: cloudpickle in /opt/app-root/lib64/python3.11/site-packages (from shap->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2)) (3.1.1)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /opt/app-root/lib64/python3.11/site-packages (from pandas->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 3)) (2.9.0.post0)\n", + "Requirement already satisfied: pytz>=2020.1 in /opt/app-root/lib64/python3.11/site-packages (from pandas->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 3)) (2024.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /opt/app-root/lib64/python3.11/site-packages (from pandas->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 3)) (2025.1)\n", + "Collecting joblib>=1.2.0 (from scikit-learn->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 4))\n", + " Downloading joblib-1.4.2-py3-none-any.whl.metadata (5.4 kB)\n", + "Collecting threadpoolctl>=3.1.0 (from scikit-learn->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 4))\n", + " Downloading threadpoolctl-3.5.0-py3-none-any.whl.metadata (13 kB)\n", + "Collecting contourpy>=1.0.1 (from matplotlib->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 5))\n", + " Downloading contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (5.4 kB)\n", + "Collecting cycler>=0.10 (from matplotlib->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 5))\n", + " Downloading cycler-0.12.1-py3-none-any.whl.metadata (3.8 kB)\n", + "Collecting fonttools>=4.22.0 (from matplotlib->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 5))\n", + " Downloading fonttools-4.55.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (165 kB)\n", + "Collecting kiwisolver>=1.3.1 (from matplotlib->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 5))\n", + " Downloading kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.2 kB)\n", + "Collecting pyparsing>=2.3.1 (from matplotlib->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 5))\n", + " Downloading pyparsing-3.2.1-py3-none-any.whl.metadata (5.0 kB)\n", + "Requirement already satisfied: jinja2 in /opt/app-root/lib64/python3.11/site-packages (from altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (3.1.5)\n", + "Requirement already satisfied: jsonschema>=3.0 in /opt/app-root/lib64/python3.11/site-packages (from altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (4.23.0)\n", + "Collecting narwhals>=1.14.2 (from altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading narwhals-1.23.0-py3-none-any.whl.metadata (10.0 kB)\n", + "Collecting gitdb<5,>=4.0.1 (from gitpython!=3.1.19,<4,>=3.0.7->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading gitdb-4.0.12-py3-none-any.whl.metadata (1.2 kB)\n", + "Requirement already satisfied: six>=1.5 in /opt/app-root/lib64/python3.11/site-packages (from python-dateutil>=2.8.2->pandas->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 3)) (1.17.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /opt/app-root/lib64/python3.11/site-packages (from requests<3,>=2.27->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (3.4.1)\n", + "Requirement already satisfied: idna<4,>=2.5 in /opt/app-root/lib64/python3.11/site-packages (from requests<3,>=2.27->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (3.10)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/app-root/lib64/python3.11/site-packages (from requests<3,>=2.27->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (2.3.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /opt/app-root/lib64/python3.11/site-packages (from requests<3,>=2.27->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (2024.12.14)\n", + "Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/app-root/lib64/python3.11/site-packages (from rich<14,>=10.14.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (3.0.0)\n", + "Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/app-root/lib64/python3.11/site-packages (from rich<14,>=10.14.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (2.19.1)\n", + "Collecting llvmlite<0.45,>=0.44.0dev0 (from numba->shap->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 2))\n", + " Downloading llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.8 kB)\n", + "Collecting smmap<6,>=3.0.1 (from gitdb<5,>=4.0.1->gitpython!=3.1.19,<4,>=3.0.7->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1))\n", + " Downloading smmap-5.0.2-py3-none-any.whl.metadata (4.3 kB)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /opt/app-root/lib64/python3.11/site-packages (from jinja2->altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (3.0.2)\n", + "Requirement already satisfied: attrs>=22.2.0 in /opt/app-root/lib64/python3.11/site-packages (from jsonschema>=3.0->altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (24.3.0)\n", + "Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /opt/app-root/lib64/python3.11/site-packages (from jsonschema>=3.0->altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (2024.10.1)\n", + "Requirement already satisfied: referencing>=0.28.4 in /opt/app-root/lib64/python3.11/site-packages (from jsonschema>=3.0->altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (0.36.1)\n", + "Requirement already satisfied: rpds-py>=0.7.1 in /opt/app-root/lib64/python3.11/site-packages (from jsonschema>=3.0->altair<6,>=4.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (0.22.3)\n", + "Requirement already satisfied: mdurl~=0.1 in /opt/app-root/lib64/python3.11/site-packages (from markdown-it-py>=2.2.0->rich<14,>=10.14.0->streamlit->-r /feast-data/feast-credit-score-local-tutorial/requirements.txt (line 1)) (0.1.2)\n", + "Downloading streamlit-1.41.1-py2.py3-none-any.whl (9.1 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m9.1/9.1 MB\u001b[0m \u001b[31m7.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading shap-0.46.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (540 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m540.2/540.2 kB\u001b[0m \u001b[31m14.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading slicer-0.0.8-py3-none-any.whl (15 kB)\n", + "Downloading scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m13.5/13.5 MB\u001b[0m \u001b[31m11.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\u001b[36m0:00:01\u001b[0mm\n", + "\u001b[?25hDownloading matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.6 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8.6/8.6 MB\u001b[0m \u001b[31m11.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading altair-5.5.0-py3-none-any.whl (731 kB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m731.2/731.2 kB\u001b[0m \u001b[31m12.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading blinker-1.9.0-py3-none-any.whl (8.5 kB)\n", + "Downloading contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (326 kB)\n", + "Downloading cycler-0.12.1-py3-none-any.whl (8.3 kB)\n", + "Downloading fonttools-4.55.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.9 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.9/4.9 MB\u001b[0m \u001b[31m15.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading GitPython-3.1.44-py3-none-any.whl (207 kB)\n", + "Downloading joblib-1.4.2-py3-none-any.whl (301 kB)\n", + "Downloading kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.4 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.4/1.4 MB\u001b[0m \u001b[31m19.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", + "\u001b[?25hDownloading pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl (4.5 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.5/4.5 MB\u001b[0m \u001b[31m12.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading pydeck-0.9.1-py2.py3-none-any.whl (6.9 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m6.9/6.9 MB\u001b[0m \u001b[31m14.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m00:01\u001b[0m\n", + "\u001b[?25hDownloading pyparsing-3.2.1-py3-none-any.whl (107 kB)\n", + "Downloading scipy-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (40.6 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m40.6/40.6 MB\u001b[0m \u001b[31m11.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading threadpoolctl-3.5.0-py3-none-any.whl (18 kB)\n", + "Downloading tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (437 kB)\n", + "Downloading watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl (79 kB)\n", + "Downloading numba-0.61.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (3.8 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.8/3.8 MB\u001b[0m \u001b[31m12.7 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading gitdb-4.0.12-py3-none-any.whl (62 kB)\n", + "Downloading llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (42.4 MB)\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m42.4/42.4 MB\u001b[0m \u001b[31m11.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0ma \u001b[36m0:00:01\u001b[0m\n", + "\u001b[?25hDownloading narwhals-1.23.0-py3-none-any.whl (306 kB)\n", + "Downloading smmap-5.0.2-py3-none-any.whl (24 kB)\n", + "Installing collected packages: watchdog, tornado, threadpoolctl, smmap, slicer, scipy, pyparsing, pillow, narwhals, llvmlite, kiwisolver, joblib, fonttools, cycler, contourpy, blinker, scikit-learn, pydeck, numba, matplotlib, gitdb, shap, gitpython, altair, streamlit\n", + "Successfully installed altair-5.5.0 blinker-1.9.0 contourpy-1.3.1 cycler-0.12.1 fonttools-4.55.5 gitdb-4.0.12 gitpython-3.1.44 joblib-1.4.2 kiwisolver-1.4.8 llvmlite-0.44.0 matplotlib-3.10.0 narwhals-1.23.0 numba-0.61.0 pillow-11.1.0 pydeck-0.9.1 pyparsing-3.2.1 scikit-learn-1.6.1 scipy-1.15.1 shap-0.46.0 slicer-0.0.8 smmap-5.0.2 streamlit-1.41.1 threadpoolctl-3.5.0 tornado-6.4.2 watchdog-6.0.0\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- bash -c 'pip install -r /feast-data/feast-credit-score-local-tutorial/requirements.txt'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Train and test the model." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/opt/app-root/lib64/python3.11/site-packages/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'zipcode'.\n", + " entity = cls(\n", + "/opt/app-root/lib64/python3.11/site-packages/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'dob_ssn'.\n", + " entity = cls(\n", + "Loan rejected!\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- bash -c 'cd /feast-data/feast-credit-score-local-tutorial && python run.py'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Interactive demo (using Streamlit)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In a new terminal, run the following command and leave it active.\n", + "\n", + "```bash\n", + "$ kubectl port-forward deploy/feast-example 8501:8501\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the Streamlit application" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Collecting usage statistics. To deactivate, set browser.gatherUsageStats to false.\n", + "\u001b[0m\n", + "\u001b[0m\n", + "\u001b[34m\u001b[1m You can now view your Streamlit app in your browser.\u001b[0m\n", + "\u001b[0m\n", + "\u001b[34m Local URL: \u001b[0m\u001b[1mhttp://localhost:8501\u001b[0m\n", + "\u001b[34m Network URL: \u001b[0m\u001b[1mhttp://10.42.0.8:8501\u001b[0m\n", + "\u001b[34m External URL: \u001b[0m\u001b[1mhttp://23.112.66.217:8501\u001b[0m\n", + "\u001b[0m\n" + ] + } + ], + "source": [ + "!kubectl exec deploy/feast-example -itc registry -- bash -c 'cd /feast-data/feast-credit-score-local-tutorial && streamlit run --server.port 8501 streamlit_app.py'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then navigate to the local URL on which Streamlit is being served.\n", + "\n", + "http://localhost:8501" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/operator-quickstart/03-Uninstall.ipynb b/examples/operator-quickstart/03-Uninstall.ipynb new file mode 100644 index 00000000000..02ebedcb50b --- /dev/null +++ b/examples/operator-quickstart/03-Uninstall.ipynb @@ -0,0 +1,97 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Uninstall the Operator and all Feast related objects" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "secret \"feast-data-stores\" deleted\n", + "featurestore.feast.dev \"example\" deleted\n", + "secret \"postgres-secret\" deleted\n", + "deployment.apps \"postgres\" deleted\n", + "service \"postgres\" deleted\n", + "deployment.apps \"redis\" deleted\n", + "service \"redis\" deleted\n", + "namespace \"feast-operator-system\" deleted\n", + "customresourcedefinition.apiextensions.k8s.io \"featurestores.feast.dev\" deleted\n", + "serviceaccount \"feast-operator-controller-manager\" deleted\n", + "role.rbac.authorization.k8s.io \"feast-operator-leader-election-role\" deleted\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-featurestore-editor-role\" deleted\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-featurestore-viewer-role\" deleted\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-manager-role\" deleted\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-metrics-auth-role\" deleted\n", + "clusterrole.rbac.authorization.k8s.io \"feast-operator-metrics-reader\" deleted\n", + "rolebinding.rbac.authorization.k8s.io \"feast-operator-leader-election-rolebinding\" deleted\n", + "clusterrolebinding.rbac.authorization.k8s.io \"feast-operator-manager-rolebinding\" deleted\n", + "clusterrolebinding.rbac.authorization.k8s.io \"feast-operator-metrics-auth-rolebinding\" deleted\n", + "service \"feast-operator-controller-manager-metrics-service\" deleted\n", + "deployment.apps \"feast-operator-controller-manager\" deleted\n" + ] + } + ], + "source": [ + "!kubectl delete -f feast.yaml\n", + "!kubectl delete -f postgres.yaml\n", + "!kubectl delete -f redis.yaml\n", + "!kubectl delete -f ../../infra/feast-operator/dist/install.yaml" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Ensure everything has been removed, or is in the process of being terminated." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "NAME READY STATUS RESTARTS AGE\n", + "pod/feast-example-74d5c98984-sr9bs 2/3 Terminating 2 (18m ago) 27m\n" + ] + } + ], + "source": [ + "!kubectl get all" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/operator-quickstart/README.md b/examples/operator-quickstart/README.md new file mode 100644 index 00000000000..56ba7173b56 --- /dev/null +++ b/examples/operator-quickstart/README.md @@ -0,0 +1,7 @@ +# Install and run a Feature Store on Kubernetes with the Feast Operator + +The following notebooks will guide you through how to install and use Feast on Kubernetes with the Feast Go Operator. + +* [01-Install.ipynb](./01-Install.ipynb): Install and configure a Feature Store in Kubernetes with the Operator. +* [02-Demo.ipynb](./02-Demo.ipynb): Validate the feature store with demo application. +* [03-Uninstall.ipynb](./03-Uninstall.ipynb): Clear the installed deployments. diff --git a/examples/operator-quickstart/feast.yaml b/examples/operator-quickstart/feast.yaml new file mode 100644 index 00000000000..def24bd9a4c --- /dev/null +++ b/examples/operator-quickstart/feast.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: Secret +metadata: + name: feast-data-stores + namespace: feast +stringData: + redis: | + connection_string: redis.feast.svc.cluster.local:6379 + sql: | + path: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres.feast.svc.cluster.local:5432/${POSTGRES_DB} + cache_ttl_seconds: 60 + sqlalchemy_config_kwargs: + echo: false + pool_pre_ping: true +--- +apiVersion: feast.dev/v1alpha1 +kind: FeatureStore +metadata: + name: example + namespace: feast +spec: + feastProject: credit_scoring_local + services: + offlineStore: + persistence: + file: + type: duckdb + envFrom: + - secretRef: + name: postgres-secret + onlineStore: + persistence: + store: + type: redis + secretRef: + name: feast-data-stores + envFrom: + - secretRef: + name: postgres-secret + registry: + local: + persistence: + store: + type: sql + secretRef: + name: feast-data-stores + envFrom: + - secretRef: + name: postgres-secret + env: + - name: MPLCONFIGDIR + value: /tmp + resources: + requests: + cpu: 150m + memory: 128Mi diff --git a/examples/operator-quickstart/postgres.yaml b/examples/operator-quickstart/postgres.yaml new file mode 100644 index 00000000000..e37caa16da4 --- /dev/null +++ b/examples/operator-quickstart/postgres.yaml @@ -0,0 +1,55 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret + namespace: feast +stringData: + POSTGRES_DB: feast + POSTGRES_USER: feast + POSTGRES_PASSWORD: feast +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: feast +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: 'postgres:16-alpine' + ports: + - containerPort: 5432 + envFrom: + - secretRef: + name: postgres-secret + volumeMounts: + - mountPath: /var/lib/postgresql + name: postgresdata + volumes: + - name: postgresdata + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: feast + labels: + app: postgres +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + protocol: TCP + selector: + app: postgres \ No newline at end of file diff --git a/examples/operator-quickstart/redis.yaml b/examples/operator-quickstart/redis.yaml new file mode 100644 index 00000000000..5d70b6bd5d6 --- /dev/null +++ b/examples/operator-quickstart/redis.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: feast +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - name: redis + image: 'bitnami/redis:latest' + ports: + - containerPort: 6379 + env: + - name: ALLOW_EMPTY_PASSWORD + value: "yes" +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: feast + labels: + app: redis +spec: + type: ClusterIP + ports: + - port: 6379 + targetPort: 6379 + protocol: TCP + selector: + app: redis \ No newline at end of file diff --git a/examples/rag/README.md b/examples/rag/README.md new file mode 100644 index 00000000000..88775fae0ed --- /dev/null +++ b/examples/rag/README.md @@ -0,0 +1,87 @@ +# 🚀 Quickstart: Retrieval-Augmented Generation (RAG) using Feast and Large Language Models (LLMs) + +This project demonstrates how to use **Feast** to power a **Retrieval-Augmented Generation (RAG)** application. +The RAG architecture combines retrieval of documents (using vector search) with In-Context-Learning (ICL) through a +**Large Language Model (LLM)** to answer user questions accurately using structured and unstructured data. + +## 💡 Why Use Feast for RAG? + +- **Online retrieval of features:** Ensure real-time access to precomputed document embeddings and other structured data. +- **Declarative feature definitions:** Define feature views and entities in a Python file and empower Data Scientists to easily ship scalabe RAG applications with all of the existing benefits of Feast. +- **Vector search:** Leverage Feast’s integration with vector databases like **Milvus** to find relevant documents based on a similarity metric (e.g., cosine). +- **Structured and unstructured context:** Retrieve both embeddings and traditional features, injecting richer context into LLM prompts. +- **Versioning and reusability:** Collaborate across teams with discoverable, versioned data pipelines. + +--- + +## 📂 Project Structure + +- **`data/`**: Contains the demo data, including Wikipedia summaries of cities with sentence embeddings stored in a Parquet file. +- **`example_repo.py`**: Defines the feature views and entity configurations for Feast. +- **`feature_store.yaml`**: Configures the offline and online stores (using local files and Milvus Lite in this demo). +- **`test_workflow.py`**: Demonstrates key Feast commands to define, retrieve, and push features. + +--- + +## 🛠️ Setup + +1. **Install the necessary packages**: + ```bash + pip install feast torch transformers openai + ``` +2. Initialize and inspect the feature store: + + ```bash + feast apply + ``` + +3. Materialize features into the online store: + + ```python + store.write_to_online_store(feature_view_name='city_embeddings', df=df) + ``` +4. Run a query: + +- Prepare your question: +`question = "Which city has the largest population in New York?"` +- Embed the question using sentence-transformers/all-MiniLM-L6-v2. +- Retrieve the top K most relevant documents using Milvus vector search. +- Pass the retrieved context to the OpenAI model for conversational output. + +## 🛠️ Key Commands for Data Scientists +- Apply feature definitions: + +```bash +feast apply +``` + +- Materialize features to the online store: +```python +store.write_to_online_store(feature_view_name='city_embeddings', df=df) +``` + +- Inspect retrieved features using Python: +```python +context_data = store.retrieve_online_documents_v2( + features=[ + "city_embeddings:vector", + "city_embeddings:item_id", + "city_embeddings:state", + "city_embeddings:sentence_chunks", + "city_embeddings:wiki_summary", + ], + query=query, + top_k=3, + distance_metric='COSINE', +).to_df() +display(context_data) +``` + +📊 Example Output +When querying: Which city has the largest population in New York? + +The model provides: + +``` +The largest city in New York is New York City, often referred to as NYC. It is the most populous city in the United States, with an estimated population of 8,335,897 in 2022. +``` \ No newline at end of file diff --git a/examples/rag/__init__.py b/examples/rag/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/rag/feature_repo/__init__.py b/examples/rag/feature_repo/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/examples/rag/feature_repo/data/city_wikipedia_summaries_with_embeddings.parquet b/examples/rag/feature_repo/data/city_wikipedia_summaries_with_embeddings.parquet new file mode 100644 index 00000000000..63270802fdf Binary files /dev/null and b/examples/rag/feature_repo/data/city_wikipedia_summaries_with_embeddings.parquet differ diff --git a/examples/rag/feature_repo/example_repo.py b/examples/rag/feature_repo/example_repo.py new file mode 100644 index 00000000000..e0a9be21452 --- /dev/null +++ b/examples/rag/feature_repo/example_repo.py @@ -0,0 +1,42 @@ +from datetime import timedelta + +from feast import ( + FeatureView, + Field, + FileSource, +) +from feast.data_format import ParquetFormat +from feast.types import Float32, Array, String, ValueType +from feast import Entity + +item = Entity( + name="item_id", + description="Item ID", + value_type=ValueType.INT64, +) + +parquet_file_path = "./data/city_wikipedia_summaries_with_embeddings.parquet" + +source = FileSource( + file_format=ParquetFormat(), + path=parquet_file_path, + timestamp_field="event_timestamp", +) + +city_embeddings_feature_view = FeatureView( + name="city_embeddings", + entities=[item], + schema=[ + Field( + name="vector", + dtype=Array(Float32), + vector_index=True, + vector_search_metric="COSINE", + ), + Field(name="state", dtype=String), + Field(name="sentence_chunks", dtype=String), + Field(name="wiki_summary", dtype=String), + ], + source=source, + ttl=timedelta(hours=2), +) \ No newline at end of file diff --git a/examples/rag/feature_repo/feature_store.yaml b/examples/rag/feature_repo/feature_store.yaml new file mode 100644 index 00000000000..223be052093 --- /dev/null +++ b/examples/rag/feature_repo/feature_store.yaml @@ -0,0 +1,17 @@ +project: rag +provider: local +registry: data/registry.db +online_store: + type: milvus + path: data/online_store.db + vector_enabled: true + embedding_dim: 384 + index_type: "IVF_FLAT" + + +offline_store: + type: file +entity_key_serialization_version: 3 +# By default, no_auth for authentication and authorization, other possible values kubernetes and oidc. Refer the documentation for more details. +auth: + type: no_auth diff --git a/examples/rag/feature_repo/test_workflow.py b/examples/rag/feature_repo/test_workflow.py new file mode 100644 index 00000000000..05cd554d823 --- /dev/null +++ b/examples/rag/feature_repo/test_workflow.py @@ -0,0 +1,74 @@ +import pandas as pd +import torch +import torch.nn.functional as F +from feast import FeatureStore +from transformers import AutoTokenizer, AutoModel +from example_repo import city_embeddings_feature_view, item + +TOKENIZER = "sentence-transformers/all-MiniLM-L6-v2" +MODEL = "sentence-transformers/all-MiniLM-L6-v2" + + +def mean_pooling(model_output, attention_mask): + token_embeddings = model_output[ + 0 + ] # First element of model_output contains all token embeddings + input_mask_expanded = ( + attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float() + ) + return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp( + input_mask_expanded.sum(1), min=1e-9 + ) + + +def run_model(sentences, tokenizer, model): + encoded_input = tokenizer( + sentences, padding=True, truncation=True, return_tensors="pt" + ) + # Compute token embeddings + with torch.no_grad(): + model_output = model(**encoded_input) + + sentence_embeddings = mean_pooling(model_output, encoded_input["attention_mask"]) + sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1) + return sentence_embeddings + +def run_demo(): + store = FeatureStore(repo_path=".") + df = pd.read_parquet("./data/city_wikipedia_summaries_with_embeddings.parquet") + embedding_length = len(df['vector'][0]) + print(f'embedding length = {embedding_length}') + + store.apply([city_embeddings_feature_view, item]) + fields = [ + f.name for f in city_embeddings_feature_view.features + ] + city_embeddings_feature_view.entities + [city_embeddings_feature_view.batch_source.timestamp_field] + print('\ndata=') + print(df[fields].head().T) + store.write_to_online_store("city_embeddings", df[fields][0:3]) + + + question = "the most populous city in the state of New York is New York" + tokenizer = AutoTokenizer.from_pretrained(TOKENIZER) + model = AutoModel.from_pretrained(MODEL) + query_embedding = run_model(question, tokenizer, model) + query = query_embedding.detach().cpu().numpy().tolist()[0] + + # Retrieve top k documents + features = store.retrieve_online_documents_v2( + features=[ + "city_embeddings:vector", + "city_embeddings:item_id", + "city_embeddings:state", + "city_embeddings:sentence_chunks", + "city_embeddings:wiki_summary", + ], + query=query, + top_k=3, + ) + print("features =") + print(features.to_df()) + store.teardown() + +if __name__ == "__main__": + run_demo() diff --git a/examples/rag/milvus-quickstart.ipynb b/examples/rag/milvus-quickstart.ipynb new file mode 100644 index 00000000000..2999d3ba43f --- /dev/null +++ b/examples/rag/milvus-quickstart.ipynb @@ -0,0 +1,1023 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "f33a2f4a-48b5-4218-8b3f-fc884070145e", + "metadata": {}, + "source": [ + "!pip install torch\n", + "!pip install transformers\n", + "!pip install openai" + ] + }, + { + "cell_type": "markdown", + "id": "b19cb54f-e63f-4d9b-b7ff-d18a30635cd2", + "metadata": {}, + "source": [ + "# Overview\n", + "\n", + "In this tutorial, we'll use Feast to inject documents and structured data (i.e., features) into the context of an LLM (Large Language Model) to power a RAG Application (Retrieval Augmented Generation).\n", + "\n", + "Feast solves several common issues in this flow:\n", + "1. **Online retrieval:** At inference time, LLMs often need access to data that isn't readily \n", + " available and needs to be precomputed from other data sources.\n", + " * Feast manages deployment to a variety of online stores (e.g. Milvus, DynamoDB, Redis, Google Cloud Datastore) and \n", + " ensures necessary features are consistently _available_ and _freshly computed_ at inference time.\n", + "2. **Vector Search:** Feast has built support for vector similarity search that is easily configured declaritively so users can focus on their application.\n", + "3. **Richer structured data:** Along with vector search, users can query standard structured fields to inject into the LLM context for better user experiences.\n", + "4. **Feature/Context and versioning:** Different teams within an organization are often unable to reuse \n", + " data across projects and services, resulting in duplicate application logic. Models have data dependencies that need \n", + " to be versioned, for example when running A/B tests on model/prompt versions.\n", + " * Feast enables discovery of and collaboration on previously used documents, features, and enables versioning of sets of \n", + " data.\n", + "\n", + "We will:\n", + "1. Deploy a local feature store with a **Parquet file offline store** and **Sqlite online store**.\n", + "2. Write/materialize the data (i.e., feature values) from the offline store (a parquet file) into the online store (Sqlite).\n", + "3. Serve the features using the Feast SDK\n", + "4. Inject the document into the LLM's context to answer questions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "425cf2f7-70b5-423c-a4f2-f470d8638135", + "metadata": {}, + "outputs": [], + "source": [ + "%%sh\n", + "pip install feast -U -q\n", + "echo \"Please restart your runtime now (Runtime -> Restart runtime). This ensures that the correct dependencies are loaded.\"" + ] + }, + { + "cell_type": "markdown", + "id": "db162bb9-e262-4958-990d-fd8f3f1f1249", + "metadata": {}, + "source": [ + "**Reminder**: Please restart your runtime after installing Feast (Runtime -> Restart runtime). This ensures that the correct dependencies are loaded." + ] + }, + { + "cell_type": "markdown", + "id": "a25cf84f-c255-4bb3-a3d7-e5512c1ba10d", + "metadata": {}, + "source": [ + "## Step 2: Create a feature repository\n", + "\n", + "A feature repository is a directory that contains the configuration of the feature store and individual features. This configuration is written as code (Python/YAML) and it's highly recommended that teams track it centrally using git. See [Feature Repository](https://docs.feast.dev/reference/feature-repository) for a detailed explanation of feature repositories.\n", + "\n", + "The easiest way to create a new feature repository to use the `feast init` command. For this demo, you **do not** need to initialize a feast repo.\n", + "\n", + "\n", + "### Demo data scenario \n", + "- We data from Wikipedia about states that we have embedded into sentence embeddings to be used for vector retrieval in a RAG application.\n", + "- We want to generate predictions for driver satisfaction for the rest of the users so we can reach out to potentially dissatisfied users." + ] + }, + { + "cell_type": "raw", + "id": "61dfdc9d8732d5a6", + "metadata": {}, + "source": [ + "!feast init feature_repo" + ] + }, + { + "cell_type": "markdown", + "id": "c969b62f-4f58-49ed-ae23-ace1916de0c0", + "metadata": {}, + "source": [ + "### Step 2a: Inspecting the feature repository\n", + "\n", + "Let's take a look at the demo repo itself. It breaks down into\n", + "\n", + "\n", + "* `data/` contains raw demo parquet data\n", + "* `example_repo.py` contains demo feature definitions\n", + "* `feature_store.yaml` contains a demo setup configuring where data sources are\n", + "* `test_workflow.py` showcases how to run all key Feast commands, including defining, retrieving, and pushing features.\n", + " * You can run this with `python test_workflow.py`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5d531836-5981-4a34-9367-51b09af18a8a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/farceo/dev/feast/examples/rag/feature_repo\n", + "__init__.py \u001b[1m\u001b[36mdata\u001b[m\u001b[m feature_store.yaml test_milvus.py\n", + "\u001b[1m\u001b[36m__pycache__\u001b[m\u001b[m example_repo.py milvus_demo.db test_workflow.py\n", + "\n", + "./__pycache__:\n", + "example_repo.cpython-311.pyc\n", + "\n", + "./data:\n", + "city_wikipedia_summaries_with_embeddings.parquet\n", + "online_store.db\n", + "registry.db\n" + ] + } + ], + "source": [ + "%cd feature_repo/\n", + "!ls -R" + ] + }, + { + "cell_type": "markdown", + "id": "d14a8073-5030-4d35-9c96-f5360aeaf39f", + "metadata": {}, + "source": [ + "### Step 2b: Inspecting the project configuration\n", + "Let's inspect the setup of the project in `feature_store.yaml`. \n", + "\n", + "The key line defining the overall architecture of the feature store is the **provider**. \n", + "\n", + "The provider value sets default offline and online stores. \n", + "* The offline store provides the compute layer to process historical data (for generating training data & feature \n", + " values for serving). \n", + "* The online store is a low latency store of the latest feature values (for powering real-time inference).\n", + "\n", + "Valid values for `provider` in `feature_store.yaml` are:\n", + "\n", + "* local: use file source with Milvus Lite\n", + "* gcp: use BigQuery/Snowflake with Google Cloud Datastore/Redis\n", + "* aws: use Redshift/Snowflake with DynamoDB/Redis\n", + "\n", + "Note that there are many other offline / online stores Feast works with, including Azure, Hive, Trino, and PostgreSQL via community plugins. See https://docs.feast.dev/roadmap for all supported connectors.\n", + "\n", + "A custom setup can also be made by following [Customizing Feast](https://docs.feast.dev/v/master/how-to-guides/customizing-feast)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "14c830ef-f5a4-4867-ad5c-87e709df7057", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[94mproject\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mrag\u001b[37m\u001b[39;49;00m\n", + "\u001b[94mprovider\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mlocal\u001b[37m\u001b[39;49;00m\n", + "\u001b[94mregistry\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mdata/registry.db\u001b[37m\u001b[39;49;00m\n", + "\u001b[94monline_store\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94mtype\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mmilvus\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94mpath\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mdata/online_store.db\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94mvector_enabled\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mtrue\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94membedding_dim\u001b[39;49;00m:\u001b[37m \u001b[39;49;00m384\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94mindex_type\u001b[39;49;00m:\u001b[37m \u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[33mIVF_FLAT\u001b[39;49;00m\u001b[33m\"\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m\u001b[39;49;00m\n", + "\u001b[94moffline_store\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94mtype\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mfile\u001b[37m\u001b[39;49;00m\n", + "\u001b[94mentity_key_serialization_version\u001b[39;49;00m:\u001b[37m \u001b[39;49;00m3\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m# By default, no_auth for authentication and authorization, other possible values kubernetes and oidc. Refer the documentation for more details.\u001b[39;49;00m\u001b[37m\u001b[39;49;00m\n", + "\u001b[94mauth\u001b[39;49;00m:\u001b[37m\u001b[39;49;00m\n", + "\u001b[37m \u001b[39;49;00m\u001b[94mtype\u001b[39;49;00m:\u001b[37m \u001b[39;49;00mno_auth\u001b[37m\u001b[39;49;00m\n" + ] + } + ], + "source": [ + "!pygmentize feature_store.yaml" + ] + }, + { + "cell_type": "markdown", + "id": "5ce80d1a-05d3-434d-bd1e-1ade8abd1f9f", + "metadata": {}, + "source": [ + "### Inspecting the raw data\n", + "\n", + "The raw feature data we have in this demo is stored in a local parquet file. The dataset Wikipedia summaries of diferent cities." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "788a27ff-16a4-4b23-8c1c-ba27fd918aa5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "embedding length = 384\n" + ] + } + ], + "source": [ + "import pandas as pd \n", + "\n", + "df = pd.read_parquet(\"./data/city_wikipedia_summaries_with_embeddings.parquet\")\n", + "df['vector'] = df['vector'].apply(lambda x: x.tolist())\n", + "embedding_length = len(df['vector'][0])\n", + "print(f'embedding length = {embedding_length}')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e433178c-51e8-49a7-884c-c9573082ad6d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
iditem_idevent_timestampstatewiki_summarysentence_chunksvector
0002025-01-09 13:36:59.280589New York, New YorkNew York, often called New York City or simply...New York, often called New York City or simply...[0.1465730518102646, -0.07317650318145752, 0.0...
1112025-01-09 13:36:59.280589New York, New YorkNew York, often called New York City or simply...The city comprises five boroughs, each of whic...[0.05218901485204697, -0.08449874818325043, 0....
2222025-01-09 13:36:59.280589New York, New YorkNew York, often called New York City or simply...New York is a global center of finance and com...[0.06769222766160965, -0.07371102273464203, -0...
3332025-01-09 13:36:59.280589New York, New YorkNew York, often called New York City or simply...New York City is the epicenter of the world's ...[0.12095861881971359, -0.04279915615916252, 0....
4442025-01-09 13:36:59.280589New York, New YorkNew York, often called New York City or simply...With an estimated population in 2022 of 8,335,...[0.17943550646305084, -0.09458263963460922, 0....
\n", + "
" + ], + "text/plain": [ + " id item_id event_timestamp state \\\n", + "0 0 0 2025-01-09 13:36:59.280589 New York, New York \n", + "1 1 1 2025-01-09 13:36:59.280589 New York, New York \n", + "2 2 2 2025-01-09 13:36:59.280589 New York, New York \n", + "3 3 3 2025-01-09 13:36:59.280589 New York, New York \n", + "4 4 4 2025-01-09 13:36:59.280589 New York, New York \n", + "\n", + " wiki_summary \\\n", + "0 New York, often called New York City or simply... \n", + "1 New York, often called New York City or simply... \n", + "2 New York, often called New York City or simply... \n", + "3 New York, often called New York City or simply... \n", + "4 New York, often called New York City or simply... \n", + "\n", + " sentence_chunks \\\n", + "0 New York, often called New York City or simply... \n", + "1 The city comprises five boroughs, each of whic... \n", + "2 New York is a global center of finance and com... \n", + "3 New York City is the epicenter of the world's ... \n", + "4 With an estimated population in 2022 of 8,335,... \n", + "\n", + " vector \n", + "0 [0.1465730518102646, -0.07317650318145752, 0.0... \n", + "1 [0.05218901485204697, -0.08449874818325043, 0.... \n", + "2 [0.06769222766160965, -0.07371102273464203, -0... \n", + "3 [0.12095861881971359, -0.04279915615916252, 0.... \n", + "4 [0.17943550646305084, -0.09458263963460922, 0.... " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display\n", + "\n", + "display(df.head())" + ] + }, + { + "cell_type": "markdown", + "id": "ec07d38d-d0ff-4dc3-b041-3bf24de9e7e3", + "metadata": {}, + "source": [ + "## Step 3: Register feature definitions and deploy your feature store\n", + "\n", + "`feast apply` scans python files in the current directory for feature/entity definitions and deploys infrastructure according to `feature_store.yaml`." + ] + }, + { + "cell_type": "markdown", + "id": "79409ca9-7552-4aa5-b95b-29f836a0d3a5", + "metadata": {}, + "source": [ + "### Step 3a: Inspecting feature definitions\n", + "Let's inspect what `example_repo.py` looks like:\n", + "\n", + "```python\n", + "from datetime import timedelta\n", + "\n", + "from feast import (\n", + " FeatureView,\n", + " Field,\n", + " FileSource,\n", + ")\n", + "from feast.data_format import ParquetFormat\n", + "from feast.types import Float32, Array, String, ValueType\n", + "from feast import Entity\n", + "\n", + "item = Entity(\n", + " name=\"item_id\",\n", + " description=\"Item ID\",\n", + " value_type=ValueType.INT64,\n", + ")\n", + "\n", + "parquet_file_path = \"./data/city_wikipedia_summaries_with_embeddings.parquet\"\n", + "\n", + "source = FileSource(\n", + " file_format=ParquetFormat(),\n", + " path=parquet_file_path,\n", + " timestamp_field=\"event_timestamp\",\n", + ")\n", + "\n", + "city_embeddings_feature_view = FeatureView(\n", + " name=\"city_embeddings\",\n", + " entities=[item],\n", + " schema=[\n", + " Field(\n", + " name=\"vector\",\n", + " dtype=Array(Float32),\n", + " vector_index=True,\n", + " vector_search_metric=\"COSINE\",\n", + " ),\n", + " Field(name=\"state\", dtype=String),\n", + " Field(name=\"sentence_chunks\", dtype=String),\n", + " Field(name=\"wiki_summary\", dtype=String),\n", + " ],\n", + " source=source,\n", + " ttl=timedelta(hours=2),\n", + ")\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "76634929-c84a-4301-93d3-88292335bde0", + "metadata": {}, + "source": [ + "### Step 3b: Applying feature definitions\n", + "Now we run `feast apply` to register the feature views and entities defined in `example_repo.py`, and sets up SQLite online store tables. Note that we had previously specified SQLite as the online store in `feature_store.yaml` by specifying a `local` provider." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "837e1530-e863-4e5f-b206-b6b4b3ca2aa2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/farceo/dev/feast/sdk/python/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/pymilvus/client/__init__.py:6: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html\n", + " from pkg_resources import DistributionNotFound, get_distribution\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/pkg_resources/__init__.py:3142: DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('sphinxcontrib')`.\n", + "Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages\n", + " declare_namespace(pkg)\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/environs/__init__.py:58: DeprecationWarning: The '__version_info__' attribute is deprecated and will be removed in in a future version. Use feature detection or 'packaging.Version(importlib.metadata.version(\"marshmallow\")).release' instead.\n", + " _SUPPORTS_LOAD_DEFAULT = ma.__version_info__ >= (3, 13)\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"MilvusOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\n", + " warnings.warn(\n", + "No project found in the repository. Using project name rag defined in feature_store.yaml\n", + "Applying changes for project rag\n", + "/Users/farceo/dev/feast/sdk/python/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity 'item_id'.\n", + " entity = cls(\n", + "/Users/farceo/dev/feast/sdk/python/feast/entity.py:173: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " entity = cls(\n", + "Connecting to Milvus in local mode using /Users/farceo/dev/feast/examples/rag/feature_repo/data/online_store.db\n", + "01/29/2025 05:11:55 PM pymilvus.milvus_client.milvus_client DEBUG: Created new connection using: 9fe4c5dfbe434f1babbf9f2a0970fb87\n", + "Deploying infrastructure for \u001b[1m\u001b[32mcity_embeddings\u001b[0m\n" + ] + } + ], + "source": [ + "! feast apply" + ] + }, + { + "cell_type": "markdown", + "id": "ad7654cc-865c-4bb4-8c0f-d3086c5d9f7e", + "metadata": {}, + "source": [ + "## Step 5: Load features into your online store" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "34ded931-3de0-4951-aead-1e8ca1679cbe", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/farceo/dev/feast/sdk/python/feast/feature_view.py:48: DeprecationWarning: Entity value_type will be mandatory in the next release. Please specify a value_type for entity '__dummy'.\n", + " DUMMY_ENTITY = Entity(\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/pymilvus/client/__init__.py:6: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html\n", + " from pkg_resources import DistributionNotFound, get_distribution\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/pkg_resources/__init__.py:3142: DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('sphinxcontrib')`.\n", + "Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages\n", + " declare_namespace(pkg)\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/environs/__init__.py:58: DeprecationWarning: The '__version_info__' attribute is deprecated and will be removed in in a future version. Use feature detection or 'packaging.Version(importlib.metadata.version(\"marshmallow\")).release' instead.\n", + " _SUPPORTS_LOAD_DEFAULT = ma.__version_info__ >= (3, 13)\n", + "/Users/farceo/dev/feast/.venv/lib/python3.11/site-packages/pydantic/_internal/_fields.py:192: UserWarning: Field name \"vector_enabled\" in \"MilvusOnlineStoreConfig\" shadows an attribute in parent \"VectorStoreConfig\"\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "from datetime import datetime\n", + "from feast import FeatureStore\n", + "\n", + "store = FeatureStore(repo_path=\".\")" + ] + }, + { + "cell_type": "markdown", + "id": "4c784d77-e96c-455c-9f1f-9183bab58d72", + "metadata": {}, + "source": [ + "### Step 5a: Using `materialize_incremental`\n", + "\n", + "We now serialize the latest values of features since the beginning of time to prepare for serving. Note, `materialize_incremental` serializes all new features since the last `materialize` call, or since the time provided minus the `ttl` timedelta. In this case, this will be `CURRENT_TIME - 1 day` (`ttl` was set on the `FeatureView` instances in [feature_repo/feature_repo/example_repo.py](feature_repo/feature_repo/example_repo.py)). \n", + "\n", + "```bash\n", + "CURRENT_TIME=$(date -u +\"%Y-%m-%dT%H:%M:%S\")\n", + "feast materialize-incremental $CURRENT_TIME\n", + "```\n", + "\n", + "An alternative to using the CLI command is to use Python:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a2655725-5cc4-4f07-ade4-dc5e705eed05", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connecting to Milvus in local mode using data/online_store.db\n" + ] + } + ], + "source": [ + "store.write_to_online_store(feature_view_name='city_embeddings', df=df)" + ] + }, + { + "cell_type": "markdown", + "id": "b836e5b1-1fe2-4e9d-8c9a-bdc91da8254e", + "metadata": {}, + "source": [ + "### Step 5b: Inspect materialized features\n", + "\n", + "Note that now there are `online_store.db` and `registry.db`, which store the materialized features and schema information, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1307b1aa-fecf-4adf-aafc-f65d89ca735c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
item_id_pkcreated_tsevent_tsitem_idsentence_chunksstatevectorwiki_summary
00100000002000000070000006974656d5f696404000000...017364478192805890New York, often called New York City or simply...New York, New York0.146573New York, often called New York City or simply...
10100000002000000070000006974656d5f696404000000...017364478192805890New York, often called New York City or simply...New York, New York-0.073177New York, often called New York City or simply...
20100000002000000070000006974656d5f696404000000...017364478192805890New York, often called New York City or simply...New York, New York0.052114New York, often called New York City or simply...
30100000002000000070000006974656d5f696404000000...017364478192805890New York, often called New York City or simply...New York, New York0.033187New York, often called New York City or simply...
40100000002000000070000006974656d5f696404000000...017364478192805890New York, often called New York City or simply...New York, New York0.012013New York, often called New York City or simply...
\n", + "
" + ], + "text/plain": [ + " item_id_pk created_ts \\\n", + "0 0100000002000000070000006974656d5f696404000000... 0 \n", + "1 0100000002000000070000006974656d5f696404000000... 0 \n", + "2 0100000002000000070000006974656d5f696404000000... 0 \n", + "3 0100000002000000070000006974656d5f696404000000... 0 \n", + "4 0100000002000000070000006974656d5f696404000000... 0 \n", + "\n", + " event_ts item_id \\\n", + "0 1736447819280589 0 \n", + "1 1736447819280589 0 \n", + "2 1736447819280589 0 \n", + "3 1736447819280589 0 \n", + "4 1736447819280589 0 \n", + "\n", + " sentence_chunks state \\\n", + "0 New York, often called New York City or simply... New York, New York \n", + "1 New York, often called New York City or simply... New York, New York \n", + "2 New York, often called New York City or simply... New York, New York \n", + "3 New York, often called New York City or simply... New York, New York \n", + "4 New York, often called New York City or simply... New York, New York \n", + "\n", + " vector wiki_summary \n", + "0 0.146573 New York, often called New York City or simply... \n", + "1 -0.073177 New York, often called New York City or simply... \n", + "2 0.052114 New York, often called New York City or simply... \n", + "3 0.033187 New York, often called New York City or simply... \n", + "4 0.012013 New York, often called New York City or simply... " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pymilvus_client = store._provider._online_store._connect(store.config)\n", + "COLLECTION_NAME = pymilvus_client.list_collections()[0]\n", + "\n", + "milvus_query_result = pymilvus_client.query(\n", + " collection_name=COLLECTION_NAME,\n", + " filter=\"item_id == '0'\",\n", + ")\n", + "pd.DataFrame(milvus_query_result[0]).head()" + ] + }, + { + "cell_type": "markdown", + "id": "5fbf3921-e775-46b7-9915-d18c6592586f", + "metadata": {}, + "source": [ + "### Quick note on entity keys\n", + "Note from the above command that the online store indexes by `entity_key`. \n", + "\n", + "[Entity keys](https://docs.feast.dev/getting-started/concepts/entity#entity-key) include a list of all entities needed (e.g. all relevant primary keys) to generate the feature vector. In this case, this is a serialized version of the `driver_id`. We use this later to fetch all features for a given driver at inference time." + ] + }, + { + "cell_type": "markdown", + "id": "516f6e4a-2d37-4428-8dba-81620a65c2ad", + "metadata": {}, + "source": [ + "## Step 6: Embedding a query using PyTorch and Sentence Transformers" + ] + }, + { + "cell_type": "markdown", + "id": "66b4e67d-6f94-4532-b107-abc4c0f002f1", + "metadata": {}, + "source": [ + "During inference (e.g., during when a user submits a chat message) we need to embed the input text. This can be thought of as a feature transformation of the input data. In this example, we'll do this with a small Sentence Transformer from Hugging Face." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "62da57be-316d-46ee-b8a7-bac54a7faf55", + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn.functional as F\n", + "from feast import FeatureStore\n", + "from pymilvus import MilvusClient, DataType, FieldSchema\n", + "from transformers import AutoTokenizer, AutoModel\n", + "from example_repo import city_embeddings_feature_view, item\n", + "\n", + "TOKENIZER = \"sentence-transformers/all-MiniLM-L6-v2\"\n", + "MODEL = \"sentence-transformers/all-MiniLM-L6-v2\"\n", + "\n", + "def mean_pooling(model_output, attention_mask):\n", + " token_embeddings = model_output[\n", + " 0\n", + " ] # First element of model_output contains all token embeddings\n", + " input_mask_expanded = (\n", + " attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()\n", + " )\n", + " return torch.sum(token_embeddings * input_mask_expanded, 1) / torch.clamp(\n", + " input_mask_expanded.sum(1), min=1e-9\n", + " )\n", + "\n", + "def run_model(sentences, tokenizer, model):\n", + " encoded_input = tokenizer(\n", + " sentences, padding=True, truncation=True, return_tensors=\"pt\"\n", + " )\n", + " # Compute token embeddings\n", + " with torch.no_grad():\n", + " model_output = model(**encoded_input)\n", + "\n", + " sentence_embeddings = mean_pooling(model_output, encoded_input[\"attention_mask\"])\n", + " sentence_embeddings = F.normalize(sentence_embeddings, p=2, dim=1)\n", + " return sentence_embeddings" + ] + }, + { + "cell_type": "markdown", + "id": "67868cdf-04e9-4086-bed8-050e4902ed71", + "metadata": {}, + "source": [ + "## Step 7: Fetching real-time vectors and data for online inference" + ] + }, + { + "cell_type": "markdown", + "id": "29b9ae94-7daa-4d56-8bca-9339d09cd1ed", + "metadata": {}, + "source": [ + "At inference time, we need to use vector similarity search through the document embeddings from the online feature store using `retrieve_online_documents_v2()` while passing the embedded query. These feature vectors can then be fed into the context of the LLM." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0c76a526-35dc-4af5-bd46-d181e3a8c23a", + "metadata": {}, + "outputs": [], + "source": [ + "question = \"Which city has the largest population in New York?\"\n", + "\n", + "tokenizer = AutoTokenizer.from_pretrained(TOKENIZER)\n", + "model = AutoModel.from_pretrained(MODEL)\n", + "query_embedding = run_model(question, tokenizer, model)\n", + "query = query_embedding.detach().cpu().numpy().tolist()[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d3099708-409b-4d9e-b1d6-8ad86de6fde2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
vectoritem_idstatesentence_chunkswiki_summarydistance
0[0.15548758208751678, -0.08017724752426147, -0...0New York, New YorkNew York, often called New York City or simply...New York, often called New York City or simply...0.743023
\n", + "
" + ], + "text/plain": [ + " vector item_id \\\n", + "0 [0.15548758208751678, -0.08017724752426147, -0... 0 \n", + "\n", + " state sentence_chunks \\\n", + "0 New York, New York New York, often called New York City or simply... \n", + "\n", + " wiki_summary distance \n", + "0 New York, often called New York City or simply... 0.743023 " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display\n", + "\n", + "# Retrieve top k documents\n", + "context_data = store.retrieve_online_documents_v2(\n", + " features=[\n", + " \"city_embeddings:vector\",\n", + " \"city_embeddings:item_id\",\n", + " \"city_embeddings:state\",\n", + " \"city_embeddings:sentence_chunks\",\n", + " \"city_embeddings:wiki_summary\",\n", + " ],\n", + " query=query,\n", + " top_k=3,\n", + " distance_metric='COSINE',\n", + ").to_df()\n", + "display(context_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "0d56cf77-b09c-4ed7-b26e-3950d351953e", + "metadata": {}, + "outputs": [], + "source": [ + "def format_documents(context_df):\n", + " output_context = \"\"\n", + " unique_documents = context_df.drop_duplicates().apply(\n", + " lambda x: \"City & State = {\" + x['state'] +\"}\\nSummary = {\" + x['wiki_summary'].strip()+\"}\",\n", + " axis=1,\n", + " )\n", + " for i, document_text in enumerate(unique_documents):\n", + " output_context+= f\"****START DOCUMENT {i}****\\n{document_text.strip()}\\n****END DOCUMENT {i}****\"\n", + " return output_context" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "595adf60-54bd-4ec7-966e-5ac08f643f25", + "metadata": {}, + "outputs": [], + "source": [ + "RAG_CONTEXT = format_documents(context_data[['state', 'wiki_summary']])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "3978561a-79a0-48bb-86ca-d81293a0e618", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "****START DOCUMENT 0****\n", + "City & State = {New York, New York}\n", + "Summary = {New York, often called New York City or simply NYC, is the most populous city in the United States, located at the southern tip of New York State on one of the world's largest natural harbors. The city comprises five boroughs, each of which is coextensive with a respective county. New York is a global center of finance and commerce, culture and technology, entertainment and media, academics and scientific output, and the arts and fashion, and, as home to the headquarters of the United Nations, is an important center for international diplomacy. New York City is the epicenter of the world's principal metropolitan economy.\n", + "With an estimated population in 2022 of 8,335,897 distributed over 300.46 square miles (778.2 km2), the city is the most densely populated major city in the United States. New York has more than double the population of Los Angeles, the nation's second-most populous city. New York is the geographical and demographic center of both the Northeast megalopolis and the New York metropolitan area, the largest metropolitan area in the U.S. by both population and urban area. With more than 20.1 million people in its metropolitan statistical area and 23.5 million in its combined statistical area as of 2020, New York City is one of the world's most populous megacities. The city and its metropolitan area are the premier gateway for legal immigration to the United States. As many as 800 languages are spoken in New York, making it the most linguistically diverse city in the world. In 2021, the city was home to nearly 3.1 million residents born outside the U.S., the largest foreign-born population of any city in the world.\n", + "New York City traces its origins to Fort Amsterdam and a trading post founded on the southern tip of Manhattan Island by Dutch colonists in approximately 1624. The settlement was named New Amsterdam (Dutch: Nieuw Amsterdam) in 1626 and was chartered as a city in 1653. The city came under English control in 1664 and was temporarily renamed New York after King Charles II granted the lands to his brother, the Duke of York. before being permanently renamed New York in November 1674. New York City was the capital of the United States from 1785 until 1790. The modern city was formed by the 1898 consolidation of its five boroughs: Manhattan, Brooklyn, Queens, The Bronx, and Staten Island, and has been the largest U.S. city ever since.\n", + "Anchored by Wall Street in the Financial District of Lower Manhattan, New York City has been called both the world's premier financial and fintech center and the most economically powerful city in the world. As of 2022, the New York metropolitan area is the largest metropolitan economy in the world with a gross metropolitan product of over US$2.16 trillion. If the New York metropolitan area were its own country, it would have the tenth-largest economy in the world. The city is home to the world's two largest stock exchanges by market capitalization of their listed companies: the New York Stock Exchange and Nasdaq. New York City is an established safe haven for global investors. As of 2023, New York City is the most expensive city in the world for expatriates to live. New York City is home to the highest number of billionaires, individuals of ultra-high net worth (greater than US$30 million), and millionaires of any city in the world.}\n", + "****END DOCUMENT 0****\n" + ] + } + ], + "source": [ + "print(RAG_CONTEXT)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "09cad16f-4078-42de-80ee-2672dae5608a", + "metadata": {}, + "outputs": [], + "source": [ + "FULL_PROMPT = f\"\"\"\n", + "You are an assistant for answering questions about states. You will be provided documentation from Wikipedia. Provide a conversational answer.\n", + "If you don't know the answer, just say \"I do not know.\" Don't make up an answer.\n", + "\n", + "Here are document(s) you should use when answer the users question:\n", + "{RAG_CONTEXT}\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "7bb4a000-8ef3-4006-9c61-7d76fa865d28", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from openai import OpenAI\n", + "\n", + "client = OpenAI(\n", + " api_key=os.environ.get(\"OPENAI_API_KEY\"),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "da814147-9c78-4906-a84a-78fc88c2fc49", + "metadata": {}, + "outputs": [], + "source": [ + "response = client.chat.completions.create(\n", + " model=\"gpt-4o-mini\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": FULL_PROMPT},\n", + " {\"role\": \"user\", \"content\": question}\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "68cbd8df-af73-4dbe-97a9-f3cd89f36f3d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The largest city in New York is New York City, often referred to as NYC. It is the most populous city in the United States, with an estimated population of 8,335,897 in 2022.\n" + ] + } + ], + "source": [ + "print('\\n'.join([c.message.content for c in response.choices]))" + ] + }, + { + "cell_type": "markdown", + "id": "d4f01627-533b-49b0-9814-292360d064c6", + "metadata": {}, + "source": [ + "# End" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/infra/charts/feast-feature-server/Chart.yaml b/infra/charts/feast-feature-server/Chart.yaml index 7b8e51b3b52..82aa3de1689 100644 --- a/infra/charts/feast-feature-server/Chart.yaml +++ b/infra/charts/feast-feature-server/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: feast-feature-server description: Feast Feature Server in Go or Python type: application -version: 0.43.0 +version: 0.44.0 keywords: - machine learning - big data diff --git a/infra/charts/feast-feature-server/README.md b/infra/charts/feast-feature-server/README.md index f8554cb4dff..7e75bb34bb3 100644 --- a/infra/charts/feast-feature-server/README.md +++ b/infra/charts/feast-feature-server/README.md @@ -1,6 +1,6 @@ # Feast Python / Go Feature Server Helm Charts -Current chart version is `0.43.0` +Current chart version is `0.44.0` ## Installation @@ -40,7 +40,7 @@ See [here](https://github.com/feast-dev/feast/tree/master/examples/python-helm-d | fullnameOverride | string | `""` | | | image.pullPolicy | string | `"IfNotPresent"` | | | image.repository | string | `"feastdev/feature-server"` | Docker image for Feature Server repository | -| image.tag | string | `"0.43.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | +| image.tag | string | `"0.44.0"` | The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) | | imagePullSecrets | list | `[]` | | | livenessProbe.initialDelaySeconds | int | `30` | | | livenessProbe.periodSeconds | int | `30` | | diff --git a/infra/charts/feast-feature-server/values.yaml b/infra/charts/feast-feature-server/values.yaml index 3bbf9ec2b20..ffbb0b8df40 100644 --- a/infra/charts/feast-feature-server/values.yaml +++ b/infra/charts/feast-feature-server/values.yaml @@ -9,7 +9,7 @@ image: repository: feastdev/feature-server pullPolicy: IfNotPresent # image.tag -- The Docker image tag (can be overwritten if custom feature server deps are needed for on demand transforms) - tag: 0.43.0 + tag: 0.44.0 logLevel: "WARNING" # Set log level DEBUG, INFO, WARNING, ERROR, and CRITICAL (case-insensitive) diff --git a/infra/charts/feast/Chart.yaml b/infra/charts/feast/Chart.yaml index 58c4ce34fbb..caa5eaf2380 100644 --- a/infra/charts/feast/Chart.yaml +++ b/infra/charts/feast/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v1 description: Feature store for machine learning name: feast -version: 0.43.0 +version: 0.44.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/README.md b/infra/charts/feast/README.md index 4170541543d..768d386c18e 100644 --- a/infra/charts/feast/README.md +++ b/infra/charts/feast/README.md @@ -8,7 +8,7 @@ This repo contains Helm charts for Feast Java components that are being installe ## Chart: Feast -Feature store for machine learning Current chart version is `0.43.0` +Feature store for machine learning Current chart version is `0.44.0` ## Installation @@ -65,8 +65,8 @@ See [here](https://github.com/feast-dev/feast/tree/master/examples/java-demo) fo | Repository | Name | Version | |------------|------|---------| | https://charts.helm.sh/stable | redis | 10.5.6 | -| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.43.0 | -| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.43.0 | +| https://feast-helm-charts.storage.googleapis.com | feature-server(feature-server) | 0.44.0 | +| https://feast-helm-charts.storage.googleapis.com | transformation-service(transformation-service) | 0.44.0 | ## Values diff --git a/infra/charts/feast/charts/feature-server/Chart.yaml b/infra/charts/feast/charts/feature-server/Chart.yaml index f1468973eb2..c6d765ceecf 100644 --- a/infra/charts/feast/charts/feature-server/Chart.yaml +++ b/infra/charts/feast/charts/feature-server/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Feast Feature Server: Online feature serving service for Feast" name: feature-server -version: 0.43.0 -appVersion: v0.43.0 +version: 0.44.0 +appVersion: v0.44.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/charts/feature-server/README.md b/infra/charts/feast/charts/feature-server/README.md index 990a1154884..7a482313146 100644 --- a/infra/charts/feast/charts/feature-server/README.md +++ b/infra/charts/feast/charts/feature-server/README.md @@ -1,6 +1,6 @@ # feature-server -![Version: 0.43.0](https://img.shields.io/badge/Version-0.43.0-informational?style=flat-square) ![AppVersion: v0.43.0](https://img.shields.io/badge/AppVersion-v0.43.0-informational?style=flat-square) +![Version: 0.44.0](https://img.shields.io/badge/Version-0.44.0-informational?style=flat-square) ![AppVersion: v0.44.0](https://img.shields.io/badge/AppVersion-v0.44.0-informational?style=flat-square) Feast Feature Server: Online feature serving service for Feast @@ -17,7 +17,7 @@ Feast Feature Server: Online feature serving service for Feast | envOverrides | object | `{}` | Extra environment variables to set | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"feastdev/feature-server-java"` | Docker image for Feature Server repository | -| image.tag | string | `"0.43.0"` | Image tag | +| image.tag | string | `"0.44.0"` | Image tag | | ingress.grpc.annotations | object | `{}` | Extra annotations for the ingress | | ingress.grpc.auth.enabled | bool | `false` | Flag to enable auth | | ingress.grpc.class | string | `"nginx"` | Which ingress controller to use | diff --git a/infra/charts/feast/charts/feature-server/values.yaml b/infra/charts/feast/charts/feature-server/values.yaml index b7098ef5ac4..fd4f31505d4 100644 --- a/infra/charts/feast/charts/feature-server/values.yaml +++ b/infra/charts/feast/charts/feature-server/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image for Feature Server repository repository: feastdev/feature-server-java # image.tag -- Image tag - tag: 0.43.0 + tag: 0.44.0 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/charts/transformation-service/Chart.yaml b/infra/charts/feast/charts/transformation-service/Chart.yaml index 1c19dd8b7d6..9fa86430b5a 100644 --- a/infra/charts/feast/charts/transformation-service/Chart.yaml +++ b/infra/charts/feast/charts/transformation-service/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v1 description: "Transformation service: to compute on-demand features" name: transformation-service -version: 0.43.0 -appVersion: v0.43.0 +version: 0.44.0 +appVersion: v0.44.0 keywords: - machine learning - big data diff --git a/infra/charts/feast/charts/transformation-service/README.md b/infra/charts/feast/charts/transformation-service/README.md index 27bd8afc609..c6c58524c3c 100644 --- a/infra/charts/feast/charts/transformation-service/README.md +++ b/infra/charts/feast/charts/transformation-service/README.md @@ -1,6 +1,6 @@ # transformation-service -![Version: 0.43.0](https://img.shields.io/badge/Version-0.43.0-informational?style=flat-square) ![AppVersion: v0.43.0](https://img.shields.io/badge/AppVersion-v0.43.0-informational?style=flat-square) +![Version: 0.44.0](https://img.shields.io/badge/Version-0.44.0-informational?style=flat-square) ![AppVersion: v0.44.0](https://img.shields.io/badge/AppVersion-v0.44.0-informational?style=flat-square) Transformation service: to compute on-demand features @@ -13,7 +13,7 @@ Transformation service: to compute on-demand features | envOverrides | object | `{}` | Extra environment variables to set | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"feastdev/feature-transformation-server"` | Docker image for Transformation Server repository | -| image.tag | string | `"0.43.0"` | Image tag | +| image.tag | string | `"0.44.0"` | Image tag | | nodeSelector | object | `{}` | Node labels for pod assignment | | podLabels | object | `{}` | Labels to be added to Feast Serving pods | | replicaCount | int | `1` | Number of pods that will be created | diff --git a/infra/charts/feast/charts/transformation-service/values.yaml b/infra/charts/feast/charts/transformation-service/values.yaml index bb2cd8e4ea5..9c1f08aea3b 100644 --- a/infra/charts/feast/charts/transformation-service/values.yaml +++ b/infra/charts/feast/charts/transformation-service/values.yaml @@ -5,7 +5,7 @@ image: # image.repository -- Docker image for Transformation Server repository repository: feastdev/feature-transformation-server # image.tag -- Image tag - tag: 0.43.0 + tag: 0.44.0 # image.pullPolicy -- Image pull policy pullPolicy: IfNotPresent diff --git a/infra/charts/feast/requirements.yaml b/infra/charts/feast/requirements.yaml index 82c6351eaf5..a544d5f6606 100644 --- a/infra/charts/feast/requirements.yaml +++ b/infra/charts/feast/requirements.yaml @@ -1,12 +1,12 @@ dependencies: - name: feature-server alias: feature-server - version: 0.43.0 + version: 0.44.0 condition: feature-server.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: transformation-service alias: transformation-service - version: 0.43.0 + version: 0.44.0 condition: transformation-service.enabled repository: https://feast-helm-charts.storage.googleapis.com - name: redis diff --git a/infra/feast-helm-operator/Makefile b/infra/feast-helm-operator/Makefile index e3edf0622f5..7806e873c5e 100644 --- a/infra/feast-helm-operator/Makefile +++ b/infra/feast-helm-operator/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 0.43.0 +VERSION ?= 0.44.0 # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") diff --git a/infra/feast-helm-operator/config/manager/kustomization.yaml b/infra/feast-helm-operator/config/manager/kustomization.yaml index e4c3f23a824..6e8af293da6 100644 --- a/infra/feast-helm-operator/config/manager/kustomization.yaml +++ b/infra/feast-helm-operator/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: feastdev/feast-helm-operator - newTag: 0.43.0 + newTag: 0.44.0 diff --git a/infra/feast-operator/.gitignore b/infra/feast-operator/.gitignore index 72df211c0ca..c0c53d8e17e 100644 --- a/infra/feast-operator/.gitignore +++ b/infra/feast-operator/.gitignore @@ -27,4 +27,8 @@ go.work *~ # Installer file generated by Kustomize - skip 'dist/' directories within the Feast project except this one. -!dist/ \ No newline at end of file +!dist/ +bin +bin/ +/bin/ + diff --git a/infra/feast-operator/Dockerfile b/infra/feast-operator/Dockerfile index 9fd79d7285a..c7d4f6b696a 100644 --- a/infra/feast-operator/Dockerfile +++ b/infra/feast-operator/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM registry.access.redhat.com/ubi9/go-toolset:1.22.9 AS builder +FROM registry.access.redhat.com/ubi8/go-toolset:1.22.9 AS builder ARG TARGETOS ARG TARGETARCH @@ -22,7 +22,7 @@ COPY internal/controller/ internal/controller/ # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go -FROM registry.access.redhat.com/ubi9/ubi-micro:9.5 +FROM registry.access.redhat.com/ubi8/ubi-micro:8.10 WORKDIR / COPY --from=builder /opt/app-root/src/manager . USER 65532:65532 diff --git a/infra/feast-operator/Makefile b/infra/feast-operator/Makefile index 56f388fde65..c06c05688a4 100644 --- a/infra/feast-operator/Makefile +++ b/infra/feast-operator/Makefile @@ -3,7 +3,7 @@ # To re-generate a bundle for another specific version without changing the standard setup, you can: # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 0.43.0 +VERSION ?= 0.44.0 # CHANNELS define the bundle channels used in the bundle. # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") @@ -51,6 +51,7 @@ endif OPERATOR_SDK_VERSION ?= v1.38.0 # Image URL to use all building/pushing image targets IMG ?= $(IMAGE_TAG_BASE):$(VERSION) +FS_IMG ?= docker.io/feastdev/feature-server:$(VERSION) # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. ENVTEST_K8S_VERSION = 1.30.0 @@ -96,7 +97,7 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) rbac:roleName=manager-role crd:maxDescLen=0 webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -111,7 +112,7 @@ vet: ## Run go vet against code. go vet ./... .PHONY: test -test: build-installer fmt vet lint envtest ## Run tests. +test: build-installer vet lint envtest ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out # Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors. @@ -172,7 +173,7 @@ docker-buildx: ## Build and push docker image for the manager for cross-platform rm Dockerfile.cross .PHONY: build-installer -build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. +build-installer: manifests generate-ref kustomize related-image-fs ## Generate a consolidated YAML with CRDs and deployment. mkdir -p dist cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default > dist/install.yaml @@ -192,7 +193,7 @@ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - .PHONY: deploy -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. +deploy: manifests kustomize related-image-fs ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - @@ -203,7 +204,8 @@ undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/. ##@ Dependencies ## Location to install dependencies to -LOCALBIN ?= $(shell pwd)/bin +LOCALDIR ?= $(shell pwd) +LOCALBIN ?= $(LOCALDIR)/bin $(LOCALBIN): mkdir -p $(LOCALBIN) @@ -211,14 +213,18 @@ $(LOCALBIN): KUBECTL ?= kubectl KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +CRD_REF_DOCS ?= $(LOCALBIN)/crd-ref-docs ENVTEST ?= $(LOCALBIN)/setup-envtest GOLANGCI_LINT = $(LOCALBIN)/golangci-lint +ENVSUBST = $(LOCALBIN)/envsubst ## Tool Versions KUSTOMIZE_VERSION ?= v5.4.2 CONTROLLER_TOOLS_VERSION ?= v0.15.0 +CRD_REF_DOCS_VERSION ?= v0.1.0 ENVTEST_VERSION ?= release-0.18 GOLANGCI_LINT_VERSION ?= v1.59.1 +ENVSUBST_VERSION ?= v1.4.2 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -240,6 +246,11 @@ golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) +.PHONY: envsubst +envsubst: $(ENVSUBST) ## Download envsubst locally if necessary. +$(ENVSUBST): $(LOCALBIN) + $(call go-install-tool,$(ENVSUBST),github.com/a8m/envsubst/cmd/envsubst,$(ENVSUBST_VERSION)) + # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary (ideally with version) # $2 - package url which can be installed @@ -274,8 +285,17 @@ OPERATOR_SDK = $(shell which operator-sdk) endif endif +.PHONY: crd-ref-docs +crd-ref-docs: $(CRD_REF_DOCS) ## Download crd-ref-docs locally if necessary. +$(CRD_REF_DOCS): $(LOCALBIN) + $(call go-install-tool,$(CRD_REF_DOCS),github.com/elastic/crd-ref-docs,$(CRD_REF_DOCS_VERSION)) + +.PHONY: generate-ref +generate-ref: generate fmt crd-ref-docs + $(CRD_REF_DOCS) --log-level=WARN --config=$(LOCALDIR)/docs/crd-ref-templates/config.yaml --source-path=$(LOCALDIR)/api/v1alpha1 --renderer=markdown --templates-dir=$(LOCALDIR)/docs/crd-ref-templates/markdown --output-path=$(LOCALDIR)/docs/api/markdown/ref.md + .PHONY: bundle -bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files. +bundle: manifests kustomize related-image-fs operator-sdk ## Generate bundle manifests and metadata, then validate generated files. $(OPERATOR_SDK) generate kustomize manifests -q cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) @@ -329,3 +349,7 @@ catalog-build: opm ## Build a catalog image. .PHONY: catalog-push catalog-push: ## Push a catalog image. $(MAKE) docker-push IMG=$(CATALOG_IMG) + +.PHONY: related-image-fs +related-image-fs: envsubst + FS_IMG=$(FS_IMG) $(ENVSUBST) < config/default/related_image_fs_patch.tmpl > config/default/related_image_fs_patch.yaml diff --git a/infra/feast-operator/README.md b/infra/feast-operator/README.md index 9d4b51f37f4..b4c3ed6565b 100644 --- a/infra/feast-operator/README.md +++ b/infra/feast-operator/README.md @@ -1,6 +1,8 @@ # Feast Operator This is a K8s Operator that can be used to deploy and manage **Feast**, an open source feature store for machine learning. +### **[FeatureStore CR API Reference](docs/api/markdown/ref.md)** + ## Getting Started ### Prerequisites @@ -153,6 +155,3 @@ make test-e2e # delete cluster once you are done. kind delete cluster ``` - - - diff --git a/infra/feast-operator/api/feastversion/version.go b/infra/feast-operator/api/feastversion/version.go index 50fddbada8e..5c9a3683d3b 100644 --- a/infra/feast-operator/api/feastversion/version.go +++ b/infra/feast-operator/api/feastversion/version.go @@ -17,4 +17,4 @@ limitations under the License. package feastversion // Feast release version -const FeastVersion = "0.43.0" +const FeastVersion = "0.44.0" diff --git a/infra/feast-operator/api/v1alpha1/featurestore_types.go b/infra/feast-operator/api/v1alpha1/featurestore_types.go index b44f776dab4..83ba2067741 100644 --- a/infra/feast-operator/api/v1alpha1/featurestore_types.go +++ b/infra/feast-operator/api/v1alpha1/featurestore_types.go @@ -33,6 +33,7 @@ const ( OfflineStoreReadyType = "OfflineStore" OnlineStoreReadyType = "OnlineStore" RegistryReadyType = "Registry" + UIReadyType = "UI" ReadyType = "FeatureStore" AuthorizationReadyType = "Authorization" @@ -42,6 +43,7 @@ const ( OfflineStoreFailedReason = "OfflineStoreDeploymentFailed" OnlineStoreFailedReason = "OnlineStoreDeploymentFailed" RegistryFailedReason = "RegistryDeploymentFailed" + UIFailedReason = "UIDeploymentFailed" ClientFailedReason = "ClientDeploymentFailed" KubernetesAuthzFailedReason = "KubernetesAuthorizationDeploymentFailed" @@ -50,6 +52,7 @@ const ( OfflineStoreReadyMessage = "Offline Store installation complete" OnlineStoreReadyMessage = "Online Store installation complete" RegistryReadyMessage = "Registry installation complete" + UIReadyMessage = "UI installation complete" ClientReadyMessage = "Client installation complete" KubernetesAuthzReadyMessage = "Kubernetes authorization installation complete" @@ -71,6 +74,7 @@ type FeatureStoreServices struct { OfflineStore *OfflineStore `json:"offlineStore,omitempty"` OnlineStore *OnlineStore `json:"onlineStore,omitempty"` Registry *Registry `json:"registry,omitempty"` + UI *ServerConfigs `json:"ui,omitempty"` DeploymentStrategy *appsv1.DeploymentStrategy `json:"deploymentStrategy,omitempty"` // Disable the 'feast repo initialization' initContainer DisableInitContainers bool `json:"disableInitContainers,omitempty"` @@ -78,13 +82,8 @@ type FeatureStoreServices struct { // OfflineStore configures the deployed offline store service type OfflineStore struct { - ServiceConfigs `json:",inline"` - Persistence *OfflineStorePersistence `json:"persistence,omitempty"` - TLS *TlsConfigs `json:"tls,omitempty"` - // LogLevel sets the logging level for the offline store service - // Allowed values: "debug", "info", "warning", "error", "critical". - // +kubebuilder:validation:Enum=debug;info;warning;error;critical - LogLevel string `json:"logLevel,omitempty"` + ServerConfigs `json:",inline"` + Persistence *OfflineStorePersistence `json:"persistence,omitempty"` } // OfflineStorePersistence configures the persistence settings for the offline store service @@ -131,13 +130,8 @@ var ValidOfflineStoreDBStorePersistenceTypes = []string{ // OnlineStore configures the deployed online store service type OnlineStore struct { - ServiceConfigs `json:",inline"` - Persistence *OnlineStorePersistence `json:"persistence,omitempty"` - TLS *TlsConfigs `json:"tls,omitempty"` - // LogLevel sets the logging level for the online store service - // Allowed values: "debug", "info", "warning", "error", "critical". - // +kubebuilder:validation:Enum=debug;info;warning;error;critical - LogLevel string `json:"logLevel,omitempty"` + ServerConfigs `json:",inline"` + Persistence *OnlineStorePersistence `json:"persistence,omitempty"` } // OnlineStorePersistence configures the persistence settings for the online store service @@ -187,13 +181,8 @@ var ValidOnlineStoreDBStorePersistenceTypes = []string{ // LocalRegistryConfig configures the deployed registry service type LocalRegistryConfig struct { - ServiceConfigs `json:",inline"` - Persistence *RegistryPersistence `json:"persistence,omitempty"` - TLS *TlsConfigs `json:"tls,omitempty"` - // LogLevel sets the logging level for the registry service - // Allowed values: "debug", "info", "warning", "error", "critical". - // +kubebuilder:validation:Enum=debug;info;warning;error;critical - LogLevel string `json:"logLevel,omitempty"` + ServerConfigs `json:",inline"` + Persistence *RegistryPersistence `json:"persistence,omitempty"` } // RegistryPersistence configures the persistence settings for the registry service @@ -286,19 +275,29 @@ type FeatureStoreRef struct { Namespace string `json:"namespace,omitempty"` } -// ServiceConfigs k8s container settings -type ServiceConfigs struct { - DefaultConfigs `json:",inline"` - OptionalConfigs `json:",inline"` +// ServerConfigs server-related configurations for a feast service +type ServerConfigs struct { + ContainerConfigs `json:",inline"` + TLS *TlsConfigs `json:"tls,omitempty"` + // LogLevel sets the logging level for the server + // Allowed values: "debug", "info", "warning", "error", "critical". + // +kubebuilder:validation:Enum=debug;info;warning;error;critical + LogLevel string `json:"logLevel,omitempty"` +} + +// ContainerConfigs k8s container settings for the server +type ContainerConfigs struct { + DefaultCtrConfigs `json:",inline"` + OptionalCtrConfigs `json:",inline"` } -// DefaultConfigs k8s container settings that are applied by default -type DefaultConfigs struct { +// DefaultCtrConfigs k8s container settings that are applied by default +type DefaultCtrConfigs struct { Image *string `json:"image,omitempty"` } -// OptionalConfigs k8s container settings that are optional -type OptionalConfigs struct { +// OptionalCtrConfigs k8s container settings that are optional +type OptionalCtrConfigs struct { Env *[]corev1.EnvVar `json:"env,omitempty"` EnvFrom *[]corev1.EnvFromSource `json:"envFrom,omitempty"` ImagePullPolicy *corev1.PullPolicy `json:"imagePullPolicy,omitempty"` @@ -386,6 +385,7 @@ type ServiceHostnames struct { OfflineStore string `json:"offlineStore,omitempty"` OnlineStore string `json:"onlineStore,omitempty"` Registry string `json:"registry,omitempty"` + UI string `json:"ui,omitempty"` } // +kubebuilder:object:root=true diff --git a/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go b/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go index 6fbd44deb70..cb5a4eb34ba 100644 --- a/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/infra/feast-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -53,7 +53,24 @@ func (in *AuthzConfig) DeepCopy() *AuthzConfig { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DefaultConfigs) DeepCopyInto(out *DefaultConfigs) { +func (in *ContainerConfigs) DeepCopyInto(out *ContainerConfigs) { + *out = *in + in.DefaultCtrConfigs.DeepCopyInto(&out.DefaultCtrConfigs) + in.OptionalCtrConfigs.DeepCopyInto(&out.OptionalCtrConfigs) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ContainerConfigs. +func (in *ContainerConfigs) DeepCopy() *ContainerConfigs { + if in == nil { + return nil + } + out := new(ContainerConfigs) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DefaultCtrConfigs) DeepCopyInto(out *DefaultCtrConfigs) { *out = *in if in.Image != nil { in, out := &in.Image, &out.Image @@ -62,12 +79,12 @@ func (in *DefaultConfigs) DeepCopyInto(out *DefaultConfigs) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultConfigs. -func (in *DefaultConfigs) DeepCopy() *DefaultConfigs { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCtrConfigs. +func (in *DefaultCtrConfigs) DeepCopy() *DefaultCtrConfigs { if in == nil { return nil } - out := new(DefaultConfigs) + out := new(DefaultCtrConfigs) in.DeepCopyInto(out) return out } @@ -164,6 +181,11 @@ func (in *FeatureStoreServices) DeepCopyInto(out *FeatureStoreServices) { *out = new(Registry) (*in).DeepCopyInto(*out) } + if in.UI != nil { + in, out := &in.UI, &out.UI + *out = new(ServerConfigs) + (*in).DeepCopyInto(*out) + } if in.DeploymentStrategy != nil { in, out := &in.DeploymentStrategy, &out.DeploymentStrategy *out = new(v1.DeploymentStrategy) @@ -253,17 +275,12 @@ func (in *KubernetesAuthz) DeepCopy() *KubernetesAuthz { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalRegistryConfig) DeepCopyInto(out *LocalRegistryConfig) { *out = *in - in.ServiceConfigs.DeepCopyInto(&out.ServiceConfigs) + in.ServerConfigs.DeepCopyInto(&out.ServerConfigs) if in.Persistence != nil { in, out := &in.Persistence, &out.Persistence *out = new(RegistryPersistence) (*in).DeepCopyInto(*out) } - if in.TLS != nil { - in, out := &in.TLS, &out.TLS - *out = new(TlsConfigs) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalRegistryConfig. @@ -279,17 +296,12 @@ func (in *LocalRegistryConfig) DeepCopy() *LocalRegistryConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OfflineStore) DeepCopyInto(out *OfflineStore) { *out = *in - in.ServiceConfigs.DeepCopyInto(&out.ServiceConfigs) + in.ServerConfigs.DeepCopyInto(&out.ServerConfigs) if in.Persistence != nil { in, out := &in.Persistence, &out.Persistence *out = new(OfflineStorePersistence) (*in).DeepCopyInto(*out) } - if in.TLS != nil { - in, out := &in.TLS, &out.TLS - *out = new(TlsConfigs) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OfflineStore. @@ -382,17 +394,12 @@ func (in *OidcAuthz) DeepCopy() *OidcAuthz { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OnlineStore) DeepCopyInto(out *OnlineStore) { *out = *in - in.ServiceConfigs.DeepCopyInto(&out.ServiceConfigs) + in.ServerConfigs.DeepCopyInto(&out.ServerConfigs) if in.Persistence != nil { in, out := &in.Persistence, &out.Persistence *out = new(OnlineStorePersistence) (*in).DeepCopyInto(*out) } - if in.TLS != nil { - in, out := &in.TLS, &out.TLS - *out = new(TlsConfigs) - (*in).DeepCopyInto(*out) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnlineStore. @@ -467,7 +474,7 @@ func (in *OnlineStorePersistence) DeepCopy() *OnlineStorePersistence { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OptionalConfigs) DeepCopyInto(out *OptionalConfigs) { +func (in *OptionalCtrConfigs) DeepCopyInto(out *OptionalCtrConfigs) { *out = *in if in.Env != nil { in, out := &in.Env, &out.Env @@ -503,12 +510,12 @@ func (in *OptionalConfigs) DeepCopyInto(out *OptionalConfigs) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionalConfigs. -func (in *OptionalConfigs) DeepCopy() *OptionalConfigs { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionalCtrConfigs. +func (in *OptionalCtrConfigs) DeepCopy() *OptionalCtrConfigs { if in == nil { return nil } - out := new(OptionalConfigs) + out := new(OptionalCtrConfigs) in.DeepCopyInto(out) return out } @@ -707,18 +714,22 @@ func (in *SecretKeyNames) DeepCopy() *SecretKeyNames { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceConfigs) DeepCopyInto(out *ServiceConfigs) { +func (in *ServerConfigs) DeepCopyInto(out *ServerConfigs) { *out = *in - in.DefaultConfigs.DeepCopyInto(&out.DefaultConfigs) - in.OptionalConfigs.DeepCopyInto(&out.OptionalConfigs) + in.ContainerConfigs.DeepCopyInto(&out.ContainerConfigs) + if in.TLS != nil { + in, out := &in.TLS, &out.TLS + *out = new(TlsConfigs) + (*in).DeepCopyInto(*out) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceConfigs. -func (in *ServiceConfigs) DeepCopy() *ServiceConfigs { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerConfigs. +func (in *ServerConfigs) DeepCopy() *ServerConfigs { if in == nil { return nil } - out := new(ServiceConfigs) + out := new(ServerConfigs) in.DeepCopyInto(out) return out } diff --git a/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml b/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml index bfd32b5a830..918fda95051 100644 --- a/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml +++ b/infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml @@ -13,13 +13,31 @@ metadata: "spec": { "feastProject": "my_project" } + }, + { + "apiVersion": "feast.dev/v1alpha1", + "kind": "FeatureStore", + "metadata": { + "name": "sample-all-services" + }, + "spec": { + "feastProject": "my_project", + "services": { + "offlineStore": {}, + "onlineStore": {}, + "registry": { + "local": {} + }, + "ui": {} + } + } } ] capabilities: Basic Install - createdAt: "2025-01-16T22:15:56Z" + createdAt: "2025-02-04T19:13:23Z" operators.operatorframework.io/builder: operator-sdk-v1.38.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 - name: feast-operator.v0.42.0 + name: feast-operator.v0.44.0 namespace: placeholder spec: apiservicedefinitions: {} @@ -109,6 +127,17 @@ spec: - list - update - watch + - apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - create + - delete + - get + - list + - update + - watch - apiGroups: - authentication.k8s.io resources: @@ -150,10 +179,8 @@ spec: - /manager env: - name: RELATED_IMAGE_FEATURE_SERVER - value: docker.io/feastdev/feature-server:0.42.0 - - name: RELATED_IMAGE_GRPC_CURL - value: docker.io/fullstorydev/grpcurl:v1.9.1-alpine - image: feastdev/feast-operator:0.42.0 + value: docker.io/feastdev/feature-server:0.44.0 + image: feastdev/feast-operator:0.44.0 livenessProbe: httpGet: path: /healthz @@ -243,8 +270,6 @@ spec: name: Feast Community url: https://lf-aidata.atlassian.net/wiki/spaces/FEAST/ relatedImages: - - image: docker.io/feastdev/feature-server:0.42.0 + - image: docker.io/feastdev/feature-server:0.44.0 name: feature-server - - image: docker.io/fullstorydev/grpcurl:v1.9.1-alpine - name: grpc-curl - version: 0.42.0 + version: 0.44.0 diff --git a/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml b/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml index ff1a77936f2..9c225243c64 100644 --- a/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml +++ b/infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml @@ -26,69 +26,30 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: FeatureStore is the Schema for the featurestores API properties: apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: - description: FeatureStoreSpec defines the desired state of FeatureStore properties: authz: - description: AuthzConfig defines the authorization settings for the - deployed Feast services. properties: kubernetes: - description: |- - KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/ properties: roles: - description: |- - The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. - Roles are managed by the operator and created with an empty list of rules. - See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission - The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. - This configuration option is only providing a way to automate this procedure. - Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. items: type: string type: array type: object oidc: - description: |- - OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. - https://auth0.com/docs/authenticate/protocols/openid-connect-protocol properties: secretRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -100,186 +61,88 @@ spec: - message: One selection required between kubernetes or oidc. rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: - description: FeastProject is the Feast project id. This can be any - alphanumeric string with underscores, but it cannot start with an - underscore. Required. pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast services. - An ephemeral registry is deployed by default. properties: deploymentStrategy: - description: DeploymentStrategy describes how to replace existing - pods with new ones. properties: rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - --- - TODO: Update this to follow our convention for oneOf, whatever we decide it - to be. properties: maxSurge: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. x-kubernetes-int-or-string: true maxUnavailable: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. x-kubernetes-int-or-string: true type: object type: - description: Type of deployment. Can be "Recreate" or "RollingUpdate". - Default is RollingUpdate. type: string type: object disableInitContainers: - description: Disable the 'feast repo initialization' initContainer type: boolean offlineStore: - description: OfflineStore configures the deployed offline store - service properties: env: items: - description: EnvVar represents an environment variable present - in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap or - its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select in - the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for volumes, - optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format of - the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the pod's - namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret or its - key must be defined type: boolean required: - key @@ -292,50 +155,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of a set - of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must be - defined type: boolean type: object x-kubernetes-map-type: atomic @@ -344,13 +181,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to - pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the offline store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -359,34 +191,18 @@ spec: - critical type: string persistence: - description: OfflineStorePersistence configures the persistence - settings for the offline store service properties: file: - description: OfflineStoreFilePersistence configures the - file-based persistence for the offline store service properties: pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent volume - access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -395,9 +211,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -406,40 +219,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -462,28 +255,13 @@ spec: type: string type: object store: - description: OfflineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the secret - key. "registry_type" & "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -508,28 +286,11 @@ spec: - message: One selection required between file or store. rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute resource - requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -545,9 +306,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -556,48 +314,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured by - default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. useful - in an openshift cluster, for example, where TLS is configured - by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key names - for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where the - TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -608,122 +341,63 @@ spec: : true' type: object onlineStore: - description: OnlineStore configures the deployed online store - service properties: env: items: - description: EnvVar represents an environment variable present - in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap or - its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select in - the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for volumes, - optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format of - the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the pod's - namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret or its - key must be defined type: boolean required: - key @@ -736,50 +410,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of a set - of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must be - defined type: boolean type: object x-kubernetes-map-type: atomic @@ -788,13 +436,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to - pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the online store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -803,36 +446,20 @@ spec: - critical type: string persistence: - description: OnlineStorePersistence configures the persistence - settings for the online store service properties: file: - description: OnlineStoreFilePersistence configures the - file-based persistence for the offline store service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent volume - access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -841,9 +468,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -852,40 +476,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -913,28 +517,13 @@ spec: rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) : true' store: - description: OnlineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the secret - key. "registry_type" & "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -966,28 +555,11 @@ spec: - message: One selection required between file or store. rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute resource - requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -1003,9 +575,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1014,48 +583,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured by - default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. useful - in an openshift cluster, for example, where TLS is configured - by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key names - for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where the - TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1066,127 +610,65 @@ spec: : true' type: object registry: - description: Registry configures the registry service. One selection - is required. Local is the default setting. properties: local: - description: LocalRegistryConfig configures the deployed registry - service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -1199,50 +681,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -1251,13 +707,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the registry service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -1266,36 +717,20 @@ spec: - critical type: string persistence: - description: RegistryPersistence configures the persistence - settings for the registry service properties: file: - description: RegistryFilePersistence configures the - file-based persistence for the registry service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -1304,9 +739,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1315,40 +747,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1388,29 +800,13 @@ spec: rule: '(has(self.s3_additional_kwargs) && has(self.path)) ? self.path.startsWith(''s3://'') : true' store: - description: RegistryDBStorePersistence configures - the DB store persistence for the registry service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1429,29 +825,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -1467,9 +845,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1478,48 +853,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1530,51 +880,26 @@ spec: : true' type: object remote: - description: |- - RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. - Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. properties: feastRef: - description: Reference to an existing `FeatureStore` CR - in the same k8s cluster. properties: name: - description: Name of the FeatureStore type: string namespace: - description: Namespace of the FeatureStore type: string required: - name type: object hostname: - description: Host address of the remote registry service - - :, e.g. `registry..svc.cluster.local:80` type: string tls: - description: TlsRemoteRegistryConfigs configures client - TLS for a remote feast registry. in an openshift cluster, - this is configured by default when the remote feast - registry is using service serving certificates. properties: certName: - description: defines the configmap key name for the - client TLS cert. type: string configMapRef: - description: references the local k8s configmap where - the TLS cert resides properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1591,58 +916,189 @@ spec: x-kubernetes-validations: - message: One selection required. rule: '[has(self.local), has(self.remote)].exists_one(c, c)' + ui: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + logLevel: + enum: + - debug + - info + - warning + - error + - critical + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tls: + properties: + disable: + type: boolean + secretKeyNames: + properties: + tlsCrt: + type: string + tlsKey: + type: string + type: object + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' + type: object type: object required: - feastProject type: object status: - description: FeatureStoreStatus defines the observed state of FeatureStore properties: applied: - description: Shows the currently applied feast configuration, including - any pertinent defaults properties: authz: - description: AuthzConfig defines the authorization settings for - the deployed Feast services. properties: kubernetes: - description: |- - KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/ properties: roles: - description: |- - The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. - Roles are managed by the operator and created with an empty list of rules. - See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission - The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. - This configuration option is only providing a way to automate this procedure. - Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. items: type: string type: array type: object oidc: - description: |- - OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. - https://auth0.com/docs/authenticate/protocols/openid-connect-protocol properties: secretRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1655,187 +1111,88 @@ spec: rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: - description: FeastProject is the Feast project id. This can be - any alphanumeric string with underscores, but it cannot start - with an underscore. Required. pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast services. - An ephemeral registry is deployed by default. properties: deploymentStrategy: - description: DeploymentStrategy describes how to replace existing - pods with new ones. properties: rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - --- - TODO: Update this to follow our convention for oneOf, whatever we decide it - to be. properties: maxSurge: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. x-kubernetes-int-or-string: true maxUnavailable: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. x-kubernetes-int-or-string: true type: object type: - description: Type of deployment. Can be "Recreate" or - "RollingUpdate". Default is RollingUpdate. type: string type: object disableInitContainers: - description: Disable the 'feast repo initialization' initContainer type: boolean offlineStore: - description: OfflineStore configures the deployed offline - store service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -1848,50 +1205,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -1900,13 +1231,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the offline store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -1915,35 +1241,18 @@ spec: - critical type: string persistence: - description: OfflineStorePersistence configures the persistence - settings for the offline store service properties: file: - description: OfflineStoreFilePersistence configures - the file-based persistence for the offline store - service properties: pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -1952,9 +1261,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1963,40 +1269,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2019,29 +1305,13 @@ spec: type: string type: object store: - description: OfflineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2067,29 +1337,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -2105,9 +1357,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2116,48 +1365,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2168,123 +1392,63 @@ spec: : true' type: object onlineStore: - description: OnlineStore configures the deployed online store - service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -2297,50 +1461,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -2349,13 +1487,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the online store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -2364,37 +1497,20 @@ spec: - critical type: string persistence: - description: OnlineStorePersistence configures the persistence - settings for the online store service properties: file: - description: OnlineStoreFilePersistence configures - the file-based persistence for the offline store - service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -2403,9 +1519,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2414,40 +1527,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2476,29 +1569,13 @@ spec: rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) : true' store: - description: OnlineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2531,29 +1608,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -2569,9 +1628,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2580,48 +1636,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2632,130 +1663,65 @@ spec: : true' type: object registry: - description: Registry configures the registry service. One - selection is required. Local is the default setting. properties: local: - description: LocalRegistryConfig configures the deployed - registry service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the - FieldPath is written in terms of, - defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required - for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults - to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to - select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in - the pod's namespace properties: key: - description: The key of the secret to - select from. Must be a valid secret - key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -2768,50 +1734,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source - of a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - must be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend - to each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - must be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -2820,13 +1760,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the registry service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -2835,39 +1770,20 @@ spec: - critical type: string persistence: - description: RegistryPersistence configures the persistence - settings for the registry service properties: file: - description: RegistryFilePersistence configures - the file-based persistence for the registry - service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new - PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to - ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -2876,9 +1792,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2887,41 +1800,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing - field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2961,29 +1853,13 @@ spec: rule: '(has(self.s3_additional_kwargs) && has(self.path)) ? self.path.startsWith(''s3://'') : true' store: - description: RegistryDBStorePersistence configures - the DB store persistence for the registry service properties: secretKeyName: - description: By default, the selected store - "type" is used as the SecretKeyName type: string secretRef: - description: Data store parameters should - be placed as-is from the "feature_store.yaml" - under the secret key. "registry_type" & - "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3003,29 +1879,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -3041,9 +1899,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -3052,48 +1907,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for - a feast service. in an openshift cluster, this is - configured by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, - where TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret - key names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3104,51 +1934,26 @@ spec: : true' type: object remote: - description: |- - RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. - Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. properties: feastRef: - description: Reference to an existing `FeatureStore` - CR in the same k8s cluster. properties: name: - description: Name of the FeatureStore type: string namespace: - description: Namespace of the FeatureStore type: string required: - name type: object hostname: - description: Host address of the remote registry service - - :, e.g. `registry..svc.cluster.local:80` type: string tls: - description: TlsRemoteRegistryConfigs configures client - TLS for a remote feast registry. in an openshift - cluster, this is configured by default when the - remote feast registry is using service serving certificates. properties: certName: - description: defines the configmap key name for - the client TLS cert. type: string configMapRef: - description: references the local k8s configmap - where the TLS cert resides properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3166,72 +1971,197 @@ spec: - message: One selection required. rule: '[has(self.local), has(self.remote)].exists_one(c, c)' + ui: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + logLevel: + enum: + - debug + - info + - warning + - error + - critical + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tls: + properties: + disable: + type: boolean + secretKeyNames: + properties: + tlsCrt: + type: string + tlsKey: + type: string + type: object + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' + type: object type: object required: - feastProject type: object clientConfigMap: - description: ConfigMap in this namespace containing a client `feature_store.yaml` - for this feast deployment type: string conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" properties: lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ type: string status: - description: status of the condition, one of True, False, Unknown. enum: - "True" - "False" - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -3248,8 +2178,6 @@ spec: phase: type: string serviceHostnames: - description: ServiceHostnames defines the service hostnames in the - format of :, e.g. example.svc.cluster.local:80 properties: offlineStore: type: string @@ -3257,6 +2185,8 @@ spec: type: string registry: type: string + ui: + type: string type: object type: object type: object diff --git a/infra/feast-operator/cmd/main.go b/infra/feast-operator/cmd/main.go index cf7b9a76f21..82f0fd2eeca 100644 --- a/infra/feast-operator/cmd/main.go +++ b/infra/feast-operator/cmd/main.go @@ -38,6 +38,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1" + routev1 "github.com/openshift/api/route/v1" + "github.com/feast-dev/feast/infra/feast-operator/internal/controller" "github.com/feast-dev/feast/infra/feast-operator/internal/controller/services" // +kubebuilder:scaffold:imports @@ -50,7 +52,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - + utilruntime.Must(routev1.AddToScheme(scheme)) utilruntime.Must(feastdevv1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } diff --git a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml index a509caf329f..81f8ee310d2 100644 --- a/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml +++ b/infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml @@ -26,69 +26,30 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: FeatureStore is the Schema for the featurestores API properties: apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: - description: FeatureStoreSpec defines the desired state of FeatureStore properties: authz: - description: AuthzConfig defines the authorization settings for the - deployed Feast services. properties: kubernetes: - description: |- - KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/ properties: roles: - description: |- - The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. - Roles are managed by the operator and created with an empty list of rules. - See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission - The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. - This configuration option is only providing a way to automate this procedure. - Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. items: type: string type: array type: object oidc: - description: |- - OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. - https://auth0.com/docs/authenticate/protocols/openid-connect-protocol properties: secretRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -100,186 +61,88 @@ spec: - message: One selection required between kubernetes or oidc. rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: - description: FeastProject is the Feast project id. This can be any - alphanumeric string with underscores, but it cannot start with an - underscore. Required. pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast services. - An ephemeral registry is deployed by default. properties: deploymentStrategy: - description: DeploymentStrategy describes how to replace existing - pods with new ones. properties: rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - --- - TODO: Update this to follow our convention for oneOf, whatever we decide it - to be. properties: maxSurge: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. x-kubernetes-int-or-string: true maxUnavailable: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. x-kubernetes-int-or-string: true type: object type: - description: Type of deployment. Can be "Recreate" or "RollingUpdate". - Default is RollingUpdate. type: string type: object disableInitContainers: - description: Disable the 'feast repo initialization' initContainer type: boolean offlineStore: - description: OfflineStore configures the deployed offline store - service properties: env: items: - description: EnvVar represents an environment variable present - in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap or - its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select in - the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for volumes, - optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format of - the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the pod's - namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret or its - key must be defined type: boolean required: - key @@ -292,50 +155,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of a set - of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must be - defined type: boolean type: object x-kubernetes-map-type: atomic @@ -344,13 +181,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to - pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the offline store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -359,34 +191,18 @@ spec: - critical type: string persistence: - description: OfflineStorePersistence configures the persistence - settings for the offline store service properties: file: - description: OfflineStoreFilePersistence configures the - file-based persistence for the offline store service properties: pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent volume - access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -395,9 +211,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -406,40 +219,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -462,28 +255,13 @@ spec: type: string type: object store: - description: OfflineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the secret - key. "registry_type" & "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -508,28 +286,11 @@ spec: - message: One selection required between file or store. rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute resource - requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -545,9 +306,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -556,48 +314,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured by - default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. useful - in an openshift cluster, for example, where TLS is configured - by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key names - for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where the - TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -608,122 +341,63 @@ spec: : true' type: object onlineStore: - description: OnlineStore configures the deployed online store - service properties: env: items: - description: EnvVar represents an environment variable present - in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap or - its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select in - the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for volumes, - optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format of - the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the pod's - namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret or its - key must be defined type: boolean required: - key @@ -736,50 +410,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of a set - of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must be - defined type: boolean type: object x-kubernetes-map-type: atomic @@ -788,13 +436,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to - pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the online store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -803,36 +446,20 @@ spec: - critical type: string persistence: - description: OnlineStorePersistence configures the persistence - settings for the online store service properties: file: - description: OnlineStoreFilePersistence configures the - file-based persistence for the offline store service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent volume - access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -841,9 +468,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -852,40 +476,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -913,28 +517,13 @@ spec: rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) : true' store: - description: OnlineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the secret - key. "registry_type" & "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -966,28 +555,11 @@ spec: - message: One selection required between file or store. rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute resource - requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -1003,9 +575,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1014,48 +583,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured by - default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. useful - in an openshift cluster, for example, where TLS is configured - by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key names - for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where the - TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1066,127 +610,65 @@ spec: : true' type: object registry: - description: Registry configures the registry service. One selection - is required. Local is the default setting. properties: local: - description: LocalRegistryConfig configures the deployed registry - service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -1199,50 +681,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -1251,13 +707,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the registry service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -1266,36 +717,20 @@ spec: - critical type: string persistence: - description: RegistryPersistence configures the persistence - settings for the registry service properties: file: - description: RegistryFilePersistence configures the - file-based persistence for the registry service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -1304,9 +739,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1315,40 +747,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1388,29 +800,13 @@ spec: rule: '(has(self.s3_additional_kwargs) && has(self.path)) ? self.path.startsWith(''s3://'') : true' store: - description: RegistryDBStorePersistence configures - the DB store persistence for the registry service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1429,29 +825,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -1467,9 +845,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1478,48 +853,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1530,51 +880,26 @@ spec: : true' type: object remote: - description: |- - RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. - Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. properties: feastRef: - description: Reference to an existing `FeatureStore` CR - in the same k8s cluster. properties: name: - description: Name of the FeatureStore type: string namespace: - description: Namespace of the FeatureStore type: string required: - name type: object hostname: - description: Host address of the remote registry service - - :, e.g. `registry..svc.cluster.local:80` type: string tls: - description: TlsRemoteRegistryConfigs configures client - TLS for a remote feast registry. in an openshift cluster, - this is configured by default when the remote feast - registry is using service serving certificates. properties: certName: - description: defines the configmap key name for the - client TLS cert. type: string configMapRef: - description: references the local k8s configmap where - the TLS cert resides properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1591,58 +916,189 @@ spec: x-kubernetes-validations: - message: One selection required. rule: '[has(self.local), has(self.remote)].exists_one(c, c)' + ui: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + logLevel: + enum: + - debug + - info + - warning + - error + - critical + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tls: + properties: + disable: + type: boolean + secretKeyNames: + properties: + tlsCrt: + type: string + tlsKey: + type: string + type: object + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' + type: object type: object required: - feastProject type: object status: - description: FeatureStoreStatus defines the observed state of FeatureStore properties: applied: - description: Shows the currently applied feast configuration, including - any pertinent defaults properties: authz: - description: AuthzConfig defines the authorization settings for - the deployed Feast services. properties: kubernetes: - description: |- - KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/ properties: roles: - description: |- - The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. - Roles are managed by the operator and created with an empty list of rules. - See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission - The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. - This configuration option is only providing a way to automate this procedure. - Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. items: type: string type: array type: object oidc: - description: |- - OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. - https://auth0.com/docs/authenticate/protocols/openid-connect-protocol properties: secretRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1655,187 +1111,88 @@ spec: rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: - description: FeastProject is the Feast project id. This can be - any alphanumeric string with underscores, but it cannot start - with an underscore. Required. pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast services. - An ephemeral registry is deployed by default. properties: deploymentStrategy: - description: DeploymentStrategy describes how to replace existing - pods with new ones. properties: rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - --- - TODO: Update this to follow our convention for oneOf, whatever we decide it - to be. properties: maxSurge: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. x-kubernetes-int-or-string: true maxUnavailable: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. x-kubernetes-int-or-string: true type: object type: - description: Type of deployment. Can be "Recreate" or - "RollingUpdate". Default is RollingUpdate. type: string type: object disableInitContainers: - description: Disable the 'feast repo initialization' initContainer type: boolean offlineStore: - description: OfflineStore configures the deployed offline - store service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -1848,50 +1205,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -1900,13 +1231,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the offline store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -1915,35 +1241,18 @@ spec: - critical type: string persistence: - description: OfflineStorePersistence configures the persistence - settings for the offline store service properties: file: - description: OfflineStoreFilePersistence configures - the file-based persistence for the offline store - service properties: pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -1952,9 +1261,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1963,40 +1269,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2019,29 +1305,13 @@ spec: type: string type: object store: - description: OfflineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2067,29 +1337,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -2105,9 +1357,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2116,48 +1365,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2168,123 +1392,63 @@ spec: : true' type: object onlineStore: - description: OnlineStore configures the deployed online store - service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -2297,50 +1461,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -2349,13 +1487,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the online store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -2364,37 +1497,20 @@ spec: - critical type: string persistence: - description: OnlineStorePersistence configures the persistence - settings for the online store service properties: file: - description: OnlineStoreFilePersistence configures - the file-based persistence for the offline store - service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -2403,9 +1519,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2414,40 +1527,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2476,29 +1569,13 @@ spec: rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) : true' store: - description: OnlineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2531,29 +1608,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -2569,9 +1628,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2580,48 +1636,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2632,130 +1663,65 @@ spec: : true' type: object registry: - description: Registry configures the registry service. One - selection is required. Local is the default setting. properties: local: - description: LocalRegistryConfig configures the deployed - registry service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the - FieldPath is written in terms of, - defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required - for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults - to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to - select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in - the pod's namespace properties: key: - description: The key of the secret to - select from. Must be a valid secret - key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -2768,50 +1734,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source - of a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - must be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend - to each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - must be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -2820,13 +1760,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the registry service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -2835,39 +1770,20 @@ spec: - critical type: string persistence: - description: RegistryPersistence configures the persistence - settings for the registry service properties: file: - description: RegistryFilePersistence configures - the file-based persistence for the registry - service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new - PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to - ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -2876,9 +1792,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2887,41 +1800,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing - field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2961,29 +1853,13 @@ spec: rule: '(has(self.s3_additional_kwargs) && has(self.path)) ? self.path.startsWith(''s3://'') : true' store: - description: RegistryDBStorePersistence configures - the DB store persistence for the registry service properties: secretKeyName: - description: By default, the selected store - "type" is used as the SecretKeyName type: string secretRef: - description: Data store parameters should - be placed as-is from the "feature_store.yaml" - under the secret key. "registry_type" & - "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3003,29 +1879,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -3041,9 +1899,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -3052,48 +1907,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for - a feast service. in an openshift cluster, this is - configured by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, - where TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret - key names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3104,51 +1934,26 @@ spec: : true' type: object remote: - description: |- - RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. - Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. properties: feastRef: - description: Reference to an existing `FeatureStore` - CR in the same k8s cluster. properties: name: - description: Name of the FeatureStore type: string namespace: - description: Namespace of the FeatureStore type: string required: - name type: object hostname: - description: Host address of the remote registry service - - :, e.g. `registry..svc.cluster.local:80` type: string tls: - description: TlsRemoteRegistryConfigs configures client - TLS for a remote feast registry. in an openshift - cluster, this is configured by default when the - remote feast registry is using service serving certificates. properties: certName: - description: defines the configmap key name for - the client TLS cert. type: string configMapRef: - description: references the local k8s configmap - where the TLS cert resides properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3166,72 +1971,197 @@ spec: - message: One selection required. rule: '[has(self.local), has(self.remote)].exists_one(c, c)' + ui: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + logLevel: + enum: + - debug + - info + - warning + - error + - critical + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tls: + properties: + disable: + type: boolean + secretKeyNames: + properties: + tlsCrt: + type: string + tlsKey: + type: string + type: object + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' + type: object type: object required: - feastProject type: object clientConfigMap: - description: ConfigMap in this namespace containing a client `feature_store.yaml` - for this feast deployment type: string conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" properties: lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ type: string status: - description: status of the condition, one of True, False, Unknown. enum: - "True" - "False" - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -3248,8 +2178,6 @@ spec: phase: type: string serviceHostnames: - description: ServiceHostnames defines the service hostnames in the - format of :, e.g. example.svc.cluster.local:80 properties: offlineStore: type: string @@ -3257,6 +2185,8 @@ spec: type: string registry: type: string + ui: + type: string type: object type: object type: object diff --git a/infra/feast-operator/config/default/kustomization.yaml b/infra/feast-operator/config/default/kustomization.yaml index 01534ae5cc8..ca573154247 100644 --- a/infra/feast-operator/config/default/kustomization.yaml +++ b/infra/feast-operator/config/default/kustomization.yaml @@ -28,11 +28,12 @@ resources: # [METRICS] Expose the controller manager metrics service. - metrics_service.yaml -# Uncomment the patches line if you enable Metrics, and/or are using webhooks and cert-manager + patches: -- path: manager_related_images_patch.yaml +- path: related_image_fs_patch.yaml target: kind: Deployment +# Uncomment the patches line if you enable Metrics, and/or are using webhooks and cert-manager # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. # More info: https://book.kubebuilder.io/reference/metrics - path: manager_metrics_patch.yaml diff --git a/infra/feast-operator/config/default/manager_related_images_patch.yaml b/infra/feast-operator/config/default/manager_related_images_patch.yaml deleted file mode 100644 index 084de876612..00000000000 --- a/infra/feast-operator/config/default/manager_related_images_patch.yaml +++ /dev/null @@ -1,10 +0,0 @@ -- op: replace - path: "/spec/template/spec/containers/0/env/0" - value: - name: RELATED_IMAGE_FEATURE_SERVER - value: docker.io/feastdev/feature-server:0.43.0 -- op: replace - path: "/spec/template/spec/containers/0/env/1" - value: - name: RELATED_IMAGE_GRPC_CURL - value: docker.io/fullstorydev/grpcurl:v1.9.1-alpine diff --git a/infra/feast-operator/config/default/related_image_fs_patch.tmpl b/infra/feast-operator/config/default/related_image_fs_patch.tmpl new file mode 100644 index 00000000000..f3508836a86 --- /dev/null +++ b/infra/feast-operator/config/default/related_image_fs_patch.tmpl @@ -0,0 +1,5 @@ +- op: replace + path: "/spec/template/spec/containers/0/env/0" + value: + name: RELATED_IMAGE_FEATURE_SERVER + value: ${FS_IMG} diff --git a/infra/feast-operator/config/default/related_image_fs_patch.yaml b/infra/feast-operator/config/default/related_image_fs_patch.yaml new file mode 100644 index 00000000000..0c6148880b7 --- /dev/null +++ b/infra/feast-operator/config/default/related_image_fs_patch.yaml @@ -0,0 +1,5 @@ +- op: replace + path: "/spec/template/spec/containers/0/env/0" + value: + name: RELATED_IMAGE_FEATURE_SERVER + value: docker.io/feastdev/feature-server:0.44.0 diff --git a/infra/feast-operator/config/manager/kustomization.yaml b/infra/feast-operator/config/manager/kustomization.yaml index 39b762031d5..17a989c8bdd 100644 --- a/infra/feast-operator/config/manager/kustomization.yaml +++ b/infra/feast-operator/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: feastdev/feast-operator - newTag: 0.43.0 + newTag: 0.44.0 diff --git a/infra/feast-operator/config/manager/manager.yaml b/infra/feast-operator/config/manager/manager.yaml index 4259cf8a7e0..e7550f0db7f 100644 --- a/infra/feast-operator/config/manager/manager.yaml +++ b/infra/feast-operator/config/manager/manager.yaml @@ -73,8 +73,6 @@ spec: env: - name: RELATED_IMAGE_FEATURE_SERVER value: feast:latest - - name: RELATED_IMAGE_GRPC_CURL - value: grpc:latest livenessProbe: httpGet: path: /healthz diff --git a/infra/feast-operator/config/overlays/odh/kustomization.yaml b/infra/feast-operator/config/overlays/odh/kustomization.yaml index b74a0e4a4d7..508757e76fb 100644 --- a/infra/feast-operator/config/overlays/odh/kustomization.yaml +++ b/infra/feast-operator/config/overlays/odh/kustomization.yaml @@ -5,7 +5,7 @@ namespace: opendatahub resources: -- ../../default + - ../../default patches: @@ -25,11 +25,20 @@ replacements: kind: ConfigMap name: feast-operator-parameters version: v1 - fieldPath: data.odh-feast-operator-controller-image + fieldPath: data.RELATED_IMAGE_FEAST_OPERATOR targets: - select: kind: Deployment name: controller-manager fieldPaths: - spec.template.spec.containers.[name=manager].image - + - source: + kind: ConfigMap + name: feast-operator-parameters + fieldPath: data.RELATED_IMAGE_FEATURE_SERVER + targets: + - select: + kind: Deployment + name: controller-manager + fieldPaths: + - spec.template.spec.containers.[name=manager].env.[name=RELATED_IMAGE_FEATURE_SERVER].value diff --git a/infra/feast-operator/config/overlays/odh/params.env b/infra/feast-operator/config/overlays/odh/params.env index df62943fbcf..4ee6e54ee29 100644 --- a/infra/feast-operator/config/overlays/odh/params.env +++ b/infra/feast-operator/config/overlays/odh/params.env @@ -1 +1,2 @@ -odh-feast-operator-controller-image=docker.io/feastdev/feast-operator:0.43.0 +RELATED_IMAGE_FEAST_OPERATOR=docker.io/feastdev/feast-operator:0.44.0 +RELATED_IMAGE_FEATURE_SERVER=docker.io/feastdev/feature-server:0.44.0 \ No newline at end of file diff --git a/infra/feast-operator/config/rbac/role.yaml b/infra/feast-operator/config/rbac/role.yaml index 6bec442790b..7fba75c23a4 100644 --- a/infra/feast-operator/config/rbac/role.yaml +++ b/infra/feast-operator/config/rbac/role.yaml @@ -74,3 +74,14 @@ rules: - list - update - watch +- apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - create + - delete + - get + - list + - update + - watch diff --git a/infra/feast-operator/config/samples/kustomization.yaml b/infra/feast-operator/config/samples/kustomization.yaml index 4869cc7b245..35eb822651a 100644 --- a/infra/feast-operator/config/samples/kustomization.yaml +++ b/infra/feast-operator/config/samples/kustomization.yaml @@ -1,4 +1,5 @@ ## Append samples of your project ## resources: - v1alpha1_featurestore.yaml +- v1alpha1_featurestore_all_services_default.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/infra/feast-operator/config/samples/v1alpha1_featurestore_all_services_default.yaml b/infra/feast-operator/config/samples/v1alpha1_featurestore_all_services_default.yaml index 1dd156378d8..1eef0874fff 100644 --- a/infra/feast-operator/config/samples/v1alpha1_featurestore_all_services_default.yaml +++ b/infra/feast-operator/config/samples/v1alpha1_featurestore_all_services_default.yaml @@ -1,14 +1,12 @@ apiVersion: feast.dev/v1alpha1 kind: FeatureStore metadata: - name: sample-all-default + name: sample-all-services spec: feastProject: my_project services: - onlineStore: - image: 'feastdev/feature-server:0.40.0' - offlineStore: - image: 'feastdev/feature-server:0.40.0' + onlineStore: {} + offlineStore: {} + ui: {} registry: - local: - image: 'feastdev/feature-server:0.40.0' \ No newline at end of file + local: {} diff --git a/infra/feast-operator/config/samples/v1alpha1_featurestore_db_persistence.yaml b/infra/feast-operator/config/samples/v1alpha1_featurestore_db_persistence.yaml index fd6feb79f2b..eaeca893491 100644 --- a/infra/feast-operator/config/samples/v1alpha1_featurestore_db_persistence.yaml +++ b/infra/feast-operator/config/samples/v1alpha1_featurestore_db_persistence.yaml @@ -3,20 +3,32 @@ kind: Secret metadata: name: postgres-secret namespace: test + labels: + app: postgres +stringData: + POSTGRES_DB: feast + POSTGRES_USER: feast + POSTGRES_PASSWORD: feast +--- +apiVersion: v1 +kind: Secret +metadata: + name: feast-data-stores + namespace: test stringData: postgres-secret-parameters: | - path: postgresql+psycopg://postgres:mysecretpassword@127.0.0.1:55001/feast + path: postgresql+psycopg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres.test.svc.cluster.local:5432/${POSTGRES_DB} cache_ttl_seconds: 60 sqlalchemy_config_kwargs: - echo: false - pool_pre_ping: true + echo: false + pool_pre_ping: true postgres: | - host: 127.0.0.1 - port: 55001 - database: feast + host: postgres.test.svc.cluster.local + port: 5432 + database: ${POSTGRES_DB} db_schema: public - user: postgres - password: mysecretpassword + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} --- apiVersion: feast.dev/v1alpha1 kind: FeatureStore @@ -31,12 +43,18 @@ spec: store: type: postgres secretRef: - name: postgres-secret + name: feast-data-stores + envFrom: + - secretRef: + name: postgres-secret registry: local: persistence: store: type: sql secretRef: - name: postgres-secret + name: feast-data-stores secretKeyName: postgres-secret-parameters # optional, will use store.type by default as the SecretKeyName if none is specified, in this case that's "sql" + envFrom: + - secretRef: + name: postgres-secret diff --git a/infra/feast-operator/config/samples/v1alpha1_featurestore_openshift_tls_disable.yaml b/infra/feast-operator/config/samples/v1alpha1_featurestore_openshift_tls_disable.yaml new file mode 100644 index 00000000000..2389356be71 --- /dev/null +++ b/infra/feast-operator/config/samples/v1alpha1_featurestore_openshift_tls_disable.yaml @@ -0,0 +1,21 @@ +# The "tls.disable = true" setting will disable the creation of TLS for the Feast services in an OpenShift environment. +apiVersion: feast.dev/v1alpha1 +kind: FeatureStore +metadata: + name: sample-services-tls-disable +spec: + feastProject: my_project + services: + onlineStore: + tls: + disable : true + offlineStore: + tls: + disable: true + ui: + tls: + disable: true + registry: + local: + tls: + disable: true diff --git a/infra/feast-operator/config/samples/v1alpha1_featurestore_services_loglevel.yaml b/infra/feast-operator/config/samples/v1alpha1_featurestore_services_loglevel.yaml index 7ae96d44f01..1291ea7dfae 100644 --- a/infra/feast-operator/config/samples/v1alpha1_featurestore_services_loglevel.yaml +++ b/infra/feast-operator/config/samples/v1alpha1_featurestore_services_loglevel.yaml @@ -9,6 +9,8 @@ spec: logLevel: debug offlineStore: logLevel: debug + ui: + logLevel: debug registry: local: logLevel: info diff --git a/infra/feast-operator/dist/install.yaml b/infra/feast-operator/dist/install.yaml index ae9a37d8c9c..1e5493f2e69 100644 --- a/infra/feast-operator/dist/install.yaml +++ b/infra/feast-operator/dist/install.yaml @@ -34,69 +34,30 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: FeatureStore is the Schema for the featurestores API properties: apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources type: string kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds type: string metadata: type: object spec: - description: FeatureStoreSpec defines the desired state of FeatureStore properties: authz: - description: AuthzConfig defines the authorization settings for the - deployed Feast services. properties: kubernetes: - description: |- - KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/ properties: roles: - description: |- - The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. - Roles are managed by the operator and created with an empty list of rules. - See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission - The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. - This configuration option is only providing a way to automate this procedure. - Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. items: type: string type: array type: object oidc: - description: |- - OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. - https://auth0.com/docs/authenticate/protocols/openid-connect-protocol properties: secretRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -108,186 +69,88 @@ spec: - message: One selection required between kubernetes or oidc. rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: - description: FeastProject is the Feast project id. This can be any - alphanumeric string with underscores, but it cannot start with an - underscore. Required. pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast services. - An ephemeral registry is deployed by default. properties: deploymentStrategy: - description: DeploymentStrategy describes how to replace existing - pods with new ones. properties: rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - --- - TODO: Update this to follow our convention for oneOf, whatever we decide it - to be. properties: maxSurge: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. x-kubernetes-int-or-string: true maxUnavailable: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. x-kubernetes-int-or-string: true type: object type: - description: Type of deployment. Can be "Recreate" or "RollingUpdate". - Default is RollingUpdate. type: string type: object disableInitContainers: - description: Disable the 'feast repo initialization' initContainer type: boolean offlineStore: - description: OfflineStore configures the deployed offline store - service properties: env: items: - description: EnvVar represents an environment variable present - in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap or - its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select in - the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for volumes, - optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format of - the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the pod's - namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret or its - key must be defined type: boolean required: - key @@ -300,50 +163,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of a set - of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must be - defined type: boolean type: object x-kubernetes-map-type: atomic @@ -352,13 +189,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to - pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the offline store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -367,34 +199,18 @@ spec: - critical type: string persistence: - description: OfflineStorePersistence configures the persistence - settings for the offline store service properties: file: - description: OfflineStoreFilePersistence configures the - file-based persistence for the offline store service properties: pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent volume - access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -403,9 +219,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -414,40 +227,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -470,28 +263,13 @@ spec: type: string type: object store: - description: OfflineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the secret - key. "registry_type" & "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -516,28 +294,11 @@ spec: - message: One selection required between file or store. rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute resource - requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -553,9 +314,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -564,48 +322,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured by - default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. useful - in an openshift cluster, for example, where TLS is configured - by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key names - for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where the - TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -616,122 +349,63 @@ spec: : true' type: object onlineStore: - description: OnlineStore configures the deployed online store - service properties: env: items: - description: EnvVar represents an environment variable present - in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap or - its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select in - the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for volumes, - optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format of - the exposed resources, defaults to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the pod's - namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret or its - key must be defined type: boolean required: - key @@ -744,50 +418,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of a set - of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must be - defined type: boolean type: object x-kubernetes-map-type: atomic @@ -796,13 +444,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when to - pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the online store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -811,36 +454,20 @@ spec: - critical type: string persistence: - description: OnlineStorePersistence configures the persistence - settings for the online store service properties: file: - description: OnlineStoreFilePersistence configures the - file-based persistence for the offline store service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent volume - access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -849,9 +476,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -860,40 +484,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -921,28 +525,13 @@ spec: rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) : true' store: - description: OnlineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the secret - key. "registry_type" & "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -974,28 +563,11 @@ spec: - message: One selection required between file or store. rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute resource - requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -1011,9 +583,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1022,48 +591,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured by - default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. useful - in an openshift cluster, for example, where TLS is configured - by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key names - for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where the - TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1074,127 +618,65 @@ spec: : true' type: object registry: - description: Registry configures the registry service. One selection - is required. Local is the default setting. properties: local: - description: LocalRegistryConfig configures the deployed registry - service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -1207,50 +689,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -1259,13 +715,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the registry service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -1274,36 +725,20 @@ spec: - critical type: string persistence: - description: RegistryPersistence configures the persistence - settings for the registry service properties: file: - description: RegistryFilePersistence configures the - file-based persistence for the registry service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -1312,9 +747,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1323,40 +755,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1396,29 +808,13 @@ spec: rule: '(has(self.s3_additional_kwargs) && has(self.path)) ? self.path.startsWith(''s3://'') : true' store: - description: RegistryDBStorePersistence configures - the DB store persistence for the registry service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1437,29 +833,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -1475,9 +853,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1486,48 +861,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1538,51 +888,26 @@ spec: : true' type: object remote: - description: |- - RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. - Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. properties: feastRef: - description: Reference to an existing `FeatureStore` CR - in the same k8s cluster. properties: name: - description: Name of the FeatureStore type: string namespace: - description: Namespace of the FeatureStore type: string required: - name type: object hostname: - description: Host address of the remote registry service - - :, e.g. `registry..svc.cluster.local:80` type: string tls: - description: TlsRemoteRegistryConfigs configures client - TLS for a remote feast registry. in an openshift cluster, - this is configured by default when the remote feast - registry is using service serving certificates. properties: certName: - description: defines the configmap key name for the - client TLS cert. type: string configMapRef: - description: references the local k8s configmap where - the TLS cert resides properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1599,58 +924,189 @@ spec: x-kubernetes-validations: - message: One selection required. rule: '[has(self.local), has(self.remote)].exists_one(c, c)' + ui: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + logLevel: + enum: + - debug + - info + - warning + - error + - critical + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tls: + properties: + disable: + type: boolean + secretKeyNames: + properties: + tlsCrt: + type: string + tlsKey: + type: string + type: object + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' + type: object type: object required: - feastProject type: object status: - description: FeatureStoreStatus defines the observed state of FeatureStore properties: applied: - description: Shows the currently applied feast configuration, including - any pertinent defaults properties: authz: - description: AuthzConfig defines the authorization settings for - the deployed Feast services. properties: kubernetes: - description: |- - KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. - https://kubernetes.io/docs/reference/access-authn-authz/rbac/ properties: roles: - description: |- - The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. - Roles are managed by the operator and created with an empty list of rules. - See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission - The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. - This configuration option is only providing a way to automate this procedure. - Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. items: type: string type: array type: object oidc: - description: |- - OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. - https://auth0.com/docs/authenticate/protocols/openid-connect-protocol properties: secretRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -1663,187 +1119,88 @@ spec: rule: '[has(self.kubernetes), has(self.oidc)].exists_one(c, c)' feastProject: - description: FeastProject is the Feast project id. This can be - any alphanumeric string with underscores, but it cannot start - with an underscore. Required. pattern: ^[A-Za-z0-9][A-Za-z0-9_]*$ type: string services: - description: FeatureStoreServices defines the desired feast services. - An ephemeral registry is deployed by default. properties: deploymentStrategy: - description: DeploymentStrategy describes how to replace existing - pods with new ones. properties: rollingUpdate: - description: |- - Rolling update config params. Present only if DeploymentStrategyType = - RollingUpdate. - --- - TODO: Update this to follow our convention for oneOf, whatever we decide it - to be. properties: maxSurge: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be scheduled above the desired number of - pods. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - This can not be 0 if MaxUnavailable is 0. - Absolute number is calculated from percentage by rounding up. - Defaults to 25%. - Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when - the rolling update starts, such that the total number of old and new pods do not exceed - 130% of desired pods. Once old pods have been killed, - new ReplicaSet can be scaled up further, ensuring that total number of pods running - at any time during the update is at most 130% of desired pods. x-kubernetes-int-or-string: true maxUnavailable: anyOf: - type: integer - type: string - description: |- - The maximum number of pods that can be unavailable during the update. - Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). - Absolute number is calculated from percentage by rounding down. - This can not be 0 if MaxSurge is 0. - Defaults to 25%. - Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods - immediately when the rolling update starts. Once new pods are ready, old ReplicaSet - can be scaled down further, followed by scaling up the new ReplicaSet, ensuring - that the total number of pods available at all times during the update is at - least 70% of desired pods. x-kubernetes-int-or-string: true type: object type: - description: Type of deployment. Can be "Recreate" or - "RollingUpdate". Default is RollingUpdate. type: string type: object disableInitContainers: - description: Disable the 'feast repo initialization' initContainer type: boolean offlineStore: - description: OfflineStore configures the deployed offline - store service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -1856,50 +1213,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -1908,13 +1239,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the offline store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -1923,35 +1249,18 @@ spec: - critical type: string persistence: - description: OfflineStorePersistence configures the persistence - settings for the offline store service properties: file: - description: OfflineStoreFilePersistence configures - the file-based persistence for the offline store - service properties: pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -1960,9 +1269,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -1971,40 +1277,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2027,29 +1313,13 @@ spec: type: string type: object store: - description: OfflineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2075,29 +1345,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -2113,9 +1365,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2124,48 +1373,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2176,123 +1400,63 @@ spec: : true' type: object onlineStore: - description: OnlineStore configures the deployed online store - service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required for - volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults to - "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in the - pod's namespace properties: key: - description: The key of the secret to select - from. Must be a valid secret key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -2305,50 +1469,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source of - a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap must - be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend to - each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret must - be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -2357,13 +1495,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the online store service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -2372,37 +1505,20 @@ spec: - critical type: string persistence: - description: OnlineStorePersistence configures the persistence - settings for the online store service properties: file: - description: OnlineStoreFilePersistence configures - the file-based persistence for the offline store - service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -2411,9 +1527,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2422,40 +1535,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2484,29 +1577,13 @@ spec: rule: 'has(self.path) ? !(self.path.startsWith(''s3://'') || self.path.startsWith(''gs://'')) : true' store: - description: OnlineStoreDBStorePersistence configures - the DB store persistence for the offline store service properties: secretKeyName: - description: By default, the selected store "type" - is used as the SecretKeyName type: string secretRef: - description: Data store parameters should be placed - as-is from the "feature_store.yaml" under the - secret key. "registry_type" & "type" fields - should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2539,29 +1616,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -2577,9 +1636,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2588,48 +1644,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for a feast - service. in an openshift cluster, this is configured - by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, where - TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret key - names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2640,130 +1671,65 @@ spec: : true' type: object registry: - description: Registry configures the registry service. One - selection is required. Local is the default setting. properties: local: - description: LocalRegistryConfig configures the deployed - registry service properties: env: items: - description: EnvVar represents an environment variable - present in a Container. properties: name: - description: Name of the environment variable. - Must be a C_IDENTIFIER. type: string value: - description: |- - Variable references $(VAR_NAME) are expanded - using the previously defined environment variables in the container and - any service environment variables. If a variable cannot be resolved, - the reference in the input string will be unchanged. Double $$ are reduced - to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. - "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". - Escaped references will never be expanded, regardless of whether the variable - exists or not. - Defaults to "". type: string valueFrom: - description: Source for the environment variable's - value. Cannot be used if value is not empty. properties: configMapKeyRef: - description: Selects a key of a ConfigMap. properties: key: - description: The key to select. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - or its key must be defined type: boolean required: - key type: object x-kubernetes-map-type: atomic fieldRef: - description: |- - Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, - spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. properties: apiVersion: - description: Version of the schema the - FieldPath is written in terms of, - defaults to "v1". type: string fieldPath: - description: Path of the field to select - in the specified API version. type: string required: - fieldPath type: object x-kubernetes-map-type: atomic resourceFieldRef: - description: |- - Selects a resource of the container: only resources limits and requests - (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. properties: containerName: - description: 'Container name: required - for volumes, optional for env vars' type: string divisor: anyOf: - type: integer - type: string - description: Specifies the output format - of the exposed resources, defaults - to "1" pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true resource: - description: 'Required: resource to - select' type: string required: - resource type: object x-kubernetes-map-type: atomic secretKeyRef: - description: Selects a key of a secret in - the pod's namespace properties: key: - description: The key of the secret to - select from. Must be a valid secret - key. type: string name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - or its key must be defined type: boolean required: - key @@ -2776,50 +1742,24 @@ spec: type: array envFrom: items: - description: EnvFromSource represents the source - of a set of ConfigMaps properties: configMapRef: - description: The ConfigMap to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the ConfigMap - must be defined type: boolean type: object x-kubernetes-map-type: atomic prefix: - description: An optional identifier to prepend - to each key in the ConfigMap. Must be a C_IDENTIFIER. type: string secretRef: - description: The Secret to select from properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string optional: - description: Specify whether the Secret - must be defined type: boolean type: object x-kubernetes-map-type: atomic @@ -2828,13 +1768,8 @@ spec: image: type: string imagePullPolicy: - description: PullPolicy describes a policy for if/when - to pull a container image type: string logLevel: - description: |- - LogLevel sets the logging level for the registry service - Allowed values: "debug", "info", "warning", "error", "critical". enum: - debug - info @@ -2843,39 +1778,20 @@ spec: - critical type: string persistence: - description: RegistryPersistence configures the persistence - settings for the registry service properties: file: - description: RegistryFilePersistence configures - the file-based persistence for the registry - service properties: path: type: string pvc: - description: |- - PvcConfig defines the settings for a persistent file store based on PVCs. - We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. properties: create: - description: Settings for creating a new - PVC properties: accessModes: - description: AccessModes k8s persistent - volume access modes. Defaults to - ["ReadWriteOnce"]. items: type: string type: array resources: - description: |- - Resources describes the storage resource requirements for a volume. - Default requested storage size depends on the associated service: - - 10Gi for offline store - - 5Gi for online store - - 5Gi for registry properties: limits: additionalProperties: @@ -2884,9 +1800,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -2895,41 +1808,20 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object storageClassName: - description: |- - StorageClassName is the name of an existing StorageClass to which this persistent volume belongs. Empty value - means that this volume does not belong to any StorageClass and the cluster default will be used. type: string type: object x-kubernetes-validations: - message: PvcCreate is immutable rule: self == oldSelf mountPath: - description: |- - MountPath within the container at which the volume should be mounted. - Must start by "/" and cannot contain ':'. type: string ref: - description: Reference to an existing - field properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -2969,29 +1861,13 @@ spec: rule: '(has(self.s3_additional_kwargs) && has(self.path)) ? self.path.startsWith(''s3://'') : true' store: - description: RegistryDBStorePersistence configures - the DB store persistence for the registry service properties: secretKeyName: - description: By default, the selected store - "type" is used as the SecretKeyName type: string secretRef: - description: Data store parameters should - be placed as-is from the "feature_store.yaml" - under the secret key. "registry_type" & - "type" fields should be removed. properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3011,29 +1887,11 @@ spec: rule: '[has(self.file), has(self.store)].exists_one(c, c)' resources: - description: ResourceRequirements describes the compute - resource requirements. properties: claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - - This field is immutable. It can only be set for containers. items: - description: ResourceClaim references one entry - in PodSpec.ResourceClaims. properties: name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. type: string required: - name @@ -3049,9 +1907,6 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object requests: additionalProperties: @@ -3060,48 +1915,23 @@ spec: - type: string pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ type: object type: object tls: - description: TlsConfigs configures server TLS for - a feast service. in an openshift cluster, this is - configured by default using service serving certificates. properties: disable: - description: will disable TLS for the feast service. - useful in an openshift cluster, for example, - where TLS is configured by default type: boolean secretKeyNames: - description: SecretKeyNames defines the secret - key names for the TLS key and cert. properties: tlsCrt: - description: defaults to "tls.crt" type: string tlsKey: - description: defaults to "tls.key" type: string type: object secretRef: - description: references the local k8s secret where - the TLS key and cert reside properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3112,51 +1942,26 @@ spec: : true' type: object remote: - description: |- - RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. - Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. properties: feastRef: - description: Reference to an existing `FeatureStore` - CR in the same k8s cluster. properties: name: - description: Name of the FeatureStore type: string namespace: - description: Namespace of the FeatureStore type: string required: - name type: object hostname: - description: Host address of the remote registry service - - :, e.g. `registry..svc.cluster.local:80` type: string tls: - description: TlsRemoteRegistryConfigs configures client - TLS for a remote feast registry. in an openshift - cluster, this is configured by default when the - remote feast registry is using service serving certificates. properties: certName: - description: defines the configmap key name for - the client TLS cert. type: string configMapRef: - description: references the local k8s configmap - where the TLS cert resides properties: name: default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - TODO: Add other useful fields. apiVersion, kind, uid? - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896. type: string type: object x-kubernetes-map-type: atomic @@ -3174,72 +1979,197 @@ spec: - message: One selection required. rule: '[has(self.local), has(self.remote)].exists_one(c, c)' + ui: + properties: + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + properties: + key: + type: string + name: + default: "" + type: string + optional: + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + type: string + secretRef: + properties: + name: + default: "" + type: string + optional: + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + logLevel: + enum: + - debug + - info + - warning + - error + - critical + type: string + resources: + properties: + claims: + items: + properties: + name: + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + tls: + properties: + disable: + type: boolean + secretKeyNames: + properties: + tlsCrt: + type: string + tlsKey: + type: string + type: object + secretRef: + properties: + name: + default: "" + type: string + type: object + x-kubernetes-map-type: atomic + type: object + x-kubernetes-validations: + - message: '`secretRef` required if `disable` is false.' + rule: '(!has(self.disable) || !self.disable) ? has(self.secretRef) + : true' + type: object type: object required: - feastProject type: object clientConfigMap: - description: ConfigMap in this namespace containing a client `feature_store.yaml` - for this feast deployment type: string conditions: items: - description: "Condition contains details for one aspect of the current - state of this API Resource.\n---\nThis struct is intended for - direct use as an array at the field path .status.conditions. For - example,\n\n\n\ttype FooStatus struct{\n\t // Represents the - observations of a foo's current state.\n\t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // - +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t - \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t - \ // other fields\n\t}" properties: lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. format: date-time type: string message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. maxLength: 32768 type: string observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. format: int64 minimum: 0 type: integer reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ type: string status: - description: status of the condition, one of True, False, Unknown. enum: - "True" - "False" - Unknown type: string type: - description: |- - type of condition in CamelCase or in foo.example.com/CamelCase. - --- - Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be - useful (see .node.status.conditions), the ability to deconflict is important. - The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string @@ -3256,8 +2186,6 @@ spec: phase: type: string serviceHostnames: - description: ServiceHostnames defines the service hostnames in the - format of :, e.g. example.svc.cluster.local:80 properties: offlineStore: type: string @@ -3265,6 +2193,8 @@ spec: type: string registry: type: string + ui: + type: string type: object type: object type: object @@ -3448,6 +2378,17 @@ rules: - list - update - watch +- apiGroups: + - route.openshift.io + resources: + - routes + verbs: + - create + - delete + - get + - list + - update + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -3580,10 +2521,8 @@ spec: - /manager env: - name: RELATED_IMAGE_FEATURE_SERVER - value: docker.io/feastdev/feature-server:0.42.0 - - name: RELATED_IMAGE_GRPC_CURL - value: docker.io/fullstorydev/grpcurl:v1.9.1-alpine - image: feastdev/feast-operator:0.42.0 + value: docker.io/feastdev/feature-server:0.44.0 + image: feastdev/feast-operator:0.44.0 livenessProbe: httpGet: path: /healthz diff --git a/infra/feast-operator/docs/api/markdown/ref.md b/infra/feast-operator/docs/api/markdown/ref.md new file mode 100644 index 00000000000..e29ad08e9e2 --- /dev/null +++ b/infra/feast-operator/docs/api/markdown/ref.md @@ -0,0 +1,544 @@ +# API Reference + +## Packages +- [feast.dev/v1alpha1](#feastdevv1alpha1) + + +## feast.dev/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the v1alpha1 API group + +### Resource Types +- [FeatureStore](#featurestore) + + + +#### AuthzConfig + + + +AuthzConfig defines the authorization settings for the deployed Feast services. + +_Appears in:_ +- [FeatureStoreSpec](#featurestorespec) + +| Field | Description | +| --- | --- | +| `kubernetes` _[KubernetesAuthz](#kubernetesauthz)_ | | +| `oidc` _[OidcAuthz](#oidcauthz)_ | | + + +#### ContainerConfigs + + + +ContainerConfigs k8s container settings for the server + +_Appears in:_ +- [LocalRegistryConfig](#localregistryconfig) +- [OfflineStore](#offlinestore) +- [OnlineStore](#onlinestore) +- [ServerConfigs](#serverconfigs) + +| Field | Description | +| --- | --- | +| `image` _string_ | | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core)_ | | +| `envFrom` _[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core)_ | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#pullpolicy-v1-core)_ | | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | | + + +#### DefaultCtrConfigs + + + +DefaultCtrConfigs k8s container settings that are applied by default + +_Appears in:_ +- [ContainerConfigs](#containerconfigs) +- [LocalRegistryConfig](#localregistryconfig) +- [OfflineStore](#offlinestore) +- [OnlineStore](#onlinestore) +- [ServerConfigs](#serverconfigs) + +| Field | Description | +| --- | --- | +| `image` _string_ | | + + +#### FeatureStore + + + +FeatureStore is the Schema for the featurestores API + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `feast.dev/v1alpha1` +| `kind` _string_ | `FeatureStore` +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[FeatureStoreSpec](#featurestorespec)_ | | +| `status` _[FeatureStoreStatus](#featurestorestatus)_ | | + + +#### FeatureStoreRef + + + +FeatureStoreRef defines which existing FeatureStore's registry should be used + +_Appears in:_ +- [RemoteRegistryConfig](#remoteregistryconfig) + +| Field | Description | +| --- | --- | +| `name` _string_ | Name of the FeatureStore | +| `namespace` _string_ | Namespace of the FeatureStore | + + +#### FeatureStoreServices + + + +FeatureStoreServices defines the desired feast services. An ephemeral registry is deployed by default. + +_Appears in:_ +- [FeatureStoreSpec](#featurestorespec) + +| Field | Description | +| --- | --- | +| `offlineStore` _[OfflineStore](#offlinestore)_ | | +| `onlineStore` _[OnlineStore](#onlinestore)_ | | +| `registry` _[Registry](#registry)_ | | +| `ui` _[ServerConfigs](#serverconfigs)_ | | +| `deploymentStrategy` _[DeploymentStrategy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#deploymentstrategy-v1-apps)_ | | +| `disableInitContainers` _boolean_ | Disable the 'feast repo initialization' initContainer | + + +#### FeatureStoreSpec + + + +FeatureStoreSpec defines the desired state of FeatureStore + +_Appears in:_ +- [FeatureStore](#featurestore) +- [FeatureStoreStatus](#featurestorestatus) + +| Field | Description | +| --- | --- | +| `feastProject` _string_ | FeastProject is the Feast project id. This can be any alphanumeric string with underscores, but it cannot start with an underscore. Required. | +| `services` _[FeatureStoreServices](#featurestoreservices)_ | | +| `authz` _[AuthzConfig](#authzconfig)_ | | + + +#### FeatureStoreStatus + + + +FeatureStoreStatus defines the observed state of FeatureStore + +_Appears in:_ +- [FeatureStore](#featurestore) + +| Field | Description | +| --- | --- | +| `applied` _[FeatureStoreSpec](#featurestorespec)_ | Shows the currently applied feast configuration, including any pertinent defaults | +| `clientConfigMap` _string_ | ConfigMap in this namespace containing a client `feature_store.yaml` for this feast deployment | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#condition-v1-meta) array_ | | +| `feastVersion` _string_ | | +| `phase` _string_ | | +| `serviceHostnames` _[ServiceHostnames](#servicehostnames)_ | | + + +#### KubernetesAuthz + + + +KubernetesAuthz provides a way to define the authorization settings using Kubernetes RBAC resources. +https://kubernetes.io/docs/reference/access-authn-authz/rbac/ + +_Appears in:_ +- [AuthzConfig](#authzconfig) + +| Field | Description | +| --- | --- | +| `roles` _string array_ | The Kubernetes RBAC roles to be deployed in the same namespace of the FeatureStore. +Roles are managed by the operator and created with an empty list of rules. +See the Feast permission model at https://docs.feast.dev/getting-started/concepts/permission +The feature store admin is not obligated to manage roles using the Feast operator, roles can be managed independently. +This configuration option is only providing a way to automate this procedure. +Important note: the operator cannot ensure that these roles will match the ones used in the configured Feast permissions. | + + +#### LocalRegistryConfig + + + +LocalRegistryConfig configures the deployed registry service + +_Appears in:_ +- [Registry](#registry) + +| Field | Description | +| --- | --- | +| `image` _string_ | | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core)_ | | +| `envFrom` _[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core)_ | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#pullpolicy-v1-core)_ | | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | | +| `tls` _[TlsConfigs](#tlsconfigs)_ | | +| `logLevel` _string_ | LogLevel sets the logging level for the server +Allowed values: "debug", "info", "warning", "error", "critical". | +| `persistence` _[RegistryPersistence](#registrypersistence)_ | | + + +#### OfflineStore + + + +OfflineStore configures the deployed offline store service + +_Appears in:_ +- [FeatureStoreServices](#featurestoreservices) + +| Field | Description | +| --- | --- | +| `image` _string_ | | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core)_ | | +| `envFrom` _[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core)_ | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#pullpolicy-v1-core)_ | | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | | +| `tls` _[TlsConfigs](#tlsconfigs)_ | | +| `logLevel` _string_ | LogLevel sets the logging level for the server +Allowed values: "debug", "info", "warning", "error", "critical". | +| `persistence` _[OfflineStorePersistence](#offlinestorepersistence)_ | | + + +#### OfflineStoreDBStorePersistence + + + +OfflineStoreDBStorePersistence configures the DB store persistence for the offline store service + +_Appears in:_ +- [OfflineStorePersistence](#offlinestorepersistence) + +| Field | Description | +| --- | --- | +| `type` _string_ | | +| `secretRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed. | +| `secretKeyName` _string_ | By default, the selected store "type" is used as the SecretKeyName | + + +#### OfflineStoreFilePersistence + + + +OfflineStoreFilePersistence configures the file-based persistence for the offline store service + +_Appears in:_ +- [OfflineStorePersistence](#offlinestorepersistence) + +| Field | Description | +| --- | --- | +| `type` _string_ | | +| `pvc` _[PvcConfig](#pvcconfig)_ | | + + +#### OfflineStorePersistence + + + +OfflineStorePersistence configures the persistence settings for the offline store service + +_Appears in:_ +- [OfflineStore](#offlinestore) + +| Field | Description | +| --- | --- | +| `file` _[OfflineStoreFilePersistence](#offlinestorefilepersistence)_ | | +| `store` _[OfflineStoreDBStorePersistence](#offlinestoredbstorepersistence)_ | | + + +#### OidcAuthz + + + +OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider. +https://auth0.com/docs/authenticate/protocols/openid-connect-protocol + +_Appears in:_ +- [AuthzConfig](#authzconfig) + +| Field | Description | +| --- | --- | +| `secretRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | | + + +#### OnlineStore + + + +OnlineStore configures the deployed online store service + +_Appears in:_ +- [FeatureStoreServices](#featurestoreservices) + +| Field | Description | +| --- | --- | +| `image` _string_ | | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core)_ | | +| `envFrom` _[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core)_ | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#pullpolicy-v1-core)_ | | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | | +| `tls` _[TlsConfigs](#tlsconfigs)_ | | +| `logLevel` _string_ | LogLevel sets the logging level for the server +Allowed values: "debug", "info", "warning", "error", "critical". | +| `persistence` _[OnlineStorePersistence](#onlinestorepersistence)_ | | + + +#### OnlineStoreDBStorePersistence + + + +OnlineStoreDBStorePersistence configures the DB store persistence for the offline store service + +_Appears in:_ +- [OnlineStorePersistence](#onlinestorepersistence) + +| Field | Description | +| --- | --- | +| `type` _string_ | | +| `secretRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | Data store parameters should be placed as-is from the "feature_store.yaml" under the secret key. "registry_type" & "type" fields should be removed. | +| `secretKeyName` _string_ | By default, the selected store "type" is used as the SecretKeyName | + + +#### OnlineStoreFilePersistence + + + +OnlineStoreFilePersistence configures the file-based persistence for the offline store service + +_Appears in:_ +- [OnlineStorePersistence](#onlinestorepersistence) + +| Field | Description | +| --- | --- | +| `path` _string_ | | +| `pvc` _[PvcConfig](#pvcconfig)_ | | + + +#### OnlineStorePersistence + + + +OnlineStorePersistence configures the persistence settings for the online store service + +_Appears in:_ +- [OnlineStore](#onlinestore) + +| Field | Description | +| --- | --- | +| `file` _[OnlineStoreFilePersistence](#onlinestorefilepersistence)_ | | +| `store` _[OnlineStoreDBStorePersistence](#onlinestoredbstorepersistence)_ | | + + +#### OptionalCtrConfigs + + + +OptionalCtrConfigs k8s container settings that are optional + +_Appears in:_ +- [ContainerConfigs](#containerconfigs) +- [LocalRegistryConfig](#localregistryconfig) +- [OfflineStore](#offlinestore) +- [OnlineStore](#onlinestore) +- [ServerConfigs](#serverconfigs) + +| Field | Description | +| --- | --- | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core)_ | | +| `envFrom` _[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core)_ | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#pullpolicy-v1-core)_ | | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | | + + +#### PvcConfig + +_Underlying type:_ `[struct{Ref *k8s.io/api/core/v1.LocalObjectReference "json:\"ref,omitempty\""; Create *PvcCreate "json:\"create,omitempty\""; MountPath string "json:\"mountPath\""}](#struct{ref-*k8sioapicorev1localobjectreference-"json:\"ref,omitempty\"";-create-*pvccreate-"json:\"create,omitempty\"";-mountpath-string-"json:\"mountpath\""})` + +PvcConfig defines the settings for a persistent file store based on PVCs. +We can refer to an existing PVC using the `Ref` field, or create a new one using the `Create` field. + +_Appears in:_ +- [OfflineStoreFilePersistence](#offlinestorefilepersistence) +- [OnlineStoreFilePersistence](#onlinestorefilepersistence) + + + + + +#### Registry + + + +Registry configures the registry service. One selection is required. Local is the default setting. + +_Appears in:_ +- [FeatureStoreServices](#featurestoreservices) + +| Field | Description | +| --- | --- | +| `local` _[LocalRegistryConfig](#localregistryconfig)_ | | +| `remote` _[RemoteRegistryConfig](#remoteregistryconfig)_ | | + + +#### RegistryDBStorePersistence + +_Underlying type:_ `[struct{Type string "json:\"type\""; SecretRef k8s.io/api/core/v1.LocalObjectReference "json:\"secretRef\""; SecretKeyName string "json:\"secretKeyName,omitempty\""}](#struct{type-string-"json:\"type\"";-secretref-k8sioapicorev1localobjectreference-"json:\"secretref\"";-secretkeyname-string-"json:\"secretkeyname,omitempty\""})` + +RegistryDBStorePersistence configures the DB store persistence for the registry service + +_Appears in:_ +- [RegistryPersistence](#registrypersistence) + + + +#### RegistryFilePersistence + +_Underlying type:_ `[struct{Path string "json:\"path,omitempty\""; PvcConfig *PvcConfig "json:\"pvc,omitempty\""; S3AdditionalKwargs *map[string]string "json:\"s3_additional_kwargs,omitempty\""}](#struct{path-string-"json:\"path,omitempty\"";-pvcconfig-*pvcconfig-"json:\"pvc,omitempty\"";-s3additionalkwargs-*map[string]string-"json:\"s3_additional_kwargs,omitempty\""})` + +RegistryFilePersistence configures the file-based persistence for the registry service + +_Appears in:_ +- [RegistryPersistence](#registrypersistence) + + + +#### RegistryPersistence + + + +RegistryPersistence configures the persistence settings for the registry service + +_Appears in:_ +- [LocalRegistryConfig](#localregistryconfig) + +| Field | Description | +| --- | --- | +| `file` _[RegistryFilePersistence](#registryfilepersistence)_ | | +| `store` _[RegistryDBStorePersistence](#registrydbstorepersistence)_ | | + + +#### RemoteRegistryConfig + + + +RemoteRegistryConfig points to a remote feast registry server. When set, the operator will not deploy a registry for this FeatureStore CR. +Instead, this FeatureStore CR's online/offline services will use a remote registry. One selection is required. + +_Appears in:_ +- [Registry](#registry) + +| Field | Description | +| --- | --- | +| `hostname` _string_ | Host address of the remote registry service - :, e.g. `registry..svc.cluster.local:80` | +| `feastRef` _[FeatureStoreRef](#featurestoreref)_ | Reference to an existing `FeatureStore` CR in the same k8s cluster. | +| `tls` _[TlsRemoteRegistryConfigs](#tlsremoteregistryconfigs)_ | | + + +#### SecretKeyNames + + + +SecretKeyNames defines the secret key names for the TLS key and cert. + +_Appears in:_ +- [TlsConfigs](#tlsconfigs) + +| Field | Description | +| --- | --- | +| `tlsCrt` _string_ | defaults to "tls.crt" | +| `tlsKey` _string_ | defaults to "tls.key" | + + +#### ServerConfigs + + + +ServerConfigs server-related configurations for a feast service + +_Appears in:_ +- [FeatureStoreServices](#featurestoreservices) +- [LocalRegistryConfig](#localregistryconfig) +- [OfflineStore](#offlinestore) +- [OnlineStore](#onlinestore) + +| Field | Description | +| --- | --- | +| `image` _string_ | | +| `env` _[EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envvar-v1-core)_ | | +| `envFrom` _[EnvFromSource](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#envfromsource-v1-core)_ | | +| `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#pullpolicy-v1-core)_ | | +| `resources` _[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | | +| `tls` _[TlsConfigs](#tlsconfigs)_ | | +| `logLevel` _string_ | LogLevel sets the logging level for the server +Allowed values: "debug", "info", "warning", "error", "critical". | + + +#### ServiceHostnames + + + +ServiceHostnames defines the service hostnames in the format of :, e.g. example.svc.cluster.local:80 + +_Appears in:_ +- [FeatureStoreStatus](#featurestorestatus) + +| Field | Description | +| --- | --- | +| `offlineStore` _string_ | | +| `onlineStore` _string_ | | +| `registry` _string_ | | +| `ui` _string_ | | + + +#### TlsConfigs + + + +TlsConfigs configures server TLS for a feast service. in an openshift cluster, this is configured by default using service serving certificates. + +_Appears in:_ +- [LocalRegistryConfig](#localregistryconfig) +- [OfflineStore](#offlinestore) +- [OnlineStore](#onlinestore) +- [ServerConfigs](#serverconfigs) + +| Field | Description | +| --- | --- | +| `secretRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | references the local k8s secret where the TLS key and cert reside | +| `secretKeyNames` _[SecretKeyNames](#secretkeynames)_ | | +| `disable` _boolean_ | will disable TLS for the feast service. useful in an openshift cluster, for example, where TLS is configured by default | + + +#### TlsRemoteRegistryConfigs + + + +TlsRemoteRegistryConfigs configures client TLS for a remote feast registry. in an openshift cluster, this is configured by default when the remote feast registry is using service serving certificates. + +_Appears in:_ +- [RemoteRegistryConfig](#remoteregistryconfig) + +| Field | Description | +| --- | --- | +| `configMapRef` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#localobjectreference-v1-core)_ | references the local k8s configmap where the TLS cert resides | +| `certName` _string_ | defines the configmap key name for the client TLS cert. | + + diff --git a/infra/feast-operator/docs/crd-ref-templates/config.yaml b/infra/feast-operator/docs/crd-ref-templates/config.yaml new file mode 100644 index 00000000000..42d10e08b03 --- /dev/null +++ b/infra/feast-operator/docs/crd-ref-templates/config.yaml @@ -0,0 +1,8 @@ +processor: + ignoreTypes: + - "(FeatureStore)List$" + ignoreFields: + - "TypeMeta$" + +render: + kubernetesVersion: "1.30" \ No newline at end of file diff --git a/infra/feast-operator/docs/crd-ref-templates/markdown/gv_details.tpl b/infra/feast-operator/docs/crd-ref-templates/markdown/gv_details.tpl new file mode 100644 index 00000000000..30ad0d75184 --- /dev/null +++ b/infra/feast-operator/docs/crd-ref-templates/markdown/gv_details.tpl @@ -0,0 +1,19 @@ +{{- define "gvDetails" -}} +{{- $gv := . -}} + +## {{ $gv.GroupVersionString }} + +{{ $gv.Doc }} + +{{- if $gv.Kinds }} +### Resource Types +{{- range $gv.SortedKinds }} +- {{ $gv.TypeForKind . | markdownRenderTypeLink }} +{{- end }} +{{ end }} + +{{ range $gv.SortedTypes }} +{{ template "type" . }} +{{ end }} + +{{- end -}} diff --git a/infra/feast-operator/docs/crd-ref-templates/markdown/gv_list.tpl b/infra/feast-operator/docs/crd-ref-templates/markdown/gv_list.tpl new file mode 100644 index 00000000000..a4d3dadf18c --- /dev/null +++ b/infra/feast-operator/docs/crd-ref-templates/markdown/gv_list.tpl @@ -0,0 +1,15 @@ +{{- define "gvList" -}} +{{- $groupVersions := . -}} + +# API Reference + +## Packages +{{- range $groupVersions }} +- {{ markdownRenderGVLink . }} +{{- end }} + +{{ range $groupVersions }} +{{ template "gvDetails" . }} +{{ end }} + +{{- end -}} diff --git a/infra/feast-operator/docs/crd-ref-templates/markdown/type.tpl b/infra/feast-operator/docs/crd-ref-templates/markdown/type.tpl new file mode 100644 index 00000000000..c0ac2e03539 --- /dev/null +++ b/infra/feast-operator/docs/crd-ref-templates/markdown/type.tpl @@ -0,0 +1,33 @@ +{{- define "type" -}} +{{- $type := . -}} +{{- if markdownShouldRenderType $type -}} + +#### {{ $type.Name }} + +{{ if $type.IsAlias }}_Underlying type:_ `{{ markdownRenderTypeLink $type.UnderlyingType }}`{{ end }} + +{{ $type.Doc }} + +{{ if $type.References -}} +_Appears in:_ +{{- range $type.SortedReferences }} +- {{ markdownRenderTypeLink . }} +{{- end }} +{{- end }} + +{{ if $type.Members -}} +| Field | Description | +| --- | --- | +{{ if $type.GVK -}} +| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` +| `kind` _string_ | `{{ $type.GVK.Kind }}` +{{ end -}} + +{{ range $type.Members -}} +| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} | +{{ end -}} + +{{ end -}} + +{{- end -}} +{{- end -}} diff --git a/infra/feast-operator/docs/crd-ref-templates/markdown/type_members.tpl b/infra/feast-operator/docs/crd-ref-templates/markdown/type_members.tpl new file mode 100644 index 00000000000..182fa182166 --- /dev/null +++ b/infra/feast-operator/docs/crd-ref-templates/markdown/type_members.tpl @@ -0,0 +1,8 @@ +{{- define "type_members" -}} +{{- $field := . -}} +{{- if eq $field.Name "metadata" -}} +Refer to Kubernetes API documentation for fields of `metadata`. +{{- else -}} +{{ $field.Doc }} +{{- end -}} +{{- end -}} diff --git a/infra/feast-operator/go.mod b/infra/feast-operator/go.mod index 337cb3e80db..c8608cb242f 100644 --- a/infra/feast-operator/go.mod +++ b/infra/feast-operator/go.mod @@ -5,6 +5,7 @@ go 1.22.9 require ( github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 + github.com/openshift/api v0.0.0-20240912201240-0a8800162826 // release-4.17 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.30.1 k8s.io/apimachinery v0.30.1 @@ -66,14 +67,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.18.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect diff --git a/infra/feast-operator/go.sum b/infra/feast-operator/go.sum index ee6c96863b6..ef5d6204916 100644 --- a/infra/feast-operator/go.sum +++ b/infra/feast-operator/go.sum @@ -96,6 +96,8 @@ github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8 github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/openshift/api v0.0.0-20240912201240-0a8800162826 h1:A8D9SN/hJUwAbdO0rPCVTqmuBOctdgurr53gK701SYo= +github.com/openshift/api v0.0.0-20240912201240-0a8800162826/go.mod h1:OOh6Qopf21pSzqNVCB5gomomBXb8o5sGKZxG2KNpaXM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -161,36 +163,36 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/infra/feast-operator/internal/controller/featurestore_controller.go b/infra/feast-operator/internal/controller/featurestore_controller.go index 99be6962b56..90248ff3de0 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller.go +++ b/infra/feast-operator/internal/controller/featurestore_controller.go @@ -39,6 +39,7 @@ import ( "github.com/feast-dev/feast/infra/feast-operator/internal/controller/authz" feasthandler "github.com/feast-dev/feast/infra/feast-operator/internal/controller/handler" "github.com/feast-dev/feast/infra/feast-operator/internal/controller/services" + routev1 "github.com/openshift/api/route/v1" ) // Constants for requeue @@ -59,6 +60,7 @@ type FeatureStoreReconciler struct { // +kubebuilder:rbac:groups=core,resources=services;configmaps;persistentvolumeclaims;serviceaccounts,verbs=get;list;create;update;watch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles;rolebindings,verbs=get;list;create;update;watch;delete // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list +// +kubebuilder:rbac:groups=route.openshift.io,resources=routes,verbs=get;list;create;update;watch;delete // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -153,7 +155,7 @@ func (r *FeatureStoreReconciler) deployFeast(ctx context.Context, cr *feastdevv1 // SetupWithManager sets up the controller with the Manager. func (r *FeatureStoreReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). + bldr := ctrl.NewControllerManagedBy(mgr). For(&feastdevv1alpha1.FeatureStore{}). Owns(&corev1.ConfigMap{}). Owns(&appsv1.Deployment{}). @@ -162,8 +164,13 @@ func (r *FeatureStoreReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.ServiceAccount{}). Owns(&rbacv1.RoleBinding{}). Owns(&rbacv1.Role{}). - Watches(&feastdevv1alpha1.FeatureStore{}, handler.EnqueueRequestsFromMapFunc(r.mapFeastRefsToFeastRequests)). - Complete(r) + Watches(&feastdevv1alpha1.FeatureStore{}, handler.EnqueueRequestsFromMapFunc(r.mapFeastRefsToFeastRequests)) + if services.IsOpenShift() { + bldr = bldr.Owns(&routev1.Route{}) + } + + return bldr.Complete(r) + } // if a remotely referenced FeatureStore is changed, reconcile any FeatureStores that reference it. diff --git a/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go b/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go index 1c9fa4bc1a5..d6ae16443f7 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_db_store_test.go @@ -443,8 +443,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) svc := &corev1.Service{} err = k8sClient.Get(ctx, types.NamespacedName{ Name: feast.GetFeastServiceName(services.RegistryFeastType), @@ -538,7 +537,7 @@ var _ = Describe("FeatureStore Controller - db storage services", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go b/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go index 381a2d40a1d..c9e270b040d 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_ephemeral_test.go @@ -212,8 +212,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) svc := &corev1.Service{} err = k8sClient.Get(ctx, types.NamespacedName{ Name: feast.GetFeastServiceName(services.RegistryFeastType), @@ -253,7 +252,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) @@ -277,7 +276,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go b/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go index e3a0e8fa646..c0c02a96148 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_kubernetes_auth_test.go @@ -202,7 +202,7 @@ var _ = Describe("FeatureStore Controller-Kubernetes authorization", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(1)) Expect(deploy.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(1)) @@ -373,7 +373,7 @@ var _ = Describe("FeatureStore Controller-Kubernetes authorization", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go b/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go index f238ca9ee4c..f3500dec084 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_loglevel_test.go @@ -61,13 +61,22 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { Services: &feastdevv1alpha1.FeatureStoreServices{ Registry: &feastdevv1alpha1.Registry{ Local: &feastdevv1alpha1.LocalRegistryConfig{ - LogLevel: "error", + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + LogLevel: "error", + }, }, }, OnlineStore: &feastdevv1alpha1.OnlineStore{ - LogLevel: "debug", + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + LogLevel: "debug", + }, }, OfflineStore: &feastdevv1alpha1.OfflineStore{ + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + LogLevel: "info", + }, + }, + UI: &feastdevv1alpha1.ServerConfigs{ LogLevel: "info", }, }, @@ -164,7 +173,7 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) command := services.GetRegistryContainer(*deploy).Command Expect(command).To(ContainElement("--log-level")) Expect(command).To(ContainElement("ERROR")) @@ -190,6 +199,7 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { }, OnlineStore: &feastdevv1alpha1.OnlineStore{}, OfflineStore: &feastdevv1alpha1.OfflineStore{}, + UI: &feastdevv1alpha1.ServerConfigs{}, } Expect(k8sClient.Update(ctx, resource)).To(Succeed()) @@ -220,7 +230,7 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) command := services.GetRegistryContainer(*deploy).Command Expect(command).NotTo(ContainElement("--log-level")) @@ -229,6 +239,9 @@ var _ = Describe("FeatureStore Controller - Feast service LogLevel", func() { command = services.GetOnlineContainer(*deploy).Command Expect(command).NotTo(ContainElement("--log-level")) + + command = services.GetUIContainer(*deploy).Command + Expect(command).NotTo(ContainElement("--log-level")) }) }) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go b/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go index 6e9adbf8eb5..85a30e79432 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_objectstore_test.go @@ -189,7 +189,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(2)) Expect(services.GetRegistryContainer(*deploy)).NotTo(BeNil()) Expect(services.GetOnlineContainer(*deploy)).To(BeNil()) Expect(services.GetOfflineContainer(*deploy)).To(BeNil()) @@ -258,7 +258,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(1)) + Expect(svcList.Items).To(HaveLen(2)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) @@ -284,7 +284,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(1)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(2)) Expect(services.GetRegistryContainer(*deploy)).NotTo(BeNil()) Expect(services.GetOnlineContainer(*deploy)).To(BeNil()) Expect(services.GetOfflineContainer(*deploy)).To(BeNil()) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go b/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go index 23a4a32702b..cf2ea78cdf0 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_oidc_auth_test.go @@ -222,7 +222,7 @@ var _ = Describe("FeatureStore Controller-OIDC authorization", func() { Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) Expect(deploy.Spec.Template.Spec.Volumes).To(HaveLen(1)) Expect(services.GetOfflineContainer(*deploy).VolumeMounts).To(HaveLen(1)) Expect(services.GetOnlineContainer(*deploy).VolumeMounts).To(HaveLen(1)) @@ -314,7 +314,7 @@ var _ = Describe("FeatureStore Controller-OIDC authorization", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go b/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go index c21f702d559..94bc0df3796 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_pvc_test.go @@ -82,6 +82,7 @@ var _ = Describe("FeatureStore Controller-Ephemeral services", func() { if err != nil && errors.IsNotFound(err) { resource := createFeatureStoreResource(resourceName, image, pullPolicy, &[]corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}}, withEnvFrom()) + resource.Spec.Services.UI = nil resource.Spec.Services.OfflineStore.Persistence = &feastdevv1alpha1.OfflineStorePersistence{ FilePersistence: &feastdevv1alpha1.OfflineStoreFilePersistence{ Type: offlineType, diff --git a/infra/feast-operator/internal/controller/featurestore_controller_test.go b/infra/feast-operator/internal/controller/featurestore_controller_test.go index 9f146bf1f61..152bd0b18f0 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_test.go @@ -132,12 +132,14 @@ var _ = Describe("FeatureStore Controller", func() { Expect(resource.Status.ClientConfigMap).To(Equal(feast.GetFeastServiceName(services.ClientFeastType))) Expect(resource.Status.ServiceHostnames.OfflineStore).To(BeEmpty()) Expect(resource.Status.ServiceHostnames.OnlineStore).To(BeEmpty()) + Expect(resource.Status.ServiceHostnames.UI).To(BeEmpty()) Expect(resource.Status.ServiceHostnames.Registry).To(Equal(feast.GetFeastServiceName(services.RegistryFeastType) + "." + resource.Namespace + ".svc.cluster.local:80")) Expect(resource.Status.Applied.FeastProject).To(Equal(resource.Spec.FeastProject)) Expect(resource.Status.Applied.AuthzConfig).To(BeNil()) Expect(resource.Status.Applied.Services).NotTo(BeNil()) Expect(resource.Status.Applied.Services.OfflineStore).To(BeNil()) Expect(resource.Status.Applied.Services.OnlineStore).To(BeNil()) + Expect(resource.Status.Applied.Services.UI).To(BeNil()) Expect(resource.Status.Applied.Services.Registry).NotTo(BeNil()) Expect(resource.Status.Applied.Services.OnlineStore).To(BeNil()) Expect(resource.Status.Applied.Services.Registry.Remote).To(BeNil()) @@ -485,10 +487,16 @@ var _ = Describe("FeatureStore Controller", func() { Expect(resource.Status.Applied.Services.Registry.Local.ImagePullPolicy).To(BeNil()) Expect(resource.Status.Applied.Services.Registry.Local.Resources).To(BeNil()) Expect(resource.Status.Applied.Services.Registry.Local.Image).To(Equal(&services.DefaultImage)) - + Expect(resource.Status.Applied.Services.UI).NotTo(BeNil()) + Expect(resource.Status.Applied.Services.UI.Env).To(Equal(&[]corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}})) + Expect(resource.Status.Applied.Services.UI.EnvFrom).To(Equal(withEnvFrom())) + Expect(resource.Status.Applied.Services.UI.ImagePullPolicy).To(Equal(&pullPolicy)) + Expect(resource.Status.Applied.Services.UI.Resources).NotTo(BeNil()) + Expect(resource.Status.Applied.Services.UI.Image).To(Equal(&image)) Expect(resource.Status.ServiceHostnames.OfflineStore).To(Equal(feast.GetFeastServiceName(services.OfflineFeastType) + "." + resource.Namespace + domain)) Expect(resource.Status.ServiceHostnames.OnlineStore).To(Equal(feast.GetFeastServiceName(services.OnlineFeastType) + "." + resource.Namespace + domain)) Expect(resource.Status.ServiceHostnames.Registry).To(Equal(feast.GetFeastServiceName(services.RegistryFeastType) + "." + resource.Namespace + domain)) + Expect(resource.Status.ServiceHostnames.UI).To(Equal(feast.GetFeastServiceName(services.UIFeastType) + "." + resource.Namespace + domain)) Expect(resource.Status.Conditions).NotTo(BeEmpty()) cond := apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.ReadyType) @@ -529,6 +537,13 @@ var _ = Describe("FeatureStore Controller", func() { Expect(cond.Type).To(Equal(feastdevv1alpha1.OnlineStoreReadyType)) Expect(cond.Message).To(Equal(feastdevv1alpha1.OnlineStoreReadyMessage)) + cond = apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.UIReadyType) + Expect(cond).ToNot(BeNil()) + Expect(cond.Status).To(Equal(metav1.ConditionTrue)) + Expect(cond.Reason).To(Equal(feastdevv1alpha1.ReadyReason)) + Expect(cond.Type).To(Equal(feastdevv1alpha1.UIReadyType)) + Expect(cond.Message).To(Equal(feastdevv1alpha1.UIReadyMessage)) + Expect(resource.Status.Phase).To(Equal(feastdevv1alpha1.ReadyPhase)) deploy := &appsv1.Deployment{} @@ -541,8 +556,7 @@ var _ = Describe("FeatureStore Controller", func() { Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) svc := &corev1.Service{} err = k8sClient.Get(ctx, types.NamespacedName{ Name: feast.GetFeastServiceName(services.RegistryFeastType), @@ -587,7 +601,7 @@ var _ = Describe("FeatureStore Controller", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) @@ -612,7 +626,7 @@ var _ = Describe("FeatureStore Controller", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) @@ -772,7 +786,7 @@ var _ = Describe("FeatureStore Controller", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) @@ -801,7 +815,7 @@ var _ = Describe("FeatureStore Controller", func() { }, deploy) Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Template.Spec.ServiceAccountName).To(Equal(deploy.Name)) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) onlineContainer := services.GetOnlineContainer(*deploy) Expect(onlineContainer.Env).To(HaveLen(3)) Expect(areEnvVarArraysEqual(onlineContainer.Env, []corev1.EnvVar{{Name: testEnvVarName, Value: testEnvVarValue}, {Name: services.TmpFeatureStoreYamlEnvVar, Value: fsYamlStr}, {Name: "fieldRefName", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}})).To(BeTrue()) @@ -859,7 +873,7 @@ var _ = Describe("FeatureStore Controller", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) // disable the Online Store service resource.Spec.Services.OnlineStore = nil @@ -877,7 +891,7 @@ var _ = Describe("FeatureStore Controller", func() { err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(2)) + Expect(svcList.Items).To(HaveLen(3)) err = k8sClient.Get(ctx, typeNamespacedName, resource) Expect(err).NotTo(HaveOccurred()) @@ -898,7 +912,7 @@ var _ = Describe("FeatureStore Controller", func() { err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(1)) + Expect(svcList.Items).To(HaveLen(2)) }) It("should handle remote registry references", func() { @@ -1007,7 +1021,7 @@ var _ = Describe("FeatureStore Controller", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(2)) + Expect(deploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) // check client config cm := &corev1.ConfigMap{} @@ -1151,7 +1165,7 @@ var _ = Describe("FeatureStore Controller", func() { err = k8sClient.Get(ctx, typeNamespacedName, resource) Expect(err).NotTo(HaveOccurred()) - Expect(resource.Status.Conditions).To(HaveLen(5)) + Expect(resource.Status.Conditions).To(HaveLen(6)) cond := apimeta.FindStatusCondition(resource.Status.Conditions, feastdevv1alpha1.ReadyType) Expect(cond).ToNot(BeNil()) diff --git a/infra/feast-operator/internal/controller/featurestore_controller_test_utils_test.go b/infra/feast-operator/internal/controller/featurestore_controller_test_utils_test.go index 57fa87ce733..e49ba3a9201 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_test_utils_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_test_utils_test.go @@ -105,18 +105,35 @@ func createFeatureStoreResource(resourceName string, image string, pullPolicy co FeastProject: feastProject, Services: &feastdevv1alpha1.FeatureStoreServices{ OfflineStore: &feastdevv1alpha1.OfflineStore{ - ServiceConfigs: feastdevv1alpha1.ServiceConfigs{ - OptionalConfigs: feastdevv1alpha1.OptionalConfigs{ - EnvFrom: envFromVar, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + ContainerConfigs: feastdevv1alpha1.ContainerConfigs{ + OptionalCtrConfigs: feastdevv1alpha1.OptionalCtrConfigs{ + EnvFrom: envFromVar, + }, }, }, }, OnlineStore: &feastdevv1alpha1.OnlineStore{ - ServiceConfigs: feastdevv1alpha1.ServiceConfigs{ - DefaultConfigs: feastdevv1alpha1.DefaultConfigs{ + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + ContainerConfigs: feastdevv1alpha1.ContainerConfigs{ + DefaultCtrConfigs: feastdevv1alpha1.DefaultCtrConfigs{ + Image: &image, + }, + OptionalCtrConfigs: feastdevv1alpha1.OptionalCtrConfigs{ + Env: envVars, + EnvFrom: envFromVar, + ImagePullPolicy: &pullPolicy, + Resources: &corev1.ResourceRequirements{}, + }, + }, + }, + }, + UI: &feastdevv1alpha1.ServerConfigs{ + ContainerConfigs: feastdevv1alpha1.ContainerConfigs{ + DefaultCtrConfigs: feastdevv1alpha1.DefaultCtrConfigs{ Image: &image, }, - OptionalConfigs: feastdevv1alpha1.OptionalConfigs{ + OptionalCtrConfigs: feastdevv1alpha1.OptionalCtrConfigs{ Env: envVars, EnvFrom: envFromVar, ImagePullPolicy: &pullPolicy, diff --git a/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go b/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go index 06355c664c3..76298ce40a0 100644 --- a/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go +++ b/infra/feast-operator/internal/controller/featurestore_controller_tls_test.go @@ -71,16 +71,25 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { FeastProject: feastProject, Services: &feastdevv1alpha1.FeatureStoreServices{ OnlineStore: &feastdevv1alpha1.OnlineStore{ - TLS: tlsConfigs, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: tlsConfigs, + }, }, OfflineStore: &feastdevv1alpha1.OfflineStore{ - TLS: tlsConfigs, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: tlsConfigs, + }, }, Registry: &feastdevv1alpha1.Registry{ Local: &feastdevv1alpha1.LocalRegistryConfig{ - TLS: tlsConfigs, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: tlsConfigs, + }, }, }, + UI: &feastdevv1alpha1.ServerConfigs{ + TLS: tlsConfigs, + }, }, }, } @@ -181,8 +190,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { Expect(err).NotTo(HaveOccurred()) Expect(deploy.Spec.Replicas).To(Equal(&services.DefaultReplicas)) Expect(controllerutil.HasControllerReference(deploy)).To(BeTrue()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) - + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) svc := &corev1.Service{} err = k8sClient.Get(ctx, types.NamespacedName{ Name: feast.GetFeastServiceName(services.RegistryFeastType), @@ -230,7 +238,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { svcList := corev1.ServiceList{} err = k8sClient.List(ctx, &svcList, listOpts) Expect(err).NotTo(HaveOccurred()) - Expect(svcList.Items).To(HaveLen(3)) + Expect(svcList.Items).To(HaveLen(4)) cmList := corev1.ConfigMapList{} err = k8sClient.List(ctx, &cmList, listOpts) @@ -245,7 +253,7 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { Namespace: objMeta.Namespace, }, deploy) Expect(err).NotTo(HaveOccurred()) - Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(deploy.Spec.Template.Spec.Containers).To(HaveLen(4)) registryContainer := services.GetRegistryContainer(*deploy) Expect(registryContainer.Env).To(HaveLen(1)) env := getFeatureStoreYamlEnvVar(registryContainer.Env) @@ -348,12 +356,16 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() { FeastProject: feastProject, Services: &feastdevv1alpha1.FeatureStoreServices{ OnlineStore: &feastdevv1alpha1.OnlineStore{ - TLS: &feastdevv1alpha1.TlsConfigs{ - Disable: &disable, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: &feastdevv1alpha1.TlsConfigs{ + Disable: &disable, + }, }, }, OfflineStore: &feastdevv1alpha1.OfflineStore{ - TLS: tlsConfigs, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: tlsConfigs, + }, }, Registry: &feastdevv1alpha1.Registry{ Remote: &feastdevv1alpha1.RemoteRegistryConfig{ diff --git a/infra/feast-operator/internal/controller/services/repo_config_test.go b/infra/feast-operator/internal/controller/services/repo_config_test.go index 1e5909b8892..0ed0fb62e06 100644 --- a/infra/feast-operator/internal/controller/services/repo_config_test.go +++ b/infra/feast-operator/internal/controller/services/repo_config_test.go @@ -371,6 +371,7 @@ func minimalFeatureStoreWithAllServices() *feastdevv1alpha1.FeatureStore { OfflineStore: &feastdevv1alpha1.OfflineStore{}, OnlineStore: &feastdevv1alpha1.OnlineStore{}, Registry: &feastdevv1alpha1.Registry{}, + UI: &feastdevv1alpha1.ServerConfigs{}, } return feast } diff --git a/infra/feast-operator/internal/controller/services/services.go b/infra/feast-operator/internal/controller/services/services.go index 1c8a7d26e53..4bf37e86dbf 100644 --- a/infra/feast-operator/internal/controller/services/services.go +++ b/infra/feast-operator/internal/controller/services/services.go @@ -22,6 +22,8 @@ import ( "strings" feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1" + routev1 "github.com/openshift/api/route/v1" + "github.com/feast-dev/feast/infra/feast-operator/internal/controller/handler" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -50,7 +52,7 @@ func (feast *FeastServices) ApplyDefaults() error { // Deploy the feast services func (feast *FeastServices) Deploy() error { if feast.noLocalServiceConfigured() { - return errors.New("At least one local service must be configured. e.g. registry / online / offline.") + return errors.New("at least one local service must be configured. e.g. registry / online / offline") } openshiftTls, err := feast.checkOpenshiftTls() if err != nil { @@ -109,6 +111,23 @@ func (feast *FeastServices) Deploy() error { return err } } + if feast.isUI() { + if err = feast.deployFeastServiceByType(UIFeastType); err != nil { + return err + } + if err = feast.createRoute(UIFeastType); err != nil { + return err + } + } else { + if err := feast.removeFeastServiceByType(UIFeastType); err != nil { + return err + } + if err := feast.removeRoute(UIFeastType); err != nil { + return err + } + + } + if err := feast.createServiceAccount(); err != nil { return err } @@ -216,6 +235,17 @@ func (feast *FeastServices) removeFeastServiceByType(feastType FeastServiceType) return nil } +func (feast *FeastServices) removeRoute(feastType FeastServiceType) error { + if !isOpenShift { + return nil + } + route := feast.initRoute(feastType) + if err := feast.Handler.DeleteOwnedFeastObj(route); err != nil { + return err + } + return nil +} + func (feast *FeastServices) createService(feastType FeastServiceType) error { logger := log.FromContext(feast.Handler.Context) svc := feast.initFeastSvc(feastType) @@ -256,6 +286,24 @@ func (feast *FeastServices) createDeployment() error { return nil } +func (feast *FeastServices) createRoute(feastType FeastServiceType) error { + logger := log.FromContext(feast.Handler.Context) + if !isOpenShift { + return nil + } + logger.Info("Reconciling route for Feast service", "ServiceType", feastType) + route := feast.initRoute(feastType) + if op, err := controllerutil.CreateOrUpdate(feast.Handler.Context, feast.Handler.Client, route, controllerutil.MutateFn(func() error { + return feast.setRoute(route, feastType) + })); err != nil { + return err + } else if op == controllerutil.OperationResultCreated || op == controllerutil.OperationResultUpdated { + logger.Info("Successfully reconciled", "Route", route.Name, "operation", op) + } + + return nil +} + func (feast *FeastServices) createPVC(pvcCreate *feastdevv1alpha1.PvcCreate, feastType FeastServiceType) error { logger := log.FromContext(feast.Handler.Context) pvc, err := feast.createNewPVC(pvcCreate, feastType) @@ -301,7 +349,6 @@ func (feast *FeastServices) setPod(podSpec *corev1.PodSpec) error { if err := feast.setContainers(podSpec); err != nil { return err } - feast.setRegistryClientInitContainer(podSpec) feast.mountTlsConfigs(podSpec) feast.mountPvcConfigs(podSpec) feast.mountEmptyDirVolumes(podSpec) @@ -326,17 +373,20 @@ func (feast *FeastServices) setContainers(podSpec *corev1.PodSpec) error { if feast.isOnlinStore() { feast.setContainer(&podSpec.Containers, OnlineFeastType, fsYamlB64) } + if feast.isUI() { + feast.setContainer(&podSpec.Containers, UIFeastType, fsYamlB64) + } return nil } func (feast *FeastServices) setContainer(containers *[]corev1.Container, feastType FeastServiceType, fsYamlB64 string) { tls := feast.getTlsConfigs(feastType) - serviceConfigs := feast.getServiceConfigs(feastType) - defaultServiceConfigs := serviceConfigs.DefaultConfigs + containerConfigs := feast.getContainerConfigs(feastType) + defaultCtrConfigs := containerConfigs.DefaultCtrConfigs probeHandler := getProbeHandler(feastType, tls) container := &corev1.Container{ Name: string(feastType), - Image: *defaultServiceConfigs.Image, + Image: *defaultCtrConfigs.Image, WorkingDir: getOfflineMountPath(feast.Handler.FeatureStore) + "/" + feast.Handler.FeatureStore.Status.Applied.FeastProject + FeatureRepoDir, Command: feast.getContainerCommand(feastType), Ports: []corev1.ContainerPort{ @@ -367,10 +417,35 @@ func (feast *FeastServices) setContainer(containers *[]corev1.Container, feastTy PeriodSeconds: 10, }, } - applyOptionalContainerConfigs(container, serviceConfigs.OptionalConfigs) + applyOptionalCtrConfigs(container, containerConfigs.OptionalCtrConfigs) *containers = append(*containers, *container) } +func (feast *FeastServices) setRoute(route *routev1.Route, feastType FeastServiceType) error { + + svcName := feast.GetFeastServiceName(feastType) + route.Labels = feast.getFeastTypeLabels(feastType) + + tls := feast.getTlsConfigs(feastType) + route.Spec = routev1.RouteSpec{ + To: routev1.RouteTargetReference{ + Kind: "Service", + Name: svcName, + }, + Port: &routev1.RoutePort{ + TargetPort: intstr.FromInt(int(getTargetPort(feastType, tls))), + }, + } + if tls.IsTLS() { + route.Spec.TLS = &routev1.TLSConfig{ + Termination: routev1.TLSTerminationPassthrough, + InsecureEdgeTerminationPolicy: routev1.InsecureEdgeTerminationPolicyRedirect, + } + } + + return controllerutil.SetControllerReference(feast.Handler.FeatureStore, route, feast.Handler.Scheme) +} + func (feast *FeastServices) getContainerCommand(feastType FeastServiceType) []string { baseCommand := "feast" options := []string{} @@ -430,29 +505,6 @@ func (feast *FeastServices) setInitContainer(podSpec *corev1.PodSpec, fsYamlB64 } } -// add grpc init container if remote registry reference (feastRef) is configured -func (feast *FeastServices) setRegistryClientInitContainer(podSpec *corev1.PodSpec) { - if !feast.Handler.FeatureStore.Status.Applied.Services.DisableInitContainers { - hostname := feast.Handler.FeatureStore.Status.ServiceHostnames.Registry - if len(hostname) > 0 && feast.IsRemoteRefRegistry() { - grpcurlFlag := "-plaintext" - hostSplit := strings.Split(hostname, ":") - if len(hostSplit) > 1 && hostSplit[1] == "443" { - grpcurlFlag = "-insecure" - } - podSpec.InitContainers = append(podSpec.InitContainers, corev1.Container{ - Name: "init-registry", - Image: getGrpcCurlImage(), - Command: []string{ - "sh", "-c", - "until grpcurl -H \"authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\" " + - grpcurlFlag + " -d '' -format text " + hostname + " grpc.health.v1.Health/Check; do echo waiting for registry; sleep 2; done", - }, - }) - } - } -} - func (feast *FeastServices) setService(svc *corev1.Service, feastType FeastServiceType) error { svc.Labels = feast.getFeastTypeLabels(feastType) if feast.isOpenShiftTls(feastType) { @@ -502,23 +554,27 @@ func (feast *FeastServices) createNewPVC(pvcCreate *feastdevv1alpha1.PvcCreate, return pvc, controllerutil.SetControllerReference(feast.Handler.FeatureStore, pvc, feast.Handler.Scheme) } -func (feast *FeastServices) getServiceConfigs(feastType FeastServiceType) feastdevv1alpha1.ServiceConfigs { +func (feast *FeastServices) getContainerConfigs(feastType FeastServiceType) feastdevv1alpha1.ContainerConfigs { appliedServices := feast.Handler.FeatureStore.Status.Applied.Services switch feastType { case OfflineFeastType: if feast.isOfflinStore() { - return appliedServices.OfflineStore.ServiceConfigs + return appliedServices.OfflineStore.ServerConfigs.ContainerConfigs } case OnlineFeastType: if feast.isOnlinStore() { - return appliedServices.OnlineStore.ServiceConfigs + return appliedServices.OnlineStore.ServerConfigs.ContainerConfigs } case RegistryFeastType: if feast.isLocalRegistry() { - return appliedServices.Registry.Local.ServiceConfigs + return appliedServices.Registry.Local.ServerConfigs.ContainerConfigs + } + case UIFeastType: + if feast.isUI() { + return appliedServices.UI.ContainerConfigs } } - return feastdevv1alpha1.ServiceConfigs{} + return feastdevv1alpha1.ContainerConfigs{} } func (feast *FeastServices) getLogLevelForType(feastType FeastServiceType) *string { @@ -533,9 +589,13 @@ func (feast *FeastServices) getLogLevelForType(feastType FeastServiceType) *stri return &services.OnlineStore.LogLevel } case RegistryFeastType: - if services.Registry != nil && services.Registry.Local.LogLevel != "" { + if feast.isLocalRegistry() && services.Registry.Local.LogLevel != "" { return &services.Registry.Local.LogLevel } + case UIFeastType: + if services.UI != nil && services.UI.LogLevel != "" { + return &services.UI.LogLevel + } } return nil } @@ -595,6 +655,11 @@ func (feast *FeastServices) setServiceHostnames() error { } else if feast.isRemoteRegistry() { return feast.setRemoteRegistryURL() } + if feast.isUI() { + objMeta := feast.initFeastSvc(UIFeastType) + feast.Handler.FeatureStore.Status.ServiceHostnames.UI = objMeta.Name + "." + objMeta.Namespace + domain + + getPortStr(feast.Handler.FeatureStore.Status.Applied.Services.UI.TLS) + } return nil } @@ -695,6 +760,11 @@ func (feast *FeastServices) noLocalServiceConfigured() bool { return !(feast.isLocalRegistry() || feast.isOnlinStore() || feast.isOfflinStore()) } +func (feast *FeastServices) isUI() bool { + appliedServices := feast.Handler.FeatureStore.Status.Applied.Services + return appliedServices != nil && appliedServices.UI != nil +} + func (feast *FeastServices) initFeastDeploy() *appsv1.Deployment { deploy := &appsv1.Deployment{ ObjectMeta: feast.GetObjectMeta(), @@ -727,7 +797,15 @@ func (feast *FeastServices) initPVC(feastType FeastServiceType) *corev1.Persiste return pvc } -func applyOptionalContainerConfigs(container *corev1.Container, optionalConfigs feastdevv1alpha1.OptionalConfigs) { +func (feast *FeastServices) initRoute(feastType FeastServiceType) *routev1.Route { + route := &routev1.Route{ + ObjectMeta: feast.GetObjectMetaType(feastType), + } + route.SetGroupVersionKind(routev1.SchemeGroupVersion.WithKind("Route")) + return route +} + +func applyOptionalCtrConfigs(container *corev1.Container, optionalConfigs feastdevv1alpha1.OptionalCtrConfigs) { if optionalConfigs.Env != nil { container.Env = envOverride(container.Env, *optionalConfigs.Env) } diff --git a/infra/feast-operator/internal/controller/services/services_types.go b/infra/feast-operator/internal/controller/services/services_types.go index 55a5a42e2a7..32ff95588ae 100644 --- a/infra/feast-operator/internal/controller/services/services_types.go +++ b/infra/feast-operator/internal/controller/services/services_types.go @@ -26,6 +26,7 @@ import ( const ( TmpFeatureStoreYamlEnvVar = "TMP_FEATURE_STORE_YAML_BASE64" + feastServerImageVar = "RELATED_IMAGE_FEATURE_SERVER" FeatureStoreYamlCmKey = "feature_store.yaml" EphemeralPath = "/feast-data" FeatureRepoDir = "/feature_repo" @@ -47,6 +48,7 @@ const ( OfflineFeastType FeastServiceType = "offline" OnlineFeastType FeastServiceType = "online" RegistryFeastType FeastServiceType = "registry" + UIFeastType FeastServiceType = "ui" ClientFeastType FeastServiceType = "client" ClientCaFeastType FeastServiceType = "client-ca" @@ -103,6 +105,11 @@ var ( TargetHttpPort: 6570, TargetHttpsPort: 6571, }, + UIFeastType: { + Args: []string{"ui", "-h", "0.0.0.0"}, + TargetHttpPort: 8888, + TargetHttpsPort: 8443, + }, } FeastServiceConditions = map[FeastServiceType]map[metav1.ConditionStatus]metav1.Condition{ @@ -145,6 +152,20 @@ var ( Reason: feastdevv1alpha1.RegistryFailedReason, }, }, + UIFeastType: { + metav1.ConditionTrue: { + Type: feastdevv1alpha1.UIReadyType, + Status: metav1.ConditionTrue, + Reason: feastdevv1alpha1.ReadyReason, + Message: feastdevv1alpha1.UIReadyMessage, + }, + metav1.ConditionFalse: { + Type: feastdevv1alpha1.UIReadyType, + Status: metav1.ConditionFalse, + Reason: feastdevv1alpha1.UIFailedReason, + }, + }, + ClientFeastType: { metav1.ConditionTrue: { Type: feastdevv1alpha1.ClientReadyType, @@ -164,7 +185,7 @@ var ( OidcClientProperties = []OidcPropertyType{OidcClientSecret, OidcUsername, OidcPassword} ) -// feast server types, not the client types +// Feast server types: Reserved only for server types like Online, Offline, and Registry servers. Should not be used for client types like the UI, etc. var feastServerTypes = []FeastServiceType{ RegistryFeastType, OfflineFeastType, diff --git a/infra/feast-operator/internal/controller/services/tls.go b/infra/feast-operator/internal/controller/services/tls.go index c1b07e7e1e6..7d8cbd16933 100644 --- a/infra/feast-operator/internal/controller/services/tls.go +++ b/infra/feast-operator/internal/controller/services/tls.go @@ -37,6 +37,9 @@ func (feast *FeastServices) setTlsDefaults() error { if feast.isLocalRegistry() { tlsDefaults(appliedServices.Registry.Local.TLS) } + if feast.isUI() { + tlsDefaults(appliedServices.UI.TLS) + } return nil } @@ -56,6 +59,14 @@ func (feast *FeastServices) setOpenshiftTls() error { }, } } + if feast.uiOpenshiftTls() { + appliedServices.UI.TLS = &feastdevv1alpha1.TlsConfigs{ + SecretRef: &corev1.LocalObjectReference{ + Name: feast.initFeastSvc(UIFeastType).Name + tlsNameSuffix, + }, + } + } + if feast.localRegistryOpenshiftTls() { appliedServices.Registry.Local.TLS = &feastdevv1alpha1.TlsConfigs{ SecretRef: &corev1.LocalObjectReference{ @@ -79,7 +90,7 @@ func (feast *FeastServices) setOpenshiftTls() error { } func (feast *FeastServices) checkOpenshiftTls() (bool, error) { - if feast.offlineOpenshiftTls() || feast.onlineOpenshiftTls() || feast.localRegistryOpenshiftTls() { + if feast.offlineOpenshiftTls() || feast.onlineOpenshiftTls() || feast.localRegistryOpenshiftTls() || feast.uiOpenshiftTls() { return true, nil } return feast.remoteRegistryOpenshiftTls() @@ -93,7 +104,10 @@ func (feast *FeastServices) isOpenShiftTls(feastType FeastServiceType) (isOpenSh isOpenShift = feast.onlineOpenshiftTls() case RegistryFeastType: isOpenShift = feast.localRegistryOpenshiftTls() + case UIFeastType: + isOpenShift = feast.uiOpenshiftTls() } + return } @@ -112,6 +126,10 @@ func (feast *FeastServices) getTlsConfigs(feastType FeastServiceType) (tls *feas if feast.isLocalRegistry() { tls = appliedServices.Registry.Local.TLS } + case UIFeastType: + if feast.isUI() { + tls = appliedServices.UI.TLS + } } return } @@ -128,6 +146,12 @@ func (feast *FeastServices) onlineOpenshiftTls() bool { feast.isOnlinStore() && feast.Handler.FeatureStore.Spec.Services.OnlineStore.TLS == nil } +// True if running in an openshift cluster and Tls not configured in the service Spec +func (feast *FeastServices) uiOpenshiftTls() bool { + return isOpenShift && + feast.isUI() && feast.Handler.FeatureStore.Spec.Services.UI.TLS == nil +} + // True if running in an openshift cluster and Tls not configured in the service Spec func (feast *FeastServices) localRegistryOpenshiftTls() bool { return isOpenShift && @@ -176,6 +200,7 @@ func (feast *FeastServices) mountTlsConfigs(podSpec *corev1.PodSpec) { feast.mountRegistryClientTls(podSpec) feast.mountTlsConfig(OfflineFeastType, podSpec) feast.mountTlsConfig(OnlineFeastType, podSpec) + feast.mountTlsConfig(UIFeastType, podSpec) } func (feast *FeastServices) mountTlsConfig(feastType FeastServiceType, podSpec *corev1.PodSpec) { diff --git a/infra/feast-operator/internal/controller/services/tls_test.go b/infra/feast-operator/internal/controller/services/tls_test.go index 6c964084204..22102dfb7dc 100644 --- a/infra/feast-operator/internal/controller/services/tls_test.go +++ b/infra/feast-operator/internal/controller/services/tls_test.go @@ -63,6 +63,8 @@ var _ = Describe("TLS Config", func() { Expect(feast.isOpenShiftTls(OfflineFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(OnlineFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(RegistryFeastType)).To(BeFalse()) + Expect(feast.isOpenShiftTls(UIFeastType)).To(BeFalse()) + openshiftTls, err := feast.checkOpenshiftTls() Expect(err).ToNot(HaveOccurred()) Expect(openshiftTls).To(BeFalse()) @@ -79,6 +81,9 @@ var _ = Describe("TLS Config", func() { tls = feast.getTlsConfigs(OnlineFeastType) Expect(tls).To(BeNil()) Expect(tls.IsTLS()).To(BeFalse()) + tls = feast.getTlsConfigs(UIFeastType) + Expect(tls).To(BeNil()) + Expect(tls.IsTLS()).To(BeFalse()) tls = feast.getTlsConfigs(RegistryFeastType) Expect(tls).NotTo(BeNil()) Expect(tls.IsTLS()).To(BeTrue()) @@ -90,7 +95,9 @@ var _ = Describe("TLS Config", func() { Expect(feast.localRegistryTls()).To(BeTrue()) Expect(feast.isOpenShiftTls(OfflineFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(OnlineFeastType)).To(BeFalse()) + Expect(feast.isOpenShiftTls(UIFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(RegistryFeastType)).To(BeTrue()) + openshiftTls, err = feast.checkOpenshiftTls() Expect(err).ToNot(HaveOccurred()) Expect(openshiftTls).To(BeTrue()) @@ -124,12 +131,19 @@ var _ = Describe("TLS Config", func() { Expect(tls.SecretRef.Name).To(Equal("feast-test-registry-tls")) Expect(tls.SecretKeyNames).To(Equal(secretKeyNames)) Expect(tls.IsTLS()).To(BeTrue()) + tls = feast.getTlsConfigs(UIFeastType) + Expect(tls).NotTo(BeNil()) + Expect(tls.SecretRef).NotTo(BeNil()) + Expect(tls.SecretRef.Name).To(Equal("feast-test-ui-tls")) + Expect(tls.SecretKeyNames).To(Equal(secretKeyNames)) + Expect(tls.IsTLS()).To(BeTrue()) Expect(feast.remoteRegistryTls()).To(BeFalse()) Expect(feast.localRegistryTls()).To(BeTrue()) Expect(feast.isOpenShiftTls(OfflineFeastType)).To(BeTrue()) Expect(feast.isOpenShiftTls(OnlineFeastType)).To(BeTrue()) Expect(feast.isOpenShiftTls(RegistryFeastType)).To(BeTrue()) + Expect(feast.isOpenShiftTls(UIFeastType)).To(BeTrue()) openshiftTls, err = feast.checkOpenshiftTls() Expect(err).ToNot(HaveOccurred()) Expect(openshiftTls).To(BeTrue()) @@ -139,24 +153,32 @@ var _ = Describe("TLS Config", func() { err = feast.setDeployment(feastDeploy) Expect(err).ToNot(HaveOccurred()) Expect(feastDeploy.Spec.Template.Spec.InitContainers).To(HaveLen(1)) - Expect(feastDeploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(feastDeploy.Spec.Template.Spec.Containers).To(HaveLen(4)) Expect(feastDeploy.Spec.Template.Spec.Containers[0].Command).To(ContainElements(ContainSubstring("--key"))) Expect(feastDeploy.Spec.Template.Spec.Containers[1].Command).To(ContainElements(ContainSubstring("--key"))) Expect(feastDeploy.Spec.Template.Spec.Containers[2].Command).To(ContainElements(ContainSubstring("--key"))) - Expect(feastDeploy.Spec.Template.Spec.Volumes).To(HaveLen(4)) + Expect(feastDeploy.Spec.Template.Spec.Containers[3].Command).To(ContainElements(ContainSubstring("--key"))) + Expect(feastDeploy.Spec.Template.Spec.Volumes).To(HaveLen(5)) // registry service w/ tls and in an openshift cluster feast.Handler.FeatureStore = minimalFeatureStore() feast.Handler.FeatureStore.Spec.Services = &feastdevv1alpha1.FeatureStoreServices{ OnlineStore: &feastdevv1alpha1.OnlineStore{ + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: &feastdevv1alpha1.TlsConfigs{}, + }, + }, + UI: &feastdevv1alpha1.ServerConfigs{ TLS: &feastdevv1alpha1.TlsConfigs{}, }, Registry: &feastdevv1alpha1.Registry{ Local: &feastdevv1alpha1.LocalRegistryConfig{ - TLS: &feastdevv1alpha1.TlsConfigs{ - SecretRef: &corev1.LocalObjectReference{}, - SecretKeyNames: feastdevv1alpha1.SecretKeyNames{ - TlsCrt: "test.crt", + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: &feastdevv1alpha1.TlsConfigs{ + SecretRef: &corev1.LocalObjectReference{}, + SecretKeyNames: feastdevv1alpha1.SecretKeyNames{ + TlsCrt: "test.crt", + }, }, }, }, @@ -171,17 +193,20 @@ var _ = Describe("TLS Config", func() { tls = feast.getTlsConfigs(OnlineFeastType) Expect(tls).NotTo(BeNil()) Expect(tls.IsTLS()).To(BeFalse()) + tls = feast.getTlsConfigs(UIFeastType) + Expect(tls).NotTo(BeNil()) + Expect(tls.IsTLS()).To(BeFalse()) tls = feast.getTlsConfigs(RegistryFeastType) Expect(tls).NotTo(BeNil()) Expect(tls.IsTLS()).To(BeTrue()) Expect(tls.SecretKeyNames).NotTo(Equal(secretKeyNames)) Expect(getPortStr(tls)).To(Equal("443")) Expect(GetTlsPath(RegistryFeastType)).To(Equal("/tls/registry/")) - Expect(feast.remoteRegistryTls()).To(BeFalse()) Expect(feast.localRegistryTls()).To(BeTrue()) Expect(feast.isOpenShiftTls(OfflineFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(OnlineFeastType)).To(BeFalse()) + Expect(feast.isOpenShiftTls(UIFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(RegistryFeastType)).To(BeFalse()) openshiftTls, err = feast.checkOpenshiftTls() Expect(err).ToNot(HaveOccurred()) @@ -193,10 +218,15 @@ var _ = Describe("TLS Config", func() { feast.Handler.FeatureStore.Spec.Services.OnlineStore.TLS = &feastdevv1alpha1.TlsConfigs{ Disable: &disable, } + feast.Handler.FeatureStore.Spec.Services.UI.TLS = &feastdevv1alpha1.TlsConfigs{ + Disable: &disable, + } feast.Handler.FeatureStore.Spec.Services.Registry = &feastdevv1alpha1.Registry{ Local: &feastdevv1alpha1.LocalRegistryConfig{ - TLS: &feastdevv1alpha1.TlsConfigs{ - Disable: &disable, + ServerConfigs: feastdevv1alpha1.ServerConfigs{ + TLS: &feastdevv1alpha1.TlsConfigs{ + Disable: &disable, + }, }, }, } @@ -219,6 +249,10 @@ var _ = Describe("TLS Config", func() { Expect(tls).NotTo(BeNil()) Expect(tls.IsTLS()).To(BeFalse()) Expect(tls.SecretKeyNames).NotTo(Equal(secretKeyNames)) + tls = feast.getTlsConfigs(UIFeastType) + Expect(tls).NotTo(BeNil()) + Expect(tls.IsTLS()).To(BeFalse()) + Expect(tls.SecretKeyNames).NotTo(Equal(secretKeyNames)) tls = feast.getTlsConfigs(RegistryFeastType) Expect(tls).NotTo(BeNil()) Expect(tls.IsTLS()).To(BeFalse()) @@ -230,6 +264,7 @@ var _ = Describe("TLS Config", func() { Expect(feast.localRegistryTls()).To(BeFalse()) Expect(feast.isOpenShiftTls(OfflineFeastType)).To(BeTrue()) Expect(feast.isOpenShiftTls(OnlineFeastType)).To(BeFalse()) + Expect(feast.isOpenShiftTls(UIFeastType)).To(BeFalse()) Expect(feast.isOpenShiftTls(RegistryFeastType)).To(BeFalse()) openshiftTls, err = feast.checkOpenshiftTls() Expect(err).ToNot(HaveOccurred()) @@ -249,11 +284,17 @@ var _ = Describe("TLS Config", func() { Expect(onlineSvc.Annotations).To(BeEmpty()) Expect(onlineSvc.Spec.Ports[0].Name).To(Equal(HttpScheme)) + uiSvc := feast.initFeastSvc(UIFeastType) + err = feast.setService(uiSvc, UIFeastType) + Expect(err).ToNot(HaveOccurred()) + Expect(uiSvc.Annotations).To(BeEmpty()) + Expect(uiSvc.Spec.Ports[0].Name).To(Equal(HttpScheme)) + // check k8s deployment objects feastDeploy = feast.initFeastDeploy() err = feast.setDeployment(feastDeploy) Expect(err).ToNot(HaveOccurred()) - Expect(feastDeploy.Spec.Template.Spec.Containers).To(HaveLen(3)) + Expect(feastDeploy.Spec.Template.Spec.Containers).To(HaveLen(4)) Expect(GetOfflineContainer(*feastDeploy)).NotTo(BeNil()) Expect(feastDeploy.Spec.Template.Spec.Volumes).To(HaveLen(2)) @@ -263,6 +304,9 @@ var _ = Describe("TLS Config", func() { Expect(GetOfflineContainer(*feastDeploy).VolumeMounts).To(HaveLen(2)) Expect(GetOnlineContainer(*feastDeploy).Command).NotTo(ContainElements(ContainSubstring("--key"))) Expect(GetOnlineContainer(*feastDeploy).VolumeMounts).To(HaveLen(1)) + Expect(GetUIContainer(*feastDeploy).Command).NotTo(ContainElements(ContainSubstring("--key"))) + Expect(GetUIContainer(*feastDeploy).VolumeMounts).To(HaveLen(1)) + }) }) }) diff --git a/infra/feast-operator/internal/controller/services/util.go b/infra/feast-operator/internal/controller/services/util.go index b39a851c14b..fc6f57c780f 100644 --- a/infra/feast-operator/internal/controller/services/util.go +++ b/infra/feast-operator/internal/controller/services/util.go @@ -11,7 +11,6 @@ import ( feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - v1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -118,7 +117,7 @@ func ApplyDefaultsToStatus(cr *feastdevv1alpha1.FeatureStore) { ensurePVCDefaults(services.Registry.Local.Persistence.FilePersistence.PvcConfig, RegistryFeastType) } - setServiceDefaultConfigs(&services.Registry.Local.ServiceConfigs.DefaultConfigs) + setDefaultCtrConfigs(&services.Registry.Local.ServerConfigs.ContainerConfigs.DefaultCtrConfigs) } else if services.Registry.Remote.FeastRef != nil && len(services.Registry.Remote.FeastRef.Namespace) == 0 { services.Registry.Remote.FeastRef.Namespace = cr.Namespace } @@ -140,7 +139,7 @@ func ApplyDefaultsToStatus(cr *feastdevv1alpha1.FeatureStore) { ensurePVCDefaults(services.OfflineStore.Persistence.FilePersistence.PvcConfig, OfflineFeastType) } - setServiceDefaultConfigs(&services.OfflineStore.ServiceConfigs.DefaultConfigs) + setDefaultCtrConfigs(&services.OfflineStore.ServerConfigs.ContainerConfigs.DefaultCtrConfigs) } if services.OnlineStore != nil { @@ -160,11 +159,15 @@ func ApplyDefaultsToStatus(cr *feastdevv1alpha1.FeatureStore) { ensurePVCDefaults(services.OnlineStore.Persistence.FilePersistence.PvcConfig, OnlineFeastType) } - setServiceDefaultConfigs(&services.OnlineStore.ServiceConfigs.DefaultConfigs) + setDefaultCtrConfigs(&services.OnlineStore.ServerConfigs.ContainerConfigs.DefaultCtrConfigs) + } + if services.UI != nil { + + setDefaultCtrConfigs(&services.UI.ContainerConfigs.DefaultCtrConfigs) } } -func setServiceDefaultConfigs(defaultConfigs *feastdevv1alpha1.DefaultConfigs) { +func setDefaultCtrConfigs(defaultConfigs *feastdevv1alpha1.DefaultCtrConfigs) { if defaultConfigs.Image == nil { img := getFeatureServerImage() defaultConfigs.Image = &img @@ -172,19 +175,12 @@ func setServiceDefaultConfigs(defaultConfigs *feastdevv1alpha1.DefaultConfigs) { } func getFeatureServerImage() string { - if img, exists := os.LookupEnv("RELATED_IMAGE_FEATURE_SERVER"); exists { + if img, exists := os.LookupEnv(feastServerImageVar); exists { return img } return DefaultImage } -func getGrpcCurlImage() string { - if img, exists := os.LookupEnv("RELATED_IMAGE_GRPC_CURL"); exists { - return img - } - return "fullstorydev/grpcurl:v1.9.1-alpine" -} - func checkOfflineStoreFilePersistenceType(value string) error { if slices.Contains(feastdevv1alpha1.ValidOfflineStoreFilePersistenceTypes, value) { return nil @@ -192,12 +188,12 @@ func checkOfflineStoreFilePersistenceType(value string) error { return fmt.Errorf("invalid file type %s for offline store", value) } -func ensureRequestedStorage(resources *v1.VolumeResourceRequirements, requestedStorage string) { +func ensureRequestedStorage(resources *corev1.VolumeResourceRequirements, requestedStorage string) { if resources.Requests == nil { - resources.Requests = v1.ResourceList{} + resources.Requests = corev1.ResourceList{} } - if _, ok := resources.Requests[v1.ResourceStorage]; !ok { - resources.Requests[v1.ResourceStorage] = resource.MustParse(requestedStorage) + if _, ok := resources.Requests[corev1.ResourceStorage]; !ok { + resources.Requests[corev1.ResourceStorage] = resource.MustParse(requestedStorage) } } @@ -396,6 +392,11 @@ func GetOfflineContainer(deployment appsv1.Deployment) *corev1.Container { return container } +func GetUIContainer(deployment appsv1.Deployment) *corev1.Container { + _, container := getContainerByType(UIFeastType, deployment.Spec.Template.Spec) + return container +} + func GetOnlineContainer(deployment appsv1.Deployment) *corev1.Container { _, container := getContainerByType(OnlineFeastType, deployment.Spec.Template.Spec) return container diff --git a/infra/feast-operator/test/e2e/e2e_test.go b/infra/feast-operator/test/e2e/e2e_test.go index e55ac0156a7..b2773f579f0 100644 --- a/infra/feast-operator/test/e2e/e2e_test.go +++ b/infra/feast-operator/test/e2e/e2e_test.go @@ -18,6 +18,7 @@ package e2e import ( "fmt" + "os" "os/exec" "time" @@ -36,70 +37,79 @@ const ( var _ = Describe("controller", Ordered, func() { BeforeAll(func() { - By("creating manager namespace") - cmd := exec.Command("kubectl", "create", "ns", feastControllerNamespace) - _, _ = utils.Run(cmd) - var err error - // projectimage stores the name of the image used in the example - var projectimage = "localhost/feast-operator:v0.0.1" - - By("building the manager(Operator) image") - cmd = exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage)) - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("loading the the manager(Operator) image on Kind") - err = utils.LoadImageToKindClusterWithName(projectimage) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("building the feast image") - cmd = exec.Command("make", "feast-ci-dev-docker-img") - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - // this image will be built in above make target. - var feastImage = "feastdev/feature-server:dev" - var feastLocalImage = "localhost/feastdev/feature-server:dev" - - By("Tag the local feast image for the integration tests") - cmd = exec.Command("docker", "image", "tag", feastImage, feastLocalImage) - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("loading the the feast image on Kind cluster") - err = utils.LoadImageToKindClusterWithName(feastLocalImage) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("installing CRDs") - cmd = exec.Command("make", "install") - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("deploying the controller-manager") - cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectimage)) - _, err = utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("Validating that the controller-manager deployment is in available state") - err = checkIfDeploymentExistsAndAvailable(feastControllerNamespace, controllerDeploymentName, timeout) - Expect(err).ToNot(HaveOccurred(), fmt.Sprintf( - "Deployment %s is not available but expected to be available. \nError: %v\n", - controllerDeploymentName, err, - )) - fmt.Printf("Feast Control Manager Deployment %s is available\n", controllerDeploymentName) + _, isRunOnOpenShiftCI := os.LookupEnv("RUN_ON_OPENSHIFT_CI") + if !isRunOnOpenShiftCI { + By("creating manager namespace") + cmd := exec.Command("kubectl", "create", "ns", feastControllerNamespace) + _, _ = utils.Run(cmd) + + var err error + // projectimage stores the name of the image used in the example + var projectimage = "localhost/feast-operator:v0.0.1" + + By("building the manager(Operator) image") + cmd = exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage)) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("loading the the manager(Operator) image on Kind") + err = utils.LoadImageToKindClusterWithName(projectimage) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + // this image will be built in above make target. + var feastImage = "feastdev/feature-server:dev" + var feastLocalImage = "localhost/feastdev/feature-server:dev" + + By("building the feast image") + cmd = exec.Command("make", "feast-ci-dev-docker-img") + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("Tag the local feast image for the integration tests") + cmd = exec.Command("docker", "image", "tag", feastImage, feastLocalImage) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("loading the the feast image on Kind cluster") + err = utils.LoadImageToKindClusterWithName(feastLocalImage) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("installing CRDs") + cmd = exec.Command("make", "install") + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("deploying the controller-manager") + cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectimage), fmt.Sprintf("FS_IMG=%s", feastLocalImage)) + _, err = utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + By("Validating that the controller-manager deployment is in available state") + err = checkIfDeploymentExistsAndAvailable(feastControllerNamespace, controllerDeploymentName, timeout) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf( + "Deployment %s is not available but expected to be available. \nError: %v\n", + controllerDeploymentName, err, + )) + fmt.Printf("Feast Control Manager Deployment %s is available\n", controllerDeploymentName) + } }) AfterAll(func() { // Add any post clean up code here. - By("Uninstalling the feast CRD") - cmd := exec.Command("kubectl", "delete", "deployment", controllerDeploymentName, "-n", feastControllerNamespace) - _, err := utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) + _, isRunOnOpenShiftCI := os.LookupEnv("RUN_ON_OPENSHIFT_CI") + if !isRunOnOpenShiftCI { + By("Uninstalling the feast CRD") + cmd := exec.Command("kubectl", "delete", "deployment", controllerDeploymentName, "-n", feastControllerNamespace) + _, err := utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + } }) Context("Operator E2E Tests", func() { It("Should be able to deploy and run a default feature store CR successfully", func() { By("deploying the Simple Feast Custom Resource to Kubernetes") namespace := "default" + cmd := exec.Command("kubectl", "apply", "-f", "test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml", "-n", namespace) _, cmdOutputerr := utils.Run(cmd) @@ -120,8 +130,8 @@ var _ = Describe("controller", Ordered, func() { namespace := "default" cmd := exec.Command("kubectl", "apply", "-f", "test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml", "-n", namespace) - _, cmdOutputerr := utils.Run(cmd) - ExpectWithOffset(1, cmdOutputerr).NotTo(HaveOccurred()) + _, cmdOutputErr := utils.Run(cmd) + ExpectWithOffset(1, cmdOutputErr).NotTo(HaveOccurred()) featureStoreName := "simple-feast-setup" validateTheFeatureStoreCustomResource(namespace, featureStoreName, timeout) @@ -134,8 +144,8 @@ var _ = Describe("controller", Ordered, func() { By("deploying the Simple Feast remote registry Custom Resource on Kubernetes") cmd = exec.Command("kubectl", "apply", "-f", "test/testdata/feast_integration_test_crs/v1alpha1_remote_registry_featurestore.yaml", "-n", remoteRegistryNs) - _, cmdOutputerr = utils.Run(cmd) - ExpectWithOffset(1, cmdOutputerr).NotTo(HaveOccurred()) + _, cmdOutputErr = utils.Run(cmd) + ExpectWithOffset(1, cmdOutputErr).NotTo(HaveOccurred()) remoteFeatureStoreName := "simple-feast-remote-setup" @@ -144,14 +154,14 @@ var _ = Describe("controller", Ordered, func() { By("deleting the feast remote registry deployment") cmd = exec.Command("kubectl", "delete", "-f", "test/testdata/feast_integration_test_crs/v1alpha1_remote_registry_featurestore.yaml", "-n", remoteRegistryNs) - _, cmdOutputerr = utils.Run(cmd) - ExpectWithOffset(1, cmdOutputerr).NotTo(HaveOccurred()) + _, cmdOutputErr = utils.Run(cmd) + ExpectWithOffset(1, cmdOutputErr).NotTo(HaveOccurred()) By("deleting the feast deployment") cmd = exec.Command("kubectl", "delete", "-f", "test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml", "-n", namespace) - _, cmdOutputerr = utils.Run(cmd) - ExpectWithOffset(1, cmdOutputerr).NotTo(HaveOccurred()) + _, cmdOutputErr = utils.Run(cmd) + ExpectWithOffset(1, cmdOutputErr).NotTo(HaveOccurred()) }) }) }) @@ -167,6 +177,7 @@ func validateTheFeatureStoreCustomResource(namespace string, featureStoreName st feastK8sResourceNames := []string{ feastResourceName + "-online", feastResourceName + "-offline", + feastResourceName + "-ui", } if !hasRemoteRegistry { diff --git a/infra/feast-operator/test/e2e/test_util.go b/infra/feast-operator/test/e2e/test_util.go index 017690a0ec9..743f04afc54 100644 --- a/infra/feast-operator/test/e2e/test_util.go +++ b/infra/feast-operator/test/e2e/test_util.go @@ -157,40 +157,53 @@ func checkIfKubernetesServiceExists(namespace, serviceName string) error { } func isFeatureStoreHavingRemoteRegistry(namespace, featureStoreName string) (bool, error) { - cmd := exec.Command("kubectl", "get", "featurestore", featureStoreName, "-n", namespace, - "-o=jsonpath='{.spec.services.registry}'") + timeout := time.Second * 30 + interval := time.Second * 2 // Poll every 2 seconds + startTime := time.Now() + + for time.Since(startTime) < timeout { + cmd := exec.Command("kubectl", "get", "featurestore", featureStoreName, "-n", namespace, + "-o=jsonpath='{.status.applied.services.registry}'") + + output, err := cmd.Output() + if err != nil { + // Retry only on transient errors + if _, ok := err.(*exec.ExitError); ok { + time.Sleep(interval) + continue + } + return false, err // Return immediately on non-transient errors + } - // Capture the output - output, err := cmd.Output() - if err != nil { - return false, err // Return false on command execution failure - } + // Convert output to string and trim any extra spaces + result := strings.TrimSpace(string(output)) - // Convert output to string and trim any extra spaces - result := strings.TrimSpace(string(output)) + // Remove single quotes if present + if strings.HasPrefix(result, "'") && strings.HasSuffix(result, "'") { + result = strings.Trim(result, "'") + } - // Remove single quotes if present - if strings.HasPrefix(result, "'") && strings.HasSuffix(result, "'") { - result = strings.Trim(result, "'") - } + if result == "" { + time.Sleep(interval) // Retry if result is empty + continue + } - if result == "" { - return false, errors.New("kubectl get featurestore command returned empty output") - } + // Parse the JSON into a map + var registryConfig v1alpha1.Registry + if err := json.Unmarshal([]byte(result), ®istryConfig); err != nil { + return false, err // Return false on JSON parsing failure + } - // Parse the JSON into a map - var registryConfig v1alpha1.Registry - if err := json.Unmarshal([]byte(result), ®istryConfig); err != nil { - return false, err // Return false on JSON parsing failure - } + if registryConfig.Remote == nil { + return false, nil + } - if registryConfig.Remote == nil { - return false, nil - } + hasHostname := registryConfig.Remote.Hostname != nil + hasValidFeastRef := registryConfig.Remote.FeastRef != nil && + registryConfig.Remote.FeastRef.Name != "" - hasHostname := registryConfig.Remote.Hostname != nil - hasValidFeastRef := registryConfig.Remote.FeastRef != nil && - registryConfig.Remote.FeastRef.Name != "" + return hasHostname || hasValidFeastRef, nil + } - return hasHostname || hasValidFeastRef, nil + return false, errors.New("timeout waiting for featurestore registry status to be ready") } diff --git a/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml b/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml index 0252a5fecf5..9ce14113de5 100644 --- a/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml +++ b/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_default_featurestore.yaml @@ -5,10 +5,6 @@ metadata: spec: feastProject: my_project services: - onlineStore: - image: 'localhost/feastdev/feature-server:dev' - offlineStore: - image: 'localhost/feastdev/feature-server:dev' - registry: - local: - image: 'localhost/feastdev/feature-server:dev' + onlineStore: {} + offlineStore: {} + ui: {} diff --git a/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_remote_registry_featurestore.yaml b/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_remote_registry_featurestore.yaml index 61c010f0576..a8845d2963f 100644 --- a/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_remote_registry_featurestore.yaml +++ b/infra/feast-operator/test/testdata/feast_integration_test_crs/v1alpha1_remote_registry_featurestore.yaml @@ -5,10 +5,9 @@ metadata: spec: feastProject: my_project services: - onlineStore: - image: 'localhost/feastdev/feature-server:dev' - offlineStore: - image: 'localhost/feastdev/feature-server:dev' + onlineStore: {} + offlineStore: {} + ui: {} registry: remote: feastRef: diff --git a/infra/scripts/release/files_to_bump.txt b/infra/scripts/release/files_to_bump.txt index 0e0ac0bce6d..0ee09ea2c1f 100644 --- a/infra/scripts/release/files_to_bump.txt +++ b/infra/scripts/release/files_to_bump.txt @@ -14,8 +14,7 @@ infra/feast-helm-operator/Makefile 6 infra/feast-helm-operator/config/manager/kustomization.yaml 8 infra/feast-operator/Makefile 6 infra/feast-operator/config/manager/kustomization.yaml 8 -infra/feast-operator/config/default/manager_related_images_patch.yaml 5 -infra/feast-operator/config/overlays/odh/params.env 1 +infra/feast-operator/config/overlays/odh/params.env 1 2 infra/feast-operator/api/feastversion/version.go 20 java/pom.xml 38 ui/package.json 3 diff --git a/java/pom.xml b/java/pom.xml index 8173385e0c9..21d7228af1a 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -35,7 +35,7 @@ - 0.43.0 + 0.44.0 https://github.com/feast-dev/feast UTF-8 diff --git a/sdk/python/feast/errors.py b/sdk/python/feast/errors.py index 84a9f12ec4f..8c72422f44e 100644 --- a/sdk/python/feast/errors.py +++ b/sdk/python/feast/errors.py @@ -32,7 +32,7 @@ def __str__(self) -> str: def __repr__(self) -> str: if hasattr(self, "__overridden_message__"): - return f"{type(self).__name__}('{getattr(self,'__overridden_message__')}')" + return f"{type(self).__name__}('{getattr(self, '__overridden_message__')}')" return super().__repr__() def to_error_detail(self) -> str: diff --git a/sdk/python/feast/feature_server.py b/sdk/python/feast/feature_server.py index e22ff43b9c2..ed742bcb98f 100644 --- a/sdk/python/feast/feature_server.py +++ b/sdk/python/feast/feature_server.py @@ -74,6 +74,7 @@ class GetOnlineFeaturesRequest(BaseModel): feature_service: Optional[str] = None features: Optional[List[str]] = None full_feature_names: bool = False + query_embedding: Optional[List[float]] = None def _get_features(request: GetOnlineFeaturesRequest, store: "feast.FeatureStore"): @@ -104,7 +105,6 @@ def _get_features(request: GetOnlineFeaturesRequest, store: "feast.FeatureStore" resource=od_feature_view, actions=[AuthzedAction.READ_ONLINE] ) features = request.features # type: ignore - return features @@ -177,6 +177,39 @@ async def get_online_features(request: GetOnlineFeaturesRequest) -> Dict[str, An ) return response_dict + @app.post( + "/retrieve-online-documents", + dependencies=[Depends(inject_user_details)], + ) + async def retrieve_online_documents( + request: GetOnlineFeaturesRequest, + ) -> Dict[str, Any]: + logger.warn( + "This endpoint is in alpha and will be moved to /get-online-features when stable." + ) + # Initialize parameters for FeatureStore.retrieve_online_documents_v2(...) call + features = await run_in_threadpool(_get_features, request, store) + + read_params = dict( + features=features, + entity_rows=request.entities, + full_feature_names=request.full_feature_names, + query=request.query_embedding, + ) + + response = await run_in_threadpool( + lambda: store.retrieve_online_documents_v2(**read_params) # type: ignore + ) + + # Convert the Protobuf object to JSON and return it + response_dict = await run_in_threadpool( + MessageToDict, + response.proto, + preserving_proto_field_name=True, + float_precision=18, + ) + return response_dict + @app.post("/push", dependencies=[Depends(inject_user_details)]) async def push(request: PushFeaturesRequest) -> None: df = pd.DataFrame(request.df) diff --git a/sdk/python/feast/feature_store.py b/sdk/python/feast/feature_store.py index 98db710d7f3..0f092538cf0 100644 --- a/sdk/python/feast/feature_store.py +++ b/sdk/python/feast/feature_store.py @@ -62,6 +62,7 @@ from feast.feast_object import FeastObject from feast.feature_service import FeatureService from feast.feature_view import DUMMY_ENTITY, DUMMY_ENTITY_NAME, FeatureView +from feast.field import Field from feast.inference import ( update_data_sources_with_inferred_event_timestamp_col, update_feature_views_with_inferred_features_and_entities, @@ -865,8 +866,7 @@ def apply( views_to_update = [ ob for ob in objects - if - ( + if ( # BFVs are not handled separately from FVs right now. (isinstance(ob, FeatureView) or isinstance(ob, BatchFeatureView)) and not isinstance(ob, StreamFeatureView) @@ -1834,7 +1834,6 @@ def retrieve_online_documents( top_k, distance_metric, ) - # TODO currently not return the vector value since it is same as feature value, if embedding is supported, # the feature value can be raw text before embedded entity_key_vals = [feature[1] for feature in document_features] @@ -1862,6 +1861,66 @@ def retrieve_online_documents( ) return OnlineResponse(online_features_response) + def retrieve_online_documents_v2( + self, + query: Union[str, List[float]], + top_k: int, + features: List[str], + distance_metric: Optional[str] = "L2", + ) -> OnlineResponse: + """ + Retrieves the top k closest document features. Note, embeddings are a subset of features. + + Args: + features: The list of features that should be retrieved from the online document store. These features can be + specified either as a list of string document feature references or as a feature service. String feature + references must have format "feature_view:feature", e.g, "document_fv:document_embeddings". + query: The query to retrieve the closest document features for. + top_k: The number of closest document features to retrieve. + distance_metric: The distance metric to use for retrieval. + """ + if isinstance(query, str): + raise ValueError( + "Using embedding functionality is not supported for document retrieval. Please embed the query before calling retrieve_online_documents." + ) + + ( + available_feature_views, + _, + ) = utils._get_feature_views_to_use( + registry=self._registry, + project=self.project, + features=features, + allow_cache=True, + hide_dummy_entity=False, + ) + feature_view_set = set() + for feature in features: + feature_view_name = feature.split(":")[0] + feature_view = self.get_feature_view(feature_view_name) + feature_view_set.add(feature_view.name) + if len(feature_view_set) > 1: + raise ValueError("Document retrieval only supports a single feature view.") + requested_features = [ + f.split(":")[1] for f in features if isinstance(f, str) and ":" in f + ] + + requested_feature_view = available_feature_views[0] + if not requested_feature_view: + raise ValueError( + f"Feature view {requested_feature_view} not found in the registry." + ) + + provider = self._get_provider() + return self._retrieve_from_online_store_v2( + provider, + requested_feature_view, + requested_features, + query, + top_k, + distance_metric, + ) + def _retrieve_from_online_store( self, provider: Provider, @@ -1879,6 +1938,10 @@ def _retrieve_from_online_store( """ Search and return document features from the online document store. """ + vector_field_metadata = _get_feature_view_vector_field_metadata(table) + if vector_field_metadata: + distance_metric = vector_field_metadata.vector_search_metric + documents = provider.retrieve_online_documents( config=self.config, table=table, @@ -1892,7 +1955,7 @@ def _retrieve_from_online_store( read_row_protos = [] row_ts_proto = Timestamp() - for row_ts, entity_key, feature_val, vector_value, distance_val in documents: + for row_ts, entity_key, feature_val, vector_value, distance_val in documents: # type: ignore[misc] # Reset timestamp to default or update if row_ts is not None if row_ts is not None: row_ts_proto.FromDatetime(row_ts) @@ -1917,6 +1980,70 @@ def _retrieve_from_online_store( ) return read_row_protos + def _retrieve_from_online_store_v2( + self, + provider: Provider, + table: FeatureView, + requested_features: List[str], + query: List[float], + top_k: int, + distance_metric: Optional[str], + ) -> OnlineResponse: + """ + Search and return document features from the online document store. + """ + vector_field_metadata = _get_feature_view_vector_field_metadata(table) + if vector_field_metadata: + distance_metric = vector_field_metadata.vector_search_metric + + documents = provider.retrieve_online_documents_v2( + config=self.config, + table=table, + requested_features=requested_features, + query=query, + top_k=top_k, + distance_metric=distance_metric, + ) + + entity_key_dict: Dict[str, List[ValueProto]] = {} + datevals, entityvals, list_of_feature_dicts = [], [], [] + for row_ts, entity_key, feature_dict in documents: # type: ignore[misc] + datevals.append(row_ts) + entityvals.append(entity_key) + list_of_feature_dicts.append(feature_dict) + if entity_key: + for key, value in zip(entity_key.join_keys, entity_key.entity_values): + python_value = value + if key not in entity_key_dict: + entity_key_dict[key] = [] + entity_key_dict[key].append(python_value) + + table_entity_values, idxs = utils._get_unique_entities_from_values( + entity_key_dict, + ) + + features_to_request: List[str] = [] + if requested_features: + features_to_request = requested_features + ["distance"] + else: + features_to_request = ["distance"] + feature_data = utils._convert_rows_to_protobuf( + requested_features=features_to_request, + read_rows=list(zip(datevals, list_of_feature_dicts)), + ) + + online_features_response = GetOnlineFeaturesResponse(results=[]) + utils._populate_response_from_feature_data( + feature_data=feature_data, + indexes=idxs, + online_features_response=online_features_response, + full_feature_names=False, + requested_features=features_to_request, + table=table, + ) + + return OnlineResponse(online_features_response) + def serve( self, host: str, @@ -2031,9 +2158,9 @@ def write_logged_features( if not isinstance(source, FeatureService): raise ValueError("Only feature service is currently supported as a source") - assert ( - source.logging_config is not None - ), "Feature service must be configured with logging config in order to use this functionality" + assert source.logging_config is not None, ( + "Feature service must be configured with logging config in order to use this functionality" + ) assert isinstance(logs, (pa.Table, Path)) @@ -2266,3 +2393,16 @@ def _validate_data_sources(data_sources: List[DataSource]): raise DataSourceRepeatNamesException(case_insensitive_ds_name) else: ds_names.add(case_insensitive_ds_name) + + +def _get_feature_view_vector_field_metadata( + feature_view: FeatureView, +) -> Optional[Field]: + vector_fields = [field for field in feature_view.schema if field.vector_index] + if len(vector_fields) > 1: + raise ValueError( + f"Feature view {feature_view.name} has multiple vector fields. Only one vector field per feature view is supported." + ) + if not vector_fields: + return None + return vector_fields[0] diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile index fd3c258d892..6e1c81b654f 100644 --- a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile +++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi9/python-311:9.5 +FROM registry.access.redhat.com/ubi8/python-311:1 ARG VERSION RUN pip install "feast[aws,gcp,snowflake,redis,go,mysql,postgres,opentelemetry,grpcio,k8s,duckdb,milvus]"==${VERSION} diff --git a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev index add170fb1dc..f5cef2ad9f3 100644 --- a/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev +++ b/sdk/python/feast/infra/feature_servers/multicloud/Dockerfile.dev @@ -1,13 +1,30 @@ -FROM registry.access.redhat.com/ubi9/python-311:9.5 +FROM registry.access.redhat.com/ubi8/python-311:1 + +USER 0 +RUN npm install -g yarn yalc && rm -rf .npm +USER default COPY --chown=default . ${APP_ROOT}/src + +WORKDIR ${APP_ROOT}/src/ui +RUN npm install && \ + npm run build:lib-dev && \ + rm -rf node_modules && \ + npm cache clean --force + +WORKDIR ${APP_ROOT}/src/sdk/python/feast/ui +RUN yalc add @feast-dev/feast-ui && \ + git diff package.json && \ + yarn install && \ + npm run build --omit=dev && \ + rm -rf node_modules && \ + npm cache clean --force && \ + yarn cache clean --all + +WORKDIR ${APP_ROOT}/src RUN pip install --no-cache-dir pip-tools && \ make install-python-ci-dependencies && \ pip uninstall -y pip-tools -RUN npm install -S yarn -ENV PATH ${PATH}:${APP_ROOT}/src/node_modules/yarn/bin -RUN make build-ui && yarn cache clean --all - # modify permissions to support running with a random uid RUN chmod g+w $(python -c "import feast.ui as ui; print(ui.__path__)" | tr -d "[']")/build/projects-list.json diff --git a/sdk/python/feast/infra/key_encoding_utils.py b/sdk/python/feast/infra/key_encoding_utils.py index 18127896bd5..ce2692a4955 100644 --- a/sdk/python/feast/infra/key_encoding_utils.py +++ b/sdk/python/feast/infra/key_encoding_utils.py @@ -83,6 +83,8 @@ def serialize_entity_key( ) output: List[bytes] = [] + if entity_key_serialization_version > 2: + output.append(struct.pack(" 2: @@ -122,7 +124,11 @@ def deserialize_entity_key( offset = 0 keys = [] values = [] - while offset < len(serialized_entity_key): + + num_keys = struct.unpack_from("= '"+start_date.strftime('%Y-%m-%d')+"' AND "+date_partition_column+" <= '"+end_date.strftime('%Y-%m-%d')+"' " if date_partition_column != "" and date_partition_column is not None else ''} + WHERE {timestamp_field} BETWEEN TIMESTAMP '{start_date.strftime("%Y-%m-%d %H:%M:%S")}' AND TIMESTAMP '{end_date.strftime("%Y-%m-%d %H:%M:%S")}' + {"AND " + date_partition_column + " >= '" + start_date.strftime("%Y-%m-%d") + "' AND " + date_partition_column + " <= '" + end_date.strftime("%Y-%m-%d") + "' " if date_partition_column != "" and date_partition_column is not None else ""} ) WHERE _feast_row = 1 """ @@ -151,7 +151,7 @@ def pull_all_from_table_or_query( SELECT {field_string} FROM {from_expression} WHERE {timestamp_field} BETWEEN TIMESTAMP '{start_date.astimezone(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]}' AND TIMESTAMP '{end_date.astimezone(tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]}' - {"AND "+date_partition_column+" >= '"+start_date.strftime('%Y-%m-%d')+"' AND "+date_partition_column+" <= '"+end_date.strftime('%Y-%m-%d')+"' " if date_partition_column != "" and date_partition_column is not None else ''} + {"AND " + date_partition_column + " >= '" + start_date.strftime("%Y-%m-%d") + "' AND " + date_partition_column + " <= '" + end_date.strftime("%Y-%m-%d") + "' " if date_partition_column != "" and date_partition_column is not None else ""} """ return AthenaRetrievalJob( diff --git a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py index 5239cfb474d..ec6b713941c 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py +++ b/sdk/python/feast/infra/offline_stores/contrib/postgres_offline_store/postgres.py @@ -156,7 +156,7 @@ def query_generator() -> Iterator[str]: # Hack for query_context.entity_selections to support uppercase in columns for context in query_context_dict: context["entity_selections"] = [ - f""""{entity_selection.replace(' AS ', '" AS "')}\"""" + f""""{entity_selection.replace(" AS ", '" AS "')}\"""" for entity_selection in context["entity_selections"] ] @@ -370,7 +370,7 @@ def build_point_in_time_query( final_output_feature_names.extend( [ ( - f'{fv["name"]}__{fv["field_mapping"].get(feature, feature)}' + f"{fv['name']}__{fv['field_mapping'].get(feature, feature)}" if full_feature_names else fv["field_mapping"].get(feature, feature) ) diff --git a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py index 4b501886327..41c180f5c3c 100644 --- a/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py +++ b/sdk/python/feast/infra/offline_stores/contrib/spark_offline_store/spark.py @@ -111,7 +111,7 @@ def pull_latest_from_table_or_query( SELECT {fields_as_string}, ROW_NUMBER() OVER({partition_by_join_key_string} ORDER BY {timestamp_desc_string}) AS feast_row_ FROM {from_expression} t1 - WHERE {timestamp_field} BETWEEN TIMESTAMP('{start_date_str}') AND TIMESTAMP('{end_date_str}'){" AND "+date_partition_column+" >= '"+start_date.strftime('%Y-%m-%d')+"' AND "+date_partition_column+" <= '"+end_date.strftime('%Y-%m-%d')+"' " if date_partition_column != "" and date_partition_column is not None else ''} + WHERE {timestamp_field} BETWEEN TIMESTAMP('{start_date_str}') AND TIMESTAMP('{end_date_str}'){" AND " + date_partition_column + " >= '" + start_date.strftime("%Y-%m-%d") + "' AND " + date_partition_column + " <= '" + end_date.strftime("%Y-%m-%d") + "' " if date_partition_column != "" and date_partition_column is not None else ""} ) t2 WHERE feast_row_ = 1 """ diff --git a/sdk/python/feast/infra/offline_stores/dask.py b/sdk/python/feast/infra/offline_stores/dask.py index d26e8609bae..51a3debb5e2 100644 --- a/sdk/python/feast/infra/offline_stores/dask.py +++ b/sdk/python/feast/infra/offline_stores/dask.py @@ -193,9 +193,7 @@ def evaluate_historical_retrieval(): ): # Make sure all event timestamp fields are tz-aware. We default tz-naive fields to UTC entity_df_with_features[entity_df_event_timestamp_col] = ( - entity_df_with_features[ - entity_df_event_timestamp_col - ].apply( + entity_df_with_features[entity_df_event_timestamp_col].apply( lambda x: x if x.tzinfo is not None else x.replace(tzinfo=timezone.utc) diff --git a/sdk/python/feast/infra/offline_stores/offline_utils.py b/sdk/python/feast/infra/offline_stores/offline_utils.py index 2076f977acc..5b12636782f 100644 --- a/sdk/python/feast/infra/offline_stores/offline_utils.py +++ b/sdk/python/feast/infra/offline_stores/offline_utils.py @@ -118,6 +118,10 @@ def get_feature_view_query_context( query_context = [] for feature_view, features in feature_views_to_feature_map.items(): + reverse_field_mapping = { + v: k for k, v in feature_view.batch_source.field_mapping.items() + } + join_keys: List[str] = [] entity_selections: List[str] = [] for entity_column in feature_view.entity_columns: @@ -125,16 +129,16 @@ def get_feature_view_query_context( entity_column.name, entity_column.name ) join_keys.append(join_key) - entity_selections.append(f"{entity_column.name} AS {join_key}") + entity_selections.append( + f"{reverse_field_mapping.get(entity_column.name, entity_column.name)} " + f"AS {join_key}" + ) if isinstance(feature_view.ttl, timedelta): ttl_seconds = int(feature_view.ttl.total_seconds()) else: ttl_seconds = 0 - reverse_field_mapping = { - v: k for k, v in feature_view.batch_source.field_mapping.items() - } features = [reverse_field_mapping.get(feature, feature) for feature in features] timestamp_field = reverse_field_mapping.get( feature_view.batch_source.timestamp_field, diff --git a/sdk/python/feast/infra/offline_stores/snowflake_source.py b/sdk/python/feast/infra/offline_stores/snowflake_source.py index 1d43fecc03c..b4fcd89af7e 100644 --- a/sdk/python/feast/infra/offline_stores/snowflake_source.py +++ b/sdk/python/feast/infra/offline_stores/snowflake_source.py @@ -285,15 +285,15 @@ def get_table_column_names_and_types( row["snowflake_type"] = "NUMBERwSCALE" elif row["type_code"] in [5, 9, 12]: - error = snowflake_unsupported_map[row["type_code"]] + datatype = snowflake_unsupported_map[row["type_code"]] raise NotImplementedError( - f"The following Snowflake Data Type is not supported: {error}" + f"The datatype of column {row['column_name']} is of type {datatype} in datasource {query}. This type is not supported. Try converting to VARCHAR." ) elif row["type_code"] in [1, 2, 3, 4, 6, 7, 8, 10, 11, 13]: row["snowflake_type"] = snowflake_type_code_map[row["type_code"]] else: raise NotImplementedError( - f"The following Snowflake Column is not supported: {row['column_name']} (type_code: {row['type_code']})" + f"The datatype of column {row['column_name']} in datasource {query} is not supported." ) return [ @@ -317,9 +317,9 @@ def get_table_column_names_and_types( } snowflake_unsupported_map = { - 5: "VARIANT -- Try converting to VARCHAR", - 9: "OBJECT -- Try converting to VARCHAR", - 12: "TIME -- Try converting to VARCHAR", + 5: "VARIANT", + 9: "OBJECT", + 12: "TIME", } python_int_to_snowflake_type_map = { diff --git a/sdk/python/feast/infra/online_stores/milvus_online_store/milvus.py b/sdk/python/feast/infra/online_stores/milvus_online_store/milvus.py index 7e840622a8a..4d14d826085 100644 --- a/sdk/python/feast/infra/online_stores/milvus_online_store/milvus.py +++ b/sdk/python/feast/infra/online_stores/milvus_online_store/milvus.py @@ -4,7 +4,6 @@ from pydantic import StrictStr from pymilvus import ( - Collection, CollectionSchema, DataType, FieldSchema, @@ -15,25 +14,29 @@ from feast.feature_view import FeatureView from feast.infra.infra_object import InfraObject from feast.infra.key_encoding_utils import ( + deserialize_entity_key, serialize_entity_key, ) from feast.infra.online_stores.online_store import OnlineStore from feast.infra.online_stores.vector_store import VectorStoreConfig -from feast.protos.feast.core.InfraObject_pb2 import InfraObject as InfraObjectProto from feast.protos.feast.core.Registry_pb2 import Registry as RegistryProto from feast.protos.feast.types.EntityKey_pb2 import EntityKey as EntityKeyProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import FeastConfigBaseModel, RepoConfig -from feast.type_map import PROTO_VALUE_TO_VALUE_TYPE_MAP +from feast.type_map import ( + PROTO_VALUE_TO_VALUE_TYPE_MAP, + VALUE_TYPE_TO_PROTO_VALUE_MAP, + feast_value_type_to_python_type, +) from feast.types import ( VALUE_TYPES_TO_FEAST_TYPES, Array, ComplexFeastType, PrimitiveFeastType, ValueType, + from_feast_type, ) from feast.utils import ( - _build_retrieve_online_document_record, _serialize_vector_to_float_list, to_naive_utc, ) @@ -89,7 +92,7 @@ class MilvusOnlineStoreConfig(FeastConfigBaseModel, VectorStoreConfig): host: Optional[StrictStr] = "localhost" port: Optional[int] = 19530 index_type: Optional[str] = "FLAT" - metric_type: Optional[str] = "L2" + metric_type: Optional[str] = "COSINE" embedding_dim: Optional[int] = 128 vector_enabled: Optional[bool] = True nlist: Optional[int] = 128 @@ -135,14 +138,15 @@ def _connect(self, config: RepoConfig) -> MilvusClient: ) return self.client - def _get_collection(self, config: RepoConfig, table: FeatureView) -> Dict[str, Any]: + def _get_or_create_collection( + self, config: RepoConfig, table: FeatureView + ) -> Dict[str, Any]: self.client = self._connect(config) + vector_field_dict = {k.name: k for k in table.schema if k.vector_index} collection_name = _table_id(config.project, table) if collection_name not in self._collections: # Create a composite key by combining entity fields - composite_key_name = ( - "_".join([field.name for field in table.entity_columns]) + "_pk" - ) + composite_key_name = _get_composite_key_name(table) fields = [ FieldSchema( @@ -170,16 +174,14 @@ def _get_collection(self, config: RepoConfig, table: FeatureView) -> Dict[str, A dim=config.online_store.embedding_dim, ) ) - elif dtype == DataType.VARCHAR: + else: fields.append( FieldSchema( name=field.name, - dtype=dtype, + dtype=DataType.VARCHAR, max_length=512, ) ) - else: - fields.append(FieldSchema(name=field.name, dtype=dtype)) schema = CollectionSchema( fields=fields, description="Feast feature view data" @@ -199,10 +201,13 @@ def _get_collection(self, config: RepoConfig, table: FeatureView) -> Dict[str, A DataType.FLOAT_VECTOR, DataType.BINARY_VECTOR, ]: + metric = vector_field_dict[ + vector_field.name + ].vector_search_metric index_params.add_index( collection_name=collection_name, field_name=vector_field.name, - metric_type=config.online_store.metric_type, + metric_type=metric or config.online_store.metric_type, index_type=config.online_store.index_type, index_name=f"vector_index_{vector_field.name}", params={"nlist": config.online_store.nlist}, @@ -233,7 +238,8 @@ def online_write_batch( progress: Optional[Callable[[int], Any]], ) -> None: self.client = self._connect(config) - collection = self._get_collection(config, table) + collection = self._get_or_create_collection(config, table) + vector_cols = [f.name for f in table.features if f.vector_index] entity_batch_to_insert = [] for entity_key, values_dict, timestamp, created_ts in data: # need to construct the composite primary key also need to handle the fact that entities are a list @@ -241,18 +247,26 @@ def online_write_batch( entity_key, entity_key_serialization_version=config.entity_key_serialization_version, ).hex() - composite_key_name = ( - "_".join([str(value) for value in entity_key.join_keys]) + "_pk" - ) + # to recover the entity key just run: + # deserialize_entity_key(bytes.fromhex(entity_key_str), entity_key_serialization_version=3) + composite_key_name = _get_composite_key_name(table) + timestamp_int = int(to_naive_utc(timestamp).timestamp() * 1e6) created_ts_int = ( int(to_naive_utc(created_ts).timestamp() * 1e6) if created_ts else 0 ) - values_dict = _extract_proto_values_to_dict(values_dict) - entity_dict = _extract_proto_values_to_dict( - dict(zip(entity_key.join_keys, entity_key.entity_values)) - ) + entity_dict = { + join_key: feast_value_type_to_python_type(value) + for join_key, value in zip( + entity_key.join_keys, entity_key.entity_values + ) + } values_dict.update(entity_dict) + values_dict = _extract_proto_values_to_dict( + values_dict, + vector_cols=vector_cols, + serialize_to_string=True, + ) single_entity_record = { composite_key_name: entity_key_str, @@ -276,8 +290,133 @@ def online_read( table: FeatureView, entity_keys: List[EntityKeyProto], requested_features: Optional[List[str]] = None, + full_feature_names: bool = False, ) -> List[Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]]]: - raise NotImplementedError + self.client = self._connect(config) + collection_name = _table_id(config.project, table) + collection = self._get_or_create_collection(config, table) + + composite_key_name = _get_composite_key_name(table) + + output_fields = ( + [composite_key_name] + + (requested_features if requested_features else []) + + ["created_ts", "event_ts"] + ) + assert all( + field in [f["name"] for f in collection["fields"]] + for field in output_fields + ), ( + f"field(s) [{[field for field in output_fields if field not in [f['name'] for f in collection['fields']]]}] not found in collection schema" + ) + composite_entities = [] + for entity_key in entity_keys: + entity_key_str = serialize_entity_key( + entity_key, + entity_key_serialization_version=config.entity_key_serialization_version, + ).hex() + composite_entities.append(entity_key_str) + + query_filter_for_entities = ( + f"{composite_key_name} in [" + + ", ".join([f"'{e}'" for e in composite_entities]) + + "]" + ) + self.client.load_collection(collection_name) + results = self.client.query( + collection_name=collection_name, + filter=query_filter_for_entities, + output_fields=output_fields, + ) + # Group hits by composite key. + grouped_hits: Dict[str, Any] = {} + for hit in results: + key = hit.get(composite_key_name) + grouped_hits.setdefault(key, []).append(hit) + + # Map the features to their Feast types. + feature_name_feast_primitive_type_map = { + f.name: f.dtype for f in table.features + } + # Build a dictionary mapping composite key -> (res_ts, res) + results_dict: Dict[ + str, Tuple[Optional[datetime], Optional[Dict[str, ValueProto]]] + ] = {} + + # here we need to map the data stored as characters back into the protobuf value + for hit in results: + key = hit.get(composite_key_name) + # Only take one hit per composite key (adjust if you need aggregation) + if key not in results_dict: + res = {} + res_ts = None + for field in output_fields: + val = ValueProto() + field_value = hit.get(field, None) + if field_value is None and ":" in field: + _, field_short = field.split(":", 1) + field_value = hit.get(field_short) + + if field in ["created_ts", "event_ts"]: + res_ts = datetime.fromtimestamp(field_value / 1e6) + elif field == composite_key_name: + # We do not return the composite key value + pass + else: + feature_feast_primitive_type = ( + feature_name_feast_primitive_type_map.get( + field, PrimitiveFeastType.INVALID + ) + ) + feature_fv_dtype = from_feast_type(feature_feast_primitive_type) + proto_attr = VALUE_TYPE_TO_PROTO_VALUE_MAP.get(feature_fv_dtype) + if proto_attr: + if proto_attr == "bytes_val": + setattr(val, proto_attr, field_value.encode()) + elif proto_attr in [ + "int32_val", + "int64_val", + "float_val", + "double_val", + ]: + setattr( + val, + proto_attr, + type(getattr(val, proto_attr))(field_value), + ) + elif proto_attr in [ + "int32_list_val", + "int64_list_val", + "float_list_val", + "double_list_val", + ]: + setattr( + val, + proto_attr, + list( + map( + type(getattr(val, proto_attr)).__args__[0], + field_value, + ) + ), + ) + else: + setattr(val, proto_attr, field_value) + else: + raise ValueError( + f"Unsupported ValueType: {feature_feast_primitive_type} with feature view value {field_value} for feature {field} with value {field_value}" + ) + # res[field] = val + key_to_use = field.split(":", 1)[-1] if ":" in field else field + res[key_to_use] = val + results_dict[key] = (res_ts, res if res else None) + + # Map the results back into a list matching the original order of composite_keys. + result_list = [ + results_dict.get(key, (None, None)) for key in composite_entities + ] + + return result_list def update( self, @@ -290,7 +429,7 @@ def update( ): self.client = self._connect(config) for table in tables_to_keep: - self._collections = self._get_collection(config, table) + self._collections = self._get_or_create_collection(config, table) for table in tables_to_delete: collection_name = _table_id(config.project, table) @@ -316,12 +455,11 @@ def teardown( self.client.drop_collection(collection_name) self._collections.pop(collection_name, None) - def retrieve_online_documents( + def retrieve_online_documents_v2( self, config: RepoConfig, table: FeatureView, - requested_feature: Optional[str], - requested_features: Optional[List[str]], + requested_features: List[str], embedding: List[float], top_k: int, distance_metric: Optional[str] = None, @@ -329,14 +467,15 @@ def retrieve_online_documents( Tuple[ Optional[datetime], Optional[EntityKeyProto], - Optional[ValueProto], - Optional[ValueProto], - Optional[ValueProto], + Optional[Dict[str, ValueProto]], ] ]: + entity_name_feast_primitive_type_map = { + k.name: k.dtype for k in table.entity_columns + } self.client = self._connect(config) collection_name = _table_id(config.project, table) - collection = self._get_collection(config, table) + collection = self._get_or_create_collection(config, table) if not config.online_store.vector_enabled: raise ValueError("Vector search is not enabled in the online store config") @@ -344,14 +483,8 @@ def retrieve_online_documents( "metric_type": distance_metric or config.online_store.metric_type, "params": {"nprobe": 10}, } - expr = f"feature_name == '{requested_feature}'" - composite_key_name = ( - "_".join([str(field.name) for field in table.entity_columns]) + "_pk" - ) - if requested_features: - features_str = ", ".join([f"'{f}'" for f in requested_features]) - expr += f" && feature_name in [{features_str}]" + composite_key_name = _get_composite_key_name(table) output_fields = ( [composite_key_name] @@ -361,7 +494,9 @@ def retrieve_online_documents( assert all( field in [f["name"] for f in collection["fields"]] for field in output_fields - ), f"field(s) [{[field for field in output_fields if field not in [f['name'] for f in collection['fields']]]}] not found in collection schema" + ), ( + f"field(s) [{[field for field in output_fields if field not in [f['name'] for f in collection['fields']]]}] not found in collection schema" + ) # Note we choose the first vector field as the field to search on. Not ideal but it's something. ann_search_field = None for field in collection["fields"]: @@ -385,29 +520,49 @@ def retrieve_online_documents( result_list = [] for hits in results: for hit in hits: - single_record = {} - for field in output_fields: - single_record[field] = hit.get("entity", {}).get(field, None) - + res = {} + res_ts = None entity_key_bytes = bytes.fromhex( hit.get("entity", {}).get(composite_key_name, None) ) - embedding = hit.get("entity", {}).get(ann_search_field) - serialized_embedding = _serialize_vector_to_float_list(embedding) - distance = hit.get("distance", None) - event_ts = datetime.fromtimestamp( - hit.get("entity", {}).get("event_ts") / 1e6 + entity_key_proto = ( + deserialize_entity_key(entity_key_bytes) + if entity_key_bytes + else None ) - prepared_result = _build_retrieve_online_document_record( - entity_key_bytes, - # This may have a bug - serialized_embedding.SerializeToString(), - embedding, - distance, - event_ts, - config.entity_key_serialization_version, + for field in output_fields: + val = ValueProto() + field_value = hit.get("entity", {}).get(field, None) + # entity_key_proto = None + if field in ["created_ts", "event_ts"]: + res_ts = datetime.fromtimestamp(field_value / 1e6) + elif field == ann_search_field: + serialized_embedding = _serialize_vector_to_float_list( + embedding + ) + res[ann_search_field] = serialized_embedding + elif entity_name_feast_primitive_type_map.get( + field, PrimitiveFeastType.INVALID + ) in [ + PrimitiveFeastType.STRING, + PrimitiveFeastType.INT64, + PrimitiveFeastType.INT32, + PrimitiveFeastType.BYTES, + ]: + res[field] = ValueProto(string_val=field_value) + elif field == composite_key_name: + pass + elif isinstance(field_value, bytes): + val.ParseFromString(field_value) + res[field] = val + else: + val.string_val = field_value + res[field] = val + distance = hit.get("distance", None) + res["distance"] = ( + ValueProto(float_val=distance) if distance else ValueProto() ) - result_list.append(prepared_result) + result_list.append((res_ts, entity_key_proto, res if res else None)) return result_list @@ -415,55 +570,57 @@ def _table_id(project: str, table: FeatureView) -> str: return f"{project}_{table.name}" -def _extract_proto_values_to_dict(input_dict: Dict[str, Any]) -> Dict[str, Any]: +def _get_composite_key_name(table: FeatureView) -> str: + return "_".join([field.name for field in table.entity_columns]) + "_pk" + + +def _extract_proto_values_to_dict( + input_dict: Dict[str, Any], + vector_cols: List[str], + serialize_to_string=False, +) -> Dict[str, Any]: numeric_vector_list_types = [ k for k in PROTO_VALUE_TO_VALUE_TYPE_MAP.keys() if k is not None and "list" in k and "string" not in k ] + numeric_types = [ + "double_val", + "float_val", + "int32_val", + "int64_val", + "bool_val", + ] output_dict = {} for feature_name, feature_values in input_dict.items(): for proto_val_type in PROTO_VALUE_TO_VALUE_TYPE_MAP: - if feature_values.HasField(proto_val_type): - if proto_val_type in numeric_vector_list_types: - vector_values = getattr(feature_values, proto_val_type).val - else: - vector_values = getattr(feature_values, proto_val_type) - output_dict[feature_name] = vector_values - return output_dict - - -class MilvusTable(InfraObject): - """ - A Milvus collection managed by Feast. - - Attributes: - host: The host of the Milvus server. - port: The port of the Milvus server. - name: The name of the collection. - """ - - host: str - port: int - - def __init__(self, host: str, port: int, name: str): - super().__init__(name) - self.host = host - self.port = port - self._connect() - - def _connect(self): - raise NotImplementedError - - def to_infra_object_proto(self) -> InfraObjectProto: - # Implement serialization if needed - raise NotImplementedError - - def update(self): - # Implement update logic if needed - raise NotImplementedError + if not isinstance(feature_values, (int, float, str)): + if feature_values.HasField(proto_val_type): + if proto_val_type in numeric_vector_list_types: + if serialize_to_string and feature_name not in vector_cols: + vector_values = getattr( + feature_values, proto_val_type + ).SerializeToString() + else: + vector_values = getattr(feature_values, proto_val_type).val + else: + if ( + serialize_to_string + and proto_val_type not in ["string_val"] + numeric_types + ): + vector_values = feature_values.SerializeToString().decode() + else: + if not isinstance(feature_values, str): + vector_values = str( + getattr(feature_values, proto_val_type) + ) + else: + vector_values = getattr(feature_values, proto_val_type) + output_dict[feature_name] = vector_values + else: + if serialize_to_string: + if not isinstance(feature_values, str): + feature_values = str(feature_values) + output_dict[feature_name] = feature_values - def teardown(self): - collection = Collection(name=self.name) - if collection.exists(): - collection.drop() + return output_dict diff --git a/sdk/python/feast/infra/online_stores/online_store.py b/sdk/python/feast/infra/online_stores/online_store.py index be3128562dc..a86fdba4017 100644 --- a/sdk/python/feast/infra/online_stores/online_store.py +++ b/sdk/python/feast/infra/online_stores/online_store.py @@ -429,6 +429,41 @@ def retrieve_online_documents( f"Online store {self.__class__.__name__} does not support online retrieval" ) + def retrieve_online_documents_v2( + self, + config: RepoConfig, + table: FeatureView, + requested_features: List[str], + embedding: List[float], + top_k: int, + distance_metric: Optional[str] = None, + ) -> List[ + Tuple[ + Optional[datetime], + Optional[EntityKeyProto], + Optional[Dict[str, ValueProto]], + ] + ]: + """ + Retrieves online feature values for the specified embeddings. + + Args: + distance_metric: distance metric to use for retrieval. + config: The config for the current feature store. + table: The feature view whose feature values should be read. + requested_features: The list of features whose embeddings should be used for retrieval. + embedding: The embeddings to use for retrieval. + top_k: The number of documents to retrieve. + + Returns: + object: A list of top k closest documents to the specified embedding. Each item in the list is a tuple + where the first item is the event timestamp for the row, and the second item is a dict of feature + name to embeddings. + """ + raise NotImplementedError( + f"Online store {self.__class__.__name__} does not support online retrieval" + ) + async def initialize(self, config: RepoConfig) -> None: pass diff --git a/sdk/python/feast/infra/online_stores/qdrant_online_store/qdrant.py b/sdk/python/feast/infra/online_stores/qdrant_online_store/qdrant.py index cdbef95348d..81652c3e2a9 100644 --- a/sdk/python/feast/infra/online_stores/qdrant_online_store/qdrant.py +++ b/sdk/python/feast/infra/online_stores/qdrant_online_store/qdrant.py @@ -69,9 +69,9 @@ def _get_client(self, config: RepoConfig) -> QdrantClient: if self._client: return self._client online_store_config = config.online_store - assert isinstance( - online_store_config, QdrantOnlineStoreConfig - ), "Invalid type for online store config" + assert isinstance(online_store_config, QdrantOnlineStoreConfig), ( + "Invalid type for online store config" + ) assert online_store_config.similarity and ( online_store_config.similarity.lower() in DISTANCE_MAPPING diff --git a/sdk/python/feast/infra/passthrough_provider.py b/sdk/python/feast/infra/passthrough_provider.py index 57aa122ae8a..74b05113282 100644 --- a/sdk/python/feast/infra/passthrough_provider.py +++ b/sdk/python/feast/infra/passthrough_provider.py @@ -313,6 +313,27 @@ def retrieve_online_documents( ) return result + def retrieve_online_documents_v2( + self, + config: RepoConfig, + table: FeatureView, + requested_features: Optional[List[str]], + query: List[float], + top_k: int, + distance_metric: Optional[str] = None, + ) -> List: + result = [] + if self.online_store: + result = self.online_store.retrieve_online_documents_v2( + config, + table, + requested_features, + query, + top_k, + distance_metric, + ) + return result + @staticmethod def _prep_rows_to_write_for_ingestion( feature_view: Union[BaseFeatureView, FeatureView, OnDemandFeatureView], @@ -473,9 +494,9 @@ def write_feature_service_logs( config: RepoConfig, registry: BaseRegistry, ): - assert ( - feature_service.logging_config is not None - ), "Logging should be configured for the feature service before calling this function" + assert feature_service.logging_config is not None, ( + "Logging should be configured for the feature service before calling this function" + ) self.offline_store.write_logged_features( config=config, @@ -493,9 +514,9 @@ def retrieve_feature_service_logs( config: RepoConfig, registry: BaseRegistry, ) -> RetrievalJob: - assert ( - feature_service.logging_config is not None - ), "Logging should be configured for the feature service before calling this function" + assert feature_service.logging_config is not None, ( + "Logging should be configured for the feature service before calling this function" + ) logging_source = FeatureServiceLoggingSource(feature_service, config.project) schema = logging_source.get_schema(registry) diff --git a/sdk/python/feast/infra/provider.py b/sdk/python/feast/infra/provider.py index efc806ba2f0..f765e754436 100644 --- a/sdk/python/feast/infra/provider.py +++ b/sdk/python/feast/infra/provider.py @@ -431,7 +431,7 @@ def retrieve_online_documents( Optional[ValueProto], Optional[ValueProto], Optional[ValueProto], - ] + ], ]: """ Searches for the top-k most similar documents in the online document store. @@ -450,6 +450,39 @@ def retrieve_online_documents( """ pass + @abstractmethod + def retrieve_online_documents_v2( + self, + config: RepoConfig, + table: FeatureView, + requested_features: List[str], + query: List[float], + top_k: int, + distance_metric: Optional[str] = None, + ) -> List[ + Tuple[ + Optional[datetime], + Optional[EntityKeyProto], + Optional[Dict[str, ValueProto]], + ] + ]: + """ + Searches for the top-k most similar documents in the online document store. + + Args: + distance_metric: distance metric to use for the search. + config: The config for the current feature store. + table: The feature view whose embeddings should be searched. + requested_features: the requested document feature names. + query: The query embedding to search for. + top_k: The number of documents to return. + + Returns: + A list of dictionaries, where each dictionary contains the datetime, entitykey, and a dictionary + of feature key value pairs + """ + pass + @abstractmethod def validate_data_source( self, diff --git a/sdk/python/feast/infra/utils/aws_utils.py b/sdk/python/feast/infra/utils/aws_utils.py index 0526cf8b65c..39fa815f7e3 100644 --- a/sdk/python/feast/infra/utils/aws_utils.py +++ b/sdk/python/feast/infra/utils/aws_utils.py @@ -1062,7 +1062,7 @@ def upload_arrow_table_to_athena( f"CREATE EXTERNAL TABLE {database}.{table_name} {'IF NOT EXISTS' if not fail_if_exists else ''}" f"({column_query_list}) " f"STORED AS PARQUET " - f"LOCATION '{s3_path[:s3_path.rfind('/')]}' " + f"LOCATION '{s3_path[: s3_path.rfind('/')]}' " f"TBLPROPERTIES('parquet.compress' = 'SNAPPY') " ) diff --git a/sdk/python/feast/offline_server.py b/sdk/python/feast/offline_server.py index 1b714a45c7e..f3642e5812e 100644 --- a/sdk/python/feast/offline_server.py +++ b/sdk/python/feast/offline_server.py @@ -266,15 +266,15 @@ def do_get(self, context: fl.ServerCallContext, ticket: fl.Ticket): return fl.RecordBatchStream(table) def _validate_offline_write_batch_parameters(self, command: dict): - assert ( - "feature_view_names" in command - ), "feature_view_names is a mandatory parameter" + assert "feature_view_names" in command, ( + "feature_view_names is a mandatory parameter" + ) assert "name_aliases" in command, "name_aliases is a mandatory parameter" feature_view_names = command["feature_view_names"] - assert ( - len(feature_view_names) == 1 - ), "feature_view_names list should only have one item" + assert len(feature_view_names) == 1, ( + "feature_view_names list should only have one item" + ) name_aliases = command["name_aliases"] assert len(name_aliases) == 1, "name_aliases list should only have one item" @@ -316,9 +316,9 @@ def write_logged_features(self, command: dict, key: str): command["feature_service_name"] ) - assert ( - feature_service.logging_config is not None - ), "feature service must have logging_config set" + assert feature_service.logging_config is not None, ( + "feature service must have logging_config set" + ) assert_permissions( resource=feature_service, @@ -335,15 +335,15 @@ def write_logged_features(self, command: dict, key: str): ) def _validate_pull_all_from_table_or_query_parameters(self, command: dict): - assert ( - "data_source_name" in command - ), "data_source_name is a mandatory parameter" - assert ( - "join_key_columns" in command - ), "join_key_columns is a mandatory parameter" - assert ( - "feature_name_columns" in command - ), "feature_name_columns is a mandatory parameter" + assert "data_source_name" in command, ( + "data_source_name is a mandatory parameter" + ) + assert "join_key_columns" in command, ( + "join_key_columns is a mandatory parameter" + ) + assert "feature_name_columns" in command, ( + "feature_name_columns is a mandatory parameter" + ) assert "timestamp_field" in command, "timestamp_field is a mandatory parameter" assert "start_date" in command, "start_date is a mandatory parameter" assert "end_date" in command, "end_date is a mandatory parameter" @@ -364,15 +364,15 @@ def pull_all_from_table_or_query(self, command: dict): ) def _validate_pull_latest_from_table_or_query_parameters(self, command: dict): - assert ( - "data_source_name" in command - ), "data_source_name is a mandatory parameter" - assert ( - "join_key_columns" in command - ), "join_key_columns is a mandatory parameter" - assert ( - "feature_name_columns" in command - ), "feature_name_columns is a mandatory parameter" + assert "data_source_name" in command, ( + "data_source_name is a mandatory parameter" + ) + assert "join_key_columns" in command, ( + "join_key_columns is a mandatory parameter" + ) + assert "feature_name_columns" in command, ( + "feature_name_columns is a mandatory parameter" + ) assert "timestamp_field" in command, "timestamp_field is a mandatory parameter" assert "start_date" in command, "start_date is a mandatory parameter" assert "end_date" in command, "end_date is a mandatory parameter" diff --git a/sdk/python/feast/repo_config.py b/sdk/python/feast/repo_config.py index 2b8d5174e1f..d943caa4c1a 100644 --- a/sdk/python/feast/repo_config.py +++ b/sdk/python/feast/repo_config.py @@ -369,14 +369,14 @@ def _validate_auth_config(cls, values: Any) -> Any: ) elif values["auth"]["type"] not in ALLOWED_AUTH_TYPES: raise ValueError( - f'auth configuration has invalid authentication type={values["auth"]["type"]}. Possible ' - f'values={ALLOWED_AUTH_TYPES}' + f"auth configuration has invalid authentication type={values['auth']['type']}. Possible " + f"values={ALLOWED_AUTH_TYPES}" ) elif isinstance(values["auth"], AuthConfig): if values["auth"].type not in ALLOWED_AUTH_TYPES: raise ValueError( - f'auth configuration has invalid authentication type={values["auth"].type}. Possible ' - f'values={ALLOWED_AUTH_TYPES}' + f"auth configuration has invalid authentication type={values['auth'].type}. Possible " + f"values={ALLOWED_AUTH_TYPES}" ) return values diff --git a/sdk/python/feast/templates/cassandra/bootstrap.py b/sdk/python/feast/templates/cassandra/bootstrap.py index 33385141145..16c82316258 100644 --- a/sdk/python/feast/templates/cassandra/bootstrap.py +++ b/sdk/python/feast/templates/cassandra/bootstrap.py @@ -57,7 +57,7 @@ def collect_cassandra_store_settings(): # it's regular Cassandra c_secure_bundle_path = None hosts_string = click.prompt( - ("Enter the seed hosts of your cluster " "(comma-separated IP addresses)"), + ("Enter the seed hosts of your cluster (comma-separated IP addresses)"), default="127.0.0.1", ) c_hosts = [ diff --git a/sdk/python/feast/type_map.py b/sdk/python/feast/type_map.py index d47a4221f60..8e3941b05bf 100644 --- a/sdk/python/feast/type_map.py +++ b/sdk/python/feast/type_map.py @@ -212,8 +212,7 @@ def python_type_to_feast_value_type( return ValueType[common_item_value_type.name + "_LIST"] raise ValueError( - f"Value with native type {type_name} " - f"cannot be converted into Feast value type" + f"Value with native type {type_name} cannot be converted into Feast value type" ) @@ -459,13 +458,13 @@ def _python_value_to_proto_value( # Numpy convert 0 to int. However, in the feature view definition, the type of column may be a float. # So, if value is 0, type validation must pass if scalar_types are either int or float. allowed_types = {np.int64, int, np.float64, float} - assert ( - type(sample) in allowed_types - ), f"Type `{type(sample)}` not in {allowed_types}" + assert type(sample) in allowed_types, ( + f"Type `{type(sample)}` not in {allowed_types}" + ) else: - assert ( - type(sample) in valid_scalar_types - ), f"Type `{type(sample)}` not in {valid_scalar_types}" + assert type(sample) in valid_scalar_types, ( + f"Type `{type(sample)}` not in {valid_scalar_types}" + ) if feast_value_type == ValueType.BOOL: # ProtoValue does not support conversion of np.bool_ so we need to convert it to support np.bool_. return [ @@ -541,6 +540,10 @@ def python_values_to_proto_values( "bool_list_val": ValueType.BOOL_LIST, } +VALUE_TYPE_TO_PROTO_VALUE_MAP: Dict[ValueType, str] = { + v: k for k, v in PROTO_VALUE_TO_VALUE_TYPE_MAP.items() +} + def _proto_value_to_value_type(proto_value: ProtoValue) -> ValueType: """ diff --git a/sdk/python/feast/types.py b/sdk/python/feast/types.py index 9fb3207e6d6..59980d816a8 100644 --- a/sdk/python/feast/types.py +++ b/sdk/python/feast/types.py @@ -207,9 +207,9 @@ def from_feast_to_pyarrow_type(feast_type: FeastType) -> pyarrow.DataType: Raises: ValueError: The conversion could not be performed. """ - assert isinstance( - feast_type, (ComplexFeastType, PrimitiveFeastType) - ), f"Expected FeastType, got {type(feast_type)}" + assert isinstance(feast_type, (ComplexFeastType, PrimitiveFeastType)), ( + f"Expected FeastType, got {type(feast_type)}" + ) if isinstance(feast_type, PrimitiveFeastType): if feast_type in FEAST_TYPES_TO_PYARROW_TYPES: return FEAST_TYPES_TO_PYARROW_TYPES[feast_type] @@ -236,3 +236,26 @@ def from_value_type( return VALUE_TYPES_TO_FEAST_TYPES[value_type] raise ValueError(f"Could not convert value type {value_type} to FeastType.") + + +def from_feast_type( + feast_type: FeastType, +) -> ValueType: + """ + Converts a Feast type to a ValueType enum. + + Args: + feast_type: The Feast type to be converted. + + Returns: + The corresponding ValueType enum. + + Raises: + ValueError: The conversion could not be performed. + """ + if feast_type in VALUE_TYPES_TO_FEAST_TYPES.values(): + return list(VALUE_TYPES_TO_FEAST_TYPES.keys())[ + list(VALUE_TYPES_TO_FEAST_TYPES.values()).index(feast_type) + ] + + raise ValueError(f"Could not convert feast type {feast_type} to ValueType.") diff --git a/sdk/python/feast/ui/package.json b/sdk/python/feast/ui/package.json index 16e413a0002..b1c524233ba 100644 --- a/sdk/python/feast/ui/package.json +++ b/sdk/python/feast/ui/package.json @@ -6,7 +6,7 @@ "@elastic/datemath": "^5.0.3", "@elastic/eui": "^72.0.0", "@emotion/react": "^11.9.0", - "@feast-dev/feast-ui": "0.43.0", + "@feast-dev/feast-ui": "0.44.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", "@testing-library/user-event": "^13.5.0", diff --git a/sdk/python/feast/ui/yarn.lock b/sdk/python/feast/ui/yarn.lock index b269c2e7f51..47b067c806d 100644 --- a/sdk/python/feast/ui/yarn.lock +++ b/sdk/python/feast/ui/yarn.lock @@ -1570,10 +1570,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@feast-dev/feast-ui@0.43.0": - version "0.43.0" - resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.43.0.tgz#0228a13d3899d8a30109bc07117c5bf141dcc810" - integrity sha512-+IjDLSN+zeY62EmstbtS0JwJKnKJVEJJCGBluktlMHBdCLdoC/Pxs8DDFLd/Q94zMmzM+3kUdaj5zABs2QinWA== +"@feast-dev/feast-ui@0.44.0": + version "0.44.0" + resolved "https://registry.yarnpkg.com/@feast-dev/feast-ui/-/feast-ui-0.44.0.tgz#71708bc66b0bef9b3961bdf236295e0669e63ba9" + integrity sha512-OZkcW0IhFd6dMoi8oKz5ssKO1iPz8SeEoeu0kA4qE6UNEE3K2MnsOsMhRSn8r6DKAl/wLV1LbjCHPNifk824jQ== dependencies: "@elastic/datemath" "^5.0.3" "@elastic/eui" "^95.12.0" diff --git a/sdk/python/feast/utils.py b/sdk/python/feast/utils.py index cfc19e37ca4..ff60217f32d 100644 --- a/sdk/python/feast/utils.py +++ b/sdk/python/feast/utils.py @@ -709,6 +709,35 @@ def _get_unique_entities( return unique_entities, indexes +def _get_unique_entities_from_values( + table_entity_values: Dict[str, List[ValueProto]], +) -> Tuple[Tuple[Dict[str, ValueProto], ...], Tuple[List[int], ...]]: + """Return the set of unique composite Entities for a Feature View and the indexes at which they appear. + + This method allows us to query the OnlineStore for data we need only once + rather than requesting and processing data for the same combination of + Entities multiple times. + """ + keys = table_entity_values.keys() + # Sort the rowise data to allow for grouping but keep original index. This lambda is + # sufficient as Entity types cannot be complex (ie. lists). + rowise = list(enumerate(zip(*table_entity_values.values()))) + rowise.sort(key=lambda row: tuple(getattr(x, x.WhichOneof("val")) for x in row[1])) + + # Identify unique entities and the indexes at which they occur. + unique_entities: Tuple[Dict[str, ValueProto], ...] + indexes: Tuple[List[int], ...] + unique_entities, indexes = tuple( + zip( + *[ + (dict(zip(keys, k)), [_[0] for _ in g]) + for k, g in itertools.groupby(rowise, key=lambda x: x[1]) + ] + ) + ) + return unique_entities, indexes + + def _drop_unneeded_columns( online_features_response: GetOnlineFeaturesResponse, requested_result_row_names: Set[str], @@ -830,6 +859,70 @@ def _populate_response_from_feature_data( ) +def _populate_response_from_feature_data_v2( + feature_data: Iterable[ + Tuple[ + Iterable[Timestamp], Iterable["FieldStatus.ValueType"], Iterable[ValueProto] + ] + ], + indexes: Iterable[List[int]], + online_features_response: GetOnlineFeaturesResponse, + requested_features: Iterable[str], +): + """Populate the GetOnlineFeaturesResponse with feature data. + + This method assumes that `_read_from_online_store` returns data for each + combination of Entities in `entity_rows` in the same order as they + are provided. + + Args: + feature_data: A list of data in Protobuf form which was retrieved from the OnlineStore. + indexes: A list of indexes which should be the same length as `feature_data`. Each list + of indexes corresponds to a set of result rows in `online_features_response`. + online_features_response: The object to populate. + full_feature_names: A boolean that provides the option to add the feature view prefixes to the feature names, + changing them from the format "feature" to "feature_view__feature" (e.g., "daily_transactions" changes to + "customer_fv__daily_transactions"). + requested_features: The names of the features in `feature_data`. This should be ordered in the same way as the + data in `feature_data`. + """ + # Add the feature names to the response. + requested_feature_refs = [(feature_name) for feature_name in requested_features] + online_features_response.metadata.feature_names.val.extend(requested_feature_refs) + + timestamps, statuses, values = zip(*feature_data) + + # Populate the result with data fetched from the OnlineStore + # which is guaranteed to be aligned with `requested_features`. + for ( + feature_idx, + (timestamp_vector, statuses_vector, values_vector), + ) in enumerate(zip(zip(*timestamps), zip(*statuses), zip(*values))): + online_features_response.results.append( + GetOnlineFeaturesResponse.FeatureVector( + values=apply_list_mapping(values_vector, indexes), + statuses=apply_list_mapping(statuses_vector, indexes), + event_timestamps=apply_list_mapping(timestamp_vector, indexes), + ) + ) + + +def _convert_entity_key_to_proto_to_dict( + entity_key_vals: List[EntityKeyProto], +) -> Dict[str, List[ValueProto]]: + entity_dict: Dict[str, List[ValueProto]] = {} + for entity_key_val in entity_key_vals: + if entity_key_val is not None: + for join_key, entity_value in zip( + entity_key_val.join_keys, entity_key_val.entity_values + ): + if join_key not in entity_dict: + entity_dict[join_key] = [] + # python_entity_value = _proto_value_to_value_type(entity_value) + entity_dict[join_key].append(entity_value) + return entity_dict + + def _get_features( registry, project, diff --git a/sdk/python/requirements/py3.10-ci-requirements.txt b/sdk/python/requirements/py3.10-ci-requirements.txt index af41030a6de..eae0a088fbc 100644 --- a/sdk/python/requirements/py3.10-ci-requirements.txt +++ b/sdk/python/requirements/py3.10-ci-requirements.txt @@ -1,6 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile -p 3.10 --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.10-ci-requirements.txt -aiobotocore==2.16.0 +aiobotocore==2.19.0 # via feast (setup.py) aiohappyeyeballs==2.4.4 # via aiohttp @@ -16,7 +16,7 @@ altair==4.2.2 # via great-expectations annotated-types==0.7.0 # via pydantic -anyio==4.7.0 +anyio==4.8.0 # via # httpx # jupyter-server @@ -46,9 +46,9 @@ async-timeout==5.0.1 # via # aiohttp # redis -atpublic==5.0 +atpublic==5.1 # via ibis-framework -attrs==24.3.0 +attrs==25.1.0 # via # aiohttp # jsonschema @@ -59,7 +59,7 @@ azure-core==1.32.0 # azure-storage-blob azure-identity==1.19.0 # via feast (setup.py) -azure-storage-blob==12.24.0 +azure-storage-blob==12.24.1 # via feast (setup.py) babel==2.16.0 # via @@ -67,15 +67,15 @@ babel==2.16.0 # sphinx beautifulsoup4==4.12.3 # via nbconvert -bigtree==0.22.3 +bigtree==0.23.1 # via feast (setup.py) -bleach==6.2.0 +bleach[css]==6.2.0 # via nbconvert -boto3==1.35.81 +boto3==1.36.3 # via # feast (setup.py) # moto -botocore==1.35.81 +botocore==1.36.3 # via # aiobotocore # boto3 @@ -86,7 +86,7 @@ build==1.2.2.post1 # feast (setup.py) # pip-tools # singlestoredb -cachetools==5.5.0 +cachetools==5.5.1 # via google-auth cassandra-driver==3.29.2 # via feast (setup.py) @@ -106,11 +106,11 @@ cffi==1.17.1 # snowflake-connector-python cfgv==3.4.0 # via pre-commit -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via # requests # snowflake-connector-python -click==8.1.7 +click==8.1.8 # via # feast (setup.py) # dask @@ -118,7 +118,7 @@ click==8.1.7 # great-expectations # pip-tools # uvicorn -cloudpickle==3.1.0 +cloudpickle==3.1.1 # via dask colorama==0.4.6 # via @@ -130,7 +130,7 @@ comm==0.2.2 # ipywidgets couchbase==4.3.2 # via feast (setup.py) -coverage[toml]==7.6.9 +coverage[toml]==7.6.10 # via pytest-cov cryptography==43.0.3 # via @@ -148,21 +148,17 @@ cryptography==43.0.3 # types-redis cython==3.0.11 # via thriftpy2 -dask[dataframe]==2024.12.1 - # via - # feast (setup.py) - # dask-expr -dask-expr==1.1.21 - # via dask -db-dtypes==1.3.1 +dask[dataframe]==2025.1.0 + # via feast (setup.py) +db-dtypes==1.4.0 # via google-cloud-bigquery -debugpy==1.8.11 +debugpy==1.8.12 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 # via nbconvert -deltalake==0.22.3 +deltalake==0.24.0 # via feast (setup.py) deprecation==2.1.0 # via python-keycloak @@ -176,9 +172,9 @@ docutils==0.19 # via sphinx duckdb==1.1.3 # via ibis-framework -elastic-transport==8.15.1 +elastic-transport==8.17.0 # via elasticsearch -elasticsearch==8.17.0 +elasticsearch==8.17.1 # via feast (setup.py) entrypoints==0.4 # via altair @@ -191,15 +187,15 @@ exceptiongroup==1.2.2 # pytest execnet==2.1.1 # via pytest-xdist -executing==2.1.0 +executing==2.2.0 # via stack-data faiss-cpu==1.9.0.post1 # via feast (setup.py) -fastapi==0.115.6 +fastapi==0.115.7 # via feast (setup.py) fastjsonschema==2.21.1 # via nbformat -filelock==3.16.1 +filelock==3.17.0 # via # snowflake-connector-python # virtualenv @@ -215,7 +211,7 @@ fsspec==2024.9.0 # dask geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.24.0 +google-api-core[grpc]==2.24.1 # via # feast (setup.py) # google-cloud-bigquery @@ -224,7 +220,7 @@ google-api-core[grpc]==2.24.0 # google-cloud-core # google-cloud-datastore # google-cloud-storage -google-auth==2.37.0 +google-auth==2.38.0 # via # google-api-core # google-cloud-bigquery @@ -234,11 +230,11 @@ google-auth==2.37.0 # google-cloud-datastore # google-cloud-storage # kubernetes -google-cloud-bigquery[pandas]==3.27.0 +google-cloud-bigquery[pandas]==3.29.0 # via feast (setup.py) google-cloud-bigquery-storage==2.27.0 # via feast (setup.py) -google-cloud-bigtable==2.27.0 +google-cloud-bigtable==2.28.1 # via feast (setup.py) google-cloud-core==2.4.1 # via @@ -266,9 +262,9 @@ googleapis-common-protos[grpc]==1.66.0 # grpcio-status great-expectations==0.18.22 # via feast (setup.py) -grpc-google-iam-v1==0.13.1 +grpc-google-iam-v1==0.14.0 # via google-cloud-bigtable -grpcio==1.68.1 +grpcio==1.70.0 # via # feast (setup.py) # google-api-core @@ -281,15 +277,15 @@ grpcio==1.68.1 # grpcio-tools # pymilvus # qdrant-client -grpcio-health-checking==1.62.3 +grpcio-health-checking==1.70.0 # via feast (setup.py) -grpcio-reflection==1.62.3 +grpcio-reflection==1.70.0 # via feast (setup.py) -grpcio-status==1.62.3 +grpcio-status==1.70.0 # via google-api-core -grpcio-testing==1.62.3 +grpcio-testing==1.70.0 # via feast (setup.py) -grpcio-tools==1.62.3 +grpcio-tools==1.70.0 # via # feast (setup.py) # qdrant-client @@ -309,7 +305,7 @@ hazelcast-python-client==5.5.0 # via feast (setup.py) hiredis==2.4.0 # via feast (setup.py) -hpack==4.0.0 +hpack==4.1.0 # via h2 httpcore==1.0.7 # via httpx @@ -321,7 +317,7 @@ httpx[http2]==0.27.2 # jupyterlab # python-keycloak # qdrant-client -hyperframe==6.0.1 +hyperframe==6.1.0 # via h2 ibis-framework[duckdb]==9.5.0 # via @@ -329,7 +325,7 @@ ibis-framework[duckdb]==9.5.0 # ibis-substrait ibis-substrait==4.0.1 # via feast (setup.py) -identify==2.6.3 +identify==2.6.6 # via pre-commit idna==3.10 # via @@ -341,7 +337,7 @@ idna==3.10 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==8.5.0 +importlib-metadata==8.6.1 # via # build # dask @@ -349,7 +345,7 @@ iniconfig==2.0.0 # via pytest ipykernel==6.29.5 # via jupyterlab -ipython==8.30.0 +ipython==8.31.0 # via # great-expectations # ipykernel @@ -375,6 +371,7 @@ jinja2==3.1.5 # sphinx jmespath==1.0.1 # via + # aiobotocore # boto3 # botocore json5==0.10.0 @@ -413,7 +410,7 @@ jupyter-events==0.11.0 # via jupyter-server jupyter-lsp==2.2.5 # via jupyterlab -jupyter-server==2.14.2 +jupyter-server==2.15.0 # via # jupyter-lsp # jupyterlab @@ -438,6 +435,8 @@ kubernetes==20.13.0 # via feast (setup.py) locket==1.0.0 # via partd +lz4==4.4.3 + # via trino makefun==1.15.6 # via great-expectations markdown-it-py==3.0.0 @@ -447,7 +446,7 @@ markupsafe==3.0.2 # jinja2 # nbconvert # werkzeug -marshmallow==3.23.2 +marshmallow==3.26.0 # via # environs # great-expectations @@ -457,15 +456,15 @@ matplotlib-inline==0.1.7 # ipython mdurl==0.1.2 # via markdown-it-py -milvus-lite==2.4.10 +milvus-lite==2.4.11 # via pymilvus minio==7.2.11 # via feast (setup.py) -mistune==3.0.2 +mistune==3.1.0 # via # great-expectations # nbconvert -mmh3==5.0.1 +mmh3==5.1.0 # via feast (setup.py) mock==2.0.0 # via feast (setup.py) @@ -479,6 +478,7 @@ msal-extensions==1.2.0 # via azure-identity multidict==6.1.0 # via + # aiobotocore # aiohttp # yarl mypy==1.11.2 @@ -491,7 +491,7 @@ mypy-protobuf==3.3.0 # via feast (setup.py) nbclient==0.10.2 # via nbconvert -nbconvert==7.16.4 +nbconvert==7.16.5 # via jupyter-server nbformat==5.10.4 # via @@ -503,7 +503,7 @@ nest-asyncio==1.6.0 # via ipykernel nodeenv==1.9.1 # via pre-commit -notebook==7.3.1 +notebook==7.3.2 # via great-expectations notebook-shim==0.2.4 # via @@ -552,7 +552,6 @@ pandas==2.2.3 # feast (setup.py) # altair # dask - # dask-expr # db-dtypes # google-cloud-bigquery # great-expectations @@ -573,7 +572,7 @@ pbr==6.1.0 # via mock pexpect==4.9.0 # via ipython -pip==24.3.1 +pip==25.0 # via pip-tools pip-tools==7.4.1 # via feast (setup.py) @@ -596,19 +595,19 @@ prometheus-client==0.21.1 # via # feast (setup.py) # jupyter-server -prompt-toolkit==3.0.48 +prompt-toolkit==3.0.50 # via ipython propcache==0.2.1 # via # aiohttp # yarl -proto-plus==1.25.0 +proto-plus==1.26.0 # via # google-api-core # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore -protobuf==4.25.5 +protobuf==5.29.3 # via # feast (setup.py) # google-api-core @@ -630,9 +629,9 @@ psutil==5.9.0 # via # feast (setup.py) # ipykernel -psycopg[binary, pool]==3.2.3 +psycopg[binary, pool]==3.2.4 # via feast (setup.py) -psycopg-binary==3.2.3 +psycopg-binary==3.2.4 # via psycopg psycopg-pool==3.2.4 # via psycopg @@ -651,7 +650,7 @@ py4j==0.10.9.7 pyarrow==17.0.0 # via # feast (setup.py) - # dask-expr + # dask # db-dtypes # deltalake # google-cloud-bigquery @@ -671,7 +670,7 @@ pycparser==2.22 # via cffi pycryptodome==3.21.0 # via minio -pydantic==2.10.4 +pydantic==2.10.6 # via # feast (setup.py) # fastapi @@ -679,7 +678,7 @@ pydantic==2.10.4 # qdrant-client pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via # feast (setup.py) # ipython @@ -702,13 +701,13 @@ pyodbc==5.2.0 # via feast (setup.py) pyopenssl==24.3.0 # via snowflake-connector-python -pyparsing==3.2.0 +pyparsing==3.2.1 # via great-expectations pyproject-hooks==1.2.0 # via # build # pip-tools -pyspark==3.5.3 +pyspark==3.5.4 # via feast (setup.py) pytest==7.4.4 # via @@ -742,6 +741,7 @@ pytest-xdist==3.6.1 # via feast (setup.py) python-dateutil==2.9.0.post0 # via + # aiobotocore # arrow # botocore # google-cloud-bigquery @@ -782,11 +782,11 @@ pyzmq==26.2.0 # ipykernel # jupyter-client # jupyter-server -qdrant-client==1.12.1 +qdrant-client==1.13.2 # via feast (setup.py) redis==4.6.0 # via feast (setup.py) -referencing==0.35.1 +referencing==0.36.2 # via # jsonschema # jsonschema-specifications @@ -820,7 +820,7 @@ requests-oauthlib==2.0.0 # via kubernetes requests-toolbelt==1.0.0 # via python-keycloak -responses==0.25.3 +responses==0.25.6 # via moto rfc3339-validator==0.1.4 # via @@ -842,15 +842,15 @@ ruamel-yaml==0.17.40 # via great-expectations ruamel-yaml-clib==0.2.12 # via ruamel-yaml -ruff==0.8.4 +ruff==0.9.3 # via feast (setup.py) -s3transfer==0.10.4 +s3transfer==0.11.2 # via boto3 -scipy==1.14.1 +scipy==1.15.1 # via great-expectations send2trash==1.8.3 # via jupyter-server -setuptools==75.6.0 +setuptools==75.8.0 # via # grpcio-tools # jupyterlab @@ -876,7 +876,7 @@ sniffio==1.3.1 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.12.4 +snowflake-connector-python[pandas]==3.13.0 # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python @@ -896,17 +896,17 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlalchemy[mypy]==2.0.36 +sqlalchemy[mypy]==2.0.37 # via feast (setup.py) sqlglot==25.20.2 # via ibis-framework -sqlite-vec==0.1.3 +sqlite-vec==0.1.1 # via feast (setup.py) -sqlparams==6.1.0 +sqlparams==6.2.0 # via singlestoredb stack-data==0.6.3 # via ipython -starlette==0.41.3 +starlette==0.45.3 # via fastapi substrait==0.23.0 # via ibis-substrait @@ -923,7 +923,7 @@ testcontainers==4.8.2 thriftpy2==0.5.2 # via happybase tinycss2==1.4.0 - # via nbconvert + # via bleach toml==0.10.2 # via feast (setup.py) tomli==2.2.1 @@ -972,11 +972,11 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -trino==0.331.0 +trino==0.332.0 # via feast (setup.py) typeguard==4.4.1 # via feast (setup.py) -types-cffi==1.16.0.20240331 +types-cffi==1.16.0.20241221 # via types-pyopenssl types-protobuf==3.19.22 # via @@ -990,15 +990,15 @@ types-python-dateutil==2.9.0.20241206 # via # feast (setup.py) # arrow -types-pytz==2024.2.0.20241003 +types-pytz==2024.2.0.20241221 # via feast (setup.py) -types-pyyaml==6.0.12.20240917 +types-pyyaml==6.0.12.20241230 # via feast (setup.py) types-redis==4.6.0.20241004 # via feast (setup.py) types-requests==2.30.0.0 # via feast (setup.py) -types-setuptools==75.6.0.20241126 +types-setuptools==75.8.0.20250110 # via # feast (setup.py) # types-cffi @@ -1019,19 +1019,21 @@ typing-extensions==4.12.2 # ipython # jwcrypto # minio + # mistune # multidict # mypy # psycopg # psycopg-pool # pydantic # pydantic-core + # referencing # rich # snowflake-connector-python # sqlalchemy # testcontainers # typeguard # uvicorn -tzdata==2024.2 +tzdata==2025.1 # via pandas tzlocal==5.2 # via @@ -1041,9 +1043,10 @@ ujson==5.10.0 # via pymilvus uri-template==1.3.0 # via jsonschema -urllib3==2.2.3 +urllib3==2.3.0 # via # feast (setup.py) + # aiobotocore # botocore # docker # elastic-transport @@ -1058,7 +1061,7 @@ uvicorn[standard]==0.34.0 # via # feast (setup.py) # uvicorn-worker -uvicorn-worker==0.2.0 +uvicorn-worker==0.3.0 # via feast (setup.py) uvloop==0.21.0 # via uvicorn @@ -1066,7 +1069,7 @@ virtualenv==20.23.0 # via # feast (setup.py) # pre-commit -watchfiles==1.0.3 +watchfiles==1.0.4 # via uvicorn wcwidth==0.2.13 # via prompt-toolkit @@ -1080,7 +1083,7 @@ websocket-client==1.8.0 # via # jupyter-server # kubernetes -websockets==14.1 +websockets==14.2 # via uvicorn werkzeug==3.1.3 # via moto @@ -1090,7 +1093,7 @@ wheel==0.45.1 # singlestoredb widgetsnbextension==4.0.13 # via ipywidgets -wrapt==1.17.0 +wrapt==1.17.2 # via # aiobotocore # testcontainers @@ -1100,3 +1103,5 @@ yarl==1.18.3 # via aiohttp zipp==3.21.0 # via importlib-metadata +zstandard==0.23.0 + # via trino diff --git a/sdk/python/requirements/py3.10-requirements.txt b/sdk/python/requirements/py3.10-requirements.txt index 2488e12304b..7e873fc510e 100644 --- a/sdk/python/requirements/py3.10-requirements.txt +++ b/sdk/python/requirements/py3.10-requirements.txt @@ -2,40 +2,36 @@ # uv pip compile -p 3.10 --system --no-strip-extras setup.py --output-file sdk/python/requirements/py3.10-requirements.txt annotated-types==0.7.0 # via pydantic -anyio==4.7.0 +anyio==4.8.0 # via # starlette # watchfiles -attrs==24.3.0 +attrs==25.1.0 # via # jsonschema # referencing -bigtree==0.22.3 +bigtree==0.23.1 # via feast (setup.py) certifi==2024.12.14 # via requests -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via requests -click==8.1.7 +click==8.1.8 # via # feast (setup.py) # dask # uvicorn -cloudpickle==3.1.0 +cloudpickle==3.1.1 # via dask colorama==0.4.6 # via feast (setup.py) -dask[dataframe]==2024.12.1 - # via - # feast (setup.py) - # dask-expr -dask-expr==1.1.21 - # via dask +dask[dataframe]==2025.1.0 + # via feast (setup.py) dill==0.3.9 # via feast (setup.py) exceptiongroup==1.2.2 # via anyio -fastapi==0.115.6 +fastapi==0.115.7 # via feast (setup.py) fsspec==2024.12.0 # via dask @@ -51,7 +47,7 @@ idna==3.10 # via # anyio # requests -importlib-metadata==8.5.0 +importlib-metadata==8.6.1 # via dask jinja2==3.1.5 # via feast (setup.py) @@ -63,9 +59,9 @@ locket==1.0.0 # via partd markupsafe==3.0.2 # via jinja2 -mmh3==5.0.1 +mmh3==5.1.0 # via feast (setup.py) -mypy==1.13.0 +mypy==1.14.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy @@ -82,26 +78,25 @@ pandas==2.2.3 # via # feast (setup.py) # dask - # dask-expr partd==1.4.2 # via dask prometheus-client==0.21.1 # via feast (setup.py) -protobuf==4.25.5 +protobuf==5.29.3 # via feast (setup.py) psutil==6.1.1 # via feast (setup.py) pyarrow==18.0.0 # via # feast (setup.py) - # dask-expr -pydantic==2.10.4 + # dask +pydantic==2.10.6 # via # feast (setup.py) # fastapi pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via feast (setup.py) pyjwt==2.10.1 # via feast (setup.py) @@ -116,7 +111,7 @@ pyyaml==6.0.2 # feast (setup.py) # dask # uvicorn -referencing==0.35.1 +referencing==0.36.2 # via # jsonschema # jsonschema-specifications @@ -130,9 +125,9 @@ six==1.17.0 # via python-dateutil sniffio==1.3.1 # via anyio -sqlalchemy[mypy]==2.0.36 +sqlalchemy[mypy]==2.0.37 # via feast (setup.py) -starlette==0.41.3 +starlette==0.45.3 # via fastapi tabulate==0.9.0 # via feast (setup.py) @@ -157,24 +152,25 @@ typing-extensions==4.12.2 # mypy # pydantic # pydantic-core + # referencing # sqlalchemy # typeguard # uvicorn -tzdata==2024.2 +tzdata==2025.1 # via pandas -urllib3==2.2.3 +urllib3==2.3.0 # via requests uvicorn[standard]==0.34.0 # via # feast (setup.py) # uvicorn-worker -uvicorn-worker==0.2.0 +uvicorn-worker==0.3.0 # via feast (setup.py) uvloop==0.21.0 # via uvicorn -watchfiles==1.0.3 +watchfiles==1.0.4 # via uvicorn -websockets==14.1 +websockets==14.2 # via uvicorn zipp==3.21.0 # via importlib-metadata diff --git a/sdk/python/requirements/py3.11-ci-requirements.txt b/sdk/python/requirements/py3.11-ci-requirements.txt index 10c31f30953..c8b7d34d7e2 100644 --- a/sdk/python/requirements/py3.11-ci-requirements.txt +++ b/sdk/python/requirements/py3.11-ci-requirements.txt @@ -1,6 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile -p 3.11 --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.11-ci-requirements.txt -aiobotocore==2.16.0 +aiobotocore==2.19.0 # via feast (setup.py) aiohappyeyeballs==2.4.4 # via aiohttp @@ -16,7 +16,7 @@ altair==4.2.2 # via great-expectations annotated-types==0.7.0 # via pydantic -anyio==4.7.0 +anyio==4.8.0 # via # httpx # jupyter-server @@ -44,9 +44,9 @@ async-property==0.2.2 # via python-keycloak async-timeout==5.0.1 # via redis -atpublic==5.0 +atpublic==5.1 # via ibis-framework -attrs==24.3.0 +attrs==25.1.0 # via # aiohttp # jsonschema @@ -57,7 +57,7 @@ azure-core==1.32.0 # azure-storage-blob azure-identity==1.19.0 # via feast (setup.py) -azure-storage-blob==12.24.0 +azure-storage-blob==12.24.1 # via feast (setup.py) babel==2.16.0 # via @@ -65,15 +65,15 @@ babel==2.16.0 # sphinx beautifulsoup4==4.12.3 # via nbconvert -bigtree==0.22.3 +bigtree==0.23.1 # via feast (setup.py) -bleach==6.2.0 +bleach[css]==6.2.0 # via nbconvert -boto3==1.35.81 +boto3==1.36.3 # via # feast (setup.py) # moto -botocore==1.35.81 +botocore==1.36.3 # via # aiobotocore # boto3 @@ -84,7 +84,7 @@ build==1.2.2.post1 # feast (setup.py) # pip-tools # singlestoredb -cachetools==5.5.0 +cachetools==5.5.1 # via google-auth cassandra-driver==3.29.2 # via feast (setup.py) @@ -104,11 +104,11 @@ cffi==1.17.1 # snowflake-connector-python cfgv==3.4.0 # via pre-commit -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via # requests # snowflake-connector-python -click==8.1.7 +click==8.1.8 # via # feast (setup.py) # dask @@ -116,7 +116,7 @@ click==8.1.7 # great-expectations # pip-tools # uvicorn -cloudpickle==3.1.0 +cloudpickle==3.1.1 # via dask colorama==0.4.6 # via @@ -128,7 +128,7 @@ comm==0.2.2 # ipywidgets couchbase==4.3.2 # via feast (setup.py) -coverage[toml]==7.6.9 +coverage[toml]==7.6.10 # via pytest-cov cryptography==43.0.3 # via @@ -146,21 +146,17 @@ cryptography==43.0.3 # types-redis cython==3.0.11 # via thriftpy2 -dask[dataframe]==2024.12.1 - # via - # feast (setup.py) - # dask-expr -dask-expr==1.1.21 - # via dask -db-dtypes==1.3.1 +dask[dataframe]==2025.1.0 + # via feast (setup.py) +db-dtypes==1.4.0 # via google-cloud-bigquery -debugpy==1.8.11 +debugpy==1.8.12 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 # via nbconvert -deltalake==0.22.3 +deltalake==0.24.0 # via feast (setup.py) deprecation==2.1.0 # via python-keycloak @@ -174,9 +170,9 @@ docutils==0.19 # via sphinx duckdb==1.1.3 # via ibis-framework -elastic-transport==8.15.1 +elastic-transport==8.17.0 # via elasticsearch -elasticsearch==8.17.0 +elasticsearch==8.17.1 # via feast (setup.py) entrypoints==0.4 # via altair @@ -184,15 +180,15 @@ environs==9.5.0 # via pymilvus execnet==2.1.1 # via pytest-xdist -executing==2.1.0 +executing==2.2.0 # via stack-data faiss-cpu==1.9.0.post1 # via feast (setup.py) -fastapi==0.115.6 +fastapi==0.115.7 # via feast (setup.py) fastjsonschema==2.21.1 # via nbformat -filelock==3.16.1 +filelock==3.17.0 # via # snowflake-connector-python # virtualenv @@ -208,7 +204,7 @@ fsspec==2024.9.0 # dask geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.24.0 +google-api-core[grpc]==2.24.1 # via # feast (setup.py) # google-cloud-bigquery @@ -217,7 +213,7 @@ google-api-core[grpc]==2.24.0 # google-cloud-core # google-cloud-datastore # google-cloud-storage -google-auth==2.37.0 +google-auth==2.38.0 # via # google-api-core # google-cloud-bigquery @@ -227,11 +223,11 @@ google-auth==2.37.0 # google-cloud-datastore # google-cloud-storage # kubernetes -google-cloud-bigquery[pandas]==3.27.0 +google-cloud-bigquery[pandas]==3.29.0 # via feast (setup.py) google-cloud-bigquery-storage==2.27.0 # via feast (setup.py) -google-cloud-bigtable==2.27.0 +google-cloud-bigtable==2.28.1 # via feast (setup.py) google-cloud-core==2.4.1 # via @@ -259,9 +255,9 @@ googleapis-common-protos[grpc]==1.66.0 # grpcio-status great-expectations==0.18.22 # via feast (setup.py) -grpc-google-iam-v1==0.13.1 +grpc-google-iam-v1==0.14.0 # via google-cloud-bigtable -grpcio==1.68.1 +grpcio==1.70.0 # via # feast (setup.py) # google-api-core @@ -274,15 +270,15 @@ grpcio==1.68.1 # grpcio-tools # pymilvus # qdrant-client -grpcio-health-checking==1.62.3 +grpcio-health-checking==1.70.0 # via feast (setup.py) -grpcio-reflection==1.62.3 +grpcio-reflection==1.70.0 # via feast (setup.py) -grpcio-status==1.62.3 +grpcio-status==1.70.0 # via google-api-core -grpcio-testing==1.62.3 +grpcio-testing==1.70.0 # via feast (setup.py) -grpcio-tools==1.62.3 +grpcio-tools==1.70.0 # via # feast (setup.py) # qdrant-client @@ -302,7 +298,7 @@ hazelcast-python-client==5.5.0 # via feast (setup.py) hiredis==2.4.0 # via feast (setup.py) -hpack==4.0.0 +hpack==4.1.0 # via h2 httpcore==1.0.7 # via httpx @@ -314,7 +310,7 @@ httpx[http2]==0.27.2 # jupyterlab # python-keycloak # qdrant-client -hyperframe==6.0.1 +hyperframe==6.1.0 # via h2 ibis-framework[duckdb]==9.5.0 # via @@ -322,7 +318,7 @@ ibis-framework[duckdb]==9.5.0 # ibis-substrait ibis-substrait==4.0.1 # via feast (setup.py) -identify==2.6.3 +identify==2.6.6 # via pre-commit idna==3.10 # via @@ -334,13 +330,13 @@ idna==3.10 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==8.5.0 +importlib-metadata==8.6.1 # via dask iniconfig==2.0.0 # via pytest ipykernel==6.29.5 # via jupyterlab -ipython==8.30.0 +ipython==8.31.0 # via # great-expectations # ipykernel @@ -366,6 +362,7 @@ jinja2==3.1.5 # sphinx jmespath==1.0.1 # via + # aiobotocore # boto3 # botocore json5==0.10.0 @@ -404,7 +401,7 @@ jupyter-events==0.11.0 # via jupyter-server jupyter-lsp==2.2.5 # via jupyterlab -jupyter-server==2.14.2 +jupyter-server==2.15.0 # via # jupyter-lsp # jupyterlab @@ -429,6 +426,8 @@ kubernetes==20.13.0 # via feast (setup.py) locket==1.0.0 # via partd +lz4==4.4.3 + # via trino makefun==1.15.6 # via great-expectations markdown-it-py==3.0.0 @@ -438,7 +437,7 @@ markupsafe==3.0.2 # jinja2 # nbconvert # werkzeug -marshmallow==3.23.2 +marshmallow==3.26.0 # via # environs # great-expectations @@ -448,15 +447,15 @@ matplotlib-inline==0.1.7 # ipython mdurl==0.1.2 # via markdown-it-py -milvus-lite==2.4.10 +milvus-lite==2.4.11 # via pymilvus minio==7.2.11 # via feast (setup.py) -mistune==3.0.2 +mistune==3.1.0 # via # great-expectations # nbconvert -mmh3==5.0.1 +mmh3==5.1.0 # via feast (setup.py) mock==2.0.0 # via feast (setup.py) @@ -470,6 +469,7 @@ msal-extensions==1.2.0 # via azure-identity multidict==6.1.0 # via + # aiobotocore # aiohttp # yarl mypy==1.11.2 @@ -482,7 +482,7 @@ mypy-protobuf==3.3.0 # via feast (setup.py) nbclient==0.10.2 # via nbconvert -nbconvert==7.16.4 +nbconvert==7.16.5 # via jupyter-server nbformat==5.10.4 # via @@ -494,7 +494,7 @@ nest-asyncio==1.6.0 # via ipykernel nodeenv==1.9.1 # via pre-commit -notebook==7.3.1 +notebook==7.3.2 # via great-expectations notebook-shim==0.2.4 # via @@ -543,7 +543,6 @@ pandas==2.2.3 # feast (setup.py) # altair # dask - # dask-expr # db-dtypes # google-cloud-bigquery # great-expectations @@ -564,7 +563,7 @@ pbr==6.1.0 # via mock pexpect==4.9.0 # via ipython -pip==24.3.1 +pip==25.0 # via pip-tools pip-tools==7.4.1 # via feast (setup.py) @@ -587,19 +586,19 @@ prometheus-client==0.21.1 # via # feast (setup.py) # jupyter-server -prompt-toolkit==3.0.48 +prompt-toolkit==3.0.50 # via ipython propcache==0.2.1 # via # aiohttp # yarl -proto-plus==1.25.0 +proto-plus==1.26.0 # via # google-api-core # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore -protobuf==4.25.5 +protobuf==5.29.3 # via # feast (setup.py) # google-api-core @@ -621,9 +620,9 @@ psutil==5.9.0 # via # feast (setup.py) # ipykernel -psycopg[binary, pool]==3.2.3 +psycopg[binary, pool]==3.2.4 # via feast (setup.py) -psycopg-binary==3.2.3 +psycopg-binary==3.2.4 # via psycopg psycopg-pool==3.2.4 # via psycopg @@ -642,7 +641,7 @@ py4j==0.10.9.7 pyarrow==17.0.0 # via # feast (setup.py) - # dask-expr + # dask # db-dtypes # deltalake # google-cloud-bigquery @@ -662,7 +661,7 @@ pycparser==2.22 # via cffi pycryptodome==3.21.0 # via minio -pydantic==2.10.4 +pydantic==2.10.6 # via # feast (setup.py) # fastapi @@ -670,7 +669,7 @@ pydantic==2.10.4 # qdrant-client pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via # feast (setup.py) # ipython @@ -693,13 +692,13 @@ pyodbc==5.2.0 # via feast (setup.py) pyopenssl==24.3.0 # via snowflake-connector-python -pyparsing==3.2.0 +pyparsing==3.2.1 # via great-expectations pyproject-hooks==1.2.0 # via # build # pip-tools -pyspark==3.5.3 +pyspark==3.5.4 # via feast (setup.py) pytest==7.4.4 # via @@ -733,6 +732,7 @@ pytest-xdist==3.6.1 # via feast (setup.py) python-dateutil==2.9.0.post0 # via + # aiobotocore # arrow # botocore # google-cloud-bigquery @@ -773,11 +773,11 @@ pyzmq==26.2.0 # ipykernel # jupyter-client # jupyter-server -qdrant-client==1.12.1 +qdrant-client==1.13.2 # via feast (setup.py) redis==4.6.0 # via feast (setup.py) -referencing==0.35.1 +referencing==0.36.2 # via # jsonschema # jsonschema-specifications @@ -811,7 +811,7 @@ requests-oauthlib==2.0.0 # via kubernetes requests-toolbelt==1.0.0 # via python-keycloak -responses==0.25.3 +responses==0.25.6 # via moto rfc3339-validator==0.1.4 # via @@ -833,15 +833,15 @@ ruamel-yaml==0.17.40 # via great-expectations ruamel-yaml-clib==0.2.12 # via ruamel-yaml -ruff==0.8.4 +ruff==0.9.3 # via feast (setup.py) -s3transfer==0.10.4 +s3transfer==0.11.2 # via boto3 -scipy==1.14.1 +scipy==1.15.1 # via great-expectations send2trash==1.8.3 # via jupyter-server -setuptools==75.6.0 +setuptools==75.8.0 # via # grpcio-tools # jupyterlab @@ -867,7 +867,7 @@ sniffio==1.3.1 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.12.4 +snowflake-connector-python[pandas]==3.13.0 # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python @@ -887,17 +887,17 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlalchemy[mypy]==2.0.36 +sqlalchemy[mypy]==2.0.37 # via feast (setup.py) sqlglot==25.20.2 # via ibis-framework -sqlite-vec==0.1.3 +sqlite-vec==0.1.1 # via feast (setup.py) -sqlparams==6.1.0 +sqlparams==6.2.0 # via singlestoredb stack-data==0.6.3 # via ipython -starlette==0.41.3 +starlette==0.45.3 # via fastapi substrait==0.23.0 # via ibis-substrait @@ -914,7 +914,7 @@ testcontainers==4.8.2 thriftpy2==0.5.2 # via happybase tinycss2==1.4.0 - # via nbconvert + # via bleach toml==0.10.2 # via feast (setup.py) tomlkit==0.13.2 @@ -953,11 +953,11 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -trino==0.331.0 +trino==0.332.0 # via feast (setup.py) typeguard==4.4.1 # via feast (setup.py) -types-cffi==1.16.0.20240331 +types-cffi==1.16.0.20241221 # via types-pyopenssl types-protobuf==3.19.22 # via @@ -971,15 +971,15 @@ types-python-dateutil==2.9.0.20241206 # via # feast (setup.py) # arrow -types-pytz==2024.2.0.20241003 +types-pytz==2024.2.0.20241221 # via feast (setup.py) -types-pyyaml==6.0.12.20240917 +types-pyyaml==6.0.12.20241230 # via feast (setup.py) types-redis==4.6.0.20241004 # via feast (setup.py) types-requests==2.30.0.0 # via feast (setup.py) -types-setuptools==75.6.0.20241126 +types-setuptools==75.8.0.20250110 # via # feast (setup.py) # types-cffi @@ -1004,11 +1004,12 @@ typing-extensions==4.12.2 # psycopg-pool # pydantic # pydantic-core + # referencing # snowflake-connector-python # sqlalchemy # testcontainers # typeguard -tzdata==2024.2 +tzdata==2025.1 # via pandas tzlocal==5.2 # via @@ -1018,9 +1019,10 @@ ujson==5.10.0 # via pymilvus uri-template==1.3.0 # via jsonschema -urllib3==2.2.3 +urllib3==2.3.0 # via # feast (setup.py) + # aiobotocore # botocore # docker # elastic-transport @@ -1035,7 +1037,7 @@ uvicorn[standard]==0.34.0 # via # feast (setup.py) # uvicorn-worker -uvicorn-worker==0.2.0 +uvicorn-worker==0.3.0 # via feast (setup.py) uvloop==0.21.0 # via uvicorn @@ -1043,7 +1045,7 @@ virtualenv==20.23.0 # via # feast (setup.py) # pre-commit -watchfiles==1.0.3 +watchfiles==1.0.4 # via uvicorn wcwidth==0.2.13 # via prompt-toolkit @@ -1057,7 +1059,7 @@ websocket-client==1.8.0 # via # jupyter-server # kubernetes -websockets==14.1 +websockets==14.2 # via uvicorn werkzeug==3.1.3 # via moto @@ -1067,7 +1069,7 @@ wheel==0.45.1 # singlestoredb widgetsnbextension==4.0.13 # via ipywidgets -wrapt==1.17.0 +wrapt==1.17.2 # via # aiobotocore # testcontainers @@ -1077,3 +1079,5 @@ yarl==1.18.3 # via aiohttp zipp==3.21.0 # via importlib-metadata +zstandard==0.23.0 + # via trino diff --git a/sdk/python/requirements/py3.11-requirements.txt b/sdk/python/requirements/py3.11-requirements.txt index 6919925f32b..09d29831c6f 100644 --- a/sdk/python/requirements/py3.11-requirements.txt +++ b/sdk/python/requirements/py3.11-requirements.txt @@ -2,38 +2,34 @@ # uv pip compile -p 3.11 --system --no-strip-extras setup.py --output-file sdk/python/requirements/py3.11-requirements.txt annotated-types==0.7.0 # via pydantic -anyio==4.7.0 +anyio==4.8.0 # via # starlette # watchfiles -attrs==24.3.0 +attrs==25.1.0 # via # jsonschema # referencing -bigtree==0.22.3 +bigtree==0.23.1 # via feast (setup.py) certifi==2024.12.14 # via requests -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via requests -click==8.1.7 +click==8.1.8 # via # feast (setup.py) # dask # uvicorn -cloudpickle==3.1.0 +cloudpickle==3.1.1 # via dask colorama==0.4.6 # via feast (setup.py) -dask[dataframe]==2024.12.1 - # via - # feast (setup.py) - # dask-expr -dask-expr==1.1.21 - # via dask +dask[dataframe]==2025.1.0 + # via feast (setup.py) dill==0.3.9 # via feast (setup.py) -fastapi==0.115.6 +fastapi==0.115.7 # via feast (setup.py) fsspec==2024.12.0 # via dask @@ -49,7 +45,7 @@ idna==3.10 # via # anyio # requests -importlib-metadata==8.5.0 +importlib-metadata==8.6.1 # via dask jinja2==3.1.5 # via feast (setup.py) @@ -61,9 +57,9 @@ locket==1.0.0 # via partd markupsafe==3.0.2 # via jinja2 -mmh3==5.0.1 +mmh3==5.1.0 # via feast (setup.py) -mypy==1.13.0 +mypy==1.14.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy @@ -80,26 +76,25 @@ pandas==2.2.3 # via # feast (setup.py) # dask - # dask-expr partd==1.4.2 # via dask prometheus-client==0.21.1 # via feast (setup.py) -protobuf==4.25.5 +protobuf==5.29.3 # via feast (setup.py) psutil==6.1.1 # via feast (setup.py) pyarrow==18.0.0 # via # feast (setup.py) - # dask-expr -pydantic==2.10.4 + # dask +pydantic==2.10.6 # via # feast (setup.py) # fastapi pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via feast (setup.py) pyjwt==2.10.1 # via feast (setup.py) @@ -114,7 +109,7 @@ pyyaml==6.0.2 # feast (setup.py) # dask # uvicorn -referencing==0.35.1 +referencing==0.36.2 # via # jsonschema # jsonschema-specifications @@ -128,9 +123,9 @@ six==1.17.0 # via python-dateutil sniffio==1.3.1 # via anyio -sqlalchemy[mypy]==2.0.36 +sqlalchemy[mypy]==2.0.37 # via feast (setup.py) -starlette==0.41.3 +starlette==0.45.3 # via fastapi tabulate==0.9.0 # via feast (setup.py) @@ -153,23 +148,24 @@ typing-extensions==4.12.2 # mypy # pydantic # pydantic-core + # referencing # sqlalchemy # typeguard -tzdata==2024.2 +tzdata==2025.1 # via pandas -urllib3==2.2.3 +urllib3==2.3.0 # via requests uvicorn[standard]==0.34.0 # via # feast (setup.py) # uvicorn-worker -uvicorn-worker==0.2.0 +uvicorn-worker==0.3.0 # via feast (setup.py) uvloop==0.21.0 # via uvicorn -watchfiles==1.0.3 +watchfiles==1.0.4 # via uvicorn -websockets==14.1 +websockets==14.2 # via uvicorn zipp==3.21.0 # via importlib-metadata diff --git a/sdk/python/requirements/py3.9-ci-requirements.txt b/sdk/python/requirements/py3.9-ci-requirements.txt index b0e00409f18..46984af1069 100644 --- a/sdk/python/requirements/py3.9-ci-requirements.txt +++ b/sdk/python/requirements/py3.9-ci-requirements.txt @@ -1,6 +1,6 @@ # This file was autogenerated by uv via the following command: # uv pip compile -p 3.9 --system --no-strip-extras setup.py --extra ci --output-file sdk/python/requirements/py3.9-ci-requirements.txt -aiobotocore==2.16.0 +aiobotocore==2.19.0 # via feast (setup.py) aiohappyeyeballs==2.4.4 # via aiohttp @@ -16,7 +16,7 @@ altair==4.2.2 # via great-expectations annotated-types==0.7.0 # via pydantic -anyio==4.7.0 +anyio==4.8.0 # via # httpx # jupyter-server @@ -48,7 +48,7 @@ async-timeout==5.0.1 # redis atpublic==4.1.0 # via ibis-framework -attrs==24.3.0 +attrs==25.1.0 # via # aiohttp # jsonschema @@ -59,7 +59,7 @@ azure-core==1.32.0 # azure-storage-blob azure-identity==1.19.0 # via feast (setup.py) -azure-storage-blob==12.24.0 +azure-storage-blob==12.24.1 # via feast (setup.py) babel==2.16.0 # via @@ -69,15 +69,15 @@ beautifulsoup4==4.12.3 # via nbconvert bidict==0.23.1 # via ibis-framework -bigtree==0.22.3 +bigtree==0.23.1 # via feast (setup.py) -bleach==6.2.0 +bleach[css]==6.2.0 # via nbconvert -boto3==1.35.81 +boto3==1.36.3 # via # feast (setup.py) # moto -botocore==1.35.81 +botocore==1.36.3 # via # aiobotocore # boto3 @@ -88,7 +88,7 @@ build==1.2.2.post1 # feast (setup.py) # pip-tools # singlestoredb -cachetools==5.5.0 +cachetools==5.5.1 # via google-auth cassandra-driver==3.29.2 # via feast (setup.py) @@ -108,11 +108,11 @@ cffi==1.17.1 # snowflake-connector-python cfgv==3.4.0 # via pre-commit -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via # requests # snowflake-connector-python -click==8.1.7 +click==8.1.8 # via # feast (setup.py) # dask @@ -120,7 +120,7 @@ click==8.1.7 # great-expectations # pip-tools # uvicorn -cloudpickle==3.1.0 +cloudpickle==3.1.1 # via dask colorama==0.4.6 # via @@ -132,7 +132,7 @@ comm==0.2.2 # ipywidgets couchbase==4.3.2 # via feast (setup.py) -coverage[toml]==7.6.9 +coverage[toml]==7.6.10 # via pytest-cov cryptography==43.0.3 # via @@ -156,15 +156,15 @@ dask[dataframe]==2024.8.0 # dask-expr dask-expr==1.1.10 # via dask -db-dtypes==1.3.1 +db-dtypes==1.4.0 # via google-cloud-bigquery -debugpy==1.8.11 +debugpy==1.8.12 # via ipykernel decorator==5.1.1 # via ipython defusedxml==0.7.1 # via nbconvert -deltalake==0.22.3 +deltalake==0.24.0 # via feast (setup.py) deprecation==2.1.0 # via python-keycloak @@ -178,9 +178,9 @@ docutils==0.19 # via sphinx duckdb==0.10.3 # via ibis-framework -elastic-transport==8.15.1 +elastic-transport==8.17.0 # via elasticsearch -elasticsearch==8.17.0 +elasticsearch==8.17.1 # via feast (setup.py) entrypoints==0.4 # via altair @@ -193,15 +193,15 @@ exceptiongroup==1.2.2 # pytest execnet==2.1.1 # via pytest-xdist -executing==2.1.0 +executing==2.2.0 # via stack-data faiss-cpu==1.9.0.post1 # via feast (setup.py) -fastapi==0.115.6 +fastapi==0.115.7 # via feast (setup.py) fastjsonschema==2.21.1 # via nbformat -filelock==3.16.1 +filelock==3.17.0 # via # snowflake-connector-python # virtualenv @@ -217,7 +217,7 @@ fsspec==2024.9.0 # dask geomet==0.2.1.post1 # via cassandra-driver -google-api-core[grpc]==2.24.0 +google-api-core[grpc]==2.24.1 # via # feast (setup.py) # google-cloud-bigquery @@ -226,7 +226,7 @@ google-api-core[grpc]==2.24.0 # google-cloud-core # google-cloud-datastore # google-cloud-storage -google-auth==2.37.0 +google-auth==2.38.0 # via # google-api-core # google-cloud-bigquery @@ -236,11 +236,11 @@ google-auth==2.37.0 # google-cloud-datastore # google-cloud-storage # kubernetes -google-cloud-bigquery[pandas]==3.27.0 +google-cloud-bigquery[pandas]==3.29.0 # via feast (setup.py) google-cloud-bigquery-storage==2.27.0 # via feast (setup.py) -google-cloud-bigtable==2.27.0 +google-cloud-bigtable==2.28.1 # via feast (setup.py) google-cloud-core==2.4.1 # via @@ -268,9 +268,9 @@ googleapis-common-protos[grpc]==1.66.0 # grpcio-status great-expectations==0.18.22 # via feast (setup.py) -grpc-google-iam-v1==0.13.1 +grpc-google-iam-v1==0.14.0 # via google-cloud-bigtable -grpcio==1.68.1 +grpcio==1.70.0 # via # feast (setup.py) # google-api-core @@ -283,15 +283,15 @@ grpcio==1.68.1 # grpcio-tools # pymilvus # qdrant-client -grpcio-health-checking==1.62.3 +grpcio-health-checking==1.70.0 # via feast (setup.py) -grpcio-reflection==1.62.3 +grpcio-reflection==1.70.0 # via feast (setup.py) -grpcio-status==1.62.3 +grpcio-status==1.70.0 # via google-api-core -grpcio-testing==1.62.3 +grpcio-testing==1.70.0 # via feast (setup.py) -grpcio-tools==1.62.3 +grpcio-tools==1.70.0 # via # feast (setup.py) # qdrant-client @@ -311,7 +311,7 @@ hazelcast-python-client==5.5.0 # via feast (setup.py) hiredis==2.4.0 # via feast (setup.py) -hpack==4.0.0 +hpack==4.1.0 # via h2 httpcore==1.0.7 # via httpx @@ -323,7 +323,7 @@ httpx[http2]==0.27.2 # jupyterlab # python-keycloak # qdrant-client -hyperframe==6.0.1 +hyperframe==6.1.0 # via h2 ibis-framework[duckdb]==9.0.0 # via @@ -331,7 +331,7 @@ ibis-framework[duckdb]==9.0.0 # ibis-substrait ibis-substrait==4.0.1 # via feast (setup.py) -identify==2.6.3 +identify==2.6.6 # via pre-commit idna==3.10 # via @@ -343,7 +343,7 @@ idna==3.10 # yarl imagesize==1.4.1 # via sphinx -importlib-metadata==8.5.0 +importlib-metadata==8.6.1 # via # build # dask @@ -384,6 +384,7 @@ jinja2==3.1.5 # sphinx jmespath==1.0.1 # via + # aiobotocore # boto3 # botocore json5==0.10.0 @@ -422,7 +423,7 @@ jupyter-events==0.11.0 # via jupyter-server jupyter-lsp==2.2.5 # via jupyterlab -jupyter-server==2.14.2 +jupyter-server==2.15.0 # via # jupyter-lsp # jupyterlab @@ -447,6 +448,8 @@ kubernetes==20.13.0 # via feast (setup.py) locket==1.0.0 # via partd +lz4==4.4.3 + # via trino makefun==1.15.6 # via great-expectations markdown-it-py==3.0.0 @@ -456,7 +459,7 @@ markupsafe==3.0.2 # jinja2 # nbconvert # werkzeug -marshmallow==3.23.2 +marshmallow==3.26.0 # via # environs # great-expectations @@ -466,15 +469,15 @@ matplotlib-inline==0.1.7 # ipython mdurl==0.1.2 # via markdown-it-py -milvus-lite==2.4.10 +milvus-lite==2.4.11 # via pymilvus minio==7.2.11 # via feast (setup.py) -mistune==3.0.2 +mistune==3.1.0 # via # great-expectations # nbconvert -mmh3==5.0.1 +mmh3==5.1.0 # via feast (setup.py) mock==2.0.0 # via feast (setup.py) @@ -488,6 +491,7 @@ msal-extensions==1.2.0 # via azure-identity multidict==6.1.0 # via + # aiobotocore # aiohttp # yarl mypy==1.11.2 @@ -500,7 +504,7 @@ mypy-protobuf==3.3.0 # via feast (setup.py) nbclient==0.10.2 # via nbconvert -nbconvert==7.16.4 +nbconvert==7.16.5 # via jupyter-server nbformat==5.10.4 # via @@ -512,7 +516,7 @@ nest-asyncio==1.6.0 # via ipykernel nodeenv==1.9.1 # via pre-commit -notebook==7.3.1 +notebook==7.3.2 # via great-expectations notebook-shim==0.2.4 # via @@ -581,7 +585,7 @@ pbr==6.1.0 # via mock pexpect==4.9.0 # via ipython -pip==24.3.1 +pip==25.0 # via pip-tools pip-tools==7.4.1 # via feast (setup.py) @@ -604,19 +608,19 @@ prometheus-client==0.21.1 # via # feast (setup.py) # jupyter-server -prompt-toolkit==3.0.48 +prompt-toolkit==3.0.50 # via ipython propcache==0.2.1 # via # aiohttp # yarl -proto-plus==1.25.0 +proto-plus==1.26.0 # via # google-api-core # google-cloud-bigquery-storage # google-cloud-bigtable # google-cloud-datastore -protobuf==4.25.5 +protobuf==5.29.3 # via # feast (setup.py) # google-api-core @@ -638,9 +642,9 @@ psutil==5.9.0 # via # feast (setup.py) # ipykernel -psycopg[binary, pool]==3.1.18 +psycopg[binary, pool]==3.2.4 # via feast (setup.py) -psycopg-binary==3.1.18 +psycopg-binary==3.2.4 # via psycopg psycopg-pool==3.2.4 # via psycopg @@ -679,7 +683,7 @@ pycparser==2.22 # via cffi pycryptodome==3.21.0 # via minio -pydantic==2.10.4 +pydantic==2.10.6 # via # feast (setup.py) # fastapi @@ -687,7 +691,7 @@ pydantic==2.10.4 # qdrant-client pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via # feast (setup.py) # ipython @@ -710,13 +714,13 @@ pyodbc==5.2.0 # via feast (setup.py) pyopenssl==24.3.0 # via snowflake-connector-python -pyparsing==3.2.0 +pyparsing==3.2.1 # via great-expectations pyproject-hooks==1.2.0 # via # build # pip-tools -pyspark==3.5.3 +pyspark==3.5.4 # via feast (setup.py) pytest==7.4.4 # via @@ -750,6 +754,7 @@ pytest-xdist==3.6.1 # via feast (setup.py) python-dateutil==2.9.0.post0 # via + # aiobotocore # arrow # botocore # google-cloud-bigquery @@ -790,11 +795,11 @@ pyzmq==26.2.0 # ipykernel # jupyter-client # jupyter-server -qdrant-client==1.12.1 +qdrant-client==1.13.2 # via feast (setup.py) redis==4.6.0 # via feast (setup.py) -referencing==0.35.1 +referencing==0.36.2 # via # jsonschema # jsonschema-specifications @@ -828,7 +833,7 @@ requests-oauthlib==2.0.0 # via kubernetes requests-toolbelt==1.0.0 # via python-keycloak -responses==0.25.3 +responses==0.25.6 # via moto rfc3339-validator==0.1.4 # via @@ -850,15 +855,15 @@ ruamel-yaml==0.17.40 # via great-expectations ruamel-yaml-clib==0.2.12 # via ruamel-yaml -ruff==0.8.4 +ruff==0.9.3 # via feast (setup.py) -s3transfer==0.10.4 +s3transfer==0.11.2 # via boto3 scipy==1.13.1 # via great-expectations send2trash==1.8.3 # via jupyter-server -setuptools==75.6.0 +setuptools==75.8.0 # via # grpcio-tools # jupyterlab @@ -884,7 +889,7 @@ sniffio==1.3.1 # httpx snowballstemmer==2.2.0 # via sphinx -snowflake-connector-python[pandas]==3.12.4 +snowflake-connector-python[pandas]==3.13.0 # via feast (setup.py) sortedcontainers==2.4.0 # via snowflake-connector-python @@ -904,17 +909,17 @@ sphinxcontrib-qthelp==2.0.0 # via sphinx sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sqlalchemy[mypy]==2.0.36 +sqlalchemy[mypy]==2.0.37 # via feast (setup.py) sqlglot==23.12.2 # via ibis-framework -sqlite-vec==0.1.3 +sqlite-vec==0.1.1 # via feast (setup.py) -sqlparams==6.1.0 +sqlparams==6.2.0 # via singlestoredb stack-data==0.6.3 # via ipython -starlette==0.41.3 +starlette==0.45.3 # via fastapi substrait==0.23.0 # via ibis-substrait @@ -931,7 +936,7 @@ testcontainers==4.8.2 thriftpy2==0.5.2 # via happybase tinycss2==1.4.0 - # via nbconvert + # via bleach toml==0.10.2 # via feast (setup.py) tomli==2.2.1 @@ -980,11 +985,11 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -trino==0.331.0 +trino==0.332.0 # via feast (setup.py) typeguard==4.4.1 # via feast (setup.py) -types-cffi==1.16.0.20240331 +types-cffi==1.16.0.20241221 # via types-pyopenssl types-protobuf==3.19.22 # via @@ -998,15 +1003,15 @@ types-python-dateutil==2.9.0.20241206 # via # feast (setup.py) # arrow -types-pytz==2024.2.0.20241003 +types-pytz==2024.2.0.20241221 # via feast (setup.py) -types-pyyaml==6.0.12.20240917 +types-pyyaml==6.0.12.20241230 # via feast (setup.py) types-redis==4.6.0.20241004 # via feast (setup.py) types-requests==2.30.0.0 # via feast (setup.py) -types-setuptools==75.6.0.20241126 +types-setuptools==75.8.0.20250110 # via # feast (setup.py) # types-cffi @@ -1028,6 +1033,7 @@ typing-extensions==4.12.2 # ipython # jwcrypto # minio + # mistune # multidict # mypy # psycopg @@ -1035,6 +1041,7 @@ typing-extensions==4.12.2 # pydantic # pydantic-core # python-json-logger + # referencing # rich # snowflake-connector-python # sqlalchemy @@ -1042,7 +1049,7 @@ typing-extensions==4.12.2 # testcontainers # typeguard # uvicorn -tzdata==2024.2 +tzdata==2025.1 # via pandas tzlocal==5.2 # via @@ -1055,6 +1062,7 @@ uri-template==1.3.0 urllib3==1.26.20 # via # feast (setup.py) + # aiobotocore # botocore # docker # elastic-transport @@ -1070,7 +1078,7 @@ uvicorn[standard]==0.34.0 # via # feast (setup.py) # uvicorn-worker -uvicorn-worker==0.2.0 +uvicorn-worker==0.3.0 # via feast (setup.py) uvloop==0.21.0 # via uvicorn @@ -1078,7 +1086,7 @@ virtualenv==20.23.0 # via # feast (setup.py) # pre-commit -watchfiles==1.0.3 +watchfiles==1.0.4 # via uvicorn wcwidth==0.2.13 # via prompt-toolkit @@ -1092,7 +1100,7 @@ websocket-client==1.8.0 # via # jupyter-server # kubernetes -websockets==14.1 +websockets==14.2 # via uvicorn werkzeug==3.1.3 # via moto @@ -1102,7 +1110,7 @@ wheel==0.45.1 # singlestoredb widgetsnbextension==4.0.13 # via ipywidgets -wrapt==1.17.0 +wrapt==1.17.2 # via # aiobotocore # testcontainers @@ -1112,3 +1120,5 @@ yarl==1.18.3 # via aiohttp zipp==3.21.0 # via importlib-metadata +zstandard==0.23.0 + # via trino diff --git a/sdk/python/requirements/py3.9-requirements.txt b/sdk/python/requirements/py3.9-requirements.txt index aab78c8ceef..bdef2f69c81 100644 --- a/sdk/python/requirements/py3.9-requirements.txt +++ b/sdk/python/requirements/py3.9-requirements.txt @@ -2,26 +2,26 @@ # uv pip compile -p 3.9 --system --no-strip-extras setup.py --output-file sdk/python/requirements/py3.9-requirements.txt annotated-types==0.7.0 # via pydantic -anyio==4.7.0 +anyio==4.8.0 # via # starlette # watchfiles -attrs==24.3.0 +attrs==25.1.0 # via # jsonschema # referencing -bigtree==0.22.3 +bigtree==0.23.1 # via feast (setup.py) certifi==2024.12.14 # via requests -charset-normalizer==3.4.0 +charset-normalizer==3.4.1 # via requests -click==8.1.7 +click==8.1.8 # via # feast (setup.py) # dask # uvicorn -cloudpickle==3.1.0 +cloudpickle==3.1.1 # via dask colorama==0.4.6 # via feast (setup.py) @@ -35,7 +35,7 @@ dill==0.3.9 # via feast (setup.py) exceptiongroup==1.2.2 # via anyio -fastapi==0.115.6 +fastapi==0.115.7 # via feast (setup.py) fsspec==2024.12.0 # via dask @@ -51,7 +51,7 @@ idna==3.10 # via # anyio # requests -importlib-metadata==8.5.0 +importlib-metadata==8.6.1 # via # dask # typeguard @@ -65,9 +65,9 @@ locket==1.0.0 # via partd markupsafe==3.0.2 # via jinja2 -mmh3==5.0.1 +mmh3==5.1.0 # via feast (setup.py) -mypy==1.13.0 +mypy==1.14.1 # via sqlalchemy mypy-extensions==1.0.0 # via mypy @@ -89,7 +89,7 @@ partd==1.4.2 # via dask prometheus-client==0.21.1 # via feast (setup.py) -protobuf==4.25.5 +protobuf==5.29.3 # via feast (setup.py) psutil==6.1.1 # via feast (setup.py) @@ -97,13 +97,13 @@ pyarrow==18.0.0 # via # feast (setup.py) # dask-expr -pydantic==2.10.4 +pydantic==2.10.6 # via # feast (setup.py) # fastapi pydantic-core==2.27.2 # via pydantic -pygments==2.18.0 +pygments==2.19.1 # via feast (setup.py) pyjwt==2.10.1 # via feast (setup.py) @@ -118,7 +118,7 @@ pyyaml==6.0.2 # feast (setup.py) # dask # uvicorn -referencing==0.35.1 +referencing==0.36.2 # via # jsonschema # jsonschema-specifications @@ -132,9 +132,9 @@ six==1.17.0 # via python-dateutil sniffio==1.3.1 # via anyio -sqlalchemy[mypy]==2.0.36 +sqlalchemy[mypy]==2.0.37 # via feast (setup.py) -starlette==0.41.3 +starlette==0.45.3 # via fastapi tabulate==0.9.0 # via feast (setup.py) @@ -159,25 +159,26 @@ typing-extensions==4.12.2 # mypy # pydantic # pydantic-core + # referencing # sqlalchemy # starlette # typeguard # uvicorn -tzdata==2024.2 +tzdata==2025.1 # via pandas -urllib3==2.2.3 +urllib3==2.3.0 # via requests uvicorn[standard]==0.34.0 # via # feast (setup.py) # uvicorn-worker -uvicorn-worker==0.2.0 +uvicorn-worker==0.3.0 # via feast (setup.py) uvloop==0.21.0 # via uvicorn -watchfiles==1.0.3 +watchfiles==1.0.4 # via uvicorn -websockets==14.1 +websockets==14.2 # via uvicorn zipp==3.21.0 # via importlib-metadata diff --git a/sdk/python/tests/example_repos/example_feature_repo_1.py b/sdk/python/tests/example_repos/example_feature_repo_1.py index daf7b7e7e6f..ea33859f4de 100644 --- a/sdk/python/tests/example_repos/example_feature_repo_1.py +++ b/sdk/python/tests/example_repos/example_feature_repo_1.py @@ -118,7 +118,12 @@ name="document_embeddings", entities=[item], schema=[ - Field(name="Embeddings", dtype=Array(Float32)), + Field( + name="Embeddings", + dtype=Array(Float32), + vector_index=True, + vector_search_metric="L2", + ), Field(name="item_id", dtype=String), ], source=rag_documents_source, diff --git a/sdk/python/tests/example_repos/example_rag_feature_repo.py b/sdk/python/tests/example_repos/example_rag_feature_repo.py index 2f55095bc69..d87a2a34df1 100644 --- a/sdk/python/tests/example_repos/example_rag_feature_repo.py +++ b/sdk/python/tests/example_repos/example_rag_feature_repo.py @@ -1,7 +1,7 @@ from datetime import timedelta from feast import Entity, FeatureView, Field, FileSource -from feast.types import Array, Float32, Int64, UnixTimestamp +from feast.types import Array, Float32, Int64, String, UnixTimestamp, ValueType # This is for Milvus # Note that file source paths are not validated, so there doesn't actually need to be any data @@ -17,20 +17,29 @@ item = Entity( name="item_id", # The name is derived from this argument, not object name. join_keys=["item_id"], + value_type=ValueType.INT64, +) + +author = Entity( + name="author_id", + join_keys=["author_id"], + value_type=ValueType.STRING, ) document_embeddings = FeatureView( name="embedded_documents", - entities=[item], + entities=[item, author], schema=[ Field( name="vector", dtype=Array(Float32), vector_index=True, - vector_search_metric="L2", + vector_search_metric="COSINE", ), Field(name="item_id", dtype=Int64), + Field(name="author_id", dtype=String), Field(name="created_timestamp", dtype=UnixTimestamp), + Field(name="sentence_chunks", dtype=String), Field(name="event_timestamp", dtype=UnixTimestamp), ], source=rag_documents_source, diff --git a/sdk/python/tests/foo_provider.py b/sdk/python/tests/foo_provider.py index 3d1f9219991..ca6a02c4bd0 100644 --- a/sdk/python/tests/foo_provider.py +++ b/sdk/python/tests/foo_provider.py @@ -164,6 +164,23 @@ def retrieve_online_documents( ]: return [] + def retrieve_online_documents_v2( + self, + config: RepoConfig, + table: FeatureView, + requested_features: List[str], + query: List[float], + top_k: int, + distance_metric: Optional[str] = None, + ) -> List[ + Tuple[ + Optional[datetime], + Optional[EntityKeyProto], + Optional[Dict[str, ValueProto]], + ] + ]: + return [] + def validate_data_source( self, config: RepoConfig, diff --git a/sdk/python/tests/integration/materialization/test_snowflake.py b/sdk/python/tests/integration/materialization/test_snowflake.py index 5f01641c3b5..a783eac0380 100644 --- a/sdk/python/tests/integration/materialization/test_snowflake.py +++ b/sdk/python/tests/integration/materialization/test_snowflake.py @@ -178,9 +178,9 @@ def test_snowflake_materialization_consistency_internal_with_lists( assert actual_value is not None, f"Response: {response_dict}" if feature_dtype == "float": for actual_num, expected_num in zip(actual_value, expected_value): - assert ( - abs(actual_num - expected_num) < 1e-6 - ), f"Response: {response_dict}, Expected: {expected_value}" + assert abs(actual_num - expected_num) < 1e-6, ( + f"Response: {response_dict}, Expected: {expected_value}" + ) else: assert actual_value == expected_value diff --git a/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py b/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py index 3f28245f3c7..37df649386c 100644 --- a/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py +++ b/sdk/python/tests/integration/offline_store/test_universal_historical_retrieval.py @@ -14,7 +14,7 @@ from feast.infra.offline_stores.offline_utils import ( DEFAULT_ENTITY_DF_EVENT_TIMESTAMP_COL, ) -from feast.types import Float32, Int32 +from feast.types import Float32, Int32, String from feast.utils import _utc_now from tests.integration.feature_repos.repo_configuration import ( construct_universal_feature_views, @@ -639,3 +639,100 @@ def test_historical_features_containing_backfills(environment): actual_df, sort_by=["driver_id"], ) + + +@pytest.mark.integration +@pytest.mark.universal_offline_stores +@pytest.mark.parametrize("full_feature_names", [True, False], ids=lambda v: str(v)) +def test_historical_features_field_mapping( + environment, universal_data_sources, full_feature_names +): + store = environment.feature_store + + # (entities, datasets, data_sources) = universal_data_sources + # feature_views = construct_universal_feature_views(data_sources) + + now = datetime.now().replace(microsecond=0, second=0, minute=0) + tomorrow = now + timedelta(days=1) + day_after_tomorrow = now + timedelta(days=2) + + entity_df = pd.DataFrame( + data=[ + {"driver_id": 1001, "event_timestamp": day_after_tomorrow}, + {"driver_id": 1002, "event_timestamp": day_after_tomorrow}, + ] + ) + + driver_stats_df = pd.DataFrame( + data=[ + { + "id": 1001, + "avg_daily_trips": 20, + "event_timestamp": now, + "created": tomorrow, + }, + { + "id": 1002, + "avg_daily_trips": 40, + "event_timestamp": tomorrow, + "created": now, + }, + ] + ) + + expected_df = pd.DataFrame( + data=[ + { + "driver_id": 1001, + "event_timestamp": day_after_tomorrow, + "avg_daily_trips": 20, + }, + { + "driver_id": 1002, + "event_timestamp": day_after_tomorrow, + "avg_daily_trips": 40, + }, + ] + ) + + driver_stats_data_source = environment.data_source_creator.create_data_source( + df=driver_stats_df, + destination_name=f"test_driver_stats_{int(time.time_ns())}_{random.randint(1000, 9999)}", + timestamp_field="event_timestamp", + created_timestamp_column="created", + # Map original "id" column to "driver_id" join key + field_mapping={"id": "driver_id"}, + ) + + driver = Entity(name="driver", join_keys=["driver_id"]) + driver_fv = FeatureView( + name="driver_stats", + entities=[driver], + schema=[ + Field(name="driver_id", dtype=String), + Field(name="avg_daily_trips", dtype=Int32), + ], + source=driver_stats_data_source, + ) + + store.apply([driver, driver_fv]) + + offline_job = store.get_historical_features( + entity_df=entity_df, + features=["driver_stats:avg_daily_trips"], + full_feature_names=False, + ) + + start_time = _utc_now() + actual_df = offline_job.to_df() + + print(f"actual_df shape: {actual_df.shape}") + end_time = _utc_now() + print(str(f"Time to execute job_from_df.to_df() = '{(end_time - start_time)}'\n")) + + assert sorted(expected_df.columns) == sorted(actual_df.columns) + validate_dataframes( + expected_df, + actual_df, + sort_by=["driver_id"], + ) diff --git a/sdk/python/tests/integration/registration/test_universal_types.py b/sdk/python/tests/integration/registration/test_universal_types.py index 928d05ad31e..9d0b620c083 100644 --- a/sdk/python/tests/integration/registration/test_universal_types.py +++ b/sdk/python/tests/integration/registration/test_universal_types.py @@ -171,9 +171,9 @@ def test_feature_get_online_features_types_match( if config.feature_is_list: for feature in online_features["value"]: assert isinstance(feature, list), "Feature value should be a list" - assert ( - config.has_empty_list or len(feature) > 0 - ), "List of values should not be empty" + assert config.has_empty_list or len(feature) > 0, ( + "List of values should not be empty" + ) for element in feature: assert isinstance(element, expected_dtype) else: @@ -224,7 +224,9 @@ def assert_expected_historical_feature_types( dtype_checkers = feature_dtype_to_expected_historical_feature_dtype[feature_dtype] assert any( check(historical_features_df.dtypes["value"]) for check in dtype_checkers - ), f"Failed to match feature type {historical_features_df.dtypes['value']} with checkers {dtype_checkers}" + ), ( + f"Failed to match feature type {historical_features_df.dtypes['value']} with checkers {dtype_checkers}" + ) def assert_feature_list_types( diff --git a/sdk/python/tests/unit/infra/offline_stores/test_snowflake.py b/sdk/python/tests/unit/infra/offline_stores/test_snowflake.py index 59caaf0b5f2..d692d0f957a 100644 --- a/sdk/python/tests/unit/infra/offline_stores/test_snowflake.py +++ b/sdk/python/tests/unit/infra/offline_stores/test_snowflake.py @@ -56,9 +56,9 @@ def test_to_remote_storage(retrieval_job): retrieval_job, "_get_file_names_from_copy_into", return_value=stored_files ) as mock_get_file_names_from_copy, ): - assert ( - retrieval_job.to_remote_storage() == stored_files - ), "should return the list of files" + assert retrieval_job.to_remote_storage() == stored_files, ( + "should return the list of files" + ) mock_to_snowflake.assert_called_once() mock_get_file_names_from_copy.assert_called_once_with(ANY, ANY) native_path = mock_get_file_names_from_copy.call_args[0][1] diff --git a/sdk/python/tests/unit/infra/test_key_encoding_utils.py b/sdk/python/tests/unit/infra/test_key_encoding_utils.py index 658c04c358b..cad5e27f367 100644 --- a/sdk/python/tests/unit/infra/test_key_encoding_utils.py +++ b/sdk/python/tests/unit/infra/test_key_encoding_utils.py @@ -52,6 +52,24 @@ def test_deserialize_entity_key(): ) +def test_deserialize_multiple_entity_keys(): + entity_key_proto = EntityKeyProto( + join_keys=["customer", "user"], + entity_values=[ValueProto(string_val="test"), ValueProto(int64_val=int(2**15))], + ) + + serialized_entity_key = serialize_entity_key( + entity_key_proto, + entity_key_serialization_version=3, + ) + + deserialized_entity_key = deserialize_entity_key( + serialized_entity_key, + entity_key_serialization_version=3, + ) + assert deserialized_entity_key == entity_key_proto + + def test_serialize_value(): v, t = _serialize_val("string_val", ValueProto(string_val="test")) assert t == ValueType.STRING diff --git a/sdk/python/tests/unit/online_store/test_online_retrieval.py b/sdk/python/tests/unit/online_store/test_online_retrieval.py index 5f0796f4eed..6b0adb62630 100644 --- a/sdk/python/tests/unit/online_store/test_online_retrieval.py +++ b/sdk/python/tests/unit/online_store/test_online_retrieval.py @@ -17,6 +17,7 @@ from feast.protos.feast.types.Value_pb2 import FloatList as FloatListProto from feast.protos.feast.types.Value_pb2 import Value as ValueProto from feast.repo_config import RegistryConfig +from feast.types import ValueType from feast.utils import _utc_now from tests.integration.feature_repos.universal.feature_views import TAGS from tests.utils.cli_repo_creator import CliRunner, get_example_repo @@ -28,7 +29,8 @@ def test_get_online_features() -> None: """ runner = CliRunner() with runner.local_repo( - get_example_repo("example_feature_repo_1.py"), "file" + get_example_repo("example_feature_repo_1.py"), + "file", ) as store: # Write some data to two tables driver_locations_fv = store.get_feature_view(name="driver_locations") @@ -273,6 +275,178 @@ def test_get_online_features() -> None: os.rename(store.config.registry.path + "_fake", store.config.registry.path) +def test_get_online_features_milvus() -> None: + """ + Test reading from the online store in local mode. + """ + runner = CliRunner() + with runner.local_repo( + get_example_repo("example_feature_repo_1.py"), + offline_store="file", + online_store="milvus", + apply=False, + teardown=False, + ) as store: + from tests.example_repos.example_feature_repo_1 import ( + all_drivers_feature_service, + customer, + customer_driver_combined, + customer_driver_combined_source, + customer_profile, + customer_profile_pandas_odfv, + customer_profile_source, + driver, + driver_locations, + driver_locations_source, + item, + pushed_driver_locations, + rag_documents_source, + ) + + store.apply( + [ + driver_locations_source, + customer_profile_source, + customer_driver_combined_source, + rag_documents_source, + driver, + customer, + item, + driver_locations, + pushed_driver_locations, + customer_profile, + customer_driver_combined, + # document_embeddings, + customer_profile_pandas_odfv, + all_drivers_feature_service, + ] + ) + + # Write some data to two tables + driver_locations_fv = store.get_feature_view(name="driver_locations") + customer_profile_fv = store.get_feature_view(name="customer_profile") + customer_driver_combined_fv = store.get_feature_view( + name="customer_driver_combined" + ) + + provider = store._get_provider() + + driver_key = EntityKeyProto( + join_keys=["driver_id"], entity_values=[ValueProto(int64_val=1)] + ) + provider.online_write_batch( + config=store.config, + table=driver_locations_fv, + data=[ + ( + driver_key, + { + "lat": ValueProto(double_val=0.1), + "lon": ValueProto(string_val="1.0"), + }, + _utc_now(), + _utc_now(), + ) + ], + progress=None, + ) + + customer_key = EntityKeyProto( + join_keys=["customer_id"], entity_values=[ValueProto(string_val="5")] + ) + provider.online_write_batch( + config=store.config, + table=customer_profile_fv, + data=[ + ( + customer_key, + { + "avg_orders_day": ValueProto(float_val=1.0), + "name": ValueProto(string_val="John"), + "age": ValueProto(int64_val=3), + }, + _utc_now(), + _utc_now(), + ) + ], + progress=None, + ) + + customer_key = EntityKeyProto( + join_keys=["customer_id", "driver_id"], + entity_values=[ValueProto(string_val="5"), ValueProto(int64_val=1)], + ) + provider.online_write_batch( + config=store.config, + table=customer_driver_combined_fv, + data=[ + ( + customer_key, + {"trips": ValueProto(int64_val=7)}, + _utc_now(), + _utc_now(), + ) + ], + progress=None, + ) + + assert len(store.list_entities()) == 3 + assert len(store.list_entities(tags=TAGS)) == 2 + + # Retrieve two features using two keys, one valid one non-existing + result = store.get_online_features( + features=[ + "driver_locations:lon", + "customer_profile:avg_orders_day", + "customer_profile:name", + "customer_driver_combined:trips", + ], + entity_rows=[ + {"driver_id": 1, "customer_id": "5"}, + {"driver_id": 1, "customer_id": 5}, + ], + full_feature_names=False, + ).to_dict() + + assert "lon" in result + assert "avg_orders_day" in result + assert "name" in result + assert result["driver_id"] == [1, 1] + assert result["customer_id"] == ["5", "5"] + assert result["lon"] == ["1.0", "1.0"] + assert result["avg_orders_day"] == [1.0, 1.0] + assert result["name"] == ["John", "John"] + assert result["trips"] == [7, 7] + + # Ensure features are still in result when keys not found + result = store.get_online_features( + features=["customer_driver_combined:trips"], + entity_rows=[{"driver_id": 0, "customer_id": 0}], + full_feature_names=False, + ).to_dict() + + assert "trips" in result + + result = store.get_online_features( + features=["customer_profile_pandas_odfv:on_demand_age"], + entity_rows=[{"driver_id": 1, "customer_id": "5"}], + full_feature_names=False, + ).to_dict() + + assert "on_demand_age" in result + assert result["driver_id"] == [1] + assert result["customer_id"] == ["5"] + assert result["on_demand_age"] == [4] + + # invalid table reference + with pytest.raises(FeatureViewNotFoundException): + store.get_online_features( + features=["driver_locations_bad:lon"], + entity_rows=[{"driver_id": 1}], + full_feature_names=False, + ) + + def test_online_to_df(): """ Test dataframe conversion. Make sure the response columns and rows are @@ -564,6 +738,7 @@ def test_sqlite_vec_import() -> None: assert result == [(2, 2.39), (1, 2.39)] +@pytest.mark.skip(reason="Skipping this test as CI struggles with it") def test_local_milvus() -> None: import random @@ -619,7 +794,7 @@ def test_local_milvus() -> None: client.drop_collection(collection_name=COLLECTION_NAME) -def test_milvus_lite_get_online_documents() -> None: +def test_milvus_lite_get_online_documents_v2() -> None: """ Test retrieving documents from the online store in local mode. """ @@ -638,7 +813,7 @@ def test_milvus_lite_get_online_documents() -> None: from datetime import timedelta from feast import Entity, FeatureView, Field, FileSource - from feast.types import Array, Float32, Int64, UnixTimestamp + from feast.types import Array, Float32, Int64, String, UnixTimestamp # This is for Milvus # Note that file source paths are not validated, so there doesn't actually need to be any data @@ -654,20 +829,28 @@ def test_milvus_lite_get_online_documents() -> None: item = Entity( name="item_id", # The name is derived from this argument, not object name. join_keys=["item_id"], + value_type=ValueType.INT64, + ) + author = Entity( + name="author_id", + join_keys=["author_id"], + value_type=ValueType.STRING, ) document_embeddings = FeatureView( name="embedded_documents", - entities=[item], + entities=[item, author], schema=[ Field( name="vector", dtype=Array(Float32), vector_index=True, - vector_search_metric="L2", + vector_search_metric="COSINE", ), Field(name="item_id", dtype=Int64), + Field(name="author_id", dtype=String), Field(name="created_timestamp", dtype=UnixTimestamp), + Field(name="sentence_chunks", dtype=String), Field(name="event_timestamp", dtype=UnixTimestamp), ], source=rag_documents_source, @@ -683,12 +866,16 @@ def test_milvus_lite_get_online_documents() -> None: item_keys = [ EntityKeyProto( - join_keys=["item_id"], entity_values=[ValueProto(int64_val=i)] + join_keys=["item_id", "author_id"], + entity_values=[ + ValueProto(int64_val=i), + ValueProto(string_val=f"author_{i}"), + ], ) for i in range(n) ] data = [] - for item_key in item_keys: + for i, item_key in enumerate(item_keys): data.append( ( item_key, @@ -698,8 +885,10 @@ def test_milvus_lite_get_online_documents() -> None: val=np.random.random( vector_length, ) + + i ) - ) + ), + "sentence_chunks": ValueProto(string_val=f"sentence chunk {i}"), }, _utc_now(), _utc_now(), @@ -715,12 +904,15 @@ def test_milvus_lite_get_online_documents() -> None: documents_df = pd.DataFrame( { "item_id": [str(i) for i in range(n)], + "author_id": [f"author_{i}" for i in range(n)], "vector": [ np.random.random( vector_length, ) + + i for i in range(n) ], + "sentence_chunks": [f"sentence chunk {i}" for i in range(n)], "event_timestamp": [_utc_now() for _ in range(n)], "created_timestamp": [_utc_now() for _ in range(n)], } @@ -734,10 +926,103 @@ def test_milvus_lite_get_online_documents() -> None: query_embedding = np.random.random( vector_length, ) - result = store.retrieve_online_documents( - feature="embedded_documents:vector", query=query_embedding, top_k=3 + + client = store._provider._online_store.client + collection_name = client.list_collections()[0] + search_params = { + "metric_type": "COSINE", + "params": {"nprobe": 10}, + } + + results = client.search( + collection_name=collection_name, + data=[query_embedding], + anns_field="vector", + search_params=search_params, + limit=3, + output_fields=[ + "item_id", + "author_id", + "sentence_chunks", + "created_ts", + "event_ts", + ], + ) + result = store.retrieve_online_documents_v2( + features=[ + "embedded_documents:vector", + "embedded_documents:item_id", + "embedded_documents:author_id", + "embedded_documents:sentence_chunks", + ], + query=query_embedding, + top_k=3, ).to_dict() - assert "vector" in result - assert "distance" in result - assert len(result["distance"]) == 3 + for k in ["vector", "item_id", "author_id", "sentence_chunks", "distance"]: + assert k in result, f"Missing {k} in retrieve_online_documents response" + assert len(result["distance"]) == len(results[0]) + + +def test_milvus_native_from_feast_data() -> None: + import random + from datetime import datetime + + import numpy as np + from pymilvus import MilvusClient + + random.seed(42) + VECTOR_LENGTH = 10 # Matches vector_length from the Feast example + COLLECTION_NAME = "embedded_documents" + + # Initialize Milvus client with local setup + client = MilvusClient("./milvus_demo.db") + + # Clear and recreate collection + for collection in client.list_collections(): + client.drop_collection(collection_name=collection) + client.create_collection( + collection_name=COLLECTION_NAME, + dimension=VECTOR_LENGTH, + metric_type="COSINE", # Matches Feast's vector_search_metric + ) + assert client.list_collections() == [COLLECTION_NAME] + + # Prepare data for insertion, similar to the Feast example + n = 10 # Number of items + data = [] + for i in range(n): + vector = (np.random.random(VECTOR_LENGTH) + i).tolist() + data.append( + { + "id": i, + "vector": vector, + "item_id": i, + "author_id": f"author_{i}", + "sentence_chunks": f"sentence chunk {i}", + "event_timestamp": datetime.utcnow().isoformat(), + "created_timestamp": datetime.utcnow().isoformat(), + } + ) + + print("Data has", len(data), "entities, each with fields:", data[0].keys()) + + # Insert data into Milvus + insert_res = client.insert(collection_name=COLLECTION_NAME, data=data) + assert insert_res == {"insert_count": n, "ids": list(range(n)), "cost": 0} + + # Perform a vector search using a random query embedding + query_embedding = (np.random.random(VECTOR_LENGTH)).tolist() + search_res = client.search( + collection_name=COLLECTION_NAME, + data=[query_embedding], + limit=3, # Top 3 results + output_fields=["item_id", "author_id", "sentence_chunks"], + ) + + # Validate the search results + assert len(search_res[0]) == 3 + print("Search Results:", search_res[0]) + + # Clean up the collection + client.drop_collection(collection_name=COLLECTION_NAME) diff --git a/sdk/python/tests/unit/permissions/test_oidc_auth_client.py b/sdk/python/tests/unit/permissions/test_oidc_auth_client.py index 68aec70fc79..3d74eb2a55f 100644 --- a/sdk/python/tests/unit/permissions/test_oidc_auth_client.py +++ b/sdk/python/tests/unit/permissions/test_oidc_auth_client.py @@ -58,6 +58,6 @@ def _assert_auth_requests_session( "Authorization header is missing in object of class: " "AuthenticatedRequestsSession " ) - assert ( - auth_req_session.headers["Authorization"] == f"Bearer {expected_token}" - ), "Authorization token is incorrect" + assert auth_req_session.headers["Authorization"] == f"Bearer {expected_token}", ( + "Authorization token is incorrect" + ) diff --git a/sdk/python/tests/unit/test_repo_operations_validate_feast_project_name.py b/sdk/python/tests/unit/test_repo_operations_validate_feast_project_name.py index 0dc4b2651b0..33d1d5307d6 100644 --- a/sdk/python/tests/unit/test_repo_operations_validate_feast_project_name.py +++ b/sdk/python/tests/unit/test_repo_operations_validate_feast_project_name.py @@ -21,6 +21,6 @@ def test_is_valid_name(): ] for name, expected in test_cases: - assert ( - is_valid_name(name) == expected - ), f"Failed for project invalid name: {name}" + assert is_valid_name(name) == expected, ( + f"Failed for project invalid name: {name}" + ) diff --git a/sdk/python/tests/utils/auth_permissions_util.py b/sdk/python/tests/utils/auth_permissions_util.py index 8a1e7b7c4d7..dcc456e1d82 100644 --- a/sdk/python/tests/utils/auth_permissions_util.py +++ b/sdk/python/tests/utils/auth_permissions_util.py @@ -101,9 +101,9 @@ def start_feature_server( timeout_msg="Unable to start the Prometheus server in 60 seconds.", ) else: - assert not check_port_open( - "localhost", 8000 - ), "Prometheus server is running when it should be disabled." + assert not check_port_open("localhost", 8000), ( + "Prometheus server is running when it should be disabled." + ) online_server_url = ( f"https://localhost:{server_port}" diff --git a/sdk/python/tests/utils/cli_repo_creator.py b/sdk/python/tests/utils/cli_repo_creator.py index 8bb696f7d4e..46df563eafb 100644 --- a/sdk/python/tests/utils/cli_repo_creator.py +++ b/sdk/python/tests/utils/cli_repo_creator.py @@ -117,9 +117,9 @@ def local_repo( stderr = result.stderr.decode("utf-8") print(f"Apply stdout:\n{stdout}") print(f"Apply stderr:\n{stderr}") - assert ( - result.returncode == 0 - ), f"stdout: {result.stdout}\nstderr: {result.stderr}" + assert result.returncode == 0, ( + f"stdout: {result.stdout}\nstderr: {result.stderr}" + ) yield FeatureStore(repo_path=str(repo_path), config=None) @@ -129,6 +129,6 @@ def local_repo( stderr = result.stderr.decode("utf-8") print(f"Apply stdout:\n{stdout}") print(f"Apply stderr:\n{stderr}") - assert ( - result.returncode == 0 - ), f"stdout: {result.stdout}\nstderr: {result.stderr}" + assert result.returncode == 0, ( + f"stdout: {result.stdout}\nstderr: {result.stderr}" + ) diff --git a/sdk/python/tests/utils/e2e_test_validation.py b/sdk/python/tests/utils/e2e_test_validation.py index a08e8fef429..ed66aead87d 100644 --- a/sdk/python/tests/utils/e2e_test_validation.py +++ b/sdk/python/tests/utils/e2e_test_validation.py @@ -131,17 +131,17 @@ def _check_offline_and_online_features( if full_feature_names: if expected_value: assert response_dict[f"{fv.name}__value"][0], f"Response: {response_dict}" - assert ( - abs(response_dict[f"{fv.name}__value"][0] - expected_value) < 1e-6 - ), f"Response: {response_dict}, Expected: {expected_value}" + assert abs(response_dict[f"{fv.name}__value"][0] - expected_value) < 1e-6, ( + f"Response: {response_dict}, Expected: {expected_value}" + ) else: assert response_dict[f"{fv.name}__value"][0] is None else: if expected_value: assert response_dict["value"][0], f"Response: {response_dict}" - assert ( - abs(response_dict["value"][0] - expected_value) < 1e-6 - ), f"Response: {response_dict}, Expected: {expected_value}" + assert abs(response_dict["value"][0] - expected_value) < 1e-6, ( + f"Response: {response_dict}, Expected: {expected_value}" + ) else: assert response_dict["value"][0] is None diff --git a/setup.py b/setup.py index 034f6699105..16240ff2722 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ "click>=7.0.0,<9.0.0", "colorama>=0.3.9,<1", "dill~=0.3.0", - "protobuf>=4.24.0,<5.0.0", + "protobuf>=4.24.0", "Jinja2>=2,<4", "jsonschema", "mmh3", diff --git a/ui/.nvmrc b/ui/.nvmrc index 67e145bf0f9..cc7ce7f49fe 100644 --- a/ui/.nvmrc +++ b/ui/.nvmrc @@ -1 +1 @@ -v20.18.0 +v22.13.1 diff --git a/ui/config/jest/cssTransform.js b/ui/config/jest/cssTransform.js index 8f65114812a..835cb114295 100644 --- a/ui/config/jest/cssTransform.js +++ b/ui/config/jest/cssTransform.js @@ -5,7 +5,7 @@ module.exports = { process() { - return 'module.exports = {};'; + return { code: 'module.exports = {};' }; }, getCacheKey() { // The output is always the same. diff --git a/ui/jest.config.js b/ui/jest.config.js index facad214439..c17029cc614 100644 --- a/ui/jest.config.js +++ b/ui/jest.config.js @@ -1,3 +1,5 @@ +const transformNodeModules = ['@elastic/eui', 'uuid']; + module.exports = { roots: ["/src"], collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"], @@ -7,7 +9,13 @@ module.exports = { "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", "/src/**/*.{spec,test}.{js,jsx,ts,tsx}", ], - testEnvironment: "jsdom", + // Couldn't get tests working with msw 2 in jsdom or jest-fixed-jsdom, + // happy-dom finally worked with added globals + testEnvironment: "@happy-dom/jest-environment", + // https://mswjs.io/docs/migrations/1.x-to-2.x#cannot-find-module-mswnode-jsdom + testEnvironmentOptions: { + customExportConditions: [''], + }, transform: { "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "/config/jest/babelTransform.js", "^.+\\.css$": "/config/jest/cssTransform.js", @@ -15,7 +23,7 @@ module.exports = { "/config/jest/fileTransform.js", }, transformIgnorePatterns: [ - "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", + `[/\\\\]node_modules[/\\\\](?!(${transformNodeModules.map(name => name.replaceAll('/', '[/\\\\]')).join('|')})[/\\\\])`, "^.+\\.module\\.(css|sass|scss)$", ], modulePaths: [], diff --git a/ui/package.json b/ui/package.json index e057b0e5e42..c5a52cdb029 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "@feast-dev/feast-ui", - "version": "0.43.0", + "version": "0.44.0", "private": false, "files": [ "dist" @@ -56,6 +56,7 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-env": "^7.25.8", "@babel/preset-react": "^7.25.7", + "@happy-dom/jest-environment": "^16.7.3", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", "@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-commonjs": "^21.0.2", @@ -68,7 +69,7 @@ "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^14.5.2", "@types/jest": "^27.0.1", - "@types/node": "^20.16.13", + "@types/node": "^22.12.0", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.0", "babel-jest": "^27.4.2", @@ -90,11 +91,12 @@ "fs-extra": "^10.0.0", "html-webpack-plugin": "^5.5.0", "identity-obj-proxy": "^3.0.0", - "jest": "^27.4.3", - "jest-resolve": "^27.4.2", - "jest-watch-typeahead": "^1.0.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-watch-typeahead": "^2.2.2", "mini-css-extract-plugin": "^2.4.5", - "msw": "^0.36.8", + "msw": "^2.7.0", "postcss": "^8.4.4", "postcss-flexbugs-fixes": "^5.0.2", "postcss-loader": "^6.2.1", @@ -126,6 +128,9 @@ "webpack-manifest-plugin": "^4.0.2", "workbox-webpack-plugin": "^6.4.1" }, + "resolutions": { + "nwsapi": "2.2.13" + }, "description": "Web UI for the [Feast Feature Store](https://feast.dev/)", "repository": { "type": "git", diff --git a/ui/public/registry.db b/ui/public/registry.db index 617771999c7..ae9a05a4a97 100644 Binary files a/ui/public/registry.db and b/ui/public/registry.db differ diff --git a/ui/src/FeastUISansProviders.test.tsx b/ui/src/FeastUISansProviders.test.tsx index 4af9490e10b..94bd2dfbe35 100644 --- a/ui/src/FeastUISansProviders.test.tsx +++ b/ui/src/FeastUISansProviders.test.tsx @@ -55,7 +55,7 @@ test("full app rendering", async () => { expect(screen.getByText(/Explore this Project/i)).toBeInTheDocument(); const projectNameRegExp = new RegExp( - parsedRegistry.projectMetadata[0].project!, + parsedRegistry.projects[0].spec?.name!, "i" ); @@ -89,7 +89,7 @@ test("routes are reachable", async () => { const routeRegExp = new RegExp(routeName, "i"); - await user.click(screen.getByRole("button", { name: routeRegExp })); + await user.click(screen.getByRole("link", { name: routeRegExp })); // Should land on a page with the heading screen.getByRole("heading", { @@ -112,7 +112,7 @@ test("features are reachable", async () => { await screen.findByText(/Explore this Project/i); const routeRegExp = new RegExp("Feature Views", "i"); - await user.click(screen.getByRole("button", { name: routeRegExp })); + await user.click(screen.getByRole("link", { name: routeRegExp })); screen.getByRole("heading", { name: "Feature Views", diff --git a/ui/src/mocks/handlers.ts b/ui/src/mocks/handlers.ts index 39f30b62a6d..dd25b6cd3fc 100644 --- a/ui/src/mocks/handlers.ts +++ b/ui/src/mocks/handlers.ts @@ -1,35 +1,25 @@ -import { rest } from "msw"; -import {readFileSync} from 'fs'; +import { http, HttpResponse } from "msw"; +import { readFileSync } from 'fs'; import path from "path"; const registry = readFileSync(path.resolve(__dirname, "../../public/registry.db")); -const projectsListWithDefaultProject = rest.get( - "/projects-list.json", - (req, res, ctx) => { - return res( - ctx.status(200), - ctx.json({ - default: "credit_score_project", - projects: [ - { - name: "Credit Score Project", - description: - "Project for credit scoring team and associated models.", - id: "credit_score_project", - registryPath: "/registry.pb", - }, - ], - }) - ); - } +const projectsListWithDefaultProject = http.get("/projects-list.json", () => + HttpResponse.json({ + default: "credit_score_project", + projects: [ + { + name: "Credit Score Project", + description: "Project for credit scoring team and associated models.", + id: "credit_score_project", + registryPath: "/registry.pb", + }, + ], + }) ); -const creditHistoryRegistry = rest.get("/registry.pb", (req, res, ctx) => { - return res( - ctx.status(200), - ctx.set('Content-Type', 'application/octet-stream'), - ctx.body(registry)); -}); +const creditHistoryRegistry = http.get("/registry.pb", () => + HttpResponse.arrayBuffer(registry.buffer) +); export { projectsListWithDefaultProject, creditHistoryRegistry }; diff --git a/ui/src/pages/Sidebar.tsx b/ui/src/pages/Sidebar.tsx index dac02709ba6..de98b213242 100644 --- a/ui/src/pages/Sidebar.tsx +++ b/ui/src/pages/Sidebar.tsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from "react"; import { EuiIcon, EuiSideNav, htmlIdGenerator } from "@elastic/eui"; -import { useNavigate, useParams } from "react-router-dom"; +import { Link, useParams } from "react-router-dom"; import { useMatchSubpath } from "../hooks/useMatchSubpath"; import useLoadRegistry from "../queries/useLoadRegistry"; import RegistryPathContext from "../contexts/RegistryPathContext"; @@ -19,8 +19,6 @@ const SideNav = () => { const [isSideNavOpenOnMobile, setisSideNavOpenOnMobile] = useState(false); - const navigate = useNavigate(); - const toggleOpenOnMobile = () => { setisSideNavOpenOnMobile(!isSideNavOpenOnMobile); }; @@ -55,58 +53,48 @@ const SideNav = () => { : "" }`; - const sideNav = [ + const baseUrl = `${process.env.PUBLIC_URL || ""}/p/${projectName}`; + + const sideNav: React.ComponentProps['items'] = [ { name: "Home", id: htmlIdGenerator("basicExample")(), - onClick: () => { - navigate(`${process.env.PUBLIC_URL || ""}/p/${projectName}/`); - }, + renderItem: props => , items: [ { name: dataSourcesLabel, id: htmlIdGenerator("dataSources")(), icon: , - onClick: () => { - navigate(`${process.env.PUBLIC_URL || ""}/p/${projectName}/data-source`); - }, - isSelected: useMatchSubpath("data-source"), + renderItem: props => , + isSelected: useMatchSubpath(`${baseUrl}/data-source`), }, { name: entitiesLabel, id: htmlIdGenerator("entities")(), icon: , - onClick: () => { - navigate(`${process.env.PUBLIC_URL || ""}/p/${projectName}/entity`); - }, - isSelected: useMatchSubpath("entity"), + renderItem: props => , + isSelected: useMatchSubpath(`${baseUrl}/entity`), }, { name: featureViewsLabel, id: htmlIdGenerator("featureView")(), icon: , - onClick: () => { - navigate(`${process.env.PUBLIC_URL || ""}/p/${projectName}/feature-view`); - }, - isSelected: useMatchSubpath("feature-view"), + renderItem: props => , + isSelected: useMatchSubpath(`${baseUrl}/feature-view`), }, { name: featureServicesLabel, id: htmlIdGenerator("featureService")(), icon: , - onClick: () => { - navigate(`${process.env.PUBLIC_URL || ""}/p/${projectName}/feature-service`); - }, - isSelected: useMatchSubpath("feature-service"), + renderItem: props => , + isSelected: useMatchSubpath(`${baseUrl}/feature-service`), }, { name: savedDatasetsLabel, id: htmlIdGenerator("savedDatasets")(), icon: , - onClick: () => { - navigate(`${process.env.PUBLIC_URL || ""}/p/${projectName}/data-set`); - }, - isSelected: useMatchSubpath("data-set"), + renderItem: props => , + isSelected: useMatchSubpath(`${baseUrl}/data-set`), }, ], }, diff --git a/ui/src/queries/useLoadRegistry.ts b/ui/src/queries/useLoadRegistry.ts index be8ab65a8cd..88274b47131 100644 --- a/ui/src/queries/useLoadRegistry.ts +++ b/ui/src/queries/useLoadRegistry.ts @@ -52,7 +52,7 @@ const useLoadRegistry = (url: string) => { // }); return { - project: objects.projectMetadata[0].project!, + project: objects.projects[0].spec?.name!, objects, mergedFVMap, mergedFVList, diff --git a/ui/src/setupTests.ts b/ui/src/setupTests.ts index 8f2609b7b3e..f30351b9164 100644 --- a/ui/src/setupTests.ts +++ b/ui/src/setupTests.ts @@ -3,3 +3,7 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; +import { BroadcastChannel } from 'worker_threads'; + +// BroadcastChannel is missing from @happy-dom/jest-environment globals +Object.assign(global, { BroadcastChannel }); diff --git a/ui/yarn.lock b/ui/yarn.lock index 773260ad861..32dcfe3996b 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -40,6 +40,15 @@ "@babel/highlight" "^7.25.9" picocolors "^1.0.0" +"@babel/code-frame@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.7", "@babel/compat-data@^7.25.8": version "7.25.8" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402" @@ -50,7 +59,12 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.9.tgz#24b01c5db6a3ebf85661b4fb4a946a9bccc72ac8" integrity sha512-yD+hEuJ/+wAJ4Ox2/rpNv5HIuPG82x3ZlQvYVn8iYCprdxzE7P1udpGF1jyjQVBU4dgznN+k2h103vxZ7NdPyw== -"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0": +"@babel/compat-data@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" + integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== + +"@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.16.0": version "7.25.8" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.8.tgz#a57137d2a51bbcffcfaeba43cb4dd33ae3e0e1c6" integrity sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg== @@ -71,6 +85,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.11.6", "@babel/core@^7.23.9": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.7.tgz#0439347a183b97534d52811144d763a17f9d2b24" + integrity sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.7" + "@babel/parser" "^7.26.7" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.26.7" + "@babel/types" "^7.26.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.21.3", "@babel/core@^7.25.8": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.9.tgz#855a4cddcec4158f3f7afadacdab2a7de8af7434" @@ -121,6 +156,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458" + integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw== + dependencies: + "@babel/parser" "^7.26.5" + "@babel/types" "^7.26.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz#63f02dbfa1f7cb75a9bdb832f300582f30bb8972" @@ -173,6 +219,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz#5d65074c76cae75607421c00d6bd517fe1892d6b" @@ -280,6 +337,15 @@ "@babel/helper-validator-identifier" "^7.25.9" "@babel/traverse" "^7.25.9" +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-optimise-call-expression@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz#1de1b99688e987af723eed44fa7fc0ee7b97d77a" @@ -436,6 +502,14 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.25.9" +"@babel/helpers@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4" + integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.7" + "@babel/highlight@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" @@ -463,6 +537,13 @@ dependencies: "@babel/types" "^7.25.8" +"@babel/parser@^7.23.9", "@babel/parser@^7.26.5", "@babel/parser@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.7.tgz#e114cd099e5f7d17b05368678da0fb9f69b3385c" + integrity sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w== + dependencies: + "@babel/types" "^7.26.7" + "@babel/parser@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.9.tgz#8fcaa079ac7458facfddc5cd705cc8005e4d3817" @@ -704,7 +785,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.7" -"@babel/plugin-syntax-jsx@^7.25.9": +"@babel/plugin-syntax-jsx@^7.25.9", "@babel/plugin-syntax-jsx@^7.7.2": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== @@ -1915,7 +1996,7 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse@^7.25.7", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== @@ -1941,6 +2022,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb" + integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/parser" "^7.26.7" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.7" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.7", "@babel/types@^7.25.8", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.25.8" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" @@ -1958,6 +2052,14 @@ "@babel/helper-string-parser" "^7.25.9" "@babel/helper-validator-identifier" "^7.25.9" +"@babel/types@^7.26.5", "@babel/types@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.7.tgz#5e2b89c0768e874d4d061961f3a5a153d71dc17a" + integrity sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@base2/pretty-print-object@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" @@ -1968,6 +2070,28 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@bundled-es-modules/cookie@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz#b41376af6a06b3e32a15241d927b840a9b4de507" + integrity sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw== + dependencies: + cookie "^0.7.2" + +"@bundled-es-modules/statuses@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz#761d10f44e51a94902c4da48675b71a76cc98872" + integrity sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg== + dependencies: + statuses "^2.0.1" + +"@bundled-es-modules/tough-cookie@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz#fa9cd3cedfeecd6783e8b0d378b4a99e52bde5d3" + integrity sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw== + dependencies: + "@types/tough-cookie" "^4.0.5" + tough-cookie "^4.1.4" + "@csstools/normalize.css@*": version "12.1.1" resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.1.1.tgz#f0ad221b7280f3fc814689786fd9ee092776ef8f" @@ -2273,6 +2397,18 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== +"@happy-dom/jest-environment@^16.7.3": + version "16.7.3" + resolved "https://registry.yarnpkg.com/@happy-dom/jest-environment/-/jest-environment-16.7.3.tgz#e32b5622dc838ea4c178a7f999fd46439d607862" + integrity sha512-82cNPOd+jJVVE3dSdlJLy7LXm41wc5rP4sMotMD96jLfL5KKA5s1fSPh0xo2QRHilk6du5seqm1xDXZwyuH++A== + dependencies: + "@jest/environment" "^29.4.0" + "@jest/fake-timers" "^29.4.0" + "@jest/types" "^29.4.0" + happy-dom "^16.7.3" + jest-mock "^29.4.0" + jest-util "^29.4.0" + "@hello-pangea/dnd@^16.6.0": version "16.6.0" resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.6.0.tgz#7509639c7bd13f55e537b65a9dcfcd54e7c99ac7" @@ -2305,6 +2441,38 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@inquirer/confirm@^5.0.0": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.4.tgz#3e2c9bfdf80331676196d8dbb2261103a67d0e9d" + integrity sha512-EsiT7K4beM5fN5Mz6j866EFA9+v9d5o9VUra3hrg8zY4GHmCS8b616FErbdo5eyKoVotBQkHzMIeeKYsKDStDw== + dependencies: + "@inquirer/core" "^10.1.5" + "@inquirer/type" "^3.0.3" + +"@inquirer/core@^10.1.5": + version "10.1.5" + resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.5.tgz#7271c177340f77c2e231704227704d8cdf497747" + integrity sha512-/vyCWhET0ktav/mUeBqJRYTwmjFPIKPRYb3COAw7qORULgipGSUO2vL32lQKki3UxDKJ8BvuEbokaoyCA6YlWw== + dependencies: + "@inquirer/figures" "^1.0.10" + "@inquirer/type" "^3.0.3" + ansi-escapes "^4.3.2" + cli-width "^4.1.0" + mute-stream "^2.0.0" + signal-exit "^4.1.0" + wrap-ansi "^6.2.0" + yoctocolors-cjs "^2.1.2" + +"@inquirer/figures@^1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.10.tgz#e3676a51c9c51aaabcd6ba18a28e82b98417db37" + integrity sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw== + +"@inquirer/type@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.3.tgz#aa9cb38568f23f772b417c972f6a2d906647a6af" + integrity sha512-I4VIHFxUuY1bshGbXZTxCmhwaaEst9s/lll3ekok+o1Z26/ZUKdx8y1b7lsoG6rtsBDwEGfiBJ2SfirjoISLpg== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2316,176 +2484,169 @@ js-yaml "^3.13.1" resolve-from "^5.0.0" -"@istanbuljs/schema@^0.1.2": +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" - integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== - dependencies: - "@jest/types" "^27.5.1" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^27.5.1" - jest-util "^27.5.1" - slash "^3.0.0" - -"@jest/console@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" - integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== +"@jest/console@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.7.0.tgz#cd4822dbdb84529265c5a2bdb529a3c9cc950ffc" + integrity sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg== dependencies: - "@jest/types" "^28.1.3" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^28.1.3" - jest-util "^28.1.3" + jest-message-util "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" -"@jest/core@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" - integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== +"@jest/core@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.7.0.tgz#b6cccc239f30ff36609658c5a5e2291757ce448f" + integrity sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg== dependencies: - "@jest/console" "^27.5.1" - "@jest/reporters" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.7.0" + "@jest/reporters" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - emittery "^0.8.1" + ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^27.5.1" - jest-config "^27.5.1" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-resolve-dependencies "^27.5.1" - jest-runner "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - jest-watcher "^27.5.1" + jest-changed-files "^29.7.0" + jest-config "^29.7.0" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-resolve-dependencies "^29.7.0" + jest-runner "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + jest-watcher "^29.7.0" micromatch "^4.0.4" - rimraf "^3.0.0" + pretty-format "^29.7.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" - integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== +"@jest/environment@^29.4.0", "@jest/environment@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" + integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== dependencies: - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^27.5.1" + jest-mock "^29.7.0" -"@jest/fake-timers@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" - integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== +"@jest/expect-utils@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: - "@jest/types" "^27.5.1" - "@sinonjs/fake-timers" "^8.0.1" + jest-get-type "^29.6.3" + +"@jest/expect@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.7.0.tgz#76a3edb0cb753b70dfbfe23283510d3d45432bf2" + integrity sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ== + dependencies: + expect "^29.7.0" + jest-snapshot "^29.7.0" + +"@jest/fake-timers@^29.4.0", "@jest/fake-timers@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" + integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== + dependencies: + "@jest/types" "^29.6.3" + "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-util "^29.7.0" -"@jest/globals@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" - integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== +"@jest/globals@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d" + integrity sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ== dependencies: - "@jest/environment" "^27.5.1" - "@jest/types" "^27.5.1" - expect "^27.5.1" + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/types" "^29.6.3" + jest-mock "^29.7.0" -"@jest/reporters@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" - integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== +"@jest/reporters@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.7.0.tgz#04b262ecb3b8faa83b0b3d321623972393e8f4c7" + integrity sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" - glob "^7.1.2" + glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-haste-map "^27.5.1" - jest-resolve "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" + jest-message-util "^29.7.0" + jest-util "^29.7.0" + jest-worker "^29.7.0" slash "^3.0.0" - source-map "^0.6.0" string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^8.1.0" + strip-ansi "^6.0.0" + v8-to-istanbul "^9.0.1" -"@jest/schemas@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" - integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: - "@sinclair/typebox" "^0.24.1" + "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" - integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" - source-map "^0.6.0" - -"@jest/test-result@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" - integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== - dependencies: - "@jest/console" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" -"@jest/test-result@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" - integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== +"@jest/test-result@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.7.0.tgz#8db9a80aa1a097bb2262572686734baed9b1657c" + integrity sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA== dependencies: - "@jest/console" "^28.1.3" - "@jest/types" "^28.1.3" + "@jest/console" "^29.7.0" + "@jest/types" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^27.5.1": - version "27.5.1" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" - integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== +"@jest/test-sequencer@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz#6cef977ce1d39834a3aea887a1726628a6f072ce" + integrity sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw== dependencies: - "@jest/test-result" "^27.5.1" + "@jest/test-result" "^29.7.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-runtime "^27.5.1" + jest-haste-map "^29.7.0" + slash "^3.0.0" "@jest/transform@^27.5.1": version "27.5.1" @@ -2508,6 +2669,27 @@ source-map "^0.6.1" write-file-atomic "^3.0.0" +"@jest/transform@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" + integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^2.0.0" + fast-json-stable-stringify "^2.1.0" + graceful-fs "^4.2.9" + jest-haste-map "^29.7.0" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.2" + "@jest/types@^27.5.1": version "27.5.1" resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" @@ -2519,12 +2701,12 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@jest/types@^28.1.3": - version "28.1.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" - integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== +"@jest/types@^29.4.0", "@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^28.1.3" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -2563,7 +2745,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -2590,25 +2772,17 @@ dependencies: unist-util-visit "^1.4.1" -"@mswjs/cookies@^0.1.7": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.1.7.tgz#d334081b2c51057a61c1dd7b76ca3cac02251651" - integrity sha512-bDg1ReMBx+PYDB4Pk7y1Q07Zz1iKIEUWQpkEXiA2lEWg9gvOZ8UBmGXilCEUvyYoRFlmr/9iXTRR69TrgSwX/Q== +"@mswjs/interceptors@^0.37.0": + version "0.37.5" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.37.5.tgz#9ce40c56be02b43fcbdb51b63f47e69fc4aaabe6" + integrity sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA== dependencies: - "@types/set-cookie-parser" "^2.4.0" - set-cookie-parser "^2.4.6" - -"@mswjs/interceptors@^0.12.7": - version "0.12.7" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" - integrity sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg== - dependencies: - "@open-draft/until" "^1.0.3" - "@xmldom/xmldom" "^0.7.2" - debug "^4.3.2" - headers-utils "^3.0.2" - outvariant "^1.2.0" - strict-event-emitter "^0.2.0" + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/logger" "^0.3.0" + "@open-draft/until" "^2.0.0" + is-node-process "^1.2.0" + outvariant "^1.4.3" + strict-event-emitter "^0.5.1" "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" @@ -2638,10 +2812,23 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@open-draft/until@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" - integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== +"@open-draft/deferred-promise@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" + integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== + +"@open-draft/logger@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" + integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== + dependencies: + is-node-process "^1.2.0" + outvariant "^1.4.0" + +"@open-draft/until@^2.0.0", "@open-draft/until@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" + integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== "@pmmmwh/react-refresh-webpack-plugin@^0.5.3": version "0.5.15" @@ -2809,24 +2996,24 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz#427d5549943a9c6fce808e39ea64dbe60d4047f1" integrity sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA== -"@sinclair/typebox@^0.24.1": - version "0.24.51" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.51.tgz#645f33fe4e02defe26f2f5c0410e1c094eac7f5f" - integrity sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sinonjs/commons@^1.7.0": - version "1.8.6" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" - integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== +"@sinonjs/commons@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" + integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ== dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@^8.0.1": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" - integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== +"@sinonjs/fake-timers@^10.0.2": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" + integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: - "@sinonjs/commons" "^1.7.0" + "@sinonjs/commons" "^3.0.0" "@surma/rollup-plugin-off-main-thread@^2.2.3": version "2.2.3" @@ -2983,10 +3170,10 @@ resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@trysound/sax@0.2.0": version "0.2.0" @@ -3024,7 +3211,7 @@ "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": version "7.20.6" resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7" integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg== @@ -3061,10 +3248,10 @@ dependencies: "@types/node" "*" -"@types/cookie@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" - integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== "@types/eslint@^7.29.0 || ^8.4.1": version "8.56.12" @@ -3139,7 +3326,7 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/graceful-fs@^4.1.2": +"@types/graceful-fs@^4.1.2", "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== @@ -3178,14 +3365,6 @@ dependencies: "@types/node" "*" -"@types/inquirer@^8.1.3": - version "8.2.10" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.10.tgz#9444dce2d764c35bc5bb4d742598aaa4acb6561b" - integrity sha512-IdD5NmHyVjWM8SHWo/kPBgtzXatwPkfwzyP3fN1jF2g9BWt5WO+8hL2F4o2GKIYsU40PpqeevuUWvkS/roXJkA== - dependencies: - "@types/through" "*" - rxjs "^7.2.0" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" @@ -3213,10 +3392,14 @@ jest-diff "^27.0.0" pretty-format "^27.0.0" -"@types/js-levenshtein@^1.1.0": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.3.tgz#a6fd0bdc8255b274e5438e0bfb25f154492d1106" - integrity sha512-jd+Q+sD20Qfu9e2aEXogiO3vpOC1PYJOUdyN9gvs4Qrvkg4wF43L5OhqrPeokdv8TL0/mXoYfpkcoGZMNN2pkQ== +"@types/jsdom@^20.0.0": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-20.0.1.tgz#07c14bc19bd2f918c1929541cdaacae894744808" + integrity sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" @@ -3287,12 +3470,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154" integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg== -"@types/node@^20.16.13": - version "20.16.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.13.tgz#148c152d757dc73f8d65f0f6f078f39050b85b0c" - integrity sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg== +"@types/node@^22.12.0": + version "22.12.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.12.0.tgz#bf8af3b2af0837b5a62a368756ff2b705ae0048c" + integrity sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA== dependencies: - undici-types "~6.19.2" + undici-types "~6.20.0" "@types/numeral@^2.0.5": version "2.0.5" @@ -3309,11 +3492,6 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== -"@types/prettier@^2.1.5": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" - integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== - "@types/prismjs@*": version "1.26.0" resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654" @@ -3418,13 +3596,6 @@ "@types/node" "*" "@types/send" "*" -"@types/set-cookie-parser@^2.4.0": - version "2.4.10" - resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz#ad3a807d6d921db9720621ea3374c5d92020bcbc" - integrity sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw== - dependencies: - "@types/node" "*" - "@types/sockjs@^0.3.33": version "0.3.36" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" @@ -3437,17 +3608,20 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/statuses@^2.0.4": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.5.tgz#f61ab46d5352fd73c863a1ea4e1cef3b0b51ae63" + integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A== + "@types/stylis@4.2.5": version "4.2.5" resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.5.tgz#1daa6456f40959d06157698a653a9ab0a70281df" integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw== -"@types/through@*": - version "0.0.33" - resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.33.tgz#14ebf599320e1c7851e7d598149af183c6b9ea56" - integrity sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ== - dependencies: - "@types/node" "*" +"@types/tough-cookie@*", "@types/tough-cookie@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== "@types/trusted-types@^2.0.2": version "2.0.7" @@ -3707,11 +3881,6 @@ "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" -"@xmldom/xmldom@^0.7.2": - version "0.7.13" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" - integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -3722,7 +3891,7 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== -abab@^2.0.3, abab@^2.0.5: +abab@^2.0.5, abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== @@ -3735,13 +3904,13 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" + acorn "^8.1.0" + acorn-walk "^8.0.2" acorn-import-attributes@^1.9.5: version "1.9.5" @@ -3753,17 +3922,19 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.0.2: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1: + version "8.14.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== -acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: version "8.13.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3" integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w== @@ -3827,13 +3998,18 @@ ajv@^8.0.0, ajv@^8.6.0, ajv@^8.9.0: json-schema-traverse "^1.0.0" require-from-string "^2.0.2" -ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" +ansi-escapes@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f" + integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig== + ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" @@ -4077,7 +4253,7 @@ axobject-query@^4.1.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== -babel-jest@^27.4.2, babel-jest@^27.5.1: +babel-jest@^27.4.2: version "27.5.1" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== @@ -4091,6 +4267,19 @@ babel-jest@^27.4.2, babel-jest@^27.5.1: graceful-fs "^4.2.9" slash "^3.0.0" +babel-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" + integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg== + dependencies: + "@jest/transform" "^29.7.0" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^29.6.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + babel-loader@^8.2.3: version "8.4.1" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.4.1.tgz#6ccb75c66e62c3b144e1c5f2eaec5b8f6c08c675" @@ -4122,6 +4311,16 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" @@ -4194,6 +4393,14 @@ babel-preset-jest@^27.5.1: babel-plugin-jest-hoist "^27.5.1" babel-preset-current-node-syntax "^1.0.0" +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== + dependencies: + babel-plugin-jest-hoist "^29.6.3" + babel-preset-current-node-syntax "^1.0.0" + babel-preset-react-app@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz#ed6005a20a24f2c88521809fa9aea99903751584" @@ -4226,11 +4433,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -4262,15 +4464,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" @@ -4343,11 +4536,6 @@ broadcast-channel@^3.4.1: rimraf "3.0.2" unload "2.2.0" -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.23.3, browserslist@^4.24.0: version "4.24.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" @@ -4370,14 +4558,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - builtin-modules@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -4464,14 +4644,6 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== -chalk@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4489,7 +4661,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4497,6 +4669,11 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -4527,11 +4704,6 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - check-types@^11.2.3: version "11.2.3" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.3.tgz#1ffdf68faae4e941fce252840b1787b8edc93b71" @@ -4584,31 +4756,10 @@ clean-css@^5.2.2: dependencies: source-map "~0.6.0" -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.5.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" +cli-width@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" + integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== cliui@^8.0.1: version "8.0.1" @@ -4619,11 +4770,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4762,7 +4908,7 @@ content-type@~1.0.4, content-type@~1.0.5: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -4782,10 +4928,10 @@ cookie@0.7.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== -cookie@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== core-js-compat@^3.38.0, core-js-compat@^3.38.1: version "3.38.1" @@ -4841,6 +4987,19 @@ cosmiconfig@^8.1.3: parse-json "^5.2.0" path-type "^4.0.0" +create-jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" + integrity sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q== + dependencies: + "@jest/types" "^29.6.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-config "^29.7.0" + jest-util "^29.7.0" + prompts "^2.0.1" + cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5055,10 +5214,10 @@ csso@^5.0.5: dependencies: css-tree "~2.2.0" -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== cssom@~0.3.6: version "0.3.8" @@ -5087,14 +5246,14 @@ damerau-levenshtein@^1.0.8: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== +data-urls@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" data-view-buffer@^1.0.1: version "1.0.1" @@ -5130,10 +5289,10 @@ debug@2.6.9, debug@^2.6.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== +debug@4: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: ms "^2.1.3" @@ -5144,20 +5303,27 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -decimal.js@^10.2.1: - version "10.4.3" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" - integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + +decimal.js@^10.4.2: + version "10.5.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22" + integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +dedent@^1.0.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" + integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== deep-equal@^2.0.5: version "2.2.3" @@ -5200,13 +5366,6 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -defaults@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" - integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== - dependencies: - clone "^1.0.2" - define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -5278,11 +5437,16 @@ detect-port-alt@^1.1.6: address "^1.0.1" debug "^2.6.0" -diff-sequences@^27.4.0, diff-sequences@^27.5.1: +diff-sequences@^27.4.0: version "27.5.1" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5351,12 +5515,12 @@ domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== dependencies: - webidl-conversions "^5.0.0" + webidl-conversions "^7.0.0" domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" @@ -5430,15 +5594,10 @@ electron-to-chromium@^1.5.28: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz#eae1ba6c49a1a61d84cf8263351d3513b2bcc534" integrity sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ== -emittery@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" - integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== - -emittery@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" - integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== +emittery@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" + integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" @@ -5483,7 +5642,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.2.0, entities@^4.4.0: +entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -5974,7 +6133,7 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0, events@^3.3.0: +events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -5999,15 +6158,16 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" - integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== +expect@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== dependencies: - "@jest/types" "^27.5.1" - jest-get-type "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" + "@jest/expect-utils" "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" express@^4.17.3: version "4.21.1" @@ -6051,15 +6211,6 @@ extend@^3.0.0: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -6119,13 +6270,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -6274,10 +6418,10 @@ fork-ts-checker-webpack-plugin@^6.5.0: semver "^7.3.2" tapable "^1.0.0" -form-data@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.2.tgz#83ad9ced7c03feaad97e293d6f6091011e1659c8" - integrity sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ== +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" @@ -6435,7 +6579,7 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -6537,10 +6681,10 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== -graphql@^15.5.1: - version "15.9.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.9.0.tgz#4e8ca830cfd30b03d44d3edd9cac2b0690304b53" - integrity sha512-GCOQdvm7XxV1S4U4CGrsdlEN37245eC8P9zaYCMr6K1BG0IPGy5lUwmJsEOGyl1GD6HXjOtl2keCP9asRBwNvA== +graphql@^16.8.1: + version "16.10.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c" + integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ== gzip-size@^6.0.0: version "6.0.0" @@ -6554,6 +6698,14 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== +happy-dom@^16.7.3: + version "16.7.3" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-16.7.3.tgz#8f3033265c4e0d31bc7e68f8678676bb92f0e7f7" + integrity sha512-76uiE9jCpC849cOyYZ8YBROpPcstW/hwCKoQYd3aiZaxHeR9zdjpup4z7qYEWbt+lY8Rb3efW2gmrckyoBftKg== + dependencies: + webidl-conversions "^7.0.0" + whatwg-mimetype "^3.0.0" + harmony-reflect@^1.4.6: version "1.6.2" resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" @@ -6705,10 +6857,10 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-utils@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/headers-utils/-/headers-utils-3.0.2.tgz#dfc65feae4b0e34357308aefbcafa99c895e59ef" - integrity sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ== +headers-polyfill@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-4.0.3.tgz#922a0155de30ecc1f785bcf04be77844ca95ad07" + integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== highlight.js@^10.4.1, highlight.js@~10.7.0: version "10.7.3" @@ -6742,12 +6894,12 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== dependencies: - whatwg-encoding "^1.0.5" + whatwg-encoding "^2.0.0" html-entities@^2.1.0, html-entities@^2.3.2: version "2.5.2" @@ -6829,12 +6981,12 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== dependencies: - "@tootallnate/once" "1" + "@tootallnate/once" "2" agent-base "6" debug "4" @@ -6858,7 +7010,7 @@ http-proxy@^1.18.1: follow-redirects "^1.0.0" requires-port "^1.0.0" -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -6871,14 +7023,14 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -6902,11 +7054,6 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - ignore@^5.1.1, ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" @@ -6951,7 +7098,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -6971,27 +7118,6 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== -inquirer@^8.2.0: - version "8.2.6" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" - integrity sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.5.5" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - wrap-ansi "^6.0.1" - inter-ui@^3.19.3: version "3.19.3" resolved "https://registry.yarnpkg.com/inter-ui/-/inter-ui-3.19.3.tgz#cf4b4b6d30de8d5463e2462588654b325206488c" @@ -7168,11 +7294,6 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - is-map@^2.0.2, is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" @@ -7188,7 +7309,7 @@ is-negative-zero@^2.0.3: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== -is-node-process@^1.0.1: +is-node-process@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== @@ -7308,11 +7429,6 @@ is-typedarray@^1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" @@ -7370,7 +7486,7 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: +istanbul-lib-instrument@^5.0.4: version "5.2.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== @@ -7381,6 +7497,17 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-report@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" @@ -7428,85 +7555,83 @@ jake@^10.8.5: filelist "^1.0.4" minimatch "^3.1.2" -jest-changed-files@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" - integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== +jest-changed-files@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" + integrity sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w== dependencies: - "@jest/types" "^27.5.1" execa "^5.0.0" - throat "^6.0.1" - -jest-circus@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" - integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + jest-util "^29.7.0" + p-limit "^3.1.0" + +jest-circus@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.7.0.tgz#b6817a45fcc835d8b16d5962d0c026473ee3668a" + integrity sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/expect" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" - expect "^27.5.1" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" + jest-each "^29.7.0" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-runtime "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" + p-limit "^3.1.0" + pretty-format "^29.7.0" + pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" - throat "^6.0.1" -jest-cli@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" - integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== +jest-cli@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.7.0.tgz#5592c940798e0cae677eec169264f2d839a37995" + integrity sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg== dependencies: - "@jest/core" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/core" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" + create-jest "^29.7.0" exit "^0.1.2" - graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" - prompts "^2.0.1" - yargs "^16.2.0" - -jest-config@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" - integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== - dependencies: - "@babel/core" "^7.8.0" - "@jest/test-sequencer" "^27.5.1" - "@jest/types" "^27.5.1" - babel-jest "^27.5.1" + jest-config "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" + yargs "^17.3.1" + +jest-config@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.7.0.tgz#bcbda8806dbcc01b1e316a46bb74085a84b0245f" + integrity sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^29.7.0" + "@jest/types" "^29.6.3" + babel-jest "^29.7.0" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" - glob "^7.1.1" + glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-get-type "^27.5.1" - jest-jasmine2 "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runner "^27.5.1" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-circus "^29.7.0" + jest-environment-node "^29.7.0" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-runner "^29.7.0" + jest-util "^29.7.0" + jest-validate "^29.7.0" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^27.5.1" + pretty-format "^29.7.0" slash "^3.0.0" strip-json-comments "^3.1.1" @@ -7520,64 +7645,70 @@ jest-diff@^27.0.0: jest-get-type "^27.4.0" pretty-format "^27.4.6" -jest-diff@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" - integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== +jest-diff@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: chalk "^4.0.0" - diff-sequences "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-docblock@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" - integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== dependencies: detect-newline "^3.0.0" -jest-each@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" - integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== +jest-each@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.7.0.tgz#162a9b3f2328bdd991beaabffbb74745e56577d1" + integrity sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" chalk "^4.0.0" - jest-get-type "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - -jest-environment-jsdom@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" - integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + jest-get-type "^29.6.3" + jest-util "^29.7.0" + pretty-format "^29.7.0" + +jest-environment-jsdom@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" + integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" - jsdom "^16.6.0" - -jest-environment-node@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" - integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/types" "^27.5.1" + jest-mock "^29.7.0" + jest-util "^29.7.0" + jsdom "^20.0.0" + +jest-environment-node@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz#0b93e111dda8ec120bc8300e6d1fb9576e164376" + integrity sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^27.5.1" - jest-util "^27.5.1" + jest-mock "^29.7.0" + jest-util "^29.7.0" -jest-get-type@^27.4.0, jest-get-type@^27.5.1: +jest-get-type@^27.4.0: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -7598,84 +7729,66 @@ jest-haste-map@^27.5.1: optionalDependencies: fsevents "^2.3.2" -jest-jasmine2@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" - integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== +jest-haste-map@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" + integrity sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA== dependencies: - "@jest/environment" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" + "@types/graceful-fs" "^4.1.3" "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^27.5.1" - is-generator-fn "^2.0.0" - jest-each "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-runtime "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" - pretty-format "^27.5.1" - throat "^6.0.1" - -jest-leak-detector@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" - integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== - dependencies: - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^29.6.3" + jest-util "^29.7.0" + jest-worker "^29.7.0" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" -jest-matcher-utils@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" - integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== +jest-leak-detector@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz#5b7ec0dadfdfec0ca383dc9aa016d36b5ea4c728" + integrity sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw== dependencies: - chalk "^4.0.0" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - pretty-format "^27.5.1" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-message-util@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" - integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== +jest-matcher-utils@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== dependencies: - "@babel/code-frame" "^7.12.13" - "@jest/types" "^27.5.1" - "@types/stack-utils" "^2.0.0" chalk "^4.0.0" - graceful-fs "^4.2.9" - micromatch "^4.0.4" - pretty-format "^27.5.1" - slash "^3.0.0" - stack-utils "^2.0.3" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + pretty-format "^29.7.0" -jest-message-util@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" - integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== +jest-message-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^28.1.3" + "@jest/types" "^29.6.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^28.1.3" + pretty-format "^29.7.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" - integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== +jest-mock@^29.4.0, jest-mock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" + integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" "@types/node" "*" + jest-util "^29.7.0" jest-pnp-resolver@^1.2.2: version "1.2.3" @@ -7687,88 +7800,86 @@ jest-regex-util@^27.5.1: resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-regex-util@^28.0.0: - version "28.0.2" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" - integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== +jest-regex-util@^29.0.0, jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" - integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== +jest-resolve-dependencies@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz#1b04f2c095f37fc776ff40803dc92921b1e88428" + integrity sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA== dependencies: - "@jest/types" "^27.5.1" - jest-regex-util "^27.5.1" - jest-snapshot "^27.5.1" + jest-regex-util "^29.6.3" + jest-snapshot "^29.7.0" -jest-resolve@^27.4.2, jest-resolve@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" - integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== +jest-resolve@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.7.0.tgz#64d6a8992dd26f635ab0c01e5eef4399c6bcbc30" + integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== dependencies: - "@jest/types" "^27.5.1" chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" + jest-haste-map "^29.7.0" jest-pnp-resolver "^1.2.2" - jest-util "^27.5.1" - jest-validate "^27.5.1" + jest-util "^29.7.0" + jest-validate "^29.7.0" resolve "^1.20.0" - resolve.exports "^1.1.0" + resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" - integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== +jest-runner@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.7.0.tgz#809af072d408a53dcfd2e849a4c976d3132f718e" + integrity sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ== dependencies: - "@jest/console" "^27.5.1" - "@jest/environment" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/console" "^29.7.0" + "@jest/environment" "^29.7.0" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - emittery "^0.8.1" + emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^27.5.1" - jest-environment-jsdom "^27.5.1" - jest-environment-node "^27.5.1" - jest-haste-map "^27.5.1" - jest-leak-detector "^27.5.1" - jest-message-util "^27.5.1" - jest-resolve "^27.5.1" - jest-runtime "^27.5.1" - jest-util "^27.5.1" - jest-worker "^27.5.1" - source-map-support "^0.5.6" - throat "^6.0.1" - -jest-runtime@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" - integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== - dependencies: - "@jest/environment" "^27.5.1" - "@jest/fake-timers" "^27.5.1" - "@jest/globals" "^27.5.1" - "@jest/source-map" "^27.5.1" - "@jest/test-result" "^27.5.1" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" + jest-docblock "^29.7.0" + jest-environment-node "^29.7.0" + jest-haste-map "^29.7.0" + jest-leak-detector "^29.7.0" + jest-message-util "^29.7.0" + jest-resolve "^29.7.0" + jest-runtime "^29.7.0" + jest-util "^29.7.0" + jest-watcher "^29.7.0" + jest-worker "^29.7.0" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.7.0.tgz#efecb3141cf7d3767a3a0cc8f7c9990587d3d817" + integrity sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ== + dependencies: + "@jest/environment" "^29.7.0" + "@jest/fake-timers" "^29.7.0" + "@jest/globals" "^29.7.0" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" + "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" - execa "^5.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^27.5.1" - jest-message-util "^27.5.1" - jest-mock "^27.5.1" - jest-regex-util "^27.5.1" - jest-resolve "^27.5.1" - jest-snapshot "^27.5.1" - jest-util "^27.5.1" + jest-haste-map "^29.7.0" + jest-message-util "^29.7.0" + jest-mock "^29.7.0" + jest-regex-util "^29.6.3" + jest-resolve "^29.7.0" + jest-snapshot "^29.7.0" + jest-util "^29.7.0" slash "^3.0.0" strip-bom "^4.0.0" @@ -7780,33 +7891,31 @@ jest-serializer@^27.5.1: "@types/node" "*" graceful-fs "^4.2.9" -jest-snapshot@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" - integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== +jest-snapshot@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" + integrity sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw== dependencies: - "@babel/core" "^7.7.2" + "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" - "@babel/types" "^7.0.0" - "@jest/transform" "^27.5.1" - "@jest/types" "^27.5.1" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.1.5" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^29.7.0" + "@jest/transform" "^29.7.0" + "@jest/types" "^29.6.3" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^27.5.1" + expect "^29.7.0" graceful-fs "^4.2.9" - jest-diff "^27.5.1" - jest-get-type "^27.5.1" - jest-haste-map "^27.5.1" - jest-matcher-utils "^27.5.1" - jest-message-util "^27.5.1" - jest-util "^27.5.1" + jest-diff "^29.7.0" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.7.0" + jest-message-util "^29.7.0" + jest-util "^29.7.0" natural-compare "^1.4.0" - pretty-format "^27.5.1" - semver "^7.3.2" + pretty-format "^29.7.0" + semver "^7.5.3" jest-util@^27.5.1: version "27.5.1" @@ -7820,68 +7929,55 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-util@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" - integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== +jest-util@^29.4.0, jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: - "@jest/types" "^28.1.3" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" - integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.7.0.tgz#7bf705511c64da591d46b15fce41400d52147d9c" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== dependencies: - "@jest/types" "^27.5.1" + "@jest/types" "^29.6.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^27.5.1" + jest-get-type "^29.6.3" leven "^3.1.0" - pretty-format "^27.5.1" + pretty-format "^29.7.0" -jest-watch-typeahead@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz#b4a6826dfb9c9420da2f7bc900de59dad11266a9" - integrity sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw== - dependencies: - ansi-escapes "^4.3.1" - chalk "^4.0.0" - jest-regex-util "^28.0.0" - jest-watcher "^28.0.0" - slash "^4.0.0" +jest-watch-typeahead@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz#5516d3cd006485caa5cfc9bd1de40f1f8b136abf" + integrity sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ== + dependencies: + ansi-escapes "^6.0.0" + chalk "^5.2.0" + jest-regex-util "^29.0.0" + jest-watcher "^29.0.0" + slash "^5.0.0" string-length "^5.0.1" strip-ansi "^7.0.1" -jest-watcher@^27.5.1: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" - integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== +jest-watcher@^29.0.0, jest-watcher@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.7.0.tgz#7810d30d619c3a62093223ce6bb359ca1b28a2f2" + integrity sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g== dependencies: - "@jest/test-result" "^27.5.1" - "@jest/types" "^27.5.1" + "@jest/test-result" "^29.7.0" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^27.5.1" - string-length "^4.0.1" - -jest-watcher@^28.0.0: - version "28.1.3" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" - integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== - dependencies: - "@jest/test-result" "^28.1.3" - "@jest/types" "^28.1.3" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - emittery "^0.10.2" - jest-util "^28.1.3" + emittery "^0.13.1" + jest-util "^29.7.0" string-length "^4.0.1" jest-worker@^26.2.1: @@ -7911,19 +8007,25 @@ jest-worker@^28.0.2: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^27.4.3: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" - integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== dependencies: - "@jest/core" "^27.5.1" - import-local "^3.0.2" - jest-cli "^27.5.1" + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" -js-levenshtein@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" - integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== +jest@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.7.0.tgz#994676fc24177f088f1c5e3737f5697204ff2613" + integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== + dependencies: + "@jest/core" "^29.7.0" + "@jest/types" "^29.6.3" + import-local "^3.0.2" + jest-cli "^29.7.0" js-sha3@0.8.0: version "0.8.0" @@ -7978,38 +8080,37 @@ jsdoc@^4.0.0: strip-json-comments "^3.1.0" underscore "~1.13.2" -jsdom@^16.6.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== +jsdom@^20.0.0: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" + integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" + abab "^2.0.6" + acorn "^8.8.1" + acorn-globals "^7.0.0" + cssom "^0.5.0" cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" + data-urls "^3.0.2" + decimal.js "^10.4.2" + domexception "^4.0.0" escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.1" + saxes "^6.0.0" symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + ws "^8.11.0" + xml-name-validator "^4.0.0" jsesc@^3.0.2, jsesc@~3.0.2: version "3.0.2" @@ -8251,19 +8352,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - long@^5.0.0, long@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" @@ -8551,31 +8644,29 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@^0.36.8: - version "0.36.8" - resolved "https://registry.yarnpkg.com/msw/-/msw-0.36.8.tgz#33ff8bfb0299626a95f43d0e4c3dc2c73c17f1ba" - integrity sha512-K7lOQoYqhGhTSChsmHMQbf/SDCsxh/m0uhN6Ipt206lGoe81fpTmaGD0KLh4jUxCONMOUnwCSj0jtX2CM4pEdw== - dependencies: - "@mswjs/cookies" "^0.1.7" - "@mswjs/interceptors" "^0.12.7" - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.1" - "@types/inquirer" "^8.1.3" - "@types/js-levenshtein" "^1.1.0" - chalk "4.1.1" - chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.5.1" - headers-utils "^3.0.2" - inquirer "^8.2.0" - is-node-process "^1.0.1" - js-levenshtein "^1.1.6" - node-fetch "^2.6.7" - path-to-regexp "^6.2.0" - statuses "^2.0.0" - strict-event-emitter "^0.2.0" - type-fest "^1.2.2" - yargs "^17.3.0" +msw@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/msw/-/msw-2.7.0.tgz#d13ff87f7e018fc4c359800ff72ba5017033fb56" + integrity sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw== + dependencies: + "@bundled-es-modules/cookie" "^2.0.1" + "@bundled-es-modules/statuses" "^1.0.1" + "@bundled-es-modules/tough-cookie" "^0.1.6" + "@inquirer/confirm" "^5.0.0" + "@mswjs/interceptors" "^0.37.0" + "@open-draft/deferred-promise" "^2.2.0" + "@open-draft/until" "^2.1.0" + "@types/cookie" "^0.6.0" + "@types/statuses" "^2.0.4" + graphql "^16.8.1" + headers-polyfill "^4.0.2" + is-node-process "^1.2.0" + outvariant "^1.4.3" + path-to-regexp "^6.3.0" + picocolors "^1.1.1" + strict-event-emitter "^0.5.1" + type-fest "^4.26.1" + yargs "^17.7.2" multicast-dns@^7.2.5: version "7.2.5" @@ -8585,10 +8676,10 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mute-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" + integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== nano-time@1.0.0: version "1.0.0" @@ -8637,13 +8728,6 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -8693,7 +8777,7 @@ numeral@^2.0.6: resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= -nwsapi@^2.2.0: +nwsapi@2.2.13, nwsapi@^2.2.2: version "2.2.13" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== @@ -8797,7 +8881,7 @@ once@^1.3.0: dependencies: wrappy "1" -onetime@^5.1.0, onetime@^5.1.2: +onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -8837,27 +8921,7 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" -ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - -outvariant@^1.2.0: +outvariant@^1.4.0, outvariant@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873" integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== @@ -8869,7 +8933,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2: +p-limit@^3.0.2, p-limit@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -8947,11 +9011,18 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1, parse5@^6.0.0: +parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.0.0, parse5@^7.1.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -8995,7 +9066,7 @@ path-to-regexp@0.1.10: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== -path-to-regexp@^6.2.0: +path-to-regexp@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== @@ -9010,7 +9081,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -9598,7 +9669,7 @@ pretty-format@^27.0.0: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^27.0.2, pretty-format@^27.4.6, pretty-format@^27.5.1: +pretty-format@^27.0.2, pretty-format@^27.4.6: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== @@ -9607,13 +9678,12 @@ pretty-format@^27.0.2, pretty-format@^27.4.6, pretty-format@^27.5.1: ansi-styles "^5.0.0" react-is "^17.0.1" -pretty-format@^28.1.3: - version "28.1.3" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" - integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: - "@jest/schemas" "^28.1.3" - ansi-regex "^5.0.1" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" @@ -9706,20 +9776,27 @@ proxy-addr@~2.0.7: ipaddr.js "1.9.1" psl@^1.1.33: - version "1.9.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" - integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + version "1.15.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" + integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== + dependencies: + punycode "^2.3.1" punycode.js@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== +pure-rand@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" + integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" @@ -10029,7 +10106,7 @@ readable-stream@^2.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.4.0: +readable-stream@^3.0.6: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -10299,10 +10376,10 @@ resolve-url-loader@^5.0.0: postcss "^8.2.14" source-map "0.6.1" -resolve.exports@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" - integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== +resolve.exports@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" + integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.4: version "1.22.8" @@ -10331,14 +10408,6 @@ resolve@^2.0.0-next.5: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - retry@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -10349,7 +10418,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -10413,11 +10482,6 @@ rollup@^2.43.1, rollup@^2.68.0: optionalDependencies: fsevents "~2.3.2" -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -10425,13 +10489,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.2.0, rxjs@^7.5.5: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -10479,10 +10536,10 @@ sass-loader@^12.3.0: klona "^2.0.4" neo-async "^2.6.2" -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== dependencies: xmlchars "^2.2.0" @@ -10614,11 +10671,6 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" -set-cookie-parser@^2.4.6: - version "2.7.0" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz#ef5552b56dc01baae102acb5fc9fb8cd060c30f9" - integrity sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ== - set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -10683,11 +10735,16 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" -signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -10698,10 +10755,10 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slash@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" - integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== +slash@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" + integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== snake-case@^3.0.4: version "3.0.4" @@ -10739,7 +10796,15 @@ source-map-loader@^3.0.0: iconv-lite "^0.6.3" source-map-js "^1.0.1" -source-map-support@^0.5.6, source-map-support@~0.5.20: +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -10841,7 +10906,7 @@ static-eval@2.0.2: dependencies: escodegen "^1.8.1" -statuses@2.0.1, statuses@^2.0.0: +statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== @@ -10858,12 +10923,10 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" -strict-event-emitter@^0.2.0: - version "0.2.8" - resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.8.tgz#b4e768927c67273c14c13d20e19d5e6c934b47ca" - integrity sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A== - dependencies: - events "^3.3.0" +strict-event-emitter@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" + integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== strict-uri-encode@^2.0.0: version "2.0.0" @@ -11107,14 +11170,6 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" - integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -11186,14 +11241,6 @@ tempy@^0.6.0: type-fest "^0.16.0" unique-string "^2.0.0" -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.10: version "5.3.10" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" @@ -11234,16 +11281,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -throat@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.2.tgz#51a3fbb5e11ae72e2cf74861ed5c8020f89f29fe" - integrity sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ== - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -11254,13 +11291,6 @@ tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.2.0.tgz#a1141f86b672a9148c72e978a19a73b9b94a15a9" integrity sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg== -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - tmp@^0.2.1: version "0.2.3" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" @@ -11288,7 +11318,7 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tough-cookie@^4.0.0: +tough-cookie@^4.1.2, tough-cookie@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.4.tgz#945f1461b45b5a8c76821c33ea49c3ac192c1b36" integrity sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag== @@ -11305,18 +11335,13 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== dependencies: punycode "^2.1.1" -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - trim-trailing-lines@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" @@ -11408,10 +11433,10 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type-fest@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" - integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type-fest@^4.26.1: + version "4.33.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.33.0.tgz#2da0c135b9afa76cf8b18ecfd4f260ecd414a432" + integrity sha512-s6zVrxuyKbbAsSAD5ZPTB77q4YIdRctkTbJ2/Dqlinwz+8ooH2gd+YA7VA6Pa93KML9GockVvoxjZ2vHP+mu8g== type-is@~1.6.18: version "1.6.18" @@ -11512,6 +11537,11 @@ undici-types@~6.19.2: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + unherit@^1.0.4: version "1.1.3" resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" @@ -11747,14 +11777,14 @@ uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-to-istanbul@^8.1.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" - integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== +v8-to-istanbul@^9.0.1: + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: + "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" + convert-source-map "^2.0.0" vary@~1.1.2: version "1.1.2" @@ -11784,21 +11814,14 @@ vfile@^4.0.0, vfile@^4.2.1: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== dependencies: - xml-name-validator "^3.0.0" + xml-name-validator "^4.0.0" -walker@^1.0.7: +walker@^1.0.7, walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -11820,37 +11843,20 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== - dependencies: - defaults "^1.0.3" - web-namespaces@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== webpack-dev-middleware@^5.3.4: version "5.3.4" @@ -11971,30 +11977,30 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== dependencies: - iconv-lite "0.4.24" + iconv-lite "0.6.3" whatwg-fetch@^3.6.2: version "3.6.20" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" + tr46 "^3.0.0" + webidl-conversions "^7.0.0" whatwg-url@^7.0.0: version "7.1.0" @@ -12005,15 +12011,6 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -12252,7 +12249,7 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -wrap-ansi@^6.0.1: +wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== @@ -12285,20 +12282,23 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@^7.4.6: - version "7.5.10" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" - integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +write-file-atomic@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" -ws@^8.13.0: +ws@^8.11.0, ws@^8.13.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== xmlchars@^2.2.0: version "2.2.0" @@ -12330,30 +12330,12 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.3.0: +yargs@^17.3.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -12371,6 +12353,11 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yoctocolors-cjs@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" + integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== + zod@^3.11.6: version "3.22.3" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060"